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; | ||||
|   --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; | ||||
|   --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; | ||||
|   --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; | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| @font-face { | ||||
|   font-family: "Arial Pixel"; | ||||
|   src: url("/fonts/PIXEAR.woff2") format("woff2"); | ||||
|   src: url("/fonts/pixearg.woff2") format("woff2"); | ||||
|   font-weight: normal; | ||||
| } | ||||
| 
 | ||||
| @font-face { | ||||
|   font-family: "Arial Pixel"; | ||||
|   src: url("/fonts/PIXEAB.woff2") format("woff2"); | ||||
|   src: url("/fonts/pixeab.woff2") format("woff2"); | ||||
|   font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| @ -23,33 +23,21 @@ | ||||
| } | ||||
| 
 | ||||
| @font-face { | ||||
|   font-family: "MLSS"; | ||||
|   font-family: "Super Star"; | ||||
|   src: url("/fonts/mario-luigi-rpg-speech-text.woff2") format("woff2"); | ||||
|   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"); | ||||
|   src: url("/fonts/PixelMplus10-Regular.woff2") format("woff2"); | ||||
|   font-weight: normal; | ||||
| } | ||||
| 
 | ||||
| @font-face { | ||||
|   font-family: "PixelMPlus 12"; | ||||
|   src: url("/fonts/PixelMplus12-Bold.woff2") format("woff"); | ||||
|   font-weight: bold; | ||||
|   src: url("/fonts/PixelMplus12-Regular.woff2") format("woff2"); | ||||
|   font-weight: normal; | ||||
| } | ||||
| 
 | ||||
| @font-face { | ||||
|  | ||||
| @ -1,6 +1,9 @@ | ||||
| --- | ||||
| title: hmhmhm! | ||||
| pubDate: 2024-02-04 | ||||
| currently: | ||||
|   mood: sad | ||||
|   reading: sad fanfic | ||||
| --- | ||||
| 
 | ||||
| hey there  this is a test | ||||
| @ -1,11 +1,11 @@ | ||||
| --- | ||||
| import type { MarkdownLayoutProps } from "astro"; | ||||
| import Layout from "./Layout.astro"; | ||||
| import Navbar from "@/components/Navbar.astro"; | ||||
| import Figure from "@/components/Figure.astro"; | ||||
| import Navbar from "~/Navbar.astro"; | ||||
| import Figure from "~/Figure.astro"; | ||||
| 
 | ||||
| import border from "@/assets/border.png"; | ||||
| import frame from "@/assets/frame.png"; | ||||
| import border from "$/images/border.png"; | ||||
| import frame from "$/images/frame.png"; | ||||
| 
 | ||||
| type Props = MarkdownLayoutProps<{ | ||||
|   avatar?: string; | ||||
|  | ||||
| @ -4,12 +4,12 @@ import { getCollection } from "astro:content"; | ||||
| import dayjs from "dayjs"; | ||||
| import utc from "dayjs/plugin/utc"; | ||||
| import Layout from "./Layout.astro"; | ||||
| import Navbar from "@/components/Navbar.astro"; | ||||
| import Navbar from "~/Navbar.astro"; | ||||
| import moods from "@/utils/moods"; | ||||
| 
 | ||||
| import outerBBS from "@/assets/guild-bbs.png"; | ||||
| import innerBBS from "@/assets/guild-bbs-content.png"; | ||||
| import sideBBS from "@/assets/guild-bbs-list.png"; | ||||
| import outerBBS from "$/images/guild-bbs.png"; | ||||
| import innerBBS from "$/images/guild-bbs-content.png"; | ||||
| import sideBBS from "$/images/guild-bbs-list.png"; | ||||
| 
 | ||||
| interface Props { | ||||
|   title: string; | ||||
| @ -36,7 +36,7 @@ dayjs.extend(utc); | ||||
|   <main> | ||||
|     <nav id="blog-links"> | ||||
|       <div class="inner"> | ||||
|         <hgroup> | ||||
|         <div class="nav-section"> | ||||
|           <h1><span class="title">recent posts</span></h1> | ||||
|           <ul> | ||||
|             {blog.map(entry => ( | ||||
| @ -50,76 +50,74 @@ dayjs.extend(utc); | ||||
|               </li> | ||||
|             ))} | ||||
|           </ul> | ||||
|         </hgroup> | ||||
|         <hgroup> | ||||
|         </div> | ||||
|         <div class="nav-section"> | ||||
|           <h1><span class="title">other links</span></h1> | ||||
|           <ul> | ||||
|             <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> | ||||
|           </ul> | ||||
|         </hgroup> | ||||
|         </div> | ||||
|       </div> | ||||
|     </nav> | ||||
| 
 | ||||
|     <section> | ||||
|       <article> | ||||
|         <div class="inner"> | ||||
|           <header> | ||||
|             <h1>{title}</h1> | ||||
|             <hr /> | ||||
|             <div class="info"> | ||||
|               <time datetime={dayjs(date).utc(true).toISOString()}> | ||||
|                 <span class="title">Date</span> | ||||
|                 <span class="desc">{dayjs(date).utc(true).format("MMMM DD, YYYY")}</span> | ||||
|               </time> | ||||
|             </div> | ||||
|           </header> | ||||
| 
 | ||||
|           <div class="content" data-simplebar> | ||||
|             <slot /> | ||||
|     <article> | ||||
|       <div class="inner"> | ||||
|         <header> | ||||
|           <h1>{title}</h1> | ||||
|           <hr /> | ||||
|           <div class="info"> | ||||
|             <time datetime={dayjs(date).utc(true).toISOString()}> | ||||
|               <span class="title">Date</span> | ||||
|               <span class="desc">{dayjs(date).utc(true).format("MMMM DD, YYYY")}</span> | ||||
|             </time> | ||||
|           </div> | ||||
|         </header> | ||||
| 
 | ||||
|           {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 class="content"> | ||||
|           <slot /> | ||||
|         </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" /> | ||||
|   </main> | ||||
| </Layout> | ||||
| @ -238,7 +236,7 @@ dayjs.extend(utc); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     hgroup { | ||||
|     .nav-section { | ||||
|       margin: 0 4px; | ||||
|       background-color: var(--bg-0); | ||||
| 
 | ||||
| @ -317,7 +315,7 @@ dayjs.extend(utc); | ||||
|     .content { | ||||
|       position: relative; | ||||
|       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-position-y: calc(2lh - 2px); | ||||
|       margin: 2rem 1rem; | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| --- | ||||
| import "@/assets/styles/base.css"; | ||||
| import "$/styles/base.css"; | ||||
| 
 | ||||
| interface Props { title?: string; } | ||||
| 
 | ||||
| @ -16,9 +16,9 @@ const { title = "haetae" }: Props = Astro.props; | ||||
| 		<slot name="header" /> | ||||
| 		<title>{title}</title> | ||||
| 		<script is:inline> | ||||
| 			localStorage.getItem("theme") ? | ||||
| 				document.documentElement.className = localStorage.getItem("theme") : | ||||
| 				document.documentElement.removeAttribute("class"); | ||||
| 			localStorage.getItem("theme") | ||||
| 				? document.documentElement.className = localStorage.getItem("theme") | ||||
| 				: document.documentElement.removeAttribute("class"); | ||||
| 		</script> | ||||
| 	</head> | ||||
| 	<body> | ||||
|  | ||||
| @ -1,12 +1,28 @@ | ||||
| --- | ||||
| title: about me | ||||
| layout: ../layouts/About.astro | ||||
| avatar: /src/assets/portrait-0025.png | ||||
| avatar: /src/assets/images/portrait-0025.png | ||||
| avatarText: pikachu from pokemon mystery dungeon | ||||
| --- | ||||
| # hello! | ||||
| 
 | ||||
| it's me, the weirdo. here's a bunch of my images: | ||||
| hey there, i go by haetae (or tae, for short) and i use he/him pronouns. | ||||
| 
 | ||||
| ## 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 type { GetStaticPaths } from 'astro'; | ||||
| import { getCollection, render } from 'astro:content'; | ||||
| import leftNormal from "@/assets/left-normal.png"; | ||||
| import leftHover from "@/assets/left-hover.png"; | ||||
| import leftActive from "@/assets/left-active.png"; | ||||
| import rightNormal from "@/assets/right-normal.png"; | ||||
| import rightHover from "@/assets/right-hover.png"; | ||||
| import rightActive from "@/assets/right-active.png"; | ||||
| import leftNormal from "$/images/left-normal.png"; | ||||
| import leftHover from "$/images/left-hover.png"; | ||||
| import leftActive from "$/images/left-active.png"; | ||||
| import rightNormal from "$/images/right-normal.png"; | ||||
| import rightHover from "$/images/right-hover.png"; | ||||
| import rightActive from "$/images/right-active.png"; | ||||
| 
 | ||||
| export const getStaticPaths = (async () => { | ||||
|   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 utc from "dayjs/plugin/utc"; | ||||
| 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); | ||||
| const result = Astro.getActionResult(actions.guestbook); | ||||
| const errors = isInputError(result?.error) ? result.error.fields : {}; | ||||
| const entries = await db.select().from(table); | ||||
| entries.sort((a, b) => b.date.valueOf() - a.date.valueOf()); | ||||
| const response = await fetch("https://guestbooks.meadow.cafe/api/v1/get-guestbook-messages/500"); | ||||
| const entries: Array<GuestBookEntry> = await response.json(); | ||||
| entries.sort((a, b) => new Date(b.CreatedAt).valueOf() - new Date(a.CreatedAt).valueOf()); | ||||
| --- | ||||
| <Layout title="haetae, guestbook"> | ||||
|   <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" aria-describedby="username-error" required /> | ||||
|       {errors.username && <p id="username-error">{errors.username.join(",")}</p>} | ||||
|     <section id="form"> | ||||
|       <form action="https://guestbooks.meadow.cafe/guestbook/500/submit" method="post" aria-describedby="errors"> | ||||
|         <label for="name">Nickname</label> | ||||
|         <input type="text" id="name" name="name" required /> | ||||
| 
 | ||||
|       <label for="website">Website (optional)</label> | ||||
|       <input type="url" name="website" id="website" aria-describedby="website-error" /> | ||||
|       {errors.website && <p id="website-error">{errors.website.join(",")}</p>} | ||||
|         <label for="website">Website (optional)</label> | ||||
|         <input type="url" id="website" name="website" /> | ||||
| 
 | ||||
|       <label for="body">Message</label> | ||||
|       <textarea name="body" id="body" rows="5" aria-describedby="body-error" required></textarea> | ||||
|       {errors.body && <p id="body-error">{errors.body.join(",")}</p>} | ||||
|         <label for="challengeQuestionAnswer">What's my name?</label> | ||||
|         <input placeholder="read my about page!" type="text" id="challengeQuestionAnswer" name="challengeQuestionAnswer" required /> | ||||
|          | ||||
|         <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> | ||||
|       <input type="text" name="password" id="password" value="yes!" aria-describedby="password-error" required /> | ||||
|       {errors.password && <p id="password-error">{errors.password.join(",")}</p>} | ||||
|       <aside> | ||||
|         Lovingly made with <a href="https://guestbooks.meadow.cafe" target="_blank" referrerpolicy="no-referrer">Guestbooks</a> | ||||
|       </aside> | ||||
|     </section> | ||||
|      | ||||
|     <hr /> | ||||
| 
 | ||||
|       <button type="submit">Post</button> | ||||
|     </form> | ||||
|     <section id="entries"> | ||||
|       <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 }) => ( | ||||
|       <article class="entry"> | ||||
|         <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> | ||||
|     ))} | ||||
|           {Text} | ||||
|         </article> | ||||
|       ))} | ||||
|     </section> | ||||
| 
 | ||||
|     <button onclick="document.getElementById('notification').showModal()">open popup</button> | ||||
|     <dialog id="notification"> | ||||
|       <form method="dialog"> | ||||
|         Successfully posted! Messages are manually approved. New entries should show up in a day or two! :) | ||||
|         <menu> | ||||
|           <button type="submit">Close</button> | ||||
|         </menu> | ||||
|       </form> | ||||
|     </dialog> | ||||
|   </main> | ||||
| </Layout> | ||||
| 
 | ||||
| <style> | ||||
| <style define:vars={{ border: `url(${speech.src})` }}> | ||||
|   :root { | ||||
|     --speech-bg-color: #f8f8f8; | ||||
|     --speech-fg-color: #404040; | ||||
|   } | ||||
| 
 | ||||
|   main { | ||||
|     max-width: clamp(75ch, 80ch, 100%); | ||||
|     margin: 1rem auto; | ||||
| @ -80,43 +113,91 @@ entries.sort((a, b) => b.date.valueOf() - a.date.valueOf()); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   form { | ||||
|     display: flex; | ||||
|     flex-flow: column wrap; | ||||
|      | ||||
|     input, textarea { | ||||
|       margin-bottom: 1rem; | ||||
|       padding: 2px 6px; | ||||
|   #form { | ||||
|     form { | ||||
|       display: flex; | ||||
|       flex-flow: column wrap; | ||||
|        | ||||
|       input, textarea { | ||||
|         margin-bottom: 1rem; | ||||
|         padding: 2px 6px; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     aside { | ||||
|       color: color-mix(in oklab, var(--fg-color) 80%, var(--bg-color)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   details { | ||||
|     & > summary { | ||||
|       list-style: none; | ||||
|       cursor: pointer; | ||||
|       font-weight: bold; | ||||
|   .entry { | ||||
|     image-rendering: pixelated; | ||||
|     font-family: var(--sans-font); | ||||
|     font-size: calc(1rem * 2); | ||||
|     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 { | ||||
|         content: "[+]"; | ||||
|       } | ||||
| 
 | ||||
|       [open] &::after { | ||||
|         content: "[_]"; | ||||
|       } | ||||
|   #notification { | ||||
|     margin: auto; | ||||
|     max-width: 35ch; | ||||
|     color: var(--fg-color); | ||||
|     background: var(--bg-color); | ||||
|     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> | ||||
| 
 | ||||
| <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!` | ||||
|   const form = document.forms[0]; | ||||
|   const challenge = form.elements["challengeQuestionAnswer" as any]; | ||||
|   const errors = document.getElementById("errors")!; | ||||
|   const notification = document.getElementById("notification")! as HTMLDialogElement; | ||||
| 
 | ||||
|   form.addEventListener("submit", async e => { | ||||
|     e.preventDefault(); | ||||
|     let data = new FormData(form); | ||||
|     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> | ||||
| @ -5,8 +5,9 @@ | ||||
|   "compilerOptions": { | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@/*": ["src/*"], | ||||
|       "~/*": ["src/components/*"], | ||||
|       "$/*": ["src/assets/*"], | ||||
|       "@/*": ["src/*"], | ||||
|     } | ||||
|   } | ||||
| } | ||||