Fix up first version and add Prettier and Docker
This commit is contained in:
parent
09a1919d36
commit
324050ee38
87 changed files with 2210 additions and 822 deletions
2
.dockerignore
Normal file
2
.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
dist/
|
||||
node_modules/
|
10
.prettierrc.mjs
Normal file
10
.prettierrc.mjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
/** @type {import("prettier").Config} */
|
||||
export default {
|
||||
trailingComma: "all",
|
||||
tabWidth: 2,
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
printWidth: 120,
|
||||
bracketSpacing: true,
|
||||
plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
|
||||
};
|
10
Dockerfile
Normal file
10
Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
|||
FROM node:lts AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM httpd:2.4 AS runtime
|
||||
COPY --from=build /app/dist /usr/local/apache2/htdocs/
|
||||
EXPOSE 80
|
48
README.md
48
README.md
|
@ -1,47 +1 @@
|
|||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal)
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.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.
|
||||
|
||||
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?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
# gallery.badmanners.xyz
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import markdownIntegration from '@astropub/md'
|
||||
import { defineConfig } from "astro/config";
|
||||
import tailwindIntegration from "@astrojs/tailwind";
|
||||
import markdownIntegration from "@astropub/md";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: 'https://gallery.badmanners.xyz',
|
||||
integrations: [
|
||||
tailwind({ applyBaseStyles: false }),
|
||||
markdownIntegration(),
|
||||
],
|
||||
site: "https://gallery.badmanners.xyz",
|
||||
integrations: [tailwindIntegration({ applyBaseStyles: false }), markdownIntegration()],
|
||||
markdown: {
|
||||
smartypants: false,
|
||||
},
|
||||
redirects: {
|
||||
'/stories': '/stories/1',
|
||||
"/stories": "/stories/1",
|
||||
},
|
||||
});
|
||||
|
|
143
package-lock.json
generated
143
package-lock.json
generated
|
@ -12,6 +12,7 @@
|
|||
"@astrojs/rss": "^4.0.5",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@astropub/md": "^0.4.0",
|
||||
"@fontsource-variable/noto-sans": "^5.0.4",
|
||||
"@fontsource-variable/noto-serif": "^5.0.5",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"astro": "^4.5.4",
|
||||
|
@ -19,6 +20,11 @@
|
|||
"github-slugger": "^2.0.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-astro": "^0.13.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
|
@ -908,6 +914,11 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource-variable/noto-sans": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-sans/-/noto-sans-5.0.4.tgz",
|
||||
"integrity": "sha512-UCBloRRF3VDWrzUHo1hRr4DOi30Yxqcyogy+oxRLEzlUe59+VWu1uVLAzl5hEszK7HEwvuavyaceNuRTQAbRZg=="
|
||||
},
|
||||
"node_modules/@fontsource-variable/noto-serif": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/noto-serif/-/noto-serif-5.0.5.tgz",
|
||||
|
@ -5082,6 +5093,114 @@
|
|||
"node": ">=8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-astro": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.13.0.tgz",
|
||||
"integrity": "sha512-5HrJNnPmZqTUNoA97zn4gNQv9BgVhv+et03314WpQ9H9N8m2L9OSV798olwmG2YLXPl1iSstlJCR1zB3x5xG4g==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^1.5.5",
|
||||
"prettier": "^3.0.0",
|
||||
"sass-formatter": "^0.7.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-plugin-astro/node_modules/@astrojs/compiler": {
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-1.8.2.tgz",
|
||||
"integrity": "sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/prettier-plugin-tailwindcss": {
|
||||
"version": "0.5.12",
|
||||
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.12.tgz",
|
||||
"integrity": "sha512-o74kiDBVE73oHW+pdkFSluHBL3cYEvru5YgEqNkBMFF7Cjv+w1vI565lTlfoJT4VLWDe0FMtZ7FkE/7a4pMXSQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "*",
|
||||
"@prettier/plugin-pug": "*",
|
||||
"@shopify/prettier-plugin-liquid": "*",
|
||||
"@trivago/prettier-plugin-sort-imports": "*",
|
||||
"prettier": "^3.0",
|
||||
"prettier-plugin-astro": "*",
|
||||
"prettier-plugin-css-order": "*",
|
||||
"prettier-plugin-import-sort": "*",
|
||||
"prettier-plugin-jsdoc": "*",
|
||||
"prettier-plugin-marko": "*",
|
||||
"prettier-plugin-organize-attributes": "*",
|
||||
"prettier-plugin-organize-imports": "*",
|
||||
"prettier-plugin-sort-imports": "*",
|
||||
"prettier-plugin-style-order": "*",
|
||||
"prettier-plugin-svelte": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@ianvs/prettier-plugin-sort-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"@prettier/plugin-pug": {
|
||||
"optional": true
|
||||
},
|
||||
"@shopify/prettier-plugin-liquid": {
|
||||
"optional": true
|
||||
},
|
||||
"@trivago/prettier-plugin-sort-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-astro": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-css-order": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-import-sort": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-jsdoc": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-marko": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-organize-attributes": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-organize-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-sort-imports": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-style-order": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-svelte": {
|
||||
"optional": true
|
||||
},
|
||||
"prettier-plugin-twig-melody": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
|
@ -5824,6 +5943,12 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/s.color": {
|
||||
"version": "0.0.15",
|
||||
"resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
|
||||
"integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
@ -5843,6 +5968,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/sass-formatter": {
|
||||
"version": "0.7.9",
|
||||
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
|
||||
"integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"suf-log": "^2.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/section-matter": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
|
||||
|
@ -6225,6 +6359,15 @@
|
|||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/suf-log": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
|
||||
"integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"s.color": "0.0.15"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
|
|
|
@ -7,13 +7,15 @@
|
|||
"start": "astro dev",
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
"astro": "astro",
|
||||
"prettier": "prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.5.9",
|
||||
"@astrojs/rss": "^4.0.5",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@astropub/md": "^0.4.0",
|
||||
"@fontsource-variable/noto-sans": "^5.0.4",
|
||||
"@fontsource-variable/noto-serif": "^5.0.5",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"astro": "^4.5.4",
|
||||
|
@ -21,5 +23,10 @@
|
|||
"github-slugger": "^2.0.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-astro": "^0.13.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.12"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,56 @@
|
|||
---
|
||||
import { type Lang, type User } from '../content/config'
|
||||
import UserComponent from './UserComponent.astro'
|
||||
import { type Lang, type User } from "../content/config";
|
||||
import UserComponent from "./UserComponent.astro";
|
||||
|
||||
type Props = {
|
||||
authors: User | User[]
|
||||
lang: Lang
|
||||
}
|
||||
authors: User | User[];
|
||||
lang: Lang;
|
||||
};
|
||||
|
||||
const { authors, lang } = Astro.props
|
||||
const authorsArray = [authors].flat()
|
||||
const { authors, lang } = Astro.props;
|
||||
const authorsArray = [authors].flat();
|
||||
---
|
||||
|
||||
{authorsArray.length > 0 && <p class="font-light">
|
||||
{lang === 'eng' && (
|
||||
(authorsArray.length > 2) ?
|
||||
<span>
|
||||
by {authorsArray.slice(0, authorsArray.length - 1).map(author => (<UserComponent user={author} /><span>, </span>))}and <UserComponent user={authorsArray[authorsArray.length - 1]} />
|
||||
</span> :
|
||||
(authorsArray.length > 1) ?
|
||||
<span>
|
||||
by <UserComponent user={authorsArray[0]} /> and <UserComponent user={authorsArray[1]} />
|
||||
</span> :
|
||||
<span>
|
||||
by <UserComponent user={authorsArray[0]} />
|
||||
</span>
|
||||
)}
|
||||
{lang === 'tok' && (
|
||||
(authorsArray.length > 1) ?
|
||||
<span>
|
||||
lipu nu li tan ni: {authorsArray.slice(0, authorsArray.length - 1).map(author => <UserComponent user={author} /><span> en </span>)}<UserComponent user={authorsArray[authorsArray.length - 1]} />
|
||||
</span> :
|
||||
<span>
|
||||
lipu nu li tan <UserComponent user={authorsArray[0]} />
|
||||
</span>
|
||||
)}
|
||||
</p>}
|
||||
{
|
||||
authorsArray.length > 0 ? (
|
||||
<p class="font-light">
|
||||
{lang === "eng" &&
|
||||
(authorsArray.length > 2 ? (
|
||||
<span>
|
||||
by{" "}
|
||||
{authorsArray.slice(0, authorsArray.length - 1).map((author) => (
|
||||
<Fragment>
|
||||
<UserComponent user={author} />,
|
||||
</Fragment>
|
||||
))}
|
||||
and <UserComponent user={authorsArray[authorsArray.length - 1]} />
|
||||
</span>
|
||||
) : authorsArray.length > 1 ? (
|
||||
<span>
|
||||
by <UserComponent user={authorsArray[0]} /> and <UserComponent user={authorsArray[1]} />
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
by <UserComponent user={authorsArray[0]} />
|
||||
</span>
|
||||
))}
|
||||
{lang === "tok" &&
|
||||
(authorsArray.length > 1 ? (
|
||||
<span>
|
||||
lipu ni li tan ni:{" "}
|
||||
{authorsArray.slice(0, authorsArray.length - 1).map((author) => (
|
||||
<Fragment>
|
||||
<UserComponent user={author} />
|
||||
{" en "}
|
||||
</Fragment>
|
||||
))}
|
||||
<UserComponent user={authorsArray[authorsArray.length - 1]} />
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
lipu ni li tan <UserComponent user={authorsArray[0]} />
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -1,25 +1,27 @@
|
|||
---
|
||||
import { type Lang, type User } from '../content/config'
|
||||
import UserComponent from './UserComponent.astro'
|
||||
import { type Lang, type User } from "../content/config";
|
||||
import UserComponent from "./UserComponent.astro";
|
||||
|
||||
type Props = {
|
||||
copyrightedCharacters?: Record<string, User>
|
||||
lang: Lang
|
||||
}
|
||||
copyrightedCharacters?: Record<string, User>;
|
||||
lang: Lang;
|
||||
};
|
||||
|
||||
const { copyrightedCharacters, lang } = Astro.props
|
||||
const { copyrightedCharacters, lang } = Astro.props;
|
||||
---
|
||||
|
||||
{copyrightedCharacters &&
|
||||
<section id="copyrighted-characters">
|
||||
{lang === 'eng' && (
|
||||
<ul>
|
||||
{Object.entries(copyrightedCharacters).map(([character, user]) =>
|
||||
<li>
|
||||
{character} is © <UserComponent user={user} />
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
{
|
||||
copyrightedCharacters ? (
|
||||
<section id="copyrighted-characters">
|
||||
{lang === "eng" && (
|
||||
<ul>
|
||||
{Object.entries(copyrightedCharacters).map(([character, user]) => (
|
||||
<li>
|
||||
{character} is © <UserComponent user={user} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
) : null
|
||||
}
|
||||
|
|
|
@ -1,11 +1,32 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a class="hover:underline focus:underline hover:text-green-800 focus:text-green-800 dark:hover:text-bm-300 dark:focus:text-bm-300" href="/">Home</a></li>
|
||||
<li><a class="hover:underline focus:underline hover:text-green-800 focus:text-green-800 dark:hover:text-bm-300 dark:focus:text-bm-300" href="/stories">Stories</a></li>
|
||||
<li><a class="hover:underline focus:underline hover:text-green-800 focus:text-green-800 dark:hover:text-bm-300 dark:focus:text-bm-300" href="/games">Games</a></li>
|
||||
<li><a class="hover:underline focus:underline hover:text-green-800 focus:text-green-800 dark:hover:text-bm-300 dark:focus:text-bm-300" href="/tags">Tags</a></li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/">Home</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/stories">Stories</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/games">Games</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="hover:text-green-800 hover:underline focus:text-green-800 focus:underline dark:hover:text-bm-300 dark:focus:text-bm-300"
|
||||
href="/tags">Tags</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
---
|
||||
|
||||
---
|
||||
<div class="prose prose-story max-w-none dark:prose-invert">
|
||||
|
||||
<div class="prose-a:text-link prose prose-story max-w-none dark:prose-invert">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
|
56
src/components/Scripts.astro
Normal file
56
src/components/Scripts.astro
Normal file
|
@ -0,0 +1,56 @@
|
|||
---
|
||||
|
||||
---
|
||||
|
||||
<script is:inline>
|
||||
/* Color scheme toggle */
|
||||
(() => {
|
||||
var colorScheme = localStorage.getItem("colorScheme");
|
||||
if (colorScheme == null) {
|
||||
localStorage.setItem("colorScheme", "auto");
|
||||
} else if (colorScheme == "auto") {
|
||||
colorScheme = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||
}
|
||||
|
||||
const bodyClassList = document.querySelector("body").classList;
|
||||
if (colorScheme === "dark") {
|
||||
bodyClassList.add("dark");
|
||||
}
|
||||
|
||||
function toggleColorScheme() {
|
||||
if (colorScheme === "dark") {
|
||||
colorScheme = "light";
|
||||
bodyClassList.remove("dark");
|
||||
} else {
|
||||
colorScheme = "dark";
|
||||
bodyClassList.add("dark");
|
||||
}
|
||||
localStorage.setItem("colorScheme", colorScheme);
|
||||
}
|
||||
|
||||
const buttonDarkMode = document.querySelector("#button-dark-mode");
|
||||
if (buttonDarkMode) {
|
||||
buttonDarkMode.addEventListener("click", toggleColorScheme);
|
||||
}
|
||||
})();
|
||||
|
||||
/* Age verification */
|
||||
(() => {
|
||||
if (window.location.pathname === "/age-verification") {
|
||||
document.querySelector("#age-verification-reject").addEventListener("click", () => {
|
||||
window.location.href = "about:blank";
|
||||
});
|
||||
document.querySelector("#age-verification-accept").addEventListener("click", () => {
|
||||
localStorage.setItem("ageVerified", "true");
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
window.location.href = decodeURIComponent(params.get("redirect") || "/");
|
||||
});
|
||||
} else {
|
||||
const ageVerified = localStorage.getItem("ageVerified");
|
||||
if (ageVerified !== "true") {
|
||||
localStorage.setItem("ageVerified", "false");
|
||||
window.location.href = `/age-verification?redirect=${encodeURIComponent(window.location.href)}`;
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
|
@ -1,11 +1,21 @@
|
|||
---
|
||||
import { type User } from '../content/config'
|
||||
import { type User } from "../content/config";
|
||||
|
||||
type Props = {
|
||||
user: User
|
||||
}
|
||||
user: User;
|
||||
};
|
||||
|
||||
const { user } = Astro.props
|
||||
const { user } = Astro.props;
|
||||
---
|
||||
|
||||
{typeof user === 'string' ? <span>{user}</span> : Object.entries(user).map(([k, v]) => <a href={v} class="text-link underline" target="_blank"><span>{k}</span></a>)[0]}
|
||||
{
|
||||
typeof user === "string" ? (
|
||||
<span>{user}</span>
|
||||
) : (
|
||||
Object.entries(user).map(([k, v]) => (
|
||||
<a href={v} class="text-link underline" target="_blank">
|
||||
<span>{k}</span>
|
||||
</a>
|
||||
))[0]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,64 +1,66 @@
|
|||
import { defineCollection, reference, z } from 'astro:content';
|
||||
import { defineCollection, reference, z } from "astro:content";
|
||||
|
||||
const user = z.union([z.string(), z.record(z.string().url())])
|
||||
const user = z.union([z.string(), z.record(z.string().url())]);
|
||||
|
||||
const lang = z.enum(['eng', 'tok']).default('eng')
|
||||
const lang = z.enum(["eng", "tok"]).default("eng");
|
||||
|
||||
export type User = z.output<typeof user>
|
||||
export type Lang = z.output<typeof lang>
|
||||
export type User = z.output<typeof user>;
|
||||
export type Lang = z.output<typeof lang>;
|
||||
|
||||
const storiesCollection = defineCollection({
|
||||
type: 'content',
|
||||
schema: ({ image }) => z.object({
|
||||
title: z.string(),
|
||||
shortTitle: z.string().optional(),
|
||||
pubDate: z.date(),
|
||||
isDraft: z.boolean().default(false),
|
||||
authors: z.union([user, z.array(user)]).default('Bad Manners'),
|
||||
wordCount: z.number().int(),
|
||||
contentWarning: z.string(),
|
||||
description: z.string(),
|
||||
descriptionPlaintext: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
thumbnail: image().optional(),
|
||||
thumbnailWidth: z.number().int().optional(),
|
||||
thumbnailHeight: z.number().int().optional(),
|
||||
tags: z.array(z.string()),
|
||||
series: z.record(z.string(), z.string()).optional(),
|
||||
commissioner: user.optional(),
|
||||
requester: user.optional(),
|
||||
copyrightedCharacters: z.record(z.string(), user).optional(),
|
||||
lang,
|
||||
prev: reference('stories').nullable().optional(),
|
||||
next: reference('stories').nullable().optional(),
|
||||
relatedStories: z.array(reference('stories')).optional(),
|
||||
relatedGames: z.array(reference('games')).optional(),
|
||||
}),
|
||||
})
|
||||
type: "content",
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
shortTitle: z.string().optional(),
|
||||
pubDate: z.date(),
|
||||
isDraft: z.boolean().default(false),
|
||||
authors: z.union([user, z.array(user)]).default("Bad Manners"),
|
||||
wordCount: z.number().int(),
|
||||
contentWarning: z.string(),
|
||||
description: z.string(),
|
||||
descriptionPlaintext: z.string().optional(),
|
||||
summary: z.string().optional(),
|
||||
thumbnail: image().optional(),
|
||||
thumbnailWidth: z.number().int().optional(),
|
||||
thumbnailHeight: z.number().int().optional(),
|
||||
tags: z.array(z.string()),
|
||||
series: z.record(z.string(), z.string()).optional(),
|
||||
commissioner: user.optional(),
|
||||
requester: user.optional(),
|
||||
copyrightedCharacters: z.record(z.string(), user).optional(),
|
||||
lang,
|
||||
prev: reference("stories").nullable().optional(),
|
||||
next: reference("stories").nullable().optional(),
|
||||
relatedStories: z.array(reference("stories")).optional(),
|
||||
relatedGames: z.array(reference("games")).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const gamesCollection = defineCollection({
|
||||
type: 'content',
|
||||
schema: ({ image }) => z.object({
|
||||
title: z.string(),
|
||||
pubDate: z.date(),
|
||||
isDraft: z.boolean().default(false),
|
||||
authors: z.union([user, z.array(user)]).default('Bad Manners'),
|
||||
contentWarning: z.string(),
|
||||
description: z.string(),
|
||||
descriptionPlaintext: z.string().optional(),
|
||||
thumbnail: image().optional(),
|
||||
thumbnailWidth: z.number().int().optional(),
|
||||
thumbnailHeight: z.number().int().optional(),
|
||||
tags: z.array(z.string()),
|
||||
series: z.record(z.string(), z.string()).optional(),
|
||||
copyrightedCharacters: z.record(z.string(), user).optional(),
|
||||
lang,
|
||||
relatedStories: z.array(reference('stories')).optional(),
|
||||
relatedGames: z.array(reference('games')).optional(),
|
||||
}),
|
||||
})
|
||||
type: "content",
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
pubDate: z.date(),
|
||||
isDraft: z.boolean().default(false),
|
||||
authors: z.union([user, z.array(user)]).default("Bad Manners"),
|
||||
contentWarning: z.string(),
|
||||
description: z.string(),
|
||||
descriptionPlaintext: z.string().optional(),
|
||||
thumbnail: image().optional(),
|
||||
thumbnailWidth: z.number().int().optional(),
|
||||
thumbnailHeight: z.number().int().optional(),
|
||||
tags: z.array(z.string()),
|
||||
series: z.record(z.string(), z.string()).optional(),
|
||||
copyrightedCharacters: z.record(z.string(), user).optional(),
|
||||
lang,
|
||||
relatedStories: z.array(reference("stories")).optional(),
|
||||
relatedGames: z.array(reference("games")).optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
stories: storiesCollection,
|
||||
games: gamesCollection,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ thumbnail: /src/assets/thumbnails/game_crossing_over_cover.png
|
|||
description: |
|
||||
[**Crossing Over**](https://bad-manners.itch.io/crossing-over) is the story of a soul and their journey towards the Hereafter. With the help of Marco, a soul ferrier, they will remember their past and fish unexpected objects out of the ethereal river.
|
||||
|
||||
This game was created all by myself on February 2024, as an entry for the [Strawberry Jam 8](https://itch.io/jam/strawberry-jam-8).
|
||||
This game was created all by myself on February 2024, as an entry for [Strawberry Jam 8](https://itch.io/jam/strawberry-jam-8).
|
||||
|
||||
### Features
|
||||
|
||||
|
@ -19,14 +19,25 @@ description: |
|
|||
descriptionPlaintext: >
|
||||
Crossing Over is the story of a soul and their journey towards the Hereafter. With the help of Marco, a soul ferrier, they will remember their past and fish unexpected objects out of the ethereal river.
|
||||
|
||||
This game was created all by myself on February 2024, as an entry for the Strawberry Jam 8.
|
||||
This game was created all by myself on February 2024, as an entry for Strawberry Jam 8.
|
||||
|
||||
Features:
|
||||
An hour-long linear narrative with branching dialogues;
|
||||
An original soundtrack with 9 exclusive songs;
|
||||
A challenging physics-based fishing minigame with scaling difficulty;
|
||||
And a special cutscene...
|
||||
tags: ['oral vore', 'anthro predator', 'willing predator', 'willing prey', 'male predator', 'non-binary prey', 'micro prey', 'soul vore', 'implied perma endo']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"male predator",
|
||||
"non-binary prey",
|
||||
"micro prey",
|
||||
"soul vore",
|
||||
"implied perma endo",
|
||||
]
|
||||
---
|
||||
|
||||
<iframe
|
||||
|
|
|
@ -10,7 +10,21 @@ description: |
|
|||
Cynthia accidentally ends up with what initially seems like a pain in the butt, but turns out to be the opposite.
|
||||
|
||||
First post of 2023. This was originally going to be another short in my latest compilation, but I felt that 1400 words didn't do it justice (especially with multiple scenes), so I ended up rewriting it!
|
||||
tags: ['anal vore', 'anthro predator', 'anthro prey', 'female predator', 'male prey', 'unwilling predator', 'willing predator', 'unwilling prey', 'willing prey', 'size difference', 'long-term endo', 'straight sex']
|
||||
tags:
|
||||
[
|
||||
"anal vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"unwilling predator",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"willing prey",
|
||||
"size difference",
|
||||
"long-term endo",
|
||||
"straight sex",
|
||||
]
|
||||
---
|
||||
|
||||
The okapi woke up with that familiar weight on her. Cynthia rubbed her eyes and yawned before carefully lowering her hands onto the spherical dome on her body below her breasts, rubbing its sides. Not too forcefully, just enough to appreciate how good it felt on her...and to get ready and lift it, in order to get up.
|
||||
|
|
|
@ -10,10 +10,30 @@ description: |
|
|||
A couple meets a new, kinky acquaintance at a party, who's more than happy to take control for them.
|
||||
|
||||
My first commission ever, with a very unique and kinky scenario. This has been lovely to work on! I hope you enjoy it too.
|
||||
tags: ['oral vore', 'anal vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'female prey', 'multiple prey', 'willing predator', 'willing prey', 'semi-willing prey', 'similar size', 'perma endo', 'straight sex', 'gay sex', 'hyper', 'netorare', 'commission']
|
||||
commissioner: {"Scion": "https://www.furaffinity.net/user/scionic"}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anal vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"semi-willing prey",
|
||||
"similar size",
|
||||
"perma endo",
|
||||
"straight sex",
|
||||
"gay sex",
|
||||
"hyper",
|
||||
"netorare",
|
||||
"commission",
|
||||
]
|
||||
commissioner: { "Scion": "https://www.furaffinity.net/user/scionic" }
|
||||
copyrightedCharacters:
|
||||
'': {"Scion": "https://www.furaffinity.net/user/scionic"}
|
||||
"": { "Scion": "https://www.furaffinity.net/user/scionic" }
|
||||
---
|
||||
|
||||
"A'ight, this place should be quiet enough." A resounding, confident manly voice boomed in the dark backroom. Music from a party kept playing far away, only the beats being discernible at this distance.
|
||||
|
|
|
@ -8,7 +8,18 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_3_annivoresary.png
|
||||
description: |
|
||||
Happy Vore Day! These two boyfriends certainly have been awaiting this date eagerly...
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'willing predator', 'willing prey', 'smaller predator', 'BDSM']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"smaller predator",
|
||||
"BDSM",
|
||||
]
|
||||
---
|
||||
|
||||
"Is this okay for you, hun?" The gray wolf asked. "They aren't too tight, are they?"
|
||||
|
|
|
@ -10,10 +10,28 @@ description: |
|
|||
Years after graduating, a whole week to meet with former high-school classmates can lead to certain developments. Some more salacious, and others completely unexpected. And for a former bully, it happens to be both.
|
||||
|
||||
My second commission for Scion! This one is entirely dedicated to long-term cock vore, which I've never written at this scale before. Although I haven't ever written anything at this scale; this is my longest standalone story to date by far, because I went way overboard on descriptions. Enjoy!
|
||||
tags: ['cock vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'multiple prey', 'willing predator', 'unwilling prey', 'similar size', 'perma endo', 'straight sex', 'gay sex', 'masturbation', 'hyper', 'netorare', 'commission']
|
||||
commissioner: {"Scion": "https://www.furaffinity.net/user/scionic"}
|
||||
tags:
|
||||
[
|
||||
"cock vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"similar size",
|
||||
"perma endo",
|
||||
"straight sex",
|
||||
"gay sex",
|
||||
"masturbation",
|
||||
"hyper",
|
||||
"netorare",
|
||||
"commission",
|
||||
]
|
||||
commissioner: { "Scion": "https://www.furaffinity.net/user/scionic" }
|
||||
copyrightedCharacters:
|
||||
'': {"Scion": "https://www.furaffinity.net/user/scionic"}
|
||||
"": { "Scion": "https://www.furaffinity.net/user/scionic" }
|
||||
---
|
||||
|
||||
## The first day
|
||||
|
|
|
@ -8,7 +8,21 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_16_big_haul.png
|
||||
description: |
|
||||
Coming back empty-handed from his latest heist, a space pirate ends up getting his hands on an even better haul.
|
||||
tags: ['unbirth', 'anthro predator', 'anthro prey', 'male predator', 'trans male predator', 'male prey', 'semi-willing predator', 'unwilling prey', 'size difference', 'long-term endo', 'gay sex', 'daddy play']
|
||||
tags:
|
||||
[
|
||||
"unbirth",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"trans male predator",
|
||||
"male prey",
|
||||
"semi-willing predator",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"long-term endo",
|
||||
"gay sex",
|
||||
"daddy play",
|
||||
]
|
||||
---
|
||||
|
||||
'RECALIBRATE PITCH. RECALIBRATE PITCH. RECALIBRATE ...'
|
||||
|
|
|
@ -16,10 +16,23 @@ descriptionPlaintext: >
|
|||
Beetle finds an odd-shaped friend deep in his work, and does what he does best: be a messy distraction.
|
||||
|
||||
This silly short was inspired by a wonderful commission that I got from Eli-Eternity and was supposed to go along with it, but this story doesn't even come close to doing it justice. Seriously, check out his piece if you haven't! It's turned out amazing, and he's done an incredible job, and I love it so much!
|
||||
tags: ['Sam Brendan', 'Beetle', 'anal vore', 'feral predator', 'anthro prey', 'male predator', 'male prey', 'willing predator', 'willing prey', 'similar size', 'gay sex']
|
||||
tags:
|
||||
[
|
||||
"Sam Brendan",
|
||||
"Beetle",
|
||||
"anal vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"similar size",
|
||||
"gay sex",
|
||||
]
|
||||
copyrightedCharacters:
|
||||
Beetle: Bad Manners
|
||||
'Sam Brendan': Bad Manners
|
||||
"Sam Brendan": Bad Manners
|
||||
---
|
||||
|
||||
"Staaaaan!" The gryphon's voice squawked across rooms, as Beetle happily strutted with his lion paws. "Where areeee yooooou~?" He sang his question, letting his wagging tail follow his orange body as he checked room after room. "Oh, there you are, Stan!"
|
||||
|
|
|
@ -18,7 +18,22 @@ descriptionPlaintext: >
|
|||
The return of Beetle! Swallowing multiple creatures, through multiple holes – myself included! –, has done little to quell his unusual appetites. But this time, he's set his eyes on you! Yes, you – a small dragon, who has been itching to try some bladder vore. An unusual request, but hey, I won't judge you!
|
||||
|
||||
...In all seriousness, I wanted to do something more daring for a PoV story. With a dragon, instead of a generic anthro, this time. And not to mention bladder vore, which is also a first for me. I'll still stick to third person narration for most of my stories, but let me know if PoV is something that you want to see more of.
|
||||
tags: ['Beetle', 'cock vore', 'feral predator', 'feral prey', 'male predator', 'ambiguous gender prey', 'willing predator', 'willing prey', 'bladder vore', 'size difference', 'long-term endo', 'point of view', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"Beetle",
|
||||
"cock vore",
|
||||
"feral predator",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"bladder vore",
|
||||
"size difference",
|
||||
"long-term endo",
|
||||
"point of view",
|
||||
"flash fiction",
|
||||
]
|
||||
copyrightedCharacters:
|
||||
Beetle: Bad Manners
|
||||
---
|
||||
|
|
|
@ -14,7 +14,18 @@ descriptionPlaintext: >
|
|||
A unique opportunity falls onto Muno's coils, but he's not too pleased about it...
|
||||
|
||||
I know everyone is already thinking about October and Halloween, but I still have a Snektember addition to share! This story follows Muno the snake after the events of "Ruffling Some Feathers", although you don't need to read that story to enjoy this one.
|
||||
tags: ['Muno', 'oral vore', 'feral predator', 'feral prey', 'male predator', 'ambiguous gender prey', 'semi-willing predator', 'semi-willing prey', 'size difference']
|
||||
tags:
|
||||
[
|
||||
"Muno",
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"semi-willing predator",
|
||||
"semi-willing prey",
|
||||
"size difference",
|
||||
]
|
||||
prev: ruffling-some-feathers
|
||||
copyrightedCharacters:
|
||||
Muno: Bad Manners
|
||||
|
|
|
@ -10,7 +10,19 @@ description: |
|
|||
With other plans for tonight, a drake unwittingly ends up becoming the main attraction for someone else... Not that he'd complain.
|
||||
|
||||
First time explicitly using PoV in a story, with first person in this one. I did try writing in second person before, but couldn't really do it in a manner where I was satisfied with the result. Third person narration still feels more natural to me, but this was still a fun experiment.
|
||||
tags: ['anal vore', 'feral predator', 'anthro prey', 'male predator', 'ambiguous gender prey', 'willing predator', 'willing prey', 'size difference', 'point of view', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"anal vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"size difference",
|
||||
"point of view",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
"Hey! Glad you came!" From the kitchen, I could hear my roommate answering the door. "Make yourself comfortable."
|
||||
|
|
|
@ -14,7 +14,19 @@ descriptionPlaintext: >
|
|||
Alqpra is willing to get his hands dirty in order to settle a score with Sonos... And a lot more than just his hands.
|
||||
|
||||
I was inspired by a certain dragon's writings, and wanted to try my hand at a few messier themes! Check out destinyisbad1's gallery if you enjoy stories like this.
|
||||
tags: ['oral vore', 'anthro predator', 'feral prey', 'male predator', 'male prey', 'willing predator', 'willing prey', 'unwilling prey', 'micro prey', 'messy stomach']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"unwilling prey",
|
||||
"micro prey",
|
||||
"messy stomach",
|
||||
]
|
||||
---
|
||||
|
||||
It was a beautiful morning in Mag Skeima, but frankly, why would it be any different in paradise? The sunless sky shone a wonderful blue over fluffy pink clouds, bringing light to the idyllic plains of the fairy lands. The sounds of a new day came from the critters of the grasslands, before the population's blathering was gradually added to the ambiance. Mag Skeima was inhabited by magical folk, some of which would travel between planes to visit the mortal world during the day, while others were perfectly content to spend every moment in this utopia.
|
||||
|
|
|
@ -8,7 +8,20 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_1_eggs_for_months.png
|
||||
description: |
|
||||
Avelia needs help with a curse, but things quickly take a weird turn. Egg shenanigans ensue.
|
||||
tags: ['sheath vore', 'feral predator', 'anthro prey', 'male predator', 'female prey', 'willing predator', 'unwilling prey', 'size difference', 'egg play', 'dubcon', 'straight sex']
|
||||
tags:
|
||||
[
|
||||
"sheath vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"egg play",
|
||||
"dubcon",
|
||||
"straight sex",
|
||||
]
|
||||
---
|
||||
|
||||
Avelia woke up to realize that it was that time of the month again, which she hated. The anthro crow recognized the weight and discomfort in her enormous belly. It was filled with eggs, but not normal ones. Each one of them was unfertilized, had a very hard shell, and worst of all, was about as big as a cantaloupe. Having one of them would have been an awful burden to bear, but right now, with a huge belly staring back at her, she must have been carrying six or seven of them!
|
||||
|
|
|
@ -10,7 +10,22 @@ description: |
|
|||
An innocuous message might have a more obscure purpose than it initially appears. But Sally is not the first one to make that mistake tonight...
|
||||
|
||||
This story was not a request by anyone! No one asked for this story to exist! ...In all seriousness, I just wanted to do something extra, themed around Vore Day, to go along with the uploads of the raffle requests. Enjoy!
|
||||
tags: ['oral vore', 'feral predator', 'anthro prey', 'female predator', 'female prey', 'multiple prey', 'willing predator', 'unwilling prey', 'semi-willing prey', 'willing prey', 'unbirth', 'implied full tour', 'lesbian sex']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"female predator",
|
||||
"female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"semi-willing prey",
|
||||
"willing prey",
|
||||
"unbirth",
|
||||
"implied full tour",
|
||||
"lesbian sex",
|
||||
]
|
||||
---
|
||||
|
||||
Sally stared back at her phone, checking the message that she'd gotten a while ago. This was definitely the right address...but something else still bothered her.
|
||||
|
|
|
@ -14,7 +14,28 @@ descriptionPlaintext: >
|
|||
One, fearful and furry; one, formidable and feathery; and one, fired-up for the follow-up. A threesome fatefully forced into a fantastical face-to-face.
|
||||
|
||||
So many ideas that I want to write...! I just haven't found a lot of motivation to write as much as before. But I'm still working, and haven't gone anywhere. At least I finally managed to churn out this one, featuring Beetle, one of my OCs! You might recognize him from one of the few things I actually uploaded this last month. Quite a fun personality to play with, along with my sona Sam and another OC, Muno - someone might remember that last name from one of my previous stories. All of my characters featured in stories are fun really, with a few that I wanna revisit or new ones that I make for each new setting. Just wish I had a better writing rhythm to put all those ideas to life like I used to, though! Managing to finish this story is a start, I guess.
|
||||
tags: ['Beetle', 'oral vore', 'cock vore', 'unbirth', 'feral predator', 'anthro prey', 'feral prey', 'male predator', 'male prey', 'female prey', 'multiple prey', 'willing predator', 'unwilling prey', 'size difference', 'micro prey', 'full tour', 'prey transfer', 'straight sex', 'gay sex']
|
||||
tags:
|
||||
[
|
||||
"Beetle",
|
||||
"oral vore",
|
||||
"cock vore",
|
||||
"unbirth",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"micro prey",
|
||||
"full tour",
|
||||
"prey transfer",
|
||||
"straight sex",
|
||||
"gay sex",
|
||||
]
|
||||
copyrightedCharacters:
|
||||
Beetle: Bad Manners
|
||||
---
|
||||
|
|
|
@ -8,7 +8,19 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_7_for_the_night.png
|
||||
description: |
|
||||
Sometimes, what a couple needs is a third-party to spice things up. And Brand will make sure that this night is unforgettable.
|
||||
tags: ['anal vore', 'anthro predator', 'anthro prey', 'male predator', 'female prey', 'willing predator', 'willing prey', 'same size', 'straight sex', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"anal vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"same size",
|
||||
"straight sex",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
"Hmmm, yeah...!" Kente moaned as he thrust into Miru's lubed ass slowly, picking up pace. The pony had wanted to do anal with his same-species girlfriend for quite a while, but she also wanted to do it to him. However, he didn't want to be on the receiving end, so those conversations never got anywhere... Until Miru brought the idea of doing a threesome.
|
||||
|
|
|
@ -10,7 +10,20 @@ description: |
|
|||
Jack celebrates a very special date with his boyfriend, but his secret might put Abdis in jeopardy...
|
||||
|
||||
So... yeah, managed to get a Halloween story done in time! Despite the craziness that this month was, for personal reasons. The start of November should be rocky too (also for personal reasons), but hopefully I can get into a better upload rhythm than 'once a month'. Maybe.
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'willing predator', 'willing prey', 'unwilling prey', 'size difference', 'regurgitation', 'transformation']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"regurgitation",
|
||||
"transformation",
|
||||
]
|
||||
---
|
||||
|
||||
Jack smiled to himself while sitting on the couch. His paws ran over the short fur on his black belly, focusing on the bulge jutting out from the midriff. They were slow as to not stir the small lynx inside. The badger had swallowed the wild cat a few hours ago, and both of them knew that he would be perfectly safe within. It was the first time that Jack had swallowed Abdis – but not the first that either of them had experienced vore.
|
||||
|
|
|
@ -10,7 +10,19 @@ description: |
|
|||
Sometimes, a date simply doesn't work out at all. And sometimes, they're both too petty and stubborn to simply give up on it.
|
||||
|
||||
My initial notes for this one were simply "hatefuck but it's vore". No wonder this was the longest one to finish out of this batch... But hey, at least it was a good excuse to write some cetacean vore!
|
||||
tags: ['unbirth', 'feral predator', 'feral prey', 'female predator', 'male prey', 'willing predator', 'unwilling prey', 'size difference', 'straight sex', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"unbirth",
|
||||
"feral predator",
|
||||
"feral prey",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"straight sex",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
"Is it in yet?" The orca shouted to the smaller cetacean near her folds. Mara's tone was annoyed; the dolphin just hadn't been a good choice of a mate.
|
||||
|
|
|
@ -8,7 +8,18 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_7_hungry_for_love.png
|
||||
description: |
|
||||
Aloe has been bitten by the Valentine's Day bug, though her plans for some kinky fun go through an unforeseen change.
|
||||
tags: ['oral vore', 'feral predator', 'anthro prey', 'male predator', 'female prey', 'willing predator', 'willing prey', 'size difference', 'straight sex']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"size difference",
|
||||
"straight sex",
|
||||
]
|
||||
---
|
||||
|
||||
Finally, Aloe thought, she was home for Valentine's! The gray American badger didn't have anyone special to spend the night with, but that didn't mean that she didn't have any plans! Perusing her phone, she would book a 'predator' through a specialized escort app, HungryLuv, for some sexy, kinky fun. It was a shame that this service was not as cheap as she'd have liked, but Aloe was always excited to treat herself to some of the hottest fucks she had ever had before being swallowed alive...and then being let out safely, of course.
|
||||
|
|
|
@ -8,7 +8,21 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_1_hyper_hunger.png
|
||||
description: |
|
||||
Fulfilling some hungers sometimes requires some additional, unwilling help.
|
||||
tags: ['oral vore', 'anthro predator', 'feral prey', 'male predator', 'ambiguous gender prey', 'willing predator', 'unwilling prey', 'size difference', 'messy stomach', 'hyper', 'masturbation', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"messy stomach",
|
||||
"hyper",
|
||||
"masturbation",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
He was sitting down naked at the table, patting his large belly, looking at all of that food that would be filling it very soon. A massive roasted turkey, a plate full of mashed potatoes, a big bowl of hot green bean soup, and a string of smoked sausages... Staring at everything he'd gotten for himself only made him hungrier, and his hyper cock twitched in anticipation.
|
||||
|
|
|
@ -8,7 +8,19 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_3_insistence_and_assistance.png
|
||||
description: |
|
||||
Some are predators, some are prey. And some want to keep things that way.
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'semi-willing predator', 'unwilling prey', 'same size', 'regurgitation', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"semi-willing predator",
|
||||
"unwilling prey",
|
||||
"same size",
|
||||
"regurgitation",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
"Yo, I'm home!" Cesar dropped his car key on the counter, announcing his arrival across the house to his brother Kevin. The two adult mice lived alone here. "Did you grab dinner or do I order something?" No response. He was probably in his room by now, playing that Super Slosh Bros. videogame alone or with one of his friends. Cesar walked over, knocked on the door, and called. "Bro...?" It sounded like someone was inside, but they didn't reply to him, so he cautiously turned the knob.
|
||||
|
|
|
@ -8,7 +8,26 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_6_lactation_action.png
|
||||
description: |
|
||||
Amy doesn't shirk from her friend's shrinking plans, hoping to milk as much fun as possible.
|
||||
tags: [ 'nipple vore', 'oral vore', 'anthro predator', 'anthro prey', 'male predator', 'female predator', 'male prey', 'female prey', 'multiple prey', 'willing predator', 'willing prey', 'asleep prey', 'micro prey', 'prey transfer', 'straight sex', 'sizeplay', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"nipple vore",
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"asleep prey",
|
||||
"micro prey",
|
||||
"prey transfer",
|
||||
"straight sex",
|
||||
"sizeplay",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
The ferret looked at the couple, still skeptical. "Are you really sure this is safe?"
|
||||
|
|
|
@ -8,7 +8,22 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_5_latest_catch.png
|
||||
description: |
|
||||
A predatory rabbit likes snakes...perhaps a bit too much.
|
||||
tags: ['cock vore', 'anthro predator', 'feral prey', 'non-binary predator', 'ambiguous gender prey', 'multiple prey', 'willing predator', 'semi-willing predator', 'willing prey', 'size difference', 'masturbation', 'perma endo', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"cock vore",
|
||||
"anthro predator",
|
||||
"feral prey",
|
||||
"non-binary predator",
|
||||
"ambiguous gender prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"semi-willing predator",
|
||||
"willing prey",
|
||||
"size difference",
|
||||
"masturbation",
|
||||
"perma endo",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
"Gotcha!" Rou celebrated their latest catch, holding the garter snake behind its head. The rust-red rabbit had set down a water plate in their backyard some time ago, and the slithering reptile was drinking from it when it was found by the lagomorph. It was thinner than his index finger and almost three feet long, the longest snake they had caught so far. Rou ran a finger over the black and yellow scales on its neck, and the wild animal was being calm about the whole situation, which was a good sign.
|
||||
|
|
|
@ -8,7 +8,20 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_2_never_too_late.png
|
||||
description: |
|
||||
After a night full of fun, Mirembe tries to squeeze out the last pleasures she can.
|
||||
tags: ['cock vore', 'anthro predator', 'anthro prey', 'male predator', 'female prey', 'asleep predator', 'willing prey', 'same size', 'masturbation', 'straight sex', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"cock vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"asleep predator",
|
||||
"willing prey",
|
||||
"same size",
|
||||
"masturbation",
|
||||
"straight sex",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
"Bye, guys!" Mirembe bade as she closed the door of her apartment. The aardwolf was still naked, enjoying the orgy in her house until the very last. And with those last guests gone, she could call it a night. It was a lot of fun, but the rooms were obviously a mess now; she brushed it off as a problem for future-Mirembe.
|
||||
|
|
|
@ -10,7 +10,18 @@ description: |
|
|||
Blume brings his new acquaintance to his secret retreat, escaping from both the frost and the past snapping at their heels.
|
||||
|
||||
I wasn't really sure if I wanted to finish this story, let alone post it, given the dark themes in this. But some friends convinced me that I still should share it anyway, so here it is, I guess. I'm posting a more uplifting story today as well, if you'd rather read that.
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'willing predator', 'semi-willing prey', 'same size', 'nudity']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"semi-willing prey",
|
||||
"same size",
|
||||
"nudity",
|
||||
]
|
||||
---
|
||||
|
||||
"Here we are," Blume smiled back to his labrador companion. The lion's fangs nearly shone white in his smiling lips. "This is what I wanted to show you."
|
||||
|
|
|
@ -10,10 +10,23 @@ description: |
|
|||
Under the pressure of following in his father's footsteps, Kuronosuke finds an unfortunate soul and offers his hospitality, whether she wants it or not.
|
||||
|
||||
This story was a request for a raffle winner, as part of an anniversary raffle I held!
|
||||
tags: ['chest maw vore', 'taur predator', 'anthro prey', 'male predator', 'trans male predator', 'female prey', 'willing predator', 'unwilling prey', 'size difference', 'perma endo', 'request']
|
||||
requester: {"Dee Lumeni": "https://aryion.com/g4/user/KeeperofLillies"}
|
||||
tags:
|
||||
[
|
||||
"chest maw vore",
|
||||
"taur predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"trans male predator",
|
||||
"female prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"size difference",
|
||||
"perma endo",
|
||||
"request",
|
||||
]
|
||||
requester: { "Dee Lumeni": "https://aryion.com/g4/user/KeeperofLillies" }
|
||||
copyrightedCharacters:
|
||||
Kuronosuke: {"Dee Lumeni": "https://aryion.com/g4/user/KeeperofLillies"}
|
||||
Kuronosuke: { "Dee Lumeni": "https://aryion.com/g4/user/KeeperofLillies" }
|
||||
---
|
||||
|
||||
"Come on, open up." Kuronosuke banged on the apartment's door again a few times. "I know you're in there. You don't wanna get on my bad side."
|
||||
|
|
|
@ -14,9 +14,22 @@ descriptionPlaintext: >
|
|||
You attend a show, unaware of how personal it can turn out...
|
||||
|
||||
This is a story based off of a YCH that I got from Helkumurrr which you should definitely check out. First piece with myself as pred, to complement my previous commission where I was prey – and also with a short (and hopefully fun) story to go along with it!
|
||||
tags: ['Sam Brendan', 'anthro predator', 'anthro prey', 'male predator', 'ambiguous gender prey', 'willing predator', 'semi-willing prey', 'oral vore', 'same size', 'point of view', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"Sam Brendan",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"willing predator",
|
||||
"semi-willing prey",
|
||||
"oral vore",
|
||||
"same size",
|
||||
"point of view",
|
||||
"flash fiction",
|
||||
]
|
||||
copyrightedCharacters:
|
||||
'Sam Brendan': Bad Manners
|
||||
"Sam Brendan": Bad Manners
|
||||
---
|
||||
|
||||
You make up your mind, and finally decide to visit this nightclub you've been told about. It was a 'one-in-a-lifetime experience', according to your friend. And everyone else who went there seems to agree with that sentiment. But honestly, what can be so interesting about people dancing around a pole? It isn't even a strip club performance... No matter. You already cleared up your agenda for tonight, and your interest is mildly piqued. You might as well visit the place. Alone, of course – you don't need any nosy witnesses; and if it turns out to be as boring as expected, you can just leave, no strings attached.
|
||||
|
|
|
@ -8,7 +8,24 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_2_pet_sit_saturday.png
|
||||
description: |
|
||||
It's Hepje's first time pet-sitting, and a huge zorgoia might be too much for the inexperienced anteater...
|
||||
tags: ['anal vore', 'anthro predator', 'feral predator', 'anthro prey', 'feral prey', 'female predator', 'female prey', 'multiple prey', 'willing predator', 'unwilling prey', 'willing prey', 'same size', 'object vore', 'prey transfer', 'masturbation']
|
||||
tags:
|
||||
[
|
||||
"anal vore",
|
||||
"anthro predator",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"female predator",
|
||||
"female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"willing prey",
|
||||
"same size",
|
||||
"object vore",
|
||||
"prey transfer",
|
||||
"masturbation",
|
||||
]
|
||||
---
|
||||
|
||||
"You are now approaching your destination."
|
||||
|
|
|
@ -10,7 +10,19 @@ description: |
|
|||
Mark finds himself on both ends of a monstrous hunt.
|
||||
|
||||
I've decided to start posting my short stories separately instead of doing compilations, but still bundle the uploads together. A bit more work to organize, but this should make it easier to find and read individual stories. Anyway! For this one, I wanted to take a fun trope from vore scenarios and flip it on its head. Though maybe I should've saved it for Halloween...
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'male predator', 'ambiguous gender prey', 'multiple prey', 'unwilling predator', 'willing prey', 'smaller predator', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"multiple prey",
|
||||
"unwilling predator",
|
||||
"willing prey",
|
||||
"smaller predator",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
A lupine howl pierced the full moon night, reaching Mark's large ears. The rabbit had been sprinting away from its source, trying to increase his distance from that terrifying call. It was bad enough that he'd decided to walk through the creepy forest trail in the darkness of night, but stumbling upon a werewolf?! How could things get any worse...?
|
||||
|
|
|
@ -14,7 +14,19 @@ descriptionPlaintext: >
|
|||
A sneaky snake switches to sudden snack, since he stopped Sovinne's sleep.
|
||||
|
||||
I'd created Muno for a different purpose, but since I wanted a story with snake prey, I thought, "why not just use him instead of making a new OC"? Also, I blame Mr.Arcaneus's artwork for putting me in a bird/snake mood!
|
||||
tags: ['Muno', 'oral vore', 'feral predator', 'feral prey', 'male predator', 'male prey', 'willing predator', 'semi-willing prey', 'size difference', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"Muno",
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"semi-willing prey",
|
||||
"size difference",
|
||||
"flash fiction",
|
||||
]
|
||||
next: bottom-of-the-food-chain
|
||||
copyrightedCharacters:
|
||||
Muno: Bad Manners
|
||||
|
|
|
@ -8,7 +8,21 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_8_spontaneous_sleepover.png
|
||||
description: |
|
||||
Akene is tired after a long trip, and her new acquaintance is happy to provide a comfy bed - despite their mutual friend's protestations.
|
||||
tags: ['tail vore', 'anthro predator', 'anthro prey', 'male predator', 'female prey', 'trans female prey', 'multiple prey', 'willing predator', 'willing prey', 'unwilling prey', 'same size', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"tail vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"trans female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"unwilling prey",
|
||||
"same size",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
Eulis couldn't contain her excitement. "I still can't believe you're here!" The pangolin took a bite off her burger, while grinning at the stoat sitting across the table. Akene was an online acquaintance of hers, and after years of only talking through a screen, she'd finally traveled all the way here for her vacation, to spend these next days with her friend, in the flesh.
|
||||
|
|
|
@ -9,9 +9,21 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_17_taken_in.png
|
||||
description: |
|
||||
A silly little story where I re-imagine my sona as a feral! It was a fun concept to play with. One more quick PoV story before I go back to the usual 3rd person narration style. I hope you still enjoy it!
|
||||
tags: ['Sam Brendan', 'feral predator', 'anthro prey', 'male predator', 'ambiguous gender prey', 'willing predator', 'unwilling prey', 'oral vore', 'full tour', 'point of view']
|
||||
tags:
|
||||
[
|
||||
"Sam Brendan",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"ambiguous gender prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"oral vore",
|
||||
"full tour",
|
||||
"point of view",
|
||||
]
|
||||
copyrightedCharacters:
|
||||
'Sam Brendan': Bad Manners
|
||||
"Sam Brendan": Bad Manners
|
||||
---
|
||||
|
||||
Clank! Shuffle! Crunch! The sounds outside are too loud to be stopped by the walls in your room, and you jolt awake.
|
||||
|
|
|
@ -10,7 +10,20 @@ description: |
|
|||
Cabira's plans for a blazing night go up in smoke.
|
||||
|
||||
Not much to say about this one in particular. Just a fun and short-ish 4/20 story, 'cause why the hell not. Probably the quickest pace I've worked on for the writing stage, getting around 5600 words done in 2 days. My usual process for standalone stories like this one goes something along the lines of: premise > research > summary > writing > editing. The last step, editing, typically inflates the final word count by 5% as I add more clarification or last minute ideas to certain sections, that's why that number doesn't match the actual word count. Research also took about 2 days, which is probably too much for a short story, but when it takes that long, it's normally a sign that I was having too much fun coming up with ideas, which was definitely the case here! Anyway, I doubt this is interesting to anyone other than me, but this part of the description now feels sufficiently padded out, so I'll leave it at that.
|
||||
tags: ['oral vore', 'feral predator', 'anthro prey', 'female predator', 'female prey', 'multiple prey', 'willing predator', 'unwilling prey', 'similar size', 'micro prey', 'object vore']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"female predator",
|
||||
"female prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"similar size",
|
||||
"micro prey",
|
||||
"object vore",
|
||||
]
|
||||
---
|
||||
|
||||
Cabira returned home from the guild, having turned in her bounty for the day. It was a simple task, slaying those pestilent slimes... They were dangerous to farmers' crops and animals, but clobbering them with her mace once would disintegrate the invasive, mindless creatures. Maybe twice, for some of the big ones. After collecting their hearts and turning them in as proof of the deed, Cabira made it out of the guild with her reward, a small pouch jingling with coins. Though she was more interested in her plans for the night than the silver she'd gotten for the menial task, the smiling moth-dragon let her long and fluffy tail swish happily behind her wings while she walked to the inn.
|
||||
|
|
|
@ -14,10 +14,28 @@ descriptionPlaintext: >
|
|||
After another semester in college, Yolk finds new opportunities to surpass his limits and grow closer to his friends than ever.
|
||||
|
||||
This commission is a sequel to the raffle request, Team Effort!
|
||||
tags: ['cock vore', 'anal vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'multiple prey', 'willing predator', 'willing prey', 'prey transfer', 'same size', 'long-term endo', 'hyper', 'inflation', 'gay sex', 'commission']
|
||||
commissioner: {'YolkMonkey': "https://furaffinity.net/user/Vampire101"}
|
||||
tags:
|
||||
[
|
||||
"cock vore",
|
||||
"anal vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"prey transfer",
|
||||
"same size",
|
||||
"long-term endo",
|
||||
"hyper",
|
||||
"inflation",
|
||||
"gay sex",
|
||||
"commission",
|
||||
]
|
||||
commissioner: { "YolkMonkey": "https://furaffinity.net/user/Vampire101" }
|
||||
copyrightedCharacters:
|
||||
Yolk: {'YolkMonkey': "https://furaffinity.net/user/Vampire101"}
|
||||
Yolk: { "YolkMonkey": "https://furaffinity.net/user/Vampire101" }
|
||||
prev: team-effort
|
||||
---
|
||||
|
||||
|
|
|
@ -10,10 +10,26 @@ description: |
|
|||
Yolk is ready to kick back and relax during the winter break, but his friends really want to keep him company.
|
||||
|
||||
This story was a request for a raffle winner, as part of an anniversary raffle I held!
|
||||
tags: ['cock vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'multiple prey', 'semi-willing predator', 'willing predator', 'willing prey', 'same size', 'hyper', 'inflation', 'gay sex', 'request']
|
||||
requester: {'YolkMonkey': "https://furaffinity.net/user/Vampire101"}
|
||||
tags:
|
||||
[
|
||||
"cock vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"multiple prey",
|
||||
"semi-willing predator",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"same size",
|
||||
"hyper",
|
||||
"inflation",
|
||||
"gay sex",
|
||||
"request",
|
||||
]
|
||||
requester: { "YolkMonkey": "https://furaffinity.net/user/Vampire101" }
|
||||
copyrightedCharacters:
|
||||
Yolk: {"YolkMonkey": "https://furaffinity.net/user/Vampire101"}
|
||||
Yolk: { "YolkMonkey": "https://furaffinity.net/user/Vampire101" }
|
||||
next: team-building
|
||||
---
|
||||
|
||||
|
|
|
@ -8,7 +8,20 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_ff_4_the_last_livestream.png
|
||||
description: |
|
||||
Happy Vore Day! These two boyfriends certainly have been awaiting this date eagerly...
|
||||
tags: ['unbirth', 'anthro predator', 'anthro prey', 'female predator', 'female prey', 'willing predator', 'willing prey', 'similar size', 'masturbation', 'lesbian sex', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"unbirth",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"female predator",
|
||||
"female prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"similar size",
|
||||
"masturbation",
|
||||
"lesbian sex",
|
||||
"flash fiction",
|
||||
]
|
||||
---
|
||||
|
||||
The video started, and the fennec fox, wearing a portable light and camera on a band on her forehead, smiled in the webcam's live feed. "Hey, guys! Samba here. Glad to see we already have so many people in chat today, even though this is gonna be my last livestream here. No game today, we're gonna see how much we can break the 'ToS' before we get banned from the platform."
|
||||
|
|
|
@ -15,8 +15,26 @@ descriptionPlaintext: >
|
|||
This is a bonus chapter of The Lost of the Marshes, set between Chapter 4 – Change and Chapter 5 – Intersection.
|
||||
|
||||
I had this idea while working on the next chapter, and wanted to try something a bit more experimental involving these characters. It's self-indulgent and loose canon, and something of a departure from my usual stories! I don't even know how to describe it... Well, whatever it is, it's certainly a mash of different thoughts and ideas that had been boiling in my head, and simmering them down into something legible was an interesting exercise. Hope it's an enjoyable read too!
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'feral prey', 'multiple prey', 'willing predator', 'willing prey', 'unwilling prey', 'non-binary predator', 'male predator', 'non-binary prey', 'male prey', 'nested vore', 'macro predator', 'size difference', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"unwilling prey",
|
||||
"non-binary predator",
|
||||
"male predator",
|
||||
"non-binary prey",
|
||||
"male prey",
|
||||
"nested vore",
|
||||
"macro predator",
|
||||
"size difference",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-4
|
||||
next: the-lost-of-the-marshes/chapter-5
|
||||
---
|
||||
|
|
|
@ -9,8 +9,18 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/tlotm_ch1.png
|
||||
description: |
|
||||
Hey! This is my first vore story, and the first story I've written in years. It's also the first chapter in a vore series, The Lost of the Marshes, focusing on non-fatal vore. I've always wanted to do a project like this and I finally decided to actually do it, so enjoy!
|
||||
tags: ['oral vore', 'feral predator', 'anthro prey', 'willing predator', 'willing prey', 'male predator', 'non-binary prey', 'macro predator']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"male predator",
|
||||
"non-binary prey",
|
||||
"macro predator",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
next: the-lost-of-the-marshes/chapter-2
|
||||
summary: |
|
||||
Quince, a genderless cat who cannot speak, separates from their taller scavenging partner Nikili, a male mongoose, in the Hamora Marshes. The cat finds a giant mute dark-blue dragon who, as gentle as he could be at his size, swallows Quince down into his crop, indulging in the cat's desires of being swallowed alive. Nikili finds the dragon, who lets Quince go, and Nikili runs away with Quince in his arms.
|
||||
|
@ -38,7 +48,7 @@ Quince made their way back to the small cluster of trees under which Nikili was
|
|||
|
||||
"Wait, are you saying we should split off here?", questioned the mongoose, prompting a nod from Quince. He brought his free hand to his chin. "I don't know... We may cover more ground, but we haven't been this far in before. If something happens to either one of us...", he thought out loud, and looked at the cat bearing a confident expression. He gave in, trusting Quince's instincts even as he had to call the shots. "Alright, we can do that. I'll head west and you'll head east. But in about an hour, we should meet back here."
|
||||
|
||||
His partner agreed, not taking a second thought before setting off to the direction that Nikili said. The mongoose chuckled and turned to his right, immediately plunging his right leg in a spot where there was no ground and partially falling. "Ugh", he groaned to himself as he lifted his leg from the water hole while sitting, and noticed his now-soaking skirt. "Off to a great start, Nikili."
|
||||
His partner agreed, not taking a second thought before setting off to the direction that Nikili said. The mongoose chuckled and turned to his right, immediately plunging his right leg in a spot where there was no ground and partially falling. "Ugh", he groaned to himself as he lifted his leg from the water hole while sitting, and noticed his now-soaking skirt. "Off to a great start, Nikili."
|
||||
|
||||
\*\*\*
|
||||
|
||||
|
|
|
@ -11,8 +11,25 @@ description: |
|
|||
Extreme circumstances lead the trio far away from Logas, where a life and the truth are both at stake.
|
||||
|
||||
It has been almost one year since I've posted the first chapter of this story. And this has been, by far, the longest and most challenging one to write. In fact, it's been almost four months between the ninth chapter and this one! I've ended up rewriting this chapter multiple times until I was satisfied, which is the main reason why it took so long, and I'm proud to finally present this. And even after about 100k words that I've written for the entire story, there's still more to be told! Hopefully, the next chapter won't take as long to put out, so stay tuned!
|
||||
tags: ['oral vore', 'anal vore', 'cock vore', 'slit vore', 'feral predator', 'anthro prey', 'male predator', 'male prey', 'female prey', 'non-binary prey', 'multiple prey', 'willing predator', 'willing prey', 'macro predator', 'gay sex']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anal vore",
|
||||
"cock vore",
|
||||
"slit vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"non-binary prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"macro predator",
|
||||
"gay sex",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-9
|
||||
next: the-lost-of-the-marshes/chapter-11
|
||||
summary: |
|
||||
|
|
|
@ -9,8 +9,25 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/tlotm_ch11.png
|
||||
description: |
|
||||
Given a respite from their pursuers while they stay in Saisa, the gang reflects on what they themselves seek to pursue.
|
||||
tags: ['oral vore', 'cock vore', 'feral predator', 'anthro prey', 'male predator', 'male prey', 'female prey', 'non-binary prey', 'multiple prey', 'willing predator', 'willing prey', 'macro predator', 'size difference', 'gay sex', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"cock vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"non-binary prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"macro predator",
|
||||
"size difference",
|
||||
"gay sex",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-10
|
||||
next: ~
|
||||
---
|
||||
|
|
|
@ -11,8 +11,21 @@ description: |
|
|||
Nikili and Quince get in some dragon-related antics once again, and their friendship gets tested.
|
||||
|
||||
If you are enjoying the story, there are a bunch of chapters coming after this, so stay tuned.
|
||||
tags: ['oral vore', 'feral predator', 'anthro prey', 'multiple prey', 'willing predator', 'willing prey', 'semi-willing prey', 'male predator', 'male prey', 'non-binary prey', 'macro predator']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"semi-willing prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"non-binary prey",
|
||||
"macro predator",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-1
|
||||
next: the-lost-of-the-marshes/chapter-3
|
||||
summary: |
|
||||
|
|
|
@ -11,8 +11,23 @@ description: |
|
|||
Nikili and Quince make their way back home, but one of them decides to bite off more than they can chew.
|
||||
|
||||
This chapter was a lot more work than the previous two, and not only in terms of word count (around 11k instead of 7k), but also research, moving stuff around, reviewing, etc. That said, it was very fun to work on, and I hope you enjoy it too. Chapter 4 is next, and it will probably take some extra work (initially it was going to be a 2-parter, so there's a lot to condense), but it'll come along eventually.
|
||||
tags: ['oral vore', 'macro predator', 'feral predator', 'anthro prey', 'multiple prey', 'willing predator', 'willing prey', 'male predator', 'non-binary predator', 'male prey', 'non-binary prey', 'size difference', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"macro predator",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"male predator",
|
||||
"non-binary predator",
|
||||
"male prey",
|
||||
"non-binary prey",
|
||||
"size difference",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-2
|
||||
next: the-lost-of-the-marshes/chapter-4
|
||||
summary: |
|
||||
|
|
|
@ -17,8 +17,23 @@ descriptionPlaintext: >
|
|||
How hard is it to sneak a giant dragon into a village? Quince and Nikili might learn the answer sooner rather than later...
|
||||
|
||||
This chapter took a lot longer to churn out than any other so far. It was still a bunch of fun to write, but each chapter so far has been a lot more work than the last... So before chapter 5 comes out (and don't worry, it will – there are plenty more chapters with these three still planned!), I might put out a few separate standalone stories that focus more on "hot sauce" and less on world-building, just so I can have a more regular posting schedule.
|
||||
tags: ['oral vore', 'anthro predator', 'feral predator', 'anthro prey', 'feral prey', 'willing predator', 'willing prey', 'male predator', 'non-binary prey', 'role reversal', 'size difference', 'nudity', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"male predator",
|
||||
"non-binary prey",
|
||||
"role reversal",
|
||||
"size difference",
|
||||
"nudity",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-3
|
||||
next: the-lost-of-the-marshes/chapter-5
|
||||
relatedStories:
|
||||
|
@ -405,7 +420,7 @@ Without any trouble this time, the kobold got undressed as Nikili relayed what s
|
|||
|
||||
Nikili didn't seem that confident, and Quince crossed their arms and gave him an annoyed trill. "But I'll try my best still." He wondered, after all, to hear what the dragon had to say, but there was something else... If he could really understand Quince somehow, then maybe the kobold could also translate for them? That curiosity alone was motivation enough for Nikili to do this.
|
||||
|
||||
Since Quince wouldn't be much help anyway, they chose to walk away to the pantry to grab a quick lunch – with the mongoose being busy and not cooking anything, some bread would have to do for today. Nikili turned to the kobold and spoke. "Okay, big guy. For starters, I wanna know how much you already can speak. What if you... Let's see... Can you try saying my name? Nikili." He looked into his blue slit eyes, and the tall lizard stared back at him. "Nikili," he repeated, awaiting his response.
|
||||
Since Quince wouldn't be much help anyway, they chose to walk away to the pantry to grab a quick lunch – with the mongoose being busy and not cooking anything, some bread would have to do for today. Nikili turned to the kobold and spoke. "Okay, big guy. For starters, I wanna know how much you already can speak. What if you... Let's see... Can you try saying my name? Nikili." He looked into his blue slit eyes, and the tall lizard stared back at him. "Nikili," he repeated, awaiting his response.
|
||||
|
||||
The transformed dragon was still weary, and slowly opened his mouth as he understood what the mongoose wanted. "Eeee..." was all that managed to come out.
|
||||
|
||||
|
|
|
@ -17,8 +17,19 @@ descriptionPlaintext: >
|
|||
In a distant place full of passing faces, our protagonists are forced to face their demons, both without and within.
|
||||
|
||||
This chapter turned out pretty heavy, both dialogue- and theme-wise... Regardless, I hope you enjoy it. I want to work on something short for the 8/8, and next will be chapter 6, which should hopefully be more positive and have more emphasis on vore.
|
||||
tags: ['oral vore', 'anthro predator', 'feral prey', 'willing predator', 'willing prey', 'female predator', 'male prey', 'size difference', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"feral prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"size difference",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-4
|
||||
next: the-lost-of-the-marshes/chapter-6
|
||||
relatedStories:
|
||||
|
|
|
@ -11,8 +11,25 @@ description: |
|
|||
Despite the chaos of their situations, the crew finds a brief reprieve in Kuir.
|
||||
|
||||
Let's pretend that I didn't disappear for two months while I sorted my priorities... Anyway, I'm back with another chapter, and TLoTM now broke over 50k words, which boggles my mind honestly. Either way, I'm not nearly done with these characters, so as usual, expect more chapters... eventually.
|
||||
tags: ['oral vore', 'unbirth', 'anthro predator', 'anthro prey', 'feral prey', 'female predator', 'male prey', 'non-binary prey', 'multiple prey', 'willing predator', 'willing prey', 'size difference', 'masturbation', 'gay sex', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"unbirth",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"non-binary prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"size difference",
|
||||
"masturbation",
|
||||
"gay sex",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-5
|
||||
next: the-lost-of-the-marshes/chapter-7
|
||||
summary: |
|
||||
|
|
|
@ -11,8 +11,28 @@ description: |
|
|||
Some mongooses, cats, and dragons just seem fated to attract trouble. And when the dust settles, emotions run high.
|
||||
|
||||
If Nikili makes Quince cry one more time, I will slap him.
|
||||
tags: ['oral vore', 'anal vore', 'feral predator', 'anthro predator', 'anthro prey', 'feral prey', 'male predator', 'male prey', 'female prey', 'non-binary prey', 'multiple prey', 'willing predator', 'semi-willing predator', 'willing prey', 'macro predator', 'size difference', 'gay sex', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anal vore",
|
||||
"feral predator",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"non-binary prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"semi-willing predator",
|
||||
"willing prey",
|
||||
"macro predator",
|
||||
"size difference",
|
||||
"gay sex",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-6
|
||||
next: the-lost-of-the-marshes/chapter-8
|
||||
summary: |
|
||||
|
|
|
@ -9,8 +9,29 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/tlotm_ch8.png
|
||||
description: |
|
||||
Nurta is forced to confront the ghosts of her past, once and for all.
|
||||
tags: ['oral vore', 'anal vore', 'anthro predator', 'feral predator', 'anthro prey', 'feral prey', 'male predator', 'female predator', 'male prey', 'non-binary prey', 'multiple prey', 'willing predator', 'semi-willing predator', 'semi-willing prey', 'willing prey', 'nested vore', 'macro predator', 'size difference', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anal vore",
|
||||
"anthro predator",
|
||||
"feral predator",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"male predator",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"non-binary prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"semi-willing predator",
|
||||
"semi-willing prey",
|
||||
"willing prey",
|
||||
"nested vore",
|
||||
"macro predator",
|
||||
"size difference",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-7
|
||||
next: the-lost-of-the-marshes/chapter-9
|
||||
summary: |
|
||||
|
|
|
@ -9,8 +9,25 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/tlotm_ch9.png
|
||||
description: |
|
||||
A whole week away from Kaati causes some uncertainties among the recluse trio.
|
||||
tags: ['oral vore', 'slit vore', 'feral predator', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'non-binary prey', 'multiple prey', 'willing predator', 'willing prey', 'macro predator', 'size difference', 'gay sex', 'transformation']
|
||||
series: {'The Lost of the Marshes': '/stories/the-lost-of-the-marshes'}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"slit vore",
|
||||
"feral predator",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"non-binary prey",
|
||||
"multiple prey",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"macro predator",
|
||||
"size difference",
|
||||
"gay sex",
|
||||
"transformation",
|
||||
]
|
||||
series: { "The Lost of the Marshes": "/stories/the-lost-of-the-marshes" }
|
||||
prev: the-lost-of-the-marshes/chapter-8
|
||||
next: the-lost-of-the-marshes/chapter-10
|
||||
summary: |
|
||||
|
|
|
@ -10,7 +10,17 @@ description: |
|
|||
soweli Lijan li kama sona e ni: ma tomo li kama jo e tomo moku sin. taso... moku li seme?
|
||||
|
||||
mi pilin e ni: lipu ni li lipu pi moku musi nanpa wan kepeken toki pona. mi pali e lipu ni kepeken ala toki Inli tan seme? tan la, tenpo suno nanpa wan pi tenpo mun nanpa tu tu li lon. tenpo suno ni li musi. toki pona li musi kin. mi kepeken toki pona la, mi pilin musi! mi pali e lipu ni la, mi pilin pona. moku musi kepeken toki pona li musi!
|
||||
tags: ['oral vore', 'ambiguous predator', 'ambiguous prey', 'ambiguous gender predator', 'ambiguous gender prey', 'willing predator', 'unwilling prey', 'flash fiction']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"ambiguous predator",
|
||||
"ambiguous prey",
|
||||
"ambiguous gender predator",
|
||||
"ambiguous gender prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"flash fiction",
|
||||
]
|
||||
summary: |
|
||||
For those curious, here's a summary of the story in English:
|
||||
|
||||
|
|
|
@ -8,7 +8,19 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_13_trouble_sleeping.png
|
||||
description: |
|
||||
An aflutter sparrow can't get any rest, and seeks an unconventional refuge in his lupine crush.
|
||||
tags: ['unbirth', 'anthro predator', 'feral prey', 'female predator', 'male prey', 'asleep predator', 'willing prey', 'size difference', 'long-term endo', 'straight sex']
|
||||
tags:
|
||||
[
|
||||
"unbirth",
|
||||
"anthro predator",
|
||||
"feral prey",
|
||||
"female predator",
|
||||
"male prey",
|
||||
"asleep predator",
|
||||
"willing prey",
|
||||
"size difference",
|
||||
"long-term endo",
|
||||
"straight sex",
|
||||
]
|
||||
---
|
||||
|
||||
Cantaloupe couldn't sleep. Even puffing out his feathers didn't really help the sentient sparrow beat out the cold that came from the air conditioner. Or maybe something else was still keeping him awake. He looked around from his perch, city lights sneaking through the blinds to reveal most outlines in the larger room. His attention was turned to the anthro wolfess, sprawled out on her bed with her belly up.
|
||||
|
|
|
@ -10,11 +10,24 @@ description: |
|
|||
Avour is sent to investigate a disturbance, but his finicky strategy is thwarted by a fennec-y trickster.
|
||||
|
||||
This story was a request for a raffle winner, as part of an anniversary raffle I held!
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'male predator', 'male prey', 'willing predator', 'unwilling prey', 'willing prey', 'same size', 'long-term endo', 'request']
|
||||
requester: {"Avour Inden": "https://furaffinity.net/user/pppp0000"}
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"male predator",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"willing prey",
|
||||
"same size",
|
||||
"long-term endo",
|
||||
"request",
|
||||
]
|
||||
requester: { "Avour Inden": "https://furaffinity.net/user/pppp0000" }
|
||||
copyrightedCharacters:
|
||||
Avour: {"Avour Inden": "https://furaffinity.net/user/pppp0000"}
|
||||
Buster: {"Holi": "https://furaffinity.net/user/CinnamonStars"}
|
||||
Avour: { "Avour Inden": "https://furaffinity.net/user/pppp0000" }
|
||||
Buster: { "Holi": "https://furaffinity.net/user/CinnamonStars" }
|
||||
---
|
||||
|
||||
This wasn't Avour's usual life-or-death assignment, where he had to hunt down supernatural entities. The red panda was skilled in both combat and magic, and had made a name for himself by facing off against formidable foes. But this time, it was just a simple side job. He was on his way to some town that he had never heard of, where there had been several complaints about some sort of pest that kept stealing people's foods and wreaking havoc.
|
||||
|
|
|
@ -8,10 +8,36 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_comm_3_within_limits.png
|
||||
description: |
|
||||
Ushitora tries out her latest invention on herself, with a bunch of lewd and eager participants to help her out.
|
||||
tags: ['unbirth', 'cock vore', 'anthro predator', 'taur predator', 'anthro prey', 'human prey', 'female predator', 'male predator', 'female prey', 'male prey', 'multiple prey', 'nested vore', 'willing predator', 'willing prey', 'smaller predator', 'similar size', 'size difference', 'prey transfer', 'hyper', 'straight sex', 'gay sex', 'lesbian sex', 'orgy', 'commission']
|
||||
commissioner: {"Asof Yeun": "https://www.furaffinity.net/user/AsofYeun/"}
|
||||
tags:
|
||||
[
|
||||
"unbirth",
|
||||
"cock vore",
|
||||
"anthro predator",
|
||||
"taur predator",
|
||||
"anthro prey",
|
||||
"human prey",
|
||||
"female predator",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"male prey",
|
||||
"multiple prey",
|
||||
"nested vore",
|
||||
"willing predator",
|
||||
"willing prey",
|
||||
"smaller predator",
|
||||
"similar size",
|
||||
"size difference",
|
||||
"prey transfer",
|
||||
"hyper",
|
||||
"straight sex",
|
||||
"gay sex",
|
||||
"lesbian sex",
|
||||
"orgy",
|
||||
"commission",
|
||||
]
|
||||
commissioner: { "Asof Yeun": "https://www.furaffinity.net/user/AsofYeun/" }
|
||||
copyrightedCharacters:
|
||||
Ushitora: {"Asof Yeun": "https://www.furaffinity.net/user/AsofYeun/"}
|
||||
Ushitora: { "Asof Yeun": "https://www.furaffinity.net/user/AsofYeun/" }
|
||||
---
|
||||
|
||||
Tonight was going to be Ushitora's big night, but she showed no signs of nervousness. She had no reason to. The holographic readings on her bracelet were all nominal, of course, but she checked on them to make sure that all sensors were on. The technician didn't want to lose a single bit of data for her research.
|
||||
|
|
|
@ -8,7 +8,23 @@ contentWarning: >
|
|||
thumbnail: /src/assets/thumbnails/bm_4_you_re_home.png
|
||||
description: |
|
||||
Vesper finds himself perplexed with his home situation, but when a friend offers to help, things quickly spiral out of control.
|
||||
tags: ['oral vore', 'anthro predator', 'anthro prey', 'female predator', 'male predator', 'female prey', 'male prey', 'willing predator', 'unwilling prey', 'nested vore', 'similar size', 'implied perma endo', 'straight sex', 'masturbation']
|
||||
tags:
|
||||
[
|
||||
"oral vore",
|
||||
"anthro predator",
|
||||
"anthro prey",
|
||||
"female predator",
|
||||
"male predator",
|
||||
"female prey",
|
||||
"male prey",
|
||||
"willing predator",
|
||||
"unwilling prey",
|
||||
"nested vore",
|
||||
"similar size",
|
||||
"implied perma endo",
|
||||
"straight sex",
|
||||
"masturbation",
|
||||
]
|
||||
---
|
||||
|
||||
Vesper slumped down on the office cafeteria's chair, placing his tray on the table with enough force that his soda can toppled over. The black bearcat grabbed it before it could roll away as well, and carefully opened it. Unsurprisingly, the drink sizzled as a good portion was expelled from the can and onto the tray and food, and the binturong sighed.
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
---
|
||||
import '@fontsource-variable/noto-serif';
|
||||
import "../styles/base.css"
|
||||
const { pageTitle } = Astro.props
|
||||
import "@fontsource-variable/noto-sans";
|
||||
import "@fontsource-variable/noto-serif";
|
||||
import "../styles/base.css";
|
||||
import Scripts from "../components/Scripts.astro";
|
||||
const { pageTitle } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
@ -16,14 +18,11 @@ const { pageTitle } = Astro.props
|
|||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>{pageTitle || 'Gallery'} | Bad Manners</title>
|
||||
<title>{pageTitle || "Gallery"} | Bad Manners</title>
|
||||
<link rel="alternate" type="application/rss+xml" title="Gallery | Bad Manners" href={`${Astro.site}feed.xml`} />
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
<script>
|
||||
import '../scripts/dark-mode.ts'
|
||||
import '../scripts/verify-age.ts'
|
||||
</script>
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,52 +1,61 @@
|
|||
---
|
||||
import { Image } from 'astro:assets'
|
||||
import BaseLayout from './BaseLayout.astro'
|
||||
import Navigation from '../components/Navigation.astro'
|
||||
import logoBM from '../assets/images/logo_bm.png'
|
||||
import { Image } from "astro:assets";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import Navigation from "../components/Navigation.astro";
|
||||
import logoBM from "../assets/images/logo_bm.png";
|
||||
|
||||
const { pageTitle } = Astro.props
|
||||
const { pageTitle } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={pageTitle}>
|
||||
<div class="flex flex-col min-h-screen md:flex-row bg-stone-200 dark:bg-stone-800 text-stone-800 dark:text-stone-200">
|
||||
<div class="text-stone-900 dark:text-stone-100 flex static md:fixed md:inset-y-0 md:left-0 md:w-60 bg-bm-300 dark:bg-green-900 flex-col items-center text-center pt-10 md:pt-20 shadow-xl mb-4 md:mb-0">
|
||||
<Image loading="eager" src={logoBM} alt="Logo for Bad Manners" class="w-full max-w-48 shadow-md my-4 border-2 border-green-950 rounded-sm" />
|
||||
<span class="text-2xl font-semibold my-2">Bad Manners</span>
|
||||
<div
|
||||
class="flex min-h-screen flex-col bg-stone-200 text-stone-800 md:flex-row dark:bg-stone-800 dark:text-stone-200 print:bg-none"
|
||||
>
|
||||
<div
|
||||
class="static mb-4 flex flex-col items-center bg-bm-300 pt-10 text-center text-stone-900 shadow-xl md:fixed md:inset-y-0 md:left-0 md:mb-0 md:w-60 md:pt-20 dark:bg-green-900 dark:text-stone-100 print:bg-none print:shadow-none"
|
||||
>
|
||||
<Image
|
||||
loading="eager"
|
||||
src={logoBM}
|
||||
alt="Logo for Bad Manners"
|
||||
class="my-4 w-full max-w-48 rounded-sm border-2 border-green-950 shadow-md"
|
||||
/>
|
||||
<span class="my-2 text-2xl font-semibold">Bad Manners</span>
|
||||
<Navigation />
|
||||
<div class="pt-4 text-center text-xs text-black dark:text-white">
|
||||
<span>© 2024</span>
|
||||
<span>© 2024 | </span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-1 mt-2 pb-10">
|
||||
<a class="p-1 text-link" href="https://badmanners.xyz/" target="_blank" aria-label="Main website">
|
||||
<svg viewBox="0 0 576 512" class="fill-current w-6 h-6" aria-hidden="true">
|
||||
<path d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"/>
|
||||
<div class="mt-2 flex items-center gap-x-1 pb-10">
|
||||
<a class="text-link p-1" href="https://badmanners.xyz/" target="_blank" aria-label="Main website">
|
||||
<svg viewBox="0 0 576 512" class="h-6 w-6 fill-current" aria-hidden="true">
|
||||
<path
|
||||
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="p-1 text-link" href="/feed.xml" target="_blank" aria-label="RSS feed">
|
||||
<svg viewBox="0 0 448 512" class="fill-current w-6 h-6" aria-hidden="true">
|
||||
<path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/>
|
||||
<a class="text-link p-1" href="/feed.xml" target="_blank" aria-label="RSS feed">
|
||||
<svg viewBox="0 0 448 512" class="h-6 w-6 fill-current" aria-hidden="true">
|
||||
<path
|
||||
d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="p-1 text-link" href="/licenses.txt" target="_blank" aria-label="Licenses">
|
||||
<svg viewBox="0 0 640 512" class="fill-current w-6 h-6" aria-hidden="true">
|
||||
<path d="M384 32H512c17.7 0 32 14.3 32 32s-14.3 32-32 32H398.4c-5.2 25.8-22.9 47.1-46.4 57.3V448H512c17.7 0 32 14.3 32 32s-14.3 32-32 32H320 128c-17.7 0-32-14.3-32-32s14.3-32 32-32H288V153.3c-23.5-10.3-41.2-31.6-46.4-57.3H128c-17.7 0-32-14.3-32-32s14.3-32 32-32H256c14.6-19.4 37.8-32 64-32s49.4 12.6 64 32zm55.6 288H584.4L512 195.8 439.6 320zM512 416c-62.9 0-115.2-34-126-78.9c-2.6-11 1-22.3 6.7-32.1l95.2-163.2c5-8.6 14.2-13.8 24.1-13.8s19.1 5.3 24.1 13.8l95.2 163.2c5.7 9.8 9.3 21.1 6.7 32.1C627.2 382 574.9 416 512 416zM126.8 195.8L54.4 320H199.3L126.8 195.8zM.9 337.1c-2.6-11 1-22.3 6.7-32.1l95.2-163.2c5-8.6 14.2-13.8 24.1-13.8s19.1 5.3 24.1 13.8l95.2 163.2c5.7 9.8 9.3 21.1 6.7 32.1C242 382 189.7 416 126.8 416S11.7 382 .9 337.1z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<button id="button-dark-mode" class="p-1 text-link" aria-label="Toggle dark mode">
|
||||
<svg viewBox="0 0 512 512" class="fill-current w-6 h-6 hidden dark:block" aria-hidden="true">
|
||||
<button id="button-dark-mode" class="text-link p-1" aria-label="Toggle dark mode">
|
||||
<svg viewBox="0 0 512 512" class="hidden h-6 w-6 fill-current dark:block" aria-hidden="true">
|
||||
<path
|
||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
<svg viewBox="0 0 512 512" class="fill-current w-6 h-6 block dark:hidden" aria-hidden="true">
|
||||
<svg viewBox="0 0 512 512" class="block h-6 w-6 fill-current dark:hidden" aria-hidden="true">
|
||||
<path
|
||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<main class="grow-0 ml-0 md:ml-60 px-2 pb-12 md:px-4 pt-4 max-w-6xl">
|
||||
<main class="ml-0 max-w-6xl px-2 pb-12 pt-4 md:ml-60 md:px-4 print:pb-0">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -1,103 +1,166 @@
|
|||
---
|
||||
import { Image } from 'astro:assets'
|
||||
import { type CollectionEntry } from 'astro:content'
|
||||
import { Markdown } from '@astropub/md'
|
||||
import { format as formatDate } from 'date-fns'
|
||||
import { enUS as enUSLocale } from 'date-fns/locale/en-US'
|
||||
import { slug } from 'github-slugger'
|
||||
import BaseLayout from './BaseLayout.astro'
|
||||
import Authors from '../components/Authors.astro'
|
||||
import CopyrightedCharacters from '../components/CopyrightedCharacters.astro'
|
||||
import Prose from '../components/Prose.astro'
|
||||
import { Image } from "astro:assets";
|
||||
import { type CollectionEntry } from "astro:content";
|
||||
import { Markdown } from "@astropub/md";
|
||||
import { format as formatDate } from "date-fns";
|
||||
import { enUS as enUSLocale } from "date-fns/locale/en-US";
|
||||
import { slug } from "github-slugger";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import Authors from "../components/Authors.astro";
|
||||
import CopyrightedCharacters from "../components/CopyrightedCharacters.astro";
|
||||
import Prose from "../components/Prose.astro";
|
||||
|
||||
type Props = CollectionEntry<'games'>['data']
|
||||
type Props = CollectionEntry<"games">["data"];
|
||||
|
||||
const { props } = Astro
|
||||
const { props } = Astro;
|
||||
//const relatedStories = (await Promise.all((props.relatedStories || []).map(story => getEntry(story)))).filter(story => !story.data.isDraft)
|
||||
// const relatedGames = (await Promise.all((props.relatedGames || []).map(game => getEntry(game)))).filter(game => !game.data.isDraft)
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={props.title}>
|
||||
<div id="top" class="relative min-w-screen min-h-screen px-1 pt-20 pb-16 bg-radial from-bm-300 to-bm-600 dark:from-green-700 dark:to-green-950 print:bg-none">
|
||||
<div id="toolbox-buttons" aria-label="Toolbox" class="absolute top-0 h-[80vh] lg:inset-y-0 lg:h-full py-2 pl-2">
|
||||
<div class="sticky top-6 rounded-full bg-white dark:bg-black shadow-md py-1 px-1 flex print:hidden">
|
||||
<a href={props.series ? Object.values(props.series)[0] : "/games"} class="p-2 my-1 w-9 h-9 text-link" aria-label={`Return to ${props.series ? Object.keys(props.series)[0] : 'games'}`}>
|
||||
<div
|
||||
id="top"
|
||||
class="min-w-screen relative min-h-screen bg-radial from-bm-300 to-bm-600 px-1 pb-16 pt-20 dark:from-green-700 dark:to-green-950 print:bg-none"
|
||||
>
|
||||
<div id="toolbox-buttons" aria-label="Toolbox" class="absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full">
|
||||
<div class="sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden">
|
||||
<a
|
||||
href={props.series ? Object.values(props.series)[0] : "/games"}
|
||||
class="text-link my-1 h-9 w-9 p-2"
|
||||
aria-label={`Return to ${props.series ? Object.keys(props.series)[0] : "games"}`}
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="fill-current" aria-hidden="true">
|
||||
<path d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"/>
|
||||
<path
|
||||
d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#description" class="p-2 my-1 w-9 h-9 border-l border-stone-300 dark:border-stone-700 text-link" aria-label="Go to description">
|
||||
<a
|
||||
href="#description"
|
||||
class="text-link my-1 h-9 w-9 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||
aria-label="Go to description"
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="fill-current" aria-hidden="true">
|
||||
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>
|
||||
<path
|
||||
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<button id="button-dark-mode" class="p-2 my-1 w-9 h-9 border-l border-stone-300 dark:border-stone-700 text-link" aria-label="Toggle dark mode">
|
||||
<svg viewBox="0 0 512 512" class="fill-current hidden dark:block" aria-hidden="true">
|
||||
<button
|
||||
id="button-dark-mode"
|
||||
class="text-link my-1 h-9 w-9 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||
aria-label="Toggle dark mode"
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="hidden fill-current dark:block" aria-hidden="true">
|
||||
<path
|
||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
<svg viewBox="0 0 512 512" class="fill-current block dark:hidden" aria-hidden="true">
|
||||
<svg viewBox="0 0 512 512" class="block fill-current dark:hidden" aria-hidden="true">
|
||||
<path
|
||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<main class="mx-auto max-w-3xl px-2 pt-1 pb-4 rounded-lg shadow-sm bg-stone-50 dark:bg-stone-900 print:bg-none print:shadow-none print:max-w-full">
|
||||
<h1 id="game-title" class="px-2 pt-2 font-serif text-3xl font-semibold text-stone-800 dark:text-stone-100">{props.title}</h1>
|
||||
<section id="game-information" class="px-2 mt-1 space-y-2 font-serif italic text-stone-600 dark:text-stone-200 font-light">
|
||||
<main
|
||||
class="mx-auto max-w-3xl rounded-lg bg-stone-50 px-2 pb-4 pt-1 shadow-sm dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none"
|
||||
>
|
||||
<h1 id="game-title" class="px-2 pt-2 font-serif text-3xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.title}
|
||||
</h1>
|
||||
<section
|
||||
id="game-information"
|
||||
class="mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
|
||||
>
|
||||
<Authors authors={props.authors} lang={props.lang} />
|
||||
{props.isDraft ?
|
||||
<p id="draft-warning" class="text-center text-red-600 text-2xl not-italic font-semibold py-2">
|
||||
DRAFT VERSION – DO NOT REDISTRIBUTE
|
||||
</p> : null
|
||||
{
|
||||
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
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
<div id="content-warning">
|
||||
<p>{props.contentWarning}</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<hr class="mx-auto my-10 border-stone-400 dark:border-stone-600 w-[80%] max-w-xl" />
|
||||
{props.thumbnail && <Image loading="eager" src={props.thumbnail} alt={`Cover art for ${props.title}`} width={props.thumbnailWidth} height={props.thumbnailHeight} class="my-5 mx-auto shadow-lg" />}
|
||||
<hr class="mx-auto my-10 border-stone-400 dark:border-stone-600 w-[80%] max-w-xl" />
|
||||
<article id="story" class="font-serif pr-1">
|
||||
<hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
|
||||
{
|
||||
props.thumbnail && (
|
||||
<Image
|
||||
loading="eager"
|
||||
src={props.thumbnail}
|
||||
alt={`Cover art for ${props.title}`}
|
||||
width={props.thumbnailWidth}
|
||||
height={props.thumbnailHeight}
|
||||
class="mx-auto my-5 shadow-lg"
|
||||
/>
|
||||
)
|
||||
}
|
||||
<hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
|
||||
<article id="story" class="pr-1 font-serif">
|
||||
<Prose>
|
||||
<slot />
|
||||
</Prose>
|
||||
</article>
|
||||
<hr class="mx-auto mt-10 mb-6 border-stone-400 dark:border-stone-600 w-[80%] max-w-xl" />
|
||||
{props.isDraft ?
|
||||
<p id="draft-warning-bottom" class="text-center font-serif text-red-600 text-2xl not-italic font-semibold py-2">
|
||||
DRAFT VERSION – DO NOT REDISTRIBUTE
|
||||
</p> :
|
||||
<p id="publish-date" class="px-2 mt-2 text-center font-serif italic text-stone-600 dark:text-stone-200" aria-label="Publish date" aria-description={formatDate(props.pubDate, 'MMMM do, yyyy', { locale: enUSLocale })}>
|
||||
{formatDate(props.pubDate, 'yyyy-MM-dd')}
|
||||
</p>
|
||||
<hr class="mx-auto mb-6 mt-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
|
||||
{
|
||||
props.isDraft ? (
|
||||
<p
|
||||
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
|
||||
</p>
|
||||
) : (
|
||||
<p
|
||||
id="publish-date"
|
||||
class="mt-2 px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
|
||||
aria-label="Publish date"
|
||||
aria-description={formatDate(props.pubDate, "MMMM do, yyyy", { locale: enUSLocale })}
|
||||
>
|
||||
{formatDate(props.pubDate, "yyyy-MM-dd")}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<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</h2>
|
||||
<h2 id="title-description" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
Description
|
||||
</h2>
|
||||
<Prose>
|
||||
<Markdown of={props.description} />
|
||||
<CopyrightedCharacters copyrightedCharacters={props.copyrightedCharacters} lang={props.lang} />
|
||||
</Prose>
|
||||
</section>
|
||||
<div class="text-right pr-3 print:hidden">
|
||||
<a href="#top" class="text-link inline-flex items-center underline"><svg class="w-6 h-6 mr-1 fill-current" viewBox="0 0 384 512" aria-hidden="true"><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"/></svg><span>To top</span></a>
|
||||
<div class="pr-3 text-right print:hidden">
|
||||
<a href="#top" class="text-link inline-flex items-center underline"
|
||||
><svg class="mr-1 h-6 w-6 fill-current" viewBox="0 0 384 512" aria-hidden="true"
|
||||
><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
|
||||
>
|
||||
</div>
|
||||
<section id="tags" aria-describedby="title-tags" class="my-5">
|
||||
<h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">Tags</h2>
|
||||
<ul class="px-2 flex flex-wrap gap-x-2 gap-y-2">
|
||||
{ props.tags.map(tag =>
|
||||
<li class="text-sm rounded-full shadow-sm px-3 py-1 bg-bm-300 text-black dark:bg-bm-600 dark:text-white print:bg-none"><a class="focus:underline hover:underline" href={`/tags/${slug(tag)}`}>{tag}</a></li>
|
||||
) }
|
||||
<ul class="flex flex-wrap gap-x-2 gap-y-2 px-2">
|
||||
{
|
||||
props.tags.map((tag) => (
|
||||
<li class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm dark:bg-bm-600 dark:text-white print:bg-none">
|
||||
<a class="hover:underline focus:underline" href={`/tags/${slug(tag)}`}>
|
||||
{tag}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
<div class="pt-6 text-center text-xs text-black dark:text-white">
|
||||
<span>© {formatDate(props.pubDate, 'yyyy')} | </span>
|
||||
<a class="focus:underline hover:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||
<span>© {formatDate(props.pubDate, "yyyy")} | </span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
|
|
|
@ -1,171 +1,287 @@
|
|||
---
|
||||
import { Image } from 'astro:assets'
|
||||
import { getEntry, type CollectionEntry } from 'astro:content'
|
||||
import { Markdown } from '@astropub/md'
|
||||
import { format as formatDate } from 'date-fns'
|
||||
import { enUS as enUSLocale } from 'date-fns/locale/en-US'
|
||||
import { slug } from 'github-slugger'
|
||||
import BaseLayout from './BaseLayout.astro'
|
||||
import Authors from '../components/Authors.astro'
|
||||
import UserComponent from '../components/UserComponent.astro'
|
||||
import CopyrightedCharacters from '../components/CopyrightedCharacters.astro'
|
||||
import Prose from '../components/Prose.astro'
|
||||
import { Image } from "astro:assets";
|
||||
import { getEntry, type CollectionEntry } from "astro:content";
|
||||
import { Markdown } from "@astropub/md";
|
||||
import { format as formatDate } from "date-fns";
|
||||
import { enUS as enUSLocale } from "date-fns/locale/en-US";
|
||||
import { slug } from "github-slugger";
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
import Authors from "../components/Authors.astro";
|
||||
import UserComponent from "../components/UserComponent.astro";
|
||||
import CopyrightedCharacters from "../components/CopyrightedCharacters.astro";
|
||||
import Prose from "../components/Prose.astro";
|
||||
|
||||
type Props = CollectionEntry<'stories'>['data']
|
||||
type Props = CollectionEntry<"stories">["data"];
|
||||
|
||||
const { props } = Astro
|
||||
let prev = props.prev && (await getEntry(props.prev))
|
||||
const { props } = Astro;
|
||||
let prev = props.prev && (await getEntry(props.prev));
|
||||
if (prev && prev.data.isDraft) {
|
||||
prev = undefined
|
||||
prev = undefined;
|
||||
}
|
||||
let next = props.next && (await getEntry(props.next))
|
||||
let next = props.next && (await getEntry(props.next));
|
||||
if (next && next.data.isDraft) {
|
||||
next = undefined
|
||||
next = undefined;
|
||||
}
|
||||
const relatedStories = (await Promise.all((props.relatedStories || []).map(story => getEntry(story)))).filter(story => !story.data.isDraft)
|
||||
// const relatedGames = (await Promise.all((props.relatedGames || []).map(game => getEntry(game)))).filter(game => !game.data.isDraft)
|
||||
const relatedStories = (await Promise.all((props.relatedStories || []).map((story) => getEntry(story)))).filter(
|
||||
(story) => !story.data.isDraft,
|
||||
);
|
||||
// const relatedGames = (await Promise.all((props.relatedGames || []).map(game => getEntry(game)))).filter(
|
||||
// (game) => !game.data.isDraft,
|
||||
// );
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle={props.title}>
|
||||
<div id="top" class="relative min-w-screen min-h-screen px-1 pt-20 pb-16 bg-radial from-bm-300 to-bm-600 dark:from-green-700 dark:to-green-950 print:bg-none">
|
||||
<div id="toolbox-buttons" aria-label="Toolbox" class="absolute top-0 h-[80vh] lg:inset-y-0 lg:h-full py-2 pl-2">
|
||||
<div class="sticky top-6 rounded-full bg-white dark:bg-black shadow-md py-1 px-1 flex print:hidden">
|
||||
<a href={props.series ? Object.values(props.series)[0] : "/stories/1"} class="p-2 my-1 w-9 h-9 text-link" aria-label={`Return to ${props.series ? Object.keys(props.series)[0] : 'stories'}`}>
|
||||
<div
|
||||
id="top"
|
||||
class="min-w-screen relative min-h-screen bg-radial from-bm-300 to-bm-600 px-1 pb-16 pt-20 dark:from-green-700 dark:to-green-950 print:bg-none"
|
||||
>
|
||||
<div id="toolbox-buttons" aria-label="Toolbox" class="absolute top-0 h-[80vh] py-2 pl-2 lg:inset-y-0 lg:h-full">
|
||||
<div class="sticky top-6 flex rounded-full bg-white px-1 py-1 shadow-md dark:bg-black print:hidden">
|
||||
<a
|
||||
href={props.series ? Object.values(props.series)[0] : "/stories/1"}
|
||||
class="text-link my-1 h-9 w-9 p-2"
|
||||
aria-label={`Return to ${props.series ? Object.keys(props.series)[0] : "stories"}`}
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="fill-current" aria-hidden="true">
|
||||
<path d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"/>
|
||||
<path
|
||||
d="M48.5 224H40c-13.3 0-24-10.7-24-24V72c0-9.7 5.8-18.5 14.8-22.2s19.3-1.7 26.2 5.2L98.6 96.6c87.6-86.5 228.7-86.2 315.8 1c87.5 87.5 87.5 229.3 0 316.8s-229.3 87.5-316.8 0c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0c62.5 62.5 163.8 62.5 226.3 0s62.5-163.8 0-226.3c-62.2-62.2-162.7-62.5-225.3-1L185 183c6.9 6.9 8.9 17.2 5.2 26.2s-12.5 14.8-22.2 14.8H48.5z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="#description" class="p-2 my-1 w-9 h-9 border-l border-stone-300 dark:border-stone-700 text-link" aria-label="Go to description">
|
||||
<a
|
||||
href="#description"
|
||||
class="text-link my-1 h-9 w-9 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||
aria-label="Go to description"
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="fill-current" aria-hidden="true">
|
||||
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/>
|
||||
<path
|
||||
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
|
||||
></path>
|
||||
</svg>
|
||||
</a>
|
||||
<button id="button-dark-mode" class="p-2 my-1 w-9 h-9 border-l border-stone-300 dark:border-stone-700 text-link" aria-label="Toggle dark mode">
|
||||
<svg viewBox="0 0 512 512" class="fill-current hidden dark:block" aria-hidden="true">
|
||||
<button
|
||||
id="button-dark-mode"
|
||||
class="text-link my-1 h-9 w-9 border-l border-stone-300 p-2 dark:border-stone-700"
|
||||
aria-label="Toggle dark mode"
|
||||
>
|
||||
<svg viewBox="0 0 512 512" class="hidden fill-current dark:block" aria-hidden="true">
|
||||
<path
|
||||
d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
<svg viewBox="0 0 512 512" class="fill-current block dark:hidden" aria-hidden="true">
|
||||
<svg viewBox="0 0 512 512" class="block fill-current dark:hidden" aria-hidden="true">
|
||||
<path
|
||||
d="M223.5 32C100 32 0 132.3 0 256S100 480 223.5 480c60.6 0 115.5-24.2 155.8-63.4c5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6c-96.9 0-175.5-78.8-175.5-176c0-65.8 36-123.1 89.3-153.3c6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"
|
||||
/>
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<main class="mx-auto max-w-3xl px-2 pt-1 pb-4 rounded-lg shadow-sm bg-stone-50 dark:bg-stone-900 print:bg-none print:shadow-none print:max-w-full">
|
||||
{(prev || next) ?
|
||||
<div class="print:hidden">
|
||||
<div id="story-nav-top" class="my-4 grid grid-cols-2 gap-2 justify-items-stretch">
|
||||
{prev ?
|
||||
<a href={`/stories/${prev.slug}`} class="py-3 px-1 flex justify-center items-center underline font-light text-link border-r border-stone-400 dark:border-stone-600">
|
||||
<svg class="mr-1 w-5 h-5 fill-current" viewBox="0 0 320 512"><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"/></svg>
|
||||
<span>Previous: {prev.data.shortTitle || prev.data.title}</span>
|
||||
</a> : <div class="h-full border-r border-stone-400 dark:border-stone-600"></div>
|
||||
}
|
||||
{next ?
|
||||
<a href={`/stories/${next.slug}`} class="py-3 px-1 flex justify-center items-center underline font-light text-link">
|
||||
<span>Next: {next.data.shortTitle || next.data.title}</span>
|
||||
<svg class="ml-1 w-5 h-5 fill-current" viewBox="0 0 320 512"><path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg>
|
||||
</a> : <div></div>
|
||||
}
|
||||
<main
|
||||
class="mx-auto max-w-3xl rounded-lg bg-stone-50 px-2 pb-4 pt-1 shadow-sm dark:bg-stone-900 print:max-w-full print:bg-none print:shadow-none"
|
||||
>
|
||||
{
|
||||
prev || next ? (
|
||||
<div class="print:hidden">
|
||||
<div id="story-nav-top" class="my-4 grid grid-cols-2 justify-items-stretch gap-2">
|
||||
{prev ? (
|
||||
<a
|
||||
href={`/stories/${prev.slug}`}
|
||||
class="text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 font-light underline dark:border-stone-600"
|
||||
>
|
||||
<svg class="mr-1 h-5 w-5 fill-current" viewBox="0 0 320 512">
|
||||
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
|
||||
</svg>
|
||||
<span>Previous: {prev.data.shortTitle || prev.data.title}</span>
|
||||
</a>
|
||||
) : (
|
||||
<div class="h-full border-r border-stone-400 dark:border-stone-600" />
|
||||
)}
|
||||
{next ? (
|
||||
<a
|
||||
href={`/stories/${next.slug}`}
|
||||
class="text-link flex items-center justify-center px-1 py-3 font-light underline"
|
||||
>
|
||||
<span>Next: {next.data.shortTitle || next.data.title}</span>
|
||||
<svg class="ml-1 h-5 w-5 fill-current" viewBox="0 0 320 512">
|
||||
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
|
||||
</svg>
|
||||
</a>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
<hr class="mx-auto mb-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
|
||||
</div>
|
||||
<hr class="mx-auto mb-5 border-stone-400 dark:border-stone-600 w-full max-w-2xl" />
|
||||
</div> : null
|
||||
) : null
|
||||
}
|
||||
<h1 id="story-title" class="px-2 pt-2 font-serif text-3xl font-semibold text-stone-800 dark:text-stone-100">{props.title}</h1>
|
||||
<section id="story-information" class="px-2 mt-1 space-y-2 font-serif italic text-stone-600 dark:text-stone-200 font-light">
|
||||
<h1 id="story-title" class="px-2 pt-2 font-serif text-3xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.title}
|
||||
</h1>
|
||||
<section
|
||||
id="story-information"
|
||||
class="mt-1 space-y-2 px-2 font-serif font-light italic text-stone-600 dark:text-stone-200"
|
||||
>
|
||||
<Authors authors={props.authors} lang={props.lang} />
|
||||
{props.isDraft ?
|
||||
<p id="draft-warning" class="text-center text-red-600 text-2xl not-italic font-semibold py-2">
|
||||
DRAFT VERSION – DO NOT REDISTRIBUTE
|
||||
</p> : null
|
||||
{
|
||||
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
|
||||
</p>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
props.commissioner && (
|
||||
<p id="commissioner">
|
||||
Commissioned by <UserComponent user={props.commissioner} />
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{
|
||||
props.requester && (
|
||||
<p id="requester">
|
||||
Requested by <UserComponent user={props.requester} />
|
||||
</p>
|
||||
)
|
||||
}
|
||||
{props.commissioner &&
|
||||
<p id="commissioner">
|
||||
Commissioned by <UserComponent user={props.commissioner} />
|
||||
</p>}
|
||||
{props.requester &&
|
||||
<p id="requester">
|
||||
Requested by <UserComponent user={props.requester} />
|
||||
</p>}
|
||||
<div id="content-warning">
|
||||
<p>{props.lang === 'eng' ? `Word count: ${props.wordCount}.` : props.lang === 'tok' ? `` : null} {props.contentWarning}</p>
|
||||
</section>
|
||||
<p>
|
||||
{props.lang === "eng" ? `Word count: ${props.wordCount}.` : props.lang === "tok" ? `` : null}
|
||||
{props.contentWarning}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<hr class="mx-auto my-10 border-stone-400 dark:border-stone-600 w-[80%] max-w-xl" />
|
||||
{props.thumbnail && <Image loading="eager" src={props.thumbnail} alt={`Cover art for ${props.title}`} width={props.thumbnailWidth} height={props.thumbnailHeight} class="my-5 mx-auto shadow-lg" />}
|
||||
<hr class="mx-auto my-10 border-stone-400 dark:border-stone-600 w-[80%] max-w-xl" />
|
||||
<article id="story" class="font-serif pr-1">
|
||||
<hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
|
||||
{
|
||||
props.thumbnail && (
|
||||
<Image
|
||||
loading="eager"
|
||||
src={props.thumbnail}
|
||||
alt={`Cover art for ${props.title}`}
|
||||
width={props.thumbnailWidth}
|
||||
height={props.thumbnailHeight}
|
||||
class="mx-auto my-5 shadow-lg"
|
||||
/>
|
||||
)
|
||||
}
|
||||
<hr class="mx-auto my-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
|
||||
<article id="story" class="pr-1 font-serif">
|
||||
<Prose>
|
||||
<slot />
|
||||
</Prose>
|
||||
</article>
|
||||
<hr class="mx-auto mt-10 mb-6 border-stone-400 dark:border-stone-600 w-[80%] max-w-xl" />
|
||||
{props.isDraft ?
|
||||
<p id="draft-warning-bottom" class="text-center font-serif text-red-600 text-2xl not-italic font-semibold py-2">
|
||||
DRAFT VERSION – DO NOT REDISTRIBUTE
|
||||
</p> :
|
||||
<p id="publish-date" class="px-2 mt-2 text-center font-serif italic text-stone-600 dark:text-stone-200" aria-label="Publish date" aria-description={formatDate(props.pubDate, 'MMMM do, yyyy', { locale: enUSLocale })}>
|
||||
{formatDate(props.pubDate, 'yyyy-MM-dd')}
|
||||
</p>
|
||||
<hr class="mx-auto mb-6 mt-10 w-[80%] max-w-xl border-stone-400 dark:border-stone-600" />
|
||||
{
|
||||
props.isDraft ? (
|
||||
<p
|
||||
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
|
||||
</p>
|
||||
) : (
|
||||
<p
|
||||
id="publish-date"
|
||||
class="mt-2 px-2 text-center font-serif font-light text-stone-600 dark:text-stone-200"
|
||||
aria-label="Publish date"
|
||||
aria-description={formatDate(props.pubDate, "MMMM do, yyyy", { locale: enUSLocale })}
|
||||
>
|
||||
{formatDate(props.pubDate, "yyyy-MM-dd")}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
<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">{props.lang === 'eng' ? 'Description' : props.lang === 'tok' ? 'lipu lili' : null}</h2>
|
||||
<h2 id="title-description" class="py-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.lang === "eng" ? "Description" : props.lang === "tok" ? "lipu lili" : null}
|
||||
</h2>
|
||||
<Prose>
|
||||
<Markdown of={props.description} />
|
||||
<CopyrightedCharacters copyrightedCharacters={props.copyrightedCharacters} lang={props.lang} />
|
||||
</Prose>
|
||||
</section>
|
||||
<div class="text-right pr-3 print:hidden">
|
||||
<a href="#top" class="text-link inline-flex items-center underline"><svg class="w-6 h-6 mr-1 fill-current" viewBox="0 0 384 512" aria-hidden="true"><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"/></svg><span>{props.lang === 'eng' ? 'To top' : props.lang === 'tok' ? 'tawa sewi' : null}</span></a>
|
||||
<div class="pr-3 text-right print:hidden">
|
||||
<a href="#top" class="text-link inline-flex items-center underline"
|
||||
><svg class="mr-1 h-6 w-6 fill-current" viewBox="0 0 384 512" aria-hidden="true"
|
||||
><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>{props.lang === "eng" ? "To top" : props.lang === "tok" ? "tawa sewi" : null}</span></a
|
||||
>
|
||||
</div>
|
||||
{(prev || next) ?
|
||||
<Fragment>
|
||||
<hr class="mx-auto mt-5 border-stone-400 dark:border-stone-600 w-full max-w-2xl" />
|
||||
<div id="story-nav-top" class="my-4 grid grid-cols-2 gap-2 justify-items-stretch">
|
||||
{prev ?
|
||||
<a href={`/stories/${prev.slug}`} class="py-3 px-1 flex justify-center items-center underline font-light text-link border-r border-stone-400 dark:border-stone-600">
|
||||
<svg class="mr-1 w-5 h-5 fill-current" viewBox="0 0 320 512"><path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"/></svg>
|
||||
<span>Previous: {prev.data.shortTitle || prev.data.title}</span>
|
||||
</a> : <div class="h-full border-r border-stone-400 dark:border-stone-600"></div>
|
||||
}
|
||||
{next ?
|
||||
<a href={`/stories/${next.slug}`} class="py-3 px-1 flex justify-center items-center underline font-light text-link">
|
||||
<span>Next: {next.data.shortTitle || next.data.title}</span>
|
||||
<svg class="ml-1 w-5 h-5 fill-current" viewBox="0 0 320 512"><path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg>
|
||||
</a> : <div></div>
|
||||
}
|
||||
</div>
|
||||
<hr class="mx-auto mb-5 border-stone-400 dark:border-stone-600 w-full max-w-2xl" />
|
||||
</Fragment> : null
|
||||
{
|
||||
prev || next ? (
|
||||
<Fragment>
|
||||
<hr class="mx-auto mt-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
|
||||
<div id="story-nav-top" class="my-4 grid grid-cols-2 justify-items-stretch gap-2">
|
||||
{prev ? (
|
||||
<a
|
||||
href={`/stories/${prev.slug}`}
|
||||
class="text-link flex items-center justify-center border-r border-stone-400 px-1 py-3 font-light underline dark:border-stone-600"
|
||||
>
|
||||
<svg class="mr-1 h-5 w-5 fill-current" viewBox="0 0 320 512">
|
||||
<path d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z" />
|
||||
</svg>
|
||||
<span>Previous: {prev.data.shortTitle || prev.data.title}</span>
|
||||
</a>
|
||||
) : (
|
||||
<div class="h-full border-r border-stone-400 dark:border-stone-600" />
|
||||
)}
|
||||
{next ? (
|
||||
<a
|
||||
href={`/stories/${next.slug}`}
|
||||
class="text-link flex items-center justify-center px-1 py-3 font-light underline"
|
||||
>
|
||||
<span>Next: {next.data.shortTitle || next.data.title}</span>
|
||||
<svg class="ml-1 h-5 w-5 fill-current" viewBox="0 0 320 512">
|
||||
<path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z" />
|
||||
</svg>
|
||||
</a>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
<hr class="mx-auto mb-5 w-full max-w-2xl border-stone-400 dark:border-stone-600" />
|
||||
</Fragment>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
relatedStories.length > 0 ?
|
||||
relatedStories.length > 0 ? (
|
||||
<section id="related" aria-describedby="title-related" class="my-5">
|
||||
<h2 id="title-related" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">Related stories</h2>
|
||||
<h2 id="title-related" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
Related stories
|
||||
</h2>
|
||||
<Prose>
|
||||
<ul>
|
||||
{ relatedStories.map(stories =>
|
||||
<li><a href={`/stories/${stories.slug}`}>{stories.data.title}</a></li>
|
||||
) }
|
||||
{relatedStories.map((stories) => (
|
||||
<li>
|
||||
<a href={`/stories/${stories.slug}`}>{stories.data.title}</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Prose>
|
||||
</section> : null
|
||||
</section>
|
||||
) : null
|
||||
}
|
||||
<section id="tags" aria-describedby="title-tags" class="my-5">
|
||||
<h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">{props.lang === 'eng' ? 'Tags' : props.lang === 'tok' ? 'nimi kulupu' : null}</h2>
|
||||
<ul class="px-2 flex flex-wrap gap-x-2 gap-y-2">
|
||||
{ props.tags.map(tag =>
|
||||
<li class="text-sm rounded-full shadow-sm px-3 py-1 bg-bm-300 text-black dark:bg-bm-600 dark:text-white print:bg-none"><a class="focus:underline hover:underline" href={`/tags/${slug(tag)}`}>{tag}</a></li>
|
||||
) }
|
||||
<h2 id="title-tags" class="p-2 font-serif text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{props.lang === "eng" ? "Tags" : props.lang === "tok" ? "nimi kulupu" : null}
|
||||
</h2>
|
||||
<ul class="flex flex-wrap gap-x-2 gap-y-2 px-2">
|
||||
{
|
||||
props.tags.map((tag) => (
|
||||
<li class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm dark:bg-bm-600 dark:text-white print:bg-none">
|
||||
<a class="hover:underline focus:underline" href={`/tags/${slug(tag)}`}>
|
||||
{tag}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
<div class="pt-6 text-center text-xs text-black dark:text-white">
|
||||
<span>© {formatDate(props.pubDate, 'yyyy')} | </span>
|
||||
<a class="focus:underline hover:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||
<span>© {formatDate(props.pubDate, "yyyy")} | </span>
|
||||
<a class="hover:underline focus:underline" href="/licenses.txt" target="_blank">Licenses</a>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
|
|
@ -4,19 +4,33 @@ import { getCollection } from "astro:content";
|
|||
import { getUnixTime } from "date-fns";
|
||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||
|
||||
const stories = (await getCollection('stories')).filter(story => !story.data.isDraft).sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate))
|
||||
const stories = (await getCollection("stories"))
|
||||
.filter((story) => !story.data.isDraft)
|
||||
.sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate));
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="Stories">
|
||||
<h1>Stories</h1>
|
||||
<p>Lorem ipsum.</p>
|
||||
<ul>
|
||||
{stories.map((story) =>
|
||||
<li>
|
||||
<a href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail && <Image src={story.data.thumbnail} alt={`Thumbnail for ${story.data.title}`} width={story.data.thumbnailWidth} height={story.data.thumbnailHeight} />}
|
||||
<span>{story.data.pubDate} - {story.data.title}</span>
|
||||
</a>
|
||||
</li>)}
|
||||
{
|
||||
stories.map((story) => (
|
||||
<li>
|
||||
<a href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail && (
|
||||
<Image
|
||||
src={story.data.thumbnail}
|
||||
alt={`Thumbnail for ${story.data.title}`}
|
||||
width={story.data.thumbnailWidth}
|
||||
height={story.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<span>
|
||||
{story.data.pubDate} - {story.data.title}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro'
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout pageTitle="Age verification">
|
||||
<div
|
||||
class="bg-stone-100 dark:bg-stone-900"
|
||||
>
|
||||
<div
|
||||
class="mx-auto flex min-h-screen max-w-3xl flex-col items-center justify-center text-center tracking-tight"
|
||||
>
|
||||
<div class="bg-stone-100 dark:bg-stone-900">
|
||||
<div class="mx-auto flex min-h-screen max-w-3xl flex-col items-center justify-center text-center tracking-tight">
|
||||
<div class="h-14 w-14 text-bm-500 sm:h-16 sm:w-16 dark:text-bm-400">
|
||||
<svg class="fill-current" viewBox="0 0 512 512">
|
||||
<path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/>
|
||||
<path
|
||||
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="pb-3 pt-2 text-2xl font-light text-stone-700 sm:pb-4 sm:pt-2 sm:text-3xl dark:text-stone-50"
|
||||
>
|
||||
<div class="pb-3 pt-2 text-2xl font-light text-stone-700 sm:pb-4 sm:pt-2 sm:text-3xl dark:text-stone-50">
|
||||
Age verification
|
||||
</div>
|
||||
<div class="w-full max-w-xl">
|
||||
|
@ -27,12 +23,10 @@ import BaseLayout from '../layouts/BaseLayout.astro'
|
|||
</div>
|
||||
</div>
|
||||
<p class="px-8 text-base font-light leading-snug text-stone-700 sm:max-w-2xl sm:text-lg dark:text-stone-50">
|
||||
By confirming that you are at least 18 years old, your selection will be saved to your browser to prevent
|
||||
this screen from appearing in the future.
|
||||
By confirming that you are at least 18 years old, your selection will be saved to your browser to prevent this
|
||||
screen from appearing in the future.
|
||||
</p>
|
||||
<div
|
||||
class="flex w-full max-w-md flex-col-reverse justify-evenly gap-y-5 px-6 pt-5 sm:max-w-2xl sm:flex-row"
|
||||
>
|
||||
<div class="flex w-full max-w-md flex-col-reverse justify-evenly gap-y-5 px-6 pt-5 sm:max-w-2xl sm:flex-row">
|
||||
<button
|
||||
id="age-verification-reject"
|
||||
class="rounded bg-stone-400 py-3 text-lg text-stone-900 hover:bg-stone-500 hover:text-stone-50 focus:bg-stone-500 focus:text-stone-50 sm:px-9 dark:bg-stone-300 dark:text-stone-900 dark:hover:bg-stone-600 dark:hover:text-stone-50 dark:focus:bg-stone-600 dark:focus:text-stone-50"
|
||||
|
@ -49,13 +43,13 @@ import BaseLayout from '../layouts/BaseLayout.astro'
|
|||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.querySelector('#age-verification-reject')!.addEventListener('click', () => {
|
||||
window.location.href = 'about:blank'
|
||||
})
|
||||
document.querySelector('#age-verification-accept')!.addEventListener('click', () => {
|
||||
localStorage.setItem('ageVerified', 'true')
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
window.location.href = params.get('redirect') || '/'
|
||||
})
|
||||
document.querySelector("#age-verification-reject")!.addEventListener("click", () => {
|
||||
window.location.href = "about:blank";
|
||||
});
|
||||
document.querySelector("#age-verification-accept")!.addEventListener("click", () => {
|
||||
localStorage.setItem("ageVerified", "true");
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
window.location.href = params.get("redirect") || "/";
|
||||
});
|
||||
</script>
|
||||
</BaseLayout>
|
||||
</BaseLayout>
|
||||
|
|
|
@ -1,34 +1,42 @@
|
|||
import rss, { type RSSFeedItem } from '@astrojs/rss'
|
||||
import type { APIRoute } from 'astro';
|
||||
import { getCollection } from 'astro:content';
|
||||
import rss, { type RSSFeedItem } from "@astrojs/rss";
|
||||
import type { APIRoute } from "astro";
|
||||
import { getCollection } from "astro:content";
|
||||
import { getUnixTime, addHours } from "date-fns";
|
||||
|
||||
type FeedItem = RSSFeedItem & {
|
||||
pubDate: Date
|
||||
}
|
||||
pubDate: Date;
|
||||
};
|
||||
|
||||
export const GET: APIRoute = async ({ site }) => {
|
||||
const stories = (await getCollection('stories')).filter(story => !story.data.isDraft)
|
||||
const games = (await getCollection('games')).filter(game => !game.data.isDraft)
|
||||
const stories = (await getCollection("stories")).filter((story) => !story.data.isDraft);
|
||||
const games = (await getCollection("games")).filter((game) => !game.data.isDraft);
|
||||
return rss({
|
||||
title: 'Gallery | Bad Manners',
|
||||
description: 'Stories, games, and artwork by Bad Manners',
|
||||
title: "Gallery | Bad Manners",
|
||||
description: "Stories, games, and more by Bad Manners",
|
||||
site: site as URL,
|
||||
items: [
|
||||
stories.map<FeedItem>((story) => ({
|
||||
title: `New story! "${story.data.title}"`,
|
||||
pubDate: addHours(story.data.pubDate, 12),
|
||||
link: `/stories/${story.slug}`,
|
||||
description: `Word count: ${story.data.wordCount}. ${story.data.contentWarning} ${story.data.descriptionPlaintext || story.data.description}`,
|
||||
categories: ['story'],
|
||||
description:
|
||||
`Word count: ${story.data.wordCount}. ${story.data.contentWarning} ${story.data.descriptionPlaintext || story.data.description}`
|
||||
.replaceAll(/\n+| +/g, " ")
|
||||
.trim(),
|
||||
categories: ["story"],
|
||||
})),
|
||||
games.map<FeedItem>((game) => ({
|
||||
title: `New game! "${game.data.title}"`,
|
||||
pubDate: addHours(game.data.pubDate, 12),
|
||||
link: `/games/${game.slug}`,
|
||||
description: `${game.data.contentWarning} ${game.data.descriptionPlaintext || game.data.description}`,
|
||||
categories: ['game'],
|
||||
description: `${game.data.contentWarning} ${game.data.descriptionPlaintext || game.data.description}`
|
||||
.replaceAll(/\n+| +/g, " ")
|
||||
.trim(),
|
||||
categories: ["game"],
|
||||
})),
|
||||
].flat().sort((a, b) => getUnixTime(b.pubDate) - getUnixTime(a.pubDate)).slice(0, 10),
|
||||
})
|
||||
}
|
||||
]
|
||||
.flat()
|
||||
.sort((a, b) => getUnixTime(b.pubDate) - getUnixTime(a.pubDate))
|
||||
.slice(0, 10),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,24 +2,41 @@
|
|||
import { Image } from "astro:assets";
|
||||
import { getCollection } from "astro:content";
|
||||
import { getUnixTime, format as formatDate } from "date-fns";
|
||||
import { enUS as enUSLocale } from "date-fns/locale/en-US"
|
||||
import { enUS as enUSLocale } from "date-fns/locale/en-US";
|
||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||
|
||||
const games = (await getCollection('games')).filter(game => !game.data.isDraft).sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate))
|
||||
const games = (await getCollection("games"))
|
||||
.filter((game) => !game.data.isDraft)
|
||||
.sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate));
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="Games">
|
||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Stories</h1>
|
||||
<p class="my-4">A game that I've gone and done.</p>
|
||||
<ul class="my-6 flex justify-center md:justify-normal flex-wrap text-center gap-4">
|
||||
{games.map((game) =>
|
||||
<li>
|
||||
<a class="focus:underline hover:underline text-link" href={`/games/${game.slug}`}>
|
||||
{game.data.thumbnail &&
|
||||
<Image class="max-w-72" src={game.data.thumbnail} alt={`Thumbnail for ${game.data.title}`} width={game.data.thumbnailWidth} height={game.data.thumbnailHeight} />
|
||||
}
|
||||
<div class="text-sm max-w-72"><span>{game.data.title}</span><br><span class="italic">{formatDate(game.data.pubDate, 'MMM d, yyyy', { locale: enUSLocale })}</span></div>
|
||||
</a>
|
||||
</li>)}
|
||||
<ul class="my-6 flex flex-wrap justify-center gap-4 text-center md:justify-normal">
|
||||
{
|
||||
games.map((game) => (
|
||||
<li>
|
||||
<a class="text-link hover:underline focus:underline" href={`/games/${game.slug}`}>
|
||||
{game.data.thumbnail && (
|
||||
<Image
|
||||
class="max-w-72"
|
||||
src={game.data.thumbnail}
|
||||
alt={`Thumbnail for ${game.data.title}`}
|
||||
width={game.data.thumbnailWidth}
|
||||
height={game.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<div class="max-w-72 text-sm">
|
||||
<>
|
||||
<span>{game.data.title}</span>
|
||||
<br />
|
||||
<span class="italic">{formatDate(game.data.pubDate, "MMM d, yyyy", { locale: enUSLocale })}</span>
|
||||
</>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
||||
import GameLayout from '../../layouts/GameLayout.astro';
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import GameLayout from "../../layouts/GameLayout.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const games = await getCollection('games');
|
||||
return games.map(story => ({
|
||||
const games = await getCollection("games");
|
||||
return games.map((story) => ({
|
||||
params: { slug: story.slug },
|
||||
props: story,
|
||||
}));
|
||||
}
|
||||
type Props = CollectionEntry<'games'>
|
||||
type Props = CollectionEntry<"games">;
|
||||
|
||||
const story = Astro.props;
|
||||
const { Content } = await story.render();
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
---
|
||||
import GalleryLayout from '../layouts/GalleryLayout.astro'
|
||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="Gallery">
|
||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">My gallery</h1>
|
||||
<p class="my-4">Welcome to my gallery! Use the navigation menu to navigate through my content.</p>
|
||||
<ul class="pl-8 list-disc">
|
||||
<li><a class="underline text-link" href="/stories/1">Read my stories.</a></li>
|
||||
<li><a class="underline text-link" href="/games/crossing-over">Play my visual novel.</a></li>
|
||||
<li><a class="underline text-link" href="/tags">Find all content with a certain tag.</a></li>
|
||||
<ul class="list-disc pl-8">
|
||||
<li><a class="text-link underline" href="/stories/1">Read my stories.</a></li>
|
||||
<li><a class="text-link underline" href="/games/crossing-over">Play my visual novel.</a></li>
|
||||
<li><a class="text-link underline" href="/tags">Find all content with a certain tag.</a></li>
|
||||
</ul>
|
||||
<p class="my-4">
|
||||
For more information about me, please check out <a
|
||||
class="text-link underline"
|
||||
href="https://badmanners.xyz/"
|
||||
target="_blank">my main website</a
|
||||
>.
|
||||
</p>
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import type { APIRoute } from 'astro';
|
||||
import type { APIRoute } from "astro";
|
||||
|
||||
const licenses =
|
||||
`The briefcase logo and any unattributed characters are copyrighted and trademarked by me.
|
||||
const licenses = `The briefcase logo and any unattributed characters are copyrighted and trademarked by me.
|
||||
|
||||
The typeface Noto Serif is copyrighted to the Noto Project Authors and is distributed under the SIL Open Font License v1.1.
|
||||
The Noto Sans and Noto Serif typefaces are copyrighted to the Noto Project Authors and distributed under the SIL Open Font License v1.1.
|
||||
|
||||
The generic SVG icons were created by Font Awesome and are distributed under the CC BY 4.0 license.
|
||||
|
||||
All third-party trademarks belong to their respective owners, and I'm not affiliated with any of them.
|
||||
`
|
||||
`;
|
||||
|
||||
export const GET: APIRoute = async ({ site }) => {
|
||||
return new Response(licenses, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
|
||||
}
|
||||
export const GET: APIRoute = () => {
|
||||
return new Response(licenses, {
|
||||
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
||||
import StoryLayout from '../../layouts/StoryLayout.astro';
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import StoryLayout from "../../layouts/StoryLayout.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const stories = await getCollection('stories');
|
||||
return stories.map(story => ({
|
||||
const stories = await getCollection("stories");
|
||||
return stories.map((story) => ({
|
||||
params: { slug: story.slug },
|
||||
props: story,
|
||||
}));
|
||||
}
|
||||
type Props = CollectionEntry<'stories'>
|
||||
type Props = CollectionEntry<"stories">;
|
||||
|
||||
const story = Astro.props;
|
||||
const { Content } = await story.render();
|
||||
|
|
|
@ -3,48 +3,111 @@ import type { GetStaticPathsOptions } from "astro";
|
|||
import { Image } from "astro:assets";
|
||||
import { getCollection } from "astro:content";
|
||||
import { getUnixTime, format as formatDate } from "date-fns";
|
||||
import { enUS as enUSLocale } from "date-fns/locale/en-US"
|
||||
import { enUS as enUSLocale } from "date-fns/locale/en-US";
|
||||
import GalleryLayout from "../../layouts/GalleryLayout.astro";
|
||||
|
||||
export async function getStaticPaths({ paginate }: GetStaticPathsOptions) {
|
||||
const stories = (await getCollection('stories')).filter(story => !story.data.isDraft).sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate))
|
||||
const stories = (await getCollection("stories"))
|
||||
.filter((story) => !story.data.isDraft)
|
||||
.sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate));
|
||||
return paginate(stories, { pageSize: 30 });
|
||||
}
|
||||
const { page } = Astro.props
|
||||
const totalPages = Math.ceil(page.total / page.size)
|
||||
const { page } = Astro.props;
|
||||
const totalPages = Math.ceil(page.total / page.size);
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="Stories">
|
||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Stories</h1>
|
||||
<p class="my-4">My main collection of content so far.</p>
|
||||
<p class="text-center font-light text-stone-950 dark:text-white">{page.start == page.end ? `Displaying story ${page.start + 1}` : `Displaying stories ${page.start + 1} - ${page.end + 1}`} / {page.total}</p>
|
||||
<div class="mx-auto mt-2 mb-6 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||||
{page.url.prev && <a class="px-2 py-1 text-link underline border-r border-stone-400 dark:border-stone-500" href={page.url.prev}>Previous page</a>}
|
||||
{[...Array(totalPages).keys()].map(p => p + 1 == page.currentPage ?
|
||||
<span class="px-4 py-1 text-stone-900 dark:text-stone-50 border-r border-stone-400 dark:border-stone-500 font-semibold">{p + 1}</span> :
|
||||
<a class="px-2 py-1 text-link underline border-r border-stone-400 dark:border-stone-500" href={`./${p + 1}`}>
|
||||
{p + 1}
|
||||
</a>)}
|
||||
{page.url.next && <a class="px-2 py-1 text-link underline" href={page.url.next}>Next page</a>}
|
||||
</div>
|
||||
<ul class="flex justify-center md:justify-normal flex-wrap text-center gap-4">
|
||||
{page.data.map((story) =>
|
||||
<li>
|
||||
<a class="focus:underline hover:underline text-link" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail &&
|
||||
<Image class="w-48" src={story.data.thumbnail} alt={`Thumbnail for ${story.data.title}`} width={story.data.thumbnailWidth} height={story.data.thumbnailHeight} />
|
||||
}
|
||||
<div class="text-sm max-w-48"><span>{story.data.title}</span><br><span class="italic">{formatDate(story.data.pubDate, 'MMM d, yyyy', { locale: enUSLocale })}</span></div>
|
||||
<p class="text-center font-light text-stone-950 dark:text-white">
|
||||
{
|
||||
page.start == page.end
|
||||
? `Displaying story ${page.start + 1}`
|
||||
: `Displaying stories ${page.start + 1} - ${page.end + 1}`
|
||||
} / {page.total}
|
||||
</p>
|
||||
<div class="mx-auto mb-6 mt-2 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||||
{
|
||||
page.url.prev && (
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" href={page.url.prev}>
|
||||
Previous page
|
||||
</a>
|
||||
</li>)}
|
||||
)
|
||||
}
|
||||
{
|
||||
[...Array(totalPages).keys()].map((p) =>
|
||||
p + 1 == page.currentPage ? (
|
||||
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
||||
{p + 1}
|
||||
</span>
|
||||
) : (
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" href={`./${p + 1}`}>
|
||||
{p + 1}
|
||||
</a>
|
||||
),
|
||||
)
|
||||
}
|
||||
{
|
||||
page.url.next && (
|
||||
<a class="text-link px-2 py-1 underline" href={page.url.next}>
|
||||
Next page
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
|
||||
{
|
||||
page.data.map((story) => (
|
||||
<li class="break-inside-avoid">
|
||||
<a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail && (
|
||||
<Image
|
||||
class="w-48"
|
||||
src={story.data.thumbnail}
|
||||
alt={`Thumbnail for ${story.data.title}`}
|
||||
width={story.data.thumbnailWidth}
|
||||
height={story.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<div class="max-w-48 text-sm">
|
||||
<>
|
||||
<span>{story.data.title}</span>
|
||||
<br />
|
||||
<span class="italic">{formatDate(story.data.pubDate, "MMM d, yyyy", { locale: enUSLocale })}</span>
|
||||
</>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
<div class="mx-auto my-6 flex w-fit rounded-lg border border-stone-400 dark:border-stone-500">
|
||||
{page.url.prev && <a class="px-2 py-1 text-link underline border-r border-stone-400 dark:border-stone-500" href={page.url.prev}>Previous page</a>}
|
||||
{[...Array(totalPages).keys()].map(p => p + 1 == page.currentPage ?
|
||||
<span class="px-4 py-1 text-stone-900 dark:text-stone-50 border-r border-stone-400 dark:border-stone-500 font-semibold">{p + 1}</span> :
|
||||
<a class="px-2 py-1 text-link underline border-r border-stone-400 dark:border-stone-500" href={`./${p + 1}`}>
|
||||
{p + 1}
|
||||
</a>)}
|
||||
{page.url.next && <a class="px-2 py-1 text-link underline" href={page.url.next}>Next page</a>}
|
||||
{
|
||||
page.url.prev && (
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" href={page.url.prev}>
|
||||
Previous page
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
[...Array(totalPages).keys()].map((p) =>
|
||||
p + 1 == page.currentPage ? (
|
||||
<span class="border-r border-stone-400 px-4 py-1 font-semibold text-stone-900 dark:border-stone-500 dark:text-stone-50">
|
||||
{p + 1}
|
||||
</span>
|
||||
) : (
|
||||
<a class="text-link border-r border-stone-400 px-2 py-1 underline dark:border-stone-500" href={`./${p + 1}`}>
|
||||
{p + 1}
|
||||
</a>
|
||||
),
|
||||
)
|
||||
}
|
||||
{
|
||||
page.url.next && (
|
||||
<a class="text-link px-2 py-1 underline" href={page.url.next}>
|
||||
Next page
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
---
|
||||
import { getCollection } from "astro:content";
|
||||
import { Image } from "astro:assets"
|
||||
import { Image } from "astro:assets";
|
||||
import { getUnixTime } from "date-fns";
|
||||
import GalleryLayout from "../../layouts/GalleryLayout.astro"
|
||||
import mapImage from '../../assets/images/tlotm_map.jpg'
|
||||
import GalleryLayout from "../../layouts/GalleryLayout.astro";
|
||||
import mapImage from "../../assets/images/tlotm_map.jpg";
|
||||
|
||||
const stories = (await getCollection('stories')).filter((story) => !story.data.isDraft && story.slug.startsWith('the-lost-of-the-marshes/'))
|
||||
const mainChapters = stories.filter(story => story.slug.startsWith('the-lost-of-the-marshes/chapter-')).sort((a, b) => getUnixTime(a.data.pubDate) - getUnixTime(b.data.pubDate))
|
||||
const bonusChapters = stories.filter(story => story.slug.startsWith('the-lost-of-the-marshes/bonus-')).sort((a, b) => getUnixTime(a.data.pubDate) - getUnixTime(b.data.pubDate))
|
||||
const mainChaptersWithSummaries = mainChapters.filter(story => story.data.summary)
|
||||
const stories = (await getCollection("stories")).filter(
|
||||
(story) => !story.data.isDraft && story.slug.startsWith("the-lost-of-the-marshes/"),
|
||||
);
|
||||
const mainChapters = stories
|
||||
.filter((story) => story.slug.startsWith("the-lost-of-the-marshes/chapter-"))
|
||||
.sort((a, b) => getUnixTime(a.data.pubDate) - getUnixTime(b.data.pubDate));
|
||||
const bonusChapters = stories
|
||||
.filter((story) => story.slug.startsWith("the-lost-of-the-marshes/bonus-"))
|
||||
.sort((a, b) => getUnixTime(a.data.pubDate) - getUnixTime(b.data.pubDate));
|
||||
const mainChaptersWithSummaries = mainChapters.filter((story) => story.data.summary);
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle="The Lost of the Marshes">
|
||||
|
@ -16,47 +22,80 @@ const mainChaptersWithSummaries = mainChapters.filter(story => story.data.summar
|
|||
<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">Main chapters</h2>
|
||||
<details class="mx-3 mt-1 mb-6 rounded-lg border border-stone-400 dark:border-stone-500 bg-stone-300 dark:bg-stone-700">
|
||||
<summary class="py-1 px-2 rounded-lg bg-stone-200 dark:bg-stone-800">Click to reveal spoilers up to {mainChaptersWithSummaries[mainChaptersWithSummaries.length - 1].data.title.match(/Chapter \d+\b/)?.[0]}</summary>
|
||||
<ul class="px-1 border-t border-stone-400 dark:border-stone-500">
|
||||
{mainChapters.filter(story => story.data.summary).map(story =>
|
||||
<li class="my-2">
|
||||
<a class="underline text-link" href={`/stories/${story.slug}`}>{story.data.shortTitle || story.data.title}</a>: <span>{story.data.summary}</span>
|
||||
</li>)
|
||||
<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"
|
||||
>
|
||||
<summary class="rounded-lg bg-stone-200 px-2 py-1 dark:bg-stone-800"
|
||||
>Click to reveal spoilers up to {
|
||||
mainChaptersWithSummaries[mainChaptersWithSummaries.length - 1].data.title.match(/Chapter \d+\b/)?.[0]
|
||||
}</summary
|
||||
>
|
||||
<ul class="border-t border-stone-400 px-1 dark:border-stone-500">
|
||||
{
|
||||
mainChapters
|
||||
.filter((story) => story.data.summary)
|
||||
.map((story) => (
|
||||
<li class="my-2">
|
||||
<a class="text-link underline" href={`/stories/${story.slug}`}>
|
||||
{story.data.shortTitle || story.data.title}
|
||||
</a>
|
||||
: <span>{story.data.summary}</span>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</details>
|
||||
<ul class="flex justify-center md:justify-normal flex-wrap text-center gap-4">
|
||||
{mainChapters.map(story =>
|
||||
<li>
|
||||
<a class="focus:underline hover:underline text-link" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail &&
|
||||
<Image class="w-48" src={story.data.thumbnail} alt={`Thumbnail for ${story.data.title}`} width={story.data.thumbnailWidth} height={story.data.thumbnailHeight} />
|
||||
}
|
||||
<div class="text-sm max-w-48">{story.data.title}</div>
|
||||
</a>
|
||||
</li>)
|
||||
<ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
|
||||
{
|
||||
mainChapters.map((story) => (
|
||||
<li class="break-inside-avoid">
|
||||
<a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail && (
|
||||
<Image
|
||||
class="w-48"
|
||||
src={story.data.thumbnail}
|
||||
alt={`Thumbnail for ${story.data.title}`}
|
||||
width={story.data.thumbnailWidth}
|
||||
height={story.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<div class="max-w-48 text-sm">{story.data.title}</div>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
<section class="my-2" aria-labelledby="bonus-chapters">
|
||||
<h2 id="bonus-chapters" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Bonus chapters</h2>
|
||||
<ul class="flex justify-center md:justify-normal flex-wrap text-center gap-4">
|
||||
{bonusChapters.map(story =>
|
||||
<li>
|
||||
<a class="focus:underline hover:underline text-link" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail &&
|
||||
<Image class="w-48" src={story.data.thumbnail} alt={`Thumbnail for ${story.data.title}`} width={story.data.thumbnailWidth} height={story.data.thumbnailHeight} />
|
||||
}
|
||||
<div class="text-sm max-w-48">{story.data.title}</div>
|
||||
</a>
|
||||
</li>)
|
||||
<ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
|
||||
{
|
||||
bonusChapters.map((story) => (
|
||||
<li class="break-inside-avoid">
|
||||
<a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail && (
|
||||
<Image
|
||||
class="w-48"
|
||||
src={story.data.thumbnail}
|
||||
alt={`Thumbnail for ${story.data.title}`}
|
||||
width={story.data.thumbnailWidth}
|
||||
height={story.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<div class="max-w-48 text-sm">{story.data.title}</div>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
<section class="mt-2 mb-6" aria-labelledby="map">
|
||||
<section class="mb-6 mt-2" aria-labelledby="map">
|
||||
<h2 id="map" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Map</h2>
|
||||
<p class="mt-2 mb-4">Updated to include locations up to Chapter 11.</p>
|
||||
<Image class="mx-auto w-full max-w-4xl" src={mapImage} alt="A geopolitical map for the setting of The Lost of the Marshes" />
|
||||
<p class="mb-4 mt-2">Updated to include locations up to Chapter 11.</p>
|
||||
<Image
|
||||
class="mx-auto w-full max-w-4xl break-before-page"
|
||||
src={mapImage}
|
||||
alt="A geopolitical map for the setting of The Lost of the Marshes"
|
||||
/>
|
||||
</section>
|
||||
</GalleryLayout>
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,68 +1,140 @@
|
|||
---
|
||||
import { getCollection } from 'astro:content';
|
||||
import { slug } from 'github-slugger'
|
||||
import GalleryLayout from '../layouts/GalleryLayout.astro';
|
||||
import { getCollection } from "astro:content";
|
||||
import { slug } from "github-slugger";
|
||||
import GalleryLayout from "../layouts/GalleryLayout.astro";
|
||||
|
||||
const [stories, games] = await Promise.all([getCollection('stories'), getCollection('games')]);
|
||||
const tagsSet = new Set<string>()
|
||||
const seriesList: Record<string, string> = {}
|
||||
stories.filter(story => !story.data.isDraft).forEach(story => {
|
||||
story.data.tags.forEach(tag => {
|
||||
tagsSet.add(tag)
|
||||
})
|
||||
if (story.data.series) {
|
||||
const [series, url] = Object.entries(story.data.series)[0]
|
||||
if (seriesList[series]) {
|
||||
if (seriesList[series] !== url) {
|
||||
throw new Error(`Mismatched series "${series}": tried to assign different links "${seriesList[series]}" and "${url}"`)
|
||||
const [stories, games] = await Promise.all([getCollection("stories"), getCollection("games")]);
|
||||
const tagsSet = new Set<string>();
|
||||
const seriesList: Record<string, string> = {};
|
||||
stories
|
||||
.filter((story) => !story.data.isDraft)
|
||||
.forEach((story) => {
|
||||
story.data.tags.forEach((tag) => {
|
||||
tagsSet.add(tag);
|
||||
});
|
||||
if (story.data.series) {
|
||||
const [series, url] = Object.entries(story.data.series)[0];
|
||||
if (seriesList[series]) {
|
||||
if (seriesList[series] !== url) {
|
||||
throw new Error(
|
||||
`Mismatched series "${series}": tried to assign different links "${seriesList[series]}" and "${url}"`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
seriesList[series] = url;
|
||||
}
|
||||
} else {
|
||||
seriesList[series] = url
|
||||
}
|
||||
}
|
||||
})
|
||||
games.filter(game => !game.data.isDraft).forEach(game => {
|
||||
game.data.tags.forEach(tag => {
|
||||
tagsSet.add(tag)
|
||||
})
|
||||
if (game.data.series) {
|
||||
const [series, url] = Object.entries(game.data.series)[0]
|
||||
if (seriesList[series]) {
|
||||
if (seriesList[series] !== url) {
|
||||
throw new Error(`Mismatched series "${series}": tried to assign different links "${seriesList[series]}" and "${url}"`)
|
||||
});
|
||||
games
|
||||
.filter((game) => !game.data.isDraft)
|
||||
.forEach((game) => {
|
||||
game.data.tags.forEach((tag) => {
|
||||
tagsSet.add(tag);
|
||||
});
|
||||
if (game.data.series) {
|
||||
const [series, url] = Object.entries(game.data.series)[0];
|
||||
if (seriesList[series]) {
|
||||
if (seriesList[series] !== url) {
|
||||
throw new Error(
|
||||
`Mismatched series "${series}": tried to assign different links "${seriesList[series]}" and "${url}"`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
seriesList[series] = url;
|
||||
}
|
||||
} else {
|
||||
seriesList[series] = url
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const categorizedTags: Record<string, string[]> = {
|
||||
'Types of vore': ['oral vore', 'anal vore', 'cock vore', 'unbirth', 'tail vore', 'slit vore', 'sheath vore', 'nipple vore', 'chest maw vore'],
|
||||
'Body types': ['anthro predator', 'feral predator', 'taur predator', 'ambiguous predator', 'human prey', 'anthro prey', 'feral prey', 'ambiguous prey'],
|
||||
'Genders': ['male predator', 'trans male predator', 'female predator', 'non-binary predator', 'ambiguous gender predator', 'male prey', 'female prey', 'trans female prey', 'non-binary prey', 'ambiguous gender prey'],
|
||||
'Relative size': ['macro predator', 'micro prey', 'size difference', 'similar size', 'same size', 'smaller predator'],
|
||||
'Willingness': ['willing predator', 'semi-willing predator', 'unwilling predator', 'asleep predator', 'willing prey', 'semi-willing prey', 'unwilling prey', 'asleep prey'],
|
||||
'Vore-related scenarios': ['point of view', 'regurgitation', 'long-term endo', 'perma endo', 'implied perma endo', 'full tour', 'implied full tour', 'prey transfer', 'object vore', 'role reversal', 'nested vore', 'multiple prey', 'messy stomach', 'bladder vore', 'soul vore'],
|
||||
'Sexual content': ['nudity', 'masturbation', 'straight sex', 'gay sex', 'lesbian sex', 'orgy'],
|
||||
'Other kinks': ['hyper', 'egg play', 'transformation', 'netorare', 'sizeplay', 'inflation', 'daddy play', 'BDSM', 'dubcon'],
|
||||
'Type of content': ['request', 'commission', 'flash fiction'],
|
||||
'Recurring characters': ['Sam Brendan', 'Beetle', 'Muno'],
|
||||
}
|
||||
"Types of vore": [
|
||||
"oral vore",
|
||||
"anal vore",
|
||||
"cock vore",
|
||||
"unbirth",
|
||||
"tail vore",
|
||||
"slit vore",
|
||||
"sheath vore",
|
||||
"nipple vore",
|
||||
"chest maw vore",
|
||||
],
|
||||
"Body types": [
|
||||
"anthro predator",
|
||||
"feral predator",
|
||||
"taur predator",
|
||||
"ambiguous predator",
|
||||
"human prey",
|
||||
"anthro prey",
|
||||
"feral prey",
|
||||
"ambiguous prey",
|
||||
],
|
||||
Genders: [
|
||||
"male predator",
|
||||
"trans male predator",
|
||||
"female predator",
|
||||
"non-binary predator",
|
||||
"ambiguous gender predator",
|
||||
"male prey",
|
||||
"female prey",
|
||||
"trans female prey",
|
||||
"non-binary prey",
|
||||
"ambiguous gender prey",
|
||||
],
|
||||
"Relative size": ["macro predator", "micro prey", "size difference", "similar size", "same size", "smaller predator"],
|
||||
Willingness: [
|
||||
"willing predator",
|
||||
"semi-willing predator",
|
||||
"unwilling predator",
|
||||
"asleep predator",
|
||||
"willing prey",
|
||||
"semi-willing prey",
|
||||
"unwilling prey",
|
||||
"asleep prey",
|
||||
],
|
||||
"Vore-related scenarios": [
|
||||
"point of view",
|
||||
"regurgitation",
|
||||
"long-term endo",
|
||||
"perma endo",
|
||||
"implied perma endo",
|
||||
"full tour",
|
||||
"implied full tour",
|
||||
"prey transfer",
|
||||
"object vore",
|
||||
"role reversal",
|
||||
"nested vore",
|
||||
"multiple prey",
|
||||
"messy stomach",
|
||||
"bladder vore",
|
||||
"soul vore",
|
||||
],
|
||||
"Sexual content": ["nudity", "masturbation", "straight sex", "gay sex", "lesbian sex", "orgy"],
|
||||
"Other kinks": [
|
||||
"hyper",
|
||||
"egg play",
|
||||
"transformation",
|
||||
"netorare",
|
||||
"sizeplay",
|
||||
"inflation",
|
||||
"daddy play",
|
||||
"BDSM",
|
||||
"dubcon",
|
||||
],
|
||||
"Type of content": ["request", "commission", "flash fiction"],
|
||||
"Recurring characters": ["Sam Brendan", "Beetle", "Muno"],
|
||||
};
|
||||
|
||||
Object.entries(categorizedTags).forEach(([category, tagList]) => {
|
||||
tagList.forEach(tag => {
|
||||
tagList.forEach((tag) => {
|
||||
if (!tagsSet.delete(tag)) {
|
||||
throw new Error(`Tag "${tag}" was added to category "${category}" but isn't present in any content`)
|
||||
throw new Error(`Tag "${tag}" was added to category "${category}" but isn't present in any content`);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
if (tagsSet.size > 0) {
|
||||
console.log("The following tags have no category:", [...tagsSet])
|
||||
categorizedTags['Uncategorized tags'] = [...tagsSet]
|
||||
console.log("The following tags have no category:", [...tagsSet]);
|
||||
categorizedTags["Uncategorized tags"] = [...tagsSet];
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle={`Tags`}>
|
||||
|
@ -70,16 +142,34 @@ if (tagsSet.size > 0) {
|
|||
<p class="my-4">You can find all content with a specific tag by selecting it below from the appropriate category.</p>
|
||||
<section class="my-2" aria-labelledby="category-series">
|
||||
<h2 id="category-series" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Series</h2>
|
||||
<ul class="pl-8 list-disc">
|
||||
{Object.entries(seriesList).map(([series, url]) => <li><a class="underline text-link" href={url}>{series}</a></li>)}
|
||||
<ul class="list-disc pl-8">
|
||||
{
|
||||
Object.entries(seriesList).map(([series, url]) => (
|
||||
<li>
|
||||
<a class="text-link underline" href={url}>
|
||||
{series}
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
{Object.entries(categorizedTags).map(([category, tagList]) =>
|
||||
<section class="my-2" aria-labelledby={`category-${slug(category)}`}>
|
||||
<h2 id={`category-${slug(category)}`} class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">{category}</h2>
|
||||
<ul class="px-2 flex flex-wrap gap-x-2 gap-y-2 max-w-3xl">
|
||||
{tagList.map(tag => <li class="text-sm rounded-full shadow-sm px-3 py-1 dark:bg-bm-600 dark:text-white bg-bm-300 text-black"><a class="focus:underline hover:underline" href={`/tags/${slug(tag)}`}>{tag}</a></li>)}
|
||||
</ul>
|
||||
</section>
|
||||
)}
|
||||
{
|
||||
Object.entries(categorizedTags).map(([category, tagList]) => (
|
||||
<section class="my-2" aria-labelledby={`category-${slug(category)}`}>
|
||||
<h2 id={`category-${slug(category)}`} class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
{category}
|
||||
</h2>
|
||||
<ul class="flex max-w-3xl flex-wrap gap-x-2 gap-y-2 px-2">
|
||||
{tagList.map((tag) => (
|
||||
<li class="rounded-full bg-bm-300 px-3 py-1 text-sm text-black shadow-sm dark:bg-bm-600 dark:text-white">
|
||||
<a class="hover:underline focus:underline" href={`/tags/${slug(tag)}`}>
|
||||
{tag}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,76 +1,102 @@
|
|||
---
|
||||
import { Image } from 'astro:assets'
|
||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
||||
import { slug } from 'github-slugger'
|
||||
import { Image } from "astro:assets";
|
||||
import { type CollectionEntry, getCollection } from "astro:content";
|
||||
import { slug } from "github-slugger";
|
||||
import { getUnixTime } from "date-fns";
|
||||
import GalleryLayout from '../../layouts/GalleryLayout.astro';
|
||||
import GalleryLayout from "../../layouts/GalleryLayout.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const [stories, games] = await Promise.all([getCollection('stories'), getCollection('games')]);
|
||||
const tags = new Set<string>()
|
||||
stories.forEach(story => {
|
||||
story.data.tags.forEach(tag => {
|
||||
tags.add(tag)
|
||||
})
|
||||
})
|
||||
games.forEach(game => {
|
||||
game.data.tags.forEach(tag => {
|
||||
tags.add(tag)
|
||||
})
|
||||
})
|
||||
return [...tags].filter(tag => !['The Lost of the Marshes'].includes(tag)).map(tag => ({
|
||||
params: { slug: slug(tag) },
|
||||
props: {
|
||||
tag,
|
||||
stories: stories.filter(story => !story.data.isDraft && story.data.tags.includes(tag)).sort((a, b) => getUnixTime(b.data.pubDate!) - getUnixTime(a.data.pubDate!)),
|
||||
games: games.filter(game => !game.data.isDraft && game.data.tags.includes(tag)).sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate)),
|
||||
},
|
||||
}));
|
||||
const [stories, games] = await Promise.all([getCollection("stories"), getCollection("games")]);
|
||||
const tags = new Set<string>();
|
||||
stories.forEach((story) => {
|
||||
story.data.tags.forEach((tag) => {
|
||||
tags.add(tag);
|
||||
});
|
||||
});
|
||||
games.forEach((game) => {
|
||||
game.data.tags.forEach((tag) => {
|
||||
tags.add(tag);
|
||||
});
|
||||
});
|
||||
return [...tags]
|
||||
.filter((tag) => !["The Lost of the Marshes"].includes(tag))
|
||||
.map((tag) => ({
|
||||
params: { slug: slug(tag) },
|
||||
props: {
|
||||
tag,
|
||||
stories: stories
|
||||
.filter((story) => !story.data.isDraft && story.data.tags.includes(tag))
|
||||
.sort((a, b) => getUnixTime(b.data.pubDate!) - getUnixTime(a.data.pubDate!)),
|
||||
games: games
|
||||
.filter((game) => !game.data.isDraft && game.data.tags.includes(tag))
|
||||
.sort((a, b) => getUnixTime(b.data.pubDate) - getUnixTime(a.data.pubDate)),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
type Props = {
|
||||
tag: string
|
||||
stories: CollectionEntry<'stories'>[]
|
||||
games: CollectionEntry<'games'>[]
|
||||
}
|
||||
tag: string;
|
||||
stories: CollectionEntry<"stories">[];
|
||||
games: CollectionEntry<"games">[];
|
||||
};
|
||||
|
||||
const { tag, stories, games } = Astro.props
|
||||
const { tag, stories, games } = Astro.props;
|
||||
---
|
||||
|
||||
<GalleryLayout pageTitle={`Works tagged "${tag}"`}>
|
||||
<h1 class="m-2 text-2xl font-semibold text-stone-800 dark:text-stone-100">Works tagged "{tag}"</h1>
|
||||
{stories.length > 0 &&
|
||||
<section class="my-2" aria-labelledby="content-stories">
|
||||
<h2 id="content-stories" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Stories</h2>
|
||||
<ul class="flex justify-center md:justify-normal flex-wrap text-center gap-4">
|
||||
{stories.map(story =>
|
||||
<li>
|
||||
<a class="focus:underline hover:underline text-link" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail &&
|
||||
<Image class="w-48" src={story.data.thumbnail} alt={`Thumbnail for ${story.data.title}`} width={story.data.thumbnailWidth} height={story.data.thumbnailHeight} />
|
||||
}
|
||||
<div class="text-sm max-w-48">{story.data.title}</div>
|
||||
</a>
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
{
|
||||
stories.length > 0 && (
|
||||
<section class="my-2" aria-labelledby="content-stories">
|
||||
<h2 id="content-stories" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
Stories
|
||||
</h2>
|
||||
<ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
|
||||
{stories.map((story) => (
|
||||
<li class="break-inside-avoid">
|
||||
<a class="text-link hover:underline focus:underline" href={`/stories/${story.slug}`}>
|
||||
{story.data.thumbnail && (
|
||||
<Image
|
||||
class="w-48"
|
||||
src={story.data.thumbnail}
|
||||
alt={`Thumbnail for ${story.data.title}`}
|
||||
width={story.data.thumbnailWidth}
|
||||
height={story.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<div class="max-w-48 text-sm">{story.data.title}</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
{games.length > 0 &&
|
||||
<section class="my-2" aria-labelledby="content-games">
|
||||
<h2 id="content-games" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">Games</h2>
|
||||
<ul class="flex justify-center md:justify-normal flex-wrap text-center gap-4">
|
||||
{games.map(game =>
|
||||
<li>
|
||||
<a class="focus:underline hover:underline text-link" href={`/games/${game.slug}`}>
|
||||
{game.data.thumbnail &&
|
||||
<Image class="w-48" src={game.data.thumbnail} alt={`Thumbnail for ${game.data.title}`} width={game.data.thumbnailWidth} height={game.data.thumbnailHeight} />
|
||||
}
|
||||
<div class="text-sm max-w-48">{game.data.title}</div>
|
||||
</a>
|
||||
</li>)
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
{
|
||||
games.length > 0 && (
|
||||
<section class="my-2" aria-labelledby="content-games">
|
||||
<h2 id="content-games" class="p-2 text-xl font-semibold text-stone-800 dark:text-stone-100">
|
||||
Games
|
||||
</h2>
|
||||
<ul class="flex flex-wrap justify-center gap-4 text-center md:justify-normal">
|
||||
{games.map((game) => (
|
||||
<li class="break-inside-avoid">
|
||||
<a class="text-link hover:underline focus:underline" href={`/games/${game.slug}`}>
|
||||
{game.data.thumbnail && (
|
||||
<Image
|
||||
class="w-48"
|
||||
src={game.data.thumbnail}
|
||||
alt={`Thumbnail for ${game.data.title}`}
|
||||
width={game.data.thumbnailWidth}
|
||||
height={game.data.thumbnailHeight}
|
||||
/>
|
||||
)}
|
||||
<div class="max-w-48 text-sm">{game.data.title}</div>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
</GalleryLayout>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
var colorScheme = localStorage.getItem('colorScheme')
|
||||
if (colorScheme == null) {
|
||||
localStorage.setItem('colorScheme', 'auto')
|
||||
} else if (colorScheme == 'auto') {
|
||||
colorScheme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
const bodyClassList = document.querySelector('body')!.classList
|
||||
if (colorScheme === 'dark') {
|
||||
bodyClassList.add('dark')
|
||||
}
|
||||
|
||||
function toggleColorScheme() {
|
||||
if (colorScheme === 'dark') {
|
||||
colorScheme = 'light'
|
||||
bodyClassList.remove('dark')
|
||||
} else {
|
||||
colorScheme = 'dark'
|
||||
bodyClassList.add('dark')
|
||||
}
|
||||
localStorage.setItem('colorScheme', colorScheme)
|
||||
}
|
||||
|
||||
const buttonDarkMode = document.querySelector('#button-dark-mode')
|
||||
if (buttonDarkMode) {
|
||||
buttonDarkMode.addEventListener('click', toggleColorScheme)
|
||||
} else {
|
||||
console.log("Couldn't find button to toggle dark mode")
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
if (window.location.pathname === '/age-verification') {
|
||||
document.querySelector('#age-verification-reject')!.addEventListener('click', () => {
|
||||
window.location.href = 'about:blank'
|
||||
})
|
||||
document.querySelector('#age-verification-accept')!.addEventListener('click', () => {
|
||||
localStorage.setItem('ageVerified', 'true')
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
window.location.href = decodeURIComponent(params.get('redirect') || '/')
|
||||
})
|
||||
} else {
|
||||
const ageVerified = localStorage.getItem('ageVerified')
|
||||
if (ageVerified !== 'true') {
|
||||
localStorage.setItem('ageVerified', 'false')
|
||||
window.location.href = `/age-verification?redirect=${encodeURIComponent(window.location.href)}`
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.text-link, .prose a {
|
||||
.text-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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,65 @@
|
|||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
import defaultTheme from "tailwindcss/defaultTheme";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
darkMode: ['variant', '@media not print { .dark & }'],
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
darkMode: ["variant", "@media not print { .dark & }"],
|
||||
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['"Noto Sans Variable"', ...defaultTheme.fontFamily.sans],
|
||||
serif: ['"Noto Serif Variable"', ...defaultTheme.fontFamily.serif],
|
||||
},
|
||||
colors: {
|
||||
bm: {
|
||||
300: 'rgb(153 230 95 / <alpha-value>)',
|
||||
400: 'rgb(125 208 90 / <alpha-value>)',
|
||||
500: 'rgb(72 168 79 / <alpha-value>)',
|
||||
600: 'rgb(57 142 72 / <alpha-value>)',
|
||||
300: "rgb(153 230 95 / <alpha-value>)",
|
||||
400: "rgb(125 208 90 / <alpha-value>)",
|
||||
500: "rgb(72 168 79 / <alpha-value>)",
|
||||
600: "rgb(57 142 72 / <alpha-value>)",
|
||||
},
|
||||
},
|
||||
backgroundImage: {
|
||||
radial: 'radial-gradient(var(--tw-gradient-stops))',
|
||||
radial: "radial-gradient(var(--tw-gradient-stops))",
|
||||
},
|
||||
typography: ({ theme }) => ({
|
||||
story: {
|
||||
css: {
|
||||
'--tw-prose-body': theme('colors.stone[800]'),
|
||||
'--tw-prose-headings': theme('colors.stone[900]'),
|
||||
'--tw-prose-lead': theme('colors.stone[700]'),
|
||||
'--tw-prose-links': theme('colors.bm[500]'),
|
||||
'--tw-prose-bold': theme('colors.stone[900]'),
|
||||
'--tw-prose-counters': theme('colors.stone[600]'),
|
||||
'--tw-prose-bullets': theme('colors.stone[500]'),
|
||||
'--tw-prose-hr': theme('colors.stone[300]'),
|
||||
'--tw-prose-quotes': theme('colors.stone[900]'),
|
||||
'--tw-prose-quote-borders': theme('colors.stone[300]'),
|
||||
'--tw-prose-captions': theme('colors.stone[700]'),
|
||||
'--tw-prose-code': theme('colors.stone[900]'),
|
||||
'--tw-prose-pre-code': theme('colors.stone[100]'),
|
||||
'--tw-prose-pre-bg': theme('colors.stone[900]'),
|
||||
'--tw-prose-th-borders': theme('colors.stone[300]'),
|
||||
'--tw-prose-td-borders': theme('colors.stone[200]'),
|
||||
'--tw-prose-invert-body': theme('colors.stone[100]'),
|
||||
'--tw-prose-invert-headings': theme('colors.white'),
|
||||
'--tw-prose-invert-lead': theme('colors.stone[300]'),
|
||||
'--tw-prose-invert-links': theme('colors.bm[400]'),
|
||||
'--tw-prose-invert-bold': theme('colors.white'),
|
||||
'--tw-prose-invert-counters': theme('colors.stone[400]'),
|
||||
'--tw-prose-invert-bullets': theme('colors.stone[500]'),
|
||||
'--tw-prose-invert-hr': theme('colors.stone[700]'),
|
||||
'--tw-prose-invert-quotes': theme('colors.stone[100]'),
|
||||
'--tw-prose-invert-quote-borders': theme('colors.stone[700]'),
|
||||
'--tw-prose-invert-captions': theme('colors.stone[400]'),
|
||||
'--tw-prose-invert-code': theme('colors.white'),
|
||||
'--tw-prose-invert-pre-code': theme('colors.stone[300]'),
|
||||
'--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
|
||||
'--tw-prose-invert-th-borders': theme('colors.stone[600]'),
|
||||
'--tw-prose-invert-td-borders': theme('colors.stone[700]'),
|
||||
"--tw-prose-body": theme("colors.stone[800]"),
|
||||
"--tw-prose-headings": theme("colors.stone[900]"),
|
||||
"--tw-prose-lead": theme("colors.stone[700]"),
|
||||
"--tw-prose-links": theme("colors.bm[500]"),
|
||||
"--tw-prose-bold": theme("colors.stone[900]"),
|
||||
"--tw-prose-counters": theme("colors.stone[600]"),
|
||||
"--tw-prose-bullets": theme("colors.stone[500]"),
|
||||
"--tw-prose-hr": theme("colors.stone[300]"),
|
||||
"--tw-prose-quotes": theme("colors.stone[900]"),
|
||||
"--tw-prose-quote-borders": theme("colors.stone[300]"),
|
||||
"--tw-prose-captions": theme("colors.stone[700]"),
|
||||
"--tw-prose-code": theme("colors.stone[900]"),
|
||||
"--tw-prose-pre-code": theme("colors.stone[100]"),
|
||||
"--tw-prose-pre-bg": theme("colors.stone[900]"),
|
||||
"--tw-prose-th-borders": theme("colors.stone[300]"),
|
||||
"--tw-prose-td-borders": theme("colors.stone[200]"),
|
||||
"--tw-prose-invert-body": theme("colors.stone[100]"),
|
||||
"--tw-prose-invert-headings": theme("colors.white"),
|
||||
"--tw-prose-invert-lead": theme("colors.stone[300]"),
|
||||
"--tw-prose-invert-links": theme("colors.bm[400]"),
|
||||
"--tw-prose-invert-bold": theme("colors.white"),
|
||||
"--tw-prose-invert-counters": theme("colors.stone[400]"),
|
||||
"--tw-prose-invert-bullets": theme("colors.stone[500]"),
|
||||
"--tw-prose-invert-hr": theme("colors.stone[700]"),
|
||||
"--tw-prose-invert-quotes": theme("colors.stone[100]"),
|
||||
"--tw-prose-invert-quote-borders": theme("colors.stone[700]"),
|
||||
"--tw-prose-invert-captions": theme("colors.stone[400]"),
|
||||
"--tw-prose-invert-code": theme("colors.white"),
|
||||
"--tw-prose-invert-pre-code": theme("colors.stone[300]"),
|
||||
"--tw-prose-invert-pre-bg": "rgb(0 0 0 / 50%)",
|
||||
"--tw-prose-invert-th-borders": theme("colors.stone[600]"),
|
||||
"--tw-prose-invert-td-borders": theme("colors.stone[700]"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
],
|
||||
}
|
||||
plugins: [require("@tailwindcss/typography")],
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue