move assets and implement meadow's guestbook
BIN
public/fonts/pixeab.woff2
Normal file
BIN
public/fonts/pixearg.woff2
Normal file
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 411 B |
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 689 B |
Before Width: | Height: | Size: 169 B After Width: | Height: | Size: 169 B |
Before Width: | Height: | Size: 731 B After Width: | Height: | Size: 731 B |
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 699 B |
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
Before Width: | Height: | Size: 252 B After Width: | Height: | Size: 252 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 255 B |
Before Width: | Height: | Size: 302 B After Width: | Height: | Size: 302 B |
@ -7,7 +7,7 @@
|
|||||||
--title-font: "Kiwi Soda", Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
|
--title-font: "Kiwi Soda", Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
|
||||||
--mono-font: "Departure Mono", ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
--mono-font: "Departure Mono", ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace;
|
||||||
--serif-font: "Redaction 35", 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif;
|
--serif-font: "Redaction 35", 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif;
|
||||||
--sans-font: "MLSS", 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
--sans-font: "Super Star", 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
||||||
--arial-font: "Arial Pixel", Arial, Helvetica, 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;
|
--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-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;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Arial Pixel";
|
font-family: "Arial Pixel";
|
||||||
src: url("/fonts/PIXEAR.woff2") format("woff2");
|
src: url("/fonts/pixearg.woff2") format("woff2");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Arial Pixel";
|
font-family: "Arial Pixel";
|
||||||
src: url("/fonts/PIXEAB.woff2") format("woff2");
|
src: url("/fonts/pixeab.woff2") format("woff2");
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,33 +23,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "MLSS";
|
font-family: "Super Star";
|
||||||
src: url("/fonts/mario-luigi-rpg-speech-text.woff2") format("woff2");
|
src: url("/fonts/mario-luigi-rpg-speech-text.woff2") format("woff2");
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "PixelMPlus 10";
|
font-family: "PixelMPlus 10";
|
||||||
src: url("/fonts/PixelMplus10-Regular.woff2") format("woff");
|
src: url("/fonts/PixelMplus10-Regular.woff2") format("woff2");
|
||||||
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-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "PixelMPlus 12";
|
font-family: "PixelMPlus 12";
|
||||||
src: url("/fonts/PixelMplus12-Bold.woff2") format("woff");
|
src: url("/fonts/PixelMplus12-Regular.woff2") format("woff2");
|
||||||
font-weight: bold;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
---
|
---
|
||||||
title: hmhmhm!
|
title: hmhmhm!
|
||||||
pubDate: 2024-02-04
|
pubDate: 2024-02-04
|
||||||
|
currently:
|
||||||
|
mood: sad
|
||||||
|
reading: sad fanfic
|
||||||
---
|
---
|
||||||
|
|
||||||
hey there this is a test
|
hey there this is a test
|
@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
import type { MarkdownLayoutProps } from "astro";
|
import type { MarkdownLayoutProps } from "astro";
|
||||||
import Layout from "./Layout.astro";
|
import Layout from "./Layout.astro";
|
||||||
import Navbar from "@/components/Navbar.astro";
|
import Navbar from "~/Navbar.astro";
|
||||||
import Figure from "@/components/Figure.astro";
|
import Figure from "~/Figure.astro";
|
||||||
|
|
||||||
import border from "@/assets/border.png";
|
import border from "$/images/border.png";
|
||||||
import frame from "@/assets/frame.png";
|
import frame from "$/images/frame.png";
|
||||||
|
|
||||||
type Props = MarkdownLayoutProps<{
|
type Props = MarkdownLayoutProps<{
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
|
@ -4,12 +4,12 @@ import { getCollection } from "astro:content";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import Layout from "./Layout.astro";
|
import Layout from "./Layout.astro";
|
||||||
import Navbar from "@/components/Navbar.astro";
|
import Navbar from "~/Navbar.astro";
|
||||||
import moods from "@/utils/moods";
|
import moods from "@/utils/moods";
|
||||||
|
|
||||||
import outerBBS from "@/assets/guild-bbs.png";
|
import outerBBS from "$/images/guild-bbs.png";
|
||||||
import innerBBS from "@/assets/guild-bbs-content.png";
|
import innerBBS from "$/images/guild-bbs-content.png";
|
||||||
import sideBBS from "@/assets/guild-bbs-list.png";
|
import sideBBS from "$/images/guild-bbs-list.png";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@ -36,7 +36,7 @@ dayjs.extend(utc);
|
|||||||
<main>
|
<main>
|
||||||
<nav id="blog-links">
|
<nav id="blog-links">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<hgroup>
|
<div class="nav-section">
|
||||||
<h1><span class="title">recent posts</span></h1>
|
<h1><span class="title">recent posts</span></h1>
|
||||||
<ul>
|
<ul>
|
||||||
{blog.map(entry => (
|
{blog.map(entry => (
|
||||||
@ -50,76 +50,74 @@ dayjs.extend(utc);
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</hgroup>
|
</div>
|
||||||
<hgroup>
|
<div class="nav-section">
|
||||||
<h1><span class="title">other links</span></h1>
|
<h1><span class="title">other links</span></h1>
|
||||||
<ul>
|
<ul>
|
||||||
<li><span class="item"><a href="/blog">archive</a></span></li>
|
<li><span class="item"><a href="/blog">archive</a></span></li>
|
||||||
<li><span class="item"><a href="/blog/rss.xml">rss feed</a></span></li>
|
<li><span class="item"><a href="/blog/rss.xml">rss feed</a></span></li>
|
||||||
</ul>
|
</ul>
|
||||||
</hgroup>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<section>
|
<article>
|
||||||
<article>
|
<div class="inner">
|
||||||
<div class="inner">
|
<header>
|
||||||
<header>
|
<h1>{title}</h1>
|
||||||
<h1>{title}</h1>
|
<hr />
|
||||||
<hr />
|
<div class="info">
|
||||||
<div class="info">
|
<time datetime={dayjs(date).utc(true).toISOString()}>
|
||||||
<time datetime={dayjs(date).utc(true).toISOString()}>
|
<span class="title">Date</span>
|
||||||
<span class="title">Date</span>
|
<span class="desc">{dayjs(date).utc(true).format("MMMM DD, YYYY")}</span>
|
||||||
<span class="desc">{dayjs(date).utc(true).format("MMMM DD, YYYY")}</span>
|
</time>
|
||||||
</time>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="content" data-simplebar>
|
|
||||||
<slot />
|
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
{currently && (
|
<div class="content">
|
||||||
<aside>
|
<slot />
|
||||||
<h2>Current</h2>
|
|
||||||
{currently?.mood && (
|
|
||||||
<dl>
|
|
||||||
<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 class="title">Game</dt>
|
|
||||||
<dd class="desc">{currently.playing}</dd>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
{currently.watching && (
|
|
||||||
<dl>
|
|
||||||
<dt class="title">Show</dt>
|
|
||||||
<dd class="desc">{currently.watching}</dd>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
{currently.reading && (
|
|
||||||
<dl>
|
|
||||||
<dt class="title">Book</dt>
|
|
||||||
<dd class="desc">{currently.reading}</dd>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
{currently.listening && (
|
|
||||||
<dl>
|
|
||||||
<dt>Currently listening</dt>
|
|
||||||
<dd>{currently.listening}</dd>
|
|
||||||
</dl>
|
|
||||||
)}
|
|
||||||
</aside>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
|
||||||
</section>
|
{currently && (
|
||||||
|
<aside>
|
||||||
|
<h2>Current</h2>
|
||||||
|
{currently?.mood && (
|
||||||
|
<dl>
|
||||||
|
<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 class="title">Game</dt>
|
||||||
|
<dd class="desc">{currently.playing}</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
{currently.watching && (
|
||||||
|
<dl>
|
||||||
|
<dt class="title">Show</dt>
|
||||||
|
<dd class="desc">{currently.watching}</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
{currently.reading && (
|
||||||
|
<dl>
|
||||||
|
<dt class="title">Book</dt>
|
||||||
|
<dd class="desc">{currently.reading}</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
{currently.listening && (
|
||||||
|
<dl>
|
||||||
|
<dt>Currently listening</dt>
|
||||||
|
<dd>{currently.listening}</dd>
|
||||||
|
</dl>
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
<slot name="pagination" />
|
<slot name="pagination" />
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
@ -238,7 +236,7 @@ dayjs.extend(utc);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hgroup {
|
.nav-section {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
background-color: var(--bg-0);
|
background-color: var(--bg-0);
|
||||||
|
|
||||||
@ -317,7 +315,7 @@ dayjs.extend(utc);
|
|||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-attachment: local;
|
background-attachment: local;
|
||||||
background-image: linear-gradient(to right, var(--bg-0) 5px, transparent 2px), linear-gradient(var(--bg-4) 2px, transparent 2px);
|
background-image: linear-gradient(to right, var(--bg-0) 5px, transparent 2px), linear-gradient(var(--bg-4) 2px, transparent 2px);
|
||||||
background-size: 10px 1lh;
|
background-size: 10px 1lh;
|
||||||
background-position-y: calc(2lh - 2px);
|
background-position-y: calc(2lh - 2px);
|
||||||
margin: 2rem 1rem;
|
margin: 2rem 1rem;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import "@/assets/styles/base.css";
|
import "$/styles/base.css";
|
||||||
|
|
||||||
interface Props { title?: string; }
|
interface Props { title?: string; }
|
||||||
|
|
||||||
@ -16,9 +16,9 @@ const { title = "haetae" }: Props = Astro.props;
|
|||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<script is:inline>
|
<script is:inline>
|
||||||
localStorage.getItem("theme") ?
|
localStorage.getItem("theme")
|
||||||
document.documentElement.className = localStorage.getItem("theme") :
|
? document.documentElement.className = localStorage.getItem("theme")
|
||||||
document.documentElement.removeAttribute("class");
|
: document.documentElement.removeAttribute("class");
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -1,12 +1,28 @@
|
|||||||
---
|
---
|
||||||
title: about me
|
title: about me
|
||||||
layout: ../layouts/About.astro
|
layout: ../layouts/About.astro
|
||||||
avatar: /src/assets/portrait-0025.png
|
avatar: /src/assets/images/portrait-0025.png
|
||||||
avatarText: pikachu from pokemon mystery dungeon
|
avatarText: pikachu from pokemon mystery dungeon
|
||||||
---
|
---
|
||||||
# hello!
|
# hello!
|
||||||
|
hey there, i go by haetae (or tae, for short) and i use he/him pronouns.
|
||||||
it's me, the weirdo. here's a bunch of my images:
|
|
||||||
|
|
||||||
## badges...
|
## badges...
|
||||||
|
|
||||||
|
# credits
|
||||||
|
i used a bunch of assets that require attribution but i also wanted to link people to the authors because their works are great!
|
||||||
|
|
||||||
|
## images
|
||||||
|
- [Pikachu portrait](https://sprites.pmdcollab.org/#/0025?form=0) by SPIKE CHUNSOFT, curated by the PMD Collab project
|
||||||
|
- [This about page's frame images](https://www.spriters-resource.com/ds_dsi/pokemonmysterydungeonexplorersoftimedarkness/sheet/5986/) by redblueyellow
|
||||||
|
- The blog's frame images and additional assets (such as the mood emoticons) were graciously provided by AymShade on the MapleLegends forums! I can't directly link because I'm scared of a Certain Company™ being evil
|
||||||
|
- [The guestbook's speech bubble images](https://www.spriters-resource.com/game_boy_advance/mlss/sheet/7573/) by MajinPiccolo
|
||||||
|
|
||||||
|
## fonts
|
||||||
|
- [Departure Mono](https://departuremono.com/) by [Helena Zhang](https://www.helenazhang.com/) is licensed under [OFL 1.1](https://www.tldrlegal.com/license/open-font-license-ofl-explained)
|
||||||
|
- [Kiwi Soda](https://fontenddev.com/fonts/kiwi-soda/) from fontenddev.com is licensed under [CC by 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
- [Mario & Luigi RPG Speech Text](https://fontstruct.com/fontstructions/show/1102228) by DarkMaxX is licensed under [CC by Share Alike 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
|
||||||
|
- [PixelMplus](https://itouhiro.hatenablog.com/entry/20130602/font) by itouhiro is licensed under [M+ font license](https://web.archive.org/web/20221024231351/http://mplus-fonts.osdn.jp/mplus-bitmap-fonts/#license)
|
||||||
|
- [Redaction](https://www.redaction.us/) by [Forest Young](https://www.moma.org/interactives/exhibitions/2011/talktome/objects/140027/) and [Jeremy Mickel](https://mckltype.com/) is dual-licensed under [LGPL 2.1](https://www.tldrlegal.com/license/gnu-lesser-general-public-license-v2-1-lgpl-2-1) and [OFL 1.1](https://www.tldrlegal.com/license/open-font-license-ofl-explained)
|
||||||
|
- [sq](https://github.com/leahneukirchen/sq) by [Leah Neukirchen](https://leahneukirchen.org/) is in public domain
|
||||||
|
- [Wonder Mail](https://www.dafont.com/wonder-mail.font) by ShinxHijinx is free for personal use
|
@ -2,12 +2,12 @@
|
|||||||
import Blog from '@/layouts/Blog.astro';
|
import Blog from '@/layouts/Blog.astro';
|
||||||
import type { GetStaticPaths } from 'astro';
|
import type { GetStaticPaths } from 'astro';
|
||||||
import { getCollection, render } from 'astro:content';
|
import { getCollection, render } from 'astro:content';
|
||||||
import leftNormal from "@/assets/left-normal.png";
|
import leftNormal from "$/images/left-normal.png";
|
||||||
import leftHover from "@/assets/left-hover.png";
|
import leftHover from "$/images/left-hover.png";
|
||||||
import leftActive from "@/assets/left-active.png";
|
import leftActive from "$/images/left-active.png";
|
||||||
import rightNormal from "@/assets/right-normal.png";
|
import rightNormal from "$/images/right-normal.png";
|
||||||
import rightHover from "@/assets/right-hover.png";
|
import rightHover from "$/images/right-hover.png";
|
||||||
import rightActive from "@/assets/right-active.png";
|
import rightActive from "$/images/right-active.png";
|
||||||
|
|
||||||
export const getStaticPaths = (async () => {
|
export const getStaticPaths = (async () => {
|
||||||
const blog = await getCollection("blog");
|
const blog = await getCollection("blog");
|
||||||
|
@ -1,65 +1,98 @@
|
|||||||
---
|
---
|
||||||
import { actions, isInputError } from "astro:actions";
|
|
||||||
import { db, Guestbook as table } from "astro:db";
|
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import Layout from "@/layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
import ThemeSwitch from "@/components/ThemeSwitch.astro";
|
import ThemeSwitch from "~/ThemeSwitch.astro";
|
||||||
|
import speech from "$/images/speech.png";
|
||||||
|
|
||||||
export const prerender = false;
|
type GuestBookEntry = {
|
||||||
|
ID: number;
|
||||||
|
CreatedAt: string;
|
||||||
|
UpdatedAt: string;
|
||||||
|
DeletedAt: null | string;
|
||||||
|
Name: string;
|
||||||
|
Text: string;
|
||||||
|
Website: null | string;
|
||||||
|
Approved: true;
|
||||||
|
GuestBookID: number;
|
||||||
|
Guestbook: object;
|
||||||
|
}
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
const result = Astro.getActionResult(actions.guestbook);
|
const response = await fetch("https://guestbooks.meadow.cafe/api/v1/get-guestbook-messages/500");
|
||||||
const errors = isInputError(result?.error) ? result.error.fields : {};
|
const entries: Array<GuestBookEntry> = await response.json();
|
||||||
const entries = await db.select().from(table);
|
entries.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valueOf());
|
||||||
entries.sort((a, b) => b.date.valueOf() - a.date.valueOf());
|
|
||||||
---
|
---
|
||||||
<Layout title="haetae, guestbook">
|
<Layout title="haetae, guestbook">
|
||||||
<ThemeSwitch />
|
<ThemeSwitch />
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<h1>Guestbook</h1>
|
<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">
|
<section id="form">
|
||||||
<label for="username">Username</label>
|
<form action="https://guestbooks.meadow.cafe/guestbook/500/submit" method="post" aria-describedby="errors">
|
||||||
<input type="text" name="username" id="username" aria-describedby="username-error" required />
|
<label for="name">Nickname</label>
|
||||||
{errors.username && <p id="username-error">{errors.username.join(",")}</p>}
|
<input type="text" id="name" name="name" required />
|
||||||
|
|
||||||
<label for="website">Website (optional)</label>
|
<label for="website">Website (optional)</label>
|
||||||
<input type="url" name="website" id="website" aria-describedby="website-error" />
|
<input type="url" id="website" name="website" />
|
||||||
{errors.website && <p id="website-error">{errors.website.join(",")}</p>}
|
|
||||||
|
|
||||||
<label for="body">Message</label>
|
<label for="challengeQuestionAnswer">What's my name?</label>
|
||||||
<textarea name="body" id="body" rows="5" aria-describedby="body-error" required></textarea>
|
<input placeholder="read my about page!" type="text" id="challengeQuestionAnswer" name="challengeQuestionAnswer" required />
|
||||||
{errors.body && <p id="body-error">{errors.body.join(",")}</p>}
|
|
||||||
|
<label for="text">Message</label>
|
||||||
|
<textarea placeholder="Message (plain text only)..." id="text" name="text" required></textarea>
|
||||||
|
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
<div id="errors" role="alert"></div>
|
||||||
|
|
||||||
<label for="password">Do you know the password?</label>
|
<aside>
|
||||||
<input type="text" name="password" id="password" value="yes!" aria-describedby="password-error" required />
|
Lovingly made with <a href="https://guestbooks.meadow.cafe" target="_blank" referrerpolicy="no-referrer">Guestbooks</a>
|
||||||
{errors.password && <p id="password-error">{errors.password.join(",")}</p>}
|
</aside>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
<button type="submit">Post</button>
|
<section id="entries">
|
||||||
</form>
|
<h2>Messages</h2>
|
||||||
|
{entries.map(({ Name, Website, Text, CreatedAt}) => (
|
||||||
|
<article class="entry">
|
||||||
|
<header>
|
||||||
|
<h1>
|
||||||
|
{Website
|
||||||
|
? <a href={Website} target="_blank" referrerpolicy="no-referrer">{Name}</a>
|
||||||
|
: <span>{Name}</span>
|
||||||
|
}
|
||||||
|
</h1>
|
||||||
|
<time datetime={dayjs(CreatedAt).utc(true).toISOString()}>
|
||||||
|
Posted on {dayjs(CreatedAt).utc(true).format("MMMM DD, YYYY")}
|
||||||
|
</time>
|
||||||
|
</header>
|
||||||
|
|
||||||
{entries.map(({ username, website, body, date }) => (
|
{Text}
|
||||||
<article class="entry">
|
</article>
|
||||||
<header>
|
))}
|
||||||
<h1>{username}</h1>
|
</section>
|
||||||
<time datetime={dayjs(date).utc(true).toISOString()}>
|
|
||||||
Posted on {dayjs(date).utc(true).format("MMMM DD, YYYY")}
|
<button onclick="document.getElementById('notification').showModal()">open popup</button>
|
||||||
</time>
|
<dialog id="notification">
|
||||||
{website && <a href={website} target="_blank" referrerpolicy="no-referrer">website</a>}
|
<form method="dialog">
|
||||||
</header>
|
Successfully posted! Messages are manually approved. New entries should show up in a day or two! :)
|
||||||
{body}
|
<menu>
|
||||||
</article>
|
<button type="submit">Close</button>
|
||||||
))}
|
</menu>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
</main>
|
</main>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<style>
|
<style define:vars={{ border: `url(${speech.src})` }}>
|
||||||
|
:root {
|
||||||
|
--speech-bg-color: #f8f8f8;
|
||||||
|
--speech-fg-color: #404040;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-width: clamp(75ch, 80ch, 100%);
|
max-width: clamp(75ch, 80ch, 100%);
|
||||||
margin: 1rem auto;
|
margin: 1rem auto;
|
||||||
@ -80,43 +113,91 @@ entries.sort((a, b) => b.date.valueOf() - a.date.valueOf());
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form {
|
#form {
|
||||||
display: flex;
|
form {
|
||||||
flex-flow: column wrap;
|
display: flex;
|
||||||
|
flex-flow: column wrap;
|
||||||
input, textarea {
|
|
||||||
margin-bottom: 1rem;
|
input, textarea {
|
||||||
padding: 2px 6px;
|
margin-bottom: 1rem;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aside {
|
||||||
|
color: color-mix(in oklab, var(--fg-color) 80%, var(--bg-color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
details {
|
.entry {
|
||||||
& > summary {
|
image-rendering: pixelated;
|
||||||
list-style: none;
|
font-family: var(--sans-font);
|
||||||
cursor: pointer;
|
font-size: calc(1rem * 2);
|
||||||
font-weight: bold;
|
letter-spacing: 1px;
|
||||||
|
color: var(--speech-fg-color);
|
||||||
|
background-color: var(--speech-bg-color);
|
||||||
|
border-image: var(--border) 7 / 7px / 7px repeat;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
&::after {
|
#notification {
|
||||||
content: "[+]";
|
margin: auto;
|
||||||
}
|
max-width: 35ch;
|
||||||
|
color: var(--fg-color);
|
||||||
[open] &::after {
|
background: var(--bg-color);
|
||||||
content: "[_]";
|
transition:
|
||||||
}
|
display 1s allow-discrete,
|
||||||
|
overlay 1s allow-discrete;
|
||||||
|
animation: fadeOut 1s forwards;
|
||||||
|
|
||||||
|
menu {
|
||||||
|
margin: 1em 0 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[open] {
|
||||||
|
animation: fadeIn 1.0s forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from { opacity: 1; }
|
||||||
|
to { opacity: 0; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const details = document.getElementsByTagName("details").item(0);
|
const form = document.forms[0];
|
||||||
const pw = document.getElementById("cool-surprise");
|
const challenge = form.elements["challengeQuestionAnswer" as any];
|
||||||
let initial = "vyvnq";
|
const errors = document.getElementById("errors")!;
|
||||||
details?.addEventListener("toggle", e => {
|
const notification = document.getElementById("notification")! as HTMLDialogElement;
|
||||||
if ((e.target as HTMLDetailsElement).open) {
|
|
||||||
pw!.innerHTML = `if you read this, put <strong>${initial.replace(/[a-zA-Z]/g, c => {
|
form.addEventListener("submit", async e => {
|
||||||
// @ts-expect-error the below code works
|
e.preventDefault();
|
||||||
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);
|
let data = new FormData(form);
|
||||||
})}</strong> for the password field!`
|
const response = await fetch(form.action, {
|
||||||
|
method: "POST",
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
form.reset();
|
||||||
|
notification.showModal();
|
||||||
|
errors.innerHTML = "";
|
||||||
|
} else {
|
||||||
|
const err = await response.text();
|
||||||
|
if (response.status === 401) {
|
||||||
|
challenge.ariaInvalid = "true";
|
||||||
|
errors.innerHTML = "The provided answer to the challenge question was invalid!";
|
||||||
|
} else {
|
||||||
|
errors.innerHTML = err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
@ -5,8 +5,9 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
|
||||||
"~/*": ["src/components/*"],
|
"~/*": ["src/components/*"],
|
||||||
|
"$/*": ["src/assets/*"],
|
||||||
|
"@/*": ["src/*"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|