77 Commits

Author SHA1 Message Date
eggy
afed4abf46 fix: add extra svgs 2023-01-23 19:57:35 -05:00
eggy
25f9e25b3e feat: add portfolio button and dominant button controls 2023-01-23 19:57:22 -05:00
eggy
a7f38e77ae fix: do not show header on non-article content (2) 2023-01-11 15:14:47 -05:00
eggy
fb03714c2f fix: do not show header on non-article content 2023-01-11 15:10:14 -05:00
eggy
ba49ac0a7d content: add projections tag 2023-01-03 15:21:07 -05:00
eggy
c930fd70ec fix: fix build issues 2023-01-03 15:13:28 -05:00
eggy
59ee896153 chore: update deps 2023-01-03 15:00:08 -05:00
eggy
c019a0fa6d chore: remove unused styles 2022-12-31 20:28:07 -05:00
eggy
806bb9b760 fix: make heading links look nicer 2022-12-31 20:27:53 -05:00
eggy
d7f0cfe8f7 chore: use main instead of div for articles 2022-12-31 17:57:07 -05:00
19fd660df7 add wayland article 2022-12-20 18:57:02 -05:00
2789dc375f Indicate that panquia is on fire 2022-12-19 15:41:40 -05:00
0eef57a376 actually draft it 2022-12-02 21:38:44 -05:00
17f66b52d9 take down nano short
we anonymous boys
2022-12-02 21:30:58 -05:00
24c2fa6a6f feat: shrink hr margin 2022-12-02 01:10:56 -05:00
751033960b fix: remove duplicate dot 2022-12-02 01:06:07 -05:00
6373789413 add the watsfic story 2022-12-02 00:46:00 -05:00
a5142fb00b feat: use time el for date strings
for better machine recognition
2022-12-01 01:24:26 -05:00
156be1cfd4 Fix extra hr 2022-11-29 14:51:32 -05:00
99bb0e9db4 Add november albatross 2022-11-29 14:49:06 -05:00
76f8186385 fix: color revision box 2022-11-01 14:17:14 -04:00
a46bc70591 fix: force footer padding to centre align text 2022-11-01 14:13:23 -04:00
d7934568bd migrate domain 2022-11-01 00:07:14 -04:00
c7cd1fef32 fix: add blog excerpt 2022-10-31 14:20:04 -04:00
631416984c feat: add filter to main page 2022-10-31 14:18:14 -04:00
49b772918b add web frameworks 2022-10-30 21:52:55 -04:00
113680fcec final touch to story 2022-10-28 19:23:56 -04:00
d09a4ccbb6 add high crimes story 2022-10-28 19:20:38 -04:00
689e951e36 add new bird story 2022-10-25 18:08:14 -04:00
702445d090 alcohol: fix typos 2022-10-24 23:44:39 -04:00
36b3ac58d1 shorten wine excerpt 2022-10-24 23:30:30 -04:00
4aed55206a add bird stories 2022-10-24 23:28:49 -04:00
d55899643e add wine albatross article 2022-10-24 23:18:37 -04:00
d047fda51e hide drafts 2022-10-24 23:15:26 -04:00
7e750d3290 add politics as draft 2022-10-24 23:11:29 -04:00
b3c365fdd0 tags: add bird tag description 2022-10-24 23:05:11 -04:00
8b7f3460d7 add new bird story 2022-10-24 23:00:10 -04:00
c4f956af60 remove extra post 2022-10-13 17:18:02 -04:00
1cf48e6c22 add missing unstag 2020 stories 2022-10-13 17:16:25 -04:00
6311f64921 feat: add descriptions to story tags 2022-10-12 21:16:13 -04:00
60895ee176 fix excerpt and front matter
oopsies
2022-10-12 20:49:50 -04:00
641c5461b9 add universe city letter
let's get back into it
2022-10-12 20:47:41 -04:00
78cf87b84c fix: cap git blockchain post excerpt 2022-10-04 11:11:52 -04:00
d252fa91b3 add git blockchain 2022-10-03 13:58:26 -04:00
66c4b32049 add albatross articles
simulpubed
2022-10-02 16:30:35 -04:00
aedc42a9b3 fix(about): remove empty list item 2022-09-20 16:22:43 -04:00
dfd90ba2de fix(about): add name to about page 2022-09-20 16:22:00 -04:00
1c6eb2d6fa feat: remove nuxt-zero-js and version bump nuxt
Nuxt rc10 adds a noScript flag, which does the same thing. Remove unnecessary dependencies.
2022-09-15 08:21:52 -04:00
e79f634d22 feat: add nuxt-zero-js
This saves us one step in the build process although doesn't fix the root problem.
2022-09-11 08:53:26 -04:00
8f65123f32 fix: load current page if Nuxt 3 version selected
Originally, it would attempt to load BASE_URL/Nuxt 3 (2022), which obviously does not exist.
2022-09-07 14:41:24 -04:00
a7efd8b1f6 feat: add revisions in footer
advertise older sites
2022-09-01 22:43:43 -04:00
16eab9d7d8 fix: do not interpolate tag titles
fixes duplicate stories
2022-08-15 21:03:46 -04:00
8474f4ff81 fix: use native color-mode for dark mode
fixes all of our problems haha
2022-08-12 21:16:08 -04:00
b5184838cf feat: switch back to totop default 2022-08-12 21:07:01 -04:00
1e98ce27a9 feat: rename scripts 2022-08-12 21:04:57 -04:00
df5085c41d fix: use correct id 2022-08-12 21:04:50 -04:00
7925c5b4e7 fix: load custom script 2022-08-12 20:47:10 -04:00
a9a555d20a feat: add manual js
i cannot believe this
2022-08-12 20:44:27 -04:00
98a2cee26a chore: update deps 2022-08-12 20:14:37 -04:00
b1ab909c5b feat: change all png to webp
we care about it in primoprod we care more now
2022-08-12 17:58:16 -04:00
0473de4873 fix: remove totop arrow
it's causing too many bugs because why wouldn't it
2022-08-10 21:21:27 -04:00
cc3e33cd2c doc: the pain and suffering 2022-08-10 21:17:29 -04:00
1fb3e222ee fix: PRERENDERING ISN'T EVEN RELEASED YET
https://github.com/nuxt/framework/issues/6411
2022-08-10 21:00:44 -04:00
e7ef4821b9 update lockfile
i hate new software so fucking much
2022-08-10 20:45:05 -04:00
ed0eeef2ab fix: god please 2022-08-10 20:42:51 -04:00
7ec729ba66 feat: add meta descriptions 2022-08-10 20:31:16 -04:00
74cad85292 fix: project images 2022-08-10 20:22:37 -04:00
00a5c8c4ca fix: home page v-bind 2022-08-10 20:12:06 -04:00
694a0b2691 fix: just screw 404 2022-08-10 19:50:50 -04:00
260b1f7f30 fix: unknown project image 2022-08-10 19:46:02 -04:00
0b0cf23796 add 404 2022-08-10 19:40:16 -04:00
762a2519c1 really fix 404 2022-08-10 19:40:10 -04:00
aedf88d446 fix: make 404 work 2022-08-10 19:12:32 -04:00
2e4e4727d1 fix: give up and make multiple 404s 2022-08-10 19:01:25 -04:00
6f8f5dad7d fix: properly 404 2022-08-10 18:56:06 -04:00
04ab476d9b feat: add 404 page 2022-08-10 18:47:39 -04:00
b59e77f432 Merge pull request 'Nuxt Content 3 migration' (#12) from js into master
Reviewed-on: https://git.eggworld.tk/eggy/public/pulls/12
2022-08-10 18:38:34 -04:00
113 changed files with 5181 additions and 1477 deletions

View File

@@ -1,6 +1,12 @@
# Nuxt 3 Minimal Starter # Eggworld v3: Nuxt 3
**WARN: Volar 0.40.0 breaks everything and I don't know why — stick with Volar 0.39.5.** After hand-written HTML and a static site generator comes Nuxt!
**WARN: Volar 0.40.0 breaks all type-checking and I don't know why — stick with Volar 0.39.5.**
Post-build instructions (while prerendering is bork)
- Compile `/script.ts` to `/script.js` (`tsc script.ts -m esnext -t esnext --moduleReslution node`)
Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more. Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
@@ -11,12 +17,6 @@ Make sure to install the dependencies:
```bash ```bash
# yarn # yarn
yarn install yarn install
# npm
npm install
# pnpm
pnpm install --shamefully-hoist
``` ```
## Development Server ## Development Server
@@ -24,21 +24,15 @@ pnpm install --shamefully-hoist
Start the development server on http://localhost:3000 Start the development server on http://localhost:3000
```bash ```bash
npm run dev yarn dev
``` ```
## Production ## Production
Build the application for production:
```bash
npm run build
```
Locally preview production build: Locally preview production build:
```bash ```bash
npm run preview yarn preview
``` ```
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information. Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.

25
app.vue
View File

@@ -28,4 +28,29 @@ div#__nuxt {
--text-color: #ebf4f1; --text-color: #ebf4f1;
--bg: #091a28; --bg: #091a28;
} }
.prose h2 > a,
.prose h3 > a,
.prose h4 > a,
.prose h5 > a,
.prose h6 > a {
/*
override default tailwind styles
these have a default specificity of 0, 4, 0 so !important is basically the only way
*/
@apply font-bold no-underline !important;
}
article .prose h2 > a:hover::before,
article .prose h3 > a:hover::before,
article .prose h4 > a:hover::before,
article .prose h5 > a:hover::before,
article .prose h6 > a:hover::before {
content: "#";
position: absolute;
left: -2rem;
opacity: 0.5;
font-style: italic;
}
</style> </style>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BlogParsedContent } from "@/shared/types"; import type { BlogParsedContent } from "@/shared/types";
import { calcReadingTime, getPrettyDate } from "@/shared/metadata"; import { calcReadingTime } from "@/shared/metadata";
const docs = await queryContent<BlogParsedContent>("/blog") const docs = await queryContent<BlogParsedContent>("/blog")
.sort({ date: 1 }) .sort({ date: 1 })
@@ -8,7 +8,6 @@ const docs = await queryContent<BlogParsedContent>("/blog")
.find(); .find();
const latest = docs.at(-1) as BlogParsedContent; const latest = docs.at(-1) as BlogParsedContent;
const prettyDate = getPrettyDate(latest);
</script> </script>
<template> <template>
@@ -21,7 +20,7 @@ const prettyDate = getPrettyDate(latest);
> >
<h2 class="m-0 mt-4 mb-1">{{ latest.title }}</h2> <h2 class="m-0 mt-4 mb-1">{{ latest.title }}</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 m-0"> <p class="text-sm text-gray-500 dark:text-gray-400 m-0">
{{ prettyDate }} · {{ calcReadingTime(latest).minutes }} min read <Date :doc="latest" /> · {{ calcReadingTime(latest).minutes }} min read
</p> </p>
<div class="tag-list mt-1"> <div class="tag-list mt-1">
<Tag <Tag

View File

@@ -1,43 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import IconSun from "@/assets/images/sun.svg?component"; import IconSun from "@/assets/images/sun.svg?component";
import IconMoon from "@/assets/images/moon.svg?component"; import IconMoon from "@/assets/images/moon.svg?component";
const colorMode = useColorMode(); const colorMode = useColorMode();
const isToggled = ref(colorMode.value === "dark");
const toggle = () => { const toggle = () => {
isToggled.value = !isToggled.value; colorMode.preference = colorMode.value === "light" ? "dark" : "light";
colorMode.preference = isToggled.value ? "dark" : "light";
}; };
/*
// it unchecks on refresh and i can't make it automatically
// check itself
const darkToggleEl: Ref<HTMLInputElement> = ref(null);
onMounted(() => {
if (isToggled.value) {
darkToggleEl.value.checked = true;
}
});
*/
</script> </script>
<template> <template>
<label for="toggle" :class="['toggle-wrapper']"> <label for="dark-toggle" class="toggle-wrapper">
<div :class="['toggle', isToggled ? 'enabled' : 'disabled']"> <div class="toggle">
<div class="icons"> <div class="icons">
<IconMoon /> <IconMoon />
<IconSun /> <IconSun />
</div> </div>
<input <input
id="toggle" id="dark-toggle"
name="toggle" name="dark-toggle"
type="checkbox" type="checkbox"
:checked="isToggled"
ref="darkToggleEl" ref="darkToggleEl"
@click="toggle" @click="toggle"
/> />
@@ -87,7 +69,7 @@ html.dark .toggle-wrapper {
transition: transform var(--transition), background var(--transition); transition: transform var(--transition), background var(--transition);
} }
.toggle.enabled::before { html.dark .toggle::before {
transform: translateX(calc(var(--scale))); transform: translateX(calc(var(--scale)));
} }

View File

@@ -7,8 +7,7 @@ const imgUrl = ref("");
const href = ref(""); const href = ref("");
onMounted(async () => { onMounted(async () => {
const results = (await useFetch(FEED_URL, { initialCache: false })) const results = (await useFetch(FEED_URL)).data as Ref<GithubPushEvent[]>;
.data as Ref<GithubPushEvent[]>;
const latestEvent = results.value.find( const latestEvent = results.value.find(
(event) => event.type === "PushEvent" (event) => event.type === "PushEvent"
) as GithubPushEvent; ) as GithubPushEvent;
@@ -22,12 +21,13 @@ onMounted(async () => {
<div class="prose dark:prose-invert"> <div class="prose dark:prose-invert">
<HomeStatBox <HomeStatBox
:href="href" :href="href"
id="github-commit-a"
color="lightgray" color="lightgray"
darkcolor="slategray" darkcolor="slategray"
title="Latest commit" title="Latest commit"
:clearstyles="true" :clearstyles="true"
> >
<img class="m-0 w-full h-full" :src="imgUrl" /> <img class="m-0 w-full h-full" :src="imgUrl" id="github-commit-img" />
<!-- <!--
<div> <div>
<h2>{{ title }}</h2> <h2>{{ title }}</h2>

13
components/Date.vue Normal file
View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { getPrettyDate, getUtcDate } from "~~/shared/metadata";
import { BlogParsedContent, StoryParsedContent } from "~~/shared/types";
const props = defineProps<{ doc: StoryParsedContent | BlogParsedContent }>();
const prettyDate = getPrettyDate(props.doc);
const utcDate = getUtcDate(props.doc);
</script>
<template>
<time pubdate :datetime="utcDate">{{ prettyDate }}</time>
</template>

View File

@@ -26,7 +26,12 @@ const getSvgIcon = async (name: string) => {
</svg> </svg>
</label> </label>
<div class="drawer prose dark:prose-invert"> <div class="drawer prose dark:prose-invert">
<li class="m-0" v-for="(item, index) in navItems" :key="index"> <li
class="m-0"
v-for="(item, index) in navItems"
:key="index"
:class="{ dominant: item.dominant }"
>
<!-- stupid vite doesn't let require work <!-- stupid vite doesn't let require work
i should have just hardcoded the navbar items --> i should have just hardcoded the navbar items -->
<a :href="item.href" class="p-2 flex gap-2"> <a :href="item.href" class="p-2 flex gap-2">
@@ -36,6 +41,11 @@ const getSvgIcon = async (name: string) => {
preload="auto" preload="auto"
/> />
{{ item.title }} {{ item.title }}
<img
v-if="item.dominant"
src="/icons/arrow-right-line.svg"
class="m-0"
/>
</a> </a>
<hr class="m-0 m-2" v-if="index !== navItems.length - 1" /> <hr class="m-0 m-2" v-if="index !== navItems.length - 1" />
</li> </li>
@@ -133,6 +143,20 @@ html.dark .drawer {
width: 100%; width: 100%;
} }
.drawer li.dominant a {
background: royalblue;
color: white;
font-weight: bold;
}
.drawer li.dominant img {
filter: invert(1);
}
.drawer li.dominant a:hover {
background: skyblue;
}
.drawer li a { .drawer li a {
/* overwrite tailwind */ /* overwrite tailwind */
text-decoration: none; text-decoration: none;

View File

@@ -7,6 +7,7 @@ import { unref as _unref } from "vue";
const { const {
href, href,
id,
color = "pink", color = "pink",
darkcolor = "#c88994", darkcolor = "#c88994",
title, title,
@@ -14,6 +15,7 @@ const {
forceheight, forceheight,
} = defineProps<{ } = defineProps<{
href?: string; href?: string;
id?: string;
color?: Color; color?: Color;
darkcolor?: Color; darkcolor?: Color;
title?: string; title?: string;
@@ -36,7 +38,11 @@ const cssVars = {
</script> </script>
<template> <template>
<a class="no-underline inline-block flex flex-col items-stretch" :href="href"> <a
class="no-underline inline-block flex flex-col items-stretch"
:href="href"
:id="id"
>
<div class="container box" :style="cssVars"> <div class="container box" :style="cssVars">
<p class="m-0 w-full title">{{ title }}</p> <p class="m-0 w-full title">{{ title }}</p>
<div class="main-content"> <div class="main-content">

View File

@@ -9,11 +9,16 @@ const props = defineProps<{ activeItem?: string }>();
<nav class="flex items-center justify-between"> <nav class="flex items-center justify-between">
<ul> <ul>
<li class="home-text"><a href="/">Eggworld</a></li> <li class="home-text"><a href="/">Eggworld</a></li>
<li v-for="(item, index) in navItems" :key="index"> <li
v-for="(item, index) in navItems"
:key="index"
:class="{ dominant: item.dominant }"
>
<a :href="item.href" class="flex gap-2"> <a :href="item.href" class="flex gap-2">
<img :src="`/nav/${item.title.toLowerCase()}.svg`" /> <img :src="`/nav/${item.title.toLowerCase()}.svg`" />
{{ item.title }}</a {{ item.title }}
> <img v-if="item.dominant" src="/icons/arrow-right-line.svg" />
</a>
</li> </li>
</ul> </ul>
<div class="flex items-center"> <div class="flex items-center">
@@ -74,6 +79,20 @@ li.home-text {
font-weight: bold; font-weight: bold;
} }
li.dominant {
background: royalblue;
color: white;
font-weight: bold;
}
li.dominant:hover {
background: skyblue;
}
li.dominant img {
filter: invert(1);
}
.hamburger { .hamburger {
width: 0rem; width: 0rem;
opacity: 0; opacity: 0;
@@ -87,6 +106,14 @@ li.home-text {
filter var(--trans), padding-left var(--trans), padding-right var(--trans); filter var(--trans), padding-left var(--trans), padding-right var(--trans);
} }
@media screen and (max-width: 750px) and (min-width: 601px) {
li.home-text {
width: 0;
opacity: 0;
padding: 0;
}
}
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
.hamburger { .hamburger {
display: flex; display: flex;

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StoryParsedContent, BlogParsedContent } from "@/shared/types"; import type { StoryParsedContent, BlogParsedContent } from "@/shared/types";
import { calcReadingTime, getPrettyDate } from "@/shared/metadata"; import { calcReadingTime } from "@/shared/metadata";
const { post, type, highlighttags } = defineProps<{ const { post, type, highlighttags } = defineProps<{
post: StoryParsedContent | BlogParsedContent; post: StoryParsedContent | BlogParsedContent;
@@ -25,7 +25,7 @@ const descText =
{{ post.title }} {{ post.title }}
</a> </a>
</h3> </h3>
<p class="my-1 text-sm">{{ getPrettyDate(post) }} · {{ descText }}</p> <p class="my-1 text-sm"><Date :doc="post" /> · {{ descText }}</p>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<Tag <Tag
:dest="`/tags/${type}/${tag}`" :dest="`/tags/${type}/${tag}`"
@@ -36,7 +36,7 @@ const descText =
{{ tag }} {{ tag }}
</Tag> </Tag>
</div> </div>
<ContentRenderer :value="post" :excerpt="true" tag="article"> <ContentRenderer :value="post" :excerpt="true" tag="section">
<template #empty>No excerpt available.</template> <template #empty>No excerpt available.</template>
</ContentRenderer> </ContentRenderer>
<!--<p v-if="!post.nopreview" class="m-0"></p>--> <!--<p v-if="!post.nopreview" class="m-0"></p>-->

View File

@@ -1,11 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Project } from "@/data/projects"; import type { Project } from "@/data/projects";
import { unref as _unref } from "vue";
const { project, reverse = false } = defineProps<{ const { project, reverse = false } = defineProps<{
project: Project; project: Project;
reverse?: boolean; reverse?: boolean;
}>(); }>();
const imgUrl = `url(/images/projects/${project.img ?? "unknown.png"})`; const imgUrl = project.img ? `url(/images/projects/${project.img})` : "none";
</script> </script>
<template> <template>
@@ -42,7 +43,7 @@ const imgUrl = `url(/images/projects/${project.img ?? "unknown.png"})`;
</div> </div>
</div> </div>
</div> </div>
<div class="card-img h-full p-4 flex" /> <div class="card-img h-full p-4 flex" :style="{ '--imgurl': imgUrl }" />
</div> </div>
</a> </a>
</template> </template>
@@ -87,7 +88,7 @@ html.dark .card-text {
.card-img { .card-img {
width: 75%; width: 75%;
background: v-bind(imgUrl); background: var(--imgurl);
background-color: rgb(255, 237, 241); background-color: rgb(255, 237, 241);
background-position: right 90% top 15%; background-position: right 90% top 15%;
background-size: cover; background-size: cover;

View File

@@ -4,13 +4,17 @@ const props = defineProps<{
href: string; href: string;
img: string; img: string;
unclickable?: boolean; unclickable?: boolean;
broken?: boolean;
}>(); }>();
const imgUrl = `/images/services/${props.img}`; const imgUrl = `/images/services/${props.img}`;
</script> </script>
<template> <template>
<a :href="unclickable ? '' : href" :class="['no-underline', { unclickable }]"> <a
:href="unclickable ? '' : href"
:class="['no-underline', { unclickable: unclickable || broken, broken }]"
>
<div class="card flex flex-col items-center justify-around"> <div class="card flex flex-col items-center justify-around">
<img class="m-0" :src="imgUrl" /> <img class="m-0" :src="imgUrl" />
<h3 class="m-0">{{ props.name }}</h3> <h3 class="m-0">{{ props.name }}</h3>
@@ -36,6 +40,26 @@ img {
box-shadow: 0 0.1rem 0.5rem 0 gray; box-shadow: 0 0.1rem 0.5rem 0 gray;
} }
a.broken::before {
content: "PANQUIA IS ON FIRE";
position: absolute;
color: red;
transform: rotate(-45deg);
font-size: 1.5rem;
text-align: center;
z-index: 2;
top: 40%;
left: -12.5%;
width: 125%;
font-family: "Roboto", sans-serif;
font-weight: bold;
}
a.broken > .card {
filter: grayscale(100%);
opacity: 0.4;
}
html.dark .card { html.dark .card {
border: 0.2rem solid rgb(126, 93, 98); border: 0.2rem solid rgb(126, 93, 98);
background: rgb(110, 90, 92); background: rgb(110, 90, 92);

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { type StoryParsedContent } from "@/shared/types"; import { type StoryParsedContent } from "@/shared/types";
import { calcReadingTime, getPrettyDate } from "@/shared/metadata"; import { calcReadingTime } from "@/shared/metadata";
const docs = await queryContent<StoryParsedContent>("/stories") const docs = await queryContent<StoryParsedContent>("/stories")
.sort({ date: 1 }) .sort({ date: 1 })
@@ -8,8 +8,6 @@ const docs = await queryContent<StoryParsedContent>("/stories")
.find(); .find();
const latest = docs.at(-1) as StoryParsedContent; const latest = docs.at(-1) as StoryParsedContent;
const prettyDate = getPrettyDate(latest);
</script> </script>
<template> <template>
@@ -22,7 +20,7 @@ const prettyDate = getPrettyDate(latest);
> >
<h2 class="m-0 mt-4 mb-1">{{ latest.title }}</h2> <h2 class="m-0 mt-4 mb-1">{{ latest.title }}</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 m-0"> <p class="text-sm text-gray-500 dark:text-gray-400 m-0">
{{ prettyDate }} · {{ calcReadingTime(latest).words.total }} words <Date :doc="latest" /> · {{ calcReadingTime(latest).words.total }} words
</p> </p>
<div class="tag-list mt-1"> <div class="tag-list mt-1">
<Tag <Tag

View File

@@ -10,6 +10,6 @@ const imgSrc =
<template> <template>
<figure class="flex flex-col items-center"> <figure class="flex flex-col items-center">
<img :src="imgSrc" /> <img :src="imgSrc" />
<figcaption class="text-center"><Markdown /></figcaption> <figcaption class="text-center"><slot /></figcaption>
</figure> </figure>
</template> </template>

View File

@@ -18,10 +18,10 @@ import { projects } from "@/data/projects";
<!-- this could be in markdown but eh --> <!-- this could be in markdown but eh -->
<p> <p>
Hello! It's very nice to meet you — I'm a student and Linux enthusiast who Hello! It's very nice to meet you — my name's Daniel, a student studying
is quite passionate about some subjects but is quite lazy in every other. Computer Engineering at the University of Waterloo who is quite passionate
about some subjects but is quite lazy in every other.
</p> </p>
<p> <p>
I've dabbled extensively and non-extensively in a variety of topics to I've dabbled extensively and non-extensively in a variety of topics to
play with, including: play with, including:
@@ -31,6 +31,7 @@ import { projects } from "@/data/projects";
<li>GUI toolkits very very briefly in GTK, Qt, and Swing</li> <li>GUI toolkits very very briefly in GTK, Qt, and Swing</li>
<li>Linux and server administration</li> <li>Linux and server administration</li>
<li>web development in the form of a Chrome extension and my sites</li> <li>web development in the form of a Chrome extension and my sites</li>
<li>hackathons</li>
<li>Godot Engine Cat Simulator DX</li> <li>Godot Engine Cat Simulator DX</li>
<li>ski instruction</li> <li>ski instruction</li>
<li>writing of literature</li> <li>writing of literature</li>
@@ -39,7 +40,9 @@ import { projects } from "@/data/projects";
<p>…and other things that I'm forgetting right now.</p> <p>…and other things that I'm forgetting right now.</p>
<p> <p>
I have two server machines at home a Dell OptiPlex 780 and a Dell I have two server machines at home a Dell OptiPlex 780 and a Dell
Latitude E5520. Yes, one of them is a laptop. Latitude E5520. One of them is a laptop and
<s>I'm surprised it hasn't burnt up yet </s>
<span class="redphasis">it has burnt up.</span>
</p> </p>
<h3>OptiPlex 780 ("asvyn")</h3> <h3>OptiPlex 780 ("asvyn")</h3>
<ul> <ul>
@@ -56,6 +59,10 @@ import { projects } from "@/data/projects";
<li><strong>RAM:</strong> 10 GB</li> <li><strong>RAM:</strong> 10 GB</li>
<li><strong>Storage:</strong> 300 GB hard drive</li> <li><strong>Storage:</strong> 300 GB hard drive</li>
<li><strong>OS:</strong> Arch Linux</li> <li><strong>OS:</strong> Arch Linux</li>
<li>
<strong>Status: </strong>
<span class="redphasis">ON FIRE.</span>
</li>
</ul> </ul>
</div> </div>
</template> </template>
@@ -69,4 +76,9 @@ ul {
margin: 0; margin: 0;
line-height: 1.35; line-height: 1.35;
} }
.redphasis {
font-weight: bold;
color: red;
}
</style> </style>

View File

@@ -12,47 +12,54 @@
</p> </p>
<!-- i could make this a list but god i'm so tired with nuxt --> <!-- i could make this a list but god i'm so tired with nuxt -->
<div class="flex justify-around flex-wrap gap-8 items-center"> <div class="flex justify-around flex-wrap gap-8 items-center">
<ServiceCard name="Gitea" href="https://git.eggworld.tk" img="gitea.png"> <ServiceCard name="Gitea" href="https://git.eggworld.me" img="gitea.webp">
Self-hosted GitHub Self-hosted GitHub
</ServiceCard> </ServiceCard>
<ServiceCard <ServiceCard
name="Eifueo" name="Eifueo"
href="https://eifueo.eggworld.tk" href="https://eifueo.eggworld.me"
img="eifueo.svg" img="eifueo.svg"
> >
Note collection Note collection
</ServiceCard> </ServiceCard>
<ServiceCard <ServiceCard
name="Primoprod" name="Primoprod"
href="https://primoprod.eggworld.tk" href="https://primoprod.eggworld.me"
img="primogem.png" img="primogem.webp"
> >
Wish simulator Wish simulator
</ServiceCard> </ServiceCard>
<ServiceCard <ServiceCard
name="Calibre" name="Calibre"
href="https://calibre.eggworld.tk" href="https://calibre.eggworld.me"
img="calibre-web.png" img="calibre-web.webp"
> >
Kobo Cloud Kobo Cloud
</ServiceCard> </ServiceCard>
<ServiceCard name="Plex" href="https://plex.eggworld.tk" img="plex.png"> <ServiceCard
name="Plex"
href="https://plex.eggworld.me"
img="plex.webp"
broken
>
Ad-filled media server Ad-filled media server
</ServiceCard> </ServiceCard>
<ServiceCard <ServiceCard
name="Jellyfin" name="Jellyfin"
href="https://jellyfin.eggworld.tk" href="https://jellyfin.eggworld.me"
img="jellyfin.png" img="jellyfin.webp"
broken
> >
FOSS media server FOSS media server
</ServiceCard> </ServiceCard>
<ServiceCard <ServiceCard
name="Minecraft" name="Minecraft"
href="minecraft.eggworld.tk" href="minecraft.eggworld.me"
img="minecraft.png" img="minecraft.webp"
:unclickable="true" unclickable
broken
> >
Whitelisted Whitelisted
</ServiceCard> </ServiceCard>

View File

@@ -2,9 +2,14 @@
* Set the page title in the format [title] | Eggworld. * Set the page title in the format [title] | Eggworld.
* @param title The title string. * @param title The title string.
*/ */
export function useTitle(title: string) { export function useTitle(title: string, description?: string) {
useHead({ useHead({
title: `${title} | Eggworld`, title: `${title} | Eggworld`,
meta: [
{ name: "viewport", content: " width=device-width,initial-scale=1" },
{ name: "description", content: description || "" },
{ name: "theme-color", content: "#ffffff" },
],
link: [ link: [
{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" }, { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
{ {
@@ -12,5 +17,13 @@ export function useTitle(title: string) {
href: "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css", href: "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css",
}, },
], ],
script: [
{
defer: true,
src: "/script.js",
hid: "stupidEmergencyScript",
type: "module",
},
],
}); });
} }

View File

@@ -17,7 +17,7 @@ However, this article will largely focus on **desktop Linux**, which competes wi
Perhaps the biggest feature of Linux is its ability to do whatever you want, however you want. After a *tiny* bit of tinkering, you'll be able to set up your computer exactly how you'd like it! Perhaps the biggest feature of Linux is its ability to do whatever you want, however you want. After a *tiny* bit of tinkering, you'll be able to set up your computer exactly how you'd like it!
::image{src=sway-desktop.png} ::image{src=sway-desktop.webp}
A terminal, an emulated Switch game, a game launcher, and a browser all automagically arranged by a tiling window manager. The currently playing song is in the top bar. A terminal, an emulated Switch game, a game launcher, and a browser all automagically arranged by a tiling window manager. The currently playing song is in the top bar.
:: ::

View File

@@ -9,6 +9,6 @@ On the desktop, dark mode is an abomination that should be eradicated from appli
Browsers, IDEs, and other applications must be freed from their shadowy chains and returned to light — where they truly belong. Browsers, IDEs, and other applications must be freed from their shadowy chains and returned to light — where they truly belong.
::image{src=light-discord.png} ::image{src=light-discord.webp}
Perfect. Perfect.
:: ::

View File

@@ -6,7 +6,7 @@ tags:
- tech - tech
--- ---
Welcome to the very first [Primoprod](https://primoprod.eggworld.tk) progress report! In a similar vein to quite a few open source emulation projects (such as those I follow myself using [emufeed](https://github.com/potatoeggy/emufeed/blob/master/sources.py)), I'll be releasing these tidbits in lieu of daily Unstagnation shorts sometimes. Welcome to the very first [Primoprod](https://primoprod.eggworld.me) progress report! In a similar vein to quite a few open source emulation projects (such as those I follow myself using [emufeed](https://github.com/potatoeggy/emufeed/blob/master/sources.py)), I'll be releasing these tidbits in lieu of daily Unstagnation shorts sometimes.
In this hopefully small series of development notes, I'll be laying out my experiences learning web development as an absolute amateur. In this hopefully small series of development notes, I'll be laying out my experiences learning web development as an absolute amateur.
@@ -34,12 +34,12 @@ At the time, coming from Python/Java, I opted in to the class components plugin
The [first few commits](https://github.com/potatoeggy/primoprod/commit/ed9d94b61bf91ea9b82ac4d832dfb2b9ff2efc59) had me playing around until I was comfortable enough to introduce my very [first component](https://github.com/potatoeggy/primoprod/commit/fcbb4068dd3b018db2809ccfcc5381d4ea3ae727): the WishButton. The [first few commits](https://github.com/potatoeggy/primoprod/commit/ed9d94b61bf91ea9b82ac4d832dfb2b9ff2efc59) had me playing around until I was comfortable enough to introduce my very [first component](https://github.com/potatoeggy/primoprod/commit/fcbb4068dd3b018db2809ccfcc5381d4ea3ae727): the WishButton.
::image{src=wish-button-emulated.png} ::image{src=wish-button-emulated.webp}
:: ::
I'd say it turned out pretty well! Since I wanted to emulate Genshin's UI, I wanted to match it as closely as I could. These two buttons are made of an image inside of a div relatively positioned with text absolutely positioned inside. Original image for comparison: I'd say it turned out pretty well! Since I wanted to emulate Genshin's UI, I wanted to match it as closely as I could. These two buttons are made of an image inside of a div relatively positioned with text absolutely positioned inside. Original image for comparison:
::image{src=wish-button-original.png} ::image{src=wish-button-original.webp}
:: ::
There are still some differences between the texts since Genshin uses antialiasing, and the alignment and shadow of the icon beside the wish quantity is slightly off too, but I would consider this result to be acceptable. There are still some differences between the texts since Genshin uses antialiasing, and the alignment and shadow of the icon beside the wish quantity is slightly off too, but I would consider this result to be acceptable.
@@ -58,7 +58,7 @@ See [GemCounter](https://github.com/potatoeggy/primoprod/blob/master/src/compone
Although I had read up on [MDN's](https://developer.mozilla.org/en-US/) fantastic tutorials/documentation a fair bit and used flexboxes and `rem` everywhere, I apparently did not catch `box-sizing: border-box` and the margins and padding just did not arrange themselves how they should have. Although I had read up on [MDN's](https://developer.mozilla.org/en-US/) fantastic tutorials/documentation a fair bit and used flexboxes and `rem` everywhere, I apparently did not catch `box-sizing: border-box` and the margins and padding just did not arrange themselves how they should have.
::image{src=mdn-box-sizing-tip.png} ::image{src=mdn-box-sizing-tip.webp}
:: ::
:/ thanks MDN for letting me know :/ thanks MDN for letting me know
@@ -69,12 +69,12 @@ Although I had read up on [MDN's](https://developer.mozilla.org/en-US/) fantasti
Designing the basic screen was pretty straightforward. For all its woes, pure CSS still works and is intuitive enough that my git history was only slightly too messy and I got my results. Designing the basic screen was pretty straightforward. For all its woes, pure CSS still works and is intuitive enough that my git history was only slightly too messy and I got my results.
::image{src=primoprod-wishbanners.png} ::image{src=primoprod-wishbanners.webp}
:: ::
Pretty good, right? Now, the design still isn't adaptive enough *since things get cut off for who knows why I thought flexboxes were supposed to solve all this* but for the most part it looks good enough. It appears I'll have to make a lot of exceptions for mobile devices… Pretty good, right? Now, the design still isn't adaptive enough *since things get cut off for who knows why I thought flexboxes were supposed to solve all this* but for the most part it looks good enough. It appears I'll have to make a lot of exceptions for mobile devices…
::image{src=primoprod-wishbanners-scaled.png} ::image{src=primoprod-wishbanners-scaled.webp}
:: ::
With some help taken by examining the assets of https://genshin.thekima.com and https://gi-wish-simulator.uzairashraf.dev, I grabbed a static background image as well as the videos! With some help taken by examining the assets of https://genshin.thekima.com and https://gi-wish-simulator.uzairashraf.dev, I grabbed a static background image as well as the videos!
@@ -83,7 +83,7 @@ For now, every time you pressed the wish button, you got a five-star animation.
## Asset prefetching and inconsistencies ## Asset prefetching and inconsistencies
Progress was steady and things appeared to be running perfectly. but on my local machine things load instantly, courtesy of it not being beamed around the world. So when it was deployed to https://primoprod.eggworld.tk, even with a relatively fast download speed, the background image loaded slowly, and there were a few seconds of downtime before a video would play. Progress was steady and things appeared to be running perfectly. but on my local machine things load instantly, courtesy of it not being beamed around the world. So when it was deployed to https://primoprod.eggworld.me, even with a relatively fast download speed, the background image loaded slowly, and there were a few seconds of downtime before a video would play.
This was unacceptable, so I investigated around on how Vue prefetches assets before giving up and setting up an [Electron](https://www.electronjs.org/) build for the desktop — this doesn't resolve the issue on the web, but until I'm more competent with Vue I can't quite make the necessary changes. The issue of slow asset loading also severely lengthened the suffering in the `ItemRevealScreen.vue` arc, where animations and audio had to be timed… This was unacceptable, so I investigated around on how Vue prefetches assets before giving up and setting up an [Electron](https://www.electronjs.org/) build for the desktop — this doesn't resolve the issue on the web, but until I'm more competent with Vue I can't quite make the necessary changes. The issue of slow asset loading also severely lengthened the suffering in the `ItemRevealScreen.vue` arc, where animations and audio had to be timed…
@@ -144,10 +144,10 @@ In fact, I consider those two to be 100% done unless I can find a way to apply a
But it looks great! But it looks great!
::image{src=itemdescriptionoverlay.png} ::image{src=itemdescriptionoverlay.webp}
:: ::
::image{src=itemobtainoverlay.png} ::image{src=itemobtainoverlay.webp}
:: ::
## Wrapping up ## Wrapping up

View File

@@ -0,0 +1,51 @@
---
title: "Stay Anonymous Online With These 4 Browser Extensions"
date: "2022-08-06"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/stay-anonymous-online-with-these-4-browser-extensions-f032c43f2bb)
It's a given that no matter what you do while browsing the web, Big Tech has their grubby fingers all over your data and tracks every site you visit, selling it to advertisers and other shady actors. Preventing this is almost impossible, short of something like Tor Browser behind a VPN in a virtual machine, which is slow and cumbersome.
<!-- more -->
If you're enamored with the convenience of major browsers, you can still increase your privacy by installing these well-known extensions for Chrome/Firefox, each tackling a different part of web security.
### 1. uBlock Origin
You might already know this extension as a popular ad blocker. However, it can do so much more — in addition to blocking ads, uBlock Origin [blocks trackers and malware sites](https://github.com/gorhill/uBlock) while being one of the lightest ad blockers out there to keep your sites snappy. By blocking trackers, you obviously reduce the amount of data that sites can collect, but by blocking ads, you also don't perform the *request* for the ad, so it's like the ad service doesn't even know you were ever there.
### 2. Decentraleyes
Whenever you connect to virtually any modern website, that website loads JavaScript libraries to make itself interactive, typically from content delivery networks (CDNs) such as Google, Microsoft, and Cloudflare. This, of course, isn't great, because that means that those CDNs know what site you're loading them from.
Enter Decentraleyes! This extension bundles quite a few common libraries so that whenever a site tries to grab it from a CDN, it loads it from Decentraleyes running locally on your computer instead. Privacy benefits aside, this also means that those sites load faster by not having to fetch more files from the web!
### 3. ClearURLs
Have you ever clicked through a bunch of links and ended up with the longest URL you've ever seen, all for a simple search query?
**A simple search for Wikipedia on Google:**
https://www.google.com/search?q=wikipedia&hl=en&ei=Ex7vYq-HI4KH0PEPpKus0Ac&ved=0ahUKEwjv3Lil0rP5AhWCAzQIHaQVC3oQ4dUDCA0&oq=wikipedia&gs_lcp=Cgdnd3Mtd2l6EAxKBAhBGABKBAhGGABQAFgAYABoAHABeACAAQCIAQCSAQCYAQA&sclient=gws-wiz
**A search for dog food on Amazon:**
https://www.amazon.com/s?k=dog+food&crid=2UT0FTXBL16XJ&sprefix=dog+foo%2Caps%2C182&ref=nb_sb_noss_2
All the nonsense after the ampersand (&) are used purely for tracking and won't be caught by content blockers like uBlock Origin because they're *in the initial request itself*. This data includes device and browser information, your search history, how you arrived at the site, and more.
ClearURLs offers a solution by simply removing all of the nonessential parts of a URL before sending it to Google or Amazon so that they never receive this information, securing a little bit more of your privacy.
### 4. NoScript
Lastly, the most intrusive forms of data collection are through JavaScript. So what if you just turned off JavaScript? You'd dramatically improve your browsing experience as sites load much faster, ads don't load at all, and none of the trackers can do a thing, but it could also result in broken sites that rely on it to function at all.
Therefore, if you truly care about privacy and want maximum control over what is allowed to load in your browser, you'll want to be able to selectively enable JavaScript per-script per website. NoScript is just the tool for this, with an easy-to-use interface that streamlines the troubleshooting process when sites require JavaScript.
------
You only need four extensions to dramatically reduce the data sites can collect on you from your browser. Although even these are no match for a properly tuned, custom solution like [LibreWolf](https://librewolf.net/), they provide an excellent first step into becoming more privacy-aware and in securing your data.

View File

@@ -0,0 +1,47 @@
---
title: "AV1 — The FOSS Video Codec"
date: 2022-11-13
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/av1-the-foss-video-codec-1761ad9b0a4a)
More than 60% of all internet traffic is video — YouTube and Netflix are 20% alone! In fact, during COVID, so many people started to watch so much video that that number climbed up to 25%, even as they reduced the quality and size of the videos they served.
This much traffic costs lots of money for internet service providers, who carry on this cost to the streaming service.
And so, media companies, including YouTube and Netflix, have banded together as the Alliance of Open Media (AOM) to develop a video format to reduce the file size of videos: AV1!
<!-- more -->
## How did they shrink video?
Digital video today is represented as a collection of many still pictures known as “frames” that are shown to the viewer really quickly to create the *illusion* of motion.
Each individual frame contains thousands to *millions* of tiny squares called *pixels.*
However, at a typical 24 to 30 frames per second, youd have to store more than a *thousand* frames per minute! Uncompressed, a 1080p would need to stream nearly 50 million pixels per second, or 50 MB/s. You might see how this could grow expensive for both the streaming service and the consumer, *fast*.
So, someone figured out that, hey, we can compress pictures! Why dont we also compress video, too?
And thats exactly what we did — video *codecs* were created to provide a standard format to compress or “encode” video, providing large file size savings at the cost of more resources required to decode them upon playback.
For example, MPEG-2 in DVDs was able to use only 0.25 MB/s of bandwidth for 1080p video when pushed to its limits, though it was a pixelated, smeary mess.
## Why not use H.265?
H.265 is the successor to the ubiquitous H.264 codec that virtually every video player can process. Popularised on many physical media such as Blu-Rays, no browser is currently able to natively play back H.265-encoded content, for one very good reason: MPEG-LA, the organisation that designed it, charges royalties for each of its users.
Understandably, browsers dont want to have to pay millions of dollars recurringly just to play video, so its been stuck playing local media.
Why doesnt H.264 have this problem, you ask? [Cisco generously open sourced](https://en.wikipedia.org/wiki/OpenH264) their implementation of the codec and agreed to pay royalties to MPEG-LA, letting anyone legally use the codec for free.
## Support AV1!
In light of these problems, Google led the development of AV1 as a royalty-free, high quality alternative to H.265. All modern browsers today have already implemented AV1 playback, and hardware released within the last two years all support hardware decoding of AV1, meaning that the performance and power cost of supporting the codec has decreased drastically.
By supporting open standards, you can support a world where outdated royalty models no longer exist, and we can all benefit off of each others work.

View File

@@ -0,0 +1,172 @@
---
title: "Etymology of Barin"
date: 2022-10-12
_draft: true
tags:
- barin
---
The [Barin](/tags/stories/barin) universe hosts a variety of different locations. The word "Barin" itself originates as an anagram of the word "brain".
<!-- more -->
## Preton
This major alliance
### Herdit
This nation
### Kora
This kingdom
### Ptuyo
This nation
## Farele
The Farelean Confederacy ("Farele")
### Ciers
This city-state
- Larapelio Avenue
- Nyread Tower
- SleepCity Ciers
- Orwell Library
- Bibliothèque de Lispector
### Leeco
This nation
- Universe City
- Cekendery
- Porter Hall
- Seascout Avenue
- Ira Hagey
- Brian Wright
### Demauge
This nation
- Ceseo Convention Centre
- Data Structure
- Stadio Avenue
- Deathfalcon Drive
- Triplesea Street
- French / FrenchScript
## Enigma
This alliance
### Xunil
This nation
### Weilam
This nation
- Saiyu Sphere
### Eos
This nation
- Emerald Forest
- Laveli Town
### Asvyn
This empire
- Favonius
- Shiganshina
- Kanokari
- Keion
- Shuchiin
- Musani
- Tonikawa
- Ruvinheigen
- Renge Academy
- Biyori
- Sayoasa
- Panquia
- Emina
- The Sinamaria Rose
- Dedication Road
- Kuvira Smith
## Other locations
### Aucervean Mountains
### Moyen Channel
### Ambera Connection (CiersEos)
### English Connection (CiersLeeco)
### Scrivener Connection (CiersXunil)
### Ezwon Connection (CiersDemauge)
### Arslash Connection (CiersHerdit)
## Characters
### Eifueo
### George Anstion
### Siava Pythone
### Minestro
### Nano and Rymo
### Rio Nohigi
### Cloche
### Garson
### Mrs. Lowshi
### Alston
### Brendan May
### Hina Asvyn
### Retadux
### Arro
### Calitea

View File

@@ -0,0 +1,53 @@
---
title: "Choosing a License — Politics in FOSS"
date: "2022-09-02"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/choosing-a-license-politics-in-foss-df2cbfe48237)
All FOSS projects must have a license before other people can use them — but which should you choose, and what are the consequences behind each one?
<!-- more -->
## You need a license
Under the [Berne Convention](https://en.wikipedia.org/wiki/Berne_Convention) signed by the vast majority of countries in 1886, you own copyright to your software the moment you create it. Rights *exclusive to you* unless given otherwise include the right to copy and modify that software.
In a nutshell, it means that even if you submit your code to GitHub, *no one else* is allowed to copy or redistribute your code (e.g., via forking the repository).
Licenses work around this by granting specific permissions to other people *so long* as they follow the rules in the license.
Although there are many different types and variations of licenses, they can be broadly broken down into two categories: **copyleft** licenses and **permissive** licenses.
## Permissive licenses (e.g., MIT, BSD, Apache)
These licenses are pretty simple: they let anyone do virtually anything with them, so long as they credit you for using your code. This makes it easier for people to incorporate your code into theirs, but also raises the possibility that others, such as corporations, simply take your code without giving back.
Permissively licensed programs tend to be used more often by bigger projects due to the lack of restrictions.
The BSD operating system is a typical example of the dynamic of permissive licenses: Apple used BSD code as a basis for all of their operating systems (macOS, iOS, iPadOS, etc.), as well as Sony in their PlayStations, Nintendo in the Nintendo Switch, and much more. These corporations chose BSD because of how easy it is to take the code for free and build off of it.
## Copyleft licenses (e.g., GPL, MPL, CC-BY-SA)
If you really care about the ideology behind the free software movement, you might consider a copyleft license instead. These require all **derivative works** to have the same license that the original program had. They can still do whatever they want with your code, though.
For example, if library A held a copyleft license and application B bundled it in an app that they published, application B must also be compatible with that copyleft license or else they are in violation of library A's license.
…Which is a lot of words to say that copyleft licenses are "viral" in that they make everything they touch copyleft.
If you're a developer, this is really good for you, because it means that if a large corporation adapts your code to make it work for them and improves it, they have to share those improvements with you.
The Linux kernel is a great example of how copyleft has helped it grow to become the most-used operating system in the world. As devices adopt Linux (e.g., in Android phones), they are required to publish sources that make Linux compatible with their hardware, which is how the custom ROM community exists today!
Some corporations really hate copyleft because it forces them to open source their code, such as Google's [infamous policy on avoiding the AGPL](https://opensource.google/documentation/reference/using/agpl-policy/) like the plague. If that matters to you, you should use a more permissive license instead.
------
It's important to make sure that you are *allowed* to use a license in your project! After all, if you have a copyleft dependency, you can't license your program permissively. In addition, plenty of licenses are incompatible with one another, so you can't use both of them together.
In general, your program must be at least as copyleft as the most copyleft license in your dependency chain.
Although not a definitive guide, [https://choosealicense.com](https://choosealicense.com/) from GitHub is a great starting point to choose a license! So long as you're careful and check your dependencies' licenses, you'll be well on your way to contributing to the FOSS community!

View File

@@ -0,0 +1,76 @@
---
title: "Git is a Blockchain"
date: "2022-10-02"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/git-is-a-blockchain-1060b53cea1f)
Git today is a beloved technology developers all around the world now use to track the history of a project through revisions. Obviously, it's not magic — if you look at the hidden files in a Git repository, you'll find a `.git` folder at least as big as the rest of your repo.
<!-- more -->
But for a technology that lets you revert the exact state of your files back any number of years, that `.git` folder is surprisingly small. You'd expect that any backup program worth its salt would actually back up each version of each file, but Git tracks *changes* to files instead of the actual files themselves, a method known as [delta encoding](https://en.wikipedia.org/wiki/Delta_encoding).
Not to mention all of the other features that Git piles on for practically no storage cost, such as branches, merges, cherry-picking, and more?
How does it do it, you ask?
Quantum Web3 decentralised machine learning VR blockchain agile AI cloud storage microservice architecture.
Only two out of the many buzzwords above are are actually used in Git, but Git was the decentralised blockchain before decentralised blockchains were cool.
But, I hear you cry, Git couldn't *possibly* be a blockchain! Blockchains are the stuff used in cryptocurrencies, scams, and other overhyped technologies!
In that case, why don't we compare the two? Let's start with the [Wikipedia definition of a blockchain:](https://en.wikipedia.org/wiki/Blockchain)
## A blockchain is a distributed ledger.
In simple terms, a blockchain isn't centralised, and a blockchain is copied across many computers, typically via peer-to-peer, such that each person with the data can verify that it's true.
…What do you know? That's exactly what Git does! Each Git repository is copied in its entirety by each person who clones it. When you push a commit, you send an update to another repo hosted somewhere else. When someone else pulls your commit, they *also* do it from a repo hosted somewhere else.
## A blockchain is decentralised.
At this point, Git servers have largely centralised into large services such as GitHub and GitLab, but that does not mean that the repository hosted there is the "true" one. Any contributor can overwrite the "centralised" version with their own local changes. In fact, there are lots of other server options out there from FOSS hosts such as Gitea, Codeberg, Sourcehut, and more. You can even host your own Git server!
Although Git *can* be centralised, the technology is inherently decentralised because it does not differentiate between servers — all of them are simple "remote"s.
## A blockchain consists of a growing list of records.
Transaction, meet commit. This one doesn't even need an explanation.
## Each record contains a cryptographic hash of the previous hash!
Hypothetically, right now, what if you run `git show` in your repository? Oh, hey! Is that a SHA-1 commit hash? Leading to the previous SHA-1 commit hash?? Who would've thunk it??? It's almost as if Git is a blockchain!!!!
```
commit ff25f8f352ed9e9f2fd07275ff136182a1711508
Author: NAME <EMAIL@EMAIL.COM>
Date: Tue Aug 16 22:45:12 2022 -0400
fix(api): add back imports
pylance keeps deleting them >:(
diff --git a/mandown/__init__.py b/mandown/__init__.py
index 4bba27f..9b12daa 100644
```
Each Git commit is hashed in a cryptographically secure* way that links back to the previous hash, which makes it so that you can't modify a previous hash without changing all of the hashes afterward.
This is literally what happens when you mess up a commit and try to revert it but the server yells at you due to mismatched histories because you messed up reverting it properly.
------
By every commonplace definition of the word, Git utilises a blockchain in its revision control system. It's at the heart of how each change can be uniquely tracked across systems.
A blockchain is nothing special. It's a technology you can use to avoid data tampering via communal tracking of transactions / commits. So, the next time you hear someone rambling on and on about blockchain and crypto, remember that it's just a tool as a means to an end, not some magical solution to everything.
------
*SHA-1 is no longer cryptographically secure, but Git has moved to a patched version that hasn't been cracked yet. SHA-256 support is also in the works.

View File

@@ -0,0 +1,56 @@
---
title: "GitHub for Dummies"
date: "2022-06-17"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/github-for-dummies-1bb448962fc5)
Ever been linked to a GitHub page to download something and couldn't figure out what to click? This short, simple guide will help you get to wherever you need in 4 easy steps.
<!-- more -->
::image{src="https://lh4.googleusercontent.com/u03FMDrVGVaU7x0ojxnNTAnM6_sPaSsnP1R6pZDJaTH95xqiH8LQKBN4OTZoU0Bigf6seLcCcDtSkuxcMwuLqLrCQH2fs6QsUZFyw58DN26sdbJcmMjXrhWjRQg6aoSzu-gBzib3gz20s0uFi_9h5k7QpgbohXqNkXw2pXPfPu4j3VibM_MNlHBK"}
What do all these words mean? Issues? Pull requests? Actions? Projects? Releases?
::
### The README and wiki
Always remember to first RTFM (Read The Fine Manual, with a slightly different word in place of "fine")! The README is always located on the home page of a repository, and it should contain a wealth of information for newcomers to familiarize themselves with the project. Installation instructions are often placed here if you scroll down far enough.
If that doesn't work, check out the project's wiki if they have one, located in the top navigation bar of the repository. Community members can also contribute to this page, so if you want to help the project and make it easier for newcomers, feel free to add or edit a page!
### Downloading releases
Now, if neither the README nor wiki have any hints to finding the link you want, you can go to the *Releases* section of the project, which on a desktop browser is in the right sidebar, while on mobile is located at the very bottom of the page.
::image{src="https://lh4.googleusercontent.com/v6G-c31NECe6ZJZhe2YSQXocQ4eCBJhYuXjNWSmECm5QQcSKaMWLpxe_roIkIonkMfUcDK4UtuqQEegVXCD1sAwHQnkssxOEk3uUrnQaMbhXL8zyeXdi0nUNv_QTKFsD5ZAUDJijHv_dc5wdTOEjggZipsIStM3vwaiabiNQ8XUY5bolApOupOwd"}
In this case, clicking one of the "primoprod" assets with the file extension for your device will get you a runnable program.
::
Releases are the "official" way for projects to upload stable versions of their program to send to others. In the "Assets" section of a release, clicking the link that is not labeled "Source code" will get you a runnable version of the program.
### Downloading repository files
Let's say you don't want to download the whole program, but instead just a certain file *inside* the repository. What do you do?
Similar to Google Drive and other cloud storage platforms, you can click through different folders and eventually on files. Although you can't download folders, you *can* download individual files by clicking on them and then clicking "Raw" or "View Raw".
If that doesn't work, right-click the page that clicking "Raw" or "View Raw" opened and press "Save Page As".
### Filing issues
Trouble in paradise? If none of the above options worked, you can always file a new issue in the "Issues" tab of the project, where you can report bugs and ask questions directly to the project owner.
![img](https://lh6.googleusercontent.com/l7mlo6OTPsAi17WcYgLeZ39aVp65D_24Kz4PMYOgKArwxJcz4jRcTCtud9UtChEUiUdnVR8sR7_6TvQJAAL2mFcKecLK-hhPvr7De_tPqrvh_mbaNCfVisD2yBn2icaXSl0eFDD4cIHKOzPKOM--2hruiM6qHkC6foW-6Pu63pU9c6FZOid10WXN)
Remember to read the project's issue guidelines, as some of them have a dedicated support forum or Discord to send help requests to.
### Contribute to FOSS!
For those just trying to get a file and dip, GitHub's interface is certainly more complex than it needs to be. The many buttons to streamline developers' experiences often get in the way of a user just trying to download the program.
As a developer, though, GitHub's many features are powerful and make development faster and more organised. If you ever decide to contribute to the FOSS community, return to GitHub and try to learn about the many other tools available to you!

View File

@@ -0,0 +1,55 @@
---
title: "Google's Guide to Taking Over the Web"
date: "2022-07-16"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/googles-guide-to-taking-over-the-web-26847a389ac5)
Do you have a dream? A dream where you call the shots for billions of other people and how they communicate with each other? Well, we've got the guide of the decade for you, taken straight from personal experience. In just four easy steps, you'll be able to corner the tech industry yourself!
<!-- more -->
## 1. Bribe your competitors.
This first step is the most important. Without getting your name out there, you don't have the influence needed for web domination. Take out a small loan if you have to. We've bought out Mozilla for $450 million and Apple for $15 billion so that we're the default search engine in Firefox and Safari. Why would our users ever bother to change the default — to something like *Bing*, no less — when ours is good enough?
You gotta get your customers to associate you with the web, and you know you've got that when your company name is added to the English dictionary. What's the first thing you do whenever you want to look something up? You Google it. And the simplest, easiest, lowest-effort way to do this is by bribing your competitors.
## 2. Create a browser.
Make it simple. Make it fast. Make it such that people actually want to use it. Creating your own browser lets you into a whole bunch of important standards organizations, including Ecma and ISO, but most importantly, the W3C.
Now that everyone is using your search engine, you can abuse your near-monopoly there to tell everyone to use your new browser.
"A better way to surf the web."
And since you're Google, they trust you. After all, they get all your information from you, so why would you lie to them? Your users will all happily switch. And, if you've gotten creative, your newfound popularity might mean that your browser engine will be used *outside* of the browser in, say, a popular [server-side language](https://nodejs.org/en/), or even to [make desktop applications](https://www.electronjs.org/)!
All giving you more power over where the future of the web goes.
## 3. Extend open standards.
Now that you have a secure foothold in guiding the future of the web and everyone loves your products, it's time to take the rest of the pages out of Microsoft's playbook: Embrace, Extend, and Extinguish. It's time to add new features. Ones that your users like! But make sure you leverage your own services to show just how important you are to the web. Make others *depend* on you.
For example, you could [send all traffic to news sites through your own servers](https://en.wikipedia.org/wiki/Accelerated_Mobile_Pages) and call it "speeding up page loads." Don't forget to pretty it up! Open source it, but *you're* the only one allowed to contribute to it. In your search engine — you know, the one that everyone relies on? — rank sites higher depending on if they use your service. That'll encourage them.
Also, this point is a great time to start hobbling your browser competitors. Lots of people are using your own browser now, but the other browsers are just as good and less people are switching. That's a problem. What can you do about that?
Hypothetically, of course, you could take another one of your services — one might even say one that is indispensable to the web — and make it [several times slower in other browsers](https://fossbytes.com/youtube-slow-mozilla-firefox-edge/) by using special code in your own browser. Now you've got users' attention. An essential service they use is slow in their browser! How convenient, then, that your browser just happens to fix all those problems!
Alternatively, if you happen to own an operating system that [powers 70% of all smartphones](https://gs.statcounter.com/os-market-share/mobile/worldwide), you can also put it in there, as only [10% of users change their default mobile browser](https://www.independent.co.uk/tech/google-alternatives-privacy-duckduckgo-search-engine-browser-chrome-eu-fine-a8455321.html)!
## 4. Make so many standards until no one can catch up.
If you've made it this far, you've got this in the bag. Just make the web experience "more complete" and "more streamlined!" Do what you've been doing in step 3 but make it impossible for any newcomers to do it securely and properly. Add a protocol for [USB access in the browser](https://en.wikipedia.org/wiki/WebUSB). Add a protocol for [Bluetooth in the browser](https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API). Replace third-party cookies with your own specialized tracking mechanism. All in the name of security and privacy, of course.
There are so many "standard" features in Chrome that it is practically unsustainable to create a new browser engine properly. Opera gave up and switched to Chrome. *Microsoft* tried with Edge before they gave up and used Chrome's engine instead.
## 5. Profit!
That's it! No one can stop you now. You have a stranglehold on the web. Your browser / browser engine have [70% of browser share worldwide](https://gs.statcounter.com/os-market-share/mobile/worldwide). Even in China, where your whole ecosystem is practically banned, you have [more than 50% market share](https://gs.statcounter.com/browser-market-share/all/china). Developers optimize for you, further reducing the market share of other browsers as sites are now broken in other browsers.
You're done. You've taken over the web. There is nothing anyone can do…except switch to Firefox. But who's going to do that? Why would anyone sacrifice the convenience of Chrome just to reject anti-competitive behavior of one company?

View File

@@ -0,0 +1,552 @@
---
title: "13 Tricks to Write Nicer Python"
date: "2022-08-21"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/13-tricks-to-write-nicer-python-d6c65897cd59)
Known as an easy-to-use and flexible programming language, Python nevertheless still has plenty of tricks you can use to make your code prettier and faster to write. No matter if youre new to Python or have years of experience, read more to find a tip for you!
<!-- more -->
## 1. F-strings
When printing out lots of text, you might find that you have to add lots of text together, which you might do by string concatenation:
```py
name = "John"
lastname = "Doe"
print("My name is " + name + " and my last name is " + lastname + ".")
```
As you can see, it gets quite long and cumbersome if the string is long enough and if youre adding enough strings. This is where **f-strings** come in to make the code readable and actually more performant! These special strings begin with the letter “f” immediately before the opening quote of a string, and any expression in curly braces will be evaluated.
```py
print(f"My name is {name} and my last name is {lastname}.")
```
Completely clear, right?
Thats not all they can do, either! F-strings also provide some ways to easily format the data by providing how you want the data to be displayed after a colon in the expression.
For example, you can limit the number of decimal places shown in a float to two decimal places…
```py
num = 123.456789
print(f"{num:.2f}")
```
…and even dates and times!
```py
from datetime import date
today = date(2022, 8, 21)
print(f"{today:%m/%d/%Y}")
```
Output:
```
08/21/2022
```
## 2. Using “if” to its maximum potential
Like any language, Python has its quirks with values that evaluate to `True` and `False` in if statements. Specifically, **only the following are False** and every other value is True.
- Zero (`0` and `0.0`)
- Empty containers, such as lists, tuples, dictionaries, sets, **and strings**
- `False`
- `None`
This means that actions like checking for empty containers can be drastically shortened from:
```py
array = []
if len(array) != 0:
print("something is in the list!")
```
to:
```py
array = []
if array:
print("something is in the list!")
```
## 3. List comprehensions
You can generate a new list very easily with an inline `for`.
```py
array = [i for i in range(10)]
print(array)
```
Output:
```py
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```
List comprehensions are just syntactic sugar for a regular for loop, so the above code is equivalent to:
```py
array = []
for i in range(10):
array.append(i)
print(array)
```
Theyre also useful for performing operations *on* the elements, much like `map` in other functional programming languages.
```py
array = [i for i in range(10)]
new_array = [i ** 2 for i in array]
print(new_array)
```
Output:
```py
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
```
In addition, as an alternative to `filter`, you can only include specific elements by adding an `if` at the end:
```py
array = [i ** 2 for i in range(10) if i % 2 == 0]
print(array)
```
Output:
```py
[0, 4, 16, 36, 64]
```
This is equivalent to:
```py
array = []
for i in range(10):
if i % 2 == 0:
array.append(i ** 2)
print(array)
```
## 4. Slicing strings and arrays
Compared to more traditional languages that only allow array accesses `array[i]` for one element, Python lets you "slice" a list or any other sequence (like strings) by a colon between the two indexes.
```py
string = "hello world!"
array = [1, 2, 3, 4, 5, 6]
print(string[2:7])
print(array[:-2])
```
Output:
```
llo w
[1, 2, 3, 4]
```
## 5. Tuple expansion
Tuple expansion is one of Pythons most powerful features, allowing you to expand or assign multiple values in a single line.
```py
a, b = 1, 2
s1, s2 = "baguette", "tomato" + "cheese"
print(a, b)
print(s1, s2)
```
Output:
```
1 2
baguette tomatocheese
```
The code above effectively assigns a tuple `(a, b)` to another tuple `(1, 2)` and is equivalent to the code below:
```py
a = 1
b = 2
s1 = "baguette"
s2 = "tomato" + "cheese"
```
This can let you return multiple values from a function.
```py
def init():
return (5, 6)
a, b = init()
```
And its also really useful in swapping variables!
```py
a = 2
b = 4
# oops, assigned them wrong
b, a = a, b
```
Tuple expansion can even be used in for loops to iterate over tuples or dictionaries!
```py
items = {
"apple": "juice",
"orange": "pulp",
"banana": "smoothie",
"mango": "slushie"
}
for key, value in items.items():
print(key, value)
```
Output:
```
apple juice
banana smoothie
mango slushie
orange pulp
```
## 6. Shorter code with shortcircuiting
Imagine you want to assign a variable based on the value of another variable. You might do it this way:
```py
name = "John"
if name == "John":
last_name = "Doe"
else:
last_name = "Unknown"
```
But thats long, and `last_name` appears twice. You can have an inline if to do it in just one line:
```py
name = "John"
last_name = "Doe" if name == "John" else "Unknown"
```
Much nicer!
You also might be familiar with logical boolean operators and how theyre used in if statements.
```py
if password == "hunter2" and name == "Joe":
print("password accepted")
```
But you might not know some of their quirks or exactly how they work.
If you have two things opposite an `and`, for example `x and y`:
- If `x` evaluates to True, `y` is returned.
- If `x` evaluates to False, `x` is returned.
This might not seem that useful, but then look at `or` in `x or y`:
- If `x` evaluates to True, `x` is returned.
- If `x` evaluates to False, `y` is returned.
This might also not seem that useful until you learn that logical operators can also be used in assignments, where theyre most commonly used for fallback values.
```py
confirm = input("Accept the EULA? (Y/n) ") or "y"
```
Because `input` returns a string, if the user doesn't enter anything, it evaluates to False, so the value `"y"` is assigned to `confirm`.
## 7. Safer file handing with context managers
In most languages, when you write something to a file, its actually held in a buffer until you close it manually or automatically when the program ends.
In Python, you would run the following to write to a file:
```py
file = open("myfile.txt")
file.write("hello file")
file.close()
```
But thats a lot of lines, and you might forget to run `file.close()`, particularly in long programs with lots of stuff being written to the file.
Thats where **context managers** come in, abstracting the whole process and calling `file.close()` automagically:
```py
with open("myfile.txt") as file:
file.write("hello file")
```
## 8. Nicer iteration with zip() and enumerate()
Pythons for loop is commonly known in other programming languages as a for-each loop. This is great if you just want each item in an iterable, but sometimes you want the index too! Instead of having to resort to `range(len(array))`, instead you can use `enumerate()` and tuple expansion to easily get both the index of the element and the element itself:
```py
array = ["a", "b", "c", "d", "e"]
for i, c in enumerate(array):
print(i, c)
```
Output:
```
0 a
1 b
2 c
3 d
4 e
```
In a similar vein, if you have two arrays you want to process at the same time, you dont need to use `range(len(array))` when you have `zip()`, which will bundle the different iterators into one big one as big as the smallest iterable.
```py
ints = [1, 2, 3, 4]
strs = ("pomme", "poutine", "pinterest", "pear")
floats = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
for i, s, f in zip(ints, strs, floats):
print(i, s, f)
```
Output:
```
1 pomme 1.0
2 poutine 2.0
3 pinterest 3.0
4 pear 4.0
```
## 9. Nicer paths with pathlib
Python is a cross-platform language, and if theres one annoying difference between Windows and macOS / Linux, its that their path separators are different: Windows uses the backslash (`\`) while macOS and Linux use the forward slash (`/`), and attempting to access a path the wrong way will result in file not found.
So, in the olden days, you would have to string together a long string with `os.path.join` just to be safe:
```py
import os
path = os.path.join("folder", os.path.join("subfolder", "subsubfile.txt"))
```
Doesnt that look clunky?
Luckily, a “new” (read: years-old) [addition to the standard library](https://docs.python.org/3/library/pathlib.html) makes that *much* easier:
```py
from pathlib import Path
path = Path("folder") / "subfolder" / "subsubfile.txt"
```
It also includes a bunch of helper methods to check for things and navigate the filesystem tree just because Python loves making your life easier.
```py
from pathlib import Path
path = Path("folder")
if path.exists() and path.is_file():
print("yay!")
print(path.parent)
print(path.suffix)
if not path.exists():
path.mkdir() ### create the folder if it doesn't exist
```
## 10. Iterable unpacking
n competitive programming, often you have to print out space-separated results. This can be done by the mildly inconveniencing
```py
print(" ".join([1, 2, 3, 4]))
```
or heaven forbid, via iteration:
```py
for i in [1, 2, 3, 4]:
print(i, end=" ")
print()
```
which is where iterable unpacking comes in, and you can go straight to
```py
array = [1, 2, 3, 4]
print(*array)
```
The **unpacking operator** (the asterisk) basically gets rid of the container and throws all of the elements inside directly into the print function as parameters.
That means that the above line of code is equivalent to:
```py
print(1, 2, 3, 4)
```
which nicely prints out the integers separated by spaces with a newline at the end.
But wait, theres more! The unpacking operator is also commonly used in function definitions as a catch-all parameter for extra arguments, stuffing them into a list.
```py
def init(a, b, *args):
print(a, b)
print(args)
init(1, 2, "pomme", 4, 6.0)
```
Output:
```
1, 2
['pomme', 4, 6.0]
```
You can also use this in normal assignment to, say, only get the first and last elements of an array:
```py
first, *args, last = [1, 2, 3, 4, 5]
print(first)
print(args)
print(last)
```
Output:
```
1
[2, 3, 4]
5
```
## 11. Else outside of if
Everyone knows what if-else does. But did you know that Python also lets you use it after loops and exception blocks?
- In loops, `else` is run *only if* the loop did not `break`.
- In exception blocks, `else` is run *only if* there was no exception.
These are especially useful so you dont have to add an indicator variable yourself:
```py
for i in array:
if i.is_tomato():
print("found a tomato!")
break
else:
print("no tomato found :(")
```
…is equivalent to
```py
found_tomato = False
for i in array:
if i.is_tomato():
found_tomato = True
break
if found_tomato:
print("found a tomato!")
else:
print("non tomato found :(")
```
Similarly, in a try-except block:
```py
try:
number = int(input("Enter a number: "))
except ValueError:
print("That's not a number >:(")
else:
print("You know how to follow instructions! :D")
```
…which is equivalent to:
```py
is_number = True
try:
number = int(input("Enter a number: "))
except ValueError:
is_number = False
if is_number:
print("You know how to follow instructions! :D")
else:
print("That's not a number >:(")
```
## 12. Type hinting
When you use a library, your IDE often knows what types a function will accept so you dont have to guess.
These come from **type hints** in the code, which you can use in your own code, especially if there is a lot of reused code and its a long program.
A colon after a variable shows its type:
```py
a: int = 4
```
…while an arrow after a function shows its return value.
```py
def pow(x: int, y: int) -> int:
return x ** 2
def largest(array: list[int]) -> int:
return max(array)
```
In more complex programs, type hinting is especially useful as your IDE can provide autocomplete and better syntax highlighting as it knows the limits of your program, so if you accidentally assume the wrong type of an object, your IDE will complain and find the bug before it causes a runtime crash.
## 13. The walrus operator (:=)
Say youre doing something over and over again and checking for a condition until its true. You could do it in a while loop:
```py
import requests
r = requests.get("https://google.com")
while r.status_code != 200:
print(r.status_code)
time.sleep(1)
r = requests.get("https://google.com")
```
But the assignment is repeated twice!
Introducing the walrus operator, which assigns *and returns the assigned value* in the same statement, allowing for less repeated code and some pretty crazy one-liners:
```py
import requests
while (r := requests.get("https://google.com")).status_code != 200:
print(r.status_code)
time.sleep(1)
```
---
Although thirteen of them are covered here, there are endless ways to optimise your code so that its faster to read, write, and run. Python is a “batteries included” language — chances are that the way youre used to doing things in other languages have a shorter and more concise method in Python.
The most important tip I can give you is to check the standard library if you want to do something — from [image format recognition](https://docs.python.org/3/library/imghdr.html) to [config file management](https://docs.python.org/3/library/configparser.html) to [basic database operations](https://docs.python.org/3/library/sqlite3.html), the standard library is chock full of useful tools included over the years.

View File

@@ -0,0 +1,56 @@
---
title: "Reviving Older Games Through Emulation"
date: "2022-05-22"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/reviving-older-games-through-emulation-ca4e9705700c)
Throughout the past few decades, video games have evolved from an art medium restricted solely to specialized consoles to one enjoyable on all sorts of platforms, including everyday devices such as PCs and phones, replayable for the years to come.
However, for all of the beloved games that *are* locked to consoles, what option do you have but to let them die out as discontinued second-hand consoles grow more and more expensive as they break down?
Introducing…emulation!
<!-- more -->
### TIL converting games to Australian birds will preserve them.
::image{src="https://upload.wikimedia.org/wikipedia/commons/9/9d/Emu_1_-_Tidbinbilla.jpg"}
(Wikipedia Commons, public domain)
::
Though *emu* and *emulation* share three letters, the only other similarity they have is that large entities have tried and failed miserably to stamp them out in the past. Emulation is the process of one system (such as a phone or computer) imitating another one (such as a video game console) to run programs designed for that system. Emus will now forever ravage the Australian wilderness, and emulation has been ruled to be legal in at least the United States.
Today, almost every popular system with games in demand has an emulator developed for them that works to some extent, allowing people to take their *rightfully owned* games and play them long after the original system has been discontinued or play them with mods to enhance their experience such as by increasing resolution.
In a nutshell, this means that if the Nintendo DS you owned as a kid suddenly broke one day, you can back up your cartridges and play Pokémon Black on your phone or computer instead!
### Cool, how do I try it out?
Modern-day emulators are nearly all FOSS, allowing them to be ported to most desktop and even mobile operating systems. We recommend the emulators below for their respective systems for a variety of reasons, including user-friendliness, ease of use, performance, and accessibility.
| Platform | PC | Android |
| ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| Sony PlayStation 2 | [PCSX2](https://pcsx2.net/) | [AetherSX2](https://www.aethersx2.com/) |
| Nintendo GameCube / Wii | [Dolphin](https://dolphin-emu.org/) | [Dolphin](https://dolphin-emu.org/) |
| Nintendo DS | [melonDS](https://melonds.kuribo64.net/) / [DeSmuME](https://desmume.org/download/) | [DraStic](https://play.google.com/store/apps/details?id=com.dsemu.drastic&gl=US) / [melonDS](https://github.com/rafaelvcaetano/melonDS-android) |
| Sony PlayStation 3 | [RPCS3](https://rpcs3.net/) | None |
| Nintendo 3DS | [Citra](https://citra-emu.org/) | [Citra](https://citra-emu.org/) / [Mikage](https://mikage.app/) |
| Nintendo Switch | [yuzu](https://yuzu-emu.org/) / [Ryujinx](https://ryujinx.org/) | [Skyline](https://github.com/skyline-emu/skyline) (WIP) |
Do note that emulators, unlike typical games, are *much* heavier on the CPU compared to the GPU because of all the translation required for the emulator to "convert" the game from the console's "language" to that of your phone or PC.
You can find more details about each emulator and other options for different consoles on the [Emulation General Wiki](https://emulation.gametechwiki.com/index.php/Main_Page).
### I want to learn more!
From stories about games relying on undocumented behaviour to *game-breaking* bugs cancelled out only by *console-breaking* bugs, emulation is a fascinating topic well-deserving of your interest if you're at all interested in low-level systems.
Modern emulators usually perform high-level emulation (HLE), where they emulate the functions that the system OS provides, compared to emulating each transistor like some monstrously complex Minecraft redstone contraption. As you might guess, this approach sacrifices some accuracy for greater performance — it's like translating a book character by character instead of word by word.
For further reading, there are plenty of blogs run by emulators outlining their tales in overcoming tricky obstacles, and we here at the *Albatross* strongly recommend you go check them out! [Dolphin](https://dolphin-emu.org/blog/) (GameCube / Wii), [yuzu](https://yuzu-emu.org/entry/) (Switch), [Ryujinx](https://blog.ryujinx.org/) (Switch), and [RPCS3](https://rpcs3.net/blog/) (PS3) all attempt to post regular progress reports on their respective blogs, linked above.

View File

@@ -0,0 +1,33 @@
---
title: "Appreciate Your Browser!"
date: "2022-09-18"
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/appreciate-your-browser-82d36a81a696)
*Click.* Milliseconds after you press a link, your screen flashes white. Elements slowly load in as a page full of search results appears in front of your eyes. How does this magic work? How does your computer know exactly what to show you when you press a button on your mouse? Let's go on a journey through your system to see exactly what happens.
<!-- more -->
------
The instant your finger pushes past a physical barrier, a switch connects a circuit and electrons go speeding through a wire (or over the air if your mouse is wireless) to your computer.
The moment the central processing unit (CPU) of your computer receives the electrons, it propagates that signal up to your operating system as a **hardware interrupt**, pulling all the brakes on whatever it's currently doing to handle the signal.
Eventually, your browser realises it's been clicked on and starts the process to fetch the page you're looking for from the internet. Through the operating system, your browser sends a small packet of information to your router, asking it where exactly https://en.wikipedia.org is through its **domain name system** (DNS)!
Your router kindly asks several other routers, which in turn ask even more routers until it gets back to you with a "208.80.154.224"!
Armed with this **internet protocol** (IP) address, your browser then asks your router to connect to it, which it happily does, informing you when it returns that the server with that address does in fact exist and is alive.
Now it's finally time to connect to the server. Your browser extends a handshake to the server, hoping it will reciprocate. Because Wikipedia is a trustworthy site that knows social expectations, it will return the handshake, whispering a secret only it knows through the **Transport Layer Security** (TLS) protocol.
Assured of Wikipedia's trustworthiness, your browser whispers secrets back, telling Wikipedia the information you're looking for. Wikipedia nods knowingly, but because they have other people to meet, they tell you everything you could ever need to know about parakeets in less than a millisecond with the power of **gzip** (compression) — you just need some time to decipher it.
Finally satisfied, your browser arranges the data prettily so that it's easy for you to read and holds it out to you expectantly, waiting for your praise.
Unfortunately, you snatch it out of your browser's hands, berating it for being so slow as you skim only the opening paragraph before yelling at it to fetch more information from Wikipedia, this time about parrots.

View File

@@ -0,0 +1,9 @@
---
title: "Politics of Barin"
date: 2022-10-24
_draft: true
tags:
- barin
---

View File

@@ -14,7 +14,7 @@ This report will cover from where the previous left off to the present day: 21 A
## No more ## No more
::image{src="primoprod-itemrevealscreen.png"} ::image{src="primoprod-itemrevealscreen.webp"}
:: ::
It's done. The pull screen is done. The element/weapon icon was added to the pull. Audio syncs up (well enough). The only thing missing is all of the fancy effects like glow and particles. It's done. The pull screen is done. The element/weapon icon was added to the pull. Audio syncs up (well enough). The only thing missing is all of the fancy effects like glow and particles.
@@ -23,7 +23,7 @@ Nah, this is good enough.
## Take this! ## Take this!
::image{src="primoprod-questscreen.png"} ::image{src="primoprod-questscreen.webp"}
:: ::
Until now, you had to manually edit the browser's `localStorage` to gain any currency. The quest screen makes primoprod finally usable as now you can make your own long-term "quests" that give 900 Primogems each as well as set four daily "tasks" that give 30 Primogems each plus 60 when all are done — if only the base game was this generous. These are editable and can have whatever title or description you want. The logic here went through several rewrites as the structure was finalised and an interface developed to the rest of primoprod. Dailies will automatically refresh themselves on the next day. Until now, you had to manually edit the browser's `localStorage` to gain any currency. The quest screen makes primoprod finally usable as now you can make your own long-term "quests" that give 900 Primogems each as well as set four daily "tasks" that give 30 Primogems each plus 60 when all are done — if only the base game was this generous. These are editable and can have whatever title or description you want. The logic here went through several rewrites as the structure was finalised and an interface developed to the rest of primoprod. Dailies will automatically refresh themselves on the next day.
@@ -56,7 +56,7 @@ Sounds incredible, right? As it turns out, as you gain more experience with tech
This is why [the shop](https://github.com/potatoeggy/primoprod/blob/master/src/components/ShopScreen.vue) and the [dialog to buy things from the shop](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemPurchaseOverlay.vue) are so nicely done! It reused most of my types and was admittedly much simpler than some of the other screens, but I only ran into one insurmountable problem: range styling. This is why [the shop](https://github.com/potatoeggy/primoprod/blob/master/src/components/ShopScreen.vue) and the [dialog to buy things from the shop](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemPurchaseOverlay.vue) are so nicely done! It reused most of my types and was admittedly much simpler than some of the other screens, but I only ran into one insurmountable problem: range styling.
::image{src="primoprod-itempurchaseoverlay.png"} ::image{src="primoprod-itempurchaseoverlay.webp"}
:: ::
*I wish I was actually this rich in the base game.* *I wish I was actually this rich in the base game.*
@@ -127,7 +127,7 @@ Back to laptop compilation we go!
Obviously, you aren't a *real* free and open source project if you don't have [pretty badges](https://github.com/potatoeggy/primoprod/blob/master/README.md) on your README. This was a major concern, so I copied Vue's style and now made primoprod a proper FOSS repo — build checkmark and badges and all! Obviously, you aren't a *real* free and open source project if you don't have [pretty badges](https://github.com/potatoeggy/primoprod/blob/master/README.md) on your README. This was a major concern, so I copied Vue's style and now made primoprod a proper FOSS repo — build checkmark and badges and all!
::image{src="primoprod-badges.png"} ::image{src="primoprod-badges.webp"}
:: ::
## Kids and their phones ## Kids and their phones
@@ -140,7 +140,7 @@ ItemRevealScreen? What's that? Now [WishBanners](https://github.com/potatoeggy/p
At last, though, we have a [proper mobile UI](https://github.com/potatoeggy/primoprod/pull/33). At last, though, we have a [proper mobile UI](https://github.com/potatoeggy/primoprod/pull/33).
::image{src="mobile-primoprod.png"} ::image{src="mobile-primoprod.webp"}
:: ::
Still some niggles to work out, but it looks "good enough"! With the completion of proper mobile orientation came the merging of the [Android branch](https://github.com/potatoeggy/primoprod/pull/32) made with [Capacitor.js](https://capacitorjs.com/), basically the mobile equivalent of Electron. It has even more niggles than the web version does. Still some niggles to work out, but it looks "good enough"! With the completion of proper mobile orientation came the merging of the [Android branch](https://github.com/potatoeggy/primoprod/pull/32) made with [Capacitor.js](https://capacitorjs.com/), basically the mobile equivalent of Electron. It has even more niggles than the web version does.
@@ -151,6 +151,6 @@ If you have any idea how to fix them, please do send a suggestion on the [issue
And that's everything that changed since August! Primoprod has come a long way from being a mere personal project to a proper personal project with fancy badges! Hopefully in the future it'll be able to be played standalone (chibi sprite combat, anyone?) so that it'll be interesting enough that people can set their goals and stick to them. And that's everything that changed since August! Primoprod has come a long way from being a mere personal project to a proper personal project with fancy badges! Hopefully in the future it'll be able to be played standalone (chibi sprite combat, anyone?) so that it'll be interesting enough that people can set their goals and stick to them.
In case you'd like to check it out, version 1.1.0 released just [yesterday](https://github.com/potatoeggy/primoprod/releases/tag/v1.1.0) and is available for download for Windows, macOS, Linux, and Android, with a web version available at https://primoprod.eggworld.tk! In case you'd like to check it out, version 1.1.0 released just [yesterday](https://github.com/potatoeggy/primoprod/releases/tag/v1.1.0) and is available for download for Windows, macOS, Linux, and Android, with a web version available at https://primoprod.eggworld.me!
Until next time! Until next time!

View File

@@ -0,0 +1,101 @@
---
title: "Rust Changes How You Think And Code"
date: 2022-11-27
tags:
- tech
- rust
- albatross
---
This article is [also published in *The FOSS Albatross.*](https://medium.com/the-foss-albatross/rust-changes-how-you-think-and-code-2b5ee4d8def2)
Rust is the hot new language on the block (as new as a language from 2006 can be) that boasts reliability and efficiency.
How does it do this? Well, Rust has something that no other language does — it guarantees memory and thread safety while maintaining the same high performance of C or C++, all the while having high level features such as pattern matching and functional programming!
<!-- more -->
Some languages come close: Go is known for being both fast to run and to write, but its garbage collector and xenophobia toward other languages adds overhead that means that it is not suited for a systems programming language.
In safe Rust, there is *no such thing* as undefined behaviour. Everything your code says it does will happen — segfaults and NullPointerExceptions are impossible.
For simpler issues, the rustc compiler tells you more or less exactly what went wrong, along with a helpful error code, a link for examples on how to fix the error code, and even a suggestion that applies directly to your current code, which more times than not immediately fixes the issue.
```rust
error: format argument must be a string literal
--> helloworld.rs:3:14
|
3 | println!(123);
| ^^^
|
help: you might be missing a string literal to format with
|
3 | println!("{}", 123);
|
error[E0384]: cannot assign twice to immutable variable `a`
--> helloworld.rs:3:5
|
2 | let a = 123;
| -
| |
| first assignment to `a`
| help: consider making this binding mutable: `mut a`
3 | a *= 2;
| ^^^^^^ cannot assign twice to immutable variable
```
Thanks, rustc!
In time, you come to stop thinking less about the edges in the language and focus more on implementing what you want to implement. Rust makes it so that you don't have to stop and ask yourself these questions every minute:
- What if this variable isn't initialised or defined?
- What if this variable is already used?
- What if I'm modifying a variable that isn't supposed to be modified?
- What if another thread changes this data while I'm reading it?
- Did I forget to handle an error?
- Did I forget to check the error?
So how does it do this?
## Immutability by default
There are debates on whether immutable or mutable objects are better. Well, Rust provides both — but you have to *explicitly* tell Rust that you want your variables to be mutable. For example, the second error message in this article shows that you need the `mut` keyword to let the compiler let you change variable values.
```rust
let mut a = 1;
```
This applies to everything: from references to function arguments. If a variable isn't passed as `mut`, it's not mutable, and there is nothing else you can do to get around that. This isn't like JavaScript's `const`, either — the internal variables of a struct also have to be declared mutable in order to overwrite them.
This added friction to mutability means that developers tend to prefer immutable objects when possible, so it's very clear when a variable can change!
## The borrow checker
Perhaps Rust's flagship feature, this is how Rust manages memory without the complexity of manual memory management or the overhead of a garbage collector. In a nutshell, each variable is given an owner, and they may only have one owner.
You can "borrow" the value if you want to do something with it but give it back to use later, but the typical pitfalls of pointers don't exist in Rust because *there are no pointers in Rust!* (At least, not safe Rust.)
Here's an example of what Rust prevents — if you operated on vector B, it would change C, so Rust's safety guarantees would not hold. That's why the compiler doesn't let you run this in the first place.
```rust
let a = vec![1, 2, 3];
let b = a;
let c = a;
2 | let a = vec![1, 2, 3];
| - move occurs because `a` has type `Vec<i32>`, which does not implement the `Copy` trait
3 | let b = a;
| - value moved here
4 | let c = a;
| ^ value used here after move
```
And it's here that you really have to appreciate how much information the compiler gives you. It:
- tells you where the value originated from
- tells you where the value was used
- and tells you where the value was used *again*, which is not allowed
## Conclusion
Though there is a rather steep learning curve from just *how much* there is to unlearn about the finnicky things you can do in more traditional languages, Rust is a language that lets the computer calculate if your program is correct, letting you think purely on how to solve your problems.

View File

@@ -0,0 +1,37 @@
---
title: "What's Wayland? Linux's \"New\" Display Server"
date: 2022-12-11
tags:
- tech
- albatross
---
Wayland is the shiny next-generation display server protocol that finally lets the Linux desktop move away from the cludgy abomination that is X. Although it was initially released in 2008, it's only recently that it has matured enough to the point that Linux distributions have begun to default to it.
<!-- more -->
## History
Ever since 1984, the Linux desktop has used the X Window System in order to output contents to a display.
38 years later, it's still alive and kicking in most Linux computers with all of its outdated features, such as slow compositing, built-in easy keylogging, and a lot of overhead just in case you want to operate your computer over the internet! Even with piled on extension (xrandr) after extension (xrender) and extension (composite), all of the bloat accumulated over time to the point that most of the X.Org server team moved to work on Wayland. As of today, X.Org is in maintenance mode, with few contributions and even fewer contributors because of all of the legacy code and practices that have to be worked with.
And that's exactly why Wayland was created — to be a simple, modern, and fast display protocol that narrows its scope to focus entirely on displaying windows efficiently and securely. Secondary priorities such as remote desktop, screen sharing, and screen tearing were implemented much later on.
No longer can any window catch every keypress without asking the user! No longer does shutting down the compositor crash nearly every program! No longer can any application decide to take over your screen and wreak havoc because it's allowed to do *literally anything!* No longer is workaround after workaround needed to maintain a *research project* that was never meant to be globally deployed!
Not to mention that because Wayland was designed from the ground up, it supports modern display technologies that X simply isn't able to due to technical limitations, such as different variable refresh rates over different displays at different resolutions.
## Adoption
Both Firefox and Chromium natively support Wayland. The GTK+, Qt, Electron, and SDL toolkits do too, letting thousands of applications "just work" by updating their dependencies.
In fact, the biggest desktop environments already support Wayland and all of its benefits:
- GNOME and KDE Plasma both have great support and even default to it on some distros.
- [Sway](https://swaywm.org) is a drop-in replacement for the i3 window manager if you're into tiling WMs.
- The Steam Deck uses Wayland to contain and sandbox games in its Gamescope compositor.
In addition, even for legacy apps such as games that can never be updated to support Wayland, a built-in compatibility layer called XWayland is there to run X applications in their very own contained X server. Even so, limitations in XWayland prevent it from being a one-size-fits-all solution to every X application.
Regardless, Wayland is still the future! Development on X has more or less completely stopped, so unless a party is desperate for a feature and is willing to invest a lot of time and money into navigating around the clunkier parts of the X.Org codebase, Wayland is the only available option for the future.

View File

@@ -0,0 +1,119 @@
---
title: "Why Use Web Frameworks?"
date: 2022-10-30
tags:
- tech
- albatross
---
This article is [also published in *The FOSS Albatross*](https://medium.com/the-foss-albatross/why-use-a-web-framework-e1bdf1a8c1cf).
You're a web developer and you need to make a website. How should you build one? Do you start writing static files right away to be served immediately, or do you start setting up a project environment?
<!-- more -->
First, do you decide to use a framework? They're all the rage these days.
By far the biggest advantage of a web framework is its ability to make things declarative. Imagine you want to make a to-do list. You might write code like this to be able to add a new todo:
```html
<script>
function newTodo() {
const divBox = document.createElement("div")
const input = document.createElement("input")
input.type = "checkbox"
const p = document.createElement("p")
p.appendChild(document.createTextNode("Untitled todo"))
divBox.appendChild(input)
divBox.appendChild(p)
document.getElementById("button-box").appendChild(divBox)
}
</script>
<div id="button-box">
<div>
<input type="checkbox" />
<p>Do chores</p>
</div>
<button onclick="newTodo">New to-do</button>
</div>
```
...but then how would you make the whole system interactive? How could you delete todos, edit todos, or mark them as completed? You'd have to bring a whole state system that updates the document just to manage a simple todo app!
Meanwhile, frameworks abstract a lot of the element creation away so that you can focus on just writing components and how they *should* behave, not how to implement their behaviour.
For example, in Vue.js, you could do something like this:
```vue
<script setup>
import { ref } from "vue";
const items = ref([
{ title: "Do chores", completed: false }
]);
function newButton() {
items.push({ title: "Untitled to-do", completed: false );
}
function deleteButton(index) {
items.splice(index, 1);
}
</script>
<template>
<div>
<div v-for="(item, i) in items" :key="i">
<input type="checkbox" @click="item.completed = !item.completed" />
<p>{{ item.title }}</p>
<button @click="deleteButton(i)">Delete</button>
</div>
<button @click="newButton()">New to-do</button>
</div>
</template>
```
This short snippet makes it clear to the developer exactly what's happening and does way more than the pure version above. Not only can it add new todos, it can delete them and check them off too! At the cost of learning just a little more syntax, you can make the framework do much of the heavy lifting for you.
In this example, you tell Vue that you want a `div` for every item in your array with a checkbox, paragraph, and button. Whenever you add or remove an item to that array, Vue is responsible for making sure that your website matches the updated array so you don't have to fiddle with the document.
## Components
Working on files with hundreds of lines is never fun. Since HTML can only be sent in one file, this means that complex client-side applications tend to have too many lines to properly read and understand if they don't use a framework.
Once again, frameworks come to the rescue! Most of them have a component model that lets you reuse a lot of code, making it easier to read.
For example, once again in Vue:
```vue
<template>
<div>
<p>Hello</p>
<p>This is going to be used a lot!</p>
</div>
</template>
```
```vue
<script setup>
import UsedALot from "UsedALot.vue";
</script>
<template>
<UsedALot />
<UsedALot />
<UsedALot />
</template>
```
This splitting of logic makes it easier for your brain to focus on specific subchunks of code so it's easier to read and understand.
---
Web frameworks offer a lot of advantages over writing in a more imperative style. However, their abstractions do come at a cost — an extra minimum [16 KB](https://vuejs.org/about/faq.html#is-vue-lightweight) in JavaScript has to be fetched from your server, which can increase bandwidth costs and make your site slower.
Ultimately, in creating your website, much like when you do anything else, you should weigh the pros and cons of web frameworks before deciding to go with one or the other.

63
content/blog/2022/wine.md Normal file
View File

@@ -0,0 +1,63 @@
---
title: "Running Windows Apps on Mac and Linux"
date: "2022-10-16"
tags:
- tech
- albatross
---
This article is [also published in The FOSS Albatross.](https://medium.com/the-foss-albatross/running-windows-apps-on-mac-and-linux-c372996588af)
Windows is a beloved operating system with absolutely no flaws whatsoever. It is the pinnacle of engineering — sheer perfection in its design.
<!-- more -->
But say you like being objectively correct and actually think that Windows has its own problems, so you decide to switch to a Mac or to use Linux instead.
What happens to all of the programs you leave behind? Linux and macOS don't recognise .exe files as ones they can run, so you won't be able to run those incompatible with Linux without extra work. If you can find alternatives for all of them, that's great, but there still might be stragglers for beloved programs such as niche apps and games.
You could launch real Windows inside a virtual machine, which guarantees compatibility (except with certain anti-cheats), but tends to have a pretty big performance impact. If you're interested, check out [our article on running virtual machines](https://medium.com/the-foss-albatross/an-os-inside-an-os-how-to-run-virtual-machines-a3ddf6c8bbed)!
In this article, we'll be looking at Wine, a program that is practically magical by letting you run Windows apps natively on macOS or Linux!
## What is Wine?
Wine is a common alcoholic beverage —
Wine is a *compatibility layer* for Windows applications, translating Windows system calls into Mac or Linux ones.
Windows programs are actually quite similar to Linux and macOS programs. They all make calls to system APIs, and they all compile down to machine code. Because the different OSes run on the same x86_64 architecture (pre-Apple M1), there's no need to go through the expensive emulation of a whole other CPU like you see in game console emulators.
To be able to run the program, Wine hooks into the application and intercepts all of the calls it makes, passing it to its own reimplementation of the Windows filesystem and its various libraries or DLLs.
In fact, some of the APIs called are actually the same on all operating systems (such as OpenGL for graphics), so Wine can pass those through directly. In some cases, running an application through Wine can be even faster than on Windows!
Even if the APIs aren't exactly the same, Wine can still do a little bit of work to make them compatible. For example, plenty of work in making games run better has led to DirectX 12, 11, and 9 all translatable to the lower-level Vulkan, supported natively on Linux. On macOS, yet another translation layer takes the Vulkan output and turns it into the macOS-preferred API, Metal. Ironically, some games on Windows run *faster on Windows* if you run a DirectX-to-Vulkan translator! Combined with the general impression of a faster Linux, you can see how games can run *faster* under Wine than in Windows!
Wine's integration and familiarity with the host (Linux or macOS) means that it has some niceties that make it easier to use than a virtual machine, such as desktop integration! Shortcuts from installed Windows apps will appear on your desktop and in your launcher, and you can access all your files from within Wine. Pre-established shared Documents / Desktop / Videos / Music folders mean that getting started is super easy.
## Does it work?
Anyone who's tried to run a modern version of Office will be able to tell you that compatibility isn't perfect. Although Windows APIs are documented by Microsoft, many applications rely on undefined behaviour and quirks in Windows' implementation of those APIs, which Wine has to chase down and patch each time.
In addition, some anti-cheats fundamentally rely on the Windows kernel, and Wine's reimplementation isn't yet complete. Valve's work on the Steam Deck, which uses Wine extensively under the hood to run games on its Linux platform, has helped substantially in persuading developers to support Wine, but still many other games remain locked to Windows under the guise of fairness.
In general, howeve
r, you can expect many applications to at least start. Wine themselves [maintains a database](https://appdb.winehq.org/) of how well many applications run on their website. If it doesnt work—it was worth a shot, at least!
## How can I use it?
Wine is already in many Linux distributions' repositories, so installing it through your package manager should get you up and ready.
On macOS (pre-10.15), Wine can be installed using homebrew [as described on their website](https://wiki.winehq.org/MacOS).
Once Wine is installed, running a program is as easy as passing the path of the program you want to run into Wine:
```
wine path/to/program.exe
```
…or more commonly, you can just double-click the .exe file.
The decades spent on making the transition away from Windows easier have resulted in a marvel of a project capable of running proprietary software on a *different operating system* with a minimal performance cost. If it's helped you out, consider [getting involved](https://www.winehq.org/getinvolved) or [donating](https://www.winehq.org/donate)!

View File

@@ -0,0 +1,134 @@
---
title: "I Found A Magical Fish While Fishing With My Cousin (In Another World)!"
date: 2022-11-14
_draft: true
tags:
- uoft
- nanowrimo
---
**Notes:** This was written as part of *University of Teyvat* for NaNoWriMo 2022, and also submitted to the WatSFiC × CWC Short Story Contest.
---
A wooden rowboat drifted over a lake. Its two occupants sat facing each other on their respective benches, wearing matching wide brim hats as they relaxed in the bright summer sun. Two fishing lines were mounted on opposite ends of the boat, completely still in the water.
<!-- more -->
All was quiet but for the light brush of wind ruffling their clothing, and the water gently lapping against the side of the boat. Yanfei tipped her head back, adjusting her cap so that it would sit over her eyes. "Thanks again for coming out with me, Ganyu. How's work, by the way?"
"Oh, it's light right now, don't worry," Glancing at the rods floating around the water, Ganyu waved the matter aside. "I should be asking about you. I hear your legal consultancy has gotten a lot of attention lately."
"Yup! Although," Yanfei sighed, "there's so much conflict between Vision-holders and the common folk lately. So many people are suing each other left and right… The divorce cases are the *worst.* No one's ever always right, and *I* have to be the one to…to… I wish society was more black and white," she said, slumping further back against the wall.
Ganyu chuckled, her eyes darkening. "You don't say."
"You've lived a lot longer than I have." Yanfei glanced up at Ganyu. "What do you think?"
"Hmm…" Ganyu said. "Do you know an old book from Mondstadt written by wind-worshippers?
"*Lost Prayers to the Sacred Winds*, right? It's supposed to be insightful — I've been meaning to check it out sometime. I wish I had one, though."
"You should give it a read. It's not going to help you very much with dealing with people, but it's still a good book. Personally, I think experience is what helped me the most. Visit new countries, meet new people, throw yourself into very different situations…" Her voice trailed off as she stared into the depths of the lake. The murky water made it hard to see past a few centimetres, but the slight wiggling of the line sent unmistakable ripples through the previously still water. "Yanfei, do you see that?"
"We got something!" Yanfei grunted, pulling hard on the fishing rod as she tried to reel it back in. "I…think this one's…a big one! Ganyu…help?"
Ganyu reached over and effortlessly *yanked* the fishing line up with her hands. A mossy treasure chest flew out of the water and landed perfectly in the centre of the boat, splashing lake water all over their shoes. More water continuously streamed out of the chest and onto the base of the boat. Ganyu wrinkled her nose as she turned on the Automatic Magic Canoe Water Drainer™.
Yanfei stared. "How'd *that* get caught on the line?" She poked the chest using the other end of the fishing rod.
The chest did not poke back.
"It's kinda heavy," Yanfei reported. "Maybe it has treasure! I'm gonna open it."
"Are you sure? Something this old at the bottom of a lake would have a larger chance of being an evil creature that wants to eat your soul. Although," Ganyu mused, "if you're lucky, it could instead be some legendary magical weapon that has the power to freeze a whole ocean."
The rusty silver lock on the chest was broken, inviting the two to pry it open for the mystical contents inside. With a lowly, mysterious *creak*, Yanfei pushed open the lid of the chest. She and Ganyu stared inside. "Is that…a bow?"
Carefully, Ganyu removed the large, shining archery implement, avoiding the sharp blue spikes along its body. As she ran a hand over its aged wooden frame, Ganyu inspected the glowing runes all around the grip and belly of the bow, giving it an experimental flex and draw. "It says…Polar Star," she read, squinting to make out the ancient language. "Seems like a good bow. I'll keep it."
"Good decision," Yanfei nodded. "If you don't overflow on CRIT Rate, it's your best-in-slot in freeze teams. Second BiS in melt, too."
"Huh?" Ganyu asked.
"Huh?" Yanfei asked. "I mean, you're the archer girl, not me. I'm not gonna be able to use it. It's all yours."
Turning the bow over one last time, Ganyu swung the bow behind her. Yanfei watched it disappear into pretty golden sparks into the secret dimensional subspace where all of them kept their weapons. "I think there was something else in the chest."
Indeed, they heard a *flop-flop-flop* coming from the chest. There was still one more item in the chest. Something…alive.
Yanfei creeped closer, eventually poking her head over the lip of the chest to peek inside.
An orange fish with bulging eyes and a mouth that looked like it was used to sucking bananas all day greeted her. Yanfei gaped. "It's so cute! Is this a koi fish, Ganyu? It's orange, so that means it has pyro powers, right? Oh, I'll name you…magic koi — Magikoi!"
"Actually, I think it's a different kind of carp."
"Oh. Magikarp, then. Come with me! I'll take care of you. You're going to grow up to be big and strong one day," Yanfei said gently, a warm smile on her face as she reached out with both hands to welcome the fish into her extended family.
Evidently, the fish did not want to be a new member of Yanfei's family and *splashed* out of the chest right onto her face.
*"Blergh!* Get it off, get it off!" Yanfei batted the fish away as she turned her head out to spit fish water, *eugh*, out of her mouth. The fish went flying toward Ganyu, who dodged it instinctively.
"It's getting away! No, Magikarp, stop!"
In one rapid movement, Ganyu drew an arrow from her quiver and fired, ice energy gathering at the arrowhead for a millisecond before she released it to trap the fish in a solid block of ice. It plummeted back to the bench beside where Yanfei was sitting.
Once she felt sufficiently less unclean, Yanfei proceeded to take out a worn music notebook and rip out one of its pages to set it on fire. The ice around the fish promptly melted and returned to reveal a fish. "It's not dead," Yanfei observed. "I think it's just fainted. That's one hardy fish."
Ganyu continued to aim the Polar Star at it. "Do you *want* it dead?"
Yanfei considered the idea. "Actually, do you mind holding off on that for now? I've always wanted to try something."
---
Ganyu and Yanfei stood on opposite ends of the small boat, which barely wobbled thanks to the Automatic Magic Boat Stabiliser™ 9000 that Ganyu activated. Yanfei thrust out her hand, kicking Magikarp out from beside her to take the field. "Go, Magikarp! I choose you!" she cried, waiting expectantly.
Like a fish out of water, Magikarp flopped onto the floor pathetically.
"Magikarp, you can do it!" Yanfei tried again.
Like someone imitating a dead fish, Magikarp floundered on the floor uselessly.
"Magikarp, I'm begging you! I'll give you snacks!" Yanfei pleaded.
Like a child throwing a temper tantrum because they didn't want to eat fish, Magikarp wiggled on the floor pitifully.
"I see. " Yanfei clenched her fist, staring down at the floor.
Like a Water-Type being electrocuted by an Electric-Type but not really caring, Magikarp jerked about on the floor woefully.
"I'm so sorry for not listening to you, partner," Yanfei whispered, falling to her knees.
Like a student just seeing the second page of the midterm paper for the first time, Magikarp drooped onto the floor anemically.
"It's my fault. I never treasured our bond enough," Yanfei said, squeezing her eyes shut. A single tear trailed down her cheek.
Like a joke that went on for far too long and was reanimated several times, Magikarp lurched about on the floor feebly.
Flames blazing around her to represent her determination, Yanfei fist-pumped the air, gesturing wildly before pointing at Ganyu. "I understand you now. Magikarp, *use Splash!"*
Like *it was the weakest and most pathetic* fish in the world, Magikarp splashed about on the floor aimlessly!
But nothing happened. Yanfei stood in silence for a few seconds. Ganyu returned from her fighting stance to a normal standing position, frowning. "How useless."
Yanfei nodded agreeably. "Yeah, Magikarp kinda sucks. The only thing it does is splash around. Magikarp," she declared, "you have disappointed me for the last time. By the power invested in me by myself, I sentence you…to exile!"
Ganyu pointed her bow at Magikarp.
"Wait! Do it outside the boat." Yanfei tossed Magikarp off the edge of the boat. Before it could land in the water, the trail of a fully charged ice arrow *schoom*ed past her and slammed into the fish, blasting it straight up. The fish comet left behind a beautiful trail of white that vanished as quickly as it appeared. "Nice shot."
"Thanks."
The two shielded their eyes, watching and waiting for the fish to come back down. "That might be a little bit farther than exile," Yanfei commented after a while.
"It's in a whole new world now," Ganyu said. "Nothing we can do about that."
"Nope. Not a thing." Yanfei settled back on her seat, lounging as they waited for another fish to bite the hook. Snowflakes gently fluttered down around them, melting and sending ripples where they landed in the water. A slight fishy scent filled the air.
"Snow in July," Ganyu observed. "That's new."
Yanfei glanced up. "There's not a cloud in the sky, either."
"Climate change, huh?"
"It's gotta be," Yanfei agreed.

View File

@@ -0,0 +1,38 @@
---
title: "Transition to Universe City"
date: 2022-10-12
tags:
- barin
---
Porter Hall\
Universe City, Leeco
Brian Wright\
Chancellor\
10077 Seascout Avenue\
Cekendery, Leeco
<!-- more -->
Dear Chancellor Wright,
Leeco's Board of Directors convened last night and have appointed me to share with you
their decision regarding the expansion of Universe City. Token resistance from the district
boards were swiftly overturned with immense support from the populace.
Universe City, the "second capital" of Leeco has grown tremendously in cultural and
economic value to the nation over the past several years. Effective 1 October 2022,
Universe City will be designated the official capital city of Leeco. District boards have
already been instructed to begin the process of migrating, and Administration are roughly
one-third of the way done the transition.
Because the decision was made unanimously, consider it impossible to revoke the will of
the Board. Cekendery must maintain good standing with the Board during the transitory
period.
I wish you the best of luck in maintaining unity.
Your Studious Pupil,\
Ira Hagey\
Vice Chancellor of Leeco\
28 September 2022

View File

@@ -10,14 +10,14 @@ Imperial Palace\
1 Kansei Road\ 1 Kansei Road\
Emina, Asvyn Emina, Asvyn
<!-- more -->
Mr. Brendan May\ Mr. Brendan May\
Chairman\ Chairman\
Enigma Alliance\ Enigma Alliance\
3 Indigo Boulevard\ 3 Indigo Boulevard\
Saiyu, Weilam Saiyu, Weilam
<!-- more -->
This is to inform the Chairman, in accordance with the Enigma Agreement, adopted at Saiyu on 12 December 1915 ("the Agreement"), that the Asvish Empire intends to exercise its right to withdraw from the Enigma Alliance. Unless the Asvish Empire identifies suitable terms for re-engagement, the Asvish Empire will submit to the Chairman, as per Article 50, paragraph 2 of the Agreement, formal written notification of its withdrawal as soon as it is eligible to do so. Pending the submission of that notification, in the interest of transparency for parties to the Agreement, the Asvish Empire requests that the Chairman inform the parties to the Agreement and the States entitled to become parties to the Agreement of this communication relating to the Agreement. This is to inform the Chairman, in accordance with the Enigma Agreement, adopted at Saiyu on 12 December 1915 ("the Agreement"), that the Asvish Empire intends to exercise its right to withdraw from the Enigma Alliance. Unless the Asvish Empire identifies suitable terms for re-engagement, the Asvish Empire will submit to the Chairman, as per Article 50, paragraph 2 of the Agreement, formal written notification of its withdrawal as soon as it is eligible to do so. Pending the submission of that notification, in the interest of transparency for parties to the Agreement, the Asvish Empire requests that the Chairman inform the parties to the Agreement and the States entitled to become parties to the Agreement of this communication relating to the Agreement.
Signed,\ Signed,\

View File

@@ -0,0 +1,39 @@
---
title: "Bienvenue au Ciers!"
date: 2020-06-17
tags:
- barin
- unstagnation
---
*“Vous êtes maintenant à la station dEscribe. Cest la dernière station de la ligne de train CiersXunil.*
*“Les portes vont ouvrir: à gauche.*
*“Veuillez vous tenir à lécart des portes.”*
---
Bienvenue au Ciers! Veuillez donc vous assurer que vous avez lune des documents suivantes:
- Si vous êtes un résident permanent ou un citoyen de Ciers, vous devez avoir votre étiquette didentification de Ciers.
- Autrement, si vous êtes un citoyen dune autre tribu dans le zone de voyage de Farele, vous devez avoir identification valide de votre gouvernement de votre tribu. Ceci peut inclure:
- - La licence étudiant de Leeco
- La licence professeur de Leeco
- La licence Non de Leeco
- Le billet intertribal de Demauge
- La carte de transports Farele de Xunil
- Autrement, si vous êtes un citoyen dune tribu de lAlliance Énigma, vous devez avoir le document de voyage FE1-D vous issue par votre tribu.
- Autrement, si vous êtes un citoyen dune tribu de lAlliance Preton, vous devez être accompagné par un fonctionnaire de votre tribu.
- Autrement, si vous êtes du continent de Barin ou tout ses îles (Voir le document de voyage 6-B pour une list complète des îles), vous devez remplir le document de voyage 6-C avant vous allez aux services frontières.
- Autrement, vous devez remplir le document de voyage 6-D avant vous allez aux services frontières.
Malheureusement, à cause de létat actuel des choses intertribal, les passeports intertribaux ne sont pas acceptées comme identification. Nous nous excusons pour tout inconvénients.
Merci pour votre coopération!

View File

@@ -8,10 +8,10 @@ tags:
A huge uproar erupts from the crowds of Saiyu as the results of the 2020 Weilamese election are revealed to the world. A huge uproar erupts from the crowds of Saiyu as the results of the 2020 Weilamese election are revealed to the world.
<!-- more -->
"…and your new President of Weilam until 2024 is…Roy Tamino!" "…and your new President of Weilam until 2024 is…Roy Tamino!"
<!-- more -->
"What the hell!" One citizen has his mouth hanging open. "How—how did *he* win!" "What the hell!" One citizen has his mouth hanging open. "How—how did *he* win!"
A few metres away, another is waving a campaign poster in the air, cheering wildly for her preferred candidate. "Woo! For Weilam! Young power!" A few metres away, another is waving a campaign poster in the air, cheering wildly for her preferred candidate. "Woo! For Weilam! Young power!"

View File

@@ -0,0 +1,39 @@
---
title: "Welcome to Ciers!"
date: 2020-06-14
tags:
- barin
- unstagnation
---
“*You are now at: Escribe Station. This is the last stop on the CiersXunil Line.*
*“The doors will be opening on the: left side.*
*“Please stand clear of the doors.”
---
Welcome to Ciers! Please ensure that you have at least one of the following before you leave the train:
- If you are a permanent resident or citizen of Ciers, or if you are a tribal representative with a Ciersian Identification Tag, you must have your Ciersian Identification Tag.
- Otherwise, if you are a citizen of another tribe in the Farele Free Travel Area, you must have valid government identification from your tribe. These include:
- - Leecan Student Licenses
- Leecan Teacher Licenses
- Leecan Non Licenses
- Demaugian Intertribal Tickets
- Xunilean Farele Travel Passes
- Otherwise, if you are a citizen of a tribe in the Enigma Alliance, you must have Travel Document FE1-D issued by your tribe.
- Otherwise, if you are a citizen of a tribe in the Preton Alliance, you must be accompanied by a government official from your tribe.
- Otherwise, if you are from the continent of Barin or any of its islands (see Travel Document 6-B for a list of all islands considered by Ciers to be as part of Barin), please fill out Travel Document 6-C prior to meeting with Border Services.
- Otherwise, please fill out Travel Document 6-D prior to meeting with Border Services.
Unfortunately, due to the current state of intertribal affairs, Ciers will not be accepting Intertribal Passports as a valid form of identification. We apologise for any inconvenience.
Thank you for your cooperation!

View File

@@ -0,0 +1,117 @@
---
title: "The Birds on Censorship"
date: 2021-07-09
tags:
- birds
- unstagnation
---
**Summary:** Sudden news rocks the Birds' world as they reminisce on their past and their future.
<!-- more -->
---
All is well in the Bird residence. Brandy has come over to hang out today and is kindly preparing breakfast for Skoomer Bird and Noodle Bird, who sit across from each other at the wooden dining table. Their buddy Mango is also in the basement, chilling and playing video games.
Skoomer chuckles as he flips through a particularly interesting page in the newspaper. "Noodle," he says, turning the paper around and leaning forward to show her its contents. "Take a look at these things they banned recently."
Noodle raises her attention from her phone to Skoomer, squinting to read the tiny inked letters. "Wow. Damn."
"And this is just a small section, the rest of the pages are…completely expected." Skoomer sighs. "Some of these I expected too. Algorithms nowadays are incredible at filtering these out, and there is human review as well. I guess to the *common* person this…sounds incredibly scary, doesn't it?" he says, smiling and shaking his head.
"Mm," Noodle agrees. "What happens if you break these rules?" She shifts her gaze back to her phone, brow furrowed. "What if I accidentally posted hentai? Will I go to jail?"
Skoomer's reply is prompt and direct. "Instant deletion and maybe an account ban. Keep in mind, since your account is connected to your ID number, they know who posted it as well. *Exactly* who posted it."
Noodle's face pales for an unknown reason. "Uh oh. Uh…wait, doesn't League count as lewd content? They have 'sexy characters' — like a lot of them basically wearing bikinis." She rapidly types on her phone as she says this.
Skoomer raps his fingers against the table. "League's problem is more of a copyright issue, I would say," he hums. "Bikinis are fine, I think…in practice."
"This is Janna." Noodle raises an eyebrow, holding her phone up to Skoomer, which is showing a picture of a fantastic (d.: "of or relating to fantasy") woman wielding a spear in very revealing attire.
Skoomer nods. "Yeah, this is fine. Nothing wrong at all."
Confusion shows itself on Noodle's face as she slightly lowers her phone. "Oh…okay, never mind — maybe I don't understand what 'lewd' means."
At that moment, Brandy comes in, setting a plate of eggs and bacon in front of both of them. He groans exasperatedly. "Guys, it's too early for this debate."
Skoomer waggles his eyebrows. "Not for me."
Brandy thinks about this for a moment. "True," he decides. "Carry on, then." He steps away back to the kitchen for the other still-sleeping members of the Bird household.
Skoomer puts his newspaper away and now considers Noodle's earlier statement. "Lewd…it's a bit more blurry, I guess. But I think the interesting part is like," he waves away the matter, "the ban on LGBTQ content and underage dating."
"I mean, it *is*, but I'm not surprised about that," Noodle waves away the wave.
"…Because it's reflective of the policies in…many other fields," Skoomer continues.
This piques Noodle's interest. "Oh?"
"At the end of the day, it's not LGBTQ that is being prohibited," Skoomer argues, "but actually the organisations that advocate for it."
Brandy suddenly pops his head back in from the kitchen, frying pan in one hand. "Oh yeah, is underage dating illegal? I remember Skoomer got someone expelled for doing that." He pauses. "Maybe," he adds.
"*Technically*," Skoomer states matter-of-factly, raising a finger in the air, "underage dating is not forbidden as a law, but as local school or district policies."
Brandy shrugs. "Basically a law, then. A by-law, you could say."
Noodle looks disappointed but not terribly surprised. "Skoomer. Why."
Skoomer clasps his hands together and looks straight in Noodle's eyes. "It's my duty." He smiles.
"Oh," Brandy starts, "I remembered that correctly?"
"Just change 'someone' to a plural and yeah."
"Well, of course," Brandy says, amused. "It takes two of them."
"Ah, well, more than two," Skoomer corrects him. "Also, I designed a policy that actually doesn't do it in pairs."
Noodle lets out a gasp of horror and utter shock, eyes wide and hands cupped around her mouth. "Skoomer! You were in a *threesome?"*
Brandy laughs. Skoomer stares at her pointedly. "Ma'am, excuse me *what*? What are you *thinking?*"
Noodle's expression is one of purely innocent confusion.
"Noodle asking the real questions! Ah, Mango, you're up — let me grab some breakfast for you." Brandy turns to head back to cooking, occasionally letting out a chuckle. "A threesome…heh."
The two at the dining table turn to see Mango up from the basement with a blank expression.
"My disappointment is immeasurable and I am questioning my friendships," he states.
At this point, Noodle gives up on restraining herself and laughs whole-heartedly. "Bwa ha ha!"
Skoomer throws his hands up in the air, grumbling indecipherable noises.
Calming herself down, Noodle is still smiling widely as she teases Skoomer further. "I don't know, I just kinda assumed Skoomer got into a relationship to expose the other person — and then he said 'more than two', and I was like…" She squints suspiciously.
Skoomer crosses his arms defensively. "Sir, we didn't play undercover, it was elementary school."
"I don't know about that, Skoomer…"
"Even our counter-espionage didn't go that far!"
"Sus."
"Fine! *One* thing I did was uh…we only expelled one person in a pair, but the pair had to choose who got expelled." He smirks self-assuredly.
"Mate," Mango interjects, staring at Noodle, "'more than two'…are you cheating on me?"
Noodle looks at him sheepishly as she slowly slides down in her chair until only her eyes peek above the table. Mango's expression is one of pure devastation.
Skoomer chuckles. "Nah, but Noodle, wouldn't you agree that my policy is highly effective?" he says proudly. "The *speed* that couples just *disintegrated* amazed me at the time."
"Do you know what else is effective?" Mango directs a side-eye glance at Skoomer.
Skoomer tilts his head away and side-eyes him right back.
Mango smirks.
Skoomer dips his head.
Mango smirks harder.
Skoomer begins to sweat nervously.
To Noodle's dismay, any possible resolution to the tension between them is broken by Runo crashing down the stairs. "Yo guys, did you see the newspaper?" He shoves a finger at a tiny line on the newspaper clutched in his other hand. "Any action that damages ethic unity," he quotes. "WHAT THE FRESH FUCK!"

View File

@@ -0,0 +1,135 @@
---
title: "The Birds on Language and Tea"
date: 2021-07-10
tags:
- birds
- unstagnation
---
**Summary:** The Birds discover Asia and its many variants of leaf juice. Waffle Bird is innocent.
<!-- more -->
---
The living room is a mess to Noodle Bird's eyes. Papers are strewn across the rug and hardwood floor with Runo lying face down and limbs splayed. There are no words to describe what must have happened.
"What happened to Runo? Why is he talking about cum?"
Runo's giggles are muffled from the floor. "Cum," he slowly enunciates.
Brandy Bird sighs from his corner, reaching down from his spinny chair to give Runo's head a solid bonk. "Bonk — go to horny jail."
Noodle's gaze flits over to Skoomer Bird, who shifts further into his desk by his open laptop as if to disassociate himself from Runo. "Well, uh…maybe he is high," he offers.
Between fits of laughter, Runo defends himself. "I'm not horny!" he protests. "It's just a *funny* word. Especially since CANAMOO used it in their official solutions…as a short form for 'cumulative'."
Noodle nods in understanding. "Oh…I see. That's the *worst abbreviation ever!"* She lapses into giggles along with Runo who pounds a fist on the rug.
"I know, right? Waffle is just perpetually horny though, I dunno what's up with her."
Brandy snorts. "Brand new phrase right there."
At the same time, from the mention of her name, Waffle pokes her head around the corner from the neighbouring room. "Woah, wait — what. What?" she demands.
Runo hits a button on his phone, waving away the matter. "It's okay, nobody cares. Also, Brandy, I don't think that's new — that's a very old sentence and I've heard it at least, like, seven times."
*"Uwu cumsy wumsy!"* Waffle's tinny but clearly cheerful voice blares through Runo's phone speaker.
"Bruh — now I've been called 'perpetually horny' *and* 'universally *moist*'," Waffle sniffs. "I don't know what is *up* with you people!"
Runo pushes himself into a sitting position, shrugging smoothly. "If it happens multiple times, it's probably you."
"No!" Waffle vehemently denies. "I think they are just *bad* because I am innocent!"
"Nobody innocent would innocently say 'uwu cumsy wumsy'," Runo points out. "You can't *do* that."
Waffle turns away from them and huffs an annoyed huff. "Well, I am gonna blame it on the lack of outside time messing with my brain!"
Skoomer decides to offer his own opinion on the matter. "Consider decreasing your caffeine overdose," he suggests helpfully.
"Come on, it was *one* coffee I bought yesterday," Waffle insists, "and I'm still not done."
"She doesn't like coffee, she said." Runo also reminds Skoomer. "It's boba."
Skoomer squints suspiciously at Waffle, who throws her hands up and gestures wildly at his accusations. "I literally drank half of the coffee and couldn't continue! Also I was really tired! And jittery. And tired!"
Skoomer considers his past experiences with Waffle. "You are perpetually jittery," he notes.
"Goddammit!"
Runo laughs. "I mean, coffee tastes *good* cold."
There is a noise of unadulterated disgust from Brandy's corner. He slowly turns and stares into the depths of Runo's soul with an unsatisfactory expression. "How do you expel the landlord?"
Waffle sticks her tongue out. "Coffee tastes terrible," she generalises. "No amount of sugar can cover how terrible coffee is. And it doesn't even work!"
This grave insult to their dignities goes unnoticed by both Brandy and Runo as they stare each other down. "What?" Runo demands. "Cold coffee is literally a thing you can buy at most places. You're just *uncultured* — not my problem."
"Like Bappachino or something?" Brandy asks.
"Moondollars!" Waffle interjects with the name of a popular coffee chain.
"I don't know coffee," Runo puts out as a disclaimer, palms held outward. "Just that cold coffee is good."
"Bruh," says Brandy. "Well, I guess that's fine — but I thought you meant hot coffee that went cold." The tension slowly dissipates as the misunderstanding is resolved.
"I mean, that's fine as well," Runo amends.
Waffle's eyes sparkle as she gasps when she remembers something amazing. "$1 iced coffee at McBirb's!"
The group only notices at this time that Skoomer has been shaking with visible anger in his seat, his fingers frozen above his keyboard.
"Calm down, green tea enjoyer," Runo faces away from him but turns back soon after. "Okay, I'm sorry — green tea is good as well…sometimes."
Skoomer returns to his work, appeased.
"Green tea good!" Waffle chirps. Noodle returns and cleanly bonks her from the side. "Yes! Black tea also good!"
"Green tea good!" Noodle agrees and leaves again.
"Orange pekoe can die in a hole!"
Runo makes an indescribable face.
"Literally *so bad!"* Waffle continues. "Worse than Earl Grey! And that already *sucks!"*
Skoomer sighs as he sips his own cup of tea. "Xihu Longjing for the win, by the way," he declares smugly.
Runo looks at him blankly. "The fuck is that?"
Skoomer shakes his head at Runo's lack of knowledge of Skoomer customs, heaving out truly the most disappointed of sighs, then launches into what would be a multi-minute explanation of its origin, benefits, and superior flavour and attributes.
He doesn't even reach ten seconds when Runo decides that he'd rather listen to something more interesting. "LMAO, okay."
Waffle flops backward onto the couch, arms out in either direction. "This cup looks so *thick*…" She points at the bubble tea clipart plastered on the ceiling of the room. "I want a thick cup of bubble tea…" Her whisper embodies longing rivalling that of the attraction between the sun and the Earth.
"It's just short," Runo dismisses.
"I want to buy some bubble tea right now…" Tears leak out of the poor girl's eyes as her bubble hopes and dreams lay crushed, malnourished, and emptied.
Skoomer notices this and doubles down. "Short…" he agrees, "like Waffle."
Waffle instantly jumps up, hair flying as she uses the full extent of her height to refute Skoomer. "No! Wrong!"
Runo shakes his head in mock disapproval. "Skoomer," he chides, "you're the only tall one here." He scooches a little closer. "We out*number* you…"
Skoomer trains his eyes on Runo, slowly rolling his spinny chair back an inch for each inch Runo approaches. "Bread!" he calls. "Save me!"
Bread Bird crashes down the stairs and freezes when greeted by the sight of Runo and Waffle staring hungrily at Skoomer. "What the fuck is going on."
Runo ignores the newcomer. "We will take your headphones first!" he declares.
"Take all his headphones!" Waffle joins in, flanking Skoomer's evacuation path. "He can't stop us!"
"He probably has enough for all of us — so rich!"
"N-no," Skoomer nervously denies, flicking his eyes occasionally to Bread in a plea for help.
Bread shakes his head sadly and salutes. "Skoomer, I cannot defend you in this scenario. Your YN-2000YN4 is too good."
"Nooo!"
Granted implicit permission, Waffle cheers and tackles Skoomer clear off of his chair, scrabbling for the headphones around his head.
Bread looks on sadly, but with no regrets.

View File

@@ -0,0 +1,143 @@
---
title: "The Birds on Posterior Acoustics"
date: 2021-07-12
tags:
- birds
- unstagnation
---
**Summary:** The Birds are confronted with a dilemma that they must resolve. Curiosity of human flesh knows no bounds.
<!-- more -->
---
'Tis but a normal evening in the Bird household. Waffle Bird once again rants about the injustices of the education system by the dining table as Muffin Bird and Egg listen attentively. At the same time, Brandy Bird and Noodle Bird pretend to listen attentively while playing a racing game together on the TV.
Soup Bird doesn't bother pretending as he scribbles on papers for the upcoming school year.
"I'm so scared of accidentally unmuting in Frances's class," Waffle finishes, breathing hard as she concludes her latest rant. Muffin and Eggy nod along in complete agreement as Runo chuckles at his phone from the couch.
*"I'm so scared of accidentally unmuting in Frances'* ass." The surprise on Runo's face is completely fake as Waffle's mangled words pierce through even the racing game's music and Soup's focus on work.
There is silence except for the slowly quieting *vroom*s from the bots that overtake Brandy's and Noodle's cars.
Muffin is the first to speak up. "Runo," he says. "Kill yourself."
Soup snorts and is unable to hold in laughter until an urgent thought strikes him, a potential misunderstanding that must be resolved in his mind. "But…" he ponders, "how do you unmute…in someone's ass? How does that work?"
Attention shifts from Runo as now everyone focuses on Soup's incomprehensible question.
"Runo…" Brandy says slowly, "you can meme him."
Eggy thinks it over and comes to a conclusion. "How *doesn't* it work?" he asks quizzically but without elaboration.
"It doesn't make logical sense?" Soup's confusion is not shared by the other members in the household. "Like," he tries to explain, "the only way I can think of —"
What the only way Soup can think of will never be known as Eggy interrupts him. "Getting it *in* there might not make sense, but if it was already there, what's stopping you from unmuting?"
An understanding "ah" is followed by a nod from Soup as he follows along. "Ohh. So —"
"What the fuck." Runo's sentiment is completely agreed with by everyone else as Eggy and Soup continue to discuss the merits of an electronic device in the human body.
Soup continues, "— there means that *Waffle* is in *Frances'* ass, and then…she talks?"
As the mental image slowly dawns upon everyone in the room to varying degrees of disgust, Eggy's expression makes it clear he's suddenly changed his mind. "I advise you stop thinking about it."
Noodle clutches her head, shaking it frantically, her controller having dropped to the ground. Her eyes attempt to roll into the back of her head but fail. "Oh my god please delete this from my eyes — i can't — I just imagined it, now it's stuck…"
"I mean…good acoustics?" Waffle's expression is frozen in a gentle smile as she tries to wrap and simultaneously unwrap the idea around her head. "What even…"
"Can I censor Soup for the good of our sanity?" Brandy asks everyone.
"No." Eggy makes it seem like they're having a perfectly normal discussion.
"Yes," Brandy insists.
"What the fuck do you mean by 'acoustics'?" Muffin says accusingly after finally letting the conversation sink in, tempting further discussion.
"It's a small enclosed space," Eggy reminds him in a sensible tone, receiving an incredulous face in return.
"But who would be listening?" Morbid curiosity fills Noodle's voice as she dares to go further. Her question goes unanswered, however, as the others pile on to correct Eggy's acoustic sense.
"As an audiophile, I feel offended." The indignation of Muffin's declaration is unparalleled. He opens his mouth to continue, but —
"But it's like a tube?" Soup's innocent question leaves Muffin's mouth hanging open.
"True," Eggy concedes. "There might be an echo."
"Censor Soup, *right now*," Muffin demands, ripping off a strip of duct tape.
"It's the large intestine, right?" Soup carries on, oblivious to potential death threats behind him.
"Yeah."
The tubular description helps Waffle realise something. "Like, you know how practising your instrument in the washroom makes it, like, sound okay? Decently?"
"But, like," Soup starts, "I don't think sound is very reflective on flesh."
"I dunno," Waffle frowns, thinking it over. "I practised in the bathroom with my friend once. Practised an instrumental, I mean."
"Human tissue is soft and absorbs stuff really easily," Eggy adds helpfully.
The sheer urge to provide concrete facts stalls Muffin long enough to prevent him from performing violent actions on Soup for the time being. "It is in fact not," he confirms, then steps menacingly closer. "I call upon the pope to strike Soup with lightning."
"Please do," Waffle groans, face in palm once she realises she's meaningfully taking part in the discussion. She joins the others in being unable to turn away but desiring to be as far removed as possible.
"Also, there are other substances in the large intestine that absorb the sound," Soup realises.
Muffin glowers at him.
"…So the acoustics aren't good probably," Eggy concludes, nodding to himself.
Unable to help herself, Waffle reluctantly returns to the shining pillar that is Soup slowly reasoning in the moat of implications of their current topic. "Okay, so many not good acoustics."
"Yeah." Soup nods in satisfaction. "URK —" In a bout of desperation to stop him from ruining the others' evening, Muffin takes it upon himself to strangle Soup from behind.
"Soup," he says sadly, "please die in a hole."
"Maybe," Waffle agrees, looking on but not moving. "LOL!"
Using the underside of his chair as leverage, Soup in a moment of clarity forces his head forward and grips Muffin's arms to throw him up and over. Surprised, Muffin's grip tightens and Soup follows along as they spin twice together before collapsing into a heap on the floor.
Inadvertently releasing his neck, Muffin glares angrily but frozen in place as Soup pins him down with his knees and an arm, the other being used to rub his throat. "I don't know… It doesn't make much sense. Okay," he nods at Muffin, "I'm high. I should stop."
Muffin sighs in relief as both the topic of conversation ends and he's released from the Soup's restraints. "Did she ever say that she spoke in his ass?" he wonders.
Muffin's duct tape he dropped is picked up by Brandy. He turns it over in his hands, examining it. "Am I justified in censoring Soup if he keeps talking about this?"
"Yes."
"Soup's EE —" Muffin jokes, brushing himself off. "An acoustic study of sound fields in the ass of Mr. Frances."
"Get Frances to supervise," Brandy adds, unable to resist.
A shiver runs through Soup as he considers this. "…Gonna get *roasted*. Hm, actually, what do you think if I — Mmph!"
Any of Soup's further inquiries are locked in his mind as Brandy finally slaps the duct tape around his mouth to a standing ovation.
------
Extra:
"Shit." Soup Bird buries his head in his hands. "Agggh…"
"What is it?" Noodle Bird leans over to see Soup Bird's screen, then winces as she reads the text. "Ah, you got *him* as your mentor? RIP — What topic are you doing?" She turns her eyes to the subject and topic submitted for approval, and her eyes widen.
"Ah." Noodle bursts out laughing, falling backward into her chair.
Soup groans, rubbing his head in pain and defeat as his eyes rescan the introductory message from his Extended Essay mentor.
*Hi Soup,*
*I took a look at your topic and found it quite interesting. However, you should consider expanding the scope of your experiment to asses in general.*
*Speaking of people in front of your computer, you might want to recruit some others so that you have a wide range of data over multiple repeats. You also would have to provide sufficient controls such as diet and routine, ensuring that the substances in each of your research locations are clearly known, or risk losing all marks for not having a proper methodology in your experiment.*
*I've already approved and locked in your topic. Please let me know of your research question and we can further discuss your research and methodology at our first formal meeting tomorrow.*
*Mr. Frances*

View File

@@ -0,0 +1,109 @@
---
title: "The Birds on Waffle Supremacy"
date: 2021-11-01
tags:
- birds
- unstagnation
---
**Summary:** The Birds enter a debate at school over the supremacy of waffles over pancakes. Obviously, one of them is the better food, but it's up to them to prove it.
<!-- more -->
---
In the student-lined halls of Vaybiew Secondary School, a lone poster hangs on a bulletin board, its top-right corner limply drooping in front of it. Colours faded from hours of abuse, a singular golden arrow labelled "waffle supremacy" directs any offendees to room 233.
Let us head into the room to observe the intellectual debate occurring between the factions.
Room 233 is styled as a traditional court. Dimly lit, intricately designed mahogany furnishes the room, from the chairs to the desks to the court podiums that are illuminated by spotlights. A substantial audience listens to the four people debating, raptured by the engaging arguments flying across the room.
"The only reason Egg hates pancakes," Noodle Bird is declaring, pounding a fist on the podium before her, "is because there are eggs in pancakes. I'm gonna cook your entire family, you twat."
Egg of the waffle camp glares at her.
The spotlight suddenly switches to Bean Bird as she slams the objection buzzer. "There are eggs in waffles too!" A pause. "But waffles are better than pancakes," she adds.
Noodle recoils at this new evidence, staggering back as if she's been shot. She grips her podium tighter.
Emboldened by this turn of events, Bean goes on the offensive. "The cubes are a better vessel for syrup!" she fires. "You're just bland, Noodle."
Noodle's argument is in shambles and she needs a moment to collect her thoughts — a moment taken by the audience to lean more in favour of the waffle camp.
"No! Pancakes are better!" Noodle racks her brain for any argument while she quickly distracts the audience.
Bean smirks knowingly, sensing victory. "Imagine being so wrong."
Noodle sighs. It's time for their secret weapon — a shame to lose it so early on, though. She nods almost imperceptibly to a shadowy figure at the back of the courtroom.
Coloured confetti is shot from both sides behind Noodle while a large banner with the pancake emblem unfurls from the ceiling as she spreads her arms, beaming. "Pancakes!" she announces righteously.
Bean can only stare at the effective tactic that earns applause from the audience. "Noodle," she says sadly, shaking her head disappointedly.
Sensing that the waffle camp is losing control of the narrative, Egg jumps in desperately to provide reinforcement behind Bean. "Pancakes," he says loudly, quieting the applause, "are just slabs of *cooked dough*. Waffles are elegantly shaped for optimum tastiness."
In front of their emblem, the pancake faction regains their momentum. "No," Noodle retorts, "y'all are so cubed. Imagine not being round!"
"Bruh, what the hell." Runo as the moderator makes a disgusted face at the image of a waffle filled with syrup. "Are you supposed to put that much syrup?"
Noodle takes the opportunity, signaling again to her spy in the audience. "Pancakes have *curves!"* Jets of confetti again fly behind her at the last word. The audience cheers as celebratory music plays, and some of them are already holding up their vote for pancakes.
"Waffles also have curves!" Egg interjects, looking to steal the spotlight.
Runo frowns. "Wait, so you think they have sex appeal?" The cheering, music, and atmosphere in the room immediately falls apart, to be filled with awkward silence.
"Where'd you get that from?" Egg finally asks.
"Isn't that what 'curves' means?"
"No," Bean recovers, answering Runo's first question after a delay. "Waffles have more sex appeal because…cubes…you can…"
"Hol' up," someone in the audience says, to universal agreement.
The debate mood is completely stifled until Egg fiddles with the projector enough to beam an image of a glorious syrup-filled waffle topped with butter and cream onto the debate screen. "The beautiful waffle!" he declares, gesturing at the golden dish. The music is restored and the audience cheers — some of them even change their vote.
"It's super sexy — I'm getting hard already!" Waffle Bird pokes her head out from backstage, to general laughter. "Hi! Help!"
"That's so hot, I'm gonna cum," Bean swoons, starry-eyed, her hands clasped in admiration.
Noodle taps the microphone, creating feedback that reverberates around the room, restoring balance. Satisfied that she is the centre of attention, she begins attacking the golden food. "Can squares roll around?" She takes the microphone out of its holder and steps in front of the podium. "No, I don't think so. Circle supremacy!" Her fist pumped in the air at the last line, a whole tub of confetti pours out behind the curtains, which gets the audience up and excited again. The waffle camp frowns.
"Waffles roll better than pancakes do," Egg corrects Noodle. "They have thicker edges." To reinforce his point, he puts a second image on the screen — a stack of perfectly browned pancakes topped with a square of butter dripping with syrup, outlining their limpness and impractical rollability. "Boring pancake."
"Right?" Bean turns to the audience, nodding. The audience nods along.
"What do you mean — the pancake looks beautiful!" Noodle protests.
Waffle takes this time to poke her head out again from backstage, stepping firmly on the side of the waffle camp. She glances at the screen and shivers. "Ew… my pp just shriveled up." Noodle gapes at her as the audience laughs.
"Pancakes will flop over," Egg says. The debate has returned to normal and the two parties are once again at each other's throats in a normal fashion.
"Like my pp when I see one," Waffle adds.
"They double as wagon wheels!" Bean concludes. The pancake camp is once again in shambles. Noodle gestures at the moderator for their last secret weapon.
Runo from his moderator chair, quieting the audience. To the waffle camp's dismay, he strides dramatically to the pancake podium. There is a sharp gasp from someone in the crowd. "I personally think…" pause for dramatic effect, "*pancakes* are better because they have more *fluff*."
This abdication of responsibility has the waffle team on full offensive, their rage and passion for waffles on full display.
"Nah," Bean dismisses.
"But it feels *dry* in your mouth!" Waffle brushes him aside.
"Unless you consume them a millisecond after they're cooked," Bean agrees. "They flop down."
"Wha —" The pancake faction clearly expected a bigger reaction, and Runo scrambles to counter. "Wait, what do you mean, 'flop'? I have never heard of this."
"They compress," Bean says matter-of-factly.
"Wait, really?" It's almost as if Runo is a paid actor for the pancake camp by how surprised he is at this nonsense.
"Because the heat and steam hold them up."
"They *deflate,* yeah!" Waffle interjects.
"Right!" Bean nods along. Right on cue, the screen somehow falls off with the pancake still on it and limply folds into itself like a bathroom towel.
"I've heard enough." The judge at the other end hammers his gavel, and all debate stops. "I hereby declare — that the waffle camp has won this debate! This meeting is now adjourned."
"You're just a grid lover — that's all you'll ever be," Noodle grumbles as she steps off the darkened stage.

View File

@@ -0,0 +1,99 @@
---
title: "A is for Alcohol and IAs"
date: 2022-10-24
tags:
- birds
- unstagnation
---
**Summary:** Bean Bird and Noodle Bird enjoy a chat together on a quiet night slaving away before they leave in the summer. Bean wishes she had a stronger drink.
<!-- more -->
---
Quiet keys clack and pencils scribble as the Bird family finds itself working, scrambling to tie up loose ends in their final months together. Their upcoming move to Universe City weighs upon all of their minds. There is no room for frivolous discussion, no time for anything but the epitome of productivity.
"Guys, I hate my AIF. With a *passion*." Bean Bird sets down a half-filled mug and she glares all around, slamming her hands on the table. "AIF sucks *ass*. But, like, not good ass."
Noodle Bird blinks, her attention suddenly lifted from her 50k-word real person fic. "Oh."
Bean continues her tirade, invigorated. "Poopy crusty ass! Like, straight white male unwashed ass." Sighing, she lays her head down on her arm for only a second before she sits upright again, strangling thin air with her bare hands. "I'm fucking *losing my mind* *I hate this IA so much."*
"Why?" For the life of her, Noodle can't imagine what difficulties one could possibly have with the Internal Assessment. The soul-wrenching dread of the IA has avoided her completely.
"I never thought I had to listen to a British guy appealing to the crowds to work with the *USSR*." She peeks at the video between her fingers like it's out to doom her and her alone. "NOOOOOO!"
"Oh, god," Noodle says sadly. There is nothing she can do. Bean must walk her own path down this chosen path, or die trying.
"It's okay," Egg assures her, with the confidence of one who has yet to experience true suffering. "Get a level 5 and get it over with."
"I *can't!"* Bean bemoans. "I did so much research! There are *so many things* in my brain!" She grabs the mug and chugs furiously. "Why won't Wroscell let me get drunk and spew out all the data to him?"
"Bro," Noodle says, patting her, "I was literally procrastinating on researching my IA until, like, yesterday! And here I am!" — she gestures at her computer, now filled with lines upon lines of words no one could possibly know was a 55k-word real person fic.
Bean thumbs through a textbook, gazing at page after page of notes. "The sticky notes in my books are so colourful…" she says wistfully, her eyes lighting up, "…and so *toxic!"*
"Eat it! Maybe it'll taste sweet!" Noodle says, eyeing the textbook stuffed with solid, packed handwriting and the tantalising aroma of adhesive.
Bean leans in beside Noodle. "I'm gonna eat you," she whispers.
"I…don't think I'm very delicious." A chair subtly scooches away.
Returning to her mug, Bean cradles it once again and drinks deeply, leaning back against her chair. She sighs. "Rose-flavoured tea is disgusting."
"I agree."
"I have to pretend I like it because my mother bought it for me. And it's like, three dollars." Bean makes a face as she sips at her cup, staring longingly into it as if hoping it were something else.
Sensing the family crisis to be just about over, Egg chimes in, "Human meat tastes like chicken." He looks as if he's been dying to join the discussion the whole time.
"Wrong," Bean scoffs. "It tastes like pork."
Egg is firmly removed from the conversation from sheer embarrassment. "Yeah, that," he says, slinking away, never to be seen again.
A thought occurs to Bean — she taps her chin thoughtfully. "Human flesh is commonly called 'long pig'. And calamari is often pig anus."
"What."
"Yuh," Bean nods, "I have really weird taste in podcasts."
Noodle seriously considers the topic. "We *are* technically longer than pigs…most of us."
"Nood you're not," Bean frowns. "You're tiny!" She notices the glare sent her way and her expression softens pityingly. "I love you, Noodle, but you're *teeny tiny*."
Bean's sincerity moves Noodle's heart and she deflates. "I know… I love you too."
"I would drink all the rose tea if it meant you didn't have to…" Bean swoons, occasionally stopping to sip out of her mug. "It's okay, you'll grow."
Noodle looks up. She knows.
Evidently, Soup also wanted to jump in earlier but was dissuaded by Egg's shutdown. Now he takes his opportunity. "Any…cannibals in the chat?"
Soup is utterly ignored as Bean continues her musings with Noodle. "I think a lack of gravity in heaven elongates the joints." She laughs. "See what I did there?"
Soup laughs along.
Bean shakes her head. "I swear to god, the day before IAs are due, I'm just delirious." As if to illustrate her point, her face plants into her keyboard.
"…IA is due tomorrow?" Soup ventures.
Bean turns her head to face him, eyes bleary and hair falling in front of her face. "Do you have any idea how many times I stayed up until four in the morning to finish my chem IA?" She pushes herself up. "And it wasn't even the day before the IA was due either! It was like, three days in advance!" She grabs another mug and chugs the whole thing down. "I just stressed myself out and dragged Snac with me to explain hydrogen bonding sites in hydrolysed sugar — that's when I learned that you can physically break bonds with hands."
To demonstrate, Bean mimes a karate chop and smashes the wooden table. Various sheets of paper fly into the air while laptops fly to the ground.
Noodle stares. "Huh. That's cool."
Soup wordlessly picks up his cracked laptop and leaves to cry in a corner.
Bean doesn't notice. "I feel like half the things I say here today can be in an archive." She shrugs. "I dunno, I feel delirious."
Dusting off her screen, Noodle places her laptop on her lap and continues to work away at her 60k word real person fic."Nothing like learning good ol' hydrogen bonding to have a great bonding experience at 4 am."
Bean nods, swaying in her chair. "I got drunk once off peach soju and this was the same experience, except I didn't have a *history IA* to write," she murmurs.
"It's the stress plus adrenaline! But it's fun!"
Bean's eyes droop. "So. Much. Fun." With her last words, Bean Bird falls out of her chair and crashes on the ground, never to be woken again…
…until the next morning, when she realises that her IA is due tomorrow and she still has to write two body paragraphs and a conclusion.

View File

@@ -0,0 +1,97 @@
---
title: "High Crimes and Misdemeanours"
date: 2022-10-28
tags:
- birds
- unstagnation
---
**Summary:** An egg catches the Birds in a dastardly scheme. They must take their revenge by public humiliation. There will be no escape.
<!-- more -->
---
An egg stands up on the stage by the corner of the road, adjusting his tie. He's ready to campaign. It'll be the biggest advance in politics in the history of politics. It'll be the crowning achievement of his whole career. His magnum opus.
By the end of the day, he *will* have passed his referendum.
"If you think poutine is great, sign here!"
The masses turn as one. For one time-stopping moment, the egg knows he has their attention, and he intends to use it. They see nothing but an innocent man in a tie, gesturing at a sheet of paper on the board. Several neon arrows flash to make it extra clear.
Indeed, it does say, *Poutine appreciation board!*
Noodle Bird immediately brandishes her pen and signs the sheet.
"Yes! Poutine is so *good!"* Soup wastes no time, pushing through the crowd to have his voice heard.
"*Yes!*" Bread Bird practically throws himself in front of the paper to put his name down.
Other passersby stop to sign their support for the rich food. The egg smiles. All according to plan.
"Fantastic." Once he reaches seven signatures, it's over. He studies the list of names, smirking. They don't know.
Slowly, in front of the star-struck poutine fans, the egg peels off the tape on his sheet to reveal the actual title of the signature sheet:
*IB appreciation board!*
Instantly, the crowd roars in outrage. The sight of their dedication to the best food ever being turned to an advertisement for some snooty high school program drives them into a frenzy.
"BAN HIM!"
"THIS TREACHERY!"
The egg takes a step back, wiping sweat off of his forehead. Perhaps he should have secured himself before unveiling his scam political maneuver.
"Yo yo yo *what!* I got *scammed!"* Soup's anger is palpable and it rouses the crowd to further raise their pitchforks. "I can prove my innocence though!" He waves a picture of the paper from before the egg turned traitor. "I said poutine!"
Faced with unmistakably the truth, people begin to settle down, knowing that this egg will be made a fool of in the news. Unfortunately for him, he has yet to leave the frying pan yet. "But this egg," Soup turns to face him, eyes blazing. "He needs to stand on *trial."*
"We must stand him on trial!" Bread cheers.
"For high crimes!"
"Yes!" Soup roars. There is nothing but passion now, passion and hatred for the egg who dared to bait their beloved food.
"I wanna be the judge, jury, *and* executioner!" Noodle shouts.
"This is unprecedented," Soup declares, spreading his arms. Somehow, he's made it to the stage and has found a soapbox to stand on.
"We must make a witch trial for his crimes!" Bread joins him on the stage. "I propose we open a court!" Someone lugs a judge's desk in front of him as he and Soup put on their judicial gowns.
Noodle raises her hand. "I propose scrambled eggs!"
The egg pales.
"I second!" says Brandy.
"Yes," Soup nods, considering the motion. "That's good…but isn't boiling him better?"
The egg wrinkles his nose. "Ew. Boiled egg."
"Scramble him with ketchup…" Bread muses, stroking his fake white beard. Almost immediately, the enthusiasm becomes mixed.
"What the fuck?" Noodle questions Bread's age. "Who puts ketchup on scrambled eggs?"
Bread tries to get the trial back on track, but somehow persistent, no-wrong-answer controversies must take precedent.
The egg sees an opportunity. He squints knowingly, waiting for his moment to strike.
"Yes!" Brandy waves his arms harder. "Scrambled eggs and ketchup are good!"
"And pepper!" the egg helpfully adds. "IKEA breakfast." The egg's carefully crafted statement suddenly turns the people's attention away. Bread glares at him, about to strike the gavel for order, but to his shock, the small hammer suddenly disappears. The chattering among the people grows.
"Oh, okay." Noodle nods. "Should we add salt?"
Much to his horror, Bread finds himself thinking about the concept. "IKEA meatballs?" he gasps, realising the egg's plan. He shakes his head rapidly to rid himself of the tangent. Seeing the audience's anger begin to decline, Bread comes to a decision. "We hold another trial!" he declares, trying to speak over everyone else.
It's no use. The unified front has broken down, killed by infighting and squabbling over meaningless, irrelevant topics. Already his judge's chair and gown have been returned to their original owners.
The egg's injustice will go unpunished. He confidently strides away, knowing that there is no one who can stop him now. He holds power now that he's no longer the centre of attention.
Bread desperately reaches out, trying to rip the signature sheet out of the egg's hand, but someone from the crowd pulls him back. As he's questioned about drugs and integration, he looks helplessly to the egg who ruined them all.
Bread clenches his fist. He will not forget this. One day, he will have his revenge. His heart has hardened. From this day onward, no longer will he sign any such "poutine appreciation board". No longer will he trust others so unconditionally. The egg has done something that he will never forget, destroyed something inside Bread that will never return.
A single tear falls from the side of his cheek.

View File

@@ -0,0 +1,117 @@
---
title: "Soup's Egirl Discord Status"
date: 2022-10-25
tags:
- birds
- unstagnation
---
**Summary:** Mango reveals a hidden secret of Soup's past. The Birds listen in and offer their own opinions. Runo wants more.
<!-- more -->
---
Soup sits in the Bird residence living room, once again playing League. All is well in the world.
Mango taps his shoulder behind him. "Did you find our Clash team yet?"
"Monkeys," Runo sniffs haughtily at the game as he passes by.
Mango pulls out a seat beside Soup. "Ask your e-girls," he insists. "You should ask."
"*Fiiiine.* But I don't think they know you too well…"
"What's her name?" Mango taps his chin, racking his brain to remember the list. "Christina?"
"Most e-girls are too great." *For you* stays unsaid. Runo comes along again and pauses.
"Yeah, I don't care." Mango waves away the matter. "You should ask the Bronze I one, to lower our MMR." He spreads his hand, wiggling his eyebrows. "Genius?"
Runo is frozen at this newest revelation to his life.
"Okay," Soup chuckles, "let me go ask."
Finally over his shock, Runo manages to force his mouth open enough to express his disbelief. "There are *multiple?*"
"Uh…" Soup's eyes dart around shiftily.
"Oh," Mango snorts, "multiple is an *understatement*."
"Uh oh," Runo says, squinting at Soup.
All eyes are on him now. There is no opportunity to escape. All of the exits are conveniently blocked off, and already more people are entering the living room with their own work. Did they hear? Soup briefly considers the amount of space available underground to store several bodies.
In the end, he decides to take the politician approach: deflect, gaslight, and get outraged. "WHAT?"
"Like, you count them in dozens! Wrong unit, mate." Heedless of the inner turmoil going on inside Soup's head, Mango shakes his head as he continues to sabotage his friend.
Runo squints harder.
"Zero dozens!" Soup tries to shut it down, but it's too late now.
"A platoon!" Muffin suggests.
Mango's eyes light up. "A concentration camp! Hmm…"
Muffin eyes him knowingly. "HMMM…"
Soup makes a general noise of disagreement.
Noodle Bird joins the group. "I have a folder just for his e-girls," she taunts, waving a manila envelope.
"What the fuck?" Hungrily eying the envelope, Runo dashes after Noodle who bolts away, cackling as she clutches it to her chest. "I'm kidding! The folder is just for his girl friends!"
"WHAT?" Soup tries outrage again, sure it will work this time.
Evidently not.
"Yeah! Like, he has like, at least 20 —" Before Mango can go on, Soup decides that one casualty is acceptable for the cost and begins to strangle him. "— 15! 15 egirls." Soup releases his neck. "At least."Mango dodges around Soup's attempt to slice fruit, smirking. "And it's increasing every day! Poggers!"
"Exponentially?" Muffin leans in.
"No sir. *Factorial."* Mango winks.
"Oh, *shit*."
Runo returns only to hear that and immediately prostrate himself at Soup's feet, inadvertently restraining him from commiting a crime.
"True!" Mango joins him, the two kneeling and bowing to their hero.
Slightly flustered by the sudden change in attitude, Soup tones down his murder tendencies and drops his knife. "Um…what? This is false. I know, like, *negative* girls."
"That's the most bullshit statement I have *ever* heard." There's no keeping secrets when Mango's around.
"I thought you had a girlfriend," Runo frowns.
It pains Soup to admit it, but it's the only way to counteract the slander. "I'm…single right now."
Rain starts to pour outside like the pathetic fallacy it is. The fanatic light in Runo's eyes fades away. "Wait, really? Um…oof?"
"False!" Mango declares as the sun pokes through the clouds. "I'm *literally* your boyfriend!" He spreads his arms, beaming like the rainbow forming outside.
"True!" Soup returns the embrace.
Runo turns away, uninterested. "F," he says flatly. "Wait..."
Muffin joins this celebration of LGBT rights by playing a kazoo.
"No, he's not!" Runo points an accusing finger at the duo. "I didn't get the joke! Fuck!"
Mango sighs theatrically, caressing Soup's face with his hand, gazing longingly into his eyes. He's drowning in the wave of beauty of those pearlescent orbs. A sea of feeling, of affection, of love, all of it wells up inside him. An **emocean.**
"If you're gonna break up with me," he says sadly, "at least do it in private."
"Wait, he *is!*" Assuaged by the display of true love, Runo falls to his knees to repent. Mango bows.
Soup steps back and laughs. "Anyways, Mango, I don't really have many 'e-girls' to ask. I asked Christina, but I'm pretty sure her answer is going to be no."
"Ask *what*?" Runo interjects.
"I'm confused too." The humble Brandy wears a befuddled expression as he stands in the doorway, freshly arriving into the conversation of e-girls.
"I don't," Soup brushes the matter away. "Mango asked."
And that was the end of that conversation. Mango never brought up the topic again, Runo moved on to complaining about politics, and Soup never had to share his list of e-girl friends.
All was well.

View File

@@ -1,29 +0,0 @@
# HOI HOI
```js
export default defineNuxtConfig({
content: {
highlight: {
// Theme used in all color schemes.
theme: 'github-light'
// OR
theme: {
// Default theme (same as single string)
default: 'github-light',
// Theme used if `html.dark`
dark: 'github-dark',
// Theme used if `html.sepia`
sepia: 'monokai'
}
}
}
})
```
for some reason **it no** work
hey wtf
- list test
- list test
- list test

View File

@@ -2,6 +2,7 @@ export const navItems = [
{ href: "/#about", title: "About" }, { href: "/#about", title: "About" },
{ href: "/blog", title: "Blog" }, { href: "/blog", title: "Blog" },
{ href: "/stories", title: "Stories" }, { href: "/stories", title: "Stories" },
{ href: "https://portfolio.eggworld.me", title: "Portfolio", dominant: true },
]; ];
export default navItems; export default navItems;

13
data/siteRevisions.ts Normal file
View File

@@ -0,0 +1,13 @@
interface SiteRevision {
title: string;
url: string;
}
export const revisions: SiteRevision[] = [
{
title: "Nuxt 3 (2022)",
url: "https://eggworld.me",
},
{ title: "Eleventy (2021)", url: "https://2021.eggworld.me" },
{ title: "Vanilla (2019-2020)", url: "https://2020.eggworld.me" },
];

View File

@@ -4,12 +4,20 @@ export interface TagData {
} }
export const tagInfo: Record<string, TagData> = { export const tagInfo: Record<string, TagData> = {
barin: { name: "Barin" }, barin: {
name: "Barin",
description:
"Welcome to Barin — a world in constant conflict between productivity and procrastination.",
},
bsscc: { bsscc: {
name: "BSSCC", name: "BSSCC",
description: "Posts related to Bayview's Computer Club.", description: "Posts related to Bayview's Computer Club.",
}, },
ibia: { name: "Ibia" }, ibia: {
name: "Ibia",
description:
"A Kurious child struggles to fight the misinformation brought by the Six Goddesses of the Subjects.",
},
misc: { name: "Miscellaneous" }, misc: { name: "Miscellaneous" },
poetry: { poetry: {
name: "Poetry", name: "Poetry",
@@ -27,5 +35,28 @@ export const tagInfo: Record<string, TagData> = {
description: description:
"A collection of very short stories written to do something productive during JuneAugust 2020 and August 2021.", "A collection of very short stories written to do something productive during JuneAugust 2020 and August 2021.",
}, },
albatross: {
name: "The FOSS Albatross",
description:
'Articles about free and open source software. Also available on <a href="https://medium.com/the-foss-albatross">Medium</a>.',
},
birds: {
name: "Bird Family",
description:
"A large, loving family of birds who have found in each other a kindred soul for eternal suffering.",
},
uoft: {
name: "University of Teyvat",
description: "A <em>Genshin Impact</em> university AU.",
},
nanowrimo: {
name: "NaNoWriMo",
description:
"Story snippets written during National Novel Writing Month as part of a larger work.",
},
skyprojections: {
name: "Projections in the Sky",
description: "Dreams or reality — what is the difference?",
},
}; };
export default tagInfo; export default tagInfo;

View File

@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { revisions } from "@/data/siteRevisions";
useHead({ title: "Eggworld" }); useHead({ title: "Eggworld" });
</script> </script>
@@ -7,19 +8,39 @@ useHead({ title: "Eggworld" });
<Navbar /> <Navbar />
<slot /> <slot />
<footer <footer
class="flex flex-col items-center p-3 bg-gray-100 w-full text-sm dark:bg-gray-800" class="flex items-center justify-between p-3 bg-gray-100 w-full text-sm dark:bg-gray-800 flex-col md:flex-row gap-2"
> >
<p> 2022 Daniel Chen</p> <div class="flex items-center gap-2">
<p> <p>Revision:</p>
Licensed under the AGPL-3.0 on <!--
<a class="underline" href="https://github.com/potatoeggy/public"> the onchange is so bad - i'd rather it be done through vue
GitHub</a but nuxt is genuinely screwing me over here
ig r4 has to be in next.js
-->
<select
class="p-2 border rounded rounded-lg dark:bg-[#222]"
onchange="location = this.value"
> >
and <option v-for="(r, i) in revisions" :key="i" :value="r.url">
<a class="underline" href="https://git.eggworld.tk/eggy/public"> {{ r.title }}
Gitea </option>
</a> </select>
</p> </div>
<div class="flex flex-col items-center">
<p> 2022 Daniel Chen</p>
<p>
Licensed under the AGPL-3.0 on
<a class="underline" href="https://github.com/potatoeggy/public">
GitHub</a
>
and
<a class="underline" href="https://git.eggworld.me/eggy/public">
Gitea
</a>
</p>
</div>
<div class="w-36"></div>
</footer> </footer>
</div> </div>
<slot name="top-button" /> <slot name="top-button" />

View File

@@ -1 +0,0 @@
<template>error!</template>

View File

@@ -1,4 +1,4 @@
import { defineNuxtConfig } from "nuxt"; import { defineNuxtConfig } from "nuxt/config";
import svgLoader from "vite-svg-loader"; import svgLoader from "vite-svg-loader";
// https://v3.nuxtjs.org/api/configuration/nuxt.config // https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({ export default defineNuxtConfig({
@@ -6,7 +6,8 @@ export default defineNuxtConfig({
"@nuxt/content", "@nuxt/content",
"@nuxtjs/tailwindcss", "@nuxtjs/tailwindcss",
"@nuxtjs/color-mode", "@nuxtjs/color-mode",
"@nuxtjs/sitemap", "@funken-studio/sitemap-nuxt-3",
"nuxt-full-static",
], ],
nitro: { nitro: {
prerender: { prerender: {
@@ -16,8 +17,9 @@ export default defineNuxtConfig({
typescript: { typescript: {
shim: false, shim: false,
}, },
/* @ts-expect-error */
sitemap: { sitemap: {
hostname: process.env.BASE_URL || "https://eggworld.tk", hostname: process.env.BASE_URL || "https://eggworld.me",
}, },
tailwindcss: {}, tailwindcss: {},
colorMode: { colorMode: {
@@ -37,6 +39,14 @@ export default defineNuxtConfig({
href: "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css", href: "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css",
}, },
], ],
script: [
{
defer: true,
src: "/script.js",
hid: "stupidEmergencyScript",
type: "module",
},
],
}, },
content: { content: {
documentDriven: false, documentDriven: false,
@@ -80,7 +90,6 @@ export default defineNuxtConfig({
}, },
experimental: { experimental: {
reactivityTransform: true, reactivityTransform: true,
noScripts: true,
}, },
target: "static",
ssr: true,
}); });

View File

@@ -7,19 +7,20 @@
"preview": "nuxt preview" "preview": "nuxt preview"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/content": "npm:@nuxt/content-edge@latest", "@nuxt/content": "^2.3.0",
"@nuxtjs/color-mode": "^3.1.4", "@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/sitemap": "^2.4.0", "@funken-studio/sitemap-nuxt-3": "^4.0.4",
"@nuxtjs/tailwindcss": "^5.3.0", "@nuxtjs/tailwindcss": "^6.2.0",
"@tailwindcss/typography": "^0.5.2", "@tailwindcss/typography": "^0.5.2",
"nuxt": "npm:nuxt3@latest", "nuxt": "^3.0.0",
"nuxt-full-static": "^0.2.1",
"reading-time": "^2.0.0-1", "reading-time": "^2.0.0-1",
"rehype-katex": "^6.0.2", "rehype-katex": "^6.0.2",
"remark-math": "^5.1.1", "remark-math": "^5.1.1",
"sitemap": "^7.1.1", "sitemap": "^7.1.1",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"unist-util-visit": "^4.1.0", "unist-util-visit": "^4.1.0",
"vite-svg-loader": "^3.4.0" "vite-svg-loader": "^4.0.0"
}, },
"dependencies": { "dependencies": {
"dayjs": "^1.11.4" "dayjs": "^1.11.4"

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BlogParsedContent, StoryParsedContent } from "@/shared/types"; import type { BlogParsedContent, StoryParsedContent } from "@/shared/types";
import { calcReadingTime, getPrettyDate } from "@/shared/metadata"; import { calcReadingTime } from "@/shared/metadata";
type GeneralParsedContent = BlogParsedContent | StoryParsedContent; type GeneralParsedContent = BlogParsedContent | StoryParsedContent;
@@ -21,19 +21,19 @@ const descText =
type === "stories" type === "stories"
? `${calcReadingTime(doc).words.total} words` ? `${calcReadingTime(doc).words.total} words`
: `${calcReadingTime(doc).minutes} min read`; : `${calcReadingTime(doc).minutes} min read`;
useTitle(doc.title); useTitle(doc.title, doc.description);
const captionText = const captionText =
type === "stories" ? "Story" : type === "blog" ? "Blog post" : ""; type === "stories" ? "Story" : type === "blog" ? "Blog post" : "";
</script> </script>
<template> <template>
<div class="container prose dark:prose-invert w-full"> <main class="container prose dark:prose-invert w-full">
<p class="m-0 uppercase font-mono text-sm" v-if="captionText !== ''"> <p class="m-0 uppercase font-mono text-sm" v-if="captionText !== ''">
{{ captionText }} {{ captionText }}
</p> </p>
<h1 class="m-0">{{ doc.title }}</h1> <h1 class="m-0">{{ doc.title }}</h1>
<p class="my-2">{{ getPrettyDate(doc) }} · {{ descText }}</p> <p class="my-2"><Date :doc="doc" /> · {{ descText }}</p>
<div class="flex flex-wrap"> <div class="flex flex-wrap">
<Tag <Tag
v-for="(tag, index) in doc.tags" v-for="(tag, index) in doc.tags"
@@ -49,9 +49,13 @@ const captionText =
</template> </template>
<template #not-found> <template #not-found>
<h1>404 - Not Found</h1> <h1>404 - Not Found</h1>
<p>
Thanks for dropping by! But the page you're looking for can't be
found.
</p>
</template> </template>
</ContentRenderer> </ContentRenderer>
</div> </main>
</template> </template>
<style scoped> <style scoped>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { BlogParsedContent } from "@/shared/types"; import type { BlogParsedContent } from "@/shared/types";
useTitle("Blog"); useTitle("Blog", "Ramblings and ideas");
definePageMeta({ layout: "withtop" }); definePageMeta({ layout: "withtop" });
// TODO: paginate stories // TODO: paginate stories
@@ -9,6 +9,14 @@ const docs = await queryContent<BlogParsedContent>("/blog")
.sort({ date: -1 }) .sort({ date: -1 })
.where({ _draft: false }) .where({ _draft: false })
.find(); .find();
const tags = new Set(
docs
.map((p) => p.tags)
.flat()
.filter((p) => !p.includes(" "))
.sort()
);
</script> </script>
<template> <template>
@@ -16,6 +24,16 @@ const docs = await queryContent<BlogParsedContent>("/blog")
class="flex flex-col grow prose dark:prose-invert max-w-3xl gap-6 transition" class="flex flex-col grow prose dark:prose-invert max-w-3xl gap-6 transition"
> >
<h1 class="mb-0">Blog</h1> <h1 class="mb-0">Blog</h1>
<div class="m-0">
Filter:
<Tag
:dest="`/tags/blog/${tag}`"
v-for="(tag, index) in tags"
:key="index"
>
{{ tag }}
</Tag>
</div>
<PostPreviewCard <PostPreviewCard
v-for="(post, index) in docs" v-for="(post, index) in docs"
:key="index" :key="index"

View File

@@ -3,7 +3,7 @@ import Services from "@/components/index/services.vue";
import About from "@/components/index/about.vue"; import About from "@/components/index/about.vue";
definePageMeta({ layout: "withtop" }); definePageMeta({ layout: "withtop" });
useTitle("Home"); useTitle("Home", "Personal website!");
</script> </script>
<template> <template>
@@ -17,6 +17,7 @@ useTitle("Home");
<StoryStatBox /> <StoryStatBox />
<CommitStatBox /> <CommitStatBox />
</div> </div>
<Services /> <Services />
<About /> <About />
</main> </main>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { StoryParsedContent } from "@/shared/types"; import type { StoryParsedContent } from "@/shared/types";
useTitle("Stories"); useTitle("Stories", "Fantasies and worlds");
definePageMeta({ layout: "withtop" }); definePageMeta({ layout: "withtop" });
// TODO: paginate stories // TODO: paginate stories
@@ -9,6 +9,14 @@ const docs = await queryContent<StoryParsedContent>("/stories")
.sort({ date: -1 }) .sort({ date: -1 })
.where({ _draft: false }) .where({ _draft: false })
.find(); .find();
const tags = new Set(
docs
.map((p) => p.tags)
.flat()
.filter((p) => !p.includes(" ")) // do not include AO3-style tags
.sort()
);
</script> </script>
<template> <template>
@@ -16,6 +24,16 @@ const docs = await queryContent<StoryParsedContent>("/stories")
class="flex flex-col grow prose dark:prose-invert max-w-3xl gap-6 transition" class="flex flex-col grow prose dark:prose-invert max-w-3xl gap-6 transition"
> >
<h1 class="mb-0">Stories</h1> <h1 class="mb-0">Stories</h1>
<div class="m-0">
Filter:
<Tag
:dest="`/tags/stories/${tag}`"
v-for="(tag, index) in tags"
:key="index"
>
{{ tag }}
</Tag>
</div>
<PostPreviewCard <PostPreviewCard
v-for="(story, index) in docs" v-for="(story, index) in docs"
:key="index" :key="index"

View File

@@ -14,6 +14,9 @@ const docs = await queryContent<BlogParsedContent>("/blog")
.sort({ date: -1 }) .sort({ date: -1 })
.where({ _draft: false, tags: { $contains: tag } }) .where({ _draft: false, tags: { $contains: tag } })
.find(); .find();
const title = details.name ?? `"${tag}"`;
useTitle(title, details.description);
</script> </script>
<template> <template>
@@ -21,7 +24,7 @@ const docs = await queryContent<BlogParsedContent>("/blog")
class="prose dark:prose-invert max-w-3xl flex flex-col grow gap-6 transition" class="prose dark:prose-invert max-w-3xl flex flex-col grow gap-6 transition"
> >
<div> <div>
<h1 class="mb-0">{{ details.name ?? `"${tag}"` }} Posts</h1> <h1 class="mb-0">{{ title }} Posts</h1>
<p <p
v-if="details.description" v-if="details.description"
v-html="details.description" v-html="details.description"

View File

@@ -14,6 +14,9 @@ const docs = await queryContent<StoryParsedContent>("/stories")
.sort({ date: -1 }) .sort({ date: -1 })
.where({ _draft: false, tags: { $contains: tag } }) .where({ _draft: false, tags: { $contains: tag } })
.find(); .find();
const title = details.name ?? `"${tag}"`;
useTitle(title, details.description);
</script> </script>
<template> <template>
@@ -21,7 +24,7 @@ const docs = await queryContent<StoryParsedContent>("/stories")
class="prose dark:prose-invert max-w-3xl flex flex-col grow gap-6 transition" class="prose dark:prose-invert max-w-3xl flex flex-col grow gap-6 transition"
> >
<div> <div>
<h1 class="mb-0">{{ details.name ?? `"${tag}"` }} Stories</h1> <h1 class="mb-0">{{ title }} Stories</h1>
<p <p
v-if="details.description" v-if="details.description"
v-html="details.description" v-html="details.description"

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.172 11l-5.364-5.364 1.414-1.414L20 12l-7.778 7.778-1.414-1.414L16.172 13H4v-2z"/></svg>

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Some files were not shown because too many files have changed in this diff Show More