add more blog styles and fix fic styles

This commit is contained in:
haetae 2025-02-23 03:05:38 -05:00
parent c3cfbccfe9
commit c8bc536cfe
54 changed files with 196 additions and 5391 deletions

View File

@ -1,4 +1,3 @@
{
"deno.enable": true,
"typescript.tsdk": "node_modules/typescript/lib"
}

5262
package-lock.json generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,6 @@
import { defineAction, ActionError } from "astro:actions";
import { db, Guestbook } from "astro:db";
import { z } from "astro:schema";
import { checkProfanity } from "./utils";
export const server = {
guestbook: defineAction({
@ -10,7 +9,7 @@ export const server = {
username: z.string(),
website: z.string().url().optional(),
body: z.string(),
honeypot: z.string().max(0).nullish(),
password: z.string().regex(/\biliad/g),
}),
handler: async (input) => {
if (input.username === "") {
@ -27,26 +26,18 @@ export const server = {
});
}
if (input.honeypot !== undefined) {
if (input.password !== "iliad") {
throw new ActionError({
code: "UNPROCESSABLE_CONTENT",
message: "Oh dear, something went wrong!",
message: "Whoops, something went wrong!",
});
}
const filter = await checkProfanity(input.body);
if (filter) {
return await db.insert(Guestbook).values({
username: input.username,
website: input.website,
body: input.body,
}).returning();
} else {
throw new ActionError({
code: "BAD_REQUEST",
message: "You can't curse!",
});
}
return await db.insert(Guestbook).values({
username: input.username,
website: input.website,
body: input.body,
}).returning();
},
}),
}

View File

@ -1,29 +0,0 @@
export async function checkProfanity(message: string) {
try {
const response = await fetch("https://vector.profanity.dev", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message }),
});
if (!response.ok) {
throw new Error(`There was an error checking your message: ${response.status}`);
}
const json = await response.json();
if (json.isProfanity) {
throw new Error("Please don't cuss!");
}
return true;
} catch (e) {
console.error(e);
return false;
}
}
async function checkSpam(form: FormData) {
const token = form.append("procaptcha-response", import.meta.env.SPAM_SITE_KEY);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

After

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 847 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 B

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 868 B

After

Width:  |  Height:  |  Size: 331 B

View File

@ -10,6 +10,8 @@
--sans-font: "MLSS", 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
--arial-font: "Arial Pixel", Arial, Helvetica, sans-serif;
--pmd-font: "Wonder Mail", Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
--mplus-10-font: "PixelMPlus 10", "MS Gothic", system-ui-ja, system-ui-zh-cn, system-ui-zh-tw, system-ui-zh-hk, system-ui-ko, sans-serif;
--mplus-12-font: "PixelMPlus 12", "MS Gothic", system-ui-ja, system-ui-zh-cn, system-ui-zh-tw, system-ui-zh-hk, system-ui-ko, sans-serif;
@media screen and (prefers-color-scheme: light) {
--bg-color: #e6f2ef;

View File

@ -28,6 +28,30 @@
font-weight: normal;
}
@font-face {
font-family: "PixelMPlus 10";
src: url("/fonts/PixelMplus10-Regular.woff2") format("woff");
font-weight: normal;
}
@font-face {
font-family: "PixelMPlus 10";
src: url("/fonts/PixelMplus10-Bold.woff2") format("woff");
font-weight: bold;
}
@font-face {
font-family: "PixelMPlus 12";
src: url("/fonts/PixelMplus12-Regular.woff2") format("woff");
font-weight: normal;
}
@font-face {
font-family: "PixelMPlus 12";
src: url("/fonts/PixelMplus12-Bold.woff2") format("woff");
font-weight: bold;
}
@font-face {
font-family: "Redaction 35";
src: url("/fonts/Redaction_35-Regular.woff2") format("woff2");

View File

@ -1,7 +1,12 @@
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
import { rssSchema } from "@astrojs/rss";
import MarkdownIt from "markdown-it";
import moods from "@/utils/moods";
const parser = new MarkdownIt({
html: true,
breaks: true,
});
const blog = defineCollection({
loader: glob({ pattern: "*.md", base: "./src/content/blog" }),
@ -16,32 +21,32 @@ const blog = defineCollection({
}),
});
function generateFicSlug({ entry, data }: { entry: string, data: any }): string {
if (data.slug) {
return data.slug as string;
}
return entry.split("/")[0];
}
const source = "./src/content/fics";
const chapters = defineCollection({
loader: glob({ pattern: "**/*.{md,mdx,mdoc}", base: source }),
schema: z.object({
title: z.string(),
publishedAt: z.coerce.date(),
notes: z.ostring(),
notes: z.ostring().transform(notes => parser.renderInline(notes ?? "", {})),
lastModified: z.coerce.date().optional(),
sortOrder: z.number(),
}),
});
const fics = defineCollection({
loader: glob({ pattern: "**/*.{yml,yaml}", base: source, generateId: generateFicSlug }),
loader: glob({
pattern: "**/*.{yml,yaml}",
base: source,
generateId: ({entry, data}) => {
if (data.slug) return data.slug as string;
return entry.split("/")[0];
}
}),
schema: z.object({
title: z.string(),
series: z.array(z.string()),
publishedAt: z.coerce.date(),
summary: z.string(),
summary: z.string().transform(summary => parser.renderInline(summary, {})),
}),
});

View File

@ -3,7 +3,7 @@ title: not a sin
publishedAt: 2024-02-01
sortOrder: 1
notes:
hello is this is a <em>test</em>, this is going to be a really long text! this is where all the author's notes will go.
hello is this is a *test*, this is going to be a really long text! this is where all the author's notes will go.
fhi
---

View File

@ -3,8 +3,9 @@ title: this IS a sin
publishedAt: 2024-03-02
sortOrder: 2
notes:
<p>hey there this is just a test </p>
<p>him!!! <em>lol!!!</em></p>
hey there this is just a test
him!!! *lol!!!*
---
Lorem ipsum dolor sit amet consectetur adipisicing elit. Reiciendis **reprehenderit** provident ullam sint *explicabo* quas esse velit, voluptatum eveniet, tempora illum expedita, eum voluptate! Odio excepturi similique ex quos tenetur.

View File

@ -81,30 +81,32 @@ dayjs.extend(utc);
{currently && (
<aside>
<h2>Current</h2>
{currently?.mood && (
<dl>
<dt>Current mood</dt>
<dd>
<Image src={`/src/assets/moods/${moods.find(mood => mood === currently?.mood)}.png`} width="32" height="32" alt={currently.mood} />
</dd>
<dt class="title">Mood</dt>
<dd class="desc">
<Image src={`/src/assets/moods/${moods.find(mood => mood === currently?.mood)}.png`} width={36} height={36} alt="" />
{currently.mood}
</dd>
</dl>
)}
{currently.playing && (
<dl>
<dt>Currently playing</dt>
<dd>{currently.playing}</dd>
<dt class="title">Game</dt>
<dd class="desc">{currently.playing}</dd>
</dl>
)}
{currently.watching && (
<dl>
<dt>Currently watching</dt>
<dd>{currently.watching}</dd>
<dt class="title">Show</dt>
<dd class="desc">{currently.watching}</dd>
</dl>
)}
{currently.reading && (
<dl>
<dt>Currently reading</dt>
<dd>{currently.reading}</dd>
<dt class="title">Book</dt>
<dd class="desc">{currently.reading}</dd>
</dl>
)}
{currently.listening && (
@ -135,13 +137,18 @@ dayjs.extend(utc);
--bg-7: #ccddee;
--bg-8: #8888bb;
--border-0: #000;
--border-1: #6699aa;
--border-2: #7799bb;
--border-3: #88aabb;
--border-4: #99bbcc;
--border-5: #bbccdd;
--border-1: #334466;
--border-2: #6699aa;
--border-3: #7799bb;
--border-4: #88aabb;
--border-5: #99bbcc;
--border-6: #bbccdd;
}
h1 { font: bold 1rem var(--arial-font); }
h2 {
font: normal 1.5rem var(--mplus-12-font);
text-transform: uppercase;
}
main {
font: 1rem var(--arial-font);
display: grid;
@ -172,17 +179,19 @@ dayjs.extend(utc);
background-color: var(--bg-3);
padding: 2px;
margin: 18px 6px;
border: 2px solid var(--border-5);
border: 2px solid var(--border-6);
}
h1 {
padding: 6px 2px;
border-block: 2px solid var(--border-2);
border-block: 2px solid var(--border-3);
.title {
display: inline-block;
background-color: var(--bg-8);
color: var(--bg-0);
font-weight: normal;
text-transform: uppercase;
padding: 5px 2px;
text-align: center;
width: 100%;
@ -200,6 +209,8 @@ dayjs.extend(utc);
border-bottom: 2px solid var(--bg-4);
.item {
font-family: var(--mplus-10-font);
font-size: 1.254rem;
display: flex;
justify-content: space-between;
align-items: center;
@ -219,7 +230,7 @@ dayjs.extend(utc);
&:hover {
a { background-color: var(--bg-7); }
time { background-color: var(--border-5); }
time { background-color: var(--border-6); }
}
}
&:last-child { border: none; }
@ -232,7 +243,7 @@ dayjs.extend(utc);
&:first-child { margin-top: 4px; }
&:last-child {
border-bottom: 2px solid var(--border-1);
border-bottom: 2px solid var(--border-2);
margin-bottom: 4px;
}
}
@ -251,13 +262,25 @@ dayjs.extend(utc);
padding: 36px 16px 74px 20px;
.inner {
border: 2px solid var(--border-1);
border: 2px solid var(--border-2);
border-image: var(--innerBorder) 3 2 2 fill / 6px 4px 4px;
margin: 14px 4px 8px;
padding: 4px 2px 2px;
background-color: var(--bg-0);
}
.title {
font: normal 1.5rem var(--mplus-12-font);
background-color: var(--bg-2);
padding: 2px 6px;
}
.desc {
background-color: var(--bg-4);
padding: 4px 6px;
flex: 1;
}
header {
border-bottom: 2px solid var(--bg-6);
@ -265,39 +288,26 @@ dayjs.extend(utc);
text-align: center;
margin: 6px 2px;
padding: 2px 0;
background-color: var(--border-5);
background-color: var(--border-6);
}
.info {
display: flex;
align-items: baseline;
gap: 2px;
border-style: solid;
border-width: 2px;
border-top-color: var(--bg-2);
border-right-color: var(--bg-2);
border-left-color: var(--bg-1);
border-bottom-color: var(--bg-1);
border-color: var(--bg-2) var(--bg-2) var(--bg-1) var(--bg-1);
padding: 2px 4px;
time, .section {
display: flex;
width: 100%;
}
.title {
background-color: var(--bg-2);
padding: 4px 6px;
}
.desc {
background-color: var(--bg-4);
padding: 4px 6px;
flex: 1;
}
}
hr {
background-color: var(--border-4);
background-color: var(--border-5);
border: none;
height: 2px;
}
@ -328,27 +338,39 @@ dayjs.extend(utc);
}
aside {
border-top: 2px solid var(--border-3);
display: grid;
grid-template-columns: auto 1fr;
grid-template-rows: repeat(4, auto);
border-top: 2px solid var(--border-4);
h2 {
display: grid;
place-content: center;
grid-area: 1 / 1 / -1;
padding-top: -2px;
padding-left: 2px;
height: 100%;
border-top: 2px solid var(--border-1);
border-right: 2px solid var(--border-4);
background-color: var(--bg-7);
}
dl {
display: flex;
align-items: center;
padding: 2px 6px;
border-bottom: 2px solid var(--border-3);
border-bottom: 2px solid var(--border-4);
border-top: 2px solid var(--border-1);
border-left: 2px solid var(--border-1);
dt {
background-color: var(--bg-2);
padding: 4px 6px;
&:first-of-type > dd {
display: flex;
align-items: center;
gap: 6px;
margin: -2px;
padding: 0 6px;
}
dd {
background-color: var(--bg-4);
padding: 4px 6px;
flex: 1;
}
&:first-child > dt ~ dd { padding: 0 6px; }
&:last-child { border: none; }
&:last-child { border-bottom: none; }
}
}
}

View File

@ -7,7 +7,7 @@ interface Props {
title: string;
ficTitle: string;
date: Date;
notes?: string;
notes?: any;
lastModified?: Date;
}
@ -30,7 +30,7 @@ dayjs.extend(utc);
)}
{notes && (
<blockquote>
<Fragment set:html={notes.split("\n").join("<br />")} />
<Fragment set:html={notes} />
</blockquote>
)}
</header>

View File

@ -38,7 +38,7 @@ dayjs.extend(utc);
<div>
<span class="title">Summary</span>
<blockquote class="data">
<Fragment set:html={fic.data.summary.split("\n").join("<br />")} />
<Fragment set:html={fic.data.summary} />
</blockquote>
</div>
<div class="links">

View File

@ -14,7 +14,9 @@ export const GET: APIRoute = async (context) => {
const fic = fics.find(({ id }) => id === context.params.ficId);
return rss({
title: `${fic?.data.title}`,
description: `${fic?.data.summary}`,
description: sanitize(parser.render(fic?.data.summary!), {
allowedTags: sanitize.defaults.allowedTags.concat(["br"]),
}),
site: context.site!,
items: chapters.map(chapter => ({
link: `/fics/${chapter.id}`,
@ -23,7 +25,7 @@ export const GET: APIRoute = async (context) => {
content: sanitize(parser.render(chapter.body!), {
allowedTags: sanitize.defaults.allowedTags.concat(["img"]),
}),
categories: fic?.data.series,
categories: fic?.data.series.concat(fic.data.title),
})),
stylesheet: "/pretty-feed-v3.xsl",
});

View File

@ -18,10 +18,15 @@ entries.sort((a, b) => b.date.valueOf() - a.date.valueOf());
<ThemeSwitch />
<main>
<h1>Guestbook</h1>
<details>
<summary>click me for a secret!</summary>
<noscript>you should put ILIAD, but in all lowercase, in the password field!</noscript>
<p id="cool-surprise"></p>
</details>
<form action={actions.guestbook} method="post">
<label for="username">Username</label>
<input type="text" name="username" id="username" required aria-describedby="username-error" />
<input type="text" name="username" id="username" aria-describedby="username-error" required />
{errors.username && <p id="username-error">{errors.username.join(",")}</p>}
<label for="website">Website (optional)</label>
@ -29,23 +34,26 @@ entries.sort((a, b) => b.date.valueOf() - a.date.valueOf());
{errors.website && <p id="website-error">{errors.website.join(",")}</p>}
<label for="body">Message</label>
<textarea name="body" id="body" rows="5" required aria-describedby="body-error"></textarea>
<textarea name="body" id="body" rows="5" aria-describedby="body-error" required></textarea>
{errors.body && <p id="body-error">{errors.body.join(",")}</p>}
<input type="hidden" name="honeypot" tabindex="-1" autocomplete="off" style="display:none" />
<button type="submit">Post</button>
<label for="password">Do you know the password?</label>
<input type="text" name="password" id="password" value="yes!" aria-describedby="password-error" required />
{errors.password && <p id="password-error">{errors.password.join(",")}</p>}
{errors.honeypot && <p>{errors.honeypot.join(",")}</p>}
<button type="submit">Post</button>
</form>
{entries.map(({ username, website, body, date }) => (
<article class="entry">
<h1>{username}</h1>
{website && <p><a href={website} target="_blank" referrerpolicy="no-referrer">website</a></p>}
<time datetime={dayjs(date).utc(true).toISOString()}>{dayjs(date).utc(true).format("MMMM DD, YYYY")}</time>
<div>
{body}
</div>
<header>
<h1>{username}</h1>
<time datetime={dayjs(date).utc(true).toISOString()}>
Posted on {dayjs(date).utc(true).format("MMMM DD, YYYY")}
</time>
{website && <a href={website} target="_blank" referrerpolicy="no-referrer">website</a>}
</header>
{body}
</article>
))}
</main>
@ -81,4 +89,34 @@ entries.sort((a, b) => b.date.valueOf() - a.date.valueOf());
padding: 2px 6px;
}
}
</style>
details {
& > summary {
list-style: none;
cursor: pointer;
font-weight: bold;
&::after {
content: "[+]";
}
[open] &::after {
content: "[_]";
}
}
}
</style>
<script>
const details = document.getElementsByTagName("details").item(0);
const pw = document.getElementById("cool-surprise");
let initial = "vyvnq";
details?.addEventListener("toggle", e => {
if ((e.target as HTMLDetailsElement).open) {
pw!.innerHTML = `if you read this, put <strong>${initial.replace(/[a-zA-Z]/g, c => {
// @ts-expect-error the below code works
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
})}</strong> for the password field!`
}
});
</script>

View File

@ -1,3 +1,15 @@
function aliasMood(emotion: string) {
switch (emotion) {
case "aggravated":
case "annoyed":
case "devious":
return "angry";
case "apathetic":
return "blah";
}
};
export default [
"accomplished",
"aggravated",

View File

@ -9,4 +9,4 @@
"~/*": ["src/components/*"],
}
}
}
}