Compare commits
153 Commits
js
...
9e4c3122f6
Author | SHA1 | Date | |
---|---|---|---|
|
9e4c3122f6 | ||
|
bdecaf8c0b | ||
|
d5bd3e3be2 | ||
|
62e1d7ef22 | ||
|
e05b8ccd81 | ||
|
189964c508 | ||
|
f789e792a4 | ||
|
00100b089f | ||
|
47514d48a3 | ||
|
194c5343c6 | ||
|
0f14ad569f | ||
|
68fa501003 | ||
|
e7acf4bd96 | ||
|
3719a20d00 | ||
|
37d09f7a49 | ||
|
080525f657 | ||
|
5b71496c9d | ||
|
0147fbbf23 | ||
|
2ad9e41c8b | ||
|
69874b977c | ||
|
4ff7bbc2f7 | ||
|
21c5d573a2 | ||
|
c6394fc87d | ||
|
8b38b7c674 | ||
|
3bc334f0f3 | ||
|
1df49b0b84 | ||
|
a4c9d71cb1 | ||
|
a0eb2d3220 | ||
|
fc690345cc | ||
|
a5f8b4ca30 | ||
|
4339520f89 | ||
|
3b627a16d2 | ||
|
be45462f4f | ||
|
adbf374010 | ||
|
1c1cb3bf8a | ||
|
74f7bc1002 | ||
|
4502c819c3 | ||
|
314b0efe3a | ||
|
ec9609b559 | ||
|
528e09a26f | ||
|
006c1494ca | ||
|
4b0950dcd0 | ||
|
89d845eebb | ||
|
016fbb559f | ||
|
d1f674ee88 | ||
|
8948fed9ba | ||
|
b96e7fed67 | ||
|
9e30863015 | ||
|
87cf20ed48 | ||
|
df4f7f8630 | ||
|
5976b6079a | ||
|
304c9d6f36 | ||
|
9d55a16040 | ||
|
7a78a89393 | ||
|
bd3f8f5d02 | ||
|
a6d376db80 | ||
|
6086680642 | ||
|
cc18627f6c | ||
|
901405087d | ||
|
a0ef11330a | ||
|
7e85b78c45 | ||
|
e9497b3cdc | ||
|
0b6b798db4 | ||
|
29cb8010c3 | ||
|
3f00ab309f | ||
|
321fbb46d5 | ||
|
07b480b4bd | ||
|
988b078fe2 | ||
|
b1a24cef2f | ||
|
f011718ec6 | ||
|
b320212ca1 | ||
|
f75a8e4976 | ||
|
98941ed5f7 | ||
|
655daea2b3 | ||
|
38f6b747d3 | ||
|
d4c1a3b515 | ||
|
a0920dfe0f | ||
|
577e00a870 | ||
|
a7f38e77ae | ||
|
fb03714c2f | ||
|
ba49ac0a7d | ||
|
c930fd70ec | ||
|
59ee896153 | ||
|
c019a0fa6d | ||
|
806bb9b760 | ||
|
d7f0cfe8f7 | ||
19fd660df7 | |||
2789dc375f | |||
0eef57a376 | |||
17f66b52d9 | |||
24c2fa6a6f | |||
751033960b | |||
6373789413 | |||
a5142fb00b | |||
156be1cfd4 | |||
99bb0e9db4 | |||
76f8186385 | |||
a46bc70591 | |||
d7934568bd | |||
c7cd1fef32 | |||
631416984c | |||
49b772918b | |||
113680fcec | |||
d09a4ccbb6 | |||
689e951e36 | |||
702445d090 | |||
36b3ac58d1 | |||
4aed55206a | |||
d55899643e | |||
d047fda51e | |||
7e750d3290 | |||
b3c365fdd0 | |||
8b7f3460d7 | |||
c4f956af60 | |||
1cf48e6c22 | |||
6311f64921 | |||
60895ee176 | |||
641c5461b9 | |||
78cf87b84c | |||
d252fa91b3 | |||
66c4b32049 | |||
aedc42a9b3 | |||
dfd90ba2de | |||
1c6eb2d6fa | |||
e79f634d22 | |||
8f65123f32 | |||
a7efd8b1f6 | |||
16eab9d7d8 | |||
8474f4ff81 | |||
b5184838cf | |||
1e98ce27a9 | |||
df5085c41d | |||
7925c5b4e7 | |||
a9a555d20a | |||
98a2cee26a | |||
b1ab909c5b | |||
0473de4873 | |||
cc3e33cd2c | |||
1fb3e222ee | |||
e7ef4821b9 | |||
ed0eeef2ab | |||
7ec729ba66 | |||
74cad85292 | |||
00a5c8c4ca | |||
694a0b2691 | |||
260b1f7f30 | |||
0b0cf23796 | |||
762a2519c1 | |||
aedf88d446 | |||
2e4e4727d1 | |||
6f8f5dad7d | |||
04ab476d9b | |||
b59e77f432 |
38
README.md
@@ -1,44 +1,36 @@
|
|||||||
# Nuxt 3 Minimal Starter
|
# Oeufs?
|
||||||
|
|
||||||
**WARN: Volar 0.40.0 breaks everything and I don't know why — stick with Volar 0.39.5.**
|
Après le HTML manuscrit et le générateur de site statique — c'est Nuxt!
|
||||||
|
|
||||||
Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
|
Instructions post-compilation (pendant Nuxt n'a pas le prérendu)
|
||||||
|
|
||||||
## Setup
|
- Compilez `/script.ts` à `/script.js` (`tsc script.ts -m esnext -t esnext --moduleResolution node`)
|
||||||
|
|
||||||
Make sure to install the dependencies:
|
Lisez la [documentation de Nuxt](https://v3.nuxtjs.org) pour en savoir plus.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Assurez-vous d'installer les dépendances:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# yarn
|
# yarn
|
||||||
yarn install
|
yarn install
|
||||||
|
|
||||||
# npm
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm install --shamefully-hoist
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development Server
|
## Serveur de développement
|
||||||
|
|
||||||
Start the development server on http://localhost:3000
|
Démarrez le serveur de développement sur http://localhost:3000
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Production
|
## Production
|
||||||
|
|
||||||
Build the application for production:
|
Prévisualisez la production sur votre système local:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run build
|
yarn preview
|
||||||
```
|
```
|
||||||
|
|
||||||
Locally preview production build:
|
Lisez la [documentation de déploiement](https://v3.nuxtjs.org/guide/deploy/presets) pour en savoir plus.
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.
|
|
||||||
|
26
app.vue
@@ -3,29 +3,3 @@
|
|||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* for that cool wave dark mode effect */
|
|
||||||
z-index: 1;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
div#__nuxt {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--text-color: #243746;
|
|
||||||
--bg: #f1e7d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--text-color: #ebf4f1;
|
|
||||||
--bg: #091a28;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
54
assets/css/base.scss
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
@import url("https://fonts.googleapis.com/css2?family=Bitter:wght@300;400;600;700;800;900&family=Domine:wght@400..700&&display=swap");
|
||||||
|
|
||||||
|
@mixin headings {
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
// for that cool wave dark mode effect
|
||||||
|
z-index: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
div#__nuxt {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--text-color: #243746;
|
||||||
|
--bg: #f1e7d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--text-color: #ebf4f1;
|
||||||
|
--bg: #091a28;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-bitter {
|
||||||
|
font-family: Bitter, ui-sans-serif, system-ui, -apple-system,
|
||||||
|
BlinkMacSystemFont, "Segoe UI", Roboto, "Open Sans", "Helvetica Neue",
|
||||||
|
sans-serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-article {
|
||||||
|
font-family: "Domine", serif;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
26
assets/css/main.scss
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@import "base.scss";
|
||||||
|
|
||||||
|
.prose article {
|
||||||
|
@include headings {
|
||||||
|
& > a:hover,
|
||||||
|
& > a:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-skip-ink: all;
|
||||||
|
@apply text-blue-700 dark:text-blue-400;
|
||||||
|
&::before {
|
||||||
|
content: "#";
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.5;
|
||||||
|
left: -2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
@apply hover:text-blue-700 dark:hover:text-blue-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
@apply text-article;
|
||||||
|
}
|
||||||
|
}
|
1
assets/images/star.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="0" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>
|
After Width: | Height: | Size: 339 B |
@@ -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,11 +8,10 @@ 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>
|
||||||
<div class="prose dark:prose-invert flex">
|
<div class="prose dark:prose-invert flex onhover">
|
||||||
<HomeStatBox
|
<HomeStatBox
|
||||||
:href="latest._path"
|
:href="latest._path"
|
||||||
color="lightblue"
|
color="lightblue"
|
||||||
@@ -21,16 +20,15 @@ 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
|
||||||
v-for="(tag, index) in latest.tags"
|
v-for="(tag, index) in latest.tags"
|
||||||
:key="index"
|
:key="index"
|
||||||
:dest="`/tags/blog/${tag}`"
|
:dest="`/tags/blog/${tag}`"
|
||||||
>
|
:name="tag"
|
||||||
{{ tag }}
|
/>
|
||||||
</Tag>
|
|
||||||
</div>
|
</div>
|
||||||
<ContentRenderer
|
<ContentRenderer
|
||||||
tag="article"
|
tag="article"
|
||||||
@@ -47,8 +45,12 @@ const prettyDate = getPrettyDate(latest);
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="scss">
|
||||||
h2 {
|
h2 {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.onhover:hover h2 {
|
||||||
|
@apply text-blue-700 dark:text-blue-400;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,44 +1,31 @@
|
|||||||
<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
|
||||||
<div :class="['toggle', isToggled ? 'enabled' : 'disabled']">
|
for="dark-toggle"
|
||||||
|
class="toggle-wrapper"
|
||||||
|
aria-label="Dark mode indicator label"
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
aria-label="Toggle dark mode"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +74,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)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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,18 @@ 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"
|
||||||
|
alt="Latest GitHub commit"
|
||||||
|
/>
|
||||||
<!--
|
<!--
|
||||||
<div>
|
<div>
|
||||||
<h2>{{ title }}</h2>
|
<h2>{{ title }}</h2>
|
||||||
|
13
components/Date.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { getPrettyDate, getUtcDate } from "~~/shared/metadata";
|
||||||
|
import type { AnyParsedContent } from "~~/shared/types";
|
||||||
|
|
||||||
|
const props = defineProps<{ doc: AnyParsedContent }>();
|
||||||
|
|
||||||
|
const prettyDate = getPrettyDate(props.doc);
|
||||||
|
const utcDate = getUtcDate(props.doc);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<time pubdate :datetime="utcDate">{{ prettyDate }}</time>
|
||||||
|
</template>
|
@@ -11,8 +11,17 @@ const getSvgIcon = async (name: string) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="hamburger">
|
<div class="hamburger">
|
||||||
<input class="checkbox" type="checkbox" id="checkbox" />
|
<input
|
||||||
<label class="checkbox-label" for="checkbox">
|
class="checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
id="checkbox"
|
||||||
|
aria-label="Hamburger menu toggle"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
class="checkbox-label"
|
||||||
|
for="checkbox"
|
||||||
|
aria-label="Hamburger menu indicator label"
|
||||||
|
>
|
||||||
<svg class="ham ham-rotate" viewBox="0 0 100 100" width="60">
|
<svg class="ham ham-rotate" viewBox="0 0 100 100" width="60">
|
||||||
<path
|
<path
|
||||||
class="line top"
|
class="line top"
|
||||||
@@ -25,7 +34,7 @@ const getSvgIcon = async (name: string) => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<div class="drawer prose dark:prose-invert">
|
<ul 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">
|
||||||
<!-- 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 -->
|
||||||
@@ -34,12 +43,13 @@ const getSvgIcon = async (name: string) => {
|
|||||||
:src="`/nav/${item.title.toLowerCase()}.svg`"
|
:src="`/nav/${item.title.toLowerCase()}.svg`"
|
||||||
class="m-0"
|
class="m-0"
|
||||||
preload="auto"
|
preload="auto"
|
||||||
|
:alt="`${item.title} logo`"
|
||||||
/>
|
/>
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
<hr class="m-0 m-2" v-if="index !== navItems.length - 1" />
|
<hr class="m-2" v-if="index !== navItems.length - 1" />
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
101
components/HeaderLoop.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{ strings: string[]; class?: string }>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<h1 :class="[props.class, 'text-loop relative text-center w-full h-16']">
|
||||||
|
<span class="text absolute w-full" v-for="s in props.strings" :key="s">
|
||||||
|
{{ s }}
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "sass:math";
|
||||||
|
@mixin text-loop($els) {
|
||||||
|
.text-loop {
|
||||||
|
overflow: hidden;
|
||||||
|
$duration: 3s;
|
||||||
|
|
||||||
|
@if $els > 1 {
|
||||||
|
& > span {
|
||||||
|
display: block;
|
||||||
|
opacity: 0;
|
||||||
|
@for $i from 1 through $els {
|
||||||
|
&:nth-child(#{$i}) {
|
||||||
|
animation: move-test-#{$i} $duration * $els infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@for $i from 1 through $els {
|
||||||
|
@keyframes move-test-#{$i} {
|
||||||
|
$interval: calc(100% / $els);
|
||||||
|
$upper_bound: $interval * $i;
|
||||||
|
$lower_bound: $interval * ($i - 1);
|
||||||
|
|
||||||
|
// we try to make the previous exit and the next enter
|
||||||
|
// at the same time, also taking care of negatives
|
||||||
|
|
||||||
|
// for i = 1, this is negative, so start the animation at the end of the cycle
|
||||||
|
@if $i > 1 {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$lower_bound - $interval * 0.05} {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$lower_bound} {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$lower_bound + $interval * 0.95} {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$upper_bound} {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@if $i == 1 {
|
||||||
|
// reset el 1
|
||||||
|
#{100% - $interval * 0.05} {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0%);
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For one element, we have the following pattern. To expand it to 2+
|
||||||
|
* els, we divide 100% by the number of els and turn on the animation
|
||||||
|
* only at the correct time.
|
||||||
|
* -5%: invis
|
||||||
|
* 0%: vis
|
||||||
|
* 95%: vis
|
||||||
|
* 100%: invis
|
||||||
|
*/
|
||||||
|
|
||||||
|
@include text-loop(3);
|
||||||
|
</style>
|
@@ -1,28 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Color, ViewportLength } from "csstype";
|
import type { Color, ViewportLength } from "csstype";
|
||||||
|
|
||||||
// fix ReferenceError: _unref is not defined
|
const props = withDefaults(
|
||||||
// https://github.com/nuxt/framework/issues/5546
|
defineProps<{
|
||||||
import { unref as _unref } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
href,
|
|
||||||
color = "pink",
|
|
||||||
darkcolor = "#c88994",
|
|
||||||
title,
|
|
||||||
clearstyles = false,
|
|
||||||
forceheight,
|
|
||||||
} = defineProps<{
|
|
||||||
href?: string;
|
href?: string;
|
||||||
|
id?: string;
|
||||||
color?: Color;
|
color?: Color;
|
||||||
darkcolor?: Color;
|
darkcolor?: Color;
|
||||||
title?: string;
|
title?: string;
|
||||||
clearstyles?: boolean;
|
clearstyles?: boolean;
|
||||||
forceheight?: ViewportLength<"rem">;
|
forceheight?: ViewportLength<"rem">;
|
||||||
}>();
|
}>(),
|
||||||
|
{ color: "pink", darkcolor: "#c88994", clearstyles: false }
|
||||||
|
);
|
||||||
|
|
||||||
const padding = clearstyles ? "0" : "1rem";
|
const padding = props.clearstyles ? "0" : "1rem";
|
||||||
const height = forceheight ?? "100%";
|
const height = props.forceheight ?? "100%";
|
||||||
|
|
||||||
// v-bind DOES NOT WORK on initial render
|
// v-bind DOES NOT WORK on initial render
|
||||||
// so unfortunately we have to use the old way
|
// so unfortunately we have to use the old way
|
||||||
@@ -30,13 +23,17 @@ const height = forceheight ?? "100%";
|
|||||||
const cssVars = {
|
const cssVars = {
|
||||||
"--padding": padding,
|
"--padding": padding,
|
||||||
"--height": height,
|
"--height": height,
|
||||||
"--color": color,
|
"--color": props.color,
|
||||||
"--darkcolor": darkcolor,
|
"--darkcolor": props.darkcolor,
|
||||||
};
|
};
|
||||||
</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">
|
||||||
|
@@ -8,10 +8,13 @@ const props = defineProps<{ activeItem?: string }>();
|
|||||||
<template>
|
<template>
|
||||||
<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="/">Oeufs?</a></li>
|
||||||
<li v-for="(item, index) in navItems" :key="index">
|
<li v-for="(item, index) in navItems" :key="index">
|
||||||
<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`"
|
||||||
|
:alt="`${item.title} logo`"
|
||||||
|
/>
|
||||||
{{ item.title }}</a
|
{{ item.title }}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -1,23 +1,33 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { StoryParsedContent, BlogParsedContent } from "@/shared/types";
|
import type { AnyParsedContent } from "@/shared/types";
|
||||||
import { calcReadingTime, getPrettyDate } from "@/shared/metadata";
|
import { calcReadingTime } from "@/shared/metadata";
|
||||||
|
import { SpecialTags } from "@/data/specialTags";
|
||||||
|
import IconStar from "@/assets/images/star.svg?component";
|
||||||
|
|
||||||
const { post, type, highlighttags } = defineProps<{
|
const props = defineProps<{
|
||||||
post: StoryParsedContent | BlogParsedContent;
|
post: AnyParsedContent;
|
||||||
type: "stories" | "blog";
|
type: "stories" | "blog";
|
||||||
highlighttags?: string[];
|
highlighttags?: string[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const readingTime = calcReadingTime(post);
|
const readingTime = calcReadingTime(props.post);
|
||||||
const descText =
|
const descText =
|
||||||
type === "stories"
|
props.type === "stories"
|
||||||
? `${readingTime.words.total} words`
|
? `${readingTime.words.total} words`
|
||||||
: `${readingTime.minutes} min read`;
|
: `${readingTime.minutes} min read`;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="story-card p-4">
|
<div
|
||||||
<h3 class="m-0">
|
class="break-words max-w-full rounded-lg p-4 shadow-md border border-2 border-gray-300 dark:border-gray-600"
|
||||||
|
>
|
||||||
|
<h3 class="m-0 flex items-center gap-1.5">
|
||||||
|
<a
|
||||||
|
:href="`/tags/${props.type}/featured`"
|
||||||
|
v-if="post.tags.includes('featured')"
|
||||||
|
>
|
||||||
|
<IconStar class="fill-yellow-500 outline-none" />
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
:href="post._path"
|
:href="post._path"
|
||||||
class="no-underline text-left text-2xl sm:text-2xl font-bold hover:text-blue-700 dark:hover:text-blue-400 leading-tight transition"
|
class="no-underline text-left text-2xl sm:text-2xl font-bold hover:text-blue-700 dark:hover:text-blue-400 leading-tight transition"
|
||||||
@@ -25,21 +35,20 @@ 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">
|
||||||
|
<template v-for="(tag, index) in post.tags" :key="index">
|
||||||
<Tag
|
<Tag
|
||||||
:dest="`/tags/${type}/${tag}`"
|
:dest="`/tags/${type}/${tag}`"
|
||||||
v-for="(tag, index) in post.tags"
|
:name="tag"
|
||||||
:key="index"
|
|
||||||
:highlight="highlighttags?.includes(tag)"
|
:highlight="highlighttags?.includes(tag)"
|
||||||
>
|
v-if="!SpecialTags.includes(tag)"
|
||||||
{{ tag }}
|
/>
|
||||||
</Tag>
|
</template>
|
||||||
</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>-->
|
|
||||||
<div class="text-right" v-if="!post.nopreview">
|
<div class="text-right" v-if="!post.nopreview">
|
||||||
<a
|
<a
|
||||||
:href="post._path"
|
:href="post._path"
|
||||||
@@ -50,12 +59,3 @@ const descText =
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.story-card {
|
|
||||||
border: 0.1rem solid gray;
|
|
||||||
max-width: 100%;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@@ -1,11 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Project } from "@/data/projects";
|
import type { Project } from "@/data/projects";
|
||||||
const { project, reverse = false } = defineProps<{
|
import { unref as _unref } from "vue";
|
||||||
|
const props = defineProps<{
|
||||||
project: Project;
|
project: Project;
|
||||||
reverse?: boolean;
|
reverse?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const imgUrl = `url(/images/projects/${project.img ?? "unknown.png"})`;
|
const imgUrl = props.project.img
|
||||||
|
? `url(/images/projects/${props.project.img})`
|
||||||
|
: "none";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -14,13 +17,14 @@ const imgUrl = `url(/images/projects/${project.img ?? "unknown.png"})`;
|
|||||||
<div class="card-text h-full px-4 py-2">
|
<div class="card-text h-full px-4 py-2">
|
||||||
<div class="h-full flex flex-col justify-between">
|
<div class="h-full flex flex-col justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="m-0">{{ project.name }}</h3>
|
<h3 class="m-0 font-bold font-sans">{{ project.name }}</h3>
|
||||||
<div class="flex gap-1 items-center flex-nowrap">
|
<div class="flex gap-1 items-center flex-nowrap">
|
||||||
<img
|
<img
|
||||||
class="h-5 w-5 m-0"
|
class="h-5 w-5 m-0"
|
||||||
:src="`/images/langs/${lang}.svg`"
|
:src="`/images/langs/${lang}.svg`"
|
||||||
v-for="(lang, index) in project.langs"
|
v-for="(lang, index) in project.langs"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
:alt="`${lang} logo`"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="text-xs text-gray-500 dark:text-gray-300 whitespace-nowrap"
|
class="text-xs text-gray-500 dark:text-gray-300 whitespace-nowrap"
|
||||||
@@ -42,7 +46,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>
|
||||||
@@ -53,6 +57,10 @@ const imgUrl = `url(/images/projects/${project.img ?? "unknown.png"})`;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-anchor:hover h3 {
|
||||||
|
@apply text-blue-700 dark:text-blue-400;
|
||||||
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
border: 0.2rem solid pink;
|
border: 0.2rem solid pink;
|
||||||
background: white;
|
background: white;
|
||||||
@@ -87,7 +95,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;
|
||||||
@@ -101,7 +109,7 @@ html.dark .card-img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desc-text {
|
.desc-text {
|
||||||
width: 139%;
|
width: 140%;
|
||||||
/* 140% is too close */
|
/* 140% is too close */
|
||||||
transition: width 0.2s ease;
|
transition: width 0.2s ease;
|
||||||
}
|
}
|
||||||
@@ -120,7 +128,7 @@ a.unclickable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.desc-text {
|
.desc-text {
|
||||||
width: 135%;
|
width: 136%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,15 +4,19 @@ 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" :alt="`${name} logo`" />
|
||||||
<h3 class="m-0">{{ props.name }}</h3>
|
<h3 class="m-0">{{ props.name }}</h3>
|
||||||
<p class="desc-text text-gray-600 dark:text-gray-200"><slot /></p>
|
<p class="desc-text text-gray-600 dark:text-gray-200"><slot /></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -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(-40deg);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 2;
|
||||||
|
top: 32.5%;
|
||||||
|
left: -8%;
|
||||||
|
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);
|
||||||
|
@@ -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,12 +8,10 @@ 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>
|
||||||
<div class="prose dark:prose-invert flex">
|
<div class="prose dark:prose-invert flex onhover">
|
||||||
<HomeStatBox
|
<HomeStatBox
|
||||||
:href="latest._path"
|
:href="latest._path"
|
||||||
color="lightgreen"
|
color="lightgreen"
|
||||||
@@ -22,16 +20,15 @@ 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
|
||||||
v-for="(tag, index) in latest.tags"
|
v-for="(tag, index) in latest.tags"
|
||||||
:key="index"
|
:key="index"
|
||||||
:dest="`/tags/stories/${tag}`"
|
:dest="`/tags/stories/${tag}`"
|
||||||
>
|
:name="tag"
|
||||||
{{ tag }}
|
/>
|
||||||
</Tag>
|
|
||||||
</div>
|
</div>
|
||||||
<ContentRenderer
|
<ContentRenderer
|
||||||
tag="article"
|
tag="article"
|
||||||
@@ -52,4 +49,8 @@ const prettyDate = getPrettyDate(latest);
|
|||||||
h2 {
|
h2 {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.onhover:hover h2 {
|
||||||
|
@apply text-blue-700 dark:text-blue-400;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,19 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { dest, highlight = false } = defineProps<{
|
const props = defineProps<{
|
||||||
|
name: string;
|
||||||
dest: string;
|
dest: string;
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
// const isLinkableTag = !props.name.includes(" ");
|
||||||
|
const isLinkableTag = true;
|
||||||
|
const tagClass = [
|
||||||
|
"inline-block text-xs rounded-lg py-1 px-2 mt-1 mr-1 transition border border-pink-200 dark:border-pink-900 border-2 font-medium",
|
||||||
|
{ "bg-pink-200 dark:bg-pink-900": props.highlight },
|
||||||
|
{ "shadow-md": isLinkableTag },
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a :href="dest">
|
<a :href="dest" v-if="isLinkableTag">
|
||||||
<div
|
<div :class="tagClass">
|
||||||
:class="[
|
{{ name }}
|
||||||
'inline-block text-xs rounded-full py-1 px-2 mt-1 mr-1 bg-gray-300 dark:bg-gray-500 transition',
|
|
||||||
{ 'bg-yellow-200 dark:bg-yellow-700 shadow-lg': highlight },
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
<div v-else>
|
||||||
|
<div :class="tagClass">
|
||||||
|
{{ name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
18
components/content/ProseImg.vue
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(defineProps<{ src: string; alt?: string }>(), {
|
||||||
|
alt: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const src = props.src;
|
||||||
|
const imgSrc =
|
||||||
|
src.startsWith("http://") || src.startsWith("https://")
|
||||||
|
? src
|
||||||
|
: `/images/posts/${src}`;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<figure class="flex flex-col items-center">
|
||||||
|
<img :src="imgSrc" class="drop-shadow-lg" :alt="alt" />
|
||||||
|
<figcaption class="text-center" v-if="alt">{{ alt }}</figcaption>
|
||||||
|
</figure>
|
||||||
|
</template>
|
@@ -1,15 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const { src } = defineProps<{ src: string }>();
|
|
||||||
|
|
||||||
const imgSrc =
|
|
||||||
src.startsWith("http://") || src.startsWith("https://")
|
|
||||||
? src
|
|
||||||
: `/images/posts/${src}`;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<figure class="flex flex-col items-center">
|
|
||||||
<img :src="imgSrc" />
|
|
||||||
<figcaption class="text-center"><Markdown /></figcaption>
|
|
||||||
</figure>
|
|
||||||
</template>
|
|
@@ -18,10 +18,9 @@ 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 — I'm a student who is quite passionate
|
||||||
is quite passionate about some subjects but is quite lazy in every other.
|
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 +30,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 +39,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 +58,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 +75,9 @@ ul {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.redphasis {
|
||||||
|
font-weight: bold;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -7,52 +7,49 @@
|
|||||||
This site is statically generated using
|
This site is statically generated using
|
||||||
<a href="https://v3.nuxtjs.org">Nuxt.js</a> with the help of templates and
|
<a href="https://v3.nuxtjs.org">Nuxt.js</a> with the help of templates and
|
||||||
Markdown — because really, writing HTML by hand is tedious and I don't
|
Markdown — because really, writing HTML by hand is tedious and I don't
|
||||||
know why I ever tried — and its source is available
|
know why I ever tried — and its
|
||||||
<a href="https://github.com/potatoeggy/public">here</a>.
|
<a href="https://github.com/potatoeggy/public">source is available here</a
|
||||||
|
>.
|
||||||
</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.svg">
|
||||||
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">
|
|
||||||
Ad-filled media server
|
|
||||||
</ServiceCard>
|
|
||||||
|
|
||||||
<ServiceCard
|
<ServiceCard
|
||||||
name="Jellyfin"
|
name="Jellyfin"
|
||||||
href="https://jellyfin.eggworld.tk"
|
href="https://jellyfin.eggworld.me"
|
||||||
img="jellyfin.png"
|
img="jellyfin.svg"
|
||||||
>
|
>
|
||||||
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.svg"
|
||||||
:unclickable="true"
|
unclickable
|
||||||
|
broken
|
||||||
>
|
>
|
||||||
Whitelisted
|
Whitelisted
|
||||||
</ServiceCard>
|
</ServiceCard>
|
||||||
|
@@ -1,16 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Set the page title in the format [title] | Eggworld.
|
* Set the page title in the format [title] | [site name].
|
||||||
* @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} | Oeufs?`,
|
||||||
link: [
|
meta: [
|
||||||
{ rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
|
{ name: "viewport", content: " width=device-width,initial-scale=1" },
|
||||||
{
|
{ name: "description", content: description ?? "" },
|
||||||
rel: "stylesheet",
|
|
||||||
href: "https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -13,13 +13,11 @@ First, what is Linux? At its heart, it is a [kernel](https://en.wikipedia.org/wi
|
|||||||
|
|
||||||
However, this article will largely focus on **desktop Linux**, which competes with other operating systems such as Windows and macOS.
|
However, this article will largely focus on **desktop Linux**, which competes with other operating systems such as Windows and macOS.
|
||||||
|
|
||||||
# Complete freedom
|
## Complete freedom
|
||||||
|
|
||||||
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}
|

|
||||||
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.
|
|
||||||
::
|
|
||||||
|
|
||||||
Or, if you aren't the type to spend hours fiddling every little thing, you can choose from a variety of existing default desktop interfaces.
|
Or, if you aren't the type to spend hours fiddling every little thing, you can choose from a variety of existing default desktop interfaces.
|
||||||
|
|
||||||
@@ -33,8 +31,7 @@ Or Plasma's endless customisation:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
::image{src=https://www.omgubuntu.co.uk/wp-content/uploads/2019/07/kde-plasma-desktop.jpg}
|

|
||||||
::
|
|
||||||
|
|
||||||
And this is only the beginning — it's not just appearance you have control over, although both GNOME and Plasma also come with their assortment of applications that have designs that perfectly mesh with the desktop, with global theming letting you click a single button in your settings menu to change colours or styles across all your apps.
|
And this is only the beginning — it's not just appearance you have control over, although both GNOME and Plasma also come with their assortment of applications that have designs that perfectly mesh with the desktop, with global theming letting you click a single button in your settings menu to change colours or styles across all your apps.
|
||||||
|
|
||||||
@@ -42,7 +39,7 @@ Don't like your file manager? Swap it out for one of the dozens out there. Don't
|
|||||||
|
|
||||||
**You can do anything.**
|
**You can do anything.**
|
||||||
|
|
||||||
# The package manager
|
## The package manager
|
||||||
|
|
||||||
Speaking of the update manager…
|
Speaking of the update manager…
|
||||||
|
|
||||||
@@ -56,22 +53,20 @@ Here are just a couple of the graphical stores available:
|
|||||||
|
|
||||||
GNOME Software for GNOME:
|
GNOME Software for GNOME:
|
||||||
|
|
||||||
::image{src=https://www.omgubuntu.co.uk/wp-content/uploads/2021/02/gnome-software-refresh.jpg}
|

|
||||||
::
|
|
||||||
|
|
||||||
Discover for Plasma:
|
Discover for Plasma:
|
||||||
|
|
||||||
::image{src=https://userbase.kde.org/images.userbase/thumb/2/2d/Discoverappfocus.png/500px-Discoverappfocus.png}
|

|
||||||
::
|
|
||||||
|
|
||||||
By contrast, the Microsoft Store was (is) a complete and utter mess that is nowhere near the integration and experience Linux has had for decades.
|
By contrast, the Microsoft Store was (is) a complete and utter mess that is nowhere near the integration and experience Linux has had for decades.
|
||||||
|
|
||||||
# Open source
|
## Open source
|
||||||
|
|
||||||
Not only that, desktop Linux was built by thousands of volunteers, each contributing their own code to make the best product they can. Because it's completely open source (anyone can see or edit the source code), it's inherently more secure as simply more people are looking at it to fix issues and squash bugs.
|
Not only that, desktop Linux was built by thousands of volunteers, each contributing their own code to make the best product they can. Because it's completely open source (anyone can see or edit the source code), it's inherently more secure as simply more people are looking at it to fix issues and squash bugs.
|
||||||
|
|
||||||
Learning Linux is a great opportunity to jump into learning more about computers because of the knowledge you gain over time of how your computer works on a fundamental level as you inevitably start troubleshooting *when* something breaks. And perhaps you'll be the one to contribute back upstream to the project too, if you fix a bug or add a new feature, and have your own code distributed around to millions of other users.
|
Learning Linux is a great opportunity to jump into learning more about computers because of the knowledge you gain over time of how your computer works on a fundamental level as you inevitably start troubleshooting *when* something breaks. And perhaps you'll be the one to contribute back upstream to the project too, if you fix a bug or add a new feature, and have your own code distributed around to millions of other users.
|
||||||
|
|
||||||
# Try it now!
|
## Try it now!
|
||||||
|
|
||||||
With dozens of well-maintained versions of Linux operating systems out there, you'll be sure to find one that suits your needs. To try GNOME, [Pop!_OS](https://pop.system76.com/) or [Fedora](https://getfedora.org/en/workstation/download/) provide a seamless out-of-the-box experience. To try Plasma, [Kubuntu](https://kubuntu.org/) is a fantastic starting point. To get a macOS-like feel, [Elementary OS](https://elementary.io/) gives you that Apple vibe while, like every other Linux OS, is completely free of charge, and lets you try it out before you decide to install it.
|
With dozens of well-maintained versions of Linux operating systems out there, you'll be sure to find one that suits your needs. To try GNOME, [Pop!_OS](https://pop.system76.com/) or [Fedora](https://getfedora.org/en/workstation/download/) provide a seamless out-of-the-box experience. To try Plasma, [Kubuntu](https://kubuntu.org/) is a fantastic starting point. To get a macOS-like feel, [Elementary OS](https://elementary.io/) gives you that Apple vibe while, like every other Linux OS, is completely free of charge, and lets you try it out before you decide to install it.
|
||||||
|
@@ -9,6 +9,4 @@ 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}
|

|
||||||
Perfect.
|
|
||||||
::
|
|
@@ -4,9 +4,10 @@ date: 2021-08-21
|
|||||||
tags:
|
tags:
|
||||||
- primoprod
|
- primoprod
|
||||||
- tech
|
- tech
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -16,9 +17,9 @@ This report will cover the beginnings of the project to the present day: 16 July
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
What is Primoprod? Short for "Productivity Primogems", it was born when I noticed that the gacha system employed by games such as *Genshin Impact* could be incredibly addictive, so I decided to see if I could take advantage of it to boost my productivity and at the same time try to learn web development.
|
What is Primoprod? Short for "Productivity Primogems", it was born when I noticed that the gacha system employed by games such as _Genshin Impact_ could be incredibly addictive, so I decided to see if I could take advantage of it to boost my productivity and at the same time try to learn web development.
|
||||||
|
|
||||||
The basic premise was to assign a given point value for each productive task and be able to spend those points on something or have them progress toward a milestone, so that productive tasks would be incentivised. The aforementioned gacha games and Uber use this to great effect, so I decided to emulate the Wish UI of [*Genshin Impact*](https://genshin.mihoyo.com/en).
|
The basic premise was to assign a given point value for each productive task and be able to spend those points on something or have them progress toward a milestone, so that productive tasks would be incentivised. The aforementioned gacha games and Uber use this to great effect, so I decided to emulate the Wish UI of [_Genshin Impact_](https://genshin.mihoyo.com/en).
|
||||||
|
|
||||||
And so the project began! I decided to work with Vue.js because of its gentler learning curve compared to Angular and more traditional HTML/CSS/JS separation compared to React. You can tell when I became more comfortable in using Vue's declarative system in the later components compared to, say, [`App.vue`](https://github.com/potatoeggy/primoprod/blob/master/src/App.vue).
|
And so the project began! I decided to work with Vue.js because of its gentler learning curve compared to Angular and more traditional HTML/CSS/JS separation compared to React. You can tell when I became more comfortable in using Vue's declarative system in the later components compared to, say, [`App.vue`](https://github.com/potatoeggy/primoprod/blob/master/src/App.vue).
|
||||||
|
|
||||||
@@ -30,17 +31,15 @@ As my first foray into web development, there were many tools and practices I co
|
|||||||
|
|
||||||
Luckily, I didn't have to make any of these decisions because Vue's [CLI](https://cli.vuejs.org/) gives you a list of sane defaults that you can pick from, and since I was learning for the future, I went with the first option of Vue 3 + Typescript. `vue-cli` even nicely [initialised a git repo for me](https://github.com/potatoeggy/primoprod/commit/9b7d7841806c905e8f580f98d1c95d4732178810)!
|
Luckily, I didn't have to make any of these decisions because Vue's [CLI](https://cli.vuejs.org/) gives you a list of sane defaults that you can pick from, and since I was learning for the future, I went with the first option of Vue 3 + Typescript. `vue-cli` even nicely [initialised a git repo for me](https://github.com/potatoeggy/primoprod/commit/9b7d7841806c905e8f580f98d1c95d4732178810)!
|
||||||
|
|
||||||
At the time, coming from Python/Java, I opted in to the class components plugin hoping it made it easier to develop for, but later removed it due to a lack of documentation with it for Vue 3. Occasional downsides of newer technologies ¯\\\_(ツ)_/¯.
|
At the time, coming from Python/Java, I opted in to the class components plugin hoping it made it easier to develop for, but later removed it due to a lack of documentation with it for Vue 3. Occasional downsides of newer technologies ¯\\\_(ツ)\_/¯.
|
||||||
|
|
||||||
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}
|

|
||||||
::
|
|
||||||
|
|
||||||
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}
|

|
||||||
::
|
|
||||||
|
|
||||||
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,10 +57,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}
|

|
||||||
::
|
|
||||||
|
|
||||||
:/ thanks MDN for letting me know
|
|
||||||
|
|
||||||
[Some foreshadowing](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemRevealScreen.vue#L224)
|
[Some foreshadowing](https://github.com/potatoeggy/primoprod/blob/master/src/components/ItemRevealScreen.vue#L224)
|
||||||
|
|
||||||
@@ -69,13 +65,11 @@ 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}
|

|
||||||
::
|
|
||||||
|
|
||||||
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}
|

|
||||||
::
|
|
||||||
|
|
||||||
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 +77,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…
|
||||||
|
|
||||||
@@ -136,19 +130,17 @@ No it doesn't it's not even close but I'm not coming back to that
|
|||||||
|
|
||||||
## Sweet release
|
## Sweet release
|
||||||
|
|
||||||
Now that `ItemRevealScreen` is *done and over with and will not need any changes*, before making myself work on the equally fun part of the project that is `ItemRevealAllOverlay`, I opted for a break in `ItemObtainScreen` and `ItemDescriptionOverlay` and a one-week hiatus.
|
Now that `ItemRevealScreen` is _done and over with and will not need any changes_, before making myself work on the equally fun part of the project that is `ItemRevealAllOverlay`, I opted for a break in `ItemObtainScreen` and `ItemDescriptionOverlay` and a one-week hiatus.
|
||||||
|
|
||||||
And they were easy! Easy and fun. It was blissful to be working with structured HTML and CSS again, and the animation pains there were *nothing* compared to the trauma of `ItemRevealScreen`.
|
And they were easy! Easy and fun. It was blissful to be working with structured HTML and CSS again, and the animation pains there were _nothing_ compared to the trauma of `ItemRevealScreen`.
|
||||||
|
|
||||||
In fact, I consider those two to be 100% done unless I can find a way to apply a border that looks exactly like the original game's that isn't an image.
|
In fact, I consider those two to be 100% done unless I can find a way to apply a border that looks exactly like the original game's that isn't an image.
|
||||||
|
|
||||||
But it looks great!
|
But it looks great!
|
||||||
|
|
||||||
::image{src=itemdescriptionoverlay.png}
|

|
||||||
::
|
|
||||||
|
|
||||||
::image{src=itemobtainoverlay.png}
|

|
||||||
::
|
|
||||||
|
|
||||||
## Wrapping up
|
## Wrapping up
|
||||||
|
|
||||||
|
52
content/blog/2022/4-anonymous-browser-extensions.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: "Stay Anonymous Online With These 4 Browser Extensions"
|
||||||
|
date: "2022-08-06"
|
||||||
|
_draft: true
|
||||||
|
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.
|
48
content/blog/2022/av1-foss-video-codec.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
title: "AV1 — The FOSS Video Codec"
|
||||||
|
date: 2022-11-13
|
||||||
|
_draft: true
|
||||||
|
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, you’d 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 don’t we also compress video, too?
|
||||||
|
|
||||||
|
And that’s 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 don’t want to have to pay millions of dollars recurringly just to play video, so it’s been stuck playing local media.
|
||||||
|
|
||||||
|
Why doesn’t 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.
|
172
content/blog/2022/etymology-of-barin.md
Normal 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 (Ciers–Eos)
|
||||||
|
|
||||||
|
### English Connection (Ciers–Leeco)
|
||||||
|
|
||||||
|
### Scrivener Connection (Ciers–Xunil)
|
||||||
|
|
||||||
|
### Ezwon Connection (Ciers–Demauge)
|
||||||
|
|
||||||
|
### Arslash Connection (Ciers–Herdit)
|
||||||
|
|
||||||
|
## Characters
|
||||||
|
|
||||||
|
### Eifueo
|
||||||
|
|
||||||
|
### George Anstion
|
||||||
|
|
||||||
|
### Siava Pythone
|
||||||
|
|
||||||
|
### Minestro
|
||||||
|
|
||||||
|
### Nano and Rymo
|
||||||
|
|
||||||
|
### Rio Nohigi
|
||||||
|
|
||||||
|
### Cloche
|
||||||
|
|
||||||
|
### Garson
|
||||||
|
|
||||||
|
### Mrs. Lowshi
|
||||||
|
|
||||||
|
### Alston
|
||||||
|
|
||||||
|
### Brendan May
|
||||||
|
|
||||||
|
### Hina Asvyn
|
||||||
|
|
||||||
|
### Retadux
|
||||||
|
|
||||||
|
### Arro
|
||||||
|
|
||||||
|
### Calitea
|
||||||
|
|
54
content/blog/2022/foss-licensing.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: "Choosing a License — Politics in FOSS"
|
||||||
|
date: "2022-09-02"
|
||||||
|
_draft: true
|
||||||
|
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!
|
77
content/blog/2022/git-is-a-blockchain.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
title: "Git is a Blockchain"
|
||||||
|
date: "2022-10-02"
|
||||||
|
_draft: true
|
||||||
|
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.
|
52
content/blog/2022/github-for-dummies.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
title: "GitHub for Dummies"
|
||||||
|
date: "2022-06-17"
|
||||||
|
_draft: true
|
||||||
|
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 -->
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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!
|
56
content/blog/2022/googles-guide-to-taking-over-the-web.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: "Google's Guide to Taking Over the Web"
|
||||||
|
date: "2022-07-16"
|
||||||
|
_draft: true
|
||||||
|
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?
|
553
content/blog/2022/handy-python-tricks-2.md
Normal file
@@ -0,0 +1,553 @@
|
|||||||
|
---
|
||||||
|
title: "13 Tricks to Write Nicer Python"
|
||||||
|
date: "2022-08-21"
|
||||||
|
_draft: true
|
||||||
|
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 you’re 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 you’re 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?
|
||||||
|
|
||||||
|
That’s 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
They’re 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 Python’s 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 it’s 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 that’s 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 they’re 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 they’re 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, it’s 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 that’s 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.
|
||||||
|
|
||||||
|
That’s 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()
|
||||||
|
|
||||||
|
Python’s 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 don’t 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 there’s one annoying difference between Windows and macOS / Linux, it’s 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"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Doesn’t 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, there’s 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 don’t 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 don’t 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 it’s 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 you’re doing something over and over again and checking for a condition until it’s 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 it’s faster to read, write, and run. Python is a “batteries included” language — chances are that the way you’re 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.
|
55
content/blog/2022/how-to-emulate.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
title: "Reviving Older Games Through Emulation"
|
||||||
|
date: "2022-05-22"
|
||||||
|
_draft: true
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
34
content/blog/2022/many-steps-to-access-a-website.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
title: "Appreciate Your Browser!"
|
||||||
|
date: "2022-09-18"
|
||||||
|
_draft: true
|
||||||
|
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.
|
9
content/blog/2022/politics-of-barin.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: "Politics of Barin"
|
||||||
|
date: 2022-10-24
|
||||||
|
_draft: true
|
||||||
|
tags:
|
||||||
|
- barin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
@@ -4,6 +4,7 @@ date: "2022-01-15"
|
|||||||
tags:
|
tags:
|
||||||
- tech
|
- tech
|
||||||
- primoprod
|
- primoprod
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
|
|
||||||
Six months have passed since the [first progress report](/blog/2021/primoprod-progress-report/). Since then, a flood of changes have made it in, including hundredth-class Android support!
|
Six months have passed since the [first progress report](/blog/2021/primoprod-progress-report/). Since then, a flood of changes have made it in, including hundredth-class Android support!
|
||||||
@@ -14,8 +15,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"}
|

|
||||||
::
|
|
||||||
|
|
||||||
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,8 +23,7 @@ Nah, this is good enough.
|
|||||||
|
|
||||||
## Take this!
|
## Take this!
|
||||||
|
|
||||||
::image{src="primoprod-questscreen.png"}
|

|
||||||
::
|
|
||||||
|
|
||||||
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,8 +55,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"}
|

|
||||||
::
|
|
||||||
|
|
||||||
*I wish I was actually this rich in the base game.*
|
*I wish I was actually this rich in the base game.*
|
||||||
|
|
||||||
@@ -67,8 +65,7 @@ As you can see, the slider looks very out of place. Why? That's because [only Fi
|
|||||||
|
|
||||||
Up until now, only one banner was supported. This was finally fixed in November with the addition of [banner headers](https://github.com/potatoeggy/primoprod/pull/25) to match the base game. Now, you can simultaneously roll for Qiqi on *both* banners!
|
Up until now, only one banner was supported. This was finally fixed in November with the addition of [banner headers](https://github.com/potatoeggy/primoprod/pull/25) to match the base game. Now, you can simultaneously roll for Qiqi on *both* banners!
|
||||||
|
|
||||||
::image{src="selected-wanderlust-invocation.webp"}
|

|
||||||
::
|
|
||||||
|
|
||||||
On a side note, did you know that Vue puts all of their reactive things into Proxies? This means you can't simply `console.log(obj)` without going through five more clicks to find what you actually want. No, no. To properly print out the actual object, you have to *copy its contents to a clean, non-reactive Object* for this to work. Why??
|
On a side note, did you know that Vue puts all of their reactive things into Proxies? This means you can't simply `console.log(obj)` without going through five more clicks to find what you actually want. No, no. To properly print out the actual object, you have to *copy its contents to a clean, non-reactive Object* for this to work. Why??
|
||||||
|
|
||||||
@@ -127,8 +124,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"}
|

|
||||||
::
|
|
||||||
|
|
||||||
## Kids and their phones
|
## Kids and their phones
|
||||||
|
|
||||||
@@ -140,8 +136,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"}
|

|
||||||
::
|
|
||||||
|
|
||||||
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 +146,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!
|
||||||
|
102
content/blog/2022/rust-changes-how-you-think-and-code.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
title: "Rust Changes How You Think And Code"
|
||||||
|
date: 2022-11-27
|
||||||
|
_draft: true
|
||||||
|
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.
|
38
content/blog/2022/wayland-intro.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
title: "What's Wayland? Linux's \"New\" Display Server"
|
||||||
|
date: 2022-12-11
|
||||||
|
_draft: true
|
||||||
|
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.
|
120
content/blog/2022/why-use-web-frameworks.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
---
|
||||||
|
title: "Why Use Web Frameworks?"
|
||||||
|
date: 2022-10-30
|
||||||
|
_draft: true
|
||||||
|
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.
|
64
content/blog/2022/wine.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
title: "Running Windows Apps on Mac and Linux"
|
||||||
|
date: "2022-10-16"
|
||||||
|
_draft: true
|
||||||
|
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 doesn’t 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)!
|
134
content/blog/2023/primoprod-progress-report-3.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
title: "Primoprod Progress Report 3"
|
||||||
|
date: 2023-12-29
|
||||||
|
tags:
|
||||||
|
- tech
|
||||||
|
- primoprod
|
||||||
|
- featured
|
||||||
|
---
|
||||||
|
|
||||||
|
Welcome back to another one of these development posts! It's been almost two years since [the last one](/blog/2022/primoprod-progress-report-2), so there are plenty of changes to talk about. Primoprod is more polished than ever, but a couple of questionable initial design decisions made it harder to work on than before.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
## Pretty pretty pulls
|
||||||
|
|
||||||
|
There's a summary screen for 10-pulls now! It has that nice slide-in animation from the base game too, along with (largely) proper item alignment and a sorting algorithm that is not exactly like the base game's but works well enough to generalise outside of Genshin too.
|
||||||
|
|
||||||
|
The last step for this screen is to have the rush of particles in the base game for 4- and 5-star items, but that's incredibly difficult for someone not frontend-oriented like myself.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Not only that, there are lots of small improvements to better mimick the base game! For example, weapons now have their weapon background behind them:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Banner headers are now dynamically generated instead of taken from screenshots, so now they can be nicely animated!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Version 3.0 of the base game brought a new book UI for the details and wish screens. I'm proud to announce that Primoprod was the very first simulator to incorporate it into its design!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Performance and prefetching
|
||||||
|
|
||||||
|
These are only a few of the design changes that make Primoprod a prettier and happier simulator. However, beauty means nothing if no one can see it. Luckily, update 1.2.2 of Primoprod brought some much-needed optimisations that make its web version much more palatable.
|
||||||
|
|
||||||
|
As of writing, Primoprod has around 75 MB of assets that it may need to show to users, the bulk of which is wish splash art. Previously, all of this was delivered as PNGs on demand, which resulted in noticeable lag waiting for the image when going through 10-pulls. By converting media to more modern formats, Primoprod gains nearly a *5x decrease in load times!*
|
||||||
|
|
||||||
|
Specifically, all PNGs were converted to WEBPs, MP4s to WEBMs (except for Safari, which doesn't support it), and MP3s to OGGs (except for Safari, which doesn't support it).
|
||||||
|
|
||||||
|
But that's not all! A 5x decrease in load time is still noticeable load time. The user doesn't want t to wait a few hundred milliseconds when they click the wish button, so we need as little latency as possible.
|
||||||
|
|
||||||
|
As it turns out, fetching the wish animation videos while the user is chilling on the main banner screen is both incredibly easy and incredibly rewarding — as long as the user doesn't wish right away, they can download the videos in the background so that when they start gambling, they don't have to wait at all!
|
||||||
|
|
||||||
|
Primoprod uses this same tactic to prefetch pull splash screens. Because rolls are determined the instant the user presses "Wish", it can prefetch the splash images required while the meteor animation plays, which means that splash screen animations are also instant! With a sufficiently fast internet connection (~1-2 MB/s), there ends up being *zero load time* for the user.
|
||||||
|
|
||||||
|
Of course, the downloadable versions of Primoprod (desktop/Android) will still always be faster, but this is such a noticeable decrease for users that it was a no-brainer.
|
||||||
|
|
||||||
|
## Quash the larvae before they hatch
|
||||||
|
|
||||||
|
### Oh, you sweet summer child
|
||||||
|
|
||||||
|
Things have changed a lot in the web development landscape since I began this project back in July 2021, and those changes have highlighted both the bad and the good in my initial designs.
|
||||||
|
|
||||||
|
I chose to use TypeScript and Vue 3, both decisions that hold up today. However, I opted for Vue's Options API over the new Composition API, which resulted in [a headache](https://github.com/potatoeggy/primoprod/pull/54) when I tried to read my old code — not helped at all by the fact that Primoprod was my first foray into web development, and there is some truly atrocious code:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<img
|
||||||
|
v-for="weapon in ['Bow', 'Catalyst', 'Claymore', 'Polearm', 'Sword']"
|
||||||
|
:key="weapon"
|
||||||
|
:src="getWeaponBgImage(weapon)"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
'active-bg-img': true,
|
||||||
|
nofilter: true,
|
||||||
|
'display-none': currentItem.element !== weapon || animationIndex < 0,
|
||||||
|
transparent: animationIndex <= 0,
|
||||||
|
'animate-weapon-bg': animationIndex === 1,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<img
|
||||||
|
:src="currentItemImage"
|
||||||
|
:class="{
|
||||||
|
'animate-image': animationIndex === 1,
|
||||||
|
'zoom-image': animationIndex === 0,
|
||||||
|
'active-img': true,
|
||||||
|
'active-img-weapon': currentItem.type === 'Weapon',
|
||||||
|
transparent: animationIndex < 0,
|
||||||
|
}"
|
||||||
|
:alt="currentItemImage"
|
||||||
|
@animationstart="playSfx"
|
||||||
|
@animationend="if (animationIndex < 2) nextAnimation();"
|
||||||
|
@load="nextAnimation"
|
||||||
|
/>
|
||||||
|
<!-- the reason why the double check is needed is
|
||||||
|
that the two animations for the drop shadow count
|
||||||
|
as two animations and trigger animationend twice -->
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
Another issue I didn't foresee but still ended up to be a real pain was Webpack. [Vite](https://vitejs.dev/) is the new kid on the block, built specifically for Vue, with the major advantage of being extremely fast. At Primoprod's scale — and Primoprod isn't even that big of a project — Webpack takes more than ten seconds to start the local dev server. Which is slow!
|
||||||
|
|
||||||
|
```
|
||||||
|
Done in 12.38s.
|
||||||
|
yarn build 57.47s user 9.66s system 535% cpu 12.528 total
|
||||||
|
```
|
||||||
|
|
||||||
|
### You will never take prop drilling away from me
|
||||||
|
|
||||||
|
Behind the scenes, a lot of systems have been tweaked and changed over the years as Primoprod grew in scope. [Pinia](https://pinia.vuejs.org/) is a global store for Vue (similar to Redux in React) that Primoprod now uses to hold information such as the current banner, inventory, and settings.
|
||||||
|
|
||||||
|
Previously, all of these were drilled deep into nested components, which is really not great.
|
||||||
|
|
||||||
|
What *you*, the user, gets out of this are a bunch of bug fixes and some really nice quality-of-life features:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Now you can choose to have infinite fates! And roll only Qiqi! And even go back to older banners, a long-overdue feature.
|
||||||
|
|
||||||
|
### Quests
|
||||||
|
|
||||||
|
Quests have been rather neglected since their release pre-1.0 a couple years ago, largely because I no longer use it to stay productive. Recently, they've received some love and a *lot* of patches to fix some really strange behaviour.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
First, there is a character limit in the title, and the box height has been slightly increased to allow multiline titles to properly overflow. Daily commissions now have icons to make it more obvious and also to copy the base game. Lastly, a bunch of bugs related to lists and quest ordering were squashed to avoid the janky horror that was pre-1.4 quests.
|
||||||
|
|
||||||
|
### For the mobile gamers in chat
|
||||||
|
|
||||||
|
Primoprod's mobile version has received the most love of all! Strangely, in WebKit-based engines (Chrome, Safari, Android WebView), using `background: fixed` with `transform: rotate(...)` causes the browser to ignore the `background-image` property. This broke mobile backgrounds on everything but Firefox Android, so that needed to be worked around.
|
||||||
|
|
||||||
|
On Android, things have tidied themselves up a bit as Primoprod comes ever closer to being able to fool the untrained eye. The notification bar used to annoy people by being ever present in the corner — now it's gone. There used to be a strange black flash right before playing videos — now that's gone. And video playback used to be very squashed on mobile — that's fixed too!
|
||||||
|
|
||||||
|
All in all, the squeaky platform got the grease.
|
||||||
|
|
||||||
|
## The future
|
||||||
|
|
||||||
|
As Primoprod approaches its two-and-a-half-year anniversary, it is slowly time to say goodbye. Updates have become more sporadic, and even I don't use it to be productive anymore. There is only so much one can polish something before it starts to blind them. This progress report may be its last.
|
||||||
|
|
||||||
|
But it was fun while it lasted!
|
53
content/blog/2023/sunsetting-eifueo.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
title: "Sunsetting the Eifueo Project"
|
||||||
|
date: 2023-05-23
|
||||||
|
tags:
|
||||||
|
- eifueo
|
||||||
|
- retrospective
|
||||||
|
---
|
||||||
|
|
||||||
|
For three and a half years, the [Eifueo project](https://eifueo.eggworld.me) has dutifully carried out its task of collecting and organising notes in a way that would be quick and easy to review. Although this worked out wonderfully in high school, the method is both inefficient and insufficient for the pace of higher education.
|
||||||
|
|
||||||
|
So how can we make it better?
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
We can't.
|
||||||
|
|
||||||
|
At their core, engineering courses in university are about problem-solving. Instead of blindly memorising rules to be applied once to get you the answer, you blindly memorise rules to be applied *two or more times* to get you the answer.
|
||||||
|
|
||||||
|
If we have to do that, it's much easier to write plenty of practice problems instead of rewriting plenty of notes.
|
||||||
|
|
||||||
|
## It takes too loooooong
|
||||||
|
|
||||||
|
Reformatting notes is not an easy endeavour. We have to re-examine our old notes, research any ambiguities, and then write all of them up in a way that is clear, concise, and professional. On average, I'd say that it took a good hour or so per day to finish Eifueo for six courses. Imagine the number of practice problems I could finish if I allocated Eifueo's time slot to those instead!
|
||||||
|
|
||||||
|
## It's booooring
|
||||||
|
|
||||||
|
Me? A writer? Imagine.
|
||||||
|
|
||||||
|
This one isn't actually too bad, and doing practice problems instead isn't going to help much, but think of all the other fun things I could be doing instead. I already *have* the notes. If I need them, I'll just look back at them.
|
||||||
|
|
||||||
|
Anyway, by the end of Eifueo's lifespan, most of the content was regurgitated onto the site.
|
||||||
|
|
||||||
|
## It doesn't help nearly as much for exams
|
||||||
|
|
||||||
|
This is perhaps the biggest reason, and I have to say that I'm quite disappointed in the education system for this one.
|
||||||
|
|
||||||
|
High school had many courses that were "expression"-focused, and those were the courses that Eifueo excelled in. English, chemistry, and history all were less about getting the answer or precise process correct, and more about how well you can bullshit your way out of it. Even math was pretty easy to apply a shallow formula and get the right answer.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Unfortunately, the physics courses are antithetical to everything Eifueo stands for. They have a rigid structure that you can't bullshit your way out of but are also flexible enough that you can't simply apply a formula. The best way to get good is simply to do more problems.
|
||||||
|
|
||||||
|
You'll have to understand that this greatly saddened me as a person who tries his hardest to do *less* problems. The cost-benefit ratio isn't worth it anymore.
|
||||||
|
|
||||||
|
## Retrospective
|
||||||
|
|
||||||
|
With a heavy heart, I must bid farewell to one of the first services I ever deployed to my server. Eifueo has tried its hardest to help me keep up, but I can only avoid doing practice for so much before it actually bites me in the butt.
|
||||||
|
|
||||||
|
o7 It has served me well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
@@ -8,4 +8,6 @@ tags:
|
|||||||
nopreview: true
|
nopreview: true
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
I was running from a dark thing and it was all horrible when my eyes snapped open and I looked at my alarm clock, which said that it was ten in the morning, which meant that I was going to be late for school unless I teleported back in time, so I got up and rushed to put on my clothes and do everything, then I didn’t even eat breakfast as I rushed to school before realising I could use my magical powers that I could as soon as I turned eleven to turn back time and avoid all this mess, so that’s what I did, but only after I actually ate breakfast and took my time, then I looked deep into myself and used my powers from friendship and determination to go back three hours (my friends were the bestest ever after I saved their lives using my magical powers), and I walked calmly to school, trying not to blush at my boyfriend (who is actually the hottest werewolf ever) when he suddenly popped out of nowhere, and I clutched his hand because I loved him so much, then he said, “oh hi babe what’s up?” then I said, “oh nothing,” then I giggled because he was so funny and then he laughed because I laughed then I laughed because he was laughing because I was giggling and everything was awesome, until suddenly a tree burst from the ground and we couldn’t get to school because suddenly there were tree monsters everywhere and I got scared but I knew that John (the werewolf) would protect me because he was a werewolf and so he transformed into a blue wolf (the rarest and most powerful kind) and after that he used his magical powers to vaporise the tree monsters with flashes of light and they all disappeared, and then I hugged him because I was scared and I said, “I was scared, thank you,” to which he said, “oh it’s nothing I would always protect you, my love,” and then I giggled again because he was being so cute, so I stared into his brilliant blue eyes which I only just realised matched the colour of his wolf form and admired all his really big muscles which would save me from any tree monster, but then suddenly he tensed and I realised that he was tense so I ran behind him again and peeked out from behind his shoulder and saw that something terrible appeared (even worse than tree monsters!), it was a ghost, and that was the first time I saw my boyfriend scared, which I could feel using my psychic powers so I told him telepathically that it was going to be ok and I could handle it using my dark powers which were super effective against psychics, and I focused really hard but nothing happened, so I thought of the strong bond that I had with all my friends and begged them for their help because there was this ghost and it could destroy the world, then they sent all their power and my eyes glowed as I tapped into a part of my power that i had never seen before, but it was now unlocked because of my friends’ determination and courage that flowed through my veins, as a white light burst from my hands that I held outstretched in front of me when I realised that this was the dark thing I was running from in my dream this morning, which I would finally kill because of the power of my friends that I had to see at school today, and I shouted as I killed the ghost, “omae wa mo shindeiru,” and it exploded and so we celebrated with my friends and they were all super impressed as I told the story and smiled at my hot boyfriend.
|
I was running from a dark thing and it was all horrible when my eyes snapped open and I looked at my alarm clock, which said that it was ten in the morning, which meant that I was going to be late for school unless I teleported back in time, so I got up and rushed to put on my clothes and do everything, then I didn’t even eat breakfast as I rushed to school before realising I could use my magical powers that I could as soon as I turned eleven to turn back time and avoid all this mess, so that’s what I did, but only after I actually ate breakfast and took my time, then I looked deep into myself and used my powers from friendship and determination to go back three hours (my friends were the bestest ever after I saved their lives using my magical powers), and I walked calmly to school, trying not to blush at my boyfriend (who is actually the hottest werewolf ever) when he suddenly popped out of nowhere, and I clutched his hand because I loved him so much, then he said, “oh hi babe what’s up?” then I said, “oh nothing,” then I giggled because he was so funny and then he laughed because I laughed then I laughed because he was laughing because I was giggling and everything was awesome, until suddenly a tree burst from the ground and we couldn’t get to school because suddenly there were tree monsters everywhere and I got scared but I knew that John (the werewolf) would protect me because he was a werewolf and so he transformed into a blue wolf (the rarest and most powerful kind) and after that he used his magical powers to vaporise the tree monsters with flashes of light and they all disappeared, and then I hugged him because I was scared and I said, “I was scared, thank you,” to which he said, “oh it’s nothing I would always protect you, my love,” and then I giggled again because he was being so cute, so I stared into his brilliant blue eyes which I only just realised matched the colour of his wolf form and admired all his really big muscles which would save me from any tree monster, but then suddenly he tensed and I realised that he was tense so I ran behind him again and peeked out from behind his shoulder and saw that something terrible appeared (even worse than tree monsters!), it was a ghost, and that was the first time I saw my boyfriend scared, which I could feel using my psychic powers so I told him telepathically that it was going to be ok and I could handle it using my dark powers which were super effective against psychics, and I focused really hard but nothing happened, so I thought of the strong bond that I had with all my friends and begged them for their help because there was this ghost and it could destroy the world, then they sent all their power and my eyes glowed as I tapped into a part of my power that i had never seen before, but it was now unlocked because of my friends’ determination and courage that flowed through my veins, as a white light burst from my hands that I held outstretched in front of me when I realised that this was the dark thing I was running from in my dream this morning, which I would finally kill because of the power of my friends that I had to see at school today, and I shouted as I killed the ghost, “omae wa mo shindeiru,” and it exploded and so we celebrated with my friends and they were all super impressed as I told the story and smiled at my hot boyfriend.
|
||||||
|
80
content/stories/shorts/ece-192-words-per-minute.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
title: "ECE 192 Words per Minute"
|
||||||
|
date: 2024-02-29
|
||||||
|
tags:
|
||||||
|
- featured
|
||||||
|
- shorts
|
||||||
|
- university
|
||||||
|
---
|
||||||
|
|
||||||
|
**Summary:** A student is mildly frustrated by his professor's pace of speaking.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"Even if we use one million, or two million, or even three million pieces, the additional tooling cost will remain constant. So you can see that in order to make a good economic analysis, you have to classify the costs related to the output activities as follows…"
|
||||||
|
|
||||||
|
Lento slammed his pencil down as the professor *finally* stopped speaking. The soot streaks on his notebook smoked from his rapid scribbling, and his wrist shook from the sheer exertion of trying to take notes from a professor who read off slides that looked like someone tried to squeeze a novel onto them.
|
||||||
|
|
||||||
|
Judging by the fire alarm going off around them, he wasn't the only one who nearly combusted his notes.
|
||||||
|
|
||||||
|
"You know," Atras murmured from beside him, "online lectures sound like a really good idea right now." His notebook was closed, his pencil pristine. The signs of a man who gave up long ago. The signs of a smart man.
|
||||||
|
|
||||||
|
"I," Lento hissed, "cannot take this anymore."
|
||||||
|
|
||||||
|
"What is he even saying?" Atras said, calmer but still exasperated. "What's all this about costs and benefits and graphs?"
|
||||||
|
|
||||||
|
"Who *cares?* Why does this *matter?* Has this man *ever had to deliver a presentation before?* I *don't need this to pass my exam.*"
|
||||||
|
|
||||||
|
Atras patted his back, an understanding but exasperated expression on his face. "Calm down. It's not that bad. It's just a humanities course."
|
||||||
|
|
||||||
|
"I'm not seeing the humanity," Lento said grimly.
|
||||||
|
|
||||||
|
"It's the bird course. The easy course. The course you don't even have to take notes in. Why are you trying so hard, anyway?"
|
||||||
|
|
||||||
|
"Says the guy who isn't taking notes!"
|
||||||
|
|
||||||
|
Atras snorted. "The prof posted all of his slides online. Why *should* you take notes? I'd rather not develop carpal tunnel in the first week."
|
||||||
|
|
||||||
|
A clock *tick*ed and Lento glanced over to the clock. The professor starts talking again, the sound like a drill burrowing directly into Lento's ears as the added knowledge of *incompetence* makes the emotional damage all too clear.
|
||||||
|
|
||||||
|
"How is he still talking?" Lento's voice steadily rose in pitch. "What is there left to talk about? Why can't I go home? Why am I here? Just to *suffer??"*
|
||||||
|
|
||||||
|
"And that's it!" the professor said brightly. "You are now free to go. Tomorrow, we'll be looking at cost estimation models. There are practice problems on LEARN if you want to apply what we covered today."
|
||||||
|
|
||||||
|
A rush of cool wind blew from somewhere out the exit, even though that would make no logical sense whatsoever, but conveniently masked the students' collective sigh of relief.
|
||||||
|
|
||||||
|
Lento stood up, dumped his notebook and pencil straight into his backpack, and promptly dragged Atras out the lecture hall with him. "Out," he demanded. "We are out of here."
|
||||||
|
|
||||||
|
"For the last time?"
|
||||||
|
|
||||||
|
"Do you even need to ask?"
|
||||||
|
|
||||||
|
"I thought it was interesting," said Atras. "The professor sounded like he knew what he was talking about. He just covered things a bit fast, that's all. And kinda derailed sometimes."
|
||||||
|
|
||||||
|
"…And he went *on and on* ***and on and on*** <u>***and on and on*** <span class="text-xl">***and on and on***<span></u> to say the equivalent of a small glossary and four lines of notes, except you don't know what four lines you need so you write down all of them anyway."
|
||||||
|
|
||||||
|
"You're too bitter," Atras observed. He held out a red rectangle. "Want a KitKat?"
|
||||||
|
|
||||||
|
"…Yes." Lento snatched the candy out of his friend's hands and ripped the wrapper open. He chewed methodically on the chocolate.
|
||||||
|
|
||||||
|
"Feeling better?"
|
||||||
|
|
||||||
|
"A little," Lento admitted. "But still. I'm not going back to that class. "
|
||||||
|
|
||||||
|
"You should," Atras coaxed, holding out a Coffee Crisp. "You always learn things better when lectures can hold you accountable. Here, do you think you can talk about the course without hating on the professor for one second?"
|
||||||
|
|
||||||
|
"Who's gonna hold the professor accountable?" Lento grumbled. Atras gave him a look. "Yeah, yeah, I know. Fine. One more class. He has *one more chance."*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Addendum:
|
||||||
|
|
||||||
|
"If you have three horses, the magnetic flux through each horse actually approaches zero as we move farther and farther away. Combined with Gauss' law, given that we know that the flux density of each horse is, in an ideal world, equal to that of free space, this lets us cancel out the electric flux density factor on both sides here, which results in seven horses!"
|
||||||
|
|
||||||
|
The hissing sprinklers and wailing fire alarm do little to stop the madman from his lecture. Lento rests his head on his arm on his desk, his pencil having been vaporised long ago. "Surely this isn't on the exam. What kind of econ is this?"
|
||||||
|
|
||||||
|
"Be sure to remember this, because it'll be a major part of your midterm!"
|
||||||
|
|
||||||
|
Atras pats his head consolingly.
|
286
content/stories/shorts/illusion-of-solitude.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
title: "Illusion of Solitude"
|
||||||
|
date: 2023-12-29
|
||||||
|
tags:
|
||||||
|
- birdseye
|
||||||
|
- nanowrimo
|
||||||
|
- featured
|
||||||
|
---
|
||||||
|
|
||||||
|
**Summary:** An old bluebird takes her grandson human-watching in Vancouver. This time, they find a young child dealing with grown-ups who just don't understand.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep to the left. Mind the seagulls. We're approaching the coast. Those bastards appear far too often. One of the only reasons why there isn't a permanent bluebird population in Vancouver.
|
||||||
|
|
||||||
|
We've almost reached our destination. I suppose we could fit _one_ more experiment here. Reach out to one last human. For this last one, how about you pick? You've seen me do it a couple of times. If you need assistance, I'll be right here.
|
||||||
|
|
||||||
|
That building? That could be anything. If I were to guess, judging by the number of residential buildings in the area, that might be a school. Good timing. School should be in session by this time in the morning.
|
||||||
|
|
||||||
|
Ah, these are younger children. Perhaps no older than two hundred times your age. An excellent choice, any of these. I don't believe we've watched any humans so young. It'll be more difficult, though. Usually you see their parents hovering around them when they're so young. Perhaps not at school, but once outside the compound, humans become fiercely protective of their young. Like no other species I have ever seen.
|
||||||
|
|
||||||
|
These particular children over here playing outside. Mind the adult. If he sees you, he'll chase you away and keep a more careful eye out. They must be in recess from learning.
|
||||||
|
|
||||||
|
Which one do you like the most? The one talking to the wall in the corner away from all of the other children? You have good taste.
|
||||||
|
|
||||||
|
"Zoe, do you want a cup of tea? Here. I made it nice and cold for you. Do you like it?"
|
||||||
|
|
||||||
|
She is truly talking to the wall with nothing in her hands. Is the wall named Zoe?
|
||||||
|
|
||||||
|
"Ruth, are you talking to nothing again? Why don't you play with the rest of the kids?"
|
||||||
|
|
||||||
|
"Mrs. Pernumble, I'm having a tea party with Zoe! And _you're_ not invited. So go away."
|
||||||
|
|
||||||
|
"Right. Zoe. Your…"
|
||||||
|
|
||||||
|
"My friend. She's waving at you. Oh — be careful, Zoe! You'll spill the tea if you wave your hand like that."
|
||||||
|
|
||||||
|
I do not see a child waving with a cup of tea.
|
||||||
|
|
||||||
|
"I…see." As a trained expert in recognising human patterns and mannerisms, I, Elizabeth von Turdidae, can attest that the woman is lying. Kindly lying, but lying nonetheless.
|
||||||
|
|
||||||
|
"Surely Zoe wouldn't mind if you spent a few recesses playing with Jeremy and Sheila. Doesn't it sound fun to play soccer? They're all laughing in the field. It sounds to me like they're having the time of their lives!"
|
||||||
|
|
||||||
|
Children are such mysterious creatures. This one might be staring daggers into the woman. "I can't leave Zoe here. She doesn't like soccer. And she gets scared without me. And I haven't finished my cup of tea!"
|
||||||
|
|
||||||
|
I would like to re-attest that there is, in fact, a lack of any tea or anything resembling tea in the immediate vicinity.
|
||||||
|
|
||||||
|
"I could keep Zoe company. She's got a lot of questions for me, right? So perhaps we could have a conversation while you play soccer."
|
||||||
|
|
||||||
|
"I don't think so, Mrs. Pernumble."
|
||||||
|
|
||||||
|
"Why not?"
|
||||||
|
|
||||||
|
"Because you haven't said a word to her and now she's very upset. It's okay, Zoe. I'll never leave you. Have another cup of tea."
|
||||||
|
|
||||||
|
I know that I'm getting along in years, but I _do not_ see a human child. Several worm children that look very tasty, yes, but no human child. Or tea. Do you see a human child?
|
||||||
|
|
||||||
|
Where is it?
|
||||||
|
|
||||||
|
Interesting. Perhaps this is a special human child. I have not known any human child that could hide themselves from other people, but humans still have plenty of surprises in them yet.
|
||||||
|
|
||||||
|
…But where's the tea?
|
||||||
|
|
||||||
|
"Oh, Ruth. Please. I'm sure the other kids would love to play with you. Can I tell you a little secret?"
|
||||||
|
|
||||||
|
"La la la I can't hear you. Can you hear a Mrs. Pernumble talking, Zoe? Yeah, me neither. Oh, thank you for the biscuit! Let me refill your tea again. You're a fast drinker!"
|
||||||
|
|
||||||
|
Heh. If there's one thing that is common between humans and bluebirds, children exasperating elders is universal across species.
|
||||||
|
|
||||||
|
Although I certainly wouldn't sigh and walk away like her. No, misbehaving children must be punished so they don't do it again.
|
||||||
|
|
||||||
|
"Thank you for the biscuit, Zoe. Have you heard from Miss Piggy recently? I heard that Miss Froggy and her had the most delicious of teas the other day. Mm. You're right. That _is_ very interesting."
|
||||||
|
|
||||||
|
I must admit, it is rather strange to watch a conversation happening with a human I cannot perceive in any way. I hope you're learning more than I am.
|
||||||
|
|
||||||
|
"Um, Ruth?"
|
||||||
|
|
||||||
|
That sigh does not belong on a child her age. How adorable — perhaps she thinks that if she's as petulant as one, others will see her as an adult.
|
||||||
|
|
||||||
|
"I'm _so_ sorry, Zoe. I know you don't like it when our tea parties get interrupted. What _is_ it, Peter?"
|
||||||
|
|
||||||
|
"Who are you talking to?"
|
||||||
|
|
||||||
|
"Zoe, of course. My best friend. She says hi, by the way. Now, get to the point. What do you want?"
|
||||||
|
|
||||||
|
Ha! Another victim of Zoe's invisibility! He looks so confused — oh, my sides.
|
||||||
|
|
||||||
|
"Mrs. Pernumble said that maybe you wanted to play with me and Alfred over there? We're playing cards."
|
||||||
|
|
||||||
|
"Oh. She — um. I… I can't. Zoe needs me to keep her company. She doesn't like it when I leave her alone."
|
||||||
|
|
||||||
|
"Who? I don't see her."
|
||||||
|
|
||||||
|
"I _told_ you, Zoe's my best friend. It's not _her_ fault that you can't see her."
|
||||||
|
|
||||||
|
That hesitant wave — the worm on top! Glorious. I might have to start observing the younger humans more. They're such a racket! Two inexperienced creatures learning about the world.
|
||||||
|
|
||||||
|
"Are you sure she's real?"
|
||||||
|
|
||||||
|
"Of _course_ she's real! Say sorry to Zoe, right now! I can't believe you'd say that. I'm sorry, Zoe. He didn't mean it. He just doesn't know better. Right?"
|
||||||
|
|
||||||
|
"Uh, I'm sorry!"
|
||||||
|
|
||||||
|
"Zoe's over _here_. Look at her properly when you say sorry."
|
||||||
|
|
||||||
|
"…Sorry."
|
||||||
|
|
||||||
|
"Hmph. She says she forgives you this time."
|
||||||
|
|
||||||
|
"If she wants, she can play with us too?"
|
||||||
|
|
||||||
|
"Nah. Zoe doesn't like cards."
|
||||||
|
|
||||||
|
"Okay then. If she doesn't want to. Bye bye."
|
||||||
|
|
||||||
|
"Bye bye."
|
||||||
|
|
||||||
|
My scientific mind is racing at the possibilities. The child couldn't see her. The human couldn't see her. I can't see her. What are the criteria for who can and cannot see Zoe?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"Be sure to finish the letters worksheet — I expect to see perfect 'A's from everyone! Now, children, fetch your bags — it's time to go home!"
|
||||||
|
|
||||||
|
The chatter of children after school is the same regardless of their age. It's always so relaxing to immerse oneself in the wave of conversations taking place left and right.
|
||||||
|
|
||||||
|
"Sheila, Sheila, are you still coming to my house after school?"
|
||||||
|
|
||||||
|
"I got this super cool fire truck for my birthday! Leo, you gotta bring your cars!"
|
||||||
|
|
||||||
|
"Nooo! My mom grounded me. You have to beat the boss without me, Dylan! You have to!"
|
||||||
|
|
||||||
|
"Come on, everyone. Less talking, more moving! Once you've changed into your outdoor shoes, line up against the wall outside the classroom!"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Well done, child. You're following your humans quite well. I barely had to help you at all.
|
||||||
|
|
||||||
|
"How was school, honey? Did you learn a lot of new things?"
|
||||||
|
|
||||||
|
"We learned about the alphabet! I can draw lots of little 'A's now. Today we learned about the big 'A'."
|
||||||
|
|
||||||
|
"Hey! It's my favourite kiddo! Ruth! Ruthie. Beat up any boys today?"
|
||||||
|
|
||||||
|
"Zoe almost. Hmph. Peter was being mean to Zoe. I feel so bad for Zoe, daddy. No one understands her and they're all so…so mean to her!"
|
||||||
|
|
||||||
|
"There's cookies and juice for you on the dining table, Ruth. Make sure to wash your hands."
|
||||||
|
|
||||||
|
"Aw, that sucks. Does Zoe want a glass of orange juice, too? Maybe it'll help her feel better?"
|
||||||
|
|
||||||
|
"Daddy! Zoe is _allergic_ to orange juice, remember? I told you _yesterday!_ She'll have her unicorn hair tea like she always does. Mommy, can I eat my cookies upstairs? Me and Zoe are having a tea party again."
|
||||||
|
|
||||||
|
"All right, but don't spill any of the crumbs! We don't want ants moving into our house, don't we?"
|
||||||
|
|
||||||
|
Oh! How unfortunate. Child, I must interject — allergies are new to you, yes? Bluebirds don't particularly have the horrid disease. If some humans — and it's impossible to tell which humans before they actually eat it — eat certain foods, like peanuts! Or fruits, or nearly anything — they could _die_. That's called an allergic reaction.
|
||||||
|
|
||||||
|
To have an allergy toward a fruit — truly a pitiable existence it must be.
|
||||||
|
|
||||||
|
"Tea party time! Zoe, here is your unicorn hair tea. Freshly stirred! Only the best for my best friend. Oh, thank you! Yes, I'm doing great. Can you help me with my homework after this? You're always so good at drawing those weird lines. Mrs. Prenumble says that they're part of letters, which are part of words.
|
||||||
|
|
||||||
|
"Zoe… Why does everyone hate you so much? They never want to talk to you. Even Mrs. Prenumble. They all pretend that you're not a _real_ person. But don't worry. _I_ know that you're real. No matter what everyone else says. You're my best friend, right? I can't leave you alone. If you want, I'll stay with you, forever and ever!
|
||||||
|
|
||||||
|
"Will you also stay with me, forever and ever?"
|
||||||
|
|
||||||
|
Human children. So adorable! Already forming pacts for life in the tender young stage of pre-adolescence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Finally some change. Pardon me, but I can only listen to a child have a multi-hour tea party go on for so long before even _my_ patience wanes. Let us investigate the commotion at the door. Ooh. An older boy. A family! Human families are so small. Their babies don't die nearly as often as bluebird babies, but there are just so few of them in the world. I suppose that explains the parents' protectiveness. But if they want their children to succeed, why don't they simply have more children? That would maximise the probability of successful children reproducing in the future.
|
||||||
|
|
||||||
|
"Mom, dad, I'm home."
|
||||||
|
|
||||||
|
"Welcome home, Jacob! Dinner will be ready in just a minute. Could you call Ruth downstairs from her room?"
|
||||||
|
|
||||||
|
"Hey, kiddo! How was school? Beat up any girls today?"
|
||||||
|
|
||||||
|
"Oh my god, you can't _do_ that, dad!"
|
||||||
|
|
||||||
|
"Hmph. All these fancy schmancy new norms are stifling. Back in my day, you could hit whoever you wanted, and the other guy would just hit you back! None of this 'no hitting girls' nonsense."
|
||||||
|
|
||||||
|
I knew that humans discriminated between their sexes, but I didn't realise that they couldn't hit females. All kerfuffle and waffle, if you ask me. If someone has wronged you in any way, giving them a good knock in the old noggin is always an excellent first move. Really sets the atmosphere for a full-out fight.
|
||||||
|
|
||||||
|
"They're weaker, so it's not fair. They can't hit back, so it's up to us men to show other men who's boss."
|
||||||
|
|
||||||
|
Nonsense! Excuse _me?_ I don't know about _humans_, but female _bluebirds_ pack a pretty punch in their wings. It's true that males usually only fight males and females usually only fight females — because, let's be honest, why would a female ever need to fight a male? — but I did not realise that there was _no_ cross-sex combat in human world. Strange, too. I could have sworn that I'd seen human males fighting human females multiple times over my many years.
|
||||||
|
|
||||||
|
"Son, maybe the times are changing. And I respect that. But no matter what happens, never lose that fighting spirit. Can't fight the girls? Fine. There's still a good half of the population you can beat some sense into. Now, if they say that _fighting is bad_ and that _no one should do it_ or whatever? That's when you put your fist down and screw the rules. Let no one tell you what you can or can't do."
|
||||||
|
|
||||||
|
"I'll do my best, dad!"
|
||||||
|
|
||||||
|
"I know you will, son. Make me proud. Nothing like a good fist to the noggin to help loosen any lips."
|
||||||
|
|
||||||
|
Wonderful parenting. _This_ I can get behind one hundred percent. I know I have a lot of negative things to say about humans, but in reality, they're so diverse that there are so many different viewpoints to learn from. There are some that are objectively wrong, some that are objectively right, and then most are in the middle. It's rather refreshing to have one's opinions validated every once in a while.
|
||||||
|
|
||||||
|
And what camaraderie! Certainly bluebird children are never so close to their parents. Or their grandparents, for that matter.
|
||||||
|
|
||||||
|
"Now bring Ruth downstairs like your mom said, will you?"
|
||||||
|
|
||||||
|
"Yessir! Ruth!"
|
||||||
|
|
||||||
|
"You might have to go upstairs to bring her down. I think she said that she was having a tea party with Zoe again."
|
||||||
|
|
||||||
|
"Zoe? Ugh. How long are we gonna entertain her, dad?"
|
||||||
|
|
||||||
|
"As long as she wants."
|
||||||
|
|
||||||
|
"Hmph. Ruth! Open up! Mom says it's time for dinner!"
|
||||||
|
|
||||||
|
I wonder why she isn't responding. Drat! If only we could be invisible and incorporeal like Zoe must be, so we could position ourselves _in_ the door and see both sides of the conversation. Having to move around to the back of the house just to see the hallway is _so_ irritating.
|
||||||
|
|
||||||
|
"Ruth! If you don't say anything, I'm coming in in three…two…"
|
||||||
|
|
||||||
|
"I'm having a tea party! Go away, Jacob."
|
||||||
|
|
||||||
|
My. She sounds aggravated. What kind of dirt landed on her head while we were away?
|
||||||
|
|
||||||
|
"I'm not gonna wait here for you to finish your prissy tea party. I'm comin' in!"
|
||||||
|
|
||||||
|
This is actually quite nice. It means if we circle around to the other side of the house, we should get a nice and proper view of everything that is happening.
|
||||||
|
|
||||||
|
"HEY! I didn't say you could come in! Zoe doesn't like you, you know. She says that you're big and fat and dumb and stupid for interrupting her tea party. Get out."
|
||||||
|
|
||||||
|
"Oi. I just came to pass on a message. Why do you have to get so pissy?"
|
||||||
|
|
||||||
|
"This is _my_ room." How funny. Ruth trying to push Jacob out of her room. Certainly it isn't happening unless it's under his own volition. Valiant effort, though. I would applaud like the humans do if I had hands. "Can't you read the sign? It says NO JACOBS!"
|
||||||
|
|
||||||
|
"Do you even know how to read?"
|
||||||
|
|
||||||
|
"Shut _up!_ Zoe said that you can't be here, and that means you can't be here! Zoe needs her time to herself. She's has a very tiring day and she needs her tea and biscuits —"
|
||||||
|
|
||||||
|
"Zoe this and Zoe that. Jeez, don't you have any _real_ people to back you up?"
|
||||||
|
|
||||||
|
Oh. That one might have hit her harder than him slamming the door open. What is that expression on her face, I wonder?
|
||||||
|
|
||||||
|
"Yeah? Don't have anything to say, dontcha? I wonder why?"
|
||||||
|
|
||||||
|
"Say sorry to Zoe right now. She's my _best friend."_
|
||||||
|
|
||||||
|
"Oh my god, Ruth. How are you still so stuck on her? Zoe's not real! She's in your _mind!_ She's one of those _imaginary friends!"_
|
||||||
|
|
||||||
|
Certainly not. I find it rather unlikely that the human adults and the human children spoke to her if she wasn't real. That Mrs. Pernumble or whatever it was wanted to have a conversation with her, too.
|
||||||
|
|
||||||
|
However, judging by the blankness of Ruth's face, she might believe him.
|
||||||
|
|
||||||
|
"No."
|
||||||
|
|
||||||
|
"Yes." Rather gleeful of him. "You're _imagining_ her. Probably because no one likes you enough for you to have _real_ friends."
|
||||||
|
|
||||||
|
"That's not true. Shut _up._"
|
||||||
|
|
||||||
|
"Oh, yeah? What's her favourite colour?"
|
||||||
|
|
||||||
|
"Yellow!"
|
||||||
|
|
||||||
|
"What's her favourite food?"
|
||||||
|
|
||||||
|
"Unicorn hair tea with rainbow biscuits!"
|
||||||
|
|
||||||
|
An eyebrow raised. In his defense, I didn't think that those were real either. But it makes sense to me. What else would an invisible person eat? Oh! It just clicked for me. That must be _why_ she's invisible! All of these exotic ingredients must have an effect on the body that makes her difficult to perceive. And Ruth must be consuming enough that she can perceive her but not turn invisible herself.
|
||||||
|
|
||||||
|
My cleverness and intelligence scare me sometimes. But I digress.
|
||||||
|
|
||||||
|
"What's her last name?"
|
||||||
|
|
||||||
|
"It's! Um…"
|
||||||
|
|
||||||
|
"Where does she live?"
|
||||||
|
|
||||||
|
"I… She must have told me…" A fearful glance to her right. Is she losing the fight? Come on, how difficult could it be to prove that a human exists?
|
||||||
|
|
||||||
|
"Exactly. What does she do when she's not with you?"
|
||||||
|
|
||||||
|
"…Stop it…" Hm. Personally, I would prefer physical combat over verbal combat. Not nearly as emotionally messy. Much easier to know when the damage is done and when they've learned their lesson. Most importantly — much easier to restrain oneself. I suppose she's been thoroughly beaten. Her father will be proud — Jacob finally beat up a girl.
|
||||||
|
|
||||||
|
"When did you meet her?"
|
||||||
|
|
||||||
|
Oh?
|
||||||
|
|
||||||
|
"When grandmama went home! She's _real_, Jacob! I _know_ I met her. She was outside on the swing outside grandmama's house when the sun was setting and I was sad and we went on the swing together and we were friends ever since. Zoe's _real!"_ And here comes the crying. If she was sobbing before, she's bawling now.
|
||||||
|
|
||||||
|
Hm? Why isn't he saying anything else? Are tears so effective against physical and verbal combat? He seems shocked. How could this be surprising? Bluebirds meet new bluebirds all the time. Is this not true for humans?
|
||||||
|
|
||||||
|
"Jacob. Come over here, please." Oh, my. All of the tension in the air and I didn't even notice the mother in the doorway.
|
||||||
|
|
||||||
|
"Oh! Um. Mom, I can explain. I was just…"
|
96
content/stories/shorts/my-precious.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
title: "My Precious"
|
||||||
|
date: 2024-02-05
|
||||||
|
tags:
|
||||||
|
- shorts
|
||||||
|
- psychological
|
||||||
|
- featured
|
||||||
|
---
|
||||||
|
|
||||||
|
**Summary:** Max tries to save his dog, but it's not enough.
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
**A/N: we take some creative liberties re: symptoms of cancer / its rate of progression!**
|
||||||
|
|
||||||
|
The day you got your dog was the happiest day of your life. Your dog didn't yell at you whenever you delivered a pizza five minutes late. Your dog never complained when you didn't add enough sugar to her coffee. No matter how you complained over and over again how you weren't getting enough sleep, Susie was always there at the end of the day to greet you, a welcome bark and an expectant gaze ready for treats and a run. For every high and every low, your little bundle of joy was right by your side.
|
||||||
|
|
||||||
|
The day your dog became sick was the saddest day of your life.
|
||||||
|
|
||||||
|
The first signs were small: she didn't eat as much, and you noticed that she shed more fur than usual. But she was still the same playful golden retriever you knew.
|
||||||
|
|
||||||
|
Out of an abundance of caution, you decided to get her checked out at your local vet.
|
||||||
|
|
||||||
|
"There's a cancerous tumour at the base of her right lung." The vet shakes her head, her tone grave. You cast a worried glance at Susie's sleeping form on the examination table. "I'm sorry, Mr. Haltmann. You're lucky you caught it this early, but it's growing fast. My diagnosis is that it'll become terminal in a few months."
|
||||||
|
|
||||||
|
Your mouth dries. "What can I do?"
|
||||||
|
|
||||||
|
The vet taps her clipboard. "She'll need surgery. I can direct you to a place, but the cost can be upwards of five thousand dollars."
|
||||||
|
|
||||||
|
"Anything for Susie," you say, shaking your head. You're already crunching the numbers, but at the very least, you'll have to scrimp and save and start working a third job to save her. But it's worth it.
|
||||||
|
|
||||||
|
All of this is for Susie, the beloved dog who kept you company for the last four years.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"I suppose that that's settled, Mr. Haltmann. Welcome to Hotel Trivago. You'll be starting Monday."
|
||||||
|
|
||||||
|
"Thank you!" Your cheeks hurt from the grin you're restraining from spreading across your face, but you keep it professional long enough to shake your interviewer's hand. As you leave the building, there's a bounce in your step and you're humming that pop song you heard over the radio in your eleventh-hand car.
|
||||||
|
|
||||||
|
You trip over a bag of dog food and end up skidding to a stop before Susie's lethargic figure. "I promised you, Susie," you whisper in her ear. "I got a job. And that means that we're on our way there." You squeeze her paws. Susie lifts her head and tilts it at you, before she sets it down again, closing her eyes.
|
||||||
|
|
||||||
|
"Hang on just a little longer, please."
|
||||||
|
|
||||||
|
The hotel housekeeping work pays you $500, after which you'll be one-tenth of the way there. You haven't fallen over yet, although juggling four part-time jobs at once is still a struggle.
|
||||||
|
|
||||||
|
All of this is for Susie, your life and joy that kept you going for so long. You can't wait until the day you can play fetch with her once again.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
It's not enough, you realise. You need more. You read online that taking care of yourself was most important, no matter what you might be going through, but you can't take your mind off of Susie. It's with a heavy heart that you make sure to feed yourself a nutritious meal every day while you know how much she's suffering.
|
||||||
|
|
||||||
|
A customer at the coffee shop you work at mentioned that tech is the place to be for veterans and newcomers alike, with plenty of jobs at many different skill levels. It'll be a time investment. Time that you might not have, but it's your best shot.
|
||||||
|
|
||||||
|
After you come home from your cashier gig at Walmart every evening, you go straight to the library and learn web development until closing time.
|
||||||
|
|
||||||
|
All of this is for Susie, your lovely little cat that needs a new leg. You can't wait to see her climbing up everywhere and gleefully shredding furniture with her claws once again.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"Already?" your manager says, her brow furrowed as she squints at your resignation papers. "You've only been working here for a year. You've been doing good work, too. Bradley hardly ever complains about your code."
|
||||||
|
|
||||||
|
"Sorry, Joyce. I got an offer from Google. They're paying me triple what you guys do, and I need the money." Even though you didn't get along too well with your colleagues, you feel a twinge of regret in your gut. These guys treated you well, and you learned so much. You almost feel ready for the big step.
|
||||||
|
|
||||||
|
She sighs, setting down the documents. "I understand. Software engineers," she muses. "They all come and go like the wind. Always chasing the rainbow. We're going to need you to document your work these last two weeks. Bradley'll take point for the knowledge transfer."
|
||||||
|
|
||||||
|
"I appreciate it, Joyce." Internally, you let out a sigh of relief. She took it better than you'd expected. You thought the crabby woman would have tried to hold you back. Hope blossoms in your chest for the future as you make your way up in the world.
|
||||||
|
|
||||||
|
All of this is for Susie, your estranged daughter who left you until you could fix your life. You can't wait to see her expression when she sees who you've become.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
She doesn't move. You stare ever more intently at her still form, waiting. It's all out of your hands now. If your calculations were correct, then…
|
||||||
|
|
||||||
|
"Mr. Haltmann, sir! Mr. Haltmann! I have urgent news!" Your office door clatters open. The glass walls around you let the midday sun shine into the hallway, illuminating a young man waving a sheaf of documents.
|
||||||
|
|
||||||
|
You chuckle. "Mr. Borparner, all news is urgent to the right ears. But do go on. What is it?"
|
||||||
|
|
||||||
|
"Their board of directors agreed! They're willing to sell you NVIDIA at market valuation, sir — I can't believe it!"
|
||||||
|
|
||||||
|
You lace your fingers together, resting them on the mahogany desk before you. "Excellent. Prepare a press release right away. I want the deal finalised before the day ends."
|
||||||
|
|
||||||
|
"Yes, sir! Right away, sir!"
|
||||||
|
|
||||||
|
The door slams shuts behind Borparner. You return to the chessboard sitting on your desk in front of you. The opposing queen wobbles, then falls over, knocking over the king in the process. "I suppose that that's over, then."
|
||||||
|
|
||||||
|
All of this is for Susie Inc, your heart and soul. You can't wait to see how her stock will jump once the trade opens tomorrow morning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
It's midday when you return to your empty house. You should buy a new house. Move somewhere nicer. This one holds a lot of sentimental value to you, but you can afford better now. You deserve better now. It holds a lot of sentimental value to you, but you can only fiddle with the keyhole so many times before you replace it. Day after day, the wasted time adds up.
|
||||||
|
|
||||||
|
A voice from the lawn next door calls out to you. "Max! Heya, neighbour. I heard you were raising money for your dog. How's she doing?"
|
||||||
|
|
||||||
|
You stare at her blankly. "What dog?"
|
||||||
|
|
||||||
|
The key finally clicks, letting you inside. Absently, you kick a persistently heavy towel away. It's starting to smell, too. How long has it been since you last did laundry? You tell your new servants to clean it up.
|
134
content/stories/shorts/nano2022-14.md
Normal 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.
|
38
content/stories/shorts/transition-to-universe-city.md
Normal 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
|
59
content/stories/shorts/trident.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: "A Triden(t) Against the World"
|
||||||
|
date: 2024-02-29
|
||||||
|
tags:
|
||||||
|
- shorts
|
||||||
|
- "content warning: political fluff"
|
||||||
|
- horror
|
||||||
|
- u.s. politics
|
||||||
|
---
|
||||||
|
|
||||||
|
**Disclaimer: This was mentally painful to write and I hope equally repulsive to read.**
|
||||||
|
|
||||||
|
"Is it yet another left-wing liberal conspiracy, or has President Trump finally taken back control of the White House? His unannounced visit is a show of strength against the authoritarian measures the Biden administration has imposed upon us all."
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"President Biden shows his patience in entertaining former President Trump upon his unauthorised entry to the White House. Reliable sources tell us that the two are discussing the change and continuity in their policies toward China."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Two men stared at each other from opposite sides of the Oval Office.
|
||||||
|
|
||||||
|
"Donald." The man at the desk stood, moving to the front of the desk.
|
||||||
|
|
||||||
|
"Joe," the other man said coolly. "It's been a while."
|
||||||
|
|
||||||
|
A pause.
|
||||||
|
|
||||||
|
"Darn right it has!" Joe grinned. "Come here, Donald!" he said, spreading his arms. "I can't believe it's really you!"
|
||||||
|
|
||||||
|
It took just three large strides before Donald was wrapping his arms around Joe, laughing. They held each other for nearly a minute, doing nothing but basking in the other's touch after so long.
|
||||||
|
|
||||||
|
Eventually, Joe pulled back, leaving his hands on Donald's shoulders, staring proudly into his eyes. Donald was the first to break eye contact. "I see you haven't changed the room much," he said. "Why don't you tell me what you've been up to?" He sat and patted the cushion beside him.
|
||||||
|
|
||||||
|
Joe chuckled, capitulating by plopping down by the armrest. "Two years in and you're acting like you own the place," he said, scooching over to press closer to Donald.
|
||||||
|
|
||||||
|
Donald waggled his bushy blonde eyebrows. "I *did* own the place."
|
||||||
|
|
||||||
|
Joe's face shadowed, and Donald pulled him closer when he noticed. "I missed you. The real you. You don't have to keep it up anymore, you know." Joe shook his head. "You've done it. It's over."
|
||||||
|
|
||||||
|
"Eight years," Donald said solemnly. "I promised that I would give you eight years. I'll run in 2024. It'll anger half the country just enough to let you clinch another term."
|
||||||
|
|
||||||
|
"You don't have to," Joe argued. "Kamala already said that she'd find someone else to be VP. I know how much effort you've put in for me already."
|
||||||
|
|
||||||
|
"Oh, my little robin." said Donald affectionately, patting Joe's head. "You know that's not going to be enough to convince them."
|
||||||
|
|
||||||
|
Joe made a face at the pet name, but he leaned into the touch all the same. "You know I hate it when you call me that," he groaned. A smile tugged at his lips nonetheless. "I wish we could be like this forever."
|
||||||
|
|
||||||
|
"I wish we could, too." Donald sighed forlornly. He ran his fingers through Joe's white locks. "But you know what the media's like. They're already on my case about today. By the way, are you using a new shampoo?"
|
||||||
|
|
||||||
|
"You noticed?" Joe said, pleased. "I smelled it off of this high schooler from Iowa. It reminded me of you. What do you think?"
|
||||||
|
|
||||||
|
"Keep it," Donald decided. "The chamomile smells so soothing."
|
||||||
|
|
||||||
|
They sat there, leaning against each other, simply enjoying the shared warmth of the other's presence. Finally, Joe spoke up. "Want to come up with a cover story together?"
|
||||||
|
|
||||||
|
Donald rubbed his hands together. "You're on. I'm thinking of asking Pingping to cover for us with a trade deal or two…"
|
@@ -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,\
|
||||||
|
41
content/stories/unstagnation/2020/bienvenue-au-ciers.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: "Bienvenue au Ciers!"
|
||||||
|
date: 2020-06-17
|
||||||
|
tags:
|
||||||
|
- barin
|
||||||
|
- unstagnation
|
||||||
|
---
|
||||||
|
|
||||||
|
_“Vous êtes maintenant à la station d’Escribe. C’est la dernière station de la ligne de train Ciers–Xunil._
|
||||||
|
|
||||||
|
_“Les portes vont ouvrir: à gauche._
|
||||||
|
|
||||||
|
_“Veuillez vous tenir à l’écart des portes.”_
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Bienvenue au Ciers! Veuillez donc vous assurer que vous avez l’une des documents suivantes:
|
||||||
|
|
||||||
|
- Si vous êtes un résident permanent ou un citoyen de Ciers, vous devez avoir votre étiquette d’identification de Ciers.
|
||||||
|
|
||||||
|
- Autrement, si vous êtes un citoyen d’une 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 d’une tribu de l’Alliance Énigma, vous devez avoir le document de voyage FE1-D vous issue par votre tribu.
|
||||||
|
|
||||||
|
- Autrement, si vous êtes un citoyen d’une tribu de l’Alliance 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!
|
@@ -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!"
|
||||||
|
@@ -4,6 +4,7 @@ date: 2020-06-25
|
|||||||
tags:
|
tags:
|
||||||
- barin
|
- barin
|
||||||
- unstagnation
|
- unstagnation
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
|
|
||||||
It is — unethical and *deeply* immoral to block our people from knowledge. Is Leeco not a free tribe? Do we not accept those in pursuit of information? We cannot block our citizens from learning more about the universe to further the human race. Our tribe was founded on the principle of helping each other learn and grow by education. We cannot learn if we never challenge our beliefs, no matter how deep their roots lie — you might remember how ingrained racism was in Leeco so many decades back — and we cannot grow as a society if we resist change! If we do not rapidly adapt to the world, the world will rapidly adapt around us. And that is unacceptable.
|
It is — unethical and *deeply* immoral to block our people from knowledge. Is Leeco not a free tribe? Do we not accept those in pursuit of information? We cannot block our citizens from learning more about the universe to further the human race. Our tribe was founded on the principle of helping each other learn and grow by education. We cannot learn if we never challenge our beliefs, no matter how deep their roots lie — you might remember how ingrained racism was in Leeco so many decades back — and we cannot grow as a society if we resist change! If we do not rapidly adapt to the world, the world will rapidly adapt around us. And that is unacceptable.
|
||||||
|
@@ -4,6 +4,7 @@ date: 2020-06-19
|
|||||||
tags:
|
tags:
|
||||||
- barin
|
- barin
|
||||||
- unstagnation
|
- unstagnation
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
|
|
||||||
*Test 19/20 failed: Memory access violation.*
|
*Test 19/20 failed: Memory access violation.*
|
41
content/stories/unstagnation/2020/welcome-to-ciers.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
title: "Welcome to Ciers!"
|
||||||
|
date: 2020-06-14
|
||||||
|
tags:
|
||||||
|
- barin
|
||||||
|
- unstagnation
|
||||||
|
---
|
||||||
|
|
||||||
|
“_You are now at: Escribe Station. This is the last stop on the Ciers–Xunil Line._
|
||||||
|
|
||||||
|
_“The doors will be opening on the: left side._
|
||||||
|
|
||||||
|
_“Please stand clear of the doors.”_
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
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!
|
@@ -3,6 +3,7 @@ title: Wet Hair
|
|||||||
date: 2020-06-26
|
date: 2020-06-26
|
||||||
tags:
|
tags:
|
||||||
- unstagnation
|
- unstagnation
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
|
|
||||||
*Splash!*
|
*Splash!*
|
||||||
|
@@ -3,6 +3,7 @@ title: A Favour
|
|||||||
date: 2021-06-20
|
date: 2021-06-20
|
||||||
tags:
|
tags:
|
||||||
- unstagnation
|
- unstagnation
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
|
|
||||||
*Ring…ring…ring…*
|
*Ring…ring…ring…*
|
||||||
|
117
content/stories/unstagnation/2021/birds-censorship.md
Normal 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!"
|
135
content/stories/unstagnation/2021/birds-language-tea.md
Normal 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.
|
143
content/stories/unstagnation/2021/birds-posterior-acoustics.md
Normal 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*
|
110
content/stories/unstagnation/2021/birds-waffle-supremacy.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
title: "The Birds on Waffle Supremacy"
|
||||||
|
date: 2021-11-01
|
||||||
|
tags:
|
||||||
|
- birds
|
||||||
|
- unstagnation
|
||||||
|
- featured
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
@@ -4,6 +4,7 @@ date: 2021-08-19
|
|||||||
tags:
|
tags:
|
||||||
- ibia
|
- ibia
|
||||||
- unstagnation
|
- unstagnation
|
||||||
|
- featured
|
||||||
---
|
---
|
||||||
"…And never come back again, boy!"
|
"…And never come back again, boy!"
|
||||||
|
|
||||||
|
100
content/stories/unstagnation/2022/a-is-for-alcohol.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
title: "A is for Alcohol and IAs"
|
||||||
|
date: 2022-10-24
|
||||||
|
tags:
|
||||||
|
- birds
|
||||||
|
- unstagnation
|
||||||
|
- featured
|
||||||
|
---
|
||||||
|
|
||||||
|
**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.
|
@@ -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.
|
117
content/stories/unstagnation/2022/soups-egirl-discord-status.md
Normal 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.
|
@@ -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
|
|
@@ -7,7 +7,9 @@ export type Language =
|
|||||||
| "react"
|
| "react"
|
||||||
| "markdown"
|
| "markdown"
|
||||||
| "flutter"
|
| "flutter"
|
||||||
| "android";
|
| "android"
|
||||||
|
| "rust"
|
||||||
|
| "golang";
|
||||||
export interface Project {
|
export interface Project {
|
||||||
name: string;
|
name: string;
|
||||||
href: string;
|
href: string;
|
||||||
@@ -16,6 +18,7 @@ export interface Project {
|
|||||||
longDescription?: string;
|
longDescription?: string;
|
||||||
langs: Language[];
|
langs: Language[];
|
||||||
license?: "AGPL-3.0" | "GPL-3.0" | "MIT" | "LGPL-3.0";
|
license?: "AGPL-3.0" | "GPL-3.0" | "MIT" | "LGPL-3.0";
|
||||||
|
type: "web" | "tool" | "embedded" | "service";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const projects: Project[] = [
|
export const projects: Project[] = [
|
||||||
@@ -26,8 +29,9 @@ export const projects: Project[] = [
|
|||||||
"A comic downloader and converter to CBZ / EPUB / PDF for my Kobo.",
|
"A comic downloader and converter to CBZ / EPUB / PDF for my Kobo.",
|
||||||
longDescription: "Available via CLI and a Qt GUI!",
|
longDescription: "Available via CLI and a Qt GUI!",
|
||||||
langs: ["python"],
|
langs: ["python"],
|
||||||
license: "LGPL-3.0",
|
license: "AGPL-3.0",
|
||||||
img: "mandown.webp",
|
img: "mandown.webp",
|
||||||
|
type: "tool",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Noveldown",
|
name: "Noveldown",
|
||||||
@@ -35,8 +39,9 @@ export const projects: Project[] = [
|
|||||||
langs: ["python"],
|
langs: ["python"],
|
||||||
license: "LGPL-3.0",
|
license: "LGPL-3.0",
|
||||||
description:
|
description:
|
||||||
"A webnovel downloader and converter to EPUB for my Kobo, with lots of metadata!",
|
"A webnovel downloader and EPUB converter for my Kobo, with lots of metadata!",
|
||||||
longDescription: "Heavily borrows Mandown's design.",
|
longDescription: "Heavily borrows Mandown's design.",
|
||||||
|
type: "tool",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Jeopardy",
|
name: "Jeopardy",
|
||||||
@@ -46,6 +51,7 @@ export const projects: Project[] = [
|
|||||||
license: "AGPL-3.0",
|
license: "AGPL-3.0",
|
||||||
description: "Kahoot-inspired Jeopardy! game, including Final Jeopardy!",
|
description: "Kahoot-inspired Jeopardy! game, including Final Jeopardy!",
|
||||||
longDescription: "Created for Bayview's Computer Club.",
|
longDescription: "Created for Bayview's Computer Club.",
|
||||||
|
type: "web",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Primoprod",
|
name: "Primoprod",
|
||||||
@@ -56,15 +62,17 @@ export const projects: Project[] = [
|
|||||||
description:
|
description:
|
||||||
"A game simulator to increase productivity with quests and gambling.",
|
"A game simulator to increase productivity with quests and gambling.",
|
||||||
longDescription: "My first project with a JS framework!",
|
longDescription: "My first project with a JS framework!",
|
||||||
|
type: "web",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Eifueo",
|
name: "PillowⓇ",
|
||||||
href: "https://github.com/potatoeggy/eifueo",
|
href: "https://github.com/potatoeggy/ece198",
|
||||||
langs: ["markdown"],
|
description:
|
||||||
|
"A water quality statistics aggregator written for the STM32 microcontroller with a display and keypad.",
|
||||||
|
langs: ["rust"],
|
||||||
license: "GPL-3.0",
|
license: "GPL-3.0",
|
||||||
img: "eifueo.webp",
|
type: "embedded",
|
||||||
description: "A collection of rewritten notes to remember things better.",
|
img: "pillow.webp",
|
||||||
longDescription: "THIS IS NOT A TEXTBOOK.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Napbot",
|
name: "Napbot",
|
||||||
@@ -72,36 +80,9 @@ export const projects: Project[] = [
|
|||||||
langs: ["python"],
|
langs: ["python"],
|
||||||
license: "AGPL-3.0",
|
license: "AGPL-3.0",
|
||||||
description:
|
description:
|
||||||
"A Discord bot initially to track sleep hours as friendly competition but is now a local music bot with synchronised lyrics!",
|
"A Discord music bot with synchronised lyrics, originally a sleep tracking bot to encourage sleeping.",
|
||||||
img: "napbot.webp",
|
img: "napbot.webp",
|
||||||
},
|
type: "service",
|
||||||
{
|
|
||||||
name: "Resketch",
|
|
||||||
href: "https://github.com/anyuan-chen/resketch",
|
|
||||||
langs: ["typescript", "react"],
|
|
||||||
img: "resketch.webp",
|
|
||||||
description:
|
|
||||||
'A "reverse-Pictionary" where you compete to have an AI recognise your drawings.',
|
|
||||||
longDescription: "Written for YRHacks 2022.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Perdiem",
|
|
||||||
href: "https://github.com/anyuan-chen/perdiem",
|
|
||||||
langs: ["javascript", "react"],
|
|
||||||
license: "AGPL-3.0",
|
|
||||||
img: "perdiem.webp",
|
|
||||||
description:
|
|
||||||
"A pretty budget tracking app where I learned too much about server-side rendering.",
|
|
||||||
longDescription: "Written for StormHacks 2022.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "RecipeReady",
|
|
||||||
href: "https://github.com/christopherlam888/recipe-ready-frontend",
|
|
||||||
langs: ["python", "android", "flutter"],
|
|
||||||
img: "recipeready.webp",
|
|
||||||
description:
|
|
||||||
"Android app to automagically plan meals and prepare a shopping list so you don't have to.",
|
|
||||||
longDescription: "Written for Hack the North 2021.",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "AutoFicFare",
|
name: "AutoFicFare",
|
||||||
@@ -110,15 +91,38 @@ export const projects: Project[] = [
|
|||||||
license: "GPL-3.0",
|
license: "GPL-3.0",
|
||||||
description:
|
description:
|
||||||
"Automatically update fanfiction in a Calibre database to instantly update them on your Kobo.",
|
"Automatically update fanfiction in a Calibre database to instantly update them on your Kobo.",
|
||||||
|
type: "tool",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const unreleasedProjects: Project[] = [
|
||||||
|
{
|
||||||
|
name: "Aleister",
|
||||||
|
href: "https://github.com/potatoeggy/aleister",
|
||||||
|
langs: ["rust"],
|
||||||
|
license: "AGPL-3.0",
|
||||||
|
type: "service",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Website",
|
name: "Aoto",
|
||||||
href: "https://github.com/potatoeggy/public",
|
href: "https://github.com/potatoeggy/aoto",
|
||||||
description:
|
langs: ["golang", "typescript", "react"],
|
||||||
"This website! It's gone through three iterations before this one, and this one's the first to use a framework.",
|
|
||||||
langs: ["typescript", "vue"],
|
|
||||||
license: "AGPL-3.0",
|
license: "AGPL-3.0",
|
||||||
img: "public.webp",
|
type: "web",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Kobink",
|
||||||
|
href: "https://github.com/potatoeggy/kobink",
|
||||||
|
langs: ["rust"],
|
||||||
|
license: "AGPL-3.0",
|
||||||
|
type: "service",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GBARR",
|
||||||
|
href: "https://github.com/potatoeggy/gbarr",
|
||||||
|
langs: ["rust"],
|
||||||
|
license: "GPL-3.0",
|
||||||
|
type: "embedded",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
13
data/siteRevisions.ts
Normal 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" },
|
||||||
|
];
|
3
data/specialTags.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const SpecialTags: string[] = [
|
||||||
|
"featured",
|
||||||
|
];
|
@@ -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,36 @@ export const tagInfo: Record<string, TagData> = {
|
|||||||
description:
|
description:
|
||||||
"A collection of very short stories written to do something productive during June–August 2020 and August 2021.",
|
"A collection of very short stories written to do something productive during June–August 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.",
|
||||||
|
},
|
||||||
|
birdseye: {
|
||||||
|
name: "Bird's-Eye View",
|
||||||
|
description: "What's the world like to a pair of human-watching bluebirds?",
|
||||||
|
},
|
||||||
|
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?",
|
||||||
|
},
|
||||||
|
featured: {
|
||||||
|
name: "Featured",
|
||||||
|
description: "Works that are less rambly and more actually good!",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
export default tagInfo;
|
export default tagInfo;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
useHead({ title: "Eggworld" });
|
import { revisions } from "@/data/siteRevisions";
|
||||||
|
useHead({ title: "Oeufs?" });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -7,19 +8,38 @@ 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>
|
<label class="flex items-center gap-2">
|
||||||
|
<p>Revision:</p>
|
||||||
|
<!--
|
||||||
|
the onchange is so bad - i'd rather it be done through vue
|
||||||
|
but nuxt is genuinely screwing me over here
|
||||||
|
|
||||||
|
ig r4 has to be in next.js
|
||||||
|
-->
|
||||||
|
<select
|
||||||
|
class="p-2 border rounded-lg dark:bg-[#222]"
|
||||||
|
onchange="location = this.value"
|
||||||
|
>
|
||||||
|
<option v-for="(r, i) in revisions" :key="i" :value="r.url">
|
||||||
|
{{ r.title }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
<p>
|
<p>
|
||||||
Licensed under the AGPL-3.0 on
|
Licensed under the AGPL-3.0 on
|
||||||
<a class="underline" href="https://github.com/potatoeggy/public">
|
<a class="underline" href="https://github.com/potatoeggy/public">
|
||||||
GitHub</a
|
GitHub</a
|
||||||
>
|
>
|
||||||
and
|
and
|
||||||
<a class="underline" href="https://git.eggworld.tk/eggy/public">
|
<a class="underline" href="https://git.eggworld.me/eggy/public">
|
||||||
Gitea
|
Gitea
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-36"></div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<slot name="top-button" />
|
<slot name="top-button" />
|
||||||
@@ -29,7 +49,9 @@ useHead({ title: "Eggworld" });
|
|||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
color: black;
|
color: black;
|
||||||
transition: color 0.2s ease, background 0.2s ease;
|
transition:
|
||||||
|
color 0.2s ease,
|
||||||
|
background 0.2s ease;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
@@ -51,13 +73,20 @@ html::before {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* div#__nuxt {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
it's better if everything is sort of long but that is not the case
|
||||||
|
*/
|
||||||
|
|
||||||
html.dark::before {
|
html.dark::before {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 60rem;
|
max-width: 90ch;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<template>error!</template>
|
|
@@ -1,34 +1,15 @@
|
|||||||
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({
|
||||||
modules: [
|
app: {
|
||||||
"@nuxt/content",
|
|
||||||
"@nuxtjs/tailwindcss",
|
|
||||||
"@nuxtjs/color-mode",
|
|
||||||
"@nuxtjs/sitemap",
|
|
||||||
],
|
|
||||||
nitro: {
|
|
||||||
prerender: {
|
|
||||||
routes: ["/sitemap.xml"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
typescript: {
|
|
||||||
shim: false,
|
|
||||||
},
|
|
||||||
sitemap: {
|
|
||||||
hostname: process.env.BASE_URL || "https://eggworld.tk",
|
|
||||||
},
|
|
||||||
tailwindcss: {},
|
|
||||||
colorMode: {
|
|
||||||
classSuffix: "",
|
|
||||||
},
|
|
||||||
vite: {
|
|
||||||
plugins: [svgLoader()],
|
|
||||||
},
|
|
||||||
head: {
|
head: {
|
||||||
|
htmlAttrs: {
|
||||||
|
lang: "en",
|
||||||
|
},
|
||||||
meta: [
|
meta: [
|
||||||
{ name: "viewport", content: " width=device-width,initial-scale=1" },
|
{ name: "viewport", content: " width=device-width,initial-scale=1" },
|
||||||
|
{ 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" },
|
||||||
@@ -37,6 +18,43 @@ 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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modules: [
|
||||||
|
"@nuxt/content",
|
||||||
|
"@nuxtjs/tailwindcss",
|
||||||
|
"@nuxtjs/color-mode",
|
||||||
|
"@nuxtjs/sitemap",
|
||||||
|
],
|
||||||
|
css: ["@/assets/css/main.scss"],
|
||||||
|
nitro: {
|
||||||
|
prerender: {
|
||||||
|
routes: ["/sitemap.xml"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
shim: false,
|
||||||
|
},
|
||||||
|
site: {
|
||||||
|
url: process.env.BASE_URL || "https://eggworld.me",
|
||||||
|
},
|
||||||
|
sitemap: {
|
||||||
|
strictNuxtContentPaths: true,
|
||||||
|
},
|
||||||
|
tailwindcss: {},
|
||||||
|
colorMode: {
|
||||||
|
classSuffix: "",
|
||||||
|
},
|
||||||
|
vite: {
|
||||||
|
plugins: [svgLoader()],
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
documentDriven: false,
|
documentDriven: false,
|
||||||
@@ -79,8 +97,9 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
reactivityTransform: true,
|
sharedPrerenderData: true,
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
noScripts: true,
|
||||||
},
|
},
|
||||||
target: "static",
|
|
||||||
ssr: true,
|
|
||||||
});
|
});
|
||||||
|
29
package.json
@@ -7,21 +7,20 @@
|
|||||||
"preview": "nuxt preview"
|
"preview": "nuxt preview"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/content": "npm:@nuxt/content-edge@latest",
|
"@nuxt/content": "^2.12.0",
|
||||||
"@nuxtjs/color-mode": "^3.1.4",
|
"@nuxtjs/color-mode": "^3.3.2",
|
||||||
"@nuxtjs/sitemap": "^2.4.0",
|
"@nuxtjs/sitemap": "^5.1.0",
|
||||||
"@nuxtjs/tailwindcss": "^5.3.0",
|
"@nuxtjs/tailwindcss": "^6.11.4",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"nuxt": "npm:nuxt3@latest",
|
"@types/node": "^20.11.24",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"nuxt": "3.10.3",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
"reading-time": "^2.0.0-1",
|
"reading-time": "^2.0.0-1",
|
||||||
"rehype-katex": "^6.0.2",
|
"rehype-katex": "^7.0.0",
|
||||||
"remark-math": "^5.1.1",
|
"remark-math": "^6.0.0",
|
||||||
"sitemap": "^7.1.1",
|
"sass": "^1.71.1",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^5.3.3",
|
||||||
"unist-util-visit": "^4.1.0",
|
"vite-svg-loader": "^5.1.0"
|
||||||
"vite-svg-loader": "^3.4.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"dayjs": "^1.11.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
pages/404.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
useTitle("404 - Not Found", "You're lost!");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="prose dark:prose-invert max-w-3xl transition">
|
||||||
|
<h1 class="mb-1">404 - Not Found</h1>
|
||||||
|
<p>
|
||||||
|
You're lost! Don't worry, here's a link
|
||||||
|
<a href="/">back to the home page.</a>
|
||||||
|
</p>
|
||||||
|
</main>
|
||||||
|
</template>
|
@@ -1,16 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BlogParsedContent, StoryParsedContent } from "@/shared/types";
|
import type { AnyParsedContent } from "@/shared/types";
|
||||||
import { calcReadingTime, getPrettyDate } from "@/shared/metadata";
|
import { calcReadingTime } from "@/shared/metadata";
|
||||||
|
|
||||||
type GeneralParsedContent = BlogParsedContent | StoryParsedContent;
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
definePageMeta({
|
// definePageMeta({
|
||||||
layout: "withtop",
|
// layout: "withtop",
|
||||||
});
|
// });
|
||||||
|
|
||||||
// we're not using ContentDoc because i need control
|
// we're not using ContentDoc because i need control
|
||||||
const doc = await queryContent<GeneralParsedContent>(route.path).findOne();
|
const doc = await queryContent<AnyParsedContent>(route.path).findOne();
|
||||||
const type = route.path.startsWith("/stories")
|
const type = route.path.startsWith("/stories")
|
||||||
? "stories"
|
? "stories"
|
||||||
: route.path.startsWith("/blog")
|
: route.path.startsWith("/blog")
|
||||||
@@ -21,27 +19,26 @@ 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"
|
||||||
:dest="`/tags/${type}/${tag}`"
|
:dest="`/tags/${type}/${tag}`"
|
||||||
:key="index"
|
:key="index"
|
||||||
>
|
:name="tag"
|
||||||
{{ tag }}
|
/>
|
||||||
</Tag>
|
|
||||||
</div>
|
</div>
|
||||||
<ContentRenderer :value="doc" tag="article" class="pt-0 w-full">
|
<ContentRenderer :value="doc" tag="article" class="pt-0 w-full">
|
||||||
<template #empty>
|
<template #empty>
|
||||||
@@ -49,18 +46,32 @@ 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>
|
||||||
.container {
|
.container {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 72ch;
|
max-width: 80ch;
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.container {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container h1 {
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
transition: color 0.2s ease;
|
transition: color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
<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
|
||||||
const docs = await queryContent<BlogParsedContent>("/blog")
|
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()
|
||||||
|
.sort()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -16,6 +23,15 @@ 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"
|
||||||
|
:name="tag"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<PostPreviewCard
|
<PostPreviewCard
|
||||||
v-for="(post, index) in docs"
|
v-for="(post, index) in docs"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
@@ -2,14 +2,23 @@
|
|||||||
import Services from "@/components/index/services.vue";
|
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!");
|
||||||
|
|
||||||
|
const welcomeStrings = ["Welcome!", "Bienvenue!", "欢迎!"];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="flex flex-col items-center justify-around gap-8">
|
<main class="flex flex-col items-center justify-around gap-8">
|
||||||
<h1>Welcome!</h1>
|
<div class="flex flex-col items-center">
|
||||||
|
<HeaderLoop class="text-bitter font-bold" :strings="welcomeStrings" />
|
||||||
<p>What are you here to see?</p>
|
<p>What are you here to see?</p>
|
||||||
|
<p>
|
||||||
|
For my portfolio, please visit
|
||||||
|
<a class="underline" href="https://github.com/potatoeggy">GitHub</a>
|
||||||
|
instead.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex justify-around items-stretch w-full flex-wrap gap-x-8 gap-y-10"
|
class="flex justify-around items-stretch w-full flex-wrap gap-x-8 gap-y-10"
|
||||||
>
|
>
|
||||||
@@ -17,6 +26,7 @@ useTitle("Home");
|
|||||||
<StoryStatBox />
|
<StoryStatBox />
|
||||||
<CommitStatBox />
|
<CommitStatBox />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Services />
|
<Services />
|
||||||
<About />
|
<About />
|
||||||
</main>
|
</main>
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
<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
|
||||||
const docs = await queryContent<StoryParsedContent>("/stories")
|
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()
|
||||||
|
.sort()
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -16,6 +23,15 @@ 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"
|
||||||
|
:name="tag"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<PostPreviewCard
|
<PostPreviewCard
|
||||||
v-for="(story, index) in docs"
|
v-for="(story, index) in docs"
|
||||||
:key="index"
|
:key="index"
|
||||||
|
@@ -3,7 +3,7 @@ import { tagInfo, type TagData } from "@/data/tagInfo";
|
|||||||
import type { BlogParsedContent } from "@/shared/types";
|
import type { BlogParsedContent } from "@/shared/types";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
definePageMeta({ layout: "withtop" });
|
//definePageMeta({ layout: "withtop" });
|
||||||
|
|
||||||
const tag =
|
const tag =
|
||||||
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];
|
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];
|
||||||
@@ -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 + " Posts", 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"
|
||||||
|
@@ -3,7 +3,7 @@ import { tagInfo, type TagData } from "@/data/tagInfo";
|
|||||||
import type { StoryParsedContent } from "@/shared/types";
|
import type { StoryParsedContent } from "@/shared/types";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
definePageMeta({ layout: "withtop" });
|
//definePageMeta({ layout: "withtop" });
|
||||||
|
|
||||||
const tag =
|
const tag =
|
||||||
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];
|
typeof route.params.tag === "string" ? route.params.tag : route.params.tag[0];
|
||||||
@@ -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 + " Stories", 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"
|
||||||
|
24
public/images/langs/golang.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32.000001">
|
||||||
|
<g transform="translate(0 -1020.3622)">
|
||||||
|
<ellipse cx="-907.35657" cy="479.90009" fill="#384e54" color="#000" overflow="visible" rx="3.5793996" ry="3.8207953" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1" transform="scale(-1 1) rotate(-60.548)"/>
|
||||||
|
<ellipse cx="-891.57654" cy="507.8461" fill="#384e54" color="#000" overflow="visible" rx="3.5793996" ry="3.8207953" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1" transform="rotate(-60.548)"/>
|
||||||
|
<path fill="#384e54" d="M16.091693 1021.3642c-1.105749.01-2.210341.049-3.31609.09C6.8422558 1021.6738 2 1026.3942 2 1032.3622v20h28v-20c0-5.9683-4.667345-10.4912-10.59023-10.908-1.10575-.078-2.212328-.099-3.318077-.09z" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<path fill="#76e1fe" d="M4.6078867 1025.0462c.459564.2595 1.818262 1.2013 1.980983 1.648.183401.5035.159385 1.0657-.114614 1.551-.346627.6138-1.005341.9487-1.696421.9365-.339886-.01-1.720283-.6372-2.042561-.8192-.97754-.5519-1.350795-1.7418-.833686-2.6576.517109-.9158 1.728749-1.2107 2.706299-.6587z" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<rect width="3.0866659" height="3.5313663" x="14.406213" y="1035.6842" fill-opacity=".32850246" color="#000" overflow="visible" ry=".62426329" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<path fill="#76e1fe" d="M16 1023.3622c-9 0-12 3.7153-12 9v20h24c-.04889-7.3562 0-18 0-20 0-5.2848-3-9-12-9z" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<path fill="#76e1fe" d="M27.074073 1025.0462c-.45957.2595-1.818257 1.2013-1.980979 1.648-.183401.5035-.159384 1.0657.114614 1.551.346627.6138 1.005335.9487 1.696415.9365.33988-.01 1.72029-.6372 2.04256-.8192.97754-.5519 1.35079-1.7418.83369-2.6576-.51711-.9158-1.72876-1.2107-2.7063-.6587z" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<circle cx="21.175734" cy="1030.3542" r="4.6537542" fill="#fff" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<circle cx="10.339486" cy="1030.3542" r="4.8316345" fill="#fff" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<rect width="3.6673687" height="4.1063409" x="14.115863" y="1035.9174" fill-opacity=".32941176" color="#000" overflow="visible" ry=".72590536" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<rect width="3.6673687" height="4.1063409" x="14.115863" y="1035.2253" fill="#fffcfb" color="#000" overflow="visible" ry=".72590536" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<path fill-opacity=".32941176" d="M19.999735 1036.5289c0 .838-.871228 1.2682-2.144766 1.1659-.02366 0-.04795-.6004-.254147-.5832-.503669.042-1.095902-.02-1.685964-.02-.612939 0-1.206342.1826-1.68549.017-.110233-.038-.178298.5838-.261532.5816-1.243685-.033-2.078803-.3383-2.078803-1.1618 0-1.2118 1.815635-2.1941 4.055351-2.1941 2.239704 0 4.055351.9823 4.055351 2.1941z" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<path fill="#c38c74" d="M19.977414 1035.7004c0 .5685-.433659.8554-1.138091 1.0001-.291933.06-.630371.096-1.003719.1166-.56405.032-1.207782.031-1.89122.031-.672834 0-1.307182 0-1.864904-.029-.306268-.017-.589429-.043-.843164-.084-.813833-.1318-1.324962-.417-1.324962-1.0344 0-1.1601 1.805642-2.1006 4.03303-2.1006 2.227377 0 4.03303.9405 4.03303 2.1006z" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<ellipse cx="15.944382" cy="1033.8501" fill="#23201f" color="#000" overflow="visible" rx="2.0801733" ry="1.343747" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<circle cx="12.414201" cy="1030.3542" r="1.9630634" fill="#171311" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<circle cx="23.110121" cy="1030.3542" r="1.9630634" fill="#171311" color="#000" overflow="visible" style="isolation:auto;mix-blend-mode:normal;solid-color:#000;solid-opacity:1"/>
|
||||||
|
<path fill="none" stroke="#384e54" stroke-linecap="round" stroke-width=".39730874" d="M5.0055377 1027.2727c-1.170435-1.0835-2.026973-.7721-2.044172-.7463"/>
|
||||||
|
<path fill="none" stroke="#384e54" stroke-linecap="round" stroke-width=".39730874" d="M4.3852457 1026.9152c-1.158557.036-1.346704.6303-1.33881.6523m23.5840973-.3951c1.17043-1.0835 2.02697-.7721 2.04417-.7463"/>
|
||||||
|
<path fill="none" stroke="#384e54" stroke-linecap="round" stroke-width=".39730874" d="M27.321773 1026.673c1.15856.036 1.3467.6302 1.3388.6522"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
57
public/images/langs/rust.svg
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<svg version="1.1" height="106" width="106" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<g id="logo" transform="translate(53, 53)">
|
||||||
|
<path id="r" transform="translate(0.5, 0.5)" stroke="black" stroke-width="1" stroke-linejoin="round" d=" M -9,-15 H 4 C 12,-15 12,-7 4,-7 H -9 Z M -40,22 H 0 V 11 H -9 V 3 H 1 C 12,3 6,22 15,22 H 40 V 3 H 34 V 5 C 34,13 25,12 24,7 C 23,2 19,-2 18,-2 C 33,-10 24,-26 12,-26 H -35 V -15 H -25 V 11 H -40 Z"/>
|
||||||
|
<g id="gear" mask="url(#holes)">
|
||||||
|
<circle r="43" fill="none" stroke="black" stroke-width="9"/>
|
||||||
|
<g id="cogs">
|
||||||
|
<polygon id="cog" stroke="black" stroke-width="3" stroke-linejoin="round" points="46,3 51,0 46,-3"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(11.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(22.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(33.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(45.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(56.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(67.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(78.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(90.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(101.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(112.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(123.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(135.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(146.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(157.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(168.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(180.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(191.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(202.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(213.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(225.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(236.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(247.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(258.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(270.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(281.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(292.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(303.75)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(315.00)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(326.25)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(337.50)"/>
|
||||||
|
<use xlink:href="#cog" transform="rotate(348.75)"/>
|
||||||
|
</g>
|
||||||
|
<g id="mounts">
|
||||||
|
<polygon id="mount" stroke="black" stroke-width="6" stroke-linejoin="round" points="-7,-42 0,-35 7,-42"/>
|
||||||
|
<use xlink:href="#mount" transform="rotate(72)"/>
|
||||||
|
<use xlink:href="#mount" transform="rotate(144)"/>
|
||||||
|
<use xlink:href="#mount" transform="rotate(216)"/>
|
||||||
|
<use xlink:href="#mount" transform="rotate(288)"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<mask id="holes">
|
||||||
|
<rect x="-60" y="-60" width="120" height="120" fill="white"/>
|
||||||
|
<circle id="hole" cy="-40" r="3"/>
|
||||||
|
<use xlink:href="#hole" transform="rotate(72)"/>
|
||||||
|
<use xlink:href="#hole" transform="rotate(144)"/>
|
||||||
|
<use xlink:href="#hole" transform="rotate(216)"/>
|
||||||
|
<use xlink:href="#hole" transform="rotate(288)"/>
|
||||||
|
</mask>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/images/posts/assessment-art.webp
Normal file
After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 88 KiB |
BIN
public/images/posts/itemdescriptionoverlay.webp
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 310 KiB |
BIN
public/images/posts/itemobtainoverlay.webp
Normal file
After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 143 KiB |
BIN
public/images/posts/light-discord.webp
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 49 KiB |
BIN
public/images/posts/mdn-box-sizing-tip.webp
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 788 KiB |
BIN
public/images/posts/mobile-primoprod.webp
Normal file
After Width: | Height: | Size: 77 KiB |
BIN
public/images/posts/primoprod-10pull.webp
Normal file
After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 8.4 KiB |
BIN
public/images/posts/primoprod-badges.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
public/images/posts/primoprod-banner-headers.webp
Normal file
After Width: | Height: | Size: 9.6 KiB |