Nuxt rewrite
This commit is contained in:
parent
602221764b
commit
abc845d5fc
465 changed files with 5842 additions and 7445 deletions
|
@ -1,33 +0,0 @@
|
|||
name: Build and Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: node:20-bullseye
|
||||
steps:
|
||||
- name: Setup RSync
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y rsync
|
||||
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
yarn run build
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: https://github.com/JamesIves/github-pages-deploy-action@v4.2.5
|
||||
with:
|
||||
branch: pages # The branch the action should deploy to.
|
||||
folder: site/.vitepress/dist # The folder the action should deploy.
|
25
.github/workflows/deploy.yml
vendored
25
.github/workflows/deploy.yml
vendored
|
@ -1,25 +0,0 @@
|
|||
name: Build and Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GH_PAT }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
|
||||
run: |
|
||||
yarn install --frozen-lockfile
|
||||
yarn run build
|
||||
|
||||
- name: Deploy 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.2.5
|
||||
with:
|
||||
branch: gh-pages # The branch the action should deploy to.
|
||||
folder: site/.vitepress/dist # The folder the action should deploy.
|
37
.gitignore
vendored
37
.gitignore
vendored
|
@ -1,26 +1,15 @@
|
|||
node_modules
|
||||
site/.vitepress/dist
|
||||
site/.vitepress/cache
|
||||
site/garden/
|
||||
site/now/
|
||||
site/public/garden/
|
||||
site/public/changelog/
|
||||
site/changelog/index.md
|
||||
site/guide-to-incrementals/
|
||||
site/public/licenses.txt
|
||||
site/public/posts/
|
||||
site/tag/
|
||||
site/tags/
|
||||
site/timeline/
|
||||
site/type/
|
||||
site/types/
|
||||
garden-output/
|
||||
reddit-export/
|
||||
youtube-export/
|
||||
*.log*
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
.output
|
||||
.data
|
||||
.env
|
||||
cache/
|
||||
site/recent-posts.data.ts
|
||||
youtube_credentials.json
|
||||
youtube_token.json
|
||||
forbidden.txt
|
||||
reddit_aliases.json
|
||||
dist
|
||||
garden-output
|
||||
assets/favorites.json
|
||||
assets/tags.json
|
||||
public/changelog
|
||||
public/garden
|
||||
public/licenses.txt
|
18
.gitmodules
vendored
18
.gitmodules
vendored
|
@ -1,32 +1,32 @@
|
|||
[submodule "gamedevtree"]
|
||||
path = site/public/gamedevtree
|
||||
path = public/gamedevtree
|
||||
url = git@code.incremental.social:thepaperpilot/The-Modding-Tree.git
|
||||
branch = gamedevtree
|
||||
[submodule "kronos"]
|
||||
path = site/public/kronos
|
||||
path = public/kronos
|
||||
url = git@code.incremental.social:thepaperpilot/The-Modding-Tree.git
|
||||
branch = kronos
|
||||
[submodule "lit"]
|
||||
path = site/public/lit
|
||||
path = public/lit
|
||||
url = git@code.incremental.social:thepaperpilot/The-Modding-Tree.git
|
||||
branch = lit
|
||||
[submodule "the_ascension_tree"]
|
||||
path = site/public/the_ascension_tree
|
||||
path = public/the_ascension_tree
|
||||
url = git@code.incremental.social:thepaperpilot/the_ascension_tree.git
|
||||
[submodule "dream"]
|
||||
path = site/public/dream
|
||||
path = public/dream
|
||||
url = git@code.incremental.social:thepaperpilot/Dream-Hero.git
|
||||
branch = gh-pages
|
||||
[submodule "Advent-Incremental.git"]
|
||||
path = site/public/advent
|
||||
path = public/advent
|
||||
url = git@code.incremental.social:thepaperpilot/Advent-Incremental.git
|
||||
branch = gh-pages
|
||||
[submodule "ludwig"]
|
||||
path = site/public/ludwig
|
||||
path = public/ludwig
|
||||
url = git@code.incremental.social:thepaperpilot/Super-Auto-Coots.git
|
||||
branch = gh-pages
|
||||
[submodule "site/public/planar"]
|
||||
path = site/public/planar
|
||||
[submodule "planar"]
|
||||
path = public/planar
|
||||
url = git@code.incremental.social:thepaperpilot/Planar-Pioneers.git
|
||||
branch = gh-pages
|
||||
[submodule "Garden.git"]
|
||||
|
|
2
.npmrc
Normal file
2
.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
39
App.vue
Normal file
39
App.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<AsidesContainer />
|
||||
<Container>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</Container>
|
||||
<Background />
|
||||
<Footer />
|
||||
<Favorites />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AsidesContainer from "~/components/asides/AsidesContainer.vue";
|
||||
import Favorites from "~/components/asides/Favorites.vue";
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
html {
|
||||
background: var(--nord1);
|
||||
color: var(--nord4);
|
||||
font-family: Itim;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#__nuxt {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
</style>
|
42
README.md
Normal file
42
README.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Content v2 Minimal Starter
|
||||
|
||||
Look at the [Content documentation](https://content.nuxt.com/) to learn more.
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure to install the dependencies:
|
||||
|
||||
```bash
|
||||
# yarn
|
||||
yarn install
|
||||
|
||||
# npm
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on http://localhost:3000
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Build the application for production:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
|
||||
```bash
|
||||
npm run preview
|
||||
```
|
||||
|
||||
Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
53
add_like.js
53
add_like.js
|
@ -1,53 +0,0 @@
|
|||
const fs = require("fs");
|
||||
|
||||
const { getLinkPreview } = require("link-preview-js");
|
||||
const { getAvatar, preparePost, encodeString, processLinkPreview, getArchivePreview, getActionDescription } = require("./utils");
|
||||
|
||||
(async () => {
|
||||
if (!fs.existsSync("./manual-posts")) {
|
||||
fs.mkdirSync("./manual-posts");
|
||||
}
|
||||
const tags = process.argv.slice(2).filter(tag => !tag.startsWith("-") && !tag.startsWith("http"));
|
||||
await Promise.all(process.argv.slice(2).map(async url => {
|
||||
if (url.startsWith("-") || !url.startsWith("http")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let timestamp = new Date().getTime();
|
||||
while (fs.existsSync("./manual-posts/" + timestamp)) {
|
||||
timestamp++;
|
||||
continue;
|
||||
}
|
||||
fs.mkdirSync("./manual-posts/" + timestamp);
|
||||
|
||||
console.log("Generating preview for", url);
|
||||
const linkPreview = await getLinkPreview(url, { followRedirects: true }).catch(async () => {
|
||||
console.log(`Failed to retrieve preview for ${url}. Trying wayback machine...`);
|
||||
return await getArchivePreview(url, timestamp);
|
||||
});
|
||||
const content = await processLinkPreview(linkPreview);
|
||||
|
||||
const fd = fs.openSync("./manual-posts/" + timestamp + "/index.md", "w+");
|
||||
fs.writeSync(fd, preparePost(`---
|
||||
kind: repost
|
||||
title: ${encodeString(linkPreview.title, 2)}
|
||||
published: ${timestamp}
|
||||
next: false
|
||||
prev: false
|
||||
---
|
||||
<div class="post">
|
||||
${getActionDescription({ timestamp, kind: "bookmark" })}
|
||||
<div class="content-container">
|
||||
${await getAvatar({
|
||||
timestamp,
|
||||
tags,
|
||||
kind: 'bookmark'
|
||||
})}
|
||||
<div class="content e-content h-cite u-repost-of">${content}</div>
|
||||
</div>
|
||||
</div>
|
||||
`));
|
||||
fs.closeSync(fd);
|
||||
console.log(`Created post for url ${url}: /manual-posts/${timestamp}`);
|
||||
}));
|
||||
})();
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
35
assets/main.css
Normal file
35
assets/main.css
Normal file
|
@ -0,0 +1,35 @@
|
|||
a[href^="http"]::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
margin-left: 2px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
background: currentColor;
|
||||
opacity: 0.7;
|
||||
flex-shrink: 0;
|
||||
mask-image: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
margin-left: calc(1em + 10px);
|
||||
display: flow-root;
|
||||
}
|
||||
|
||||
ul.inline li,
|
||||
.ol.inline li {
|
||||
display: inline flow-root list-item;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
li::before {
|
||||
content: "✲";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: calc(100% + 5px);
|
||||
}
|
|
@ -22,13 +22,11 @@ vec4 permute(vec4 x) {
|
|||
return mod289(((x*34.0)+10.0)*x);
|
||||
}
|
||||
|
||||
vec4 taylorInvSqrt(vec4 r)
|
||||
{
|
||||
vec4 taylorInvSqrt(vec4 r) {
|
||||
return 1.79284291400159 - 0.85373472095314 * r;
|
||||
}
|
||||
|
||||
float snoise(vec3 v)
|
||||
{
|
||||
float snoise(vec3 v) {
|
||||
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
|
||||
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
||||
|
||||
|
@ -99,8 +97,8 @@ float snoise(vec3 v)
|
|||
vec4 m = max(0.5 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
||||
m = m * m;
|
||||
return 105.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1),
|
||||
dot(p2,x2), dot(p3,x3) ) );
|
||||
}
|
||||
dot(p2,x2), dot(p3,x3) ) );
|
||||
}
|
||||
|
||||
// demo code:
|
||||
// float color(vec2 xy) { return 0.7 * snoise(vec3(xy, 0.3*iTime)); }
|
232
assets/nord.css
Normal file
232
assets/nord.css
Normal file
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright (c) 2016-present Sven Greb <development@svengreb.de>
|
||||
* This source code is licensed under the MIT license found in the license file.
|
||||
*/
|
||||
|
||||
/*
|
||||
* References:
|
||||
* 1. https://www.w3.org/TR/css-variables
|
||||
* 2. https://www.w3.org/TR/selectors/#root-pseudo
|
||||
* 3. https://drafts.csswg.org/css-variables
|
||||
* 4. https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables
|
||||
* 5. https://warpspire.com/kss
|
||||
* 6. https://github.com/kss-node/kss-node
|
||||
*/
|
||||
|
||||
/*
|
||||
An arctic, north-bluish color palette.
|
||||
Created for the clean- and minimal flat design pattern to achieve a optimal focus and readability for code syntax
|
||||
highlighting and UI.
|
||||
It consists of a total of sixteen, carefully selected, dimmed pastel colors for a eye-comfortable, but yet colorful
|
||||
ambiance.
|
||||
|
||||
Styleguide Nord
|
||||
*/
|
||||
|
||||
:root {
|
||||
/*
|
||||
Base component color of "Polar Night".
|
||||
|
||||
Used for texts, backgrounds, carets and structuring characters like curly- and square brackets.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#2e3440; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Polar Night
|
||||
*/
|
||||
--nord0: #2e3440;
|
||||
|
||||
/*
|
||||
Lighter shade color of the base component color.
|
||||
|
||||
Used as a lighter background color for UI elements like status bars.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#3b4252; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Polar Night
|
||||
*/
|
||||
--nord1: #3b4252;
|
||||
|
||||
/*
|
||||
Lighter shade color of the base component color.
|
||||
|
||||
Used as line highlighting in the editor.
|
||||
In the UI scope it may be used as selection- and highlight color.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#434c5e; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Polar Night
|
||||
*/
|
||||
--nord2: #434c5e;
|
||||
|
||||
/*
|
||||
Lighter shade color of the base component color.
|
||||
|
||||
Used for comments, invisibles, indent- and wrap guide marker.
|
||||
In the UI scope used as pseudoclass color for disabled elements.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#4c566a; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Polar Night
|
||||
*/
|
||||
--nord3: #4c566a;
|
||||
|
||||
/*
|
||||
Base component color of "Snow Storm".
|
||||
|
||||
Main color for text, variables, constants and attributes.
|
||||
In the UI scope used as semi-light background depending on the theme shading design.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#d8dee9; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Snow Storm
|
||||
*/
|
||||
--nord4: #d8dee9;
|
||||
|
||||
/*
|
||||
Lighter shade color of the base component color.
|
||||
|
||||
Used as a lighter background color for UI elements like status bars.
|
||||
Used as semi-light background depending on the theme shading design.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#e5e9f0; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Snow Storm
|
||||
*/
|
||||
--nord5: #e5e9f0;
|
||||
|
||||
/*
|
||||
Lighter shade color of the base component color.
|
||||
|
||||
Used for punctuations, carets and structuring characters like curly- and square brackets.
|
||||
In the UI scope used as background, selection- and highlight color depending on the theme shading design.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#eceff4; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Snow Storm
|
||||
*/
|
||||
--nord6: #eceff4;
|
||||
|
||||
/*
|
||||
Bluish core color.
|
||||
|
||||
Used for classes, types and documentation tags.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#8fbcbb; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Frost
|
||||
*/
|
||||
--nord7: #8fbcbb;
|
||||
|
||||
/*
|
||||
Bluish core accent color.
|
||||
|
||||
Represents the accent color of the color palette.
|
||||
Main color for primary UI elements and methods/functions.
|
||||
|
||||
Can be used for
|
||||
- Markup quotes
|
||||
- Markup link URLs
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#88c0d0; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Frost
|
||||
*/
|
||||
--nord8: #88c0d0;
|
||||
|
||||
/*
|
||||
Bluish core color.
|
||||
|
||||
Used for language-specific syntactic/reserved support characters and keywords, operators, tags, units and
|
||||
punctuations like (semi)colons,commas and braces.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#81a1c1; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Frost
|
||||
*/
|
||||
--nord9: #81a1c1;
|
||||
|
||||
/*
|
||||
Bluish core color.
|
||||
|
||||
Used for markup doctypes, import/include/require statements, pre-processor statements and at-rules (`@`).
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#5e81ac; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Frost
|
||||
*/
|
||||
--nord10: #5e81ac;
|
||||
|
||||
/*
|
||||
Colorful component color.
|
||||
|
||||
Used for errors, git/diff deletion and linter marker.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#bf616a; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Aurora
|
||||
*/
|
||||
--nord11: #bf616a;
|
||||
|
||||
/*
|
||||
Colorful component color.
|
||||
|
||||
Used for annotations.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#d08770; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Aurora
|
||||
*/
|
||||
--nord12: #d08770;
|
||||
|
||||
/*
|
||||
Colorful component color.
|
||||
|
||||
Used for escape characters, regular expressions and markup entities.
|
||||
In the UI scope used for warnings and git/diff renamings.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#ebcb8b; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Aurora
|
||||
*/
|
||||
--nord13: #ebcb8b;
|
||||
|
||||
/*
|
||||
Colorful component color.
|
||||
|
||||
Main color for strings and attribute values.
|
||||
In the UI scope used for git/diff additions and success visualizations.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#a3be8c; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Aurora
|
||||
*/
|
||||
--nord14: #a3be8c;
|
||||
|
||||
/*
|
||||
Colorful component color.
|
||||
|
||||
Used for numbers.
|
||||
|
||||
Markup:
|
||||
<div style="background-color:#b48ead; width=60; height=60"></div>
|
||||
|
||||
Styleguide Nord - Aurora
|
||||
*/
|
||||
--nord15: #b48ead;
|
||||
}
|
||||
|
101
build_changelog.ts
Normal file
101
build_changelog.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
import fs from "fs";
|
||||
|
||||
import util from 'node:util';
|
||||
import child_process from 'node:child_process';
|
||||
const exec = util.promisify(child_process.exec);
|
||||
|
||||
import { Feed } from 'feed';
|
||||
|
||||
(async () => {
|
||||
const feed = new Feed({
|
||||
title: "The Paper Pilot's Digital Garden Changelog",
|
||||
description: "A feed of updates made to my digital garden!",
|
||||
id: "https://www.thepaperpilot.org/changelog/",
|
||||
link: "https://www.thepaperpilot.org/changelog/",
|
||||
language: "en", // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
||||
// image: "http://example.com/image.png",
|
||||
// favicon: "http://example.com/favicon.ico",
|
||||
copyright: `All rights reserved ${new Date().getFullYear()}, The Paper Pilot`,
|
||||
// updated: new Date(2013, 6, 14), // optional, default = today
|
||||
// generator: "awesome", // optional, default = 'Feed for Node.js'
|
||||
feedLinks: {
|
||||
rss: "https://www.thepaperpilot.org/changelog/rss",
|
||||
json: "https://www.thepaperpilot.org/changelog/json",
|
||||
// atom: "https://www.thepaperpilot.org/changelog/atom"
|
||||
},
|
||||
author: {
|
||||
name: "The Paper Pilot",
|
||||
email: "thepaperpilot@incremental.social",
|
||||
link: "https://www.thepaperpilot.org/about"
|
||||
}
|
||||
});
|
||||
|
||||
const stdout = await exec('git log --after="2024-09-22T0:0:0+0000" --pretty=%H origin/master -- content/garden')
|
||||
.then(res => res.stdout)
|
||||
.catch(err => console.warn(`Error calculating git history:\n${err}`));
|
||||
if (!stdout) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all(stdout.split("\n").filter(p => p).map(hash => new Promise(async (resolve) => {
|
||||
const { stdout: fullTime } = await exec(`git show --quiet --format=%ad ${hash}`);
|
||||
let { stdout: changes } = await exec(`git show --format="" --stat --relative ${hash} .`, {
|
||||
cwd: 'content/garden'
|
||||
});
|
||||
|
||||
changes = changes.replaceAll(/\/index.md/g, '');
|
||||
changes = changes.replaceAll(
|
||||
/(\| +[0-9]+ \+*)(-+)/g,
|
||||
'$1<span style="color:#BF616A">$2</span>');
|
||||
changes = changes.replaceAll(
|
||||
/(\| +[0-9]+ )(\++)/g,
|
||||
'$1<span style="color:#A3BE8C">$2</span>');
|
||||
const lines = changes.split('\n');
|
||||
const summary = lines[lines.length - 2];
|
||||
changes = lines.slice(0, -2).map(line => {
|
||||
const [page, changes] = line.split("|").map(p => p.trim());
|
||||
return `<tr><td><a href="/garden/${page}">${page}</a></td><td style="font-family: monospace; white-space: nowrap;">${changes}</td></tr>`;
|
||||
}).join("\n");
|
||||
|
||||
const commitLink = `https://code.incremental.social/thepaperpilot/pages/commit/${hash}`
|
||||
const content = `<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="align: center">Page</th>
|
||||
<th style="align: center">Changes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${changes}
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
||||
feed.addItem({
|
||||
title: summary,
|
||||
id: commitLink,
|
||||
link: commitLink,
|
||||
description: summary,
|
||||
content,
|
||||
date: new Date(fullTime)
|
||||
});
|
||||
|
||||
resolve(null);
|
||||
})));
|
||||
|
||||
if (!fs.existsSync("public/changelog")) {
|
||||
fs.mkdirSync("public/changelog");
|
||||
}
|
||||
|
||||
let fd = fs.openSync("public/changelog/rss", "w+");
|
||||
fs.writeSync(fd, feed.rss2());
|
||||
fs.closeSync(fd);
|
||||
|
||||
// Atom1 fails here for some reason
|
||||
// fd = fs.openSync("public/changelog/atom", "w+");
|
||||
// fs.writeSync(fd, feed.atom1());
|
||||
// fs.closeSync(fd);
|
||||
|
||||
fd = fs.openSync("public/changelog/json", "w+");
|
||||
fs.writeSync(fd, feed.json1());
|
||||
fs.closeSync(fd);
|
||||
})();
|
232
build_garden.ts
Normal file
232
build_garden.ts
Normal file
|
@ -0,0 +1,232 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
import wordCounting from "word-counting";
|
||||
import { walk } from "./utils/fs-utils.js";
|
||||
|
||||
import util from 'node:util';
|
||||
import child_process from 'node:child_process';
|
||||
const exec = util.promisify(child_process.exec);
|
||||
|
||||
function toSlug(string: string) {
|
||||
return string.toLowerCase().replaceAll(' ', '-');
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const blockRefs: Record<string, string> = {};
|
||||
const blockLinks: Record<string, string> = {};
|
||||
const indices: string[] = [];
|
||||
await walk("./garden-output/logseq-pages", (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
const data = fs.readFileSync(filePath).toString();
|
||||
const slug = path.basename(file, ".md").replaceAll('___', '/').replaceAll(/%3F/gi, '')
|
||||
.replace('what-is-content-', 'what-is-content');
|
||||
for (const match of data.matchAll(/(.*)\n\s*id:: (.*)/gm)) {
|
||||
const text = match[1];
|
||||
const id = match[2];
|
||||
const link = `/garden/${slug}#${id}`;
|
||||
blockLinks[id] = link;
|
||||
blockRefs[id] = `[${text}](${link})`;
|
||||
}
|
||||
if (data.match(/index: "true"/g)) {
|
||||
indices.push(slug);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
const pageLinks: Record<string, string> = {};
|
||||
const taggedBy: Record<string, string[]> = {};
|
||||
const tagged: Record<string, string[]> = {};
|
||||
const referencedBy: Record<string, string[]> = {};
|
||||
// Walk through the pages to make sure we get the canonical name page (pre-slug)
|
||||
// The logseq-export README made it sound like even the title property is transformed sometimes
|
||||
await walk("./Garden/pages", (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
if (data.match(/public::/g) == null) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const startPrivate = data.indexOf("- private");
|
||||
if (startPrivate > 0) {
|
||||
data = data.slice(0, startPrivate);
|
||||
}
|
||||
|
||||
const name = path.basename(file, ".md").replaceAll('___', '/');
|
||||
const slug = toSlug(name).replaceAll(/%3F/gi, '').replaceAll('\'', '-');
|
||||
const link = `/garden/${slug}`;
|
||||
pageLinks[name.replaceAll(/%3F/gi, '?')] = link;
|
||||
|
||||
for (const match of data.matchAll(/alias:: (.*)/g)) {
|
||||
match[1].split(", ").forEach(page => (pageLinks[page] = link));
|
||||
}
|
||||
|
||||
for (const match of data.matchAll(/tags:: (.*)/g)) {
|
||||
match[1].split(", ").forEach(page => {
|
||||
const pageSlug = toSlug(page);
|
||||
taggedBy[pageSlug] = [...(taggedBy[pageSlug] ?? []), name];
|
||||
tagged[slug] = [...(tagged[slug] ?? []), page];
|
||||
});
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
await walk("./Garden/pages", (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
if (data.match(/public::/g) == null) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const name = path.basename(file, ".md").replaceAll('___', '/');
|
||||
const slug = toSlug(name).replaceAll(/%3F/gi, '').replaceAll('\'', '-');
|
||||
|
||||
if (!indices.includes(slug)) {
|
||||
for (const match of data.matchAll(/\[\[([^\[\]]*)\]\]/g)) {
|
||||
const pageSlug = pageLinks[match[1].replaceAll(/%3F/gi, '?')];
|
||||
referencedBy[pageSlug] = [...(referencedBy[pageSlug] ?? []),
|
||||
name.replaceAll(/%3F/gi, '?')];
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
Object.keys(referencedBy).forEach(page => {
|
||||
referencedBy[page] = Array.from(new Set(referencedBy[page]));
|
||||
});
|
||||
|
||||
// Move everything from ./garden-output/logseq-assets into ./public/garden
|
||||
fs.mkdirSync("./content/garden", { recursive: true });
|
||||
await walk("./garden-output/logseq-pages", async (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
|
||||
// Count word counts with a special set of transformations that should make it more accurate
|
||||
const strippedData = data.replace(/---\n[\S\s]*\n---/gm, '').replaceAll(/.*::.*/g, '')
|
||||
.replaceAll(/\[([^\]]*)\]\(.*\)/g, '$1');
|
||||
const wc = wordCounting(strippedData).wordsCount;
|
||||
data = data.replace(/---\n\n/gm, `wordCount: ${wc}\n---\n\n`);
|
||||
|
||||
data = data.replace(/public: .*\n/, '');
|
||||
data = data.replace(/slug: .*\n/, '');
|
||||
data = data.replace(/alias: .*\n/, '');
|
||||
const contentPath =
|
||||
path.resolve("./content/garden", path.relative("./garden-output/logseq-pages", file));
|
||||
const firstCommit =
|
||||
exec(`git log -n 1 --diff-filter=A --format="%H,%at" -- "${contentPath}"`)
|
||||
.then(output => output.stdout)
|
||||
.catch(err =>
|
||||
console.warn(`Error calculating first commit for ${contentPath}:\n${err}`));
|
||||
const lastCommit =
|
||||
exec(`git log -n 1 --diff-filter=M --format="%H,%at" -- "${contentPath}"`)
|
||||
.then(output => output.stdout)
|
||||
.catch(err =>
|
||||
console.warn(`Error calculating last commit for ${contentPath}:\n${err}`));
|
||||
|
||||
const [hash, timestampString] = (await firstCommit)?.trim().split(",") ?? ["", ""];
|
||||
const timestamp = parseInt(timestampString);
|
||||
data = data.replace(/---\n\n/gm,
|
||||
`published:\n hash: ${hash}\n timestamp: ${timestamp * 1000}\n---\n\n`);
|
||||
if (await lastCommit) {
|
||||
const [hash, timestampString] = (await lastCommit)!.trim().split(",");
|
||||
const timestamp = parseInt(timestampString);
|
||||
data = data.replace(/---\n\n/gm,
|
||||
`edited:\n hash: ${hash}\n timestamp: ${timestamp * 1000}\n---\n\n`);
|
||||
}
|
||||
|
||||
// Replace youtube embeds
|
||||
data = data.replaceAll(
|
||||
/{{video https:\/\/(?:www\.)?youtube\.com\/watch\?v=(.*)}}/g,
|
||||
'<iframe width="560" height="315" src="https://www.youtube.com/embed/$1" title="" frameBorder="0" allowFullScreen />');
|
||||
// Replace internal links
|
||||
data = data.replaceAll(
|
||||
/]\(\/logseq-pages\/([^\)]*)\)/g,
|
||||
'](/garden/$1)');
|
||||
// Replace block links
|
||||
data = data.replaceAll(
|
||||
/\(\((.*)\)\)/g,
|
||||
(_, id) => blockRefs[id]);
|
||||
// Remove id:: lines
|
||||
data = data.replaceAll(
|
||||
/(#+) (.*)\n\s*id:: (.*)/gm,
|
||||
(_, h, title, id) => `<h${h.length} id="${id}">${title}</h${h.length}>\n`);
|
||||
data = data.replaceAll(
|
||||
/(.*)\n\s*id:: (.*)/gm,
|
||||
'<span id="$2">$1</span>');
|
||||
// Fix internal links with spaces not getting mapped
|
||||
data = data.replaceAll(
|
||||
/\[\[([^\[\]]*)\]\]/g,
|
||||
(_, page) => `[${page}](${pageLinks[page]})`);
|
||||
// Fix internal asset links
|
||||
data = data.replaceAll(
|
||||
/\(\/logseq-assets\/([^\)]*)\)/g,
|
||||
'(/garden/$1)');
|
||||
// Fix logseq block links
|
||||
data = data.replaceAll(
|
||||
/logseq:\/\/graph\/Garden\?block-id=([^\)]*)/g,
|
||||
(_, block) => `${blockLinks[block]})`);
|
||||
// Fix logseq page links
|
||||
data = data.replaceAll(
|
||||
/logseq:\/\/graph\/Garden\?page=([^\)]*)/g,
|
||||
(_, page) => `${pageLinks[page.replaceAll('%20', ' ')]})`);
|
||||
// Wrap images
|
||||
data = data.replaceAll(
|
||||
/!\[([^\]]*)\]\(([^\)]*)\)/g,
|
||||
(_, title, src) => `:postsCard{image="${src}" alt="${title}"}`)
|
||||
// Add tags and references
|
||||
const title = path.basename(file, ".md");
|
||||
if (title in tagged) {
|
||||
data = data.replace(/tags: \[.*\]\n/, '');
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`tags:\n${tagged[title].map(tag =>
|
||||
` ${tag}: ${pageLinks[tag]}`).join("\n")}\n---\n\n`);
|
||||
}
|
||||
if (title in taggedBy) {
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`taggedBy:\n${taggedBy[title].map(page =>
|
||||
` ${page}: ${pageLinks[page]}`).join("\n")}\n---\n\n`);
|
||||
}
|
||||
// TODO show context on references? Perhaps in a `::: info` block?
|
||||
const pageTitle = data.match(/title: "(.+)"/)?.[1] ?? "";
|
||||
if (pageLinks[pageTitle] in referencedBy) {
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`referencedBy:\n${referencedBy[pageLinks[pageTitle]].map(page =>
|
||||
` ${page}: ${pageLinks[page]}`).join("\n")}\n---\n\n`);
|
||||
}
|
||||
// Fix links to /now
|
||||
data = data.replace('NOW', '/now')
|
||||
data = data.replaceAll('___', '/');
|
||||
const folders = path.relative(dir, file).split('___');
|
||||
const filename = folders.splice(folders.length - 1, 1)[0];
|
||||
fs.mkdirSync(path.resolve("./content/garden", ...folders), { recursive: true });
|
||||
const fd = fs.openSync(path.resolve("./content/garden", ...folders, filename), "w+");
|
||||
fs.writeSync(fd, data);
|
||||
fs.closeSync(fd);
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Move everything from ./garden-output/logseq-assets into ./public/garden
|
||||
fs.mkdirSync("./public/garden", { recursive: true });
|
||||
await walk("./garden-output/logseq-assets", (dir, file, resolve) => {
|
||||
fs.copyFileSync(path.resolve(dir, file), path.resolve("./public/garden",
|
||||
...path.basename(file).split('___')));
|
||||
resolve();
|
||||
});
|
||||
|
||||
// For what is content, remove the - at the end
|
||||
fs.cpSync('./content/garden/guide-to-incrementals/what-is-content-.md',
|
||||
'./content/garden/guide-to-incrementals/what-is-content.md');
|
||||
fs.rmSync('./content/garden/guide-to-incrementals/what-is-content-.md');
|
||||
|
||||
// Save favorites to assets
|
||||
const favorites = (fs.readFileSync("./Garden/logseq/config.edn").toString()
|
||||
.matchAll(/:favorites \["([^\]]+)"\]/g).next()?.value as string)[1]?.split("\" \"")
|
||||
.map(page => ({ text: page, link: `/garden/${page.toLowerCase().replaceAll(' ', '-')}` }));
|
||||
const fd = fs.openSync("./assets/favorites.json", "w+");
|
||||
fs.writeSync(fd, JSON.stringify(favorites));
|
||||
fs.closeSync(fd);
|
||||
})();
|
|
@ -1,405 +1,72 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const wordCounting = require("word-counting");
|
||||
|
||||
const util = require('node:util');
|
||||
const exec = util.promisify(require('node:child_process').exec);
|
||||
const { Feed } = require('feed');
|
||||
|
||||
const { walk } = require("./utils");
|
||||
|
||||
function toSlug(string) {
|
||||
return string.toLowerCase().replaceAll(' ', '-');
|
||||
}
|
||||
|
||||
function moveImportStatementUp(filePath, times = 1) {
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
const fd = fs.openSync(filePath, "w+");
|
||||
for (let i = 0; i < times; i++) {
|
||||
data = data.replace(/'\.\.\//g, '\'');
|
||||
}
|
||||
fs.writeSync(fd, data);
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
// TODO When importing this as a module, I get a type-error from arborist
|
||||
const glf = require("generate-license-file");
|
||||
|
||||
(async () => {
|
||||
// Clear out old folders
|
||||
await exec("rm -rf ./site/guide-to-incrementals");
|
||||
await exec("rm -rf ./site/public/garden");
|
||||
await exec("rm -rf ./site/public/changelog");
|
||||
await exec("rm -rf ./site/changelog");
|
||||
await exec("rm -rf ./site/now");
|
||||
await exec("rm -rf ./site/garden");
|
||||
|
||||
const blockRefs = {};
|
||||
const blockLinks = {};
|
||||
const indices = [];
|
||||
await walk("./garden-output/logseq-pages", (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
const data = fs.readFileSync(filePath).toString();
|
||||
const slug = path.basename(file, ".md").replaceAll('___', '/').replaceAll(/%3F/gi, '').replace('what-is-content-', 'what-is-content');
|
||||
for (const match of data.matchAll(/(.*)\n\s*id:: (.*)/gm)) {
|
||||
const text = match[1];
|
||||
const id = match[2];
|
||||
const link = `/garden/${slug}#${id}`;
|
||||
blockLinks[id] = link;
|
||||
blockRefs[id] = `[${text}](${link})`;
|
||||
}
|
||||
if (data.match(/index: "true"/g)) {
|
||||
indices.push(slug);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
|
||||
const pageLinks = {};
|
||||
const taggedBy = {};
|
||||
const tagged = {};
|
||||
const referencedBy = {};
|
||||
// Walk through the pages to make sure we get the canonical name page (pre-slug)
|
||||
// The logseq-export README made it sound like even the title property is transformed sometimes
|
||||
await walk("./Garden/pages", (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
if (data.match(/public::/g) == null) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const startPrivate = data.indexOf("- private");
|
||||
if (startPrivate > 0) {
|
||||
data = data.slice(0, startPrivate);
|
||||
}
|
||||
|
||||
const name = path.basename(file, ".md").replaceAll('___', '/');
|
||||
const slug = toSlug(name).replaceAll(/%3F/gi, '').replaceAll('\'', '-');
|
||||
const link = `/garden/${slug}`;
|
||||
pageLinks[name.replaceAll(/%3F/gi, '?')] = link;
|
||||
|
||||
for (const match of data.matchAll(/alias:: (.*)/g)) {
|
||||
match[1].split(", ").forEach(page => (pageLinks[page] = link));
|
||||
}
|
||||
|
||||
for (const match of data.matchAll(/tags:: (.*)/g)) {
|
||||
match[1].split(", ").forEach(page => {
|
||||
const pageSlug = toSlug(page);
|
||||
taggedBy[pageSlug] = [...(taggedBy[pageSlug] ?? []), name];
|
||||
tagged[slug] = [...(tagged[slug] ?? []), page];
|
||||
});
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
await walk("./Garden/pages", (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
if (data.match(/public::/g) == null) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const name = path.basename(file, ".md").replaceAll('___', '/');
|
||||
const slug = toSlug(name).replaceAll(/%3F/gi, '').replaceAll('\'', '-');
|
||||
|
||||
if (!indices.includes(slug)) {
|
||||
for (const match of data.matchAll(/\[\[([^\[\]]*)\]\]/g)) {
|
||||
const pageSlug = pageLinks[match[1].replaceAll(/%3F/gi, '?')];
|
||||
referencedBy[pageSlug] = [...(referencedBy[pageSlug] ?? []), name.replaceAll(/%3F/gi, '?')];
|
||||
}
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
Object.keys(referencedBy).forEach(page => {
|
||||
referencedBy[page] = Array.from(new Set(referencedBy[page]));
|
||||
});
|
||||
pageLinks["NOW"] = "/now/index";
|
||||
|
||||
await walk("./garden-output/logseq-pages", async (dir, file, resolve) => {
|
||||
const filePath = path.resolve(dir, file);
|
||||
let data = fs.readFileSync(filePath).toString();
|
||||
|
||||
// Count word counts with a special set of transformations that should make it more accurate
|
||||
const strippedData = data.replace(/---\n[\S\s]*\n---/gm, '').replaceAll(/.*::.*/g, '').replaceAll(/\[([^\]]*)\]\(.*\)/g, '$1');
|
||||
const wc = wordCounting(strippedData).wordsCount;
|
||||
|
||||
// Replace youtube embeds
|
||||
data = data.replaceAll(
|
||||
/{{video https:\/\/(?:www\.)?youtube\.com\/watch\?v=(.*)}}/g,
|
||||
'<iframe width="560" height="315" src="https://www.youtube.com/embed/$1" title="" frameBorder="0" allowFullScreen />');
|
||||
// Replace internal links
|
||||
data = data.replaceAll(
|
||||
/]\(\/logseq-pages\/([^\)]*)\)/g,
|
||||
'](/garden/$1)');
|
||||
// Replace block links
|
||||
data = data.replaceAll(
|
||||
/\(\((.*)\)\)/g,
|
||||
(_, id) => blockRefs[id]);
|
||||
// Remove id:: lines
|
||||
data = data.replaceAll(
|
||||
/(#+) (.*)\n\s*id:: (.*)/gm,
|
||||
(_, h, title, id) => `<span id="${id}"><h${h.length}>${title}</h${h.length}></span>`);
|
||||
data = data.replaceAll(
|
||||
/(.*)\n\s*id:: (.*)/gm,
|
||||
'<span id="$2">$1</span>');
|
||||
// Fix internal links with spaces not getting mapped
|
||||
data = data.replaceAll(
|
||||
/\[\[([^\[\]]*)\]\]/g,
|
||||
(_, page) => `[${page}](${pageLinks[page]})`);
|
||||
// Fix internal asset links
|
||||
data = data.replaceAll(
|
||||
/\(\/logseq-assets\/([^\)]*)\)/g,
|
||||
'(/garden/$1)');
|
||||
// Fix logseq block links
|
||||
data = data.replaceAll(
|
||||
/logseq:\/\/graph\/Garden\?block-id=([^\)]*)/g,
|
||||
(_, block) => `${blockLinks[block]})`);
|
||||
// Fix logseq page links
|
||||
data = data.replaceAll(
|
||||
/logseq:\/\/graph\/Garden\?page=([^\)]*)/g,
|
||||
(_, page) => `${pageLinks[page.replaceAll('%20', ' ')]})`);
|
||||
// Wrap images
|
||||
data = data.replaceAll(
|
||||
/!\[([^\]]*)\]\(([^\)]*)\)/g,
|
||||
(_, title, src) => `<div class="img-container"><img src="${src}" title="${title}"/></div>`)
|
||||
// Add tags and references
|
||||
const title = path.basename(file, ".md");
|
||||
if (title in tagged) {
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`---\n\n<details><summary>Tags:</summary>${tagged[title].map(tag => `<a href="${pageLinks[tag]}">${tag}</a>`).join("")}</details>\n\n`);
|
||||
}
|
||||
if (title in taggedBy) {
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`---\n\n<details><summary>Tagged by:</summary>${taggedBy[title].map(tag => `<a href="${pageLinks[tag]}">${tag}</a>`).join("")}</details>\n\n`);
|
||||
}
|
||||
// TODO show context on references? Perhaps in a `::: info` block?
|
||||
const pageTitle = data.match(/title: "(.+)"/)[1];
|
||||
if (pageLinks[pageTitle] in referencedBy) {
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`---\n\n<details><summary>Referenced by:</summary>${referencedBy[pageLinks[pageTitle]].map(tag => `<a href="${pageLinks[tag]}">${tag}</a>`).join("")}</details>\n\n`);
|
||||
}
|
||||
// Fix links to /now
|
||||
data = data.replace('NOW', '/now')
|
||||
// Add header to the top
|
||||
data = data.replaceAll('___', '/');
|
||||
const relPath = path.relative("./garden-output/logseq-pages", path.resolve(...filePath.split("___"))).replaceAll(/%3F/gi, '').replace('what-is-content-', 'what-is-content').replace('.md', '/index.html');
|
||||
data = data.replaceAll(
|
||||
/---\n\n/gm,
|
||||
`prev: false
|
||||
next: false
|
||||
---
|
||||
<script setup>
|
||||
import { data } from '${path.relative(path.resolve("site", relPath), path.resolve("site", "git.data.ts")).replaceAll('\\', '/')}';
|
||||
import { useData } from 'vitepress';
|
||||
const pageData = useData();
|
||||
</script>
|
||||
<h1 class="p-name">${pageTitle.replace("NOW", "/now").replaceAll('___', '/')}</h1>
|
||||
<p>${wc} words, ~${Math.round(wc / 183)} minute read. <span v-html="data[\`site/\${pageData.page.value.relativePath}\`]" /></p>
|
||||
<hr/>
|
||||
\n`);
|
||||
|
||||
const fd = fs.openSync(filePath, "w+");
|
||||
fs.writeSync(fd, data);
|
||||
fs.closeSync(fd);
|
||||
resolve();
|
||||
});
|
||||
|
||||
fs.mkdirSync("./site/public/garden", { recursive: true });
|
||||
|
||||
// Move everything from ./garden-output/logseq-pages into ./site/garden
|
||||
await walk("./garden-output/logseq-pages", (dir, file, resolve) => {
|
||||
const folder = path.resolve("./site/garden", ...path.basename(file, ".md").split('___'));
|
||||
fs.mkdirSync(folder, { recursive: true });
|
||||
fs.copyFileSync(path.resolve(dir, file), path.resolve(folder, "index.md"));
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Move everything from ./garden-output/logseq-assets into ./site/public/garden
|
||||
await walk("./garden-output/logseq-assets", (dir, file, resolve) => {
|
||||
fs.copyFileSync(path.resolve(dir, file), path.resolve("./site/public/garden", ...path.basename(file).split('___')));
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Copy the guide-to-incrementals pages to the old locations so links don't break
|
||||
fs.mkdirSync('./site/guide-to-incrementals');
|
||||
fs.copyFileSync('./site/garden/guide-to-incrementals/index.md', './site/guide-to-incrementals/index.md');
|
||||
moveImportStatementUp('./site/guide-to-incrementals/index.md');
|
||||
fs.mkdirSync('./site/guide-to-incrementals/design');
|
||||
fs.mkdirSync('./site/guide-to-incrementals/design/criticism');
|
||||
fs.copyFileSync('./site/garden/guide-to-incrementals/navigating-criticism/index.md', './site/guide-to-incrementals/design/criticism/index.md');
|
||||
fs.mkdirSync('./site/guide-to-incrementals/ludology');
|
||||
fs.mkdirSync('./site/guide-to-incrementals/ludology/appeal-developers');
|
||||
fs.copyFileSync('./site/garden/guide-to-incrementals/appeal-to-developers/index.md', './site/guide-to-incrementals/ludology/appeal-developers/index.md');
|
||||
fs.mkdirSync('./site/guide-to-incrementals/ludology/appeal-gamers');
|
||||
fs.copyFileSync('./site/garden/guide-to-incrementals/appeal-to-players/index.md', './site/guide-to-incrementals/ludology/appeal-gamers/index.md');
|
||||
fs.mkdirSync('./site/guide-to-incrementals/ludology/content');
|
||||
// For what is content, also remove the - at the end
|
||||
fs.cpSync('./site/garden/guide-to-incrementals/what-is-content-', './site/garden/guide-to-incrementals/what-is-content', { recursive: true });
|
||||
fs.copyFileSync('./site/garden/guide-to-incrementals/what-is-content-/index.md', './site/guide-to-incrementals/ludology/content/index.md');
|
||||
fs.rmSync('./site/garden/guide-to-incrementals/what-is-content-', { recursive: true });
|
||||
fs.mkdirSync('./site/guide-to-incrementals/ludology/definition');
|
||||
fs.copyFileSync('./site/garden/guide-to-incrementals/defining-the-genre/index.md', './site/guide-to-incrementals/ludology/definition/index.md');
|
||||
|
||||
fs.mkdirSync('./site/now');
|
||||
fs.renameSync('./site/garden/now/index.md', './site/now/index.md');
|
||||
moveImportStatementUp('./site/now/index.md');
|
||||
|
||||
// Build changelog
|
||||
fs.mkdirSync("./site/changelog");
|
||||
|
||||
const feed = new Feed({
|
||||
title: "The Paper Pilot's Digital Garden Changelog",
|
||||
description: "A feed of updates made to my digital garden!",
|
||||
id: "https://www.thepaperpilot.org/changelog/",
|
||||
link: "https://www.thepaperpilot.org/changelog/",
|
||||
language: "en", // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
|
||||
// image: "http://example.com/image.png",
|
||||
// favicon: "http://example.com/favicon.ico",
|
||||
copyright: `All rights reserved ${new Date().getFullYear()}, The Paper Pilot`,
|
||||
// updated: new Date(2013, 6, 14), // optional, default = today
|
||||
// generator: "awesome", // optional, default = 'Feed for Node.js'
|
||||
feedLinks: {
|
||||
rss: "https://www.thepaperpilot.org/changelog/rss",
|
||||
json: "https://www.thepaperpilot.org/changelog/json",
|
||||
atom: "https://www.thepaperpilot.org/changelog/atom"
|
||||
},
|
||||
author: {
|
||||
name: "The Paper Pilot",
|
||||
email: "thepaperpilot@incremental.social",
|
||||
link: "https://www.thepaperpilot.org/"
|
||||
}
|
||||
});
|
||||
|
||||
const { stdout } = await exec('git log --after="2024-06-03T0:0:0+0000" --pretty=%H origin/master -- site/garden');
|
||||
const entries = await Promise.all(stdout.split("\n").filter(p => p).map(hash => new Promise(async (resolve) => {
|
||||
const { stdout: time } = await exec(`git show --quiet --format=%as ${hash}`);
|
||||
const { stdout: fullTime } = await exec(`git show --quiet --format=%ad ${hash}`);
|
||||
let { stdout: changes } = await exec(`git show --format="" --stat --relative ${hash} .`, { cwd: 'site/garden' });
|
||||
|
||||
changes = changes.replaceAll(/\/index.md/g, '');
|
||||
changes = changes.replaceAll(
|
||||
/(\| +[0-9]+ \+*)(-+)/g,
|
||||
'$1<span style="color:#BF616A">$2</span>');
|
||||
changes = changes.replaceAll(
|
||||
/(\| +[0-9]+ )(\++)/g,
|
||||
'$1<span style="color:#A3BE8C">$2</span>');
|
||||
const lines = changes.split('\n');
|
||||
const summary = lines[lines.length - 2];
|
||||
changes = lines.slice(0, -2).map(line => {
|
||||
const [page, changes] = line.split("|").map(p => p.trim());
|
||||
return `<tr><td><a href="/garden/${page}">${page}</a></td><td style="font-family: monospace; white-space: nowrap;">${changes}</td></tr>`;
|
||||
}).join("\n");
|
||||
|
||||
const commitLink = `https://code.incremental.social/thepaperpilot/pages/commit/${hash}`
|
||||
const content = `<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="align: center">Page</th>
|
||||
<th style="align: center">Changes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${changes}
|
||||
</tbody>
|
||||
</table>`;
|
||||
|
||||
feed.addItem({
|
||||
title: summary,
|
||||
id: commitLink,
|
||||
link: commitLink,
|
||||
description: summary,
|
||||
content,
|
||||
date: new Date(fullTime)
|
||||
});
|
||||
|
||||
resolve(
|
||||
`<article class="h-entry">
|
||||
<h2 class="p-name">${summary}</h2>
|
||||
<div class="e-content">
|
||||
<a class="u-url" href="${commitLink}">Pushed on <time class="dt-published" datetime="${fullTime}">${time}</time></a>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="align: center">Page</th>
|
||||
<th style="align: center">Changes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${changes}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</article>`);
|
||||
})));
|
||||
|
||||
let fd = fs.openSync("site/changelog/index.md", "w+");
|
||||
fs.writeSync(fd,
|
||||
`---
|
||||
title: Garden Changelog
|
||||
prev: false
|
||||
next: false
|
||||
---
|
||||
<section class="h-feed">
|
||||
<h1 class="p-name">Garden Changelog</h1>
|
||||
<p>This feed starts when I formatted the site to be a <a href="/garden/digital-gardens/">Digital Garden</a>. If you'd like to look further into this site's history, check <a href="https://code.incremental.social/thepaperpilot/pages/commits/branch/master">here</a>!</p>
|
||||
|
||||
${entries.join("\n\n")}
|
||||
</section>
|
||||
`);
|
||||
fs.closeSync(fd);
|
||||
|
||||
fs.mkdirSync("site/public/changelog");
|
||||
|
||||
fd = fs.openSync("site/public/changelog/rss", "w+");
|
||||
fs.writeSync(fd, feed.rss2());
|
||||
fs.closeSync(fd);
|
||||
|
||||
fd = fs.openSync("site/public/changelog/atom", "w+");
|
||||
fs.writeSync(fd, feed.atom1());
|
||||
fs.closeSync(fd);
|
||||
|
||||
fd = fs.openSync("site/public/changelog/json", "w+");
|
||||
fs.writeSync(fd, feed.json1());
|
||||
fs.closeSync(fd);
|
||||
|
||||
// Update commit info in footer
|
||||
const commitLink = (await exec(`git log -n 1 --format="https://code.incremental.social/thepaperpilot/pages/commit/%H"`)).stdout.replaceAll(/\n$/g, '');
|
||||
const commitTime = (await exec(`git log -n 1 --date=format:"%A, %B %d, %Y at %X" --format=%ad`)).stdout.replaceAll(/\n$/g, '');
|
||||
|
||||
fd = fs.openSync("site/.vitepress/theme/Layout.vue", "w+");
|
||||
let layoutData = fs.readFileSync("site/.vitepress/theme/Layout.vue.in").toString();
|
||||
layoutData = layoutData.replace(/COMMIT_LINK/g, commitLink);
|
||||
layoutData = layoutData.replace(/COMMIT_TIME/g, commitTime);
|
||||
fs.writeSync(fd, layoutData);
|
||||
fs.closeSync(fd);
|
||||
|
||||
// Write licenses to /licenses
|
||||
fd = fs.openSync("site/public/licenses.txt", "w+");
|
||||
const licenses = (await exec(`yarn licenses generate-disclaimer --frozen-lockfile`)).stdout;
|
||||
fs.writeSync(fd,
|
||||
`# Licenses
|
||||
|
||||
${licenses}
|
||||
|
||||
-----
|
||||
|
||||
The following software may be included in this product: webgl-noise. A copy of the source code may be downloaded from git+https://github.com/ashima/webgl-noise.git The software contains the following license and notice below:
|
||||
|
||||
Copyright (C) 2011 by Ashima Arts (Simplex noise)
|
||||
Copyright (C) 2011-2016 by Stefan Gustavson (Classic noise and others)
|
||||
|
||||
const fd = fs.openSync("public/licenses.txt", "w+");
|
||||
const licenses = await glf.getLicenseFileText("./package.json", {
|
||||
replace: {
|
||||
"rc@1.2.8": "./node_modules/rc/LICENSE.MIT",
|
||||
"bare-path@2.1.3": "./node_modules/bare-path/LICENSE",
|
||||
"type-fest@3.13.1": "https://raw.githubusercontent.com/sindresorhus/type-fest/refs/heads/main/license-mit",
|
||||
"@cloudflare/kv-asset-handler@0.3.4": "https://raw.githubusercontent.com/cloudflare/workers-sdk/refs/heads/main/LICENSE-MIT",
|
||||
"only@0.0.2": "https://raw.githubusercontent.com/tj/node-only/refs/heads/master/LICENSE"
|
||||
},
|
||||
exclude: [
|
||||
"@tresjs/nuxt"
|
||||
]
|
||||
});
|
||||
fs.writeSync(fd, licenses + `
|
||||
|
||||
-----------
|
||||
|
||||
The following npm package may be included in this product:
|
||||
|
||||
- @tresjs/nuxt
|
||||
|
||||
This package contains the following license and notice below:
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-present, (alvarosabu) Alvaro Saburido and Tres contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
The following software may be included in this product: webgl-noise. A copy of the source code may be downloaded from git+https://github.com/ashima/webgl-noise.git The software contains the following license and notice below:
|
||||
|
||||
Copyright (C) 2011 by Ashima Arts (Simplex noise)
|
||||
Copyright (C) 2011-2016 by Stefan Gustavson (Classic noise and others)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
|
@ -407,15 +74,15 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
-----
|
||||
|
||||
|
||||
The following software may be included in this product: Hero Patterns by Steve Schoger. A copy of the software may be downloaded from https://heropatterns.com. The software contains the following license and notice below:
|
||||
|
||||
|
||||
Attribution 4.0 International
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
|
@ -425,16 +92,16 @@ warranties regarding its licenses, any material licensed under their
|
|||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
|
@ -448,7 +115,7 @@ exhaustive, and do not form part of our licenses.
|
|||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
|
@ -465,11 +132,11 @@ exhaustive, and do not form part of our licenses.
|
|||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
|
||||
Creative Commons Attribution 4.0 International Public License
|
||||
|
||||
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution 4.0 International Public License ("Public License"). To the
|
||||
|
@ -478,10 +145,10 @@ granted the Licensed Rights in consideration of Your acceptance of
|
|||
these terms and conditions, and the Licensor grants You such rights in
|
||||
consideration of benefits the Licensor receives from making the
|
||||
Licensed Material available under these terms and conditions.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 1 -- Definitions.
|
||||
|
||||
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
|
@ -491,11 +158,11 @@ Section 1 -- Definitions.
|
|||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
|
||||
c. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
|
@ -503,29 +170,29 @@ Section 1 -- Definitions.
|
|||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
|
||||
d. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
|
||||
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
|
||||
f. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
|
||||
g. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
|
||||
h. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
|
||||
i. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
|
@ -533,39 +200,39 @@ Section 1 -- Definitions.
|
|||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
|
||||
j. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
|
||||
k. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
|
||||
a. License grant.
|
||||
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part; and
|
||||
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material.
|
||||
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
|
@ -577,31 +244,31 @@ Section 2 -- Scope.
|
|||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
|
||||
b. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
|
||||
b. Other rights.
|
||||
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
|
@ -609,94 +276,94 @@ Section 2 -- Scope.
|
|||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
|
||||
a. Attribution.
|
||||
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
|
||||
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
|
||||
4. If You Share Adapted Material You produce, the Adapter's
|
||||
License You apply must not prevent recipients of the Adapted
|
||||
Material from complying with this Public License.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database;
|
||||
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material; and
|
||||
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
|
@ -707,7 +374,7 @@ Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
|
@ -717,78 +384,78 @@ Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
|
||||
|
||||
|
||||
=======================================================================
|
||||
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
|
@ -805,7 +472,8 @@ to any of its public licenses or any other arrangements,
|
|||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.`);
|
||||
fs.closeSync(fd);
|
||||
fs.closeSync(fd);
|
||||
})();
|
||||
|
BIN
bun.lockb
Normal file
BIN
bun.lockb
Normal file
Binary file not shown.
22
clean.js
22
clean.js
|
@ -1,22 +0,0 @@
|
|||
const fs = require("fs");
|
||||
|
||||
if (fs.existsSync("./site/article")) {
|
||||
console.log("Cleaning old articles...")
|
||||
fs.rmSync("./site/article", { recursive: true });
|
||||
}
|
||||
if (fs.existsSync("./site/repost")) {
|
||||
console.log("Cleaning old reposts...")
|
||||
fs.rmSync("./site/repost", { recursive: true });
|
||||
}
|
||||
if (fs.existsSync("./site/bookmark")) {
|
||||
console.log("Cleaning old bookmarks...")
|
||||
fs.rmSync("./site/bookmark", { recursive: true });
|
||||
}
|
||||
if (fs.existsSync("./site/favorite")) {
|
||||
console.log("Cleaning old favorites...")
|
||||
fs.rmSync("./site/favorite", { recursive: true });
|
||||
}
|
||||
if (fs.existsSync("./site/reply")) {
|
||||
console.log("Cleaning old replies...")
|
||||
fs.rmSync("./site/reply", { recursive: true });
|
||||
}
|
23
components/Background.vue
Normal file
23
components/Background.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="background">
|
||||
<TresCanvas>
|
||||
<TresOrthographicCamera :position="[0, 0, 10]" />
|
||||
<TresAmbientLight :intensity="1" />
|
||||
<Suspense>
|
||||
<TilingCircuitBoard />
|
||||
</Suspense>
|
||||
</TresCanvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.background {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
24
components/Container.vue
Normal file
24
components/Container.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Header />
|
||||
<Paper>
|
||||
<slot />
|
||||
</Paper>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.container {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: 310px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.container {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
81
components/Footer.vue
Normal file
81
components/Footer.vue
Normal file
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<footer>
|
||||
<p>
|
||||
<span>
|
||||
CC {{ new Date().getFullYear() }} <a class="h-card" rel="me" href="/about"><img class="p-right" src="/me.jpg" alt="" />The Paper Pilot</a>.
|
||||
</span>
|
||||
<span>
|
||||
All original content is licensed <a rel="license" href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>.
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Any and all opinions listed here are my own and not representative of my employers; future, past and present.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>
|
||||
Check out my <a class="p-right" href="https://resume.incremental.social/thepaperpilot/thepaperpilot">resume</a>. (not seeking new opportunities)
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span>
|
||||
Site built from <a class="p-right" :href="`https://code.incremental.social/thepaperpilot/pages/commit/${buildCommitHash}`">this commit</a> on <time>{{ buildTime }}</time>.
|
||||
</span>
|
||||
<span>
|
||||
(Thorough) <NuxtLink to="https://www.thepaperpilot.org/licenses.txt">legal disclaimers</NuxtLink>.
|
||||
</span>
|
||||
</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { buildCommitHash, buildTime } = useRuntimeConfig().public;
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
footer {
|
||||
padding: 1em;
|
||||
font-size: small;
|
||||
background: var(--nord2);
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
p, a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
flex-flow: row wrap;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--nord8);
|
||||
}
|
||||
|
||||
p > span {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
p > span:has( + span) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
p > :not(span), span > * {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.p-right {
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
169
components/Header.vue
Normal file
169
components/Header.vue
Normal file
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<header>
|
||||
<NuxtLink to="/" class="home">The Paper Pilot</NuxtLink>
|
||||
<div class="separator" />
|
||||
<menu id="menu">
|
||||
<Search />
|
||||
<a href="https://moddingtree.com" class="profectus">Profectus</a>
|
||||
<a href="https://incremental.social/" class="incsoc">Incremental Social</a>
|
||||
<a href="https://code.incremental.social/thepaperpilot" rel="me" class="forgejo">
|
||||
<Icon name="simple-icons:forgejo" /><span class="mobile-only">Forgejo</span>
|
||||
</a>
|
||||
<a href="https://matrix.to/#/@thepaperpilot:incremental.social" rel="me" class="matrix">
|
||||
<Icon name="simple-icons:matrix" /><span class="mobile-only">Matrix</span>
|
||||
</a>
|
||||
<a href="https://incremental.social/u/thepaperpilot" rel="me" class="fediverse">
|
||||
<Icon name="ph:fediverse-logo" /><span class="mobile-only">Fediverse</span>
|
||||
</a>
|
||||
<div class="mobile-only close-instructions">
|
||||
Tap anywhere to close
|
||||
</div>
|
||||
</menu>
|
||||
<button class="mobile-menu-toggle">Socials</button>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Search from "./modals/Search.vue";
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
header {
|
||||
width: 800px;
|
||||
max-width: 95%;
|
||||
display: flex;
|
||||
padding: 8px 30px 0 30px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
menu {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.separator {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: unset;
|
||||
}
|
||||
|
||||
.home {
|
||||
background-color: #00ffff;
|
||||
font-size: x-large;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.profectus {
|
||||
background-color: #00ff00;
|
||||
}
|
||||
|
||||
.incsoc {
|
||||
background-color: #ff00ff;
|
||||
}
|
||||
|
||||
.forgejo {
|
||||
background-color: #ff7700;
|
||||
}
|
||||
|
||||
.matrix {
|
||||
background-color: #3c9a3c;
|
||||
}
|
||||
|
||||
.fediverse {
|
||||
background-color: #a75aff;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
background-color: red;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.close-instructions {
|
||||
color: var(--nord4) !important;
|
||||
margin-top: 24px !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
height: 36px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
padding: 0 8px;
|
||||
margin-bottom: -8px;
|
||||
color: var(--nord1);
|
||||
padding-top: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
button:hover,
|
||||
button:focus {
|
||||
margin-bottom: unset;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.iconify {
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
}
|
||||
|
||||
a::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#menu:has(~ .mobile-menu-toggle:focus) {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: var(--nord1);
|
||||
padding: 2em;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#menu > * {
|
||||
background-color: transparent !important;
|
||||
color: var(--nord6) !important;
|
||||
font-size: large;
|
||||
padding: 8px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
#menu > :not(.fediverse):not(.close-instructions) {
|
||||
border-bottom: solid 1px var(--nord4);
|
||||
}
|
||||
|
||||
#menu .iconify {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -7,14 +7,13 @@
|
|||
<TresCircleGeometry :args="[Math.min(sizes.width.value, sizes.height.value) / 2 * .9, 360]" />
|
||||
<TresShaderMaterial :vertexShader="vertexShader" :fragmentShader="fragmentShaderBorderless" :uniforms="uniforms" :blending="NormalBlending" :colorWrite="false" :depthWrite="false" :depthTest="false" :stencilWrite="true" :stencilRef="1" :stencilFunc="AlwaysStencilFunc" :stencilFail="KeepStencilOp" :stencilZFail="KeepStencilOp" :stencilZPass="ReplaceStencilOp" />
|
||||
</TresMesh>
|
||||
<Background :mask="1" />
|
||||
<TilingCircuitBoard :mask="1" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTresContext } from '@tresjs/core';
|
||||
import { AlwaysStencilFunc, KeepStencilOp, NormalBlending, ReplaceStencilOp, Vector3 } from "three";
|
||||
import Background from "./Background.vue";
|
||||
import noise from "./noise.glsl?raw";
|
||||
import noise from "~/assets/noise.glsl";
|
||||
|
||||
const { sizes } = useTresContext();
|
||||
|
4
components/NotFound.vue
Normal file
4
components/NotFound.vue
Normal file
|
@ -0,0 +1,4 @@
|
|||
<template>
|
||||
<h1>Page not found (404)</h1>
|
||||
<div>Perhaps you can find it by searching?</div>
|
||||
</template>
|
101
components/Pagination.vue
Normal file
101
components/Pagination.vue
Normal file
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<div class="pagination" v-if="totalPages > 1">
|
||||
<NuxtLink :to="`?page=1`"
|
||||
v-if="currentPage !== 1 && currentPage !== 2 && useNavigation">
|
||||
<span>1</span>
|
||||
<Icon name="material-symbols:first-page" />
|
||||
</NuxtLink>
|
||||
<button @click="emits('switchPage', 1)" v-else-if="currentPage !== 1 && currentPage !== 2">
|
||||
<span>1</span>
|
||||
<Icon name="material-symbols:first-page" />
|
||||
</button>
|
||||
|
||||
<NuxtLink :to="`?page=${currentPage - 1}`" rel="prev"
|
||||
v-if="currentPage !== 1 && useNavigation">
|
||||
{{ currentPage - 1 }}
|
||||
</NuxtLink>
|
||||
<button @click="emits('switchPage', currentPage - 1)" v-else-if="currentPage !== 1">
|
||||
{{ currentPage - 1 }}
|
||||
</button>
|
||||
|
||||
<div>{{ currentPage }}</div>
|
||||
|
||||
<NuxtLink :to="`?page=${currentPage + 1}`" rel="next"
|
||||
v-if="currentPage !== totalPages && useNavigation">
|
||||
{{ currentPage + 1 }}
|
||||
</NuxtLink>
|
||||
<button @click="emits('switchPage', currentPage + 1)"
|
||||
v-else-if="currentPage !== totalPages">
|
||||
{{ currentPage + 1 }}
|
||||
</button>
|
||||
|
||||
<NuxtLink :to="`?page=${totalPages}`"
|
||||
v-if="currentPage !== totalPages && currentPage !== totalPages - 1 && useNavigation">
|
||||
<Icon name="material-symbols:last-page" />
|
||||
<span>{{ totalPages }}</span>
|
||||
</NuxtLink>
|
||||
<button @click="emits('switchPage', totalPages)"
|
||||
v-else-if="currentPage !== totalPages && currentPage !== totalPages - 1">
|
||||
<Icon name="material-symbols:last-page" />
|
||||
<span>{{ totalPages }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
useNavigation?: boolean;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
switchPage: [page: number];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.pagination {
|
||||
margin-left: 50%;
|
||||
margin-top: 29px;
|
||||
margin-bottom: -1px;
|
||||
transform: translateX(-50%);
|
||||
display: inline-flex;
|
||||
border: solid 1px var(--nord0);
|
||||
border-radius: 8px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.pagination > * {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.pagination > :not(:last-child) {
|
||||
border-right: solid 1px var(--nord0);
|
||||
}
|
||||
|
||||
.pagination button,
|
||||
.pagination a {
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
background-image: linear-gradient( to right, rgba(255, 225, 0, 0.05), rgba(255, 225, 0, 0.35) 4%, rgba(255, 225, 0, 0.15) );
|
||||
box-decoration-break: clone;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pagination button:hover,
|
||||
.pagination a:hover {
|
||||
background-image: linear-gradient( to right, rgba(255, 225, 0, 0.1), rgba(255, 225, 0, 0.7) 4%, rgba(255, 225, 0, 0.3) );
|
||||
}
|
||||
|
||||
.pagination button :first-child,
|
||||
.pagination a :first-child {
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
117
components/Paper.vue
Normal file
117
components/Paper.vue
Normal file
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<div class="paper">
|
||||
<div class="margin-lines" />
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.paper {
|
||||
width: 800px;
|
||||
max-width: 95%;
|
||||
color: var(--nord1);
|
||||
background: linear-gradient(to bottom, var(--nord6) 29px, var(--nord4) 1px);
|
||||
background-size: 100% 30px;
|
||||
line-height: 30px;
|
||||
box-shadow: 0 0 10px 1px #0003 !important;
|
||||
padding: 30px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.paper::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
background: radial-gradient(5px 5px at 50% 50%, var(--nord1) 0%, var(--nord1) 100%, #0000 100%);
|
||||
background-size: 100% 30px;
|
||||
}
|
||||
|
||||
.paper::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -15px;
|
||||
width: 30px;
|
||||
z-index: 1;
|
||||
background: linear-gradient(to bottom, transparent 11px, #333 11px, #333 13px, transparent 13px,transparent 17px, #333 17px, #333 19px, transparent 19px);
|
||||
background-size: auto;
|
||||
background-size: 100% 30px;
|
||||
}
|
||||
|
||||
.margin-lines {
|
||||
border-left: double 7px var(--nord4);
|
||||
flex: 0 0 1;
|
||||
margin-top: -30px;
|
||||
margin-bottom: -30px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Un-scoped css used to set the default for all child elements -->
|
||||
<style lang="scss">
|
||||
.paper * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.paper a {
|
||||
color: unset;
|
||||
text-decoration: unset;
|
||||
text-underline-offset: unset;
|
||||
}
|
||||
|
||||
.paper :not(h2, h3, h4, h5, h6) > a,
|
||||
.paper .contains-link a {
|
||||
padding: 0 0.4em;
|
||||
border-radius: 0.8em 0.3em;
|
||||
background: transparent;
|
||||
background-image: none;
|
||||
background-image: linear-gradient( to right, rgba(255, 225, 0, 0.05), rgba(255, 225, 0, 0.35) 4%, rgba(255, 225, 0, 0.15) );
|
||||
box-decoration-break: clone;
|
||||
|
||||
&:hover {
|
||||
color: unset;
|
||||
background-image: linear-gradient( to right, rgba(255, 225, 0, 0.1), rgba(255, 225, 0, 0.7) 4%, rgba(255, 225, 0, 0.3) );
|
||||
}
|
||||
}
|
||||
|
||||
.paper :is(h1, h2, h3, h4, h5, h6):not(.contains-link) > a:not(:has(> a)):hover:after {
|
||||
content: "🔗"
|
||||
}
|
||||
|
||||
.paper h1 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.paper :not(h1) + :is(h1, h2, h3, h4, h5, h6) {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.paper p + p {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.paper blockquote {
|
||||
border-left: dashed 2px var(--nord9);
|
||||
padding-left: 1em;
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
.paper .h-entry {
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,9 +1,19 @@
|
|||
<template>
|
||||
<TresGroup ref="groupRef" v-if="renderer" :renderOrder="1">
|
||||
<TresMesh v-for="i in rows * cols" :position="[((i % cols) - cols / 2) * 304, (Math.floor((i - 1) / cols) - rows / 2) * 304, 1]">
|
||||
<TresShapeGeometry :args="[shapes]" />
|
||||
<TresShaderMaterial :vertexShader="vertexShader" :fragmentShader="fragmentShader" :uniforms="uniforms" :blending="AdditiveBlending" :stencilWrite="mask != null" :stencilRef="mask ?? 0" :stencilFunc="EqualStencilFunc" :stencilFail="KeepStencilOp" :stencilZFail="KeepStencilOp" :stencilZPass="KeepStencilOp" />
|
||||
</TresMesh>
|
||||
<TresMesh v-for="i in rows * cols" :position="[((i % cols) - cols / 2) * 304, (Math.floor((i - 1) / cols) - rows / 2) * 304, 1]">
|
||||
<TresShapeGeometry :args="[shapes]" />
|
||||
<TresShaderMaterial
|
||||
:vertexShader="vertexShader"
|
||||
:fragmentShader="fragmentShader"
|
||||
:uniforms="uniforms"
|
||||
:blending="AdditiveBlending"
|
||||
:stencilWrite="mask != null"
|
||||
:stencilRef="mask ?? 0"
|
||||
:stencilFunc="EqualStencilFunc"
|
||||
:stencilFail="KeepStencilOp"
|
||||
:stencilZFail="KeepStencilOp"
|
||||
:stencilZPass="KeepStencilOp" />
|
||||
</TresMesh>
|
||||
</TresGroup>
|
||||
</template>
|
||||
|
||||
|
@ -11,8 +21,8 @@
|
|||
import { useLoader, useRenderLoop, useTresContext } from '@tresjs/core';
|
||||
import { AdditiveBlending, EqualStencilFunc, Group, KeepStencilOp, Vector2, Vector3 } from "three";
|
||||
import { SVGLoader } from "three/examples/jsm/loaders/SVGLoader.js";
|
||||
import { computed, onMounted, onUnmounted, ref, shallowRef } from "vue";
|
||||
import noise from "./noise.glsl?raw";
|
||||
import noise from "~/assets/noise.glsl";
|
||||
import cicuitBoard from "assets/circuit-board.svg";
|
||||
|
||||
const { renderer, sizes } = useTresContext();
|
||||
|
||||
|
@ -20,8 +30,18 @@ const props = defineProps<{
|
|||
mask?: number;
|
||||
}>();
|
||||
|
||||
// Setup window listeners
|
||||
onMounted(() => {
|
||||
window.addEventListener("mousemove", updateMousePos);
|
||||
window.addEventListener("mouseout", handleMouseLeave);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("mousemove", updateMousePos);
|
||||
window.removeEventListener("mouseout", handleMouseLeave);
|
||||
});
|
||||
|
||||
// Load SVG
|
||||
const { paths } = await useLoader(SVGLoader, '/circuit-board.svg');
|
||||
const { paths } = await useLoader(SVGLoader, cicuitBoard);
|
||||
const shapes = paths.map(path => SVGLoader.createShapes(path)).reduce((acc, curr) => [...acc, ...curr]);
|
||||
|
||||
const rows = computed(() => Math.ceil(sizes.height.value / 304));
|
||||
|
@ -49,16 +69,6 @@ function handleMouseLeave(event: MouseEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
// Setup window listeners
|
||||
onMounted(() => {
|
||||
window.addEventListener("mousemove", updateMousePos);
|
||||
window.addEventListener("mouseout", handleMouseLeave);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("mousemove", updateMousePos);
|
||||
window.removeEventListener("mouseout", handleMouseLeave);
|
||||
});
|
||||
|
||||
const { onLoop } = useRenderLoop();
|
||||
onLoop(({ elapsed }) => {
|
||||
if (groupRef.value) {
|
18
components/asides/Aside.vue
Normal file
18
components/asides/Aside.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<ClientOnly>
|
||||
<Teleport to="#asides">
|
||||
<div class="aside">
|
||||
<slot />
|
||||
</div>
|
||||
</Teleport>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.aside {
|
||||
background: #feff9c !important;
|
||||
padding: 1em;
|
||||
color: var(--nord1);
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
65
components/asides/AsidesContainer.vue
Normal file
65
components/asides/AsidesContainer.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div id="asides" :class="{ open }" />
|
||||
<div class="asides-toggle" @click="open = !open">
|
||||
<Icon name="material-symbols:menu-open" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
#asides {
|
||||
width: 300px;
|
||||
height: calc(100% - 180px);
|
||||
position: fixed;
|
||||
left: calc((100% - 310px - min(800px, .95 * (100% - 310px))) / 2);
|
||||
top: 18px;
|
||||
padding-bottom: 30px;
|
||||
mask-image: linear-gradient(to bottom, transparent 0px, white 18px, white calc(100% - 30px), transparent);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.asides-toggle {
|
||||
display: none;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 45px;
|
||||
background-color: #ff7eb9;
|
||||
z-index: 1;
|
||||
font-size: 150%;
|
||||
color: var(--nord1);
|
||||
width: 45px;
|
||||
height: 30px;
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
padding-left: 4px;
|
||||
transition: width .25s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.asides-toggle:hover,
|
||||
.open + .asides-toggle {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.asides-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#asides {
|
||||
z-index: 1;
|
||||
right: 0;
|
||||
left: unset;
|
||||
top: 75px;
|
||||
height: calc(100% - 300px);
|
||||
transition: right .25s ease;
|
||||
}
|
||||
|
||||
#asides:not(.open) {
|
||||
right: -300px;
|
||||
}
|
||||
}
|
||||
</style>
|
42
components/asides/Favorites.vue
Normal file
42
components/asides/Favorites.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<Aside>
|
||||
<h3>Recommended Pages</h3>
|
||||
<ul>
|
||||
<li v-for="favorite in favorites">
|
||||
<NuxtLink :to="favorite.link">{{ favorite.text }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Posts</h3>
|
||||
<ul>
|
||||
<li><NuxtLink to="/tags">Tags</NuxtLink></li>
|
||||
<li><NuxtLink to="/types">Types</NuxtLink></li>
|
||||
<li>
|
||||
<NuxtLink to="/posts" rel="alternate" type="text/mf2+html" title="All posts">
|
||||
All posts
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Meta</h3>
|
||||
<ul>
|
||||
<li><NuxtLink to="/about">About me</NuxtLink></li>
|
||||
<li><NuxtLink to="/garden/now">/now</NuxtLink></li>
|
||||
<li><NuxtLink to="/changelog">Garden changelog</NuxtLink></li>
|
||||
</ul>
|
||||
</Aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Aside from "./Aside.vue";
|
||||
import favorites from "~/assets/favorites.json";
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--nord8);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
46
components/garden/GardenHeader.vue
Normal file
46
components/garden/GardenHeader.vue
Normal file
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<p class="garden-header">
|
||||
{{ doc.wordCount }} words, ~{{ Math.ceil(doc.wordCount / 183) }} minute read.
|
||||
Planted <a
|
||||
:href="`https://code.incremental.social/thepaperpilot/pages/commit/${doc.published.hash}`">
|
||||
<time :datetime="publishedTime" :title="publishedTime">{{ publishedDate }}</time>
|
||||
</a>.
|
||||
<template v-if="doc.edited">
|
||||
{{ doc.edited }}
|
||||
Last tended to <a
|
||||
:href="`https://code.incremental.social/thepaperpilot/pages/commit/${doc.edited.hash}`">
|
||||
<time :datetime="editedTime" :title="editedTime">{{ editedDate }}</time>
|
||||
</a>.
|
||||
</template>
|
||||
</p>
|
||||
<ul class="garden-header inline tagged" v-if="doc.tags">
|
||||
<li v-for="(link, text) in doc.tags">
|
||||
<NuxtLink :to="link">{{ text }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ParsedContent } from '@nuxt/content';
|
||||
|
||||
const props = defineProps<{
|
||||
doc: ParsedContent;
|
||||
}>();
|
||||
|
||||
const publishedTime = computed(() => new Date(props.doc.published.timestamp).toLocaleString());
|
||||
const publishedDate = computed(() => new Date(props.doc.published.timestamp).toLocaleDateString());
|
||||
|
||||
const editedTime = computed(() => new Date(props.doc.edited.timestamp).toLocaleString());
|
||||
const editedDate = computed(() => new Date(props.doc.edited.timestamp).toLocaleDateString());
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.garden-header {
|
||||
margin-top: -30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.tagged::before {
|
||||
content: "Tagged";
|
||||
}
|
||||
</style>
|
19
components/garden/ReferencedBy.vue
Normal file
19
components/garden/ReferencedBy.vue
Normal file
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<template v-if="doc.referencedBy">
|
||||
<h2 id="referenced-by">
|
||||
<NuxtLink href="#referenced-by">Pages that reference "{{ doc.title }}":</NuxtLink>
|
||||
</h2>
|
||||
<ul class="inline">
|
||||
<li v-for="(link, text) in doc.referencedBy">
|
||||
<NuxtLink :to="link">{{ text }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ParsedContent } from '@nuxt/content';
|
||||
defineProps<{
|
||||
doc: ParsedContent;
|
||||
}>();
|
||||
</script>
|
47
components/garden/TableOfContents.vue
Normal file
47
components/garden/TableOfContents.vue
Normal file
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<Aside>
|
||||
<h3>Outline</h3>
|
||||
<NuxtLink to="#top" class="to-top">
|
||||
<Icon name="material-symbols:vertical-align-top" /> Top
|
||||
</NuxtLink>
|
||||
<ul>
|
||||
<li v-for="({ id, text }) in doc.body.toc.links">
|
||||
<NuxtLink :to="`#${id}`">{{ text }}</NuxtLink>
|
||||
</li>
|
||||
<li v-if="doc.referencedBy">
|
||||
<NuxtLink to="#referenced-by">Pages that reference "{{ doc.title }}"</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</Aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Aside from "~/components/asides/Aside.vue";
|
||||
import type { ParsedContent } from '@nuxt/content';
|
||||
|
||||
defineProps<{
|
||||
doc: ParsedContent;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--nord8);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.to-top {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
display: block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
30
components/garden/Tagged.vue
Normal file
30
components/garden/Tagged.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<Aside v-if="doc.taggedBy">
|
||||
<h3>Pages tagged "{{ doc.title }}":</h3>
|
||||
<ul>
|
||||
<li v-for="(link, text) in doc.taggedBy">
|
||||
<NuxtLink :to="link">{{ text }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</Aside>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Aside from "~/components/asides/Aside.vue";
|
||||
import type { ParsedContent } from '@nuxt/content';
|
||||
|
||||
defineProps<{
|
||||
doc: ParsedContent;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--nord8);
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
53
components/modals/Modal.vue
Normal file
53
components/modals/Modal.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<ClientOnly>
|
||||
<Teleport to="#teleports" v-if="open">
|
||||
<div class="container" @click.self="emits('close')">
|
||||
<Paper class="centered-paper">
|
||||
<slot />
|
||||
</Paper>
|
||||
</div>
|
||||
<div class="closer" />
|
||||
</Teleport>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
open: boolean;
|
||||
}>();
|
||||
|
||||
const emits = defineEmits<{
|
||||
close: [];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.closer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 9;
|
||||
background: black;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.centered-paper {
|
||||
margin-top: 5vh;
|
||||
margin-bottom: 5vh;
|
||||
margin-left: calc((100vw - min(800px, 95vw)) / 2);
|
||||
cursor: auto;
|
||||
}
|
||||
</style>
|
111
components/modals/Search.vue
Normal file
111
components/modals/Search.vue
Normal file
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<div class="search" @click="open = true">
|
||||
<Icon name="material-symbols:search" />Search
|
||||
</div>
|
||||
<Modal :open="open" @close="open = false">
|
||||
<h1>Search</h1>
|
||||
<input ref="inputRef" v-model="search" placeholder="Your query..." autofocus>
|
||||
|
||||
<h2>
|
||||
<template v-if="results.length > POSTS_PER_PAGE">
|
||||
{{ (currentPage - 1) * POSTS_PER_PAGE + 1 }}
|
||||
-
|
||||
{{ Math.min(currentPage * POSTS_PER_PAGE, results.length) + 1 }} of
|
||||
</template>
|
||||
{{ results.length }} result{{ results.length === 1 ? '' : 's' }}
|
||||
</h2>
|
||||
|
||||
<div v-for="post in currentPageResults">
|
||||
<NuxtLink :to="getUrlFromId(post.id)" @click="open = false">{{ post.title }}</NuxtLink>
|
||||
<div v-html="post.content" />
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<Pagination :current-page="currentPage"
|
||||
:total-pages="Math.ceil(results.length / POSTS_PER_PAGE)"
|
||||
@switch-page="page => currentPage = page" />
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Modal from "./Modal.vue";
|
||||
|
||||
const POSTS_PER_PAGE = 20;
|
||||
|
||||
const open = ref(false);
|
||||
const search = ref('');
|
||||
const currentPage = ref(1);
|
||||
const inputRef = ref(null);
|
||||
|
||||
const results = await searchContent(search);
|
||||
const currentPageResults = computed(() => results.value.slice(
|
||||
(currentPage.value - 1) * POSTS_PER_PAGE,
|
||||
currentPage.value * POSTS_PER_PAGE));
|
||||
|
||||
// Too many requests, and I'm not sure we really need this information anyways
|
||||
// Especially since garden posts can't use VuePost
|
||||
// const { data: posts, status, error, refresh } = await useAsyncData("search", () =>
|
||||
// Promise.all(currentPageResults.value.map(result => {
|
||||
// if (result.id.startsWith("/posts")) {
|
||||
// console.log(result.id.split("/").slice(1))
|
||||
// return queryContent(...result.id.split("/").slice(1)).findOne() as Promise<Post>;
|
||||
// }
|
||||
// const indexHash = result.id.indexOf("#");
|
||||
// const id = indexHash === -1 ? result.id : result.id.slice(0, indexHash);
|
||||
// return queryContent(id).findOne() as Promise<ParsedContent>
|
||||
// })
|
||||
// ), {
|
||||
// watch: [currentPageResults],
|
||||
// dedupe: "cancel",
|
||||
// lazy: true,
|
||||
// default: () => []
|
||||
// });
|
||||
|
||||
// watch(error, console.error);
|
||||
|
||||
// TODO debounce
|
||||
// Ideally checking status first
|
||||
// watch(search, refresh);
|
||||
|
||||
watch(inputRef, inputRef => {
|
||||
if (inputRef) {
|
||||
inputRef.focus();
|
||||
}
|
||||
});
|
||||
|
||||
function getUrlFromId(id: string) {
|
||||
if (id.startsWith("/posts")) {
|
||||
const [_, _2, kind, y, m, d, timestamp] = id.split("/");
|
||||
return `/${kind}/${timestamp}`;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.search {
|
||||
background-color: yellow;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
height: 36px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
padding: 0 8px;
|
||||
margin-bottom: -8px;
|
||||
color: var(--nord1);
|
||||
padding-top: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.search:hover {
|
||||
margin-bottom: unset;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
input {
|
||||
outline: none;
|
||||
border: none;
|
||||
border-bottom: solid 2px var(--nord1);
|
||||
background: none;
|
||||
height: 28px;
|
||||
}
|
||||
</style>
|
104
components/posts/Avatar.vue
Normal file
104
components/posts/Avatar.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div class="avatar p-author h-card">
|
||||
<div class="photo">
|
||||
<img class="u-photo" :src="author.image" />
|
||||
<div v-if="action" class="action">
|
||||
<Icon :name="POST_TYPES[action].icon" />
|
||||
</div>
|
||||
</div>
|
||||
<a class="u-url p-name" :href="author.url">{{ author.name }}</a>
|
||||
<time class="dt-published" :datetime="timeString" :title="timeString">
|
||||
{{ dateString }}
|
||||
</time>
|
||||
<ul class="syndications" v-if="syndications.length > 0">
|
||||
<Syndication v-for="url in syndications" :url="url" />
|
||||
</ul>
|
||||
<ul class="tags" v-if="tags.length > 0">
|
||||
<li v-for="tag in tags">
|
||||
<NuxtLink :to="`/tags/${tag}`">{{ tag }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Syndication from './Syndication.vue';
|
||||
import { POST_TYPES } from '~/post_types';
|
||||
import type { Author, PostType } from '~/types';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
author?: Author;
|
||||
published: number;
|
||||
syndications?: string[];
|
||||
tags?: string[];
|
||||
action?: PostType;
|
||||
}>(), {
|
||||
author: () => ({
|
||||
name: "The Paper Pilot",
|
||||
url: "/about",
|
||||
image: "/me.jpg"
|
||||
}),
|
||||
syndications: () => [],
|
||||
tags: () => []
|
||||
});
|
||||
|
||||
const timeString = computed(() => new Date(props.published).toLocaleString());
|
||||
const dateString = computed(() => new Date(props.published).toLocaleDateString());
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.avatar {
|
||||
width: 120px;
|
||||
margin-right: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.avatar > * {
|
||||
text-align: center;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.photo {
|
||||
position: relative;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.u-photo {
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
margin: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.action {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 10px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
background: var(--nord2);
|
||||
color: var(--nord4);
|
||||
font-size: x-large;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tags li {
|
||||
margin-left: 0;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.tags li::before {
|
||||
content: "#";
|
||||
}
|
||||
</style>
|
92
components/posts/Card.global.vue
Normal file
92
components/posts/Card.global.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<div class="card" :class="{ full: title == null && description == null && url == null }">
|
||||
<img v-if="image" class="u-photo" :src="image" :alt="alt" />
|
||||
<h2 v-if="title" class="p-name">
|
||||
<a class="u-url" :href="url" v-if="url">{{ title }}</a>
|
||||
<template v-else>{{ title }}</template>
|
||||
</h2>
|
||||
<div class="description e-content" :class="{ fadeDescription }"
|
||||
v-if="description" v-html="description" />
|
||||
<a class="u-url" v-if="url && !title" :href="url">{{ url }}</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
title?: string;
|
||||
image?: string;
|
||||
alt?: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
fadeDescription?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.card {
|
||||
padding: 15px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card.full {
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
height: 11lh;
|
||||
}
|
||||
|
||||
.card::before, .card::after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
border: 50px solid transparent;
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.card::before {
|
||||
top: -60px;
|
||||
left: -65px;
|
||||
box-shadow: 0px -7px 6px -9px black;
|
||||
}
|
||||
|
||||
.card::after {
|
||||
bottom: -60px;
|
||||
right: -65px;
|
||||
box-shadow: 0px 7px 6px -9px black;
|
||||
}
|
||||
|
||||
.card:not(.full) {
|
||||
height: unset;
|
||||
background: white;
|
||||
clip-path: polygon(-30px -30px, calc(100% + 30px) -30px, calc(100% + 30px) calc(100% - 75px), calc(100% - 75px) calc(100% + 30px), -30px calc(100% + 30px));
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card:not(.full)::before {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.card:not(.full)::after {
|
||||
border: 75px solid transparent;
|
||||
bottom: -105px;
|
||||
right: -105px;
|
||||
}
|
||||
|
||||
.card img {
|
||||
max-height: 11lh;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
background: white;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.card.full img {
|
||||
clip-path: polygon(18px 0%, 100% 0, 100% calc(100% - 18px), calc(100% - 18px) 100%, 0 100%, 0% 18px);
|
||||
}
|
||||
|
||||
.fadeDescription {
|
||||
max-height: 180px;
|
||||
mask-image: linear-gradient(to bottom, white 150px, transparent);
|
||||
}
|
||||
</style>
|
48
components/posts/DatedContentWarning.vue
Normal file
48
components/posts/DatedContentWarning.vue
Normal file
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class="old-warning" v-if="yearsDiff > 2">
|
||||
This is an old post made over {{Math.floor(yearsDiff)}} years ago!<br/>
|
||||
My views <NuxtLink to="/garden/my-political-journey">change over time</NuxtLink> and my older timeline posts may not reflect my current views!
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
published?: number;
|
||||
}>();
|
||||
|
||||
const yearsDiff = computed(() => props.published ? Math.floor((Date.now() - props.published) / (1000 * 60 * 60 * 24 * 365)) : 0);
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.old-warning {
|
||||
background: red;
|
||||
padding: 15px;
|
||||
margin-bottom: 30px;
|
||||
font-size: 24px;
|
||||
background: white;
|
||||
border: red dashed 10px;
|
||||
position: relative;
|
||||
padding-left: 130px;
|
||||
min-height: 150px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.old-warning::before {
|
||||
content: "STOP";
|
||||
position: absolute;
|
||||
--o: calc(50%* tan(-22.5deg));
|
||||
clip-path: polygon(var(--o) 50%, 50% var(--o), calc(100% - var(--o)) 50%, 50% calc(100% - var(--o)));
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: red;
|
||||
left: 15px;
|
||||
top: 15px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
padding-top: 32px;
|
||||
font-size: 32px;
|
||||
font-family: 'Roboto Mono';
|
||||
font-weight: 900;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
59
components/posts/Post.vue
Normal file
59
components/posts/Post.vue
Normal file
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="h-entry" :class="{ 'h-cite': isParent }">
|
||||
<DatedContentWarning v-if="!isParent" :published="post.published" />
|
||||
<PostHeader v-if="!isParent" :kind="post.kind" :id="post.published" />
|
||||
<Post v-if="post.parent" :post="post.parent" :isParent="true" />
|
||||
<div class="post" :class="{
|
||||
'h-cite': ['favorite', 'bookmark', 'repost'].includes(post.kind),
|
||||
'u-in-reply-to': post.parent != null,
|
||||
'u-like-of': post.kind === 'favorite',
|
||||
'u-repost-of': post.kind === 'repost',
|
||||
'u-bookmark': post.kind === 'bookmark'
|
||||
}">
|
||||
<Avatar v-if="post.author || selfAuthored"
|
||||
:published="post.published"
|
||||
:author="post.author"
|
||||
:action="selfAuthored ? post.kind : undefined"
|
||||
:syndications="post.syndications"
|
||||
:tags="post.tags" />
|
||||
<Card :title="post.title"
|
||||
:image="post.image"
|
||||
:alt="post.imageAlt"
|
||||
:description="post.description"
|
||||
:url="post.url"
|
||||
:fadeDescription="fadeDescription" />
|
||||
</div>
|
||||
<template v-if="post.replies?.length ?? 0 > 0">
|
||||
<h2>Comments</h2>
|
||||
<Reply v-for="reply in post.replies" :reply="reply" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Avatar from "./Avatar.vue";
|
||||
import Card from "./Card.global.vue";
|
||||
import DatedContentWarning from "./DatedContentWarning.vue";
|
||||
import PostHeader from "./PostHeader.vue";
|
||||
import Reply from "./Reply.vue";
|
||||
import type { Post } from "~/types";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
post: Post;
|
||||
isParent?: boolean;
|
||||
fadeDescription?: boolean;
|
||||
}>(), {
|
||||
isParent: false,
|
||||
fadeDescription: false
|
||||
});
|
||||
|
||||
const selfAuthored = computed(() =>
|
||||
props.post.author == null && ['article', 'reply'].includes(props.post.kind));
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.post {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
}
|
||||
</style>
|
33
components/posts/PostHeader.vue
Normal file
33
components/posts/PostHeader.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div class="post-header">
|
||||
<Icon :name="POST_TYPES[kind].icon" />
|
||||
I <NuxtLink class="u-url" :to="`/${kind}/${id}`">{{ POST_TYPES[kind].verb }}</NuxtLink>
|
||||
<span>this post on <time class="dt-published" :datetime="timeString" :title="timeString">
|
||||
{{ dateString }}
|
||||
</time>:</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { POST_TYPES } from '~/post_types';
|
||||
import type { PostType } from '~/types';
|
||||
|
||||
const props = defineProps<{
|
||||
kind: PostType,
|
||||
id: number;
|
||||
}>();
|
||||
|
||||
const timeString = computed(() => new Date(props.id).toLocaleString());
|
||||
const dateString = computed(() => new Date(props.id).toLocaleDateString());
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.post-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.iconify {
|
||||
margin: 0 4px;
|
||||
}
|
||||
</style>
|
18
components/posts/Reply.vue
Normal file
18
components/posts/Reply.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<template>
|
||||
<div class="u-comment h-cite">
|
||||
<Avatar :published="reply.published"
|
||||
:author="reply.author"
|
||||
action="reply"
|
||||
:syndications="[reply.syndication]" />
|
||||
<Card :description="reply.body" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Avatar from "./Avatar.vue";
|
||||
import Card from "./Card.global.vue";
|
||||
import type { Reply } from "~/types";
|
||||
defineProps<{
|
||||
reply: Reply;
|
||||
}>();
|
||||
</script>
|
21
components/posts/Syndication.vue
Normal file
21
components/posts/Syndication.vue
Normal file
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<li><a :href="url"><Icon :name="icon" /></a></li>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
url: string;
|
||||
}>();
|
||||
|
||||
const icon = computed(() => {
|
||||
const hostname = new URL(props.url).hostname;
|
||||
if (hostname.endsWith("reddit.com")) {
|
||||
return "ph:reddit-logo";
|
||||
} else if (hostname.endsWith("youtube.com")) {
|
||||
return "ph:youtube-logo";
|
||||
} else if (hostname.endsWith("itch.io")) {
|
||||
return "cib:itch-io";
|
||||
}
|
||||
return "material-symbols:open-in-new";
|
||||
});
|
||||
</script>
|
92
content/garden/abolitionism.md
Normal file
92
content/garden/abolitionism.md
Normal file
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
title: "Abolitionism"
|
||||
wordCount: 1493
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Anarchism: /garden/anarchism
|
||||
Consensus Democracy: /garden/consensus-democracy
|
||||
My Political Beliefs: /garden/my-political-beliefs
|
||||
---
|
||||
|
||||
I'm a supporter of the police abolition movement, which calls for police and prisons to be abolished. It argues that there are many inherent problems with policing and incarcerating people that cannot be fixed with just further training or restrictions - the entire system must be entirely abolished. In this way, it is a more extreme version of the police reform or defund the police movements. The movement also posits that there are alternatives to policing and incarceration that can be more effective at reducing crime.
|
||||
|
||||
## What is police abolition?
|
||||
|
||||
Just to make sure we're all on the same page as to what police abolition means and involves, I'll be using some definitions from [Critical Reach](https://criticalresistance.org/mission-vision/not-so-common-language/):
|
||||
|
||||
> The **prison industrial complex (PIC)** is a term we use to describe the overlapping interests of government and industry that use surveillance, policing, and imprisonment as solutions to economic, social and political problems.
|
||||
|
||||
> **PIC abolition** is a political vision with the goal of eliminating imprisonment, policing, and surveillance and creating lasting alternatives to punishment and imprisonment.
|
||||
|
||||
I'll generally just say police or prison abolition, but I'm still referring to pieces of PIC abolition.
|
||||
|
||||
## Why abolish police?
|
||||
|
||||
There are a variety of reasons for abolishing the police, from its controversial origins to its pervasive discrimination to its overall lack of effectiveness. Ultimately though, I would argue this movement stems from anarchistic principles and values. Anarchism posits that no person should hold power over another (a simplification sufficient for this document), which would include the use of force or imprisonment. Anarchists argue the State has no right to exist, let alone that it has no right to call it's violence legitimate. From this premise, any form of policing or incarceration is unjust. However, let's explore additional problems within the specific context of the US:
|
||||
|
||||
### Origins of policing
|
||||
|
||||
The initial implementation of the police in the US was inspired both by the systems they experienced across the pond as well as the bands of [slave catchers](https://www.americanbar.org/groups/crsj/publications/human_rights_magazine_home/civil-rights-reimagining-policing/how-you-start-is-how-you-finish/) that were present at the time. The fact policing and incarceration as concepts had to be invented and introduced to people who were already existing just fine is telling enough, and the fact they were introduced to bring slaves back to their owners is even more telling. These origins have ingrained racism into the entire system, and they are still evident today when looking at racial profiling, incarceration rates, etc.
|
||||
|
||||
Throughout their history police have, in practice, protected "property" (which slaves were considered to be at the time), not people. In a broader sense, they were a tool of the capitalist elite. That's why the Supreme Court [ruled](https://www.nytimes.com/2005/06/28/politics/justices-rule-police-do-not-have-a-constitutional-duty-to-protect.html) that police do not actually have an obligation to protect nor serve citizens and why early cops were also [strike breakers](https://minnesotareformer.com/2020/07/07/minneapolis-police-were-once-used-as-strike-breakers/).
|
||||
|
||||
While the origins of an institution don't necessarily dictate how it is structured and operated hundreds of years later, they do give insight into how the institution was designed and what purposes/roles it was actually filling. Today, thanks to various forms of "[copaganda](https://www.independent.co.uk/arts-entertainment/tv/features/police-brutality-tv-copaganda-brooklyn-nine-nine-paw-patrol-cops-george-floyd-a9610956.html)" in media, there's rhetoric that people should support and appreciate the police, that they are fair and effective and protect us from crime and villainy. As we'll continue to describe, I think the police are much more similar to their roots than the fiction they're portrayed as.
|
||||
|
||||
### Discriminating against the poor
|
||||
|
||||
The police are designed to reinforce the capitalist status quo; the "law and order" they uphold is less so to do with ensuring life is fair and safe for all, but rather to keep the working class in check so the capitalist class are safe and secure. This is most evident by looking at how many laws primarily affect the poor and how rarely laws are enforced when the perpetrator is a capitalist.
|
||||
|
||||
> "The law, in its majestic equality, forbids rich and poor alike to sleep under bridges, to beg in the streets, and to steal their bread."
|
||||
[Anatole France](https://www.goodreads.com/quotes/361132-the-law-in-its-majestic-equality-forbids-rich-and-poor)
|
||||
|
||||
Property crime lead to over [750,000 arrests](https://ucr.fbi.gov/crime-in-the-u.s/2019/crime-in-the-u.s.-2019/topic-pages/tables/table-43) in 2019. Wage theft, which according to a 2014 study may account for up to [$50 Billion](https://www.epi.org/press/wage-theft-costs-american-workers-50-billion/) stolen, is a civil matter that doesn't lead to any arrests. In fact, white collar misdeeds are _often_ not classified as criminal offenses - that's why after the 2008 housing market crash that devastated millions, only [1 banker was arrested](https://www.nytimes.com/2014/05/04/magazine/only-one-top-banker-jail-financial-crisis.html) and it was for something relatively trivial compared to the actual crash.
|
||||
|
||||
Even when it _is_ a criminal offense, white collar crime is [rarely punished](https://www.psychologytoday.com/us/blog/wicked-deeds/201704/why-elite-white-collar-criminals-are-rarely-punished). A lot of societal elites get away with [horrendous misdeeds](https://www.independent.co.uk/voices/jeffrey-epstein-suicide-prison-case-trump-billionaires-capitalism-a9056091.html) essentially out in the open. In fact, prosecuting the rich is so out of the norm that a rookie cop [arresting Justin Timberlake](https://nypost.com/2024/06/22/us-news/dumbass-justin-timberlake-ignored-warning-minutes-before-dwi-bust-from-same-cop-who-ended-up-arresting-him/) caused a kerfuffle online because of just how much people like Justin aren't _supposed_ to get arrested.
|
||||
|
||||
> As a side note, there's a double standard here where rich are treated differently from poor people when they're the perpetrator of the crime, but not when they're the victim. If you look online for opinions on whether stealing from the rich to give to the poor is actually justified, you'll get a fairly overwhelming response that "theft is theft" and the context within which the theft occurred is not relevant. I think it's important to keep in mind the context of how the rich have rigged the system to consolidate wealth in the hands of as few as possible, and the impact that has had on society. On other issues, people are just fine dealing with nuance, like how violence is justified when it's self defense. I believe our society has been deliberately shaped to have the value that the victim being poor vs rich shouldn't matter, specifically because it benefits the rich.
|
||||
|
||||
### Racism
|
||||
|
||||
The PIC has not shaken off the racism ingrained within its roots. Arrests [are not proportional](https://naacp.org/resources/criminal-justice-fact-sheet) to actual crime rates, disproportionately affecting people of color - _including_ when [the cops themselves](https://apnews.com/article/law-enforcement-us-news-tyre-nichols-memphis-racism-c36c88bdca39d801eee9bd247fcc0c71) are black. Additionally, laws have been introduced throughout the history of the US that were explicitly designed to affect minorities, like the Jim Crow laws or the entirety of [Nixon's war on drugs](https://www.vox.com/2016/3/22/11278760/war-on-drugs-racism-nixon) (a victimless "crime").
|
||||
|
||||
The police are an active threat to black people, who are [3x as likely](https://abcnews.go.com/US/latest-research-tells-us-racial-bias-policing/story?id=70994421) to be shot and killed from the police without presenting a threat. That makes cops people to be avoided and who cannot be relied upon in an emergency. Even calling the police yourself is a risk.
|
||||
|
||||
### Cost
|
||||
|
||||
The government spends [hundreds of billions](https://www.urban.org/policy-centers/cross-center-initiatives/state-and-local-finance-initiative/state-and-local-backgrounders/criminal-justice-police-corrections-courts-expenditures) of dollars on the incarceral system, an absolutely profound amount of money that could make enormous differences had it been put to social services instead.
|
||||
|
||||
The prison system has [for profit private prisons](https://www.sentencingproject.org/reports/private-prisons-in-the-united-states/) with [minimum occupancy clauses](https://aublr.org/2017/11/private-prison-contracts-minimum-occupancy-clauses/) that cost taxpayers money if we don't arrest enough people. This is a gross financial incentive to arrest more people, and it disincentivizes actual rehabilitation. These contribute to the US' [incredibly high recidivism rates](https://bjs.ojp.gov/library/publications/2018-update-prisoner-recidivism-9-year-follow-period-2005-2014).
|
||||
|
||||
### Ineffectiveness at stopping crime
|
||||
|
||||
Most crimes performed are out of necessity, not malice. For example, few would be stealing baby formula or bread if it weren't for our economic system introducing artificial scarcity to keep prices going up.
|
||||
|
||||
America has the highest incarceration rate in the world, for a variety of the reasons mentioned in this document. Yet, the fear of punishment and imprisonment does not seem to have the effect of discouraging crime. Indeed, [increasing incarceration does not decrease crime](https://interrogatingjustice.org/ending-mass-incarceration/explainer-incarceration-rates-vs-crime-rates/).
|
||||
|
||||
### Criminal officers
|
||||
|
||||
Officers themselves perpetrate [a lot of crime](https://policecrime.bgsu.edu/) (and I suspect there's a lot of police crimes that doesn't get reported), including [1/3 of all murders that involve a stranger](https://granta.com/violence-in-blue/), and are typically protected by a corrupt system with the [strongest union](https://www.nytimes.com/2020/06/06/us/police-unions-minneapolis-kroll.html) in the nation (often attributed to the union serving the interests of capital, rather than workers like labor unions). In particular, officers have [incredibly high rates](https://policing.umhistorylabs.lsa.umich.edu/files/original/5528df2d5b5c33cfeaa930146cfe20ccb5cad0cd.pdf) of interspousal conflicts. Therefore, [abolitionism is the only solution to gender-based violence](https://gal-dem.com/how-does-police-abolition-work/).
|
||||
|
||||
## How to abolish police
|
||||
|
||||
There are many ways to reduce the need of police until it's eventually zero. Immediately, perpetratore of victimless crimes should be let out of prison. You can also reduce most root causes of crime, rather than spending the money on incarcerating the perpetrators
|
||||
|
||||
### Crime reduction
|
||||
|
||||
Abolitionists still want to ensure public safety, just not through policing and incarceration. They believe, since most crime is not born of malice, that improving society by ensuring everyone's needs are met would evaporate the majority of crimes as well - at least as many as are prevented by the current system. Police are incredibly over funded, enough to buy things like [surplus tanks](https://www.aclu.org/news/national-security/towns-dont-need-tanks-they-have-them) from the US military. That money can instead go to social programs that would solve the root causes that lead to crimes.
|
||||
|
||||
Societies can and will find alternative ways of preventing any specific crime. An often used example is if you have a drunk friend who is about to drive home, you typically will help get them a ride (driving yourself if you're sober, or calling them a cab or uber otherwise) rather than calling the police on them. Abolitionism finds policing as only required by those who lack the political imagination to find other solutions - basically, cure the diseases rather than treat the symptoms. At a systemic level, drunk driving can be reduced or eliminated by improving public transit.
|
||||
|
||||
### Dangerous incidents
|
||||
|
||||
With the police abolished, there are likely to still be cases where an individual is a danger to themselves or others. For these situations, there are ideas on how to resolve it without state sanctioned violence, that typically focus on local communities setting up alternative resources. Mobile crisis teams are one such alternative, although not without similar concerns regarding their authority to forcibly incarcerate people in mental health hospitals. There are a lot of alternatives out there, but they're typically specific to local communities and don't really have a "one size fits all" solution. Here's an [article](https://roarmag.org/essays/mental-health-system-abolition/) about a couple such initiatives, and [this article linked earlier](https://gal-dem.com/how-does-police-abolition-work/) also discusses some specific local initiatives.
|
||||
|
||||
Another criticism of _prison_ abolitionism specifically is [What do you do with the murderers and rapists?](https://medium.com/@amparker/what-about-the-rapists-and-murderers-7a81955b772c), but as that article describes: we already don't incarcerate many murderers and rapists, especially the significant amount of murderers and rapists who are also officers. It argues the incarceral system already fails in this regard, and focuses on punishing the perpetrator rather than helping the victims, which is something we could do without incarceration.
|
||||
|
||||
## Further reading
|
||||
|
||||
I have not finished reading it yet myself, but I recommend [We Do This ‘Til We Free Us](https://www.haymarketbooks.org/books/1664-we-do-this-til-we-free-us) by Mariame Kaba based on [this NBC article on the book and author](https://www.nbcnews.com/think/opinion/abolishing-police-prisons-lot-more-practical-critics-claim-ncna1258659).
|
||||
|
||||
[How police make up the law](https://youtu.be/Vica_-UEg0Q) by Philosophy Tube is a very well produced look at how police got their de facto supreme authority, and it's implications. They have several other videos on the police I recommend, and any other videos they've made :).
|
13
content/garden/activitypub.md
Normal file
13
content/garden/activitypub.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "ActivityPub"
|
||||
wordCount: 8
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
Decentralized: /garden/decentralized
|
||||
referencedBy:
|
||||
Fediverse: /garden/fediverse
|
||||
---
|
||||
|
||||
[ActivityPub](https://activitypub.rocks) is a protocol for [Federated Social Media](/garden/fediverse)
|
22
content/garden/advent-incremental.md
Normal file
22
content/garden/advent-incremental.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: "Advent Incremental"
|
||||
wordCount: 104
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
Profectus: /garden/profectus
|
||||
---
|
||||
|
||||
Play it [here](https://thepaperpilot.org/advent)!
|
||||
|
||||
An [Open Source](/garden/open-source) game made in [Profectus](/garden/profectus) over the course of 1 month by myself and other devs I know in the Incremental Games community!
|
||||
|
||||
I had the idea of an advent-style game that unlocked new pieces of content every real-life day a couple days before December started.
|
||||
|
||||
This was one of the most hectic months of my life!
|
||||
|
||||
I'm super happy with how it turned out. It ended up being way more ambitious than I anticipated but the end result is super large and awesome!
|
||||
|
||||
The [TV Tropes](https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/AdventIncremental) page on this game mentions some of the cool things about this game
|
32
content/garden/anarchism.md
Normal file
32
content/garden/anarchism.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
title: "Anarchism"
|
||||
wordCount: 1043
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Individualism: /garden/individualism
|
||||
Local Communities: /garden/local-communities
|
||||
My Political Beliefs: /garden/my-political-beliefs
|
||||
Representative Democracy: /garden/representative-democracy
|
||||
---
|
||||
|
||||
I like and appreciate a lot of the anarchist values and would like to see them influence policy. Anarchists believe that states are inherently immoral, and societies should be structured to have as minimal of a hierarchy as possible. This entails focusing on [Local Communities](/garden/local-communities) and spreading power as thinly as possible, to avoid the possibility of individuals becoming corrupt and abusing their power.
|
||||
|
||||
Anarchism is anti-authoritarian, and explicitly denounces any use of violence to enforce rules, thus requiring [Police Abolition](/garden/abolitionism). By similar logic, anarchists tend to oppose imperialism and capitalism and the respective hierarchies they create. There are those who consider themselves "anarcho-capitalists" without realizing (or are ignoring) the hierarchies created by wealth inequality. These are incompatible views, and the person is likely actually authoritarian.
|
||||
|
||||
Democracy is a form of electoralism that is typically compatible with Anarchism, although some definitions of anarchism disallow any form of rules, even when agreed upon unanimously. There are different forms of democracy, with [Direct Democracy](/garden/direct-democracy) and [Consensus Democracy](/garden/consensus-democracy) being the most popular variants that are compatible with anarchism. The US government is a [Representative Democracy](/garden/representative-democracy), which is NOT anarchistic. Representatives abstract policy making from the views of the people. If we're supposed to vote on the representative that will most closely vote to how we feel on all issues, then the theoretical perfect representative would just be ourselves - and at that point, we should just be voting on the issues directly. Therefore if striving for anarchism, you should not use a representative democracy as in its theoretical ideal its still only just as good as any other variant of Democracy, and in practice will be much worse.
|
||||
|
||||
A core principle of anarchism is "free association", referring to how individuals should be able to freely move between anarchist organizations to find one they're compatible with, or even frequently move between several communities they like. This can cause concerns of encouraging segregation, so I think its important for these communities to encourage diversity as much as they can. They can also refuse to associate with other bigoted communities, theoretically discouraging those bigoted views through social and material isolation.
|
||||
|
||||
Anarchistic organizations can still appoint roles to people. For example, if a nation like America were to be made anarchistic, it would likely maintain some roles of the President, such as that of Commander-In-Chief. It is primarily the law making and enforcing that would need to be democratized, and of course making sure those appointed roles are elected democratically.
|
||||
|
||||
Anarchism relies on the idea that there are enough individuals motivated to systemically fix problems that they will do so without direct personal gain (beyond the problem being solved), and that others will not block those efforts, even if the policy won't help them in particular. I believe this would and does hold true. I believe our society being filled with greedy individuals is primarily caused by our society rewarding greed. Without the profit motive and returning to a culture of collaboration and mutual aid, greed would for the most part become a non-factor in policy making. Those who are already at the top of the hierarchy don't want to lose their position, and have thus been propagandizing that hierarchies are necessary/inevitable, and even just. This concept gets discussed in [The Alt-Right Playbook: Always a Bigger Fish](https://www.youtube.com/watch?v=agzNANfNlTs).
|
||||
|
||||
Democracies where the people vote on individual issues are often criticized by citing the US' current low turnout rates during elections. I believe the rates are more indicative of a lack of faith in electoralism, and in any case its not a reason to be alarmed that policies would be dictated by a minority of the population. The low turnout can work in favor of direct and consensus democracies, as it means it only takes a few motivated individuals to improve society or block proposals that would worsen it. The fact getting engaged in politics takes time and effort means you're less likely to see people blocking policies in bad faith out of contrarianism. In theory, any consolidation of power would also negatively affect most people, which would motivate them to block the proposal. That makes anarchism very stable.
|
||||
|
||||
In contrast to [Neoliberalism](/garden/neoliberalism), anarchism calls for systemic solutions to problems, rather than reliance on individual charity. In America, charity has never been sufficient to end hunger or homelessness. Anarchists and leftists believe we need systemic issues to these problems, such as making food, shelter, and healthcare freely accessible to all. Technology has made it trivial to provide for everyone. In America, there is more food waste than it would take to feed all the hungry, and enough vacant houses to shelter all the homeless. The scarcity is artificial, created by those at the top of the hierarchy.
|
||||
|
||||
Places of work can also be democratized! Typical American corporations are very hierarchical, with a few hands at the top having ultimate say over the company - what it does, how much it pays its employees, who it fires, etc. Worker's co-operatives are alternatives to corporations that are entirely worker owned and operated, with a flat hierarchy. This makes technology work in employees' favor, rather than owners' (since the employees are the owners). For example, lets say some technological innovation made employees twice as productive. Under a capitalist structure, the owners would have no reason to increase compensation based on the increased production, and in fact would be discouraged from doing so. They'd likely either use the increased productivity to sell more products, or half the workforce to cut down on significant expenditures. Under a socialist structure, the needs and desires of the employees are most important, so workers are likely to either see increased compensation due to their increased productivity, or reduced hours without a reduction in compensation. The co-operative could still decide to also just utilize the increased productivity without reducing hours nor increasing compensation, but the decision to do so would have been consensually made by the workers themselves, not their boss.
|
||||
|
||||
China recently enacted a [policy](https://www.taylorwessing.com/en/insights-and-events/insights/2024/01/employees-participation-in-corporate-governance-under-the-revised-chinese-company-law) to make all of its corporations operate democratically, with a "Employee Assembly" made up of up to 100 workers, that can decide on things like firing supervisors or, in big companies, appointing 1/3 of the board of directors. That goes a long way in democratizing the remaining private businesses in China.
|
14
content/garden/art-is-never-complete.md
Normal file
14
content/garden/art-is-never-complete.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: "Art is Never Complete"
|
||||
wordCount: 92
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Guide to Incrementals/What is Content?: /garden/guide-to-incrementals/what-is-content
|
||||
---
|
||||
|
||||
> Art is never finished, only abandoned.
|
||||
> \- Leonardo Da Vinci
|
||||
|
||||
This quote intrigues me and feels quite relatable. I've struggled with perfectionism (and in fact it's a large theme in Kronos), something Leonardo famously did as well. This quote feels like an exit ticket to getting out of perfectionist thinking - if art is _never_ finished, then when its "done" (abandoned) is arbitrary. Perhaps that's why Leonardo left so many WIPs behind in his journal - if the point at which you abandon them is arbitrary, then why not abandon projects early?
|
27
content/garden/artificial-intelligence.md
Normal file
27
content/garden/artificial-intelligence.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: "Artificial Intelligence"
|
||||
wordCount: 101
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Command Palettes: /garden/command-palettes
|
||||
---
|
||||
|
||||
Catch all term that refers to many different things
|
||||
|
||||
Generative AI
|
||||
- Models trained on large amounts of existing human made content in order to produce more of that content
|
||||
- Copyright concerns over how training data is obtained
|
||||
- [What Ethical AI Really Means](https://nebula.tv/videos/philosophytube-what-ethical-ai-really-means/) by Philosophy Tube
|
||||
- > Ethical AI cannot exist under Capitalism
|
||||
- Common Examples
|
||||
- LLMs like ChatGPT
|
||||
- Some also take voice and video input, like [Gemini](https://gemini.google.com) or [ChatGPT-4o](https://openai.com/index/hello-gpt-4o/)
|
||||
- Art generators like [Dall-E](https://openai.com/index/dall-e-3/) or [Midjourney](https://www.midjourney.com/home)
|
||||
|
||||
Human + AI cooperation
|
||||
- ["Cyborgs"](https://www.patreon.com/posts/cyborgs-85486143) by Nicky Case
|
||||
- Personal AI assistants
|
||||
- [Personal vs Personalized AI](https://doc.searls.com/2024/05/10/personal-vs-personalized/)
|
||||
- [Home-Cooked Software and Barefoot Developers](https://maggieappleton.com/home-cooked-software) discusses how language models can help individuals build personal specialized software
|
17
content/garden/atproto.md
Normal file
17
content/garden/atproto.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "ATProto"
|
||||
wordCount: 31
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
Decentralized: /garden/decentralized
|
||||
referencedBy:
|
||||
Fediverse: /garden/fediverse
|
||||
---
|
||||
|
||||
The [AT Protocol](https://atproto.com) is a protocol for [Federated Social Media](/garden/fediverse)
|
||||
|
||||
Currently only used by [Bluesky](https://bsky.app)
|
||||
|
||||
In comparison to other [Fediverse](/garden/fediverse) protocols, ATProto is designed for a small number of large instances
|
22
content/garden/babble-buds.md
Normal file
22
content/garden/babble-buds.md
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: "Babble Buds"
|
||||
wordCount: 113
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
---
|
||||
|
||||
[Babble Buds](http://babblebuds.xyz) is a tool for creating puppets and interacting with puppets controlled by others on a shared stage
|
||||
|
||||
> Note: I need to move the website off replit because of their monetization strategy changing. In the meantime, you can check it out from its [github repository](https://github.com/thepaperpilot/babble-buds)
|
||||
|
||||
Inspired by Puppet Pals by Robert Moran
|
||||
|
||||
Intended for use in RPG Campaigns
|
||||
|
||||
The renderer was separated into its own project, [babble.js](https://github.com/thepaperpilot/babble.js), so it could be used for stuff like cutscenes
|
||||
|
||||
I ported the engine to C# and used it for the cutscenes in [Dice Armor](/garden/dice-armor)
|
||||
- I don't believe I ever separated it out into its own project, but you can find the code [here](https://github.com/sreynoldsdesign/dice_armor/tree/master/Assets/Scripts/babble.cs)
|
15
content/garden/capture-the-citadel.md
Normal file
15
content/garden/capture-the-citadel.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: "Capture the Citadel"
|
||||
wordCount: 39
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
---
|
||||
|
||||
A 3D VR re-envisioning of a Slay the Spire-style game by Anthony Lawn and Grant Barbee for their VR class in college's final project.
|
||||
|
||||
For more details, visit [Grant's page on the game](https://grantcbarbee.github.io/conquer-the-citadel.html).
|
||||
|
||||
:postsCard{image="/garden/screenshot_1717381273245_0.png" alt="screenshot.png"}
|
14
content/garden/chat-glue.md
Normal file
14
content/garden/chat-glue.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: "Chat Glue"
|
||||
wordCount: 23
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Commune: /garden/commune
|
||||
The Small Web: /garden/the-small-web
|
||||
---
|
||||
|
||||
A theoretical chat system designed to solve the problems of transcribing branching conversations into linear timelines.
|
||||
|
||||
Defined by the [Chatting with Glue](https://a9.io/glue-comic/) comic.
|
13
content/garden/chromatic-lattice.md
Normal file
13
content/garden/chromatic-lattice.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "Chromatic Lattice"
|
||||
wordCount: 7
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Fedi v2: /garden/fedi-v2
|
||||
Incremental Social: /garden/incremental-social
|
||||
/now: /garden/now
|
||||
---
|
||||
|
||||
A multiplayer game I have in development :)
|
23
content/garden/chronological.md
Normal file
23
content/garden/chronological.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
title: "Chronological"
|
||||
wordCount: 73
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Freeform vs Chronological Dichotomy: /garden/freeform-vs-chronological-dichotomy
|
||||
---
|
||||
|
||||
A collection of information that is tied to its creation or edit date
|
||||
|
||||
Part of the [Freeform vs Chronological Dichotomy](/garden/freeform-vs-chronological-dichotomy)
|
||||
|
||||
Anything with a "timeline" or "feed" is considered chronological
|
||||
- Even if there's algorithmic sortings that take things other than creation or edit date into account!
|
||||
|
||||
Chronological displays are less suitable as stores of knowledge ([Digital Gardens](/garden/digital-gardens))
|
||||
|
||||
Social media overuses timelines and feeds
|
||||
|
||||
RSS feeds work really well with this form of content
|
11
content/garden/cinny.md
Normal file
11
content/garden/cinny.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: "Cinny"
|
||||
wordCount: 3
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Incremental Social: /garden/incremental-social
|
||||
---
|
||||
|
||||
[Cinny](https://cinny.in) is an [Open Source](/garden/open-source) web client for the [Matrix](/garden/matrix) messaging protocol
|
24
content/garden/command-palettes.md
Normal file
24
content/garden/command-palettes.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: "Command Palettes"
|
||||
wordCount: 117
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
---
|
||||
|
||||
Command palettes are a design pattern where apps expose functionality through a search bar
|
||||
|
||||
Typing what you want is almost certainly easier and faster than finding the action in some submenu or remembering an arcane hotkey
|
||||
- Especially with fuzzy search that also looks through descriptions of actions
|
||||
- Command palettes scale very well with large amounts of actions
|
||||
|
||||
[Artificial Intelligence](/garden/artificial-intelligence) will make command palettes increasingly powerful
|
||||
- Eventually these may become conversational interfaces
|
||||
|
||||
Maggie Appleton discusses this pattern in her article on [Command K Bars](https://maggieappleton.com/command-bar)
|
||||
- The name comes from the fact many apps use the ctrl/cmd k shortcut to open the command palette
|
||||
|
||||
Many softwares I use have some form of command palette
|
||||
- Linear
|
||||
- [Logseq](/garden/logseq)
|
||||
- Visual Studio Code
|
37
content/garden/commune.md
Normal file
37
content/garden/commune.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "Commune"
|
||||
wordCount: 144
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Federated Identity: /garden/federated-identity
|
||||
/now: /garden/now
|
||||
Webrings: /garden/webrings
|
||||
Weird: /garden/weird
|
||||
---
|
||||
|
||||
An [Open Source](/garden/open-source) [Matrix](/garden/matrix) web client built to be better for communities than anything else out there
|
||||
- Currently in development
|
||||
- Exposes certain channels such that they are web indexable
|
||||
- Will include features like [Chat Glue](/garden/chat-glue) and communal [Digital Gardens](/garden/digital-gardens)
|
||||
|
||||
Created by [Erlend Sogge Heggen](https://writing.exchange/@erlend), a ex-employee from Discourse
|
||||
- Maintains the [Commune Blog](https://blog.commune.sh) with great write ups on the issues of the modern web, social media, etc. and how they can be improved (by Commune or related projects)
|
||||
- Also maintains a [Personal Blog](https://blog.erlend.sh) about similar topics
|
||||
|
||||
The Commune community is very interested in various topics and how they can relate together:
|
||||
- [Federated Identity](/garden/federated-identity)
|
||||
- [Personal Web](/garden/the-small-web)
|
||||
- [Digital Gardens](/garden/digital-gardens)
|
||||
- [Social Media](/garden/social-media)
|
||||
- The common themes here are they want these things [Decentralized](/garden/decentralized) and [Freeform](/garden/freeform)
|
||||
- They're also building [Weird](/garden/weird) to make several of these more accessible
|
||||
|
||||
Related projects:
|
||||
- [@laxla@tech.lgbt](https://tech.lgbt/@laxla) is creating Gimli, a federated discord alternative
|
||||
- Built on ActivityPub
|
||||
- "Guild-based" in ways matrix is not?
|
||||
- Will integrate with F3 as well
|
||||
- Wants to handle blogging as well
|
||||
- Certainly seems similar to Commune's message gardening concept
|
18
content/garden/consensus-democracy.md
Normal file
18
content/garden/consensus-democracy.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: "Consensus Democracy"
|
||||
wordCount: 162
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Anarchism: /garden/anarchism
|
||||
My Political Beliefs: /garden/my-political-beliefs
|
||||
---
|
||||
|
||||
A form of democracy similar to [Direct Democracy](/garden/direct-democracy) but with higher requirements for passing policies, typically requiring unanimity or near-unanimity. This helps reduce (although doesn't eliminate) the possibility of a majority group oppressing a minority group.
|
||||
|
||||
Consensus democracy encourages and requires innovative solutions to problems (similar to how [Police Abolition](/garden/abolitionism)) and pragmatic compromises. However, this can make them susceptible to "design by committee" and can make policies impossibly difficult to pass for large groups of people.
|
||||
|
||||
Since consensus democracy doesn't scale well, larger governments could be structured as a federation of smaller governments. The smaller governments still use consensus democracy, but the federation only adopts policies that a super-majority of the smaller governments have agreed upon. Alternatively, the federation could specifically ask the local governments for policy proposals, then use [Direct Democracy](/garden/direct-democracy) to decide whether to approve it or not, still requiring a super-majority.
|
||||
|
||||
For policies that still are unable to pass federally, local governments could form coalitions that organize larger-scale initiatives between several districts. For example, this could empower efforts like transit systems between districts.
|
24
content/garden/davey-wreden.md
Normal file
24
content/garden/davey-wreden.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: "Davey Wreden"
|
||||
wordCount: 37
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
taggedBy:
|
||||
Ivy Road: /garden/ivy-road
|
||||
The Beginner's Guide: /garden/the-beginner-s-guide
|
||||
Wanderstop: /garden/wanderstop
|
||||
referencedBy:
|
||||
Ivy Road: /garden/ivy-road
|
||||
The Beginner's Guide: /garden/the-beginner-s-guide
|
||||
---
|
||||
|
||||
Projects:
|
||||
- The Stanley Parable
|
||||
- [The Beginner's Guide](/garden/the-beginner-s-guide)
|
||||
- [Ivy Road](/garden/ivy-road)
|
||||
|
||||
Talks and Interviews:
|
||||
- LATER [Tone Control 20: Davey Wreden](https://www.idlethumbs.net/tonecontrol/episodes/davey-wreden-1)
|
||||
- [I played The Stanley Parable with the game's creator](https://www.youtube.com/watch?v=REnFIJhVA-g)
|
||||
- [Davey Wreden: Playing Stories - Aalto University Games Now!](https://www.youtube.com/watch?v=bKMAJ8vOMDg)
|
35
content/garden/decentralized.md
Normal file
35
content/garden/decentralized.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
title: "Decentralized"
|
||||
wordCount: 80
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
taggedBy:
|
||||
ActivityPub: /garden/activitypub
|
||||
ATProto: /garden/atproto
|
||||
Federated Identity: /garden/federated-identity
|
||||
Fediverse: /garden/fediverse
|
||||
Nostr: /garden/nostr
|
||||
referencedBy:
|
||||
Commune: /garden/commune
|
||||
Fedi v2: /garden/fedi-v2
|
||||
Matrix: /garden/matrix
|
||||
Social Media: /garden/social-media
|
||||
---
|
||||
|
||||
Something with no central source of authority
|
||||
|
||||
Common examples:
|
||||
- RSS
|
||||
- Email
|
||||
- The [Fediverse](/garden/fediverse)
|
||||
|
||||
In practice, the "pick a server" problem causes email and the fediverse to trend towards a handful of large servers that still suffer from some of the issues of centralization
|
||||
|
||||
Advantages over centralization:
|
||||
- Data ownership
|
||||
- Increased privacy
|
||||
- No rules to follow
|
||||
- Can fully customize your experience
|
||||
- No single entity can make the experience worse for everyone
|
||||
- Anyone and everyone can try their hand at improving the ecosystem
|
55
content/garden/dice-armor.md
Normal file
55
content/garden/dice-armor.md
Normal file
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
title: "Dice Armor"
|
||||
wordCount: 963
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
referencedBy:
|
||||
Babble Buds: /garden/babble-buds
|
||||
---
|
||||
|
||||
Download it [here](https://drive.google.com/open?id=18rwqEIdMChdGtB-9LdI4wiqeM5C5ViOL)
|
||||
|
||||
Dice Armor is a game that started development as a semester-long project by a team of nine: a producer, a creative director, a narrative writer, an artist, two programmers, and 3 game designers. The information here is about my contributions as the lead programmer over the semester because I can show off stuff like the editor scripts I wrote. I was doing everything from interface coding, editor scripts, integrating Babble Buds, and of course, everything related to the gameplay itself. To date I'm still the lead programmer for the game; for more up-to-date information on the current state of the game please visit the official site.
|
||||
|
||||
The build available here was created for showing off at the end of the semester, and as such has some buttons present to make the game easier to skip parts of the game to see all the content: You start with all the dice in the game already in the shop, there's a button to give yourself free money to buy these dice with, and in the duel, there are buttons to force a win or a loss, which can be used to skip the tutorial (not recommended for first-time players).
|
||||
|
||||
:postsCard{image="/garden/da2_1717378483173_0.png" alt="Tutorial"}
|
||||
|
||||
Dice Armor is a dice dueling game. Players can use abilities, flip dice, and attack each other to win in a dice game that puts chance into the hands of the players. This is what the dueling scene looks like, with a tutorial cutscene happening on top to guide the player through the basics. Also, all the dice are constructed dynamically, using quaternion math to figure out the placement of each component relative to the face it is going on. The die in the middle has one of the player' and opponents' portraits on each of its sides.
|
||||
|
||||
:postsCard{image="/garden/editors_1717378509527_0.png" alt="Editors"}
|
||||
|
||||
For many of the objects I've created, I've made scriptable objects so that game designers can add and modify them easily. Additionally, I would create custom inspectors for the objects to help make them as easy to understand and edit as possible. The opponent's artificial intelligence is made up of many strategies, in a prioritized list. When it is the opponents' turn they go through each strategy and check if they can be run, and if so then the opponent performs the strategy and starts back over at the top of the list of strategies. The + sign under the list of strategies opens an organized dropdown of all the various strategies.
|
||||
|
||||
:postsCard{image="/garden/simulator_1717378525890_0.jpg" alt="Simulator"}
|
||||
|
||||
In addition to custom inspector code, I've created new tools for the editor for our game designers to use. This is a duel simulator that will take two opponents and simulate an arbitrary number of duels between them, and output the results and summarize them for you, much much quicker than manually going through the duels, even with an absurdly high timeScale. This will become incredibly useful in making balance changes and testing new dice against existing sets. This is a screenshot of it in edit mode, but in play mode it removes the "Dueling Managers" field and will use whatever the current duel balance settings are, allowing for the GDs to test freely in play mode without worrying about undoing all their changes afterward.
|
||||
|
||||
:postsCard{image="/garden/da1_1717378469912_0.png" alt="da1.png"}
|
||||
|
||||
I created the Babble Buds puppet editor and ported the rendering library I wrote for it to C# so it could be used in Unity. Dice Armor has a full campaign using cutscenes made using the Babble Buds cutscene editor, taking advantage of its support for custom commands and fields to control things like talking, giving the player dice and money, starting duels, and controlling player progression through the story.
|
||||
|
||||
:postsCard{image="/garden/da6_1717379962786_0.png" alt="Action Wheel"}
|
||||
|
||||
When a cutscene ends, its final command is to either start a duel or set the next cutscene in the story. In the latter case, there is an additional field for what to call the next cutscene, and what location it takes place. The cutscene is then added to the player's save file, and when they visit the city locations are greyed out until they have at least one action to do there. Each location has a dynamically populated action wheel with a custom range of acceptable angles.
|
||||
|
||||
:postsCard{image="/garden/da7_1717379991458_0.png" alt="Shop"}
|
||||
|
||||
The dice shop is dynamically populated by a list of dice available to the player, which can be changed during cutscenes, and is checked against the dice owned by the player to generate sold-out indicators. On the left, the player can choose to filter the options down to a single dice effect, which also updates the "Buy All" button to buy only all the dice in the current filter.
|
||||
|
||||
:postsCard{image="/garden/da8_1717380011914_0.png" alt="Inventory"}
|
||||
|
||||
The inventory works most the same as the shop, but for equipping dice. It also allows you to drag individual dice or entire sets to the equipped dice glyph. While dragging it will highlight all the slots the new dice will be equipped into.
|
||||
|
||||
:postsCard{image="/garden/da3_1717380046653_0.png" alt="Dice Rolling"}
|
||||
|
||||
The dice rolling uses the physics engine and detects once the dice have stopped moving, then determines which side is face up based on which of the normals is closest to straight up. It flags the die as cocked if that smallest angle is above a threshold. The dice sink into the table when not rolling to not interfere with any dice that are rolling.
|
||||
|
||||
:postsCard{image="/garden/da9_1717380177060_0.png" alt="Missile Storm"}
|
||||
|
||||
During certain events like winning the game or having the face of a die broken, the players' portraits will flash an emotion for a second. After winning, a random living die from the winning player is chosen to play their "finisher move", a flashy and dramatic effect to end the game. Shown is the arcane mechana's finisher, "Missile Storm".
|
||||
|
||||
After development stopped, the project became [Open Source](/garden/open-source) - check it out [here](https://github.com/sreynoldsdesign/dice_armor/tree/master/Assets/Scripts/babble.cs)
|
26
content/garden/digital-gardens.md
Normal file
26
content/garden/digital-gardens.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "Digital Gardens"
|
||||
wordCount: 67
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Chronological: /garden/chronological
|
||||
Commune: /garden/commune
|
||||
Garden-RSS: /garden/garden-rss
|
||||
/now: /garden/now
|
||||
The Cozy Web: /garden/the-cozy-web
|
||||
The Small Web: /garden/the-small-web
|
||||
This Knowledge Hub: /garden/this-knowledge-hub
|
||||
---
|
||||
|
||||
Digital Gardens are [Freeform](/garden/freeform) collections of information made by an individual or community
|
||||
- Alternatives to [Chronological](/garden/chronological) personal blogs
|
||||
- Exist in a middleground between the dark forest and [The Cozy Web](/garden/the-cozy-web)
|
||||
|
||||
[This Knowledge Hub](/garden/this-knowledge-hub) is a digital garden
|
||||
|
||||
Collections of digital gardens and resources for creating them:
|
||||
- **[https://github.com/MaggieAppleton/digital-gardeners](https://github.com/MaggieAppleton/digital-gardeners)**
|
||||
- https://github.com/lyz-code/best-of-digital-gardens
|
||||
- https://github.com/KasperZutterman/Second-Brain
|
12
content/garden/direct-democracy.md
Normal file
12
content/garden/direct-democracy.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: "Direct Democracy"
|
||||
wordCount: 40
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Anarchism: /garden/anarchism
|
||||
Consensus Democracy: /garden/consensus-democracy
|
||||
---
|
||||
|
||||
A form of democracy where every voter gets to vote on every issue directly, and the majority rules. This form of voting is often criticized for having no safe guards to prevent a majority group from oppressing a minority group.
|
25
content/garden/federated-identity.md
Normal file
25
content/garden/federated-identity.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
title: "Federated Identity"
|
||||
wordCount: 68
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
Decentralized: /garden/decentralized
|
||||
referencedBy:
|
||||
Commune: /garden/commune
|
||||
Fedi v2: /garden/fedi-v2
|
||||
Weird: /garden/weird
|
||||
---
|
||||
|
||||
Allow for validating one's identity without relying on a specific centralized server
|
||||
|
||||
Implementations:
|
||||
- Private and public keypairs
|
||||
- [IndieAuth](https://indieweb.org/IndieAuth) by [The IndieWeb](/garden/the-small-web)
|
||||
- Supported by [Rauthy](https://github.com/sebadob/rauthy) which the [Commune](/garden/commune) community endorses
|
||||
|
||||
Self hosted identity providers are NOT enough to be considered federated identity
|
||||
- OIDC and OAuth require the service owner to have pre-configured with explicitly allowed identity providers
|
||||
|
||||
[Incremental Social](/garden/incremental-social) uses Zitadel which does NOT support IndieAuth and probably won't
|
142
content/garden/fedi-v2.md
Normal file
142
content/garden/fedi-v2.md
Normal file
|
@ -0,0 +1,142 @@
|
|||
---
|
||||
title: "Fedi v2"
|
||||
wordCount: 3376
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Social Media: /garden/social-media
|
||||
The IndieWeb/Signature Blocks: /garden/the-indieweb/signature-blocks
|
||||
Weird: /garden/weird
|
||||
---
|
||||
|
||||
A placeholder name for a theoretical new federated network that is client-centric, in contrast to the server-centric [Fediverse](/garden/fediverse). Many of the ideas here will be implemented as described or similarly by people much smarter than me as part of [Agentic Federation on Iroh](https://github.com/commune-os/weird/discussions/32), an initiative by the [Weird](/garden/weird) developers.
|
||||
|
||||
## Motivation
|
||||
|
||||
The current fediverse, while in theory fully [Decentralized](/garden/decentralized), in practice suffers many of the issues associated with centralization. This is primarily caused by the friction of having to pick a server and the non feasibility of individuals buying a domain and setting up a single user instance - both of these causes lead to a handful of large servers with the bulk of the users. You can see this in action by looking up the relative sizes of lemmy and mastodon instances. [Single-user Mastodon Instance is a Bad Idea](https://mull.net/mastodon) goes over the non feasibility of self hosting and how it contributes to a handful of servers having the majority of the users.
|
||||
|
||||
The promise of federation is the ability to interact with the whole network, while being able to fully choose and customize how you yourself interact with the network. In practice though, clients are severely limited to what they can do based on the server software. Of particular note, Lemmy and Mastodon show content in different formats (threads vs microblogs), and no clients allow changing how they're displayed, or respecting the format of the source of the content. Clients also are unable to change sorting algorithms or how downvotes are handled - those are all dependent on the server. [A Plan for Social Media - Rethinking Federation](https://raphael.lullis.net/a-plan-for-social-media-less-fedi-more-webby/) similarly criticizes how much of the decisions are dependent on the server, which most people won't be able to or willing to self host.
|
||||
|
||||
The pick a server problem is such a problem because not only do you have to pick what server has moderation policies you align with, but that you're also linking your identity with that server. Smaller servers tend to be more focused or niche, which is unlikely to fully encompass any person's entire identity. Why would I confine myself to being `thepaperpilot@writinglovers.com` if I'm more than a writing lover? Additionally, I'm risking that the community at that instance won't grow away from things I want to associate with, such as fascism or crypto. My identity could end up being associated with things I drastically don't want it to be.
|
||||
|
||||
[Nostr](https://nostr.com/) fixes the pick a server problem with a properly decentralized identity, however it's done so by associating itself with crypto and the alt right, and fixing that culture problem is more effort than it's worth. It'll be difficult to gain broad adoption as anyone using the platform will have to take care to explain how they're using nostr but aren't alt right.
|
||||
|
||||
[ATProto](https://atproto.com/) by bluesky offers a version of federation built for a handful of large instances, but allowing smaller servers to be spun up that can implement custom sorting algorithms, views, etc. This fixes a couple of the problems where you're unable to change certain things dictated by the servers, but doesn't quite go far enough - and of particular note, you still have to associate your identity with a specific server.
|
||||
|
||||
[NextGraph](https://docs.nextgraph.org/en/introduction/) looks very similar to what we're trying to build. My only note is really that it gives a bit of a crypto vibe through decisions like calling identities "wallets" that I think may make it fall into the same problems nostr has, but conceptually its _really_ similar to everything discussed here, which is great! It should be incredibly easy to interoperate, at the very least.
|
||||
|
||||
<h2 id="66527b3e-58af-41c4-8345-5c0951e42f54">Identity</h2>
|
||||
|
||||
|
||||
The new fediverse should have a fully [Decentralized Identity](/garden/federated-identity), where it's completely attached to the client rather than any server(s). This means you don't have to pick a server, worry about your chosen server going down, or that yout identity will become associated with an undesired community. It can properly allow you to engage in your variety of interests without having to associate any as core enough to attach your identity to.
|
||||
|
||||
This identity can be accomplished by the client merely generating a private and public key, which can then be stored however the user pleases. Reasonably, there's be default options to back up the private key to Google drive or alternatives, as most users will not desire to go the extra effort of backing up their identity without relying on big storage websites. But for those who wish, you could keep the identity solely on the device, or choose your own method of storage and backup.
|
||||
|
||||
The client can sign messages using the private key and distribute it through whatever means they wish, and it can be verified as sent by that identity using the public key to decode the message. In this context, messages could also refer to any other signed information, such as a bundle of profile information.
|
||||
|
||||
For casual conversation, a nickname in the profile data should be sufficient. Once a client interacts with someone, they can be added as a contact as a way of verifying the next conversation with someone with that username is actually the same person as before. For situations where you want to verify an identity actually has a credentials they claim, you can query a nameserver that vouches for them. For example, whitehouse.gov would have a nameserver that specifies which identity is the actual president of the United States (and other government officials). There could be nameservers that allow you to openly "register" your identity with them to get a unique human readable username you can include on billboards or other visual media where you want someone to be able to memorize the identifier, rather than scan a QR code or something. For more details on how these decentralized usernames would work, check out [Petnames](https://spritely.institute/static/papers/petnames.html).
|
||||
|
||||
Through cryptography, you can be confident messages are verifiably sent by someone with access to the private key, and that the message was not tampered with. These are guarantees you can't make with the current server centric fediverse.
|
||||
|
||||
If you lose access to your account, it's gone forever. That is why I think there should be defaults to backup your private key to existing reliable servers, even if they're owned by large corporations. Otherwise I would see someone have their phone stolen, lost, or upgraded and be surprised when their account is now inaccessible.
|
||||
|
||||
If you do lose your account and create a new identity, you could have others "vouch" for the new identity being an alias for the previous identity. You could verify your new identity with people IRL, and then other clients can see those vouches publicly and, after sufficient quantity, just implicitly assume they're the same (although with some sort of indicator, so if this gets abused, users are at least aware of the possibility of impersonation.
|
||||
|
||||
## Servers
|
||||
|
||||
Servers would act as mere relays, whose job it is is to store messages and send them to any other clients or servers that have requested to hear about any new messages. Some relays may also display these messages in a web interface, so that you can still share links to messages online. Because your identity isn't attached to any specific server, you could send your messages out to any servers you wish, and change that list as often as you'd like. It's extremely resilient to individual servers going down.
|
||||
|
||||
If you want a private network, say for a school or job, you could setup a relay server that requires some sort of password when sending them messages, and then have that server not federate with the rest of the fediverse.
|
||||
|
||||
Servers would give clients ways to subscribe to subsets of all received messages - e.g. all messages, all messages from a specific user, any replies to messages from a specific user, or "shallow" subscriptions to a message, meaning it'll send you only 1 level of replies to that message.
|
||||
|
||||
## Content
|
||||
|
||||
The protocol should be fairly content agnostic, and allow arbitrary metadata on messages that can be used by the community to come up with their own new forms of content to transmit over the protocol. For example, perhaps there's a body field that could include arbitrary text _or_ binary data, and for binary data another field could clarify if its audio, video, an image, or something else.
|
||||
|
||||
The signature of the message acts as the de facto ID of that message, for replying purposes. Edits and reactions would be handled by "replying" to a message with a metadata flag indicating what the message actually represents. Edit messages should typically be ignored if they're not from the same author as the original message. We should assume some servers will always make an edit history fully public. Reactions should just be replies without any actual body, and a tag for what the reaction is - either binary image data or a code representing an emoji, like "+1" or "laughing". Upvotes and downvotes could be implemented via reactions.
|
||||
|
||||
Edit replies could be sent by people other than the original poster as well. Perhaps some clients would trust edits from a list of identities, but the original poster could also reply to an edit message with an "accepted" message as a form of officially accepting/endorsing that edit suggestion. Clients could also potentially include a list of "proposed edits" that haven't been accepted.
|
||||
|
||||
Groups/communities could also be specially flagged messages, effectively allowing for subreddit-style content. Posting to the community is just replying to the message. Subscribing to that community is just subscribing to that message. The original message creator can send edits to update stuff like the description of the community. Perhaps they can also send a message detailing other identities to trust for editing or moderating the community.
|
||||
|
||||
A bot could fairly easily be setup to make [IndieWeb](/garden/the-small-web) posts and web mentions use this protocol. Indeed, this protocol is very POSSE-friendly because you could have your original content on the website, and the messages can be spread across the network while allowing clients to verify it was untampered with and definitely came from that website. I plan on writing a proposal for IndieWeb posts to include [The IndieWeb/Signature Blocks](/garden/the-indieweb/signature-blocks) to enable this. Within this framework, Fedi v2 would not just be a other social media silo. It would be the source of truth, fully controlled by the author. Even if the author cross posts to other social media (silos), we'd effectively still be the original copy.
|
||||
|
||||
## Moderation
|
||||
|
||||
Anyone can send edit and delete requests as replies to messages, but they'll typically be ignored since they'd be coming from a different identity. However, you could have clients or even servers honor those requests based on various heuristics - deleting anything that's gotten X delete requests, or trust specific identities to act as moderators. Since individual clients could choose which identities to honor, the moderation is effectively fully decentralized. Clients will probably have a couple identities trusted by default, to check for illegal content or that would otherwise prevent the app from breaking the app store's TOS.
|
||||
|
||||
Anyone can spin up their own bots that just automatically send out delete requests based on custom logic, like checking for images that match the CSAM hash list, or messages that ChatGPT says are non-constructive. Through this, the ecosystem will over time allow people to further customize their experience by filtering out unwanted content more and more precisely.
|
||||
|
||||
## Success
|
||||
|
||||
I believe the main benefits of this new fediverse are mostly going to apply to the techy power users who will appreciate the increased control over their identity and browsing experience. As far as the general public goes, I think the main benefit will be verified authorship and guaranteeing lack of tampering. Specifically, I think this will appeal to notable figures who have to be wary of concerns like that. Reddit and Twitter could edit your content or stifle it in the algorithm, or any other sort of malicious actions. So I think success of this platform will mostly come from seeing notable figures switching to it, and treating is as the source of truth (even if they cross post it to other platforms for increased outreach). Ideally, they even host their messages on their own website.
|
||||
|
||||
## Component Definitions
|
||||
|
||||
The agentic fediverse is currently being designed such that messages are entities in a Entity-Component relationship. Component schemas can then be formally defined and then implemented in clients, without the need of a centralized authority releasing formal spec bumps. In theory _anyone_ could propose a new schema.
|
||||
|
||||
Here's some initial ideas for components I currently plan on proposing and perhaps even implementing:
|
||||
- **Community** (placeholder name): Marks that replies to this message should be displayed like a collection of threads, reddit-style.
|
||||
- **Delete**: Marks that this message is a deletion request to the message its a reply to.
|
||||
- Can be used to delete specific components in the parent message.
|
||||
- **Edit**: Marks that this message is an edit request to the message its a reply to.
|
||||
- Can be used to edit specific components in the parent message.
|
||||
- **Accept**: Marks that any requests in the message this message is a reply to were accepted.
|
||||
- Takes an optional schema property to identify which specific request component of the parent message was accepted.
|
||||
- **Reaction**: Marks that this message is a reaction to the message its a reply to.
|
||||
- **Ascii**: Describes a text component to be rendered.
|
||||
- **Unicode**: Describes a text component to be rendered.
|
||||
- **Image**: Describes an image component to be rendered. (Or perhaps specific file formats should have their own schemas)
|
||||
- **Audio**: Describes an audio component to be rendered. (Or perhaps specific file formats should have their own schemas)
|
||||
- **Video**: Describes a video component to be rendered. (Or perhaps specific file formats should have their own schemas)
|
||||
- **Topics**: Describes a list of topics/tags this message is relevant to, for use in client searching and filtering.
|
||||
- **Editors**: Describes a list of identities who have the power to edit this message, or accept edit requests to this message.
|
||||
- **Deleters**: Describes a list of identities who have the power to delete this message, or accept deletion requests to this message.
|
||||
- **No Discovery**: Marks that this message should not be included in any global feeds or search results. Servers should only send it to servers and clients that subscribe to messages like this one.
|
||||
- **Timestamp Requested**: Marks that this message would like to receive a response from a trusted server (optionally defined in the component data) once it is delivered. May also include a schema ID that represents what the timestamp represents. Defaults to referring to the published date.
|
||||
- **Calendar Event**: Describes a calendar event.
|
||||
- **RSVP**: Marks that you [are, might be, or aren't] participating in whatever a linked entity is describing.
|
||||
|
||||
### Chatting
|
||||
|
||||
The agentic fediverse could theoretically also implement chat rooms, bringing advantages (like divorcing identity from servers) that current decentralized chat protocols like matrix don't offer.
|
||||
|
||||
Here are some of the components that could be used to represent a chat room:
|
||||
- **Chat room**: Marks that replies to this message should be shown as messages in a chat room.
|
||||
- **Bridge**: Marks that this _identity_ is a bridge for an account on another service. Implies that the verification of authorship may not be preserved.
|
||||
|
||||
### Games
|
||||
|
||||
The agentic fediverse could support sharing games using a Game component that includes a url or raw html required to play a game. In theory they could even support "cloud saves" by signing a message of their save data that only they can decrypt and sending it as a reply to the game message. Clients could handle displaying the game alongside the usual filtering and sorting features.
|
||||
|
||||
I'd also be excited in seeing a sort of MMO style game on the agentic fediverse. So you see other players and there's a shared game state, calculated on the client based on the actions recorded by the various different players. And since the rules would have to be defined by the components, people could create their own copies of the world (e.g. to play with a friend group or solo), or even make their own mods of the game. I'd like to look into that. I'll perhaps rethink [Chromatic Lattice](/garden/chromatic-lattice) to work on such a framework, although it may be too complicated for this idea.
|
||||
|
||||
Having the game state be calculatable by the client like that would also allow trophies and achievements to work verifiably. People could probably still write software to copy someone else's events at the right times and effectively replicate their save, but I think that won't happen commonly enough to matter.
|
||||
|
||||
## Local identity and contact management
|
||||
|
||||
If I have multiple apps that use the agentic fediverse (e.g. one for reddit like content, one for Twitter, discord, Google drive, etc.), I'd like to easily have them all use the same identity(s), as well as a shared contact list (so I know the person I saw do something on one app is the same as the person that did something on another app).
|
||||
|
||||
To that end, there should be an app/program that manages your identities and contacts on that device. It sets up your initial identity, any cloud backups, etc., and the other apps talk to it as needed. That could be sending it individual messages to sign or asking for a key that can be used to do limited functionality.
|
||||
|
||||
Contacts could be signed such that they're only readable by us, and then sent over the network so I can have multiple devices that keep their contact list synced between them
|
||||
|
||||
An identity management app could also work as a link handler for the `leaf` protocol. It could take a schema ID as another path component, which then describes the purpose of the URI and the expected remaining data in the URI. The identity management app can then pass the message along to any app that has specified it knows how to handle that schema.
|
||||
|
||||
## Sustainability
|
||||
|
||||
Servers are expensive, especially as they get popular. Most current fediverse instances are free and funded by donations. Things like ads or paying for an account are difficult to do due to the nature of federation. This is a pretty major problem because if a server becomes too expensive to host, it will shut down, along with all the accounts associated with it. Fedi v2 makes individual servers going down not be an issue anymore, since identities aren't attached to them. However, it's an issue if _all_ the instances go down, and if there's no way to pay for them still, why would _any_ instance stay up?
|
||||
|
||||
Since instance nodes do not have to do filtering, sorting, or really any other processing, but rather just serving the events and sending out notifications to clients, the cost will be cheaper than the current fediverse. It's really just a file server, which is cheap. For example, idrive charges $40 per tb per year, which is enough for a LOT of content. So I expect some instance nodes to have fairly generous free tiers that will suffice for a lot of users. Idrive also doesn't have egress charges, so the cost only scales with how much content is being published, not downloaded.
|
||||
|
||||
For power users, instance nodes could accept payments to store data above the free quota. This would likely most often happen for people wishing to upload high resolution images or videos. A user could also switch nodes after filling a quota on one node - you don't have to delete your content on the old instance. You could also do this to backup your content on multiple nodes (although you should also keep a local copy of all your content).
|
||||
|
||||
I assume this aspect of Fedi v2 will be the most controversial - people really like free services, and are expecting it. Knowing they might eventually need to pay to post more will perhaps require a cultural shift. I think it's worth it to not have ads or tracking, and in general we should be supporting sustainable services.
|
||||
|
||||
## What about Incremental Social?
|
||||
|
||||
Well, the agentic fediverse is a long ways out. But eventually I'd probably like to replace mbin with an Iroh node and ActivityPub <=> Fedi v2 bridge, transitioning all existing accounts.
|
||||
|
||||
The bridge would work by looking for a signed message to register a handle on the server. If that handle is not taken nor reserved, the AP actor is created and posts from the identity get bridged. The actor will have some metadata linking to the Fedi v2 identity, thus allowing both fediverses to know the identities are linked.
|
29
content/garden/fediverse.md
Normal file
29
content/garden/fediverse.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: "Fediverse"
|
||||
wordCount: 29
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
Decentralized: /garden/decentralized
|
||||
referencedBy:
|
||||
ActivityPub: /garden/activitypub
|
||||
ATProto: /garden/atproto
|
||||
Decentralized: /garden/decentralized
|
||||
Fedi v2: /garden/fedi-v2
|
||||
Incremental Social: /garden/incremental-social
|
||||
Mbin: /garden/mbin
|
||||
Nostr: /garden/nostr
|
||||
Social Media: /garden/social-media
|
||||
The Small Web: /garden/the-small-web
|
||||
Weird: /garden/weird
|
||||
---
|
||||
|
||||
A collection of [Social Media](/garden/social-media) websites that can all talk to each other by virtue of a shared protocol
|
||||
|
||||
Typically refers to sites implementing [ActivityPub](/garden/activitypub)
|
||||
|
||||
Implementations:
|
||||
- [ActivityPub](/garden/activitypub)
|
||||
- [ATProto](/garden/atproto)
|
||||
- [Nostr](/garden/nostr)
|
11
content/garden/forgejo.md
Normal file
11
content/garden/forgejo.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
title: "Forgejo"
|
||||
wordCount: 5
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Incremental Social: /garden/incremental-social
|
||||
---
|
||||
|
||||
[Forgejo](https://forgejo.org) is an [Open Source](/garden/open-source) code repository hosting software
|
12
content/garden/freeform-vs-chronological-dichotomy.md
Normal file
12
content/garden/freeform-vs-chronological-dichotomy.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: "Freeform vs Chronological Dichotomy"
|
||||
wordCount: 10
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Chronological: /garden/chronological
|
||||
Freeform: /garden/freeform
|
||||
---
|
||||
|
||||
Describes a dichotomy between displaying information in a [Freeform](/garden/freeform) vs [Chronological](/garden/chronological) manner
|
21
content/garden/freeform.md
Normal file
21
content/garden/freeform.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
title: "Freeform"
|
||||
wordCount: 46
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Commune: /garden/commune
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Freeform vs Chronological Dichotomy: /garden/freeform-vs-chronological-dichotomy
|
||||
Garden-RSS: /garden/garden-rss
|
||||
---
|
||||
|
||||
A collection of information that is not tied to when it was created or edited
|
||||
|
||||
Part of the [Freeform vs Chronological Dichotomy](/garden/freeform-vs-chronological-dichotomy)
|
||||
|
||||
Anything wiki-style is considered freeform
|
||||
- A collection of living documents
|
||||
|
||||
[Garden-RSS](/garden/garden-rss), a theoretical alternative to RSS that's better for freeform content
|
17
content/garden/game-dev-tree.md
Normal file
17
content/garden/game-dev-tree.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
title: "Game Dev Tree"
|
||||
wordCount: 34
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
---
|
||||
|
||||
Play it [here](https://thepaperpilot.org/gamedevtree)!
|
||||
|
||||
My first (good) incremental game! (My actual first was [Shape Tycoon](https://thepaperpilot.itch.io/shape-tycoon) - I don't recommend it!)
|
||||
|
||||
It's [Open Source](/garden/open-source)!
|
||||
|
||||
The [TV Tropes](https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/TheGameDevTree) page on this game mentions some of the cool things about this game
|
25
content/garden/garden-rss.md
Normal file
25
content/garden/garden-rss.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
title: "Garden-RSS"
|
||||
wordCount: 59
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Freeform: /garden/freeform
|
||||
The Small Web: /garden/the-small-web
|
||||
This Knowledge Hub: /garden/this-knowledge-hub
|
||||
---
|
||||
|
||||
A theoretical alternative to RSS that's better for [Freeform](/garden/freeform) websites (and [Digital Gardens](/garden/digital-gardens) specifically )
|
||||
|
||||
Why is it useful?
|
||||
- [Feeds are not fit for gardening](https://v5.chriskrycho.com/essays/feeds-are-not-fit-for-gardening/)
|
||||
- Describes the issues with RSS for [Digital Gardens](/garden/digital-gardens)
|
||||
- Proposes creating an alternative, which they call `grdn`
|
||||
|
||||
How should it work?
|
||||
- Could display changes similar to git diffs
|
||||
|
||||
Existing Work
|
||||
- [`grdn` Specification](https://github.com/chriskrycho/grdn/blob/main/SPEC.md)
|
||||
- [Proposal to build set of extensions to RSS](https://forum.summerofprotocols.com/t/pig-rss-all-the-things/383)
|
28
content/garden/guide-to-incrementals.md
Normal file
28
content/garden/guide-to-incrementals.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
title: "Guide to Incrementals"
|
||||
wordCount: 230
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
My Personal Website: /garden/my-personal-website
|
||||
---
|
||||
|
||||
This is a comprehensive guide to Incremental Games, a genre of video games. It will explore defining the genre, why it's appealing, and how to design and build your own incremental game. Along the way will be ~~interactive examples~~, snippets from other creators, and relevant material to contextualize everything.
|
||||
|
||||
> Note: This is an incomplete document. I want to keep adding opinions and opposing views from other incremental games developers, and add interactive examples to illustrate various points regarding game design and balancing. Consider this a living document - and see the changelog at the end.
|
||||
|
||||
## Why am I making this?
|
||||
|
||||
That's a good question! What authority do I have to be making this guide? I haven't made the best incremental games, nor the most incremental games, certainly not the most popular ones either. But I do have some formal education in game development, know a lot of incremental game devs (as well as other game devs), and have a passionate interest in ludology, classifying genres, etc. I've also made [a couple of incremental games](/garden/my-projects)) myself.
|
||||
|
||||
If you have any additional questions about my credentials or anything on this site, feel free to reach out!
|
||||
|
||||
## Ludology
|
||||
- [Guide to Incrementals/Defining the Genre](/garden/guide-to-incrementals/defining-the-genre)
|
||||
- [Guide to Incrementals/Appeal to Players](/garden/guide-to-incrementals/appeal-to-players)
|
||||
- [Guide to Incrementals/Appeal to Developers](/garden/guide-to-incrementals/appeal-to-developers)
|
||||
- [Guide to Incrementals/What is Content?](/garden/guide-to-incrementals/what-is-content)
|
||||
|
||||
## Making an Incremental
|
||||
- [Guide to Incrementals/Navigating Criticism](/garden/guide-to-incrementals/navigating-criticism)
|
38
content/garden/guide-to-incrementals/appeal-to-developers.md
Normal file
38
content/garden/guide-to-incrementals/appeal-to-developers.md
Normal file
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
title: "Guide to Incrementals/Appeal to Developers"
|
||||
wordCount: 636
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Incremental Social: /garden/incremental-social
|
||||
Kronos: /garden/kronos
|
||||
My Political Journey: /garden/my-political-journey
|
||||
/now: /garden/now
|
||||
Social Media: /garden/social-media
|
||||
---
|
||||
|
||||
There are a lot of developers in the incremental games community - the genre seems to draw them in, and convert a lot of players _into_ developers. Let's explore the reasons why this genre appeals to developers.
|
||||
|
||||
## Incrementals are Easy to Make
|
||||
|
||||
Compared to other genres, incrementals have quite low expectations. You don't need to make fancy art, or music, or lay things out nicely. If you can make a button and learn the few lines of code necessary to make a number go up, you can make an incremental. This low threshold makes the genre perfect for those who are actively learning to code and haven't developed any gamedev-related skills yet.
|
||||
|
||||
Additionally, unlike other genres incrementals are uniquely easy to implement in a normal web page - no need to worry about rendering sprites, moving them around, implementing physics, etc. New developers can just use HTML to add a button, and the game is now available in your browser. You don't need to choose an engine, have admin privileges, or hell for the dedicated you don't even need a _computer_ - there are tools for web development that run in the browser itself, so you can technically use your phone if that's all you have.
|
||||
|
||||
Javascript is a perfectly viable language for making web games, whereas other genres are typically going to require using other more difficult languages to learn. There are countless javascript tutorials that start from 0 knowledge of programming, making it incredibly accessible to beginners.
|
||||
|
||||
## Players are Easy to Find
|
||||
|
||||
Once you've finished your game and uploaded it on github pages or itch or just copied the link if you're using glitch or replit (all of which are easy to do), anyone can now play the game in their browser. This low barrier to entry has shown tremendous success in getting completely unknown developers to have thousands of plays.
|
||||
|
||||
The incremental games community, which mostly centers around [r/incremental_games](https://www.reddit.com/r/incremental_games), is always looking for new games and tends to flood any new ones posted with initial players.
|
||||
|
||||
Having your games be played can be incredibly motivating, and the community makes it quite clear that you can expect players to play your game. These communities - both for incremental games in general as well as game-specific communities - tend to be very developer friendly as well. A lot of the developers know each other, and welcome new developers with open arms, often with dedicated channels for programming help and discussions.
|
||||
|
||||
## Monetization
|
||||
|
||||
I'd like to clarify that everything I've said above mainly applies to _web-based incrementals_. Incremental games are also _incredibly_ popular on mobile, but with a much different culture and community. Many mobile gamers will still participate in the web-focused community _for_ the culture. This web-focused community has a culture that has been criticized for being "anti-monetization". Ads, IAPs, and similar forms of monetization are often criticized, mainly due to the abundance of completely non-monetized games available from hobbyist developers. There are exceptions, like paid games often being considered fine, like Increlution or Stuck in Time, or donation ware games like kittens game, but even popular games that have IAP see some level of regular criticism, like NGU Idle, Idle Skilling, or Idle Pins. A large part of this can be explained by the community being hyper-aware of the [addictive](/garden/guide-to-incrementals/appeal-to-players#665ceed1-72a9-49f2-9215-dd690f89aee3)) nature of this genre and its susceptibility to exploiting players.
|
||||
|
||||
On mobile, however, monetization is the norm and expected. If an incremental game is available on mobile, it almost _certainly_ will be monetized, and mobile players are aware and accepting of that. Mobile incremental games, due to their addictive nature, tend to make a _lot_ of money. It's very lucrative, and therefore these games are quite abundant on mobile storefronts.
|
70
content/garden/guide-to-incrementals/appeal-to-players.md
Normal file
70
content/garden/guide-to-incrementals/appeal-to-players.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: "Guide to Incrementals/Appeal to Players"
|
||||
wordCount: 2166
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Incremental Social: /garden/incremental-social
|
||||
Kronos: /garden/kronos
|
||||
My Political Journey: /garden/my-political-journey
|
||||
/now: /garden/now
|
||||
Social Media: /garden/social-media
|
||||
---
|
||||
|
||||
This is something that has been discussed and analyzed by many people, and to some extent, I feel like everything that can be said on the topic already has. However, a lot of these analyses are from the perspective of those with not as much experience and involvement within the genre as I'd argue would be necessary for a fully contextualized answer. I'm interested in ludology and part of that includes interpreting games as art, and to that end what constitutes a game, let alone a "good game". Incremental games are oft criticized, unfairly in my biased opinion, of not even constituting games, such as was posited by [this polygon article](https://www.polygon.com/2013/9/30/4786780/the-cult-of-the-cookie-clicker-when-is-a-game-not-a-game).
|
||||
|
||||
## Numbers Going Up
|
||||
|
||||
This is a very common response to why people enjoy incremental games, although it's not one I find compels me personally, and I suspect it might be a stand-in for [progression](/garden/guide-to-incrementals/appeal-to-players#665ceed1-704e-4cd0-8263-9a1756b09f4a)) or [Guide to Incrementals/What is Content?](/garden/guide-to-incrementals/what-is-content). But reportedly, some people do just like _seeing_ big numbers. I must reiterate I suspect the actual cause is seeing big numbers _in context_ though - if you start at 1e1000 of a currency and get to 1e1001, that isn't going to feel as satisfying as going from 1e10 to 1e100, and in any case, I don't think a button that just adds a zero to your number will feel quite satisfying - I believe its the sense of having made progress, and comparing where you are to where you started and feeling like you've earned your way here that is enjoyable.
|
||||
|
||||
<h2 id="665ceed1-704e-4cd0-8263-9a1756b09f4a">Progression</h2>
|
||||
|
||||
|
||||
I think a strong sense of progression is seen as very enjoyable to many players of all sorts of genres - engine builder board games, RPGs, rogue_lites_, etc. Incremental games tend to have an extremely exaggerated sense of progression, which makes them very appealing.
|
||||
|
||||
Meta-progression is when games have some sort of progression that persists when other progress gets lost - for example, upgrades that persist between runs of a roguelite game. These are common mechanics in incremental games - in fact, its not uncommon to have multiple of these reset mechanics nested on top of each other, each with their own meta-progression. These are satisfying to players, although they can be a bit controversial. These mechanics can often be seen as an optional crutch, and in roguelite games, players often challenge themselves to win without any meta progression. Essentially these challenges argue that meta-progression de-emphasizes player skill by replacing it with time served. Incremental games, through their exaggerated progression, eschew that possibility though - they make it impossible to beat without the meta progression systems, as the meta-progression becomes an entire chapter of the gameplay. I'd argue this does not detract from the game, however, and is actually a part of what makes incremental games, and roguelikes, enjoyable to many players: meta-progression _augments_ the increases in skill the player is naturally gaining as they play. In effect, it's not _replacing_ the skill increase, but _exaggerating_ it to make it feel more real to the player.
|
||||
|
||||
## Effortlessness
|
||||
|
||||
Incremental games are so easy, a lot of them even have you progress while you're not playing! Part of the appeal is being able to feel like you're making progress while doing something _actually_ productive - [multitasking](https://www.youtube.com/watch?v=g-LziX2HynI), in a way. In this sense, the game is more of a fidget toy - not something to think hard about and play actively, but something to click a few buttons every so often while you're paying attention to a lecture or studying or working. Of course, not _all_ incremental games lend themselves to being played this way - it's specifically "idle" games that work like this. These are games that take an incredibly long amount of time to see all the content, stretching it as thin as possible, but they aren't expecting you to be sitting at your device playing it the entire time. They expect you to leave and come back later to make a bit of progress and repeat the cycle.
|
||||
|
||||
If you look at the higher-level play of most games, you'll see them perform difficult feats with ease and speed. They'll achieve a "flow state" that takes all their knowledge and experience of the game and uses it to play the game as instinctively as possible. It's incredible to watch things like Slay the Spire speed runs or competitive DDR-likes. I'd argue the _goal_ of a lot of games with a competitive scene is to get so good that the game _becomes_ effortless. In that sense, a game that allows you to reach that point earlier isn't any less legitimate, but rather lowers the barrier to entry by allowing more people to get "really good" at the game. And to be clear, (most) incremental games aren't _trivially_ easy - they, and to an extent, every game will have _some_ level of learning and improvement over time.
|
||||
|
||||
<h2 id="665ceed1-72a9-49f2-9215-dd690f89aee3">Addiction</h2>
|
||||
|
||||
|
||||
A lot of these reasons for why incremental games appeal may have reminded you of why _gambling_ appeals to people, particularly those prone to addiction. Indeed, incremental games are quite often criticized for their similarity to a [skinner box](https://www.youtube.com/watch?v=tWtvrPTbQ_c). Some have gone as far as to say incremental games as a genre are commenting that [all games are skinner boxes](/garden/guide-to-incrementals/defining-the-genre#665cea25-b1e5-40bc-8c82-2296982ce1d1)). The argument goes that some games are not fun, but rather condition players into continuing to play without actually getting anything from the experience. When tied to real-world money this is seen as predatory, and to a lesser extent, even free games may be feeding the addictive sides of people and making them more prone to seek out gambling or micro-transaction heavy games.
|
||||
|
||||
> While incremental games can be fun and even healthy in certain contexts, they can exacerbate video game addiction more than other genres. If you feel like playing incremental games is taking priority over other things in your life, or manipulating your sleep schedule, it may be prudent to seek help. See [r/StopGaming](https://www.reddit.com/r/StopGaming) for resources.
|
||||
|
||||
Since incremental games are often built on extrinsic motivations in the form of progression systems, it's hard to argue whether players continue to play because they are enjoying the gameplay, or if they are just conditioned to keep doing it because the game keeps rewarding them. Unfortunately, it can often feel like it's the latter, as there isn't typically anything compelling about the "gameplay" of clicking a button and waiting. There may be a significant overlap between those who enjoy incremental games and those who are most prone to addiction, and there are often posts on [r/incremental_games](https://www.reddit.com/r/incremental_games) about someone either [struggling with or overcoming video game addiction](https://www.reddit.com/r/incremental_games/search?q=addictive+%28flair%3A%22none%22+OR+flair%3A%22meta%22%29&restrict_sr=on&sort=relevance&t=all).
|
||||
|
||||
## Strategy
|
||||
|
||||
Incremental games could be considered a subset of [strategy games](/garden/guide-to-incrementals/defining-the-genre#665cea25-437a-49a4-8445-00422fb9ded1)), and inherit the appeals of strategy games. This includes the appeal of feeling like you've found a good solution to a puzzle, or that you're learning more about the game and are improving at making decisions within it.
|
||||
|
||||
Note that strategy games are not all the same difficulty, as well. Cookie Clicker is probably easier than Starcraft 2 (although late game may beg to differ). Plenty of incremental games can be used as evidence that "easier" strategies may have their separate appeal to harder strategy games - players like to feel smart and that they figured the game out and have optimized or mastered it, and the game being easier doesn't detract from that sense of accomplishment as much as it allows more and more users to be able to reach the point where they gain that sense.
|
||||
|
||||
## Avoiding Staleness
|
||||
|
||||
Incremental games tend to have "paradigm shifts", where the gameplay changes in a meaningful way at various times throughout the progression of the game. These upset and change the gameplay loop, which helps keep them from stagnating. This constant "freshness" to the gameplay can keep players engaged for longer, compared to a game with a repetitive and static gameplay loop.
|
||||
|
||||
## Good Game Design
|
||||
|
||||
Incremental games tend to show their game design "plainly", so it's more readily apparent if a game has good game design while playing, even if you're not looking for it. While different players have different preferences and might enjoy different types of games more than others, there are underlying good and bad game design principles that players _will_ notice the effects of. To be clear, this isn't talking about stuff like big numbers being enjoyable, where I can comfortably agree to disagree with other players. They don't intrinsically make my experience better, but I'm aware of those for whom it does and I won't argue against their feelings. However, the game designer in me _does_ feel like there are some extremely clear-cut examples of good and bad game design philosophies.
|
||||
|
||||
Let's start by giving an example of a mechanic I think can be easily and strongly argued is _good_ game design. There are of course many examples, but a personal favorite of mine is how DOOM encourages aggressive gameplay by linking health drops to melee attacks. It has an intended experience it's trying to give the player - immersing themselves as DOOM guy, who would not hide behind cover when low on health - and this mechanic does a great job at encouraging and effectively teaching players to behave properly. This is in sharp contrast to shooters like Call of Duty, which have you regen health passively, encouraging players to hide behind cover and wait after getting hit. Note that I'm not arguing CoD is poorly designed, as the games have different intended experiences. I'm specifically praising DOOM for having a mechanic that does a good job at ensuring the player has that intended experience.
|
||||
|
||||
To contrast with an example I think is _bad_ game design, let's talk about shields in souls-likes. This is a bit of a famous example, and I highly recommend [this video essay](https://www.youtube.com/watch?v=AC3OuLU5XCw) which spends quite a good bit of time on this topic. Essentially, the argument boils down to players of earlier games in the souls games using shields too much - playing slowly, conservatively, and ultimately having less fun. Players wanted to feel safe, so they ended up playing in a way that ruined the experience for them. The developers solved this by removing shields, apart from an intentionally bad one effectively mocking the playstyle, and it did its job at getting players to play more aggressively, and often have more fun.
|
||||
|
||||
To bring the conversation back to incrementals, I'm _incredibly_ opinionated on what makes a _good_ incremental game, which I'll discuss in the game design section. Suffice it to say, incremental games rely more on good game design than other genres, due to not having much to distract from bad game design. This helps (although imperfectly - gamers are a bit too tolerant of bad game design!) well-designed games rise to the top within the genre.
|
||||
|
||||
## Artistic Merit
|
||||
|
||||
The discussion of whether video games are art has resulted in a pretty universal "yes, they are", but with some games the argument may still crop up. The reason why Incremental games are sometimes questioned is due to their perceived lack of complexity. However, even setting aside the fact that if players are having fun then it's not time wasted, I think games can have artistic merit that supersedes the necessity of having (any / engaging / "deep") gameplay. Incremental games are no less legitimate of a game or the "art" label because of any lack perceived lack of depth. For what it's worth, most art can be consumed with more ease than any video game - any painting, movie, sculpture, etc.
|
||||
|
||||
A lot of incrementals have a narrative context that can similarly qualify them as art. Cookie Clicker is, as has been pointed out numerous times before, commenting on excess and increasing production beyond any reasonable limits - devolving into increasing production for its own sake. Indeed, a lot of incremental games are written to comment upon various concepts like capitalism or tropes in games, as discussed when [defining Incrementals](/garden/guide-to-incrementals/defining-the-genre#665cea25-b1e5-40bc-8c82-2296982ce1d1)). However, I'd like to argue _most_ incremental games are still art, even without any narrative context. "Art" as a concept is pretty nebulous already, but I personally like those who define it as an act of expression more than any physical result. The creator and the context within which they created the art, and any meaning they put into it, are all relevant and a part of the art itself. Most incremental games have artistic merit from things like _why_ the creator made it, why they chose to make it an incremental game, and why they made any particular design decision. Hell, even if you play through an entire incremental game without a single thought or feeling, that very fact it elicited nothing can itself be artistic merit!
|
||||
|
||||
I'm not an art major, and I may be taking a somewhat extreme take on what is art and what has artistic merit, but I'd argue the overall point stands that games, and incremental games specifically, _can_ have artistic merit, which appeals to many gamers.
|
151
content/garden/guide-to-incrementals/defining-the-genre.md
Normal file
151
content/garden/guide-to-incrementals/defining-the-genre.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
title: "Guide to Incrementals/Defining the Genre"
|
||||
wordCount: 3429
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Incremental Social: /garden/incremental-social
|
||||
Kronos: /garden/kronos
|
||||
My Political Journey: /garden/my-political-journey
|
||||
/now: /garden/now
|
||||
Social Media: /garden/social-media
|
||||
---
|
||||
|
||||
Video games are placed into genres for a variety of reasons. They can give a mental shorthand to set the player's expectations up, they can help a game market itself by its similarities to other, already popular games, and honestly, people just love categorization for its own sake. For this guide, it's important to define the genre so it is clear what games it's even talking about.
|
||||
|
||||
This poses a problem. "Incremental" is a _horribly_ vague way to define games. _Most_ games have numbers going up in some form or another. We need a more specific definition - similar to how "strategy" can't just mean any game with _any_ amount of strategy because that would be _most_ games. What specifically differentiates incremental games from the rest?
|
||||
|
||||
"Incremental" implies it's a genre defined by a game mechanic, but all those game mechanics it could imply exist in many other games. Having a skill tree or upgrades doesn't make you incremental, and if a reset mechanic is all it takes then every roguelite would be an incremental as well. So clearly there's more to it than that - what makes an incremental an incremental?
|
||||
|
||||
I'd like to go over a couple of popular suggestions I've seen on defining the genre here. I have my personal preferences and will state them here, but I don't think there's a truly perfect answer here.
|
||||
|
||||
> Disclaimer: I mostly play incremental games on my computer, and my definitions will be heavily biased towards the games I'm familiar with.
|
||||
|
||||
## Incrementals vs Idlers vs Clickers
|
||||
|
||||
Oftentimes people refer to this genre as idle games and/or clicker games. You'll even find a trend of oxymoronic game titles that contain both terms. "Incremental games" is the umbrella term both those terms fall under. However, I'd like to argue that not only is it better to just use the term "incremental games", but calling them "idle games" or "clicker games" is _wrong_. Almost universally, these terms are used interchangeably to refer to the _same kind of game_, where you start the game click spamming and eventually automate the process. Frankly, that kind of game deserves neither title, and the genre of incremental games has trended away from ever requiring click spamming, as it's a bad mechanic, anyways.
|
||||
|
||||
While these games do span a spectrum of how active it requires you to be, and sorting games by that metric can be useful for those looking for a particular experience, the borders of when an incremental game counts as an "idler" is too blurry for the term to be useful. "Incremental games" may not be a great descriptive term for the genre (hence this many thousands of words long page on defining what the genre even is), but it's strictly better than calling them "idler" or "clicker" games. This guide will always use the term "incremental games" unless quoting someone else, as it is the term you typically see on all modern games in the genre.
|
||||
|
||||
<h2 id="665cea25-b1e5-40bc-8c82-2296982ce1d1">Incrementals as Parodies</h2>
|
||||
|
||||
|
||||
Let's start with one of the most _interesting_ definitions of incremental games. Incremental games appear to be distilled versions of games or genres, "revealing" the naked game design at the core of these games or genres not unlike how parodies comment upon their source material.
|
||||
|
||||
To understand what that means, think of how a casino uses skinner boxes to emotionally manipulate its customers to keep playing, but "dressing" up the skinner box with tons of stimuli to hide that ultimately the goal is to condition you into coming back compulsively. The idea that incremental games are parodies means taking the stance that at some level _all_ games are similarly manipulating you, giving dopamine rewards in a way that manipulates you to keep playing while not necessarily giving you any value or fulfillment. Incremental games, then, are any games that plainly display the skinner box, and the manipulative core of the game, at the forefront of the experience.
|
||||
|
||||
> While incremental games can be fun and even healthy in certain contexts, they can exacerbate video game addiction more than other genres. If you feel like playing incremental games is taking priority over other things in your life, or manipulating your sleep schedule, it may be prudent to seek help. See [r/StopGaming](https://www.reddit.com/r/StopGaming) for resources.
|
||||
|
||||
This "undressing" tends to go hand in hand with a reduced focus on aesthetics, often just printing the game state directly to the screen as text. This makes incremental games much easier to develop, particularly for those with programming skills but not art skills, but that's a tangent for why Incremental Games [Guide to Incrementals/Appeal to Developers](/garden/guide-to-incrementals/appeal-to-developers).
|
||||
|
||||
Before I continue, I'd like to make my stance clear that I love games and incremental games, and do not think they should be considered inherently bad or manipulative with the above logic. Skinner boxes are just a way of manipulating behavior _via rewards_. The games are still fun - that's the reward! I'd believe the real criticism here is that it is "empty fun", or "empty dopamine", that doesn't offer any additional value or sense of fulfillment. I don't think that's inherently bad in moderation, although it can become a problem if the game is manipulating you for profit-seeking, or if you play the game to the detriment of the other parts of your life.
|
||||
|
||||
Another interpretation of incremental games as parodies comes from several mainstream incremental games that are also parodies of capitalism, such as [cookie clicker](http://orteil.dashnet.org/cookieclicker/) and [adventure capitalist](https://store.steampowered.com/app/346900/adventure_capitalist). It's a very common framework for incremental games to portray the ever-increasing numbers as an insatiable hunger for resources, like the ones observed within capitalism. Therefore, these games are used as evidence that the genre as a whole is about parody and commentary.
|
||||
|
||||
Popular videos on incremental games that portray the genre as parodies are [Why Idle games make good satire, and how it was ruined.](https://www.youtube.com/watch?v=7khbIR-WQIw) and [Bad Game Design - Clicker Games](https://www.youtube.com/watch?v=C-9ASzBErjo). You may also be interested in this response to the latter video from a fan of incremental games: [~~Bad~~Good Game Design - Clicker Games](https://www.youtube.com/watch?v=vBuYzLUzPqw).
|
||||
|
||||
I think that this definition ultimately ascribes a motive to the genre as a whole that only happens to apply to some of the more mainstream titles. There certainly _are_ incremental games commenting on different things, including the genre itself as in the case of [The Prestige Tree Classic](https://jacorb90.me/Prestige-Tree-Classic/), [The Ascension Tree](https://ascensiontree.semenar.ru/), or [Omega Layers](https://veprogames.github.io/omega-layers/), but certainly not all. And of course, not all games that comment on something or parody something are incremental games! Additionally, a very large majority of incremental games are mobile games using these manipulative strategies to get players to spend as much money as possible - hell, Adventure Capitalist is ostensibly a critique on capitalism but features microtransactions and gameplay that manipulates you into buying them! These profit-seeking incremental games certainly belong within the genre but are hardly parodies when they too use manipulation to serve their interests. Also, from my own anecdotal experience, those who use this definition seem to do so from a fairly surface-level familiarity with the genre, and often in the context of criticizing the genre or the fans thereof.
|
||||
|
||||
## Incrementals as NGU
|
||||
|
||||
Another broad definition often used is that incremental games are games where the _focus_ of the game is "numbers going up". This definition proposes that other genres simply use increasing numbers as a means to an end, but incremental games uniquely _only_ care about the numbers themselves going up. Put another way, it implies there should be no narrative justification for the numbers going up other than "why _shouldn't_ they be going up?"
|
||||
|
||||
While this definition is common because it _feels_ easy to understand, it is difficult to formally define. Often phrases are used to describe games using this framework, such as having an "exaggerated sense of progression" or "big" numbers. These terms are vague and don't demonstrate an actual threshold between non-incrementals and incrementals. Most games have a sense of progression, so when is it "exaggerated"? How big are "big" numbers? Most notably, RPGs that are typically not considered incrementals will often pass this definition.
|
||||
|
||||
Additionally, a lot of incrementals tend to have _some_ theme guiding the gameplay, or at least the names of mechanics. This makes the line blurred between when numbers are going up for their own sake versus for a contextual reason. I believe this point is best illustrated that, while _most_ RPGs are not considered incremental games, there _is_ a sub-genre of "incremental RPGs" that typically relates to RPGs that perform combat automatically. This definition of incremental games does not support RPGs and "incremental RPGs" being on distinct sides of the line if the only difference between them is manual vs automatic combat.
|
||||
|
||||
<h2 id="665cea25-437a-49a4-8445-00422fb9ded1">Incrementals as Strategies</h2>
|
||||
|
||||
|
||||
This is a rarer interpretation, but there are similarities between incremental games and strategy games, implying incrementals might just be a sub-genre of strategy games. By this approach, incremental games would be defined by their relation to strategy games, and how they involve player strategy. Incremental games are often large optimization problems - above all else, the actual gameplay the player is performing is deciding what to do next. The consequences of wrong decisions are typically more lenient in incremental games - such as just not making optimal progress - but they _certainly_ get complex.
|
||||
|
||||
So if we accept the premise that incrementals could fall under strategy, we still need to define what makes a strategy game an incremental versus some other strategy sub-genre. This is a bit tricky due to one particular sub-genre of strategy games: Factory Builders.
|
||||
|
||||
Factory builders, such as Factorio or Satisfactory, are games about gaining ever increasing resources, optimizing production, and expanding more and more. That... sounds pretty similar, doesn't it? In fact, there's been some debate on whether factory builders would fall under the "incremental" umbrella. I think it's safe to say the two are certainly related, and probably have quite a bit of overlap in playerbase.
|
||||
|
||||
## Roguelites as Incrementals?
|
||||
|
||||
Earlier on, I mentioned reset mechanics shouldn't be used in the definition because that could make all roguelites incrementals... But what if it does? A _lot_ of incrementals can be described as games with a strong sense of progression, often with layers of meta-progression. Roguelites fit that bill to a T. What would make roguelites _not_ incremental? I honestly don't think there's a good explanation here, but many fans of incremental games will state they do believe the two genres to be unrelated, even if there's a significant overlap between their player bases due to having similar appealing traits.
|
||||
|
||||
At this point, it'd be appropriate to consider what part of the definition of roguelites precludes them from also being incrementals, but that reveals a new problem: What are roguelites? They're usually defined as rogue_likes with meta-progression, but that just pushes the problem back a step: Incrementals aren't the only genre to have difficulties defining themselves, it seems! Roguelikes are another genre where the community argues over the formal definition of their genre, although that means we can borrow from their process of coming to a consensus, and maybe come across a viable definition for incremental games.
|
||||
|
||||
### The Berlin Interpretation
|
||||
|
||||
By far the most popular way of defining roguelikes is the "[Berlin Interpretation](http://www.roguebasin.com/index.php?title=Berlin_Interpretation)", which acknowledged the diversity of games within the genre and argued the definition should not be based on any ideals about what the genre _ought_ to be, but rather defined by "its canon". They argued there are a handful of games that can be used to define the canon for roguelikes, and from those games, a list of factors can be derived to determine a game's "roguelikeness". The more factors a game has, the more of a roguelike it is. This strategy is very lenient, allowing a game to not present any specific factor so long as it shows _enough_, and accounts for the blurriness of any genre definition by not explicitly stating how many factors a game must have to qualify as a definite roguelike.
|
||||
|
||||
I believe this strategy for defining genres can be applied to other genres as well. A handful of games can be argued to be the incremental games canon, and a list of factors derived from them can be used to judge any game based on its "incrementalness". I'll propose such a canon and list of factors here, but by no means should it be considered the end-all-be-all.
|
||||
|
||||
> Note: The "Temple of the roguelike", an authority within the genre, has since replaced the Berlin Interpretation with a new set of factors here: https://blog.roguetemple.com/what-is-a-traditional-roguelike/
|
||||
|
||||
### The Incremental Games Canon
|
||||
|
||||
Alright, time to get controversial. Up til now, I've been trying my best to stay objective and analytical, but now it's time to start making some _opinionated decisions_. Here is a list of games I think could justifiably make up an Incremental Games Canon:
|
||||
- [A Dark Room](https://adarkroom.doublespeakgames.com)
|
||||
- [Clicker Heroes](https://www.clickerheroes.com)
|
||||
- [Crank](https://faedine.com/games/crank/b39/)
|
||||
- [Increlution](https://store.steampowered.com/app/1593350/Increlution/)
|
||||
- [Kitten's Game](https://kittensgame.com/web/)
|
||||
- [NGU Idle](https://store.steampowered.com/app/1147690/NGU_IDLE/)
|
||||
- [Realm Grinder](https://store.steampowered.com/app/610080/Realm_Grinder/)
|
||||
- [Synergism](https://pseudo-corp.github.io/SynergismOfficial/)
|
||||
- [Universal Paperclips](https://www.decisionproblem.com/paperclips/)
|
||||
- [Learn to Fly](https://www.coolmathgames.com/0-learn-to-fly)
|
||||
- ~~Hades~~ _Just Kidding!_
|
||||
|
||||
I chose a variety of games here, biasing towards newer games, purposefully to avoid making a narrow or "traditional" definition. The genre is growing and shouldn't be constrained by the traits of the early popular titles. A lot of these could easily be replaced with other games that are mechanically congruent, so ultimately I'm sure if you asked 10 people for their canon list you'd just get 10 different answers, but I think this should sufficiently allow us to determine what factors make a game have higher "incrementalness".
|
||||
|
||||
### The Paradigm Shift
|
||||
|
||||
The Paradigm Shift is probably the _highest_ possible value factor for an incremental. It's so common that for a while people referred to incrementals that exhibit this trait as "unfolding" games, to the point of trying to _replace_ the term incremental due to their popularity. Paradigm shifts refer to when the gameplay significantly changes. There are too many examples to list here, but notably, every single reset mechanic is typically going to be a paradigm shift. Examples of games with paradigm shifts that _aren't_ tied to reset mechanics include [Universal Paperclips](https://www.decisionproblem.com/paperclips/) and [A Dark Room](http://adarkroom.doublespeakgames.com/).
|
||||
|
||||
There are many reasons for the appeal of paradigm shifts. Oftentimes each mechanic builds on top of the existing mechanics, increasing the complexity of the game in steps so the player can follow along. They provide a sense of mystery, with the player anticipating what will happen next. They shake up the gameplay before it gets too stale - allowing the game to entertain for longer before the sense of [Guide to Incrementals/What is Content?](/garden/guide-to-incrementals/what-is-content) dissipates. Of the canon games selected above, I would argue _every single one_ contains a paradigm shift (although I could see someone disagreeing with that statement wrt Increlution).
|
||||
|
||||
I should take a moment to say that while I'm hyping up this specific factor, we cannot just reduce the genre definition to "does it have paradigm shifts". Many games have paradigm shifts that are not incremental, so it's just an _indicator_ of incrementalness. Additionally, it can become quite hard to determine how large of a shift is a "paradigm" shift. Take, for example, any game with a skill tree. In some games, each skill node might have a large impact on how you play with the game, and qualify as a paradigm shift for some players. In other games, each skill node might just be a small percentage modifier on some stat that doesn't really impact much more than a slight bias towards an already established mechanic that's newly buffed. Every single canon game may show that it's common amongst incremental games, but could just as easily indicate that they're common in games in general.
|
||||
|
||||
### High-Value Factors
|
||||
|
||||
I won't take as long to discuss the high and low-value factors, as you've already seen most of them brought up earlier on this page. As a reminder, a game does NOT need all of these to be an incremental game, but these are factors that each indicate a strong possibility the game is an incremental, so having several of these means they probably are. These factors apply to most of the canon incremental games.
|
||||
|
||||
**"Pure UI" Display**. Incrementals typically have a textual presentation of the game state - there isn't a visual representation of the entities within the game. The interface is closer to what would be just the UI of a game in another genre or the control panel of a plane. If there _is_ a visual representation, the player is often still interacting with non-diegetic game elements.
|
||||
|
||||
**Reduced Consequences**. Incrementals tend to have reduced repurcussions for misplaying. They very rarely have fail states, where often the largest consequence is simply _not_ progressing - never _losing_ progress.
|
||||
|
||||
**Optimization Problems**. The _predominant gameplay_ of incrementals is typically solving optimization problems, from deciding which purchase to save up for to reasoning and deciding between different mutually exclusive options the game presents.
|
||||
|
||||
**Resource Management**. Incrementals tend to have a lot of resources within the game to keep track of.
|
||||
|
||||
### Low-Value Factors
|
||||
|
||||
These are low-value factors, meaning they aren't as strongly correlated with incremental games. Incremental games may have none of these, and non-incrementals may have several of these - if a game _only_ has low-value factors, they're probably not an incremental.
|
||||
|
||||
**Fast Numeric Growth**. Numbers in incremental games tend to grow faster than in other genres. There are more instances of superlinear growth. The larger the numbers get, the stronger of a signal this factor is.
|
||||
|
||||
**Automation**. As an incremental game progresses, the player often no longer has to deal with earlier mechanics, by having them either happen automatically or otherwise be replaced with an alternative that requires less player interaction.
|
||||
|
||||
**Goal-Oriented**. Incrementals are often heavily reliant on extrinsic motivation to guide the player. Typically this is through some sort of in-game goal to work towards, such as a certain amount of a resource being required to unlock or purchase something new.
|
||||
|
||||
**Waiting is a Mechanic**. In incremental games, the player may come across times where there is no action they can take, and the game will progress automatically instead. The player must wait for some amount of this automatic progress to occur before they can resume interaction with the game.
|
||||
|
||||
### Are Roguelites Incrementals?
|
||||
|
||||
Having made our variation of the Berlin Interpretation for incremental games, we can compare it to the Berlin Interpretation to determine if there's enough overlap that any game that "passes" the Berlin Interpretation would also pass the incremental variant. That is to say, whether any roguelite would also be considered an incremental game.
|
||||
|
||||
The meta-progression of an incremental game could arguably be considered a paradigm shift, and certainly adds some resource management. Goal-oriented would probably also apply. I think anything other than those would be a stretch, and in my opinion that just isn't enough to qualify. To be totally honest, I was never expecting to conclude otherwise though ;)
|
||||
|
||||
## Sub-Genres
|
||||
|
||||
There are some trends in incremental games that go beyond just being a commonly used mechanic, such that they deeply affect the rest of the game design. These trends can be used to determine sub-genres within the incremental games umbrella:
|
||||
|
||||
**Loops** games are a sub-genre defined by having a core mechanic related to a loop, where the player is deciding the actions taken per loop. Notable examples include [Idle Loops](https://omsi6.github.io/loops), [Stuck in Time](https://store.steampowered.com/app/1814010/Stuck_In_Time/), [Cavernous II](https://nucaranlaeg.github.io/incremental/CavernousII/), and [Increlution](https://store.steampowered.com/app/1593350/Increlution/). You may also argue [Groundhog Life](https://mogron.itch.io/groundhog-life) and [Progress Knight](https://ihtasham42.github.io/progress-knight/) fall into this sub-genre.
|
||||
|
||||
**ITRTG-like** games are a sub-genre defined by having a core mechanic based on clearing increasingly difficult battles and often tend to have a lot of different mechanics to become progressively stronger. Notable examples include [Idling to Rule the Gods](https://store.steampowered.com/app/466170/Idling_to_Rule_the_Gods/), [NGU Idle](https://store.steampowered.com/app/1147690/NGU_IDLE/), and [Wizard and Minion Idle](https://store.steampowered.com/app/1011510/Wizard_And_Minion_Idle/).
|
||||
|
||||
**Polynomial Growth** games are a sub-genre defined by having a core mechanic related to a higher degree polynomial. Notable examples include the base layer of [Antimatter Dimensions](https://ivark.github.io) and [Swarm Simulator](https://www.swarmsim.com).
|
||||
|
||||
**Upgrades Games** is a category popular on flash games websites that featured games focused on buying upgrades that would allow you to attain more currency in some sort of minigame that would earn you more money to buy more upgrades, which I'd argue now belong under the fold of incremental games. Notable examples include the [Learn to Fly](https://www.coolmathgames.com/0-learn-to-fly) series and [Upgrade Complete](https://www.kongregate.com/games/armorgames/upgrade-complete).
|
||||
|
||||
## Other Related Genres
|
||||
|
||||
**Cultivation RPGs** are a genre of games, books, and anime popular in China that center around being in a fantasy world with characters getting stronger over time. While few of them get translated into English, a fan of incremental games may find the available games interesting.
|
34
content/garden/guide-to-incrementals/navigating-criticism.md
Normal file
34
content/garden/guide-to-incrementals/navigating-criticism.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
title: "Guide to Incrementals/Navigating Criticism"
|
||||
wordCount: 747
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Incremental Social: /garden/incremental-social
|
||||
Kronos: /garden/kronos
|
||||
My Political Journey: /garden/my-political-journey
|
||||
/now: /garden/now
|
||||
Social Media: /garden/social-media
|
||||
---
|
||||
|
||||
Developing games is fun and exciting and teaches a lot of wonderful skills - I enthusiastically encourage anyone with an interest in game development to try it out - and incremental games are a wonderful way to get started. However, there are many challenges young and inexperienced developers have to face, and I think the hardest one - harder than coding, debugging, balancing, etc. - is handling criticism. When you put your heart and soul into a game it is natural to feel very vulnerable. While I think there's a lot communities can do to ensure they're welcoming, positive and constructive with their criticisms, inevitably you will eventually read some, and potentially a lot, of comments that can deeply affect you. No one is immune to this, from young incremental game developers to the largest content creators you can think of. That's why it's important to be able to process and navigate criticism, because ultimately collecting feedback is essential to the journey to becoming a better developer. On this page, we'll explore how to embrace criticism, grow from it, and continue to post your games publicly with confidence.
|
||||
|
||||
## Reading Feedback
|
||||
|
||||
Game development is a skill that takes time and practice to get truly great at. Criticism and other constructive feedback are vital to continually improving. It's useful to look at the criticism as solely a tool for improving this game and future games - that is to say, it should never be used against you as a person. Insults towards the developer(s) themselves are never okay and should not be allowed within whatever community you're sharing your works in. If you do come across a comment you interpret as an attack upon your person, you should report it. For other negative comments, try not to internalize them; instead, focus on improving the game. By distancing your own identity from your work emotionally, you can better analyze the game and use the feedback to your advantage.
|
||||
|
||||
Not all feedback is made equal, and you don't need to feel compelled to read and obey every piece of feedback you receive. Learn to distinguish between constructive feedback and unhelpful comments. Constructive feedback typically offers specific suggestions for improvement, while unhelpful comments are often vague or hurtful. Prioritize the former and disregard the latter. That said, most feedback you get will not be from game developers, so take specific suggestions with a grain of salt. Determine the actual problem they're experiencing, and design what you believe the best solution to that problem would be, regardless if that's the specific solution the player asked for. And keep in mind, due to different player preferences you'll never satisfy everyone, and you don't need to. Ultimately if even just you find the game fun, then that's a success.
|
||||
|
||||
## Seeking Feedback
|
||||
|
||||
When deciding where to share your game, consider the type of players you anticipate getting, and the kind of feedback you can anticipate receiving. Different communities will have different levels of support for learning developers, and certain communities may prefer certain types of games or mechanics. It's important to get a diverse set of feedback focused on players you think will enjoy the specific game you're making.
|
||||
|
||||
Collecting feedback from other game developers is incredibly helpful. They've trained themselves to recognize good and bad game design and how to articulate the differences, and from my experience are much more likely to leave positive and constructive comments since they've been in your shoes before! They understand the struggles and can offer guidance and emotional support.
|
||||
|
||||
## Responding to Feedback
|
||||
|
||||
Negative feedback can naturally feel like an attack, and it's okay to get angry. However, lashing back is never the appropriate response. It's best to cool off IRL, and keep in mind all the positive comments you've received. There's a concept in Psychology called negative bias that explains how negative feedback tends to stick with us much more prominently than positive feedback, so it's useful to regularly remind yourself of all the positive feedback you've received. Celebrate your successes, no matter how small they may seem - getting a game to a state you can publicly share it with people is an accomplishment in and of itself!
|
||||
|
||||
Remember your passion and your initial reasons for getting into game development. The journey will have its ups and downs, but staying true to your vision and passion will keep you motivated.
|
70
content/garden/guide-to-incrementals/what-is-content.md
Normal file
70
content/garden/guide-to-incrementals/what-is-content.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
---
|
||||
title: "Guide to Incrementals/What is Content?"
|
||||
wordCount: 2272
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Digital Gardens: /garden/digital-gardens
|
||||
Incremental Social: /garden/incremental-social
|
||||
Kronos: /garden/kronos
|
||||
My Political Journey: /garden/my-political-journey
|
||||
/now: /garden/now
|
||||
Social Media: /garden/social-media
|
||||
---
|
||||
|
||||
If you've been in the incremental games community for any amount of time, you'll quickly find the number one thing players want is _content_. They want as much of it as possible! The most popular incremental games have tons of content, so they just keep stretching on and on and on, introducing mechanic after mechanic, and players love it. In fact, players seem to value the _amount_ of content over the quality of any _specific_ content. However, there's a bit of a lack of understanding concerning _what_ content is, and I'd like to explore what counts as content, and how we measure it. As a baseline definition, I think "content" can just be described as the parts of the game that engage the player, but to truly understand it we need to contextualize what that means and how it affects the gameplay experience.
|
||||
|
||||
To clarify the purpose of this page, my goal is not to get (too) nitpicky or to attack games with "low content". There's nothing wrong with short / low-content games - I'm quite a big fan of those games myself! This is mostly targeted toward those who _ask_ for content and settle for "long" games, and those who _want_ to provide content but want to make sure they're not just artificially inflating the game. Ultimately, I suppose the goal is to just reduce the amount of artificially inflated content for the sake of having a "longer" game.
|
||||
|
||||
## Interaction
|
||||
|
||||
I think it should be a fairly non-controversial opinion that time spent _solely_ waiting should not count towards content. That is not including the time reading various effects or making decisions in your head, but rather time spent waiting for a condition to be met so you can re-engage with the game.
|
||||
|
||||
That is not to say games should necessarily try to minimize this time. Plenty of games lead towards more infrequent interaction and still get popular. In fact, these games appeal to many gamers who want to have something to check up on in between bursts of working on some other activity. These games seem to have fallen slightly out of fashion amongst modern incremental games, but they're still fully valid. The point I'm trying to make here is just that this time is not content. As an extreme example, a game with no interactions and just a counter that goes up every second could safely be said to have 0 content beyond the time it takes to understand what's going on. If it has a list of "goals" to hit, then the time understanding those goals and a short time after achieving each one could be considered content, but not the idle times in between.
|
||||
|
||||
Let's take a look at the opposite end of the spectrum - interaction that is so frequent as to become mindless. This is any mechanic where you need to spam-click something to progress. This may be a more controversial take, but I do not believe this constitutes content either. It does not engage the player, because each consecutive click blends together and they do not individually change the gameplay experience. That is to say, a single click and 100 clicks are not meaningfully different in terms of engaging the player. I'd go as far as to say clicking 100 times would be actively _worse_, as it's artificially delaying the next piece of actual content, alongside the issues of accessibility and potentially causing RSI.
|
||||
|
||||
### Repeatable Purchases
|
||||
|
||||
Imagine an entity in a game that you can purchase multiple times, each time it performs the same thing but for a higher cost. These are incredibly common, from the buildings in [cookie clicker](https://orteil.dashnet.org/cookieclicker/) to the units in [swarm sim](https://www.swarmsim.com/) to the IP and EP multipliers in [antimatter dimensions](https://ivark.github.io/). However, how much content is each specific purchase? Is it content beyond the first purchase? Does it have diminishing returns? What if you are oscillating between two different repeatable purchases? How much content is lost when you [automate](/garden/guide-to-incrementals/what-is-content#665cf570-e3d3-48f6-9fde-aa94e68a8682)) away a repeatable purchase?
|
||||
|
||||
I don't want to take too harsh a stance against repeatable purchases. They're useful tools and can be used in a myriad of interesting ways. I feel they do become "stale" or less meaningful content over time, and this happens exponentially quickly the more frequently it can be purchased. A classic example that I believe goes too far is the IP/EP multipliers in Antimatter Dimensions. I would go as far as to say they are a chore and do not provide any meaningful content after you've bought them a couple of times. It's a method for inflating numbers (effectively making every OOM a 5x step instead of 10x), that punishes the player progression-wise whenever they forget to max it again, and eventually gets automated away as a _reward_ to the player for making enough progress.
|
||||
|
||||
Just to voice the other side of this argument, Acamaeda defended the IP multiplier as giving the player a "good" upgrade every OOM. I can understand that to a point and need to clarify I'm mainly criticizing IP/EP multipliers after they've been introduced for a while. In fact, I would defend the multipliers for a short while after they're introduced using the same logic I would use to defend normal dimensions as repeatable purchases, at least pre-infinity. There's "content" to be had in looking at what dimensions will become affordable next, and then choosing which to buy amongst those. The IP/EP multipliers, early into infinity or eternity respectively, provide another option that gets put into that mental queue of things to buy with each OOM reached - although the optimal order is often quite trivial and not particularly engaging.
|
||||
|
||||
The IP/EP multipliers are not the only repeatable purchase in antimatter dimensions I take offense to. The time dimensions are also a series of repeatable purchases, that are all so similar and static that it doesn't take long before you never need to put any thought into buying them, how much you're buying at once, or the order you buy them in - you just press max all and move on. The entire tab could've been just the max all button and it would not have made a difference beyond the start of the eternity layer. The normal dimensions technically have this problem as well, but since you're constantly getting antimatter the order feels like it has a larger impact and it's more meaningful content, right up until they're automated away. Infinity dimensions are a compromise between the two, so I'm highlighting time dimensions here as the _most_ egregious.
|
||||
|
||||
## Following Instructions
|
||||
|
||||
We're getting more and more controversial as we go along! Let's talk about how linear content is not content now (in some circumstances). A trend in incremental games is adding difficulty by adding a web of effects that abstract the true change you can expect from any specific purchase or decision you make. If a game is both linear and sufficiently abstracts the effect of player decisions, then the player will no longer be engaging with the content - they'll simply be clicking on things as they become available. This isn't necessarily a _bad_ thing, as plenty of players don't mind this style of gameplay, but I'd argue once you reach a point where players _don't bother reading the effects_, those interactions are no longer truly content. Note that unlike the previous qualifiers mentioned, this qualifier is based on the player, and therefore subjective. In effect, it's a spectrum where the more complicated the web of effects becomes, the more likely it is to disengage the player.
|
||||
|
||||
This over-complicatedness leading to disengaging the player can _also_ happen from non-linear gameplay. If the web of effects becomes sufficiently complicated and finding the optimal progression route too time-consuming to discover, players will seek out guides from other players who've completed the game. The _second_ they do this, the game effectively becomes linearly following the instructions of the guide and all the above criticisms apply. Similarly to as before, though, this is a spectrum and not everyone will seek out a guide at the same level of difficulty.
|
||||
|
||||
<h2 id="665cf570-e3d3-48f6-9fde-aa94e68a8682">Automation</h2>
|
||||
|
||||
|
||||
Automation is a staple of the genre, but it has certain implications for the design of the game. Why, when new content is introduced, must the older content be automated away - why is it a chore and it feels rewarding to not have to do it again? Why does the new mechanic have such appeal if we know it too will just be automated away later on, and we'll be happy when that happens? It honestly begs the question of why this framework of introducing content and automating the old content is even enjoyable - and nearly nonexistent in other genres. You're not going to reach a point in a platformer game where they just automate the jumping part - _that's the core mechanic!_ Instead, platformers either add new mechanics that _build_ on the core mechanic or at least re-contextualize the core mechanic. However, in incremental games new content very frequently means _replacing_ older content, as opposed to augmenting it.
|
||||
|
||||
Admittedly, the above paragraph ignores the obvious answer that separates incremental games in this regard. These mechanics _become_ chores as their frequency increases. The frequency increases to give a sense of progression, and automation is seen as a reward because it now manages what was becoming unmanageable. The new content then comes in and continues the loop to give a stronger sense of progression. That's all good and a fine justification for automating content instead of building upon the base mechanic. It's also _much_ easier to design, as each layer essentially lets you start over instead of needing to think of ideas that conform to the original core mechanic.
|
||||
|
||||
So, what's the problem? Even if this trend is justified and easy to implement, there are some other effects it has on the game design. First off, and this is probably a neutral point, incremental games with this cycle of replacing old mechanics with new ones trend towards more and more abstract and further away from any narrative throughline as they add layers. There are only so many justifications for resetting progress, so if a game wants to have several of these layers they're inevitably going to become generic or increasingly loosely associated with the original content. It's most unfortunate, in my opinion when an interesting or innovative core mechanic gets fully automated once a generic "prestige" layer is unlocked.
|
||||
|
||||
A recent example is [Really Grass Cutting Incremental](https://mrredshark77.github.io/Really-Grass-Cutting-Incremental/), an incremental game about cutting grass (although I'm really criticizing the Roblox game it's based on). Except, it doesn't _continue_ to be about cutting grass. After you buy enough upgrades to increase your grass cutting and level up sufficiently you "prestige", an abstract term that in this case means you reset all your progress to get some currency to buy upgrades that do the same things as the original upgrades, but these won't reset on future prestiges. You'll eventually be able to "crystallize", which means you reset all your progress to get some currency to buy upgrades that do the same things as the original upgrades (and a couple of new ones) and won't reset on future crystallizes. Fine. You'll progress a bit, complete some challenges, and finally get to... grasshop? Grasshopping is this mechanic where you reset all your progress to get some resource that _isn't_ for buying upgrades - this time you just unlock different modifiers on everything based on their amount. You may have gotten the point by now, but there are also "steelie" resets which give you steel for some reason, before unlocking a factory with various machines - none of which are directly tied to cutting grass, and start gathering things like oil and reset for rocket parts and reset to go to space and so on and so on. Throughout all of this there is absolutely no narrative justification or throughline for the direction the game is going, or why cutting grass is still relevant when we're collecting things like rocket parts. I may be going a little hard on GCI, but it is far from alone.
|
||||
|
||||
## Ending the Game
|
||||
|
||||
Incremental games do not often have a planned out narrative or ending,, such that each content update is approached as its own unit of narrative and gameplay. This prevents content updates from wrapping up the game nicely - it always has to leave something open for another content layer; be it another mechanic, reset layer, etc. This cycle will continue until the updates just stop, at which point the game will just have an unsatisfying conclusion that will never get the next thing it was supposed to be leading into. This reminds me of a Leonardo Da Vinci quote about how [Art is Never Complete](/garden/art-is-never-complete):
|
||||
|
||||
> Art is never finished, only abandoned.
|
||||
> \- Leonardo Da Vinci
|
||||
|
||||
For what its worth, there are exceptions here (including several of [My Projects](/garden/my-projects)). I believe this practice is actually fairly reasonable, considering how many incremental game developers are learning game design and programming - keeping the scope small and expanding if it still interests you is a great way to keep learning without letting things like perfectionism or sunk cost fallacies get in the way.
|
||||
|
||||
## Tips for Developers
|
||||
|
||||
If you're a developer, by this point you should have a pretty decent idea of how to create "true" content in your game. Here are some other specific tips I'd suggest:
|
||||
|
||||
An upgrade that simply unlocks another upgrade trivially isn't content. However, many games have an upgrade that just unlocks a feature, which then has a wait or other requirements before it can be used. Try to make sure when you unlock a feature, there is immediately something to do with the feature - for example, perhaps give them a small amount of the new currency it unlocks, if applicable.
|
||||
|
||||
If you don't have a large web of effects, and can definitively say the impact of a purchase is to multiply the gain of the cost currency by N, and the _next_ purchase costs N times the amount of that same currency, then this purchase effectively made no difference and it may have made more sense to just go directly to the next upgrade. That said, having effects based on things like the number of purchases made will quickly invalidate this tip.
|
20
content/garden/incremental-social.md
Normal file
20
content/garden/incremental-social.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Incremental Social"
|
||||
wordCount: 20
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
referencedBy:
|
||||
Federated Identity: /garden/federated-identity
|
||||
My Personal Website: /garden/my-personal-website
|
||||
/now: /garden/now
|
||||
Webrings: /garden/webrings
|
||||
---
|
||||
|
||||
[Incremental Social](https://incremental.social/) is a [Fediverse](/garden/fediverse) website hosted by me!
|
||||
|
||||
Made explicitly for the incremental games community
|
||||
|
||||
Most notably hosts an instance of [Mbin](/garden/mbin), [Forgejo](/garden/forgejo), and [Synapse](/garden/synapse) (and [Cinny](/garden/cinny))
|
16
content/garden/individualism.md
Normal file
16
content/garden/individualism.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: "Individualism"
|
||||
wordCount: 194
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Local Communities: /garden/local-communities
|
||||
Neoliberalism: /garden/neoliberalism
|
||||
---
|
||||
|
||||
Individualism is a value system centered around independence and self sufficiency. It argues for taking care of oneself before others, and that it's wrong for people to be forced to take care of others before their selves, i.e. via wealth redistribution. This value system is antithetical to the [Anarchist](/garden/anarchism) values of community and mutual aid. I personally am against individualism and see it as against humans nature of cooperation. We're a social people and have for our entire existence relied upon each other.
|
||||
|
||||
As a personal anecdote, I'm a recent parent and the whole "it takes a village" adage makes a lot of sense, and has made me hyper aware of how individualism has made it very hard to raise a kid these days. There's no 3 generations living in a house anymore, and suburbs are spread out and isolating, preventing strong [Local Communities](/garden/local-communities) from forming. To sum up, the "village" doesn't exist anymore.
|
||||
|
||||
Hyper individualism is a modern invention, not a "good ole traditional value" we should all aspire to. It was explicitly created by capitalist values, and replaced pre-existing value systems that prioritized co-operation.
|
14
content/garden/ivy-road.md
Normal file
14
content/garden/ivy-road.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
title: "Ivy Road"
|
||||
wordCount: 6
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
Davey Wreden: /garden/davey-wreden
|
||||
referencedBy:
|
||||
Davey Wreden: /garden/davey-wreden
|
||||
Wanderstop: /garden/wanderstop
|
||||
---
|
||||
|
||||
[Ivy Road](https://www.ivyroad.fun/) is a indie game studio created by [Davey Wreden](/garden/davey-wreden), Karla Kimonja, and C418
|
20
content/garden/kronos.md
Normal file
20
content/garden/kronos.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Kronos"
|
||||
wordCount: 60
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
Profectus: /garden/profectus
|
||||
referencedBy:
|
||||
/now: /garden/now
|
||||
V-ecs: /garden/v-ecs
|
||||
---
|
||||
|
||||
My largest and most ambitious incremental game I've ever made
|
||||
- A magnum opus, of sorts ;P
|
||||
|
||||
Still in development, and will be for a long time. I have full intention of completing it, however
|
||||
|
||||
An older version, that is built in The Modding Tree, only has the gameplay, and only goes up to Chapter 2, can be played [here](https://thepaperpilot.org/kronos/)
|
61
content/garden/life-is-strange.md
Normal file
61
content/garden/life-is-strange.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
title: "Life is Strange"
|
||||
wordCount: 654
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
---
|
||||
|
||||
A series of narrative driven video games with a focus on player choices
|
||||
|
||||
I really enjoyed the first game and engaged with the community a lot. I would get very emotional playing through the later chapters, and it became one of my favorite games.
|
||||
- A good "starter" collection of things the community made is available at [Life is Strange Post-Game Depression Starter Kit](https://www.reddit.com/r/lifeisstrange/comments/41hjk6/life_is_strange_postgame_depression_starter_kit/)
|
||||
|
||||
Playthroughs I enjoyed:
|
||||
- [Deadbones](https://www.youtube.com/playlist?list=PLweH2EmozgiPQ1xRAF88-VE-eHHlf5sfS)
|
||||
- [Laura Kate](https://www.youtube.com/playlist?list=PLD0NeEbRY7VR3Vl35qtQyexV9edtlkODU)
|
||||
- [Jesse and Dodger](https://www.youtube.com/playlist?list=PLFx-KViPXIkFWTwFCBku5KNgv_rsmPh-r)
|
||||
- I got a shirt they made for this series signed by Jesse at PAX South 2017
|
||||
- :postsCard{image="/garden/6346b024-885e-45e0-9df6-5ee0311133f7_1718332409063_0.png" alt="6346b024-885e-45e0-9df6-5ee0311133f7.png"}
|
||||
- :postsCard{image="/garden/ce7b2612-2ddb-423e-82eb-95c2ed08c4da_1718332277410_0.png" alt="ce7b2612-2ddb-423e-82eb-95c2ed08c4da.png"}
|
||||
- [LiS Voice Actors](https://www.youtube.com/watch?v=zvQmqdnFkZA)
|
||||
|
||||
Around the start of Haley and I's relationship, we'd play through LiS1 on projectors in our's classrooms
|
||||
|
||||
## The ending
|
||||
|
||||
I was not a huge fan of the ending of Life is Strange. Of the two endings, one very clearly got a lot more resources spent on it, but I think Max's story has a better conclusion under the other.
|
||||
|
||||
In my mind, Max's arc (and major themes of the last chapter) was about learning not to rely on her powers, and to learn to live with the consequences of her actions. To that end, having the game end with her taking responsibility and living with the consequences of such a major event is a fitting thematic conclusion to the game and her character arc. In contrast, the other ending shows Max relying on her powers once again and, in my opinion, portrays Max as having not grown nor learned any lessons throughout the entire series. I think its a disservice this ending got so much more attention, with unique outfits and a song dedicated to it.
|
||||
|
||||
## Life is Strange: Before the Storm
|
||||
|
||||
I really enjoyed this story. I didn't get _quite_ as invested, perhaps due to it being a condensed 3 chapters rather than 5, but the characters will still just as amazing, which is what makes LiS so good. The ending didn't have as much of a punch as LiS1 - in fact, the entire back half of episode 3 didn't quite meet the bar in my eyes. At the time, I enjoyed these threads discussing criticisms levied at that part of the game:
|
||||
- https://www.reddit.com/r/lifeisstrange/comments/7l0vgq/bts_e3_did_the_last_playable_scene_feel_off_to/
|
||||
- https://www.reddit.com/r/lifeisstrange/comments/7l1i6k/all_inconsistencies_and_general_issues_with/
|
||||
|
||||
Deadbones played through the game [here](https://www.youtube.com/playlist?list=PLweH2EmozgiOuLDIbtyoOnkQ9mWC72HmV)
|
||||
|
||||
## The Awesome Adventures of Captain Spirit
|
||||
|
||||
I enjoyed the demo, and it certainly got me hyped for LiS2 (although I would've been regardless). Switching to UE seemed like a really good move, and the comic theme fits the aesthetic of the series well.
|
||||
|
||||
## Life is Strange 2
|
||||
|
||||
Unfortunately, I've only played the first chapter of this game. I wanted to play through it with Haley, but we haven't found time to do so.
|
||||
|
||||
LATER Skip the rest of LiS1 and play LiS2 with Haley
|
||||
|
||||
## Life is Strange: True Colors
|
||||
|
||||
This game seems interesting, although the power does seem a little funnily close to "has empathy". I haven't had time to play it, but would like to.
|
||||
|
||||
## Life is Strange: Double Exposure
|
||||
|
||||
I'm very excited for this game! Returning to Max's story sounds awesome. Although I will say, from the trailer alone it's _incredibly_ obvious what one of the story beats will be: Revealing that the two timelines follow the two disparate endings from LiS1.
|
||||
|
||||
This game was announced to come with premium editions that get access to the game earlier
|
||||
- I think this is a horrible anti-consumer practice
|
||||
- Since this game is narratively driven, it is prone to spoilers
|
||||
- They're getting more money from their biggest fans without providing any tangible value to them, other than making the experience worse for everyone who plays but doesn't cough up that money
|
||||
- I hope other narratively driven games find other ways to do [Video Game Monetization](/garden/video-game-monetization)
|
20
content/garden/local-communities.md
Normal file
20
content/garden/local-communities.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
title: "Local Communities"
|
||||
wordCount: 302
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Anarchism: /garden/anarchism
|
||||
Individualism: /garden/individualism
|
||||
---
|
||||
|
||||
Strongly connected local communities are important to have. They satisfy our social needs for in-person connections, and help organize mutual aid. These needs cannot be sufficiently satisfied exclusively by online friends/communities - of particular note, new parents need help raising their kid.
|
||||
|
||||
Historically, society has had these strongly connected local communities, via the way society was organized (i.e. tribes, multi-generational households, etc.) or through entities that focused on community (i.e. local churches). Churches in particular would ensure everyone meets up regularly to see each other, connect, and catch up. They'd host community events and services throughout the year, and mobilize the community during emergencies.
|
||||
|
||||
The religious aspect of churches was never a requirement for the benefits they contributed to their local communities, and in fact there are mega-churches today that do not confer these benefits despite retaining the religious aspect.
|
||||
|
||||
There are several reasons for why local communities have since weakened. The car has weakened them by making the people physically more spread out and reducing the number of "third places". The internet created a convenient alternative whose communities were not immediately recognized as insufficient imitations of in person communities. Newer generations trend towards irreligiousness, making churches decreasingly popular. Combined, these changes have led to a cultural shift towards [Individualism](/garden/individualism) and [Neoliberalism](/garden/neoliberalism) that has further cemented our weakened local communities.
|
||||
|
||||
The way to "fix" our local communities and make them more strongly connected is to support multi-generational households, increasing population density, and using or creating entities that can replace the community-building role of the church. Such alternatives could be community centers or HOAs. HOAs get a bad reputation due to their tendency to attract those who want power to micro-manage the community, but there are ways to organize them to mitigate that issue (see [Anarchism](/garden/anarchism)).
|
13
content/garden/logseq.md
Normal file
13
content/garden/logseq.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "Logseq"
|
||||
wordCount: 3
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Command Palettes: /garden/command-palettes
|
||||
My Personal Website: /garden/my-personal-website
|
||||
This Knowledge Hub: /garden/this-knowledge-hub
|
||||
---
|
||||
|
||||
[Logseq](https://logseq.com) is an [Open Source](/garden/open-source) outlining software
|
13
content/garden/matrix.md
Normal file
13
content/garden/matrix.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "Matrix"
|
||||
wordCount: 2
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Cinny: /garden/cinny
|
||||
Commune: /garden/commune
|
||||
Synapse: /garden/synapse
|
||||
---
|
||||
|
||||
[Matrix](https://matrix.org) is a protocol for [Decentralized](/garden/decentralized) messaging
|
13
content/garden/mbin.md
Normal file
13
content/garden/mbin.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "Mbin"
|
||||
wordCount: 12
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Incremental Social: /garden/incremental-social
|
||||
---
|
||||
|
||||
[Mbin](https://github.com/MbinOrg/mbin) is an [Open Source](/garden/open-source) [Fediverse](/garden/fediverse) software
|
||||
|
||||
Can show both twitter-style posts and reddit-style threads
|
12
content/garden/mtx.md
Normal file
12
content/garden/mtx.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
title: "MTX"
|
||||
wordCount: 10
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Premium Currency: /garden/premium-currency
|
||||
Video Game Monetization: /garden/video-game-monetization
|
||||
---
|
||||
|
||||
Purchaseable items in video games that cost real life currencies
|
46
content/garden/my-personal-website.md
Normal file
46
content/garden/my-personal-website.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: "My Personal Website"
|
||||
wordCount: 422
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
The Small Web: /garden/the-small-web
|
||||
---
|
||||
|
||||
A [Personal Websites](/garden/the-small-web) for myself, available at https://thepaperpilot.org
|
||||
|
||||
## Tech Stack
|
||||
|
||||
I use [Logseq](/garden/logseq) to journal and collect my thoughts on various topics that interest me
|
||||
- Seafile syncs my logseq files between my devices
|
||||
- Git syncs my logseq files to a private repo on [Incremental Social](/garden/incremental-social) for purposes of version control and using as a submodule
|
||||
- The seafile files and all repos on [Incremental Social](/garden/incremental-social) are independently backed up daily to backblaze
|
||||
|
||||
My logseq files are synced to a private git repo which is added as a submodule to [my website repo](https://code.incremental.social/thepaperpilot/pages)
|
||||
|
||||
A [Node.js script](https://code.incremental.social/thepaperpilot/pages/src/branch/master/build_garden.js) pre-processes my logseq files into markdown files in the `/garden` path of the website
|
||||
- Converts all links and block references
|
||||
- Adds lists of tags and references to pages
|
||||
- Adds `<h1 />` titles, word counts, update commits, etc. to each page
|
||||
- Moves the /now page to [/now](https://thepaperpilot.org/now) instead of /garden/now
|
||||
- Copies some of the [Guide to Incrementals](/garden/guide-to-incrementals) pages to [/guide-to-incrementals](https://thepaperpilot.org/guide-to-incrementals/) so as to not break links made before the current site iteration
|
||||
- Generates [/changelog](https://www.thepaperpilot.org/changelog/) and its RSS, Atom, and JSON feeds
|
||||
- The outputs of the generation are NOT .gitignore'd, as I use the git log to determine which pages updated when
|
||||
<span id="66757760-16ab-4777-976e-8bcbac053923"> - Commit information about when a file was last updated is added via a [data loader](https://vitepress.dev/guide/data-loading) because if it was added to the file directly, rebuilding the site would count as having updated every page, by updating each commit to the changes introduced last build</span>
|
||||
|
||||
[Vitepress](/garden/vitepress) builds a static site from the markdown files
|
||||
- Includes a custom theme that makes the whole site paper-themed
|
||||
- Includes some pages like the [homepage](https://thepaperpilot.org) and the [about me page](https://thepaperpilot.org/about) that require markup, thus don't make sense to maintain inside logseq
|
||||
- The sidebar is generated from my favorited pages within Logseq
|
||||
- Includes various static files, like copies of several of my games and a [robots.txt](https://www.thepaperpilot.org/robots.txt) (borrowed from [Tracy Durnell](https://tracydurnell.com/)'s [robots.txt](https://tracydurnell.com/robots.txt)) that blocks several crawlers specifically used for training AI models
|
||||
|
||||
Three.js is used to create the effect in the background
|
||||
- Simplex noise gets used to adjust the opacity of a repeating SVG pattern
|
||||
- Initially tried to use just SVG, which supports creating noise and using it as a mask, but it only does 2d noise and I need 2d slices of 3d noise
|
||||
|
||||
Microformats are used to markup the site for [The IndieWeb](/garden/the-small-web)
|
||||
- The footer contains a minimal [h-card](https://microformats.org/wiki/h-card) with a link to my full profile
|
||||
- Each garden page is an [h-entry](https://indieweb.org/h-entry), and the changelog an [h-feed](https://indieweb.org/h-feed)
|
||||
- All my socials are added as [rel-me](https://indieweb.org/rel-me) links, and the profiles then link back to me (as rel-me links, if allowed by the platform)
|
||||
- Together this can verify the owner of this website and those socials are the same person
|
26
content/garden/my-political-beliefs.md
Normal file
26
content/garden/my-political-beliefs.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
title: "My Political Beliefs"
|
||||
wordCount: 228
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
My Political Journey: /garden/my-political-journey
|
||||
Political Quizzes: /garden/political-quizzes
|
||||
---
|
||||
|
||||
# Government
|
||||
|
||||
I tend to argue in favor of a government structured as a federation of local communities, where the local communities operate through [Consensus Democracy](/garden/consensus-democracy) , and the federation has a system where it takes a very significant super majority to enact anything and operates through referendums rather than any form of representation, which I see as an unnecessary yet corruptible abstraction of the will of the people. The goal is to ensure everyone has an equal voice and are supportive or at least compatible with every policy they live under.
|
||||
|
||||
# Economy
|
||||
|
||||
I believe we should eventually arrive at communism; a post-scarcity society where everyone's needs are met via automation and without the need for currency. Along the way there I expect us to democratize the workplace and work towards nationalizing every idustry.
|
||||
|
||||
# Society
|
||||
|
||||
I believe in maximizing personal liberties, so long as one is not actively harmful to others, including most forms of discrimination.
|
||||
|
||||
# Security
|
||||
|
||||
I'm against the use of violence by anyone, including the state. I believe in [Police Abolition](/garden/abolitionism) and am against the military and espionage both foreign and domestic. I believe in the [Anarchist](/garden/anarchism) value of free association, so I believe we should have fully open borders, both for travel and immigration/emmigration. I am anti-imperialist and believe in a fairly isolationist foreign policy, but am not against humanitarian foreign aid.
|
29
content/garden/my-political-journey.md
Normal file
29
content/garden/my-political-journey.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: "My Political Journey"
|
||||
wordCount: 711
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Political Quizzes: /garden/political-quizzes
|
||||
---
|
||||
|
||||
[My Political Beliefs](/garden/my-political-beliefs) have changed over time, although I believe it mostly stems from me becoming increasingly informed. Since this website contains content from as far back as 2006, when I was only 10 years old, I'd like to make it extremely clear that I have changed as a person, as we all have, and disavow a lot of my older opinions, roughly everything before 2020 or so but to be safe let's just give a dynamic (current date - 2 years). It's likely you are on this page from viewing a timeline post from that range and clicking the banner. There are bits in that period I may still agree with, but perhaps it would be best to just assume I don't have a stance on it unless there's something more recent about it. Things in [My Digital Garden](undefined) are evergreen though, meaning I'll keep them up to date as my views change. Feel completely free to judge me for my opinions in there.
|
||||
|
||||
## Core beliefs
|
||||
|
||||
I believe I've been fairly political since I was quite young, and had a relatively consistent set of core values that have just been expressed in different ways and through different lenses during different points of my life. For example I've always believed in fairness, but have used that belief to both argue for and against affirmative action. Concepts like freedom or meritocracies have similarly been redefined as I've become more aware of the influence of imperialism and colonialism on our cultural values and society.
|
||||
|
||||
Other beliefs of mine have always been around, but I just wasn't informed enough to properly express my views. I've voiced for a very long time that I think society will inevitably trend towards post-scarcity, a world without class, currency, or jobs, where all our needs and desires are met fully, automatically, and sustainably. I've never once pictured that society retaining a hierarchy of wealth or power in any way. But I would not have recognized that as an anarchistic society until relatively recently, mostly due to misinformation and propaganda against anarchy.
|
||||
|
||||
## Anti-SJW
|
||||
|
||||
Alright, I'm guessing you're specifically on this page because you saw me upvote something on r/KotakuInAction or like a video from PSASitch or something like that. I'm not proud of this time period, where I read and watched a bit of Anti-SJW content. I'm glad to have moved on from this point of my life, and wish it hadn't happened in the first place. I don't really have any excuses, but appreciate understanding that I have grown as a person since this time.
|
||||
|
||||
I migrated a LOT of posts to this website for the sake of having them all in one place and under my control, rather than other websites'. I have not been able to look through the tens of thousands of posts (mostly upvotes/likes), but have done some basic filtering of these opinions I no longer hold. I'm open about the fact I once held these beliefs, but I don't need to be de facto promoting them by sharing them on this website. If you stumble across a post you think I'd rather not have on this site anymore, please [reach out](https://www.thepaperpilot.org/about/).
|
||||
|
||||
## Radicalization
|
||||
|
||||
I believe a lot of things contributed to my radicalization, which happened sometime in the early 2020s. Ultimately I think I was just aware that I didn't really like the views I was being exposed to, the direction that media was trying to to pull me, and slowly over time just engaged less and less with that kind of content. I'd always been very economically leftist, so just needed to get over my edgy/cringe phase. I think what put the nail in the coffin was watching through the [alt right playbook](https://youtube.com/playlist?list=PLJA_jUddXvY7v0VkYRbANnTnzkA_HMFtQ), a great series I highly recommend. I also started really enjoying a lot of leftist creators, like [hasanabi](https://twitch.tv/hasanabi), [philosophy tube](https://youtube.com/@philosophytube), and others. The people around me also affect my views, and after leaving college I think I interacted with nicer people on average. Of particular note here is my wife, who had their own political journey which has similarly culminated in us sort of having a positive feedback loop further and further left. Certain events like the BLM protests following George Floyd similarly cemented our position further and further left.
|
||||
|
||||
I actually want to also point out I've found a lot of people in this space to be very accepting of people who previously held problematic beliefs. It's largely why I feel comfortable (enough) having a lot of my history public both on this page and the site in general, and being able to describe how my political journey got me to where I am today, a very radical leftist.
|
41
content/garden/my-projects.md
Normal file
41
content/garden/my-projects.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
index: "true"
|
||||
title: "My Projects"
|
||||
wordCount: 72
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
taggedBy:
|
||||
Advent Incremental: /garden/advent-incremental
|
||||
Babble Buds: /garden/babble-buds
|
||||
Capture the Citadel: /garden/capture-the-citadel
|
||||
Dice Armor: /garden/dice-armor
|
||||
Game Dev Tree: /garden/game-dev-tree
|
||||
Incremental Social: /garden/incremental-social
|
||||
Kronos: /garden/kronos
|
||||
Opti-Speech: /garden/opti-speech
|
||||
Planar Pioneers: /garden/planar-pioneers
|
||||
Profectus: /garden/profectus
|
||||
V-ecs: /garden/v-ecs
|
||||
referencedBy:
|
||||
Guide to Incrementals/What is Content?: /garden/guide-to-incrementals/what-is-content
|
||||
---
|
||||
|
||||
I like making games and tools!
|
||||
|
||||
<h2 id="665e3a7a-395f-4493-8f3a-482f136ea157">Games</h2>
|
||||
|
||||
- [Planar Pioneers](/garden/planar-pioneers) ([play](https://thepaperpilot.org/planar))
|
||||
- [Advent Incremental](/garden/advent-incremental) ([play](https://thepaperpilot.org/advent))
|
||||
- [Game Dev Tree](/garden/game-dev-tree) ([play](https://thepaperpilot.org/gamedevtree))
|
||||
- [Dice Armor](/garden/dice-armor)
|
||||
- [Capture the Citadel](/garden/capture-the-citadel)
|
||||
- I have more you can find on [my Itch.io page](https://thepaperpilot.itch.io/)
|
||||
- ... And several more in development! Most aren't going to have their own pages on here, but a long-term project of mine called [Kronos](/garden/kronos) is the exception!
|
||||
|
||||
## Tools (and other non-games)
|
||||
- [Profectus](/garden/profectus)
|
||||
- [Incremental Social](/garden/incremental-social)
|
||||
- [Babble Buds](/garden/babble-buds)
|
||||
- [V-ecs](/garden/v-ecs)
|
||||
- [Opti-Speech](/garden/opti-speech)
|
16
content/garden/neoliberalism.md
Normal file
16
content/garden/neoliberalism.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: "Neoliberalism"
|
||||
wordCount: 133
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Anarchism: /garden/anarchism
|
||||
Local Communities: /garden/local-communities
|
||||
---
|
||||
|
||||
Neoliberalism is a conservative political philosophy that emphasizes [Individualism](/garden/individualism) and is resistant to change/progress. It became popular with the advent of President Raegan and his sweeping changes to the US economy and government (replacing the comparatively socialist polices of the New Deal and the Great Society), and affects both the Republican and Democratic US political parties.
|
||||
|
||||
I believe neoliberalism primarily affected the boomer generation and generation x. This lines up with [trends in protest participation](https://nealcaren.org/publication/caren-social-2011/caren-social-2011.pdf), which dipped during that time (by birth cohort) and picked back up starting with Millenials. The government is still largely controlled by those generations and still very neoliberal, despite increasingly progressive and left-leaning youth demographics.
|
||||
|
||||
Neoliberalism often supports ideas like meritocracies, calling them fair and just. However, by ignoring the context surrounding people's abilities and socioeconomic status, meritocracies end up merely reinforcing the current status quo - and if the current status quo is unfair or unjust, so too will be the meritocracy. Needless to say, we do not currently live in a society where the status quo is fair and just.
|
13
content/garden/nostr.md
Normal file
13
content/garden/nostr.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
title: "Nostr"
|
||||
wordCount: 8
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
Decentralized: /garden/decentralized
|
||||
referencedBy:
|
||||
Fediverse: /garden/fediverse
|
||||
---
|
||||
|
||||
[Nostr](https://nostr.com) is a protocol for [Federated Social Media](/garden/fediverse)
|
29
content/garden/now.md
Normal file
29
content/garden/now.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: "/now"
|
||||
wordCount: 229
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
---
|
||||
|
||||
This "now page" offers a big picture glimpse into what I’m focused on at this point in my life. [What is a now page](https://nownownow.com/about)?
|
||||
|
||||
## Digital Gardens
|
||||
|
||||
I've been learning about a ton of concepts that I think can be interestingly combined; namely [The Small Web](/garden/the-small-web), [Digital Garden](/garden/digital-gardens)s, [Commune](/garden/commune), and [Local-First Software](undefined). I have an idea for a digital garden that is structured like a network of topics and includes conversations, pages, and citations about each topic. I want to build it on the [Agentic fediverse](undefined) so clients can build a large network by subscribing to community made networks across the fediverse.
|
||||
|
||||
Ultimately, I think this project could have some implications on how _this_ digital garden operates, so I've decided to stop further indieweb integrations like webmentions for now. I'd like to see a server be able to bridge indieweb and agentic fediverse posts, and start using the agentic fediverse posts as my new source of truth.
|
||||
|
||||
## Incremental Social
|
||||
|
||||
I'm running and improving the social media site [Incremental Social](/garden/incremental-social), along with CardboardEmpress.
|
||||
|
||||
I'd like to look into it eventually hosting a bridge between the mbin AP actors and [Agentic fediverse](undefined) identities.
|
||||
|
||||
## Chromatic Lattice
|
||||
|
||||
I'm working on a multiplayer incremental game called [Chromatic Lattice](/garden/chromatic-lattice) .
|
||||
|
||||
## Kronos
|
||||
|
||||
I'm working on a long single player narratively driven incremental game called [Kronos](/garden/kronos) . This is a very long-term project.
|
25
content/garden/open-source.md
Normal file
25
content/garden/open-source.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
title: "Open Source"
|
||||
wordCount: 25
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Advent Incremental: /garden/advent-incremental
|
||||
Cinny: /garden/cinny
|
||||
Commune: /garden/commune
|
||||
Dice Armor: /garden/dice-armor
|
||||
Forgejo: /garden/forgejo
|
||||
Game Dev Tree: /garden/game-dev-tree
|
||||
Logseq: /garden/logseq
|
||||
Mbin: /garden/mbin
|
||||
Planar Pioneers: /garden/planar-pioneers
|
||||
Profectus: /garden/profectus
|
||||
Synapse: /garden/synapse
|
||||
Vitepress: /garden/vitepress
|
||||
Weird: /garden/weird
|
||||
---
|
||||
|
||||
Projects with the source code publicly accessible
|
||||
|
||||
Typically also grants users the right to modify the code and redistribute those changes, depending on the license
|
37
content/garden/opti-speech.md
Normal file
37
content/garden/opti-speech.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
title: "Opti-Speech"
|
||||
wordCount: 312
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
---
|
||||
|
||||
In college I continued development on the Opti-Speech project, originally built alongside the scientific paper [Opti-speech: a real-time, 3d visual feedback system for speech training](https://www.researchgate.net/profile/Thomas-Campbell-11/publication/354182612_Opti-speech_a_real-time_3d_visual_feedback_system_for_speech_training/links/6424679ca1b72772e4360fa2/Opti-speech-a-real-time-3d-visual-feedback-system-for-speech-training.pdf)
|
||||
|
||||
## The Original Project
|
||||
|
||||
The Optispeech project involves designing and testing a real-time tongue model that can be viewed in a transparent head while a subject talks — for the purposes of treating speech errors and teaching foreign language sounds. This work has been conducted in partnership with Vulintus and with support from the National Institutes of Health (NIH).
|
||||
|
||||
:postsCard{image="/garden/system-architecture-600_1717384793933_0.jpg" alt="system-architecture-600.jpg"}
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/9uHqIRs7ZjM" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="display: block; margin: auto;"></iframe>
|
||||
|
||||
This video shows a talker with WAVE sensors placed on the tongue hitting a virtual target sphere located at the alveolar ridge. When an alveolar consonant is hit (e.g., /s/, /n/, /d/) the sphere changes color from red to green.
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Oz42mKvlzqI" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="display: block; margin: auto;"></iframe>
|
||||
|
||||
This video shows an American talker learning a novel sound not found in English. When the post-alveolar consonant is hit, the target sphere changes color from red to green. Here, the NDI WAVE system serves as input.
|
||||
|
||||
## My Work
|
||||
|
||||
As the sole programmer at UT Dallas Speech Production Lab at the time, my changes involved updating to a more modern version of Unity, improving the interface, in general cleaning up tech debt so it can more easily support new features, and added support for additional EMA systems, namely the Carstens AG501.
|
||||
|
||||
:postsCard{image="/garden/new-interface_1717384734845_0.png" alt="new-interface.png"}
|
||||
|
||||
In addition, the program now includes documentation and unit tests to improve program stability and maintainability going forward.
|
||||
|
||||
:postsCard{image="/garden/documentation_1717384823218_0.png" alt="documentation.png"}
|
||||
|
||||
:postsCard{image="/garden/unittests_1717384825666_0.png" alt="unittests.png"}
|
16
content/garden/planar-pioneers.md
Normal file
16
content/garden/planar-pioneers.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
title: "Planar Pioneers"
|
||||
wordCount: 25
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
tags:
|
||||
My Projects: /garden/my-projects
|
||||
Profectus: /garden/profectus
|
||||
---
|
||||
|
||||
Play it [here](https://thepaperpilot.org/planar)!
|
||||
|
||||
An [Open Source](/garden/open-source) game designed to show off [Profectus](/garden/profectus)' dynamic layer system!
|
||||
|
||||
The [TV Tropes](https://tvtropes.org/pmwiki/pmwiki.php/VideoGame/PlanarPioneers) page on this game mentions some of the cool things about this game
|
83
content/garden/political-quizzes.md
Normal file
83
content/garden/political-quizzes.md
Normal file
|
@ -0,0 +1,83 @@
|
|||
---
|
||||
title: "Political Quizzes"
|
||||
wordCount: 841
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
---
|
||||
|
||||
Political quizzes are a bit of a guilty pleasure of mine. I really enjoy getting my beliefs distilled into a handful of labels, and getting forced to think about issues I may not have thought about that thoroughly. I often take issue with the wording of various questions though, and certainly have opinions on some quizzes being better than others.
|
||||
|
||||
Ultimately, the reason I consider these quizzes a _guilty_ pleasure is because the results shouldn't really be used for anything. I believe we should vote on the issues directly, and any form of [Representative Democracy](/garden/representative-democracy) is an unnecessary abstraction. The labels may be useful as a mnemonic, but not as useful as the individual answers and the justifications behind those answers. Plus, people are going to interpret the questions differently, especially when it comes to understanding terms like liberalism, freedom, or merit.
|
||||
|
||||
With all that said, here I'll discuss some tests I've taken, the results I've gotten l, and my overall thoughts on it. I'll include the dates taken so they can map to [My Political Journey](/garden/my-political-journey).
|
||||
|
||||
# Prism Political Quiz
|
||||
|
||||
Made by the six triangles creator, I really like this [test](https://prismquiz.github.io)! It actually might be my favorite. It novelly gives you multiple positions to take on a given issue, rather than a statement you can agree or disagree with. I overall really liked the choices, and nearly always felt one represented my views.
|
||||
|
||||
I like the [results](https://prismquiz.github.io/results.html?result=m0QWd0KZP&lang=en) I got on 2024-09-06. I was surprised at my government value being just "direct democracy", when the way I define my views on Government in [My Political Beliefs](/garden/my-political-beliefs) (at the time of taking the test), which I believe I reflected accurately in my responses here, would probably include at least some points on anarchism and confederationism. That said, I liked these results so much that they inspired me to write that page on my political beliefs, which takes clear inspiration from this test.
|
||||
|
||||
:postsCard{image="/garden/image_1725623164393_0.png" alt="image.png"}
|
||||
|
||||
# Six Triangles
|
||||
|
||||
I was intrigued by [Six Triangles](https://sixtriangles.github.io/index.html)' idea of replacing axes with triangles, although some of the additional points seem a bit redundant, or just acted as a disguised additional axis. For example, the truth corner of personal freedom is not really related to the axis of freedom vs security. It could have easily been its own axis specific to misinformation. Similarly, I think the burden corner of equality is going to be highly correlated to their equality of opportunity score. Others, however, really benefit from the third point, like economy being split up into laissez faire capitalism, "well regulated" capitalism, and socialism.
|
||||
|
||||
Unfortunately, I found a lot of the questions to be poorly worded or vague. For example, I disagree with the statement "The government occasionally needs to do things which aren't popular for the good of its people" because popular isn't sufficient, but in a system that requires unanimity (or near unanimity), I'd argue everything that gets passed is for the good of the people, based off the values and considerations of those specific people. That nuance doesn't carry over if I just select "disagree" though. They also have the statement "Small government is usually better than big government" without any context for what big vs small mean in this context: number of employees? Amount of nationalized services? Number of constituents? Amount of regulations? This distinction matters for getting (more) accurate results.
|
||||
|
||||
The [results](https://sixtriangles.github.io/results.html?xzacdiqqqqfrqqlqbnwoipzx&lang=en) from taking it on 2024-09-05 were quite satisfying to go through. I particularly enjoyed being called a "Fanatic anti-imperialist". The main score I disagreed with was the government triangle; I would've preferred a higher minarchy score.
|
||||
|
||||
:postsCard{image="/garden/image_1725595248824_0.png" alt="image.png"}
|
||||
|
||||
:postsCard{image="/garden/image_1725595266970_0.png" alt="image.png"}
|
||||
|
||||
# SapplyValues
|
||||
|
||||
I took the [SapplyValues](https://sapplyvalues.github.io/) quiz on 2024-05-07:
|
||||
|
||||
:postsCard{image="/garden/image_1725596689335_0.png" alt="image.png"}
|
||||
|
||||
# 4Orbs
|
||||
|
||||
I took the [4Orbs](https://theghostofinky.github.io/4orbs/index.html) quiz on 2023-07-09:
|
||||
|
||||
:postsCard{image="/garden/image_1725596872858_0.png" alt="image.png"}
|
||||
|
||||
# Spekr
|
||||
|
||||
I like that this quiz gives live feedback, as it helped me introspect on the differences between my self-reported positions versus positions political quizzes assign me (for example, I usually think I'm way closer to anarchist than these tests usually put me, although funnily enough this quiz probably gave me one of the strongest anarchist score of any test I've taken). I also like that the creator is an anarchist themselves, which is likely uncommon across political quiz writers. I also felt like a lot of the questions were phrased in quite interesting ways.
|
||||
|
||||
I took the [spekr](https://jarick.works/spekr/) quiz on 2023-05-17:
|
||||
|
||||
:postsCard{image="/garden/image_1725597384430_0.png" alt="image.png"}
|
||||
|
||||
# isidewith
|
||||
|
||||
I really didn't like this quiz. I think its overly-constrained to the range of discussion considered politically viable in America, and therefore didn't ask any questions about abolishing the state or replacing corporations with worker's co-operatives. I disagree with my results here moreso than any other political quiz I've taken.
|
||||
|
||||
I took the [isidewith](https://www.isidewith.com/political-quiz) quiz on 2023-05-03:
|
||||
|
||||
:postsCard{image="/garden/image_1725598183342_0.png" alt="image.png"}
|
||||
|
||||
:postsCard{image="/garden/image_1725598206435_0.png" alt="image.png"}
|
||||
|
||||
I got quite different [results](https://secure.isidewith.com/elections/2016-presidential/1596263908) on 2016-10-30, pre-radicalization:
|
||||
|
||||
:postsCard{image="/garden/image_1725617078179_0.png" alt="image.png"}
|
||||
|
||||
# Political compass
|
||||
|
||||
I took the [political compass](https://www.politicalcompass.org/test) quiz on 2023-02-19:
|
||||
|
||||
:postsCard{image="/garden/image_1725598398891_0.png" alt="image.png"}
|
||||
|
||||
I'd gotten similar results on 2022-06-15:
|
||||
|
||||
:postsCard{image="/garden/image_1725598481054_0.png" alt="image.png"}
|
||||
|
||||
# 9Axes
|
||||
|
||||
I only took the short version, but here are my results from taking the [9Axes](https://9axes.github.io/) quiz on 2022-06-15:
|
||||
|
||||
:postsCard{image="/garden/image_1725598637811_0.png" alt="image.png"}
|
27
content/garden/pre-order-bonuses.md
Normal file
27
content/garden/pre-order-bonuses.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
title: "Pre-Order Bonuses"
|
||||
wordCount: 98
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Video Game Monetization: /garden/video-game-monetization
|
||||
---
|
||||
|
||||
Pre-order bonuses are benefits given to players who buy a game before it comes out
|
||||
|
||||
They primarily serve to benefit the company
|
||||
- People commit to buying before the embargo date passes
|
||||
- Heuristic of how well it'll sell after launch
|
||||
- Slight lead on return on investment
|
||||
- More significantly impacts indie studios, who will likely have less cash on hand
|
||||
- Companies make deals with storefronts to have exclusive bonuses, to drive customers to said storefronts
|
||||
|
||||
Common bonuses:
|
||||
- Digital goods:
|
||||
- Soundtrack
|
||||
- Cosmetics
|
||||
- [Premium Currency](/garden/premium-currency)
|
||||
- Physical goods:
|
||||
- Typically pins, keychains, etc.
|
||||
- Typically only included in physical editions of the game
|
18
content/garden/premium-currency.md
Normal file
18
content/garden/premium-currency.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
title: "Premium Currency"
|
||||
wordCount: 71
|
||||
published:
|
||||
hash:
|
||||
timestamp: NaN
|
||||
referencedBy:
|
||||
Pre-Order Bonuses: /garden/pre-order-bonuses
|
||||
---
|
||||
|
||||
A popular form of [MTX](/garden/mtx) where instead of receiving a useful item or effect directly, you receive a currency that is then spent on an in game store
|
||||
|
||||
Reasons companies use them
|
||||
- Abstracts the real world price of items
|
||||
- No strict conversion ratio
|
||||
- Discounts for bulk purchasing
|
||||
- Small amounts given for free based on story progression or watching ads
|
||||
- Consolidates smaller purchases into a larger one (decreasing friction of individual purchases)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue