final stretch before release
|
@ -2,12 +2,54 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
import react from '@astrojs/react';
|
import react from '@astrojs/react';
|
||||||
|
|
||||||
import tailwind from '@astrojs/tailwind';
|
import tailwind from '@astrojs/tailwind';
|
||||||
|
|
||||||
import mdx from '@astrojs/mdx';
|
import mdx from '@astrojs/mdx';
|
||||||
|
import m2dx from 'astro-m2dx';
|
||||||
|
|
||||||
|
import rehypeSlug from 'rehype-slug';
|
||||||
|
import rehypeMdxCodeProps from 'rehype-mdx-code-props';
|
||||||
|
|
||||||
|
import icon from 'astro-icon';
|
||||||
|
|
||||||
|
import node from '@astrojs/node';
|
||||||
|
|
||||||
|
/** @type {import('astro-m2dx').Options} */
|
||||||
|
const m2dxOptions = {
|
||||||
|
frontmatter: true,
|
||||||
|
exportComponents: true,
|
||||||
|
};
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [react(), tailwind(), mdx()]
|
site: "https://eleboog.com",
|
||||||
|
|
||||||
|
redirects: {
|
||||||
|
'/about': '/me',
|
||||||
|
'/slashes': '/me',
|
||||||
|
'/why': '/me#why',
|
||||||
|
'/uses': '/me#everyday',
|
||||||
|
'/carry': '/me#everyday',
|
||||||
|
'/tip': '/me#tip',
|
||||||
|
'/contact': '/me#contact',
|
||||||
|
'/verify': '/me#contact',
|
||||||
|
'/colophon': '/me#colophon',
|
||||||
|
'/now': '/journal#now',
|
||||||
|
'/links': '/sharefeed',
|
||||||
|
'/ideas': '/me#ideas',
|
||||||
|
'/feed.xml': '/feeds/feed.xml',
|
||||||
|
'/feeds': '/feeds/feed.xml',
|
||||||
|
},
|
||||||
|
|
||||||
|
integrations: [react(), tailwind(), mdx({
|
||||||
|
syntaxHighlight: 'false',
|
||||||
|
remarkPlugins: [[m2dx, m2dxOptions]],
|
||||||
|
rehypePlugins: [rehypeSlug, [rehypeMdxCodeProps, {tagName: 'code'}]],
|
||||||
|
extendDefaultPlugins: true,
|
||||||
|
}), icon()],
|
||||||
|
|
||||||
|
output: 'server',
|
||||||
|
|
||||||
|
adapter: node({
|
||||||
|
mode: 'standalone'
|
||||||
|
})
|
||||||
});
|
});
|
34
package.json
|
@ -11,16 +11,42 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.4",
|
"@astrojs/check": "^0.9.4",
|
||||||
"@astrojs/mdx": "^3.1.7",
|
"@astrojs/mdx": "^3.1.9",
|
||||||
|
"@astrojs/node": "^8.3.4",
|
||||||
|
"@astrojs/prism": "^3.1.0",
|
||||||
"@astrojs/react": "^3.6.2",
|
"@astrojs/react": "^3.6.2",
|
||||||
"@astrojs/tailwind": "^5.1.1",
|
"@astrojs/rss": "^4.0.9",
|
||||||
|
"@astrojs/tailwind": "^5.1.2",
|
||||||
|
"@fontsource/libre-baskerville": "^5.1.0",
|
||||||
|
"@fontsource/nunito": "^5.1.0",
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.11",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"astro": "^4.15.11",
|
"astro": "^4.16.10",
|
||||||
|
"astro-icon": "^1.1.1",
|
||||||
|
"astro-m2dx": "^0.7.16",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
|
"date-fns-tz": "^3.2.0",
|
||||||
|
"feed": "^4.2.2",
|
||||||
|
"github-slugger": "^2.0.0",
|
||||||
|
"linkedom": "^0.18.5",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"prism": "^4.1.2",
|
||||||
|
"prism-react-renderer": "^2.4.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
"react-code-block": "^1.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-icons": "^5.3.0",
|
||||||
|
"rehype-mdx-code-props": "^3.0.1",
|
||||||
|
"rehype-pretty-code": "^0.14.0",
|
||||||
|
"rehype-slug": "^6.0.0",
|
||||||
"tailwindcss": "^3.4.13",
|
"tailwindcss": "^3.4.13",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c"
|
"packageManager": "pnpm@9.10.0+sha512.73a29afa36a0d092ece5271de5177ecbf8318d454ecd701343131b8ebc0c1a91c487da46ab77c8e596d6acf1461e3594ced4becedf8921b074fbd8653ed7051c",
|
||||||
|
"devDependencies": {
|
||||||
|
"astro-auto-import": "^0.4.4",
|
||||||
|
"astro-mdx-code-blocks": "^0.0.6",
|
||||||
|
"node-html-parser": "^6.1.13"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
2504
pnpm-lock.yaml
generated
BIN
public/audio/test/stephano_draft.mp3
Normal file
35
public/auth/passkey.svg
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="1.5em"
|
||||||
|
height="1.5em"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
id="layer1"><g
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="g1"
|
||||||
|
transform="matrix(5.5,0,0,5.5,-18,-1)"><path
|
||||||
|
d="m 17.383412,21 v -2 c 0,-1.742816 -0.174273,-4 -2.383412,-4 H 9 c -2.209139,0 -4,1.790861 -4,4 v 2"
|
||||||
|
id="path1" /><circle
|
||||||
|
cx="12"
|
||||||
|
cy="7"
|
||||||
|
r="4"
|
||||||
|
id="circle1" /></g><g
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
|
id="g2"
|
||||||
|
transform="matrix(2.0754901,-2.0754901,2.0754901,2.0754901,52.211816,76.69653)"><path
|
||||||
|
d="m 2,18 v 3 c 0,0.6 0.4,1 1,1 h 4 v -3 h 3 v -3 h 2 l 1.4,-1.4 a 6.5,6.5 0 1 0 -4,-4 z"
|
||||||
|
id="path1-4"
|
||||||
|
style="stroke-width:2.38486;stroke-dasharray:none" /><circle
|
||||||
|
fill="currentColor"
|
||||||
|
id="circle1-3"
|
||||||
|
cy="7.5"
|
||||||
|
cx="16.5"
|
||||||
|
r="0.62505203"
|
||||||
|
style="stroke-width:2.50021" /></g></g></svg>
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/blog/banner_sept19.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
public/blog/blogbanner.png
Normal file
After Width: | Height: | Size: 876 KiB |
BIN
public/blog/blogbkglow.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
public/blog/hello-again/banner-lake.png
Normal file
After Width: | Height: | Size: 2.5 MiB |
BIN
public/blog/hello-again/meme-podcasts.png
Normal file
After Width: | Height: | Size: 746 KiB |
BIN
public/blog/hello-again/meme-vtec.png
Normal file
After Width: | Height: | Size: 334 KiB |
BIN
public/blog/hello-again/new-site-homepage.png
Normal file
After Width: | Height: | Size: 277 KiB |
BIN
public/blog/hello-again/old-next-shot-login.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
public/blog/hello-again/old-next-shot-main.png
Normal file
After Width: | Height: | Size: 369 KiB |
BIN
public/blog/hello-again/old-next-shot-menu.png
Normal file
After Width: | Height: | Size: 147 KiB |
BIN
public/blog/hello-again/old-next-shot-mobile.png
Normal file
After Width: | Height: | Size: 268 KiB |
BIN
public/blog/hello-again/old-next-shot-nav.png
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
public/blog/hello-again/xe-iaso-blog.png
Normal file
After Width: | Height: | Size: 228 KiB |
BIN
public/blog/kebo_ref.jpg
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
public/blog/python-lab/banner-explode.png
Normal file
After Width: | Height: | Size: 378 KiB |
BIN
public/blog/python-lab/banner-explode2.png
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
public/blog/python-lab/screenshot-output.png
Normal file
After Width: | Height: | Size: 163 KiB |
BIN
public/blog/screenshot_calckey.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
public/blog/screenshot_nova.png
Normal file
After Width: | Height: | Size: 1.5 MiB |
BIN
public/blog/shipment.png
Normal file
After Width: | Height: | Size: 942 KiB |
|
@ -1,9 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
|
||||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
|
||||||
<style>
|
|
||||||
path { fill: #000; }
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
path { fill: #FFF; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 749 B |
75
public/feed.xsl
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<xsl:stylesheet version="3.0"
|
||||||
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
|
||||||
|
<xsl:template match="/">
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="referrer" content="unsafe-url" />
|
||||||
|
<title><xsl:value-of select="/atom:feed/atom:title"/></title>
|
||||||
|
<link rel="stylesheet" href="https://www.rss.style/css/water.min.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>
|
||||||
|
<img alt="feed icon" src="https://www.vectorlogo.zone/logos/rss/rss-tile.svg" style="height:1em;vertical-align:middle;" /> 
|
||||||
|
<xsl:value-of select="/atom:feed/atom:title"/>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<xsl:value-of select="/atom:feed/atom:subtitle"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
This is the Atom <a href="https://www.rss.style/what-is-a-feed.html">news feed</a> for the 
|
||||||
|
<a><xsl:attribute name="href">
|
||||||
|
<xsl:value-of select="/atom:feed/atom:link[@rel='alternate']/@href | /atom:feed/atom:link[not(@rel)]/@href"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select="/atom:feed/atom:title"/></a> 
|
||||||
|
website.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>It is meant for <a href="https://www.rss.style/newsreaders.html">news readers</a>, not humans. Please copy-and-paste the URL into your news reader!</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<pre>
|
||||||
|
<code id="feedurl"><xsl:value-of select="/atom:feed/atom:link[@rel='self']/@href"/></code>
|
||||||
|
</pre>
|
||||||
|
<button
|
||||||
|
class="clipboard"
|
||||||
|
data-clipboard-target="#feedurl">
|
||||||
|
Copy to clipboard
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<xsl:for-each select="/atom:feed/atom:entry">
|
||||||
|
<details><summary>
|
||||||
|
<a>
|
||||||
|
<xsl:attribute name="href">
|
||||||
|
<xsl:value-of select="atom:id"/>
|
||||||
|
</xsl:attribute>
|
||||||
|
<xsl:value-of select="atom:title"/>
|
||||||
|
</a> - 
|
||||||
|
<xsl:value-of select="atom:updated" />
|
||||||
|
</summary>
|
||||||
|
<xsl:choose>
|
||||||
|
<xsl:when test="atom:content">
|
||||||
|
<xsl:value-of disable-output-escaping="yes" select="atom:content" />
|
||||||
|
</xsl:when>
|
||||||
|
<xsl:otherwise>
|
||||||
|
<xsl:value-of select="atom:summary" />
|
||||||
|
</xsl:otherwise>
|
||||||
|
</xsl:choose>
|
||||||
|
</details>
|
||||||
|
</xsl:for-each>
|
||||||
|
<p><xsl:value-of select="count(/atom:feed/atom:entry)"/> news items.</p>
|
||||||
|
<p><small>Powered by <a href="https://www.rss.style/"><img referrerpolicy="origin" src="https://www.rss.style/favicon.svg" style="height:1em;padding-right:0.25em;vertical-align:middle;" />RSS.Style</a></small></p>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"></script>
|
||||||
|
<script>
|
||||||
|
new ClipboardJS('.clipboard');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</xsl:template>
|
||||||
|
</xsl:stylesheet>
|
BIN
public/fonts/monof55.ttf
Normal file
BIN
public/fonts/monofur.ttf
Normal file
53
public/moffsoft.svg
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128mm"
|
||||||
|
height="128mm"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer2"
|
||||||
|
style="display:none">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -9.2868595,95.750726 H 133.85887 Z"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -9.2868595,56.361632 H 133.85887 Z"
|
||||||
|
id="path3" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -9.2868595,40.029568 H 133.85887 Z"
|
||||||
|
id="path4" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="matrix(1.1621419,0,0,1.1566525,-5.8107084,-13.922649)">
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#000000;stroke-width:6.98098;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 56.335379,95.559664 c 0,0 -47.5781154,-57.469822 -25.497919,-57.651895 21.764064,-0.179466 35.796534,10.638486 35.796534,10.638486"
|
||||||
|
id="path5" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:3.74999;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 55.011374,96.484246 c 0,0 11.048141,-82.389444 32.664062,-82.571766 21.620494,-0.182359 25.577414,52.297038 25.577414,52.297038 0,0 -40.948736,-0.467916 -58.241476,30.274728 z"
|
||||||
|
id="path6" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:8.18444;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1"
|
||||||
|
cx="26.073496"
|
||||||
|
cy="101.81606"
|
||||||
|
rx="16.924622"
|
||||||
|
ry="16.814188" />
|
||||||
|
<path
|
||||||
|
d="M 72.596859,92.827372 C 80.696472,84.026639 92.181106,78.877633 104.1848,78.375423"
|
||||||
|
style="display:inline;fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.73888;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1-9" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/moffsoft_64.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
53
public/moffsoft_adaptive.svg
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="128mm"
|
||||||
|
height="128mm"
|
||||||
|
viewBox="0 0 128 128"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
id="layer2"
|
||||||
|
style="display:none">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -9.2868595,95.750726 H 133.85887 Z"
|
||||||
|
id="path2" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -9.2868595,56.361632 H 133.85887 Z"
|
||||||
|
id="path3" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ffffff;stroke-width:1;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M -9.2868595,40.029568 H 133.85887 Z"
|
||||||
|
id="path4" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="layer1"
|
||||||
|
transform="matrix(1.1621419,0,0,1.1566525,-5.8107084,-13.922649)">
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:none;stroke:#fe5711;stroke-width:6.98098;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 56.335379,95.559664 c 0,0 -47.5781154,-57.469822 -25.497919,-57.651895 21.764064,-0.179466 35.796534,10.638486 35.796534,10.638486"
|
||||||
|
id="path5" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#fe5711;fill-opacity:1;stroke:#fe5711;stroke-width:3.75024;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 55.011374,96.484246 c 0,0 11.048141,-82.389444 32.664062,-82.571766 21.620494,-0.182359 25.577414,52.297038 25.577414,52.297038 0,0 -40.948736,-0.467916 -58.241476,30.274728 z"
|
||||||
|
id="path6" />
|
||||||
|
<ellipse
|
||||||
|
style="fill:none;stroke:#fe5711;stroke-width:8.18444;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1"
|
||||||
|
cx="26.073496"
|
||||||
|
cy="101.81606"
|
||||||
|
rx="16.924622"
|
||||||
|
ry="16.814188" />
|
||||||
|
<path
|
||||||
|
d="M 72.596859,92.827372 C 80.696472,84.026639 92.181106,78.877633 104.1848,78.375423"
|
||||||
|
style="display:inline;fill:none;fill-opacity:1;stroke:#fe5711;stroke-width:3.73888;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1-9" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/moffsoft_dark_64.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
public/moffsoft_light_64.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
12
src/_components.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import HR from '/src/components/HR.astro'
|
||||||
|
import MDXImage from '/src/components/mdx/MDXImage.astro'
|
||||||
|
import MDXCallout from '/src/components/mdx/MDXCallout.astro'
|
||||||
|
import MDXCodeBlock from '/src/components/mdx/MDXCodeBlock.astro'
|
||||||
|
|
||||||
|
export const components = {
|
||||||
|
hr: HR,
|
||||||
|
img: MDXImage,
|
||||||
|
MDXImage: MDXImage,
|
||||||
|
MDXCallout: MDXCallout,
|
||||||
|
pre: MDXCodeBlock,
|
||||||
|
}
|
1
src/_frontmatter.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
layout: '/src/layouts/PostLayout.astro'
|
34
src/components/Footer.astro
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import Icon from "../components/Icon.astro";
|
||||||
|
import HR from "../components/HR.astro";
|
||||||
|
|
||||||
|
const iconClass = 'fill-current hover:fill-crusta-800 dark:hover:fill-night-400'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<HR/>
|
||||||
|
<div class="flex items-center">contact — 
|
||||||
|
<a href='https://plush.city/@kebokyo' target='_blank' rel='noopener noreferrer' aria-label="fediverse">
|
||||||
|
<Icon icon="PiFediverseLogoFill" class={iconClass} title="fediverse" aria-label="fediverse"/>
|
||||||
|
</a>
|
||||||
|
 
|
||||||
|
<a href='https://bsky.app/profile/kebokyo.eleboog.com' target='_blank' rel='noopener noreferrer' aria-label="bluesky">
|
||||||
|
<Icon icon="FaBluesky" class={iconClass} title="bluesky" aria-label="bluesky"/>
|
||||||
|
</a>
|
||||||
|
 
|
||||||
|
<a href='mailto:kebokyo@eleboog.com' target='_blank' rel='noopener noreferrer' aria-label="email">
|
||||||
|
<Icon icon="MdMail" class={iconClass} title="email" aria-label="email"/>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- <SocialLink href='https://plush.city/@kebokyo' alt="fediverse" icon={PiFediverseLogoFill}/>
|
||||||
|
 
|
||||||
|
<SocialLink href='https://bsky.app/profile/kebokyo.eleboog.com' alt="bluesky" icon={FaBluesky}/>
|
||||||
|
 
|
||||||
|
<SocialLink href='mailto:kebokyo@eleboog.com' alt="email" icon={MdMail}/> -->
|
||||||
|
 |  <a href="https://ko-fi.com/kebokyo" class="text-subtitle hover:underline">tips appreciated!</a>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm">The writing contained within this site may contain the author's personal opinions. These opinions do not represent any of the author's employers or other organizations the author is a part of — past, present, or future.</p>
|
||||||
|
<p class="font-mono text-sm">© 2023-2024 Kebo Kitanari — All Rights Reserved</p>
|
||||||
|
</div>
|
5
src/components/HR.astro
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="flex-grow w-auto self-baseline h-1 border-t border-crusta-200 dark:border-night-800 m-4"></div>
|
31
src/components/Header.astro
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
const {current} = Astro.props;
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex items-baseline">
|
||||||
|
<a href="/" class="text-2xl xs:text-3xl sm:text-4xl font-serif title-gradient pb-2">
|
||||||
|
eleboog<span class='title-dot'>.</span>com
|
||||||
|
</a>
|
||||||
|
<div class="flex w-8 self-baseline h-1 border-t border-crusta-200 dark:border-night-800 mx-2"></div>
|
||||||
|
<h2 class="font-mono text-xl sm:text-2xl">{current}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navbar -->
|
||||||
|
<h2 class="font-mono mb-4 text-lg">
|
||||||
|
<a href='/newest' class="text-subtitle hover:underline">newest</a>
|
||||||
|
/
|
||||||
|
<a href='/blog/' class="text-subtitle hover:underline">archive</a>
|
||||||
|
/
|
||||||
|
<a href='/journal' class="text-subtitle hover:underline">journal</a>
|
||||||
|
/
|
||||||
|
<a href='/sharefeed' class="text-subtitle hover:underline">sharefeed</a>
|
||||||
|
/
|
||||||
|
<a href="/me" class="text-subtitle hover:underline">me</a>
|
||||||
|
/
|
||||||
|
<a href='/feeds' class="text-subtitle hover:underline">rss</a>
|
||||||
|
</h2>
|
||||||
|
</div>
|
41
src/components/Icon.astro
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
import { parse } from 'node-html-parser';
|
||||||
|
|
||||||
|
// Thank you David Warrington!
|
||||||
|
// https://ellodave.dev/blog/article/using-svgs-as-astro-components-and-inline-css/
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSVG(name: string) {
|
||||||
|
const filepath = `/src/svg/${name}.svg`;
|
||||||
|
const files = import.meta.glob<string>('/src/svg/**/*.svg', {
|
||||||
|
as: 'raw', eager: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!(filepath in files)) {
|
||||||
|
throw new Error(`${filepath} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = parse(files[filepath]);
|
||||||
|
|
||||||
|
const svg = root.querySelector('svg');
|
||||||
|
const { attributes, innerHTML } = svg;
|
||||||
|
|
||||||
|
return {
|
||||||
|
attributes,
|
||||||
|
innerHTML,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { icon, ...attributes } = Astro.props as Props;
|
||||||
|
const { attributes: baseAttributes, innerHTML } = getSVG(icon);
|
||||||
|
|
||||||
|
const svgAttributes = { ...baseAttributes, ...attributes };
|
||||||
|
---
|
||||||
|
|
||||||
|
<svg
|
||||||
|
{...svgAttributes}
|
||||||
|
set:html={innerHTML}
|
||||||
|
></svg>
|
58
src/components/mdx/MDXCallout.astro
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
---
|
||||||
|
import { MdInfoOutline, MdOutlineWarningAmber, MdOutlineStarBorder, MdCheck } from "react-icons/md";
|
||||||
|
|
||||||
|
import colors from 'tailwindcss/colors';
|
||||||
|
|
||||||
|
/*export type MDXCalloutProps = {
|
||||||
|
preset?: string,
|
||||||
|
// --- custom ---
|
||||||
|
color?: string,
|
||||||
|
title?: string,
|
||||||
|
icon?: any,
|
||||||
|
children: any,
|
||||||
|
}*/
|
||||||
|
|
||||||
|
const props = Astro.props
|
||||||
|
|
||||||
|
function parsePresets(preset?: string): {color: string, title: string, Icon: any} {
|
||||||
|
if (preset == null) return {color: 'blue', title: 'note', Icon: MdInfoOutline};
|
||||||
|
|
||||||
|
if (preset.match('note')) {
|
||||||
|
return {color: 'blue', title: 'note', Icon: MdInfoOutline};
|
||||||
|
} else if (preset.match('warning')) {
|
||||||
|
return {color: 'orange', title: 'warning', Icon: MdOutlineWarningAmber};
|
||||||
|
} else if (preset.match('important')) {
|
||||||
|
return {color: 'amber', title: 'important', Icon: MdOutlineStarBorder};
|
||||||
|
} else if (preset.match('new')) {
|
||||||
|
return {color: 'green', title: 'new', Icon: MdCheck};
|
||||||
|
} else return {color: 'blue', title: 'note', Icon: MdInfoOutline};
|
||||||
|
}
|
||||||
|
|
||||||
|
let {color, title, Icon} = parsePresets(props.preset);
|
||||||
|
|
||||||
|
if (props.color) color = props.color;
|
||||||
|
if (props.title) title = props.title;
|
||||||
|
if (props.icon) Icon = props.icon;
|
||||||
|
|
||||||
|
let class_colors = "bg-[var(--props-bg-color)] border-[var(--props-border-color)] dark:bg-[var(--props-dark-bg-color)] dark:bg-opacity-70";
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<blockquote class={"p-4 border-s-4 mb-2 rounded-lg" + ' ' + class_colors}
|
||||||
|
style={{
|
||||||
|
//@ts-ignore
|
||||||
|
'--props-bg-color': colors[color][100],
|
||||||
|
//@ts-ignore
|
||||||
|
'--props-border-color': colors[color][400],
|
||||||
|
//@ts-ignore
|
||||||
|
'--props-dark-bg-color': colors[color][900],
|
||||||
|
}}>
|
||||||
|
{/*<Icon className="h-full w-auto"/>*/}
|
||||||
|
<p class="font-bold uppercase" style={{
|
||||||
|
//@ts-ignore
|
||||||
|
color: colors[color][400]
|
||||||
|
}}>{title}</p>
|
||||||
|
<slot/>
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
125
src/components/mdx/MDXCodeBlock.astro
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
//import { CodeBlock } from 'react-code-block';
|
||||||
|
//import { themes } from 'prism-react-renderer';
|
||||||
|
import { Highlight } from 'prism-react-renderer';
|
||||||
|
import type {PrismTheme} from "prism-react-renderer";
|
||||||
|
|
||||||
|
import { parseHTML } from 'linkedom';
|
||||||
|
|
||||||
|
import pkg from 'lodash';
|
||||||
|
const {unescape} = pkg;
|
||||||
|
|
||||||
|
import { Prism } from '@astrojs/prism'
|
||||||
|
|
||||||
|
const theme: PrismTheme = {
|
||||||
|
plain: {
|
||||||
|
|
||||||
|
},
|
||||||
|
styles: [
|
||||||
|
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = Astro.props;
|
||||||
|
|
||||||
|
/*export type MDXCodeBlockProps = {
|
||||||
|
children: any,
|
||||||
|
class: string,
|
||||||
|
language?: string,
|
||||||
|
filename?: string,
|
||||||
|
lines?: [string],
|
||||||
|
words?: [string],
|
||||||
|
showLineNumbers?: boolean,
|
||||||
|
startLine?: number,
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*let darkMode = useMediaQuery(
|
||||||
|
{ query: "(prefers-color-scheme: dark)" },
|
||||||
|
undefined,
|
||||||
|
(isDark) => { darkMode = isDark }
|
||||||
|
);*/
|
||||||
|
|
||||||
|
let lang: string = '';
|
||||||
|
let filename: string = '';
|
||||||
|
|
||||||
|
const code_html = await Astro.slots.render('default');
|
||||||
|
|
||||||
|
const renderCode = (html) => {
|
||||||
|
const { document } = parseHTML(html);
|
||||||
|
const child = document.children
|
||||||
|
const match = /language-(\w+)/.exec(child[0].className);
|
||||||
|
lang = match ? match[1] : 'plaintext';
|
||||||
|
|
||||||
|
const match2 = /filename="(.*?)"/.exec(child[0].getAttribute('metastring'))
|
||||||
|
filename = match2 ? match2[1] : null;
|
||||||
|
//console.log(match)
|
||||||
|
return unescape(child[0].innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
const code: string = renderCode(code_html);
|
||||||
|
|
||||||
|
/*
|
||||||
|
(typeof props.children === 'string')
|
||||||
|
? props.children
|
||||||
|
: React.Children.toArray(props.children).reduce<string>((acc: any, child: ReactNode) => {
|
||||||
|
if (React.isValidElement(child) && typeof child.props.children === 'string') {
|
||||||
|
const match = /language-(\w+)/.exec(child.props.class);
|
||||||
|
lang = match ? match[1] : 'plaintext';
|
||||||
|
return acc + child.props.children;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, '');
|
||||||
|
*/
|
||||||
|
|
||||||
|
const lineNumbers = props.showLineNumbers ?? false;
|
||||||
|
const wrap = props.wrap ?? false;
|
||||||
|
const codeBackgroundTW = 'p-2 bg-neutral-300 bg-opacity-50 dark:bg-slate-800 dark:bg-opacity-100 rounded-lg font-mono';
|
||||||
|
|
||||||
|
/* bg-neutral-300 bg-opacity-50 dark:bg-slate-600 dark:bg-opacity-100
|
||||||
|
text-neutral-800 dark:text-slate-300
|
||||||
|
theme={darkMode ? themes.vsDark : themes.vsLight}*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
<Highlight
|
||||||
|
theme={theme}
|
||||||
|
code={code}
|
||||||
|
language={lang}
|
||||||
|
>
|
||||||
|
{({ class, style, tokens, getLineProps, getTokenProps }) => (
|
||||||
|
<>
|
||||||
|
{props.filename ? (
|
||||||
|
<div class={codeBackgroundTW + 'rounded-b-none w-fit bg-opacity-90 dark:bg-opacity-100 dark:bg-slate-600 text-black dark:text-white'}>
|
||||||
|
{props.filename}
|
||||||
|
</div>
|
||||||
|
) : ''}
|
||||||
|
<pre class={ codeBackgroundTW +
|
||||||
|
(wrap ? 'text-wrap' : 'overflow-y-scroll') + ' ' +
|
||||||
|
(props.filename ? 'rounded-tl-none' : '') + ' mb-2' }>
|
||||||
|
{tokens.slice(0,-1).map((line, i) => (
|
||||||
|
<div {...getLineProps({ line, key: i })}>
|
||||||
|
{line.map((token, key) => (
|
||||||
|
<span {...getTokenProps({ token, key })} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</pre>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Highlight>
|
||||||
|
*/
|
||||||
|
|
||||||
|
---
|
||||||
|
<>
|
||||||
|
{filename ? (
|
||||||
|
<div class={codeBackgroundTW + ' rounded-br-none w-fit bg-opacity-90 dark:bg-opacity-100 dark:bg-slate-600 text-black dark:text-white'}>
|
||||||
|
{filename}
|
||||||
|
</div>
|
||||||
|
) : ''}
|
||||||
|
<Prism
|
||||||
|
class={codeBackgroundTW + (filename ? 'rounded-tl-none' : '')}
|
||||||
|
code={code}
|
||||||
|
lang={lang}
|
||||||
|
/>
|
||||||
|
</>
|
22
src/components/mdx/MDXImage.astro
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
const className = 'relative -z-10 border-4 border-crusta-200 dark:border-night-800 rounded-lg shadow-lgr shadow-crusta-400/20 dark:shadow-night-400/50 my-2' +
|
||||||
|
' sm:w-[var(--props-size)]' +
|
||||||
|
' sm:m-[var(--props-center)]'
|
||||||
|
|
||||||
|
const props = Astro.props
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src={props.src}
|
||||||
|
alt={props.alt}
|
||||||
|
class={className}
|
||||||
|
style={{
|
||||||
|
//@ts-ignore
|
||||||
|
'--props-size': props.size + '%',
|
||||||
|
'--props-center': props.center ? 'auto' : 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<p class="text-sm italic text-subtitle mt-2">{props.alt}</p>
|
||||||
|
</div>
|
48
src/components/mdx/PostHeader.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { Post } from "@/.contentlayer/generated"
|
||||||
|
import MDXImage from "./MDXImage"
|
||||||
|
|
||||||
|
import { format, parseISO } from 'date-fns'
|
||||||
|
|
||||||
|
export default function PostHeader(post: Post) {
|
||||||
|
|
||||||
|
const numberToWord = (num: number) => {
|
||||||
|
switch(num) {
|
||||||
|
case 1: return "one";
|
||||||
|
case 2: return "two";
|
||||||
|
case 3: return "three";
|
||||||
|
case 4: return "four";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
{ ( post.cover && post.cover_alt ) ? (
|
||||||
|
<div className="mb-4">
|
||||||
|
<MDXImage src={post.cover} alt={post.cover_alt}/>
|
||||||
|
</div>
|
||||||
|
) : '' }
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-serif font-bold text-title">{post.title}</h1>
|
||||||
|
<h2 className="font-mono">— {format(parseISO(post.date), 'LLLL d, yyyy')}
|
||||||
|
{(post.updated) ? (
|
||||||
|
<span>, <span className="text-subtitle">updated {format(parseISO(post.updated), 'LLLL d, yyyy')}</span></span>
|
||||||
|
) : ''}
|
||||||
|
</h2>
|
||||||
|
<h2 className="text-sm italic mb-4 text-subtitle">{post.summary}</h2>
|
||||||
|
|
||||||
|
{(post.toc) ? ( <>
|
||||||
|
<h3 className="font-mono text-xl text-title">table of contents</h3>
|
||||||
|
<div id="toc" className="mb-4 ml-2 pl-2 border-l border-neutral-400 ">
|
||||||
|
{post.headings.map((heading: {level: number, text: string, slug: string}) => {
|
||||||
|
return (
|
||||||
|
<div key={`#${heading.slug}`}>
|
||||||
|
<a className="font-serif text-sm text-subtitle hover:underline data-[level=two]:pl-3 data-[level=three]:pl-6" data-level={numberToWord(heading.level)} href={'#' + heading.slug}>
|
||||||
|
{(heading.level > 1) ? '> ' : ''}
|
||||||
|
{heading.text}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>):''}
|
||||||
|
</>)
|
||||||
|
}
|
49
src/content/archives/2023-02-18-shipment.mdx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
date: 2023-02-18
|
||||||
|
tags: cod video_games game_design rant skub
|
||||||
|
title: Shipment is the Worst COD Map
|
||||||
|
summary: A rant about the infamous Call of Duty map "Shipment" and why I believe it is the worst map in the franchise.
|
||||||
|
toc: true
|
||||||
|
---
|
||||||
|
Yes. You heard me correctly. Shipment is the worst map in Call of Duty. Let me explain my reasoning.
|
||||||
|
|
||||||
|
# Wait, what *is* Shipment?
|
||||||
|
|
||||||
|
Shipment is a very smol map that is literally just a square arena with four shipping containers creating a four-way intersection in the middle, along with some more shipping containers on the sides to make those parts of the math not just complete sniper death zones.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In actuality, it's a very interesting map. Removed with all of the context surrouding COD's camo and weapon grind, I would say Shipment is a pretty cool map. Not the best or pentultimate COD map (right now that crown goes to Farm 18 but I'm probably showing my zoomer cringe nae-nae baby-ness for saying that), but a fun excursion.
|
||||||
|
|
||||||
|
The problem is that Shipment doesn't exist in a bubble. The whole reason Shipment is so popular is solely because of the fact that camos exist.
|
||||||
|
|
||||||
|
# A quick recap of COD's camo system
|
||||||
|
|
||||||
|
If you haven't played COD, you might not know that since Call of Duty 4: Modern Warfare, you can apply different paint jobs to your guns to show everyone how Operator™ you are. In that game, you only had to get headshots to get camos, but over time, players had to dance many more different types of dances in order to get all of the coolest camos.
|
||||||
|
|
||||||
|
In the most recent Modern Warfare games, you have to complete certain achievements with your gun in order to unlock camos. These achievements include headshots (again) but also kills from far away, killing multiple people in quick succession, killing multiple people without dying, etc.
|
||||||
|
|
||||||
|
Obviously, it takes a while to unlock all of these achievements. If only there was a way to get into fights more quickly to allow for unlocking these achievements faster. Oh wait.
|
||||||
|
|
||||||
|
# Shipment is great for camo grinding...
|
||||||
|
The map is extremely small for 12 players to occupy at once. Thus, you get into a lot more fights a lot more quickly. This, of course, makes it easier to accomplish certain camo challenges like double kills, headshots, hipfire kills, etc. You may have trouble getting longshot kills due to the map size, and you may have trouble saying alive long enough to get multiple kills without dying... but otherwise, it is a great map for grinding out those camo challenges and unlocking them all for a particular weapon.
|
||||||
|
|
||||||
|
# ...and that's a problem.
|
||||||
|
|
||||||
|
[Players like to optimize the fun out of a game](https://www.designer-notes.com/game-developer-column-17-water-finds-a-crack/). If they wanna do something, and there is a better way to do that thing, they will do that thing in an attempt to be better at the game... even if it's A. not what the developers want the players to do and/or B. it just makes the game worse to play. A great example is in stealth games: there is a large section of players who bypass an entire section of a stealth game's design and reload their save whenever they get detected.
|
||||||
|
|
||||||
|
In COD, Shipment presents this exact same problem. Part of COD's design is the varied maps that are on offer. A good COD game needs good maps in order to shine. The Modern Warfare reboot released in 2019 (which I will from now on refer to as MW19) is a good example of this: the mechanics are tight, but the maps are shit. If there are lots of fun maps on offer, they supplement the core gameplay by providing the player with a steady feed of varied arenas to run around and shoot people in.
|
||||||
|
|
||||||
|
But there's also that camo grind to think about. Obviously, you'll get through the camo grind faster if you play on Shipment. So... what if you... *only. played. on Shipment?*
|
||||||
|
|
||||||
|
Sure, you'll get the camo grind done faster. But it will also suck. Cause you're playing on the exact same map over and over again. You'll start to get bored. You'll start to get annoyed at all of the little mistakes the developers made with the core gameplay because that's all that's left after you stripped out the variable of different maps. Congratulations. You just optimized the fun out of Call of Duty.
|
||||||
|
|
||||||
|
# Quick aside about Modern Warfare II
|
||||||
|
Like I said before, MW19 had shit maps. So, of course, one of the things Infinity Ward aimed to fix with the new Modern Warfare II (which I will from now on refer to as MW22) was to make the maps... not shit. And they did it! The maps are... not actually trash this time! I've already shouted out Farm 18 as one of the best COD maps ever made. Shoothouse is also brilliant (tho that kinda doesn't count because it actually came from MW19, shockingly). Crown Raceway, Fortress, and Las Almas are very solid as well. The only maps that I feel are "meh" are Museum and El Asilo, but the fact that there are no maps as bad as Picadilly, Hackney Yard, or Azhir Cave is a massive plus in my book.
|
||||||
|
|
||||||
|
Of course... you won't realize any of that... if the only map you play on... *is fucking **Shipment.***
|
||||||
|
|
||||||
|
This is why I feel a bunch of ppl who complain about MW22 are somewhat misguided: if you purposefully ignore one of the best parts about MW22, of course the scales are going to be scewed towards the negatives. The maps are one the biggest reasons why I want to play MW22 and have massively enjoyed my time with the MW22 beta and free trial. Sure, MW22 is not perfect (see: Activision doing everything in their power to leave MP players stranded in the desert starving and dehydrated while offering every single Warzone player a private villa and a million COD Points)... but actively ignoring one of the best parts about the game is doing the game a massive disservice and paints you as disingenuous.
|
||||||
|
|
||||||
|
# Conclusion
|
||||||
|
Shipment sucks, suck my cock, ok goodbye
|
21
src/content/archives/2023-03-26-fastmail.mdx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
date: 2023-03-26
|
||||||
|
tags: selfhost tech nerd email fedi fediverse mastodon
|
||||||
|
title: I gave up (I'm using Fastmail now)
|
||||||
|
summary: A little update about establishing eleboog.com email through Fastmail.
|
||||||
|
---
|
||||||
|
I wanted to make my own email server so I could be independent from big tech's monopoly in email. I quickly found out that it was not worth it.
|
||||||
|
|
||||||
|
It was such a pain in the ass to set up, and I couldn't send emails to my GMail account because my home-rolled email looked too sus to GMail's filters.
|
||||||
|
|
||||||
|
So... I gave up. On a recommendation from someone else, I started my free trial of Fastmail and got it set up. After configuring my domain's DNS records, it works flawlessly!
|
||||||
|
|
||||||
|
Feel free to send me emails through my listed email: [kebokyo@eleboog.com](mailto:kebokyo@eleboog.com). Plaintext, personal emails are prefered.
|
||||||
|
|
||||||
|
Next on my list is getting PeerTube up and running and possibly getting a Fediverse instance going as well. I also need to use the Fediverse a little more lol. Currently I'm at [@kebokyo@plush.city](https://plush.city/@kebokyo), so feel free to hit me up on there!
|
||||||
|
|
||||||
|
so yee, cya later
|
||||||
|
|
||||||
|
p.s. i love how the day after i get the email set up, someone emails me out of the blue hehe
|
||||||
|
|
||||||
|
p.p.s. if you sent me an email within the past few months, send it again bc i probably didn't get it ;w;
|
57
src/content/archives/2023-03-28-jekyll-to-gemini.mdx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
date: 2023-03-28
|
||||||
|
tags: jekyll meta open_source blog gemini
|
||||||
|
title: New project! jekyll-to-gemini
|
||||||
|
summary: An announcment of a project I was working on that would allow me to port a Jekyll website to Geminispace.
|
||||||
|
---
|
||||||
|
Hey! I'm gonna start working on a plugin for Jekyll called [jekyll-to-gemini](https://gitea.eleboog.com/kebokyo/jekyll-to-gemini) that automatically generates a Gemini capsule from the Markdown that forms your website. This is so I can have a Gemini mirror of my blog for Cooler Dans to check out.
|
||||||
|
|
||||||
|
I would tell you about my plans for it, but I literally wrote all of them in the README, so imma just copy that into here lol.
|
||||||
|
|
||||||
|
## Notice: This is still a massive WIP and is currently non-functional.
|
||||||
|
|
||||||
|
## Intro
|
||||||
|
|
||||||
|
I wanted to create a Gemini version of my Jekyll blog in order to show my support for the open, indie web. However, as far as I can tell, there currently is no plugin that provides that functionality. The closest thing is [jekyll-gemtext](https://github.com/jebw/jekyll-gemtext), but that allows you to use Gemtext in place of Markdown for writing your pages and is more suited from making a Jekyll site out of an already existing Gemini capsule. I'm wanting the other way around: to automatically generate a Gemini capsule out of my Jekyll website.
|
||||||
|
|
||||||
|
So, I'm making it myself. With blackjack and hookers. Yeeee.
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
Gemtext has a few quirks that make converting an existing website to it a bit difficult. Luckily, there's some ways we can work around that.
|
||||||
|
|
||||||
|
The most notable limitation is that Gemtext does not support inline links. Yup. You can't do something [like this](http://picard.ytmnd.com/) in Gemtext. Instead, links have to go on their own separate line. The default solution will be to convert inline links into footnotes, like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
The most notable limitation is that Gemtext does not support inline links. Yup. You can't do something like this[1] in Gemtext. Instead, links have to go on their own separate line.
|
||||||
|
... (bottom of page) ...
|
||||||
|
# Footnotes
|
||||||
|
=> http://picard.ytmnd.com/ [1] like this
|
||||||
|
```
|
||||||
|
There will also be a few ways to customize how footnotes are generated, like where / how footnotes are placed, how much of the original text is included in the footnote for context, etc.
|
||||||
|
|
||||||
|
Images will be rendered as links with their alt text used as the display text. For example, an image like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Will be converted to Gemtext like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
=> https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/Jean-Luc_Picard_2.jpg/200px-Jean-Luc_Picard_2.jpg it's the funny bald man
|
||||||
|
```
|
||||||
|
|
||||||
|
Some Gemini clients allow for the rendering of images linked like this, so doing this lets Gemini clients that support that view the image like HTTP normies can.
|
||||||
|
|
||||||
|
In terms of other formatting, like italics, bold, etc, the Markdown syntax is generally left alone. Why? Y'all know that `*this*` means *this*, so even if it doesn't look like *this*, you'll still know what it's supposed to mean. Plus, the asterisks still perform their function of emphasizing a bit of text even if they don't work like they were meant to. Life hack!
|
||||||
|
|
||||||
|
## Compatability
|
||||||
|
|
||||||
|
I'm going to be focusing on getting this to work on [my own website](https://gitea.eleboog.com/kebokyo/eleboog.com) first, which is a personal blog built on Jekyll 4.3.2 and a slightly modified version of Minima 2.5.1. If your website doesn't fit that bill, it might not be the most ideal for now. Feel free to suggest improvements, tho!
|
||||||
|
|
||||||
|
Most notably in terms of compatability, this plugin parses the Markdown files used to build your website in order to generate the proper Gemtext. This obviously means that if you don't use Markdown for your site, this isn't gonna work. I don't plan on supporting any alternative languages right now, but feel free to shout if you're using something else. If enough people say they use X thing and they wanna be able to use this, I may add support for X thing to this plugin. Alternatively... fork this!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This plugin will use GPLv3 as its license. TL;DR: you can make any changes you want to this, but if you share your fork with others, you have to provide the source somewhere too. Part of me wants to do AGPLv3, but I don't think everyone is crazy enough to publish their website's source on the internet like me lol.
|
||||||
|
|
||||||
|
so yee! hopefully this goes somewhere. cyall l8r
|
29
src/content/archives/2023-07-06-fediverse-and-nova.mdx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
date: 2023-07-06
|
||||||
|
title: "The Fediverse! + Nova editor impressions"
|
||||||
|
tags: fedi mastodon fediverse nova mac csci calckey meta
|
||||||
|
summary: Thoughts about Calckey and the Nova text editor.
|
||||||
|
---
|
||||||
|
|
||||||
|
I've been continuously carving out my online presence in ways that is not beholden to any one corporation or organization. I see it as a way to learn real-world software engineering skills as well as contribute to the re-democratization of the internet. Yesterday, I took another big step towards those goals by setting up my own ActivityPub (or "fediverse") instance running [Calckey](https://calckey.org/). You can find it at [fedi.eleboog.com](https://fedi.eleboog.com)!
|
||||||
|
|
||||||
|
![A screenshot of my Calckey instance as it is currently set up. My profile picture is in the top-left corner on top of a side bar containing numerous icons linking to different sections of the app. A "Federation" ticker is on the top of the screen, showcasing the instances that I am actively federating with. The main feed shows a post from zenith containing the text "Gonna be furry trash at my ML presentation today [smiley face]" and a picture of a t-shirt zenith is wearing featuring multiple anthro characters. Another side bar on the right has numerous widgets, including the time, date, notifications, and server stats such as CPU and RAM usage.](/blog/screenshot_calckey.png)
|
||||||
|
|
||||||
|
Currently, the instance is just for me, but if you really want to join it, shoot me and email and I'll think about it. Alternatively, you can send me a message on fedi through my new handle [@kebokyo@fedi.eleboog.com](https://fedi.eleboog.com/@kebokyo).
|
||||||
|
|
||||||
|
Sadly, due to the high ram utilization of both Calckey and Discourse, I can't have both running at the same time. So, the Discourse forums that I had up for about a month has been shut down for now. Calckey has a bunch of cool features like local-only posts and "pages" that could make it a decent replacement (and one that's definitely more future-proof).
|
||||||
|
|
||||||
|
I've also been checking out ways to improve my workflow. I've been trying out [Sublime Merge](https://www.sublimemerge.com/) to make managing my website's git repo a bit easier, and I've enjoyed it so far! However, I recently also decided to give [Nova](https://nova.app) a shot... and it blew me away.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The layout of Nova is very flexible, allowing you to add multiple different sections to the screen with different content such as terminals and a web view. It also has a pretty neat source control system that can replace Sublime Merge for simple tasks.
|
||||||
|
|
||||||
|
Sadly, it does have some bugs. I tried CTRL+S on a .png file (for some reason) and the app crashed. Whoops. On top of that, it is a paid, closed-source app that costs $99, but it's a one-time payment! You do have to subscribe for $49/yr if you want updates after the first year, but if you don't, you still keep the last version you got forever. It's a much better model than a lot of apps nowadays (where once you stop paying the subscription, you lose access to the app).
|
||||||
|
|
||||||
|
Overall, I'm really enjoying my time with Nova, and I will continue to use it until at least my free trial expires.
|
||||||
|
|
||||||
|
Finally, I want to briefly shout out something that I added to the site a bit ago that I never made a blog post about: the [sharefeed](/sharefeed/). It's essentially a place for me to share articles, blog posts, websites, and other links that I find interesting. It also has an [Atom](/sharefeed/rss/) feed that you can use in pretty much any RSS/Atom reader to keep up to date with the things I share in it. I don't update it as much as I would like to, but I plan to keep this around for a long time. If you have any suggestions for things I could add to it, let me know!
|
||||||
|
|
||||||
|
See y'all again in... fuck, four months i guess
|
||||||
|
|
38
src/content/config.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import {z, defineCollection} from 'astro:content';
|
||||||
|
|
||||||
|
const Posts = defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
date: z.date(),
|
||||||
|
updated: z.date().optional(),
|
||||||
|
summary: z.string().optional(),
|
||||||
|
cover: z.string().optional(),
|
||||||
|
cover_alt: z.string().optional(),
|
||||||
|
tags: z.string().optional(),
|
||||||
|
draft: z.boolean().optional(),
|
||||||
|
toc: z.boolean().optional(),
|
||||||
|
narration: z.string().optional(),
|
||||||
|
narration_date: z.date().optional(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const Archives = defineCollection({
|
||||||
|
type: 'content',
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
date: z.date(),
|
||||||
|
updated: z.date().optional(),
|
||||||
|
summary: z.string().optional(),
|
||||||
|
cover: z.string().optional(),
|
||||||
|
cover_alt: z.string().optional(),
|
||||||
|
tags: z.string().optional(),
|
||||||
|
draft: z.boolean().optional(),
|
||||||
|
toc: z.boolean().optional(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
'posts': Posts,
|
||||||
|
'archives': Archives
|
||||||
|
}
|
307
src/content/posts/2024-06-16-hello-again.mdx
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
---
|
||||||
|
title: Hello Again
|
||||||
|
date: 2024-06-18
|
||||||
|
updated: 2024-07-05
|
||||||
|
cover: '/blog/hello-again/banner-lake.png'
|
||||||
|
cover_alt: A picture of a lake surrounded by trees, with an orange kayak visible on the right side. Photo by Alice Triquet on Unsplash.
|
||||||
|
summary: "The first post to go on my new blog. Remarks on the development of this website, why it took so long for me to rework it, and where I want to go from here."
|
||||||
|
toc: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Every time I do one of these posts, it is woefully short and vague.
|
||||||
|
The last one was just a short list of all the new stuff that's online. I don't want to do that this time.
|
||||||
|
I put way too much effort into figuring this out for me to just say "hey, new blog pogchamp, go bookmark it or something i guess".
|
||||||
|
Cause yeah... this took *way* too long to figure out.
|
||||||
|
|
||||||
|
Let's start at the beginning:
|
||||||
|
|
||||||
|
# Why do a redesign in the first place?
|
||||||
|
|
||||||
|
I actually like Jekyll a lot. It's one of the simpler static site generators to get up and running (after you wrangle past ruby).
|
||||||
|
It's also very customizable, though I wish the plugin ecosystem was more documented.
|
||||||
|
|
||||||
|
The big problems started around the time I implemented the Chirpy theme.
|
||||||
|
I wanted to expand the functionality of the site with things like the sharefeed and do some heavy visual customization in order to make the site look
|
||||||
|
less like a cookie-cutter clone-template-send-tweet kinda site and more like its own thing with its own graphic design vision.
|
||||||
|
|
||||||
|
Once I started messing with that stuff, I quickly realized that Jekyll wasn't really designed for making fancy UX,
|
||||||
|
and it *especially* was not designed for working with bespoke data like, say, an array of links to other websites with custom descriptions.
|
||||||
|
|
||||||
|
At the end of the day, Jekyll's web pages are still mostly made using static markup languages like HTML or Markdown. There can be dynamic functionality
|
||||||
|
built out using Ruby and Liquid, but that's more of an extension on top of the static pages. If I want to enable more dynamic content within my site, it
|
||||||
|
would require libraries on top of libraries on top of ruby gem extensions on top of *aaaaaaaaaaaa*
|
||||||
|
|
||||||
|
Oh, and it doesn't help that the Chirpy theme is not very friendly on mobile.
|
||||||
|
I noticed *two different levels of scrollbars* when trying to navigate on mobile, and the responsive design is still a bit too scaled up with many
|
||||||
|
elements overly padded taking up too much space. If I want to build a site ideal for both desktop *and* mobile,
|
||||||
|
it would be more ideal to roll my own UX rather than relying on a template someone else created... and that's not easy to do with just HTML + CSS, even
|
||||||
|
with Jekyll's built-in support for preprocessors like SASS.
|
||||||
|
|
||||||
|
At that point, what I need is less a static site generator and more...
|
||||||
|
|
||||||
|
## A modern web framework
|
||||||
|
|
||||||
|
Oh god. Oh fuck. I need to learn Javascript. And React. I'm going to die.
|
||||||
|
|
||||||
|
Well, I'm not going to die, I've done this before in a college course about databases. And I did just fine in the end.
|
||||||
|
There was just one small problem: learning React was not on the curriculum.
|
||||||
|
|
||||||
|
Our final for this course was not an exam but a group project where up to four people had to make a fake e-commerce website
|
||||||
|
requiring the use of not one but *two* databases (we had to copy a database the professor provided into our own database that we then worked with).
|
||||||
|
My group project partners decided that instead of making the website in PHP like we were taught, we needed to use "modern web frameworks" like React
|
||||||
|
and Tailwind and host our database on a serverless database host like Supabase and generally make everything needlessly complicated for the sake
|
||||||
|
of "this is what we oughtta do cause everyone else (in the industry, not our class) is doing it!!1!"
|
||||||
|
|
||||||
|
Of course, I had never done any of this before. So getting familiar with Javascript and React took some getting used to. I still hate the whole
|
||||||
|
`boo = () => far` syntax with a passion. It just looks so stupid and makes no sense to me.
|
||||||
|
|
||||||
|
We also ended up being so focused on getting our "modern web framework" to actually, well, *work* that we ended up missing a very
|
||||||
|
obvious-in-hindsight sanity check.
|
||||||
|
One of the "products" that we copied from the legacy database just had a datetime
|
||||||
|
for the name. Tuns out that datetime updated every couple hours or so on the legacy database, so if we were making sure to pull from the legacy
|
||||||
|
database occasionally to make sure everything was gucci, that product's name on *our* database would more or less tell you the current date.
|
||||||
|
We didn't do that. So our sanity check product was way behind. The TA ended up docking some points on our project for that, and all of my project
|
||||||
|
partners were super salty in the group Discord about it, calling the TA incompetent and all that jazz. I have a feeling their response had something
|
||||||
|
to do with the fact that the TA was clearly South Asian...
|
||||||
|
|
||||||
|
Anyways, looking back on all that mess, I guess I can thank it for introducing me to React and Javascript. Without that foundation to build off of,
|
||||||
|
doing this website rework would have taken a lot longer... though that isn't saying much.
|
||||||
|
|
||||||
|
The group project was done in base React (using `create-react-app` as a starting point). That was hell. So if I was going to use React, I would want
|
||||||
|
to use a different "wrapper" framework that would make building my site a lot easier. I ended up landing on Next.js because it was one of the more
|
||||||
|
popular metaframeworks and it seemed easy enough to hop into.
|
||||||
|
|
||||||
|
# My Next.js journey
|
||||||
|
|
||||||
|
This isn't the first time I tried remaking my website with Next.js. Originally, I intended to design my website much like contemporary webapps \-
|
||||||
|
navbar at the top with fancy shadcn/ui hover menus, a detailed footer, a detailed account menu (I'll get into where "accounts" come into the equation shortly),
|
||||||
|
and lots of nested divs and Cards and stuff.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
There were two main goals with this redesign:
|
||||||
|
- 1) Design a homegrown comments system using Postgres & Auth.js. Essentially, allow visitors to make an eleboog.com account and leave comments on my blog posts.
|
||||||
|
- 2) Use responsive design to make the site easy to navigate on both desktop & mobile. The main way I accomplished this is turning the sitemap into a pullout menu you can access by tapping a hamburger on the navbar.
|
||||||
|
|
||||||
|
<MDXImage size='50' center='true' src='/blog/hello-again/old-next-shot-login.png' alt='A screenshot of the original Next.js blog redesign's login dialog. The title says "login to eleboog.com", with the description reading "With an eleboog.com account, you can comment on eleboog.com blog posts and join a growing community of tech & smallweb enthusiasts." The main login button lets you log in with a passkey, but there are also single sign-on buttons for Google, GitHub, and Discord. Finally, there is a link to register a new account.' />
|
||||||
|
<MDXImage size='35' center='true' src='/blog/hello-again/old-next-shot-menu.png' alt='A screenshot of the original Next.js blog redesign's mobile navigation menu. It includes sections for "blog", "community", "account", and "about", mirroring the sitemap seen at the footer of the desktop site.' />
|
||||||
|
|
||||||
|
I ended up dropping this version of the redesign before I fully implemented the sitemap / mobile menu nor the comment system.
|
||||||
|
|
||||||
|
The big reason why was because I bit off *slightly* more than I could chew. In my attempt to be "trendy", I used shadcn/ui components
|
||||||
|
for a large majority of the site's design. This worked well... until I needed to edit the components to work better for my usecases. For example,
|
||||||
|
the mobile menu was created using the Drawer component, but I had to edit the component to allow for a variant that comes out from the top rather
|
||||||
|
than the bottom. The fact that I *could* edit the component is indeed a perk of shadcn/ui, but the fact that I *had to* rather than that functionality
|
||||||
|
already being accounted for in the original component just ended up reminding me of why I tend to shy away from WYSIWYG-style solutions:
|
||||||
|
once I start bumping up against the boundaries of what the tool is capable with, my patience can run incredibly thin.
|
||||||
|
|
||||||
|
I think another reason why things fell apart was that instead of making sure I had a "minimal viable product" before expanding the project's scope,
|
||||||
|
I started work on multiple different aspects of my site at the same time, expanding my project's scope too early.
|
||||||
|
I didn't start figuring out mobile UX until *after* I had started implementing the account features.
|
||||||
|
I started writing a guide on "Flexdown" before I even started actually implementing it, even on the supposed "Flexdown guide" itself.
|
||||||
|
I was so ambitious with what I wanted to do that even though I had been using [Linear](https://linear.app) to plan out the development of my
|
||||||
|
project, I still ended up putting my eggs into too many baskets.
|
||||||
|
I ended up perpetually [halfway to Malamaroo](https://www.youtube.com/watch?v=PmWUAIr3VdY&pp=ygUdaGFsZndheSB0byBrYWxhbWF6b28gZHIgc2V1c3M%3D)
|
||||||
|
once again with seemingly no escape in sight.
|
||||||
|
|
||||||
|
## Can we start over?
|
||||||
|
|
||||||
|
After a brief stint with SvelteKit (which I may make a blog post about later), I decided that the problem wasn't Next.js but instead the way I was
|
||||||
|
approaching the project.
|
||||||
|
|
||||||
|
I was chasing trends and "staple webdev libraries" without thinking about how they would benefit me first.
|
||||||
|
I saw a bunch of people using shadcn/ui and ranting and raving about how cool it is, and I got swept up in the hype.
|
||||||
|
I thought it would make the development process so much easier when in reality it just made things more complicated.
|
||||||
|
|
||||||
|
In a way, I fell into the same trap my group project partners did: they got swept up in doing the things the way everyone else does them, and
|
||||||
|
ended up working so hard on getting those tools working that they missed crucial details staring them right in the face. I was so caught up in
|
||||||
|
getting shadcn/ui and Auth.js and Neon and all that stuff working... that I forgot to put actual links in the sitemap or make the blog archive link
|
||||||
|
actually go anywhere. I spread myself too thin, and the scope of everything balooned so much that it got very overwhelming very quick.
|
||||||
|
|
||||||
|
Instead, I needed to start small and work my way *up* to more tools as I need them. Instead of adding a new tool because I think it *would* help me,
|
||||||
|
I need to add tools that I *know* will help me. I also needed to focus my work on one area of the site at a time. I needed to make sure one section
|
||||||
|
was *completely done* before I moved on to the next (of course, nothing is ever "completely done", but the focus here is on the *feeling* of being done,
|
||||||
|
something I rarely get to experience because I keep hopping between different projects and never finishing what I start... like that alien from the
|
||||||
|
kids show clip I linked lol).
|
||||||
|
|
||||||
|
I had to start over. And this time, I needed to do things right.
|
||||||
|
|
||||||
|
# How I designed this version of the site
|
||||||
|
|
||||||
|
I was heavily inspired by [the personal website & blog of Xe Iaso](https://xeiaso.net). The minimal UX "fluff" and emphasis on text
|
||||||
|
along with a cream-colored light theme that's easy on the eyes were the main aspects I wanted to ~~rip off~~ *make homages to*, as you may
|
||||||
|
be able to tell from the home page.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
It's also very much influenced by how much trouble I had wrangling shadcn/ui: while I reluctantly added
|
||||||
|
it to my project to fix some CSS variable shennanigans, I'm trying very hard to minimize the usage of it.
|
||||||
|
The UX style of shadcn/ui, while visually appealing, is a bit too... "samey"? Waltz into any hobbyist "webapp" developed in the past couple years
|
||||||
|
and you'll see tons of UI elements imported from shadcn/ui and slapped onto the page without much modification other than color palletes.
|
||||||
|
|
||||||
|
It's also contradictory to the "text-first" theming I'm giving to this current vision of the site. For example, you may have already noticed a
|
||||||
|
distinct lack of buttons on this site in favor of text links. It not only makes the site easier to operate w/ screen readers & keyboard
|
||||||
|
navigation (accessibility is definitely something I want to work on for this version of the site), but it also results in less visual clutter.
|
||||||
|
This is another aspect I ~~ripped~~ *borrowed* from le orca's website. I'm already trying to incorporate a "think smarter, not harder" philosophy
|
||||||
|
into the development of this site, so what better way to visually represent that than to make the site look like it came from both 1999 and 2099
|
||||||
|
at the same time?
|
||||||
|
|
||||||
|
## Compare & Contrast
|
||||||
|
|
||||||
|
Even though the original Next.js iteration of this site didn't last very long, it still taught me a lot, and a lot of elements of that site
|
||||||
|
have been caried forward into the verison you see now.
|
||||||
|
|
||||||
|
I kept the same font selection in order to make my blog stand out from others.
|
||||||
|
I'm especially proud of using the mono font ([Monofur](https://www.dafont.com/monofur.font)) in more parts of the site,
|
||||||
|
as it is my prefered terminal font.
|
||||||
|
I am also (for now) using MDX to power my blog posts, but instead of wrangling `next/mdx`, I decided to try Contentlayer... and I'm very impressed!
|
||||||
|
It made implementing and extending MDX very easy, and I would definitely recommend it for anyone wanting to make a similar site to mine.
|
||||||
|
|
||||||
|
There are, however, a couple of key differences. First of all, right now I'm focusing squarely on getting my blog exactly where I want it to be,
|
||||||
|
*including the amount of content available*, before I start developing the other systems of the site, such as the comment and account systems.
|
||||||
|
I want to actually, ya know, *write stuff* more often this time, and the perpetual busywork of adding new features to my blog directly contributes
|
||||||
|
to me not writing jack shit for the blog itself. I also want to make sure the formatting of my blog and the accessibility of my content is at a
|
||||||
|
good place before I throw two brand-new full-stack systems into the mix. Accessibility is something I suspect was lacking in the earlier form of this blog,
|
||||||
|
so I want to focus on making sure the blog is fully accessible through screen readers and keyboard navigation.
|
||||||
|
|
||||||
|
# Where to go from here
|
||||||
|
|
||||||
|
Right now, the focus is on making sure the base features are in order and that I'm actually writing instead of constantly working on new features
|
||||||
|
that don't matter. So, until I have at least **two to five additional articles published on this website**, I will soely work on maintaining and
|
||||||
|
improving the existing functionality of this website. This includes the MDX rendering, overall site performance & responsiveness, UX tweaks, etc.
|
||||||
|
|
||||||
|
After I feel like I'm ready, I want to start delivering new features that are either supplemental to my blog or allow me to add more things to my articles.
|
||||||
|
|
||||||
|
The following is a list of planned features, starting with...
|
||||||
|
|
||||||
|
## Sharefeed 2.0
|
||||||
|
|
||||||
|
Last time on Dragon Ball Z, I introduced a "sharefeed", essentially a collection of links to other websites that I thought were interesting.
|
||||||
|
It had its own RSS feed so, if you wanted, you could subscribe to it and get a currated collection of random shit in your Feedly or whatever.
|
||||||
|
|
||||||
|
My main problems with the sharefeed were how rarely I updated it (of course) and how exactly it was implemented. The Chirpy theme for Jekyll had
|
||||||
|
functionality built in to generate an RSS feed along with the rest of the site. It was basically a whole bunch of Liquid magic.
|
||||||
|
To create the sharefeed, I duplicated this functionality and tweaked it to instead work with a YAML array of bespoke data I crafted myself. I also
|
||||||
|
duped the functionality that powered the home page listing of posts, card formatting and all, to power the sharefeed's page on my site.
|
||||||
|
It worked! But it always felt slightly hacky. Jekyll is definitely not designed for stuff like this, and implementing the sharefeed just reinforced
|
||||||
|
how hard it is to get Jekyll to do exactly what you want.
|
||||||
|
|
||||||
|
Now that I'm using an actual fullstack framework, I think it's time to give the sharefeed another shot. This time, I'll be using JSON to format
|
||||||
|
the data instead of YAML since there are better tools for handling JSON in JS/TS (it's almost like JavaScript is *in the freakin' name*).
|
||||||
|
This will make it much easier to display the sharefeed on the website as well as build a RSS feed off of it.
|
||||||
|
|
||||||
|
One detail I want to fix with this version of the sharefeed, though, is related to the RSS feed. I want to allow people to subscribe to one feed for
|
||||||
|
all of my content instead of having to subscribe to multiple feeds. So while my blog posts and the sharefeed will each get their own separate feeds,
|
||||||
|
there will also be **one shared RSS feed that contains all of the "feedable" content I post on the site.** There's one more feed that will
|
||||||
|
eventually be incorporated into this as well, but that warrants more explanation...
|
||||||
|
|
||||||
|
## Embedded media
|
||||||
|
|
||||||
|
One thing I didn't really do too much on Jekyll was embed content within my articles. The most I ever did was static images. I want to change that.
|
||||||
|
|
||||||
|
Now that I am working with MDX, implementing embedded media is as simple as creating a new component for that embed, then adding that component into
|
||||||
|
the list of `mdxComponents` available to me. After that, all I need to do is just call it with something like `<YouTube v="fjasljfkj" t="253"/>` and boom,
|
||||||
|
I got a YouTube video embedded in my article. It isn't the *most* elegant looking solution, but its implementation is so smooth that I'm not too
|
||||||
|
nitpicky about it anymore.
|
||||||
|
|
||||||
|
The main use for embedded media that I'm excited about, though, is embedding audio through an audio player. Why?
|
||||||
|
|
||||||
|
<MDXImage src="/blog/hello-again/meme-podcasts.png" size="80" center='true' alt='A variation of the SpongeBob "Imagination" meme: the titular sponge bob is sitting in a cardboard box, spreading his hands out wide, drawing a (fake) rainbow between them, and exclaiming "Imagination!" ...But this time, instead of "Imagination", it's "PODCASTS".'/>
|
||||||
|
|
||||||
|
Okay, maybe not podcasts necessarily, but close. Basically, one problem I see with screen reading my articles is that if I include visual elements
|
||||||
|
like images, the only description you can get of them is whatever I can fit inside the alt text (which, if you may have noticed, pulls double duty
|
||||||
|
as the image caption... at least for now). You also probably know of the struggles of conveying tone through text. I try to do that with
|
||||||
|
*fancy* **formatting** <span className="font-serif">techniques</span>, but obviously if you can't see the text, you can't see whether that
|
||||||
|
text is bold or italic or whatever. This isn't just an accessibility issue: what if you're out on a walk and want to catch up with something I wrote
|
||||||
|
without having to stare at your phone the entire time? You can't easily just put it on in the background and listen to it like you can a YouTube video.
|
||||||
|
|
||||||
|
But you know what YouTube videos have? People talking. What if... get this... *I talked?*
|
||||||
|
|
||||||
|
Something I want to start doing with my blog posts, especially longer ones (like this one lol) is providing "narrated" versions that you can listen
|
||||||
|
to either on the website itself or through an RSS feed. Once the narration is available, all you need to do is click or tap on an embedded
|
||||||
|
audio player at the top of the page and my lovely voice will start reading the article out loud to you.
|
||||||
|
I will try to convey the tone that may be hard to convey over text, describe any visual elements such as images with
|
||||||
|
more detail than I may put in the alt tags, and generally provide a quality, enjoyable, and (most importantly) fun narration.
|
||||||
|
|
||||||
|
It would also be a good idea to put these narrated articles up on their own RSS feed.
|
||||||
|
Of course, they will also be a part of the "main" feed along with my text posts and sharefeed,
|
||||||
|
but I want to give it its own RSS feed for one specific purpose: podcast players. Many podcasts are delivered in the form of an RSS feed,
|
||||||
|
so in turn many podcast players allow users to import RSS feeds into the app to subscribe to a podcast directly without having to
|
||||||
|
go through third-party platforms like Apple Podcasts, Spotify, etc.
|
||||||
|
|
||||||
|
If I make an RSS feed for my narrated articles, you could subscribe to it on, say, your iOS Podcasts app, or any other podcasts app that you choose.
|
||||||
|
Not only will you get notified when a new narrated article is out, but you'll also be able to listen to it through the app,
|
||||||
|
either right there and then or whenever the hell you want. On top of that, if I do publish my narrated articles through Apple Podcasts, Spotify, etc.,
|
||||||
|
it will mean that content is now officially part of a content recommendation algorithm. Somebody can stumble across my dumb bullshit ramblings
|
||||||
|
just by the will of the ghost of Steve Jobs. Woa.
|
||||||
|
|
||||||
|
This whole concept is really cool to me, and I really want to try to get it implemented. Once I do, I'll be sure to write up an article about it.
|
||||||
|
I'll also make sure that the narrated version of that article is published at the exact same time as the text version, so you can hear me talk about
|
||||||
|
the system as you are using it. Wild stuff.
|
||||||
|
|
||||||
|
## The journal, or "shittenings"
|
||||||
|
|
||||||
|
Before I wrap things up, I briefly want to touch on my half-baked attempt at a ["HTML Journal"](https://journal.miso.town), a page I called "shittenings".
|
||||||
|
It was essentially a single page where I would occassionally post small updates on what I was working on. I really like the concept of it, but
|
||||||
|
I'm not sure whether to make it again or try to transition over to social media like fedi, bluesky, cohost, etc. If I do implement it, I'd want to make
|
||||||
|
a habit of updating it every week even if I don't post a full article. I may even put it on its own RSS feed or mix it with audio rambles that would
|
||||||
|
also be posted on my "podcast" RSS feed. I don't know for sure.
|
||||||
|
|
||||||
|
And now, for something completely different...
|
||||||
|
|
||||||
|
# A reflection
|
||||||
|
|
||||||
|
Usually, whenever I get to the end of a blog post, I'm not really sure how to end it. Most of the time they're just a stream of consciousness, a
|
||||||
|
loosely linked list of thoughts and ideas and concepts and opinions that just go on and on until I run out of things to say... and
|
||||||
|
it's not like you can tie rabbit holes up in a neat little bow unless you're a god or something.
|
||||||
|
|
||||||
|
To be fair, this particular post still falls under that general template. I didn't know exactly what I wanted to write going into this.
|
||||||
|
I just knew I didn't want this to be as short as most of my other blog posts,
|
||||||
|
and I wanted to give some idea as to how the hell I got here (for prosperity!).
|
||||||
|
|
||||||
|
You can probably tell I'm getting tired and out of my element. It's taking me a bit too much of my being to not end this on one of those zingers
|
||||||
|
like I usually do.
|
||||||
|
|
||||||
|
But I don't want to blog like I usually do anymore. I want to blog like someone who actually has stuff to say. Sure, I still want to be relatively
|
||||||
|
casual with what I talk about (I'm certainly not an expert in much of anything right now), but I want what I put out there to be meaningful and
|
||||||
|
substantial. I don't think I'll ever get to Professional™ YouTube® Video™ Essayist™ levels of "substantial"... but checking the word count I've
|
||||||
|
got in this thing so far, around 2500 words is way more than I ever imagined I'd put out for a first post.
|
||||||
|
|
||||||
|
The problem is that specific desire to be "meaningful" and "substantial" in what I have to say... is one of the meanest and toughest obstacles
|
||||||
|
between me and actually saying anything at all.
|
||||||
|
|
||||||
|
There's a reason why "Updates once or twice a week I guess" has become my version of ["YGS Every Friday"](https://www.urbandictionary.com/define.php?term=YGS%20Every%20friday):
|
||||||
|
to post an update is to have something I want to talk about. When I have something I want to talk about, I need to be able to talk about it in a way
|
||||||
|
that feels "meaningful" and "substantial". Of course, I also need to actually finish what I start, which as I have already established is close to impossible on a good day.
|
||||||
|
I may *start* on a good idea, but as I whittle away at that idea, I almost instantly get discouraged. I inevitably come towards a "Now what?" moment that
|
||||||
|
puts a dampener on the whole thing. So I put it down. And then I never pick it back up.
|
||||||
|
|
||||||
|
Still... once in a while... I start something. And then I keep going. And going. And going. Never letting off the gas.
|
||||||
|
Because I know in the back of my mind as soon as I let go, the spark is lost. So surely... eventually... through hours and hours of hyperfocus...
|
||||||
|
I actually finish what I start.
|
||||||
|
|
||||||
|
At first, I thought this post was one of those moments. I started working on this post in the morning. It is now 11:11PM. I worked on this. The. Entire. Day.
|
||||||
|
|
||||||
|
But I can't do that every day, let alone "once or twice a week".
|
||||||
|
I need to find a way to motivate myself to actually finish the articles I write. And I think through writing this, I finally figured out how.
|
||||||
|
|
||||||
|
I don't *need* to be "meaningful" or "substantial".
|
||||||
|
The fact that I started this article means that the idea is already meaningful enough **to me** to be worth exploring.
|
||||||
|
|
||||||
|
When I inevitably get to a "Now what?" moment, I don't have to push through no matter what. I can allow myself to put it down. I can allow myself to rest.
|
||||||
|
It's how this article got made. When I felt like my wheels were spinning fruitlessly, I stood up.
|
||||||
|
I made myself food. I joined a voice chat and listened to people talk. I listened to good music.
|
||||||
|
I did something to refresh my mind, and suddenly it became a lot easier to pick up the article again and start writing from where I left off.
|
||||||
|
It's also how this website got made. When what I was doing wasn't working, I put it down, let it sit there for a while, then came back to it with fresh eyes.
|
||||||
|
|
||||||
|
What you're seeing is the result of allowing myself to rest. When you find meaning through rest, the substance isn't that hard to find.
|
||||||
|
The motivation and meaning come from your ability to step back and regroup, and the substance comes from letting your brain cool off before working it again.
|
||||||
|
|
||||||
|
I don't need to seek meaning or substance. I don't need to persue it, afraid if I let go that it will float away, never to be seen again.
|
||||||
|
Once I sit down for a second to live a little, the magic finds *me*.
|
||||||
|
|
||||||
|
...It's almost like that's how you deal with ADHD or something. Wild.
|
||||||
|
|
||||||
|
Thanks for reading. [Here's an album suggestion as a gift for sitting down yourself.](https://www.youtube.com/watch?v=nU55ar-O2V0&list=PLEz_mLoNZMeRJxPMw7JRs9w-9GG-rCGi6) See ya in a week or two.
|
72
src/content/posts/2024-09-19-what-am-i-doing.mdx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
---
|
||||||
|
title: "Wait, what am I actually doing?"
|
||||||
|
date: 2024-09-19
|
||||||
|
summary: "A quick update post thinking about the blog aspect of my site... and wondering if I'm going about this the right way."
|
||||||
|
cover: "/blog/banner_sept19.png"
|
||||||
|
cover_alt: "A mysterious sattelite tower standing in the center of a dense dark fog, a sickly yellow sky peering from behind. Photo by Julian Zwengel on Unsplash."
|
||||||
|
---
|
||||||
|
|
||||||
|
<MDXCallout preset="warning">
|
||||||
|
Lower your expectations. Dumbness lies ahead.
|
||||||
|
</MDXCallout>
|
||||||
|
|
||||||
|
I might have lied.
|
||||||
|
|
||||||
|
I thought I was going to be able to pump out an article once a month, maybe two a month. Turns out that did not happen.
|
||||||
|
Instead, I'm now just three days off of exactly three months since my last article. Whoops.
|
||||||
|
|
||||||
|
# How could this happen??!?!
|
||||||
|
|
||||||
|
This summer was weirdly stressful for me. Everyone was talking about "hot girl summer", "brat summer", all that stuff... to me it all
|
||||||
|
just felt aimless and endless. Every day I wanted the summer to end so I could go back to some sort of routine that I could fit myself in.
|
||||||
|
|
||||||
|
Now that it's the fall and I'm back in school, my mental health has improved considerably. Even though I technically have less free time,
|
||||||
|
I feel like I can do *more* with that free time because I have a structure around that free time I can plan around. Without a
|
||||||
|
consistent schedule, my time blindness gets jacked up to 11 and I have no idea how to organize that extra time that I have.
|
||||||
|
|
||||||
|
Part of it is probably just me not being used to adulting yet. Now in my fifth year of college, I feel like I'm just starting to find my
|
||||||
|
footing in this "adulting" business. Every time I thought I was getting closer, reality taught me I had a much longer way to go... but now,
|
||||||
|
I think I am *actually* making progress this time. Trial and error with time management and general organization is slowly giving way
|
||||||
|
to actual understanding, though I really wish I didn't have to learn all of this the hard way.
|
||||||
|
|
||||||
|
The result of all of this is that I feel like I have more focus and energy *to* focus, which means I can make better progress on the
|
||||||
|
creative projects I want to work on... which includes this blog. It's a shame it took this long to (mostly) figure everything out, though.
|
||||||
|
|
||||||
|
## So what were all of those new features added to the site for?
|
||||||
|
|
||||||
|
That was me reaching for tasks related to my site that would give me more dopamine / a greater sense of accomplishment from engaging in that work.
|
||||||
|
Adding new features to the site is frankly more fun than writing new blog posts.
|
||||||
|
|
||||||
|
I think for the forseeable future, I'm pausing my idea for narrated articles — in fact, I'm hiding the feed for that so there's less random stuff
|
||||||
|
that doesn't do anything on my site. It's actually way more difficult than I thought it would be to read through an entire blog post out loud
|
||||||
|
and edit the resulting audio to remove stutters, random pauses, and other unpleasantries. I might do it one day if I make a post specifically for
|
||||||
|
conversion into a podcast, but for right now I think it would be too much work for not enough benefits back. If there is anyone who would benefit from
|
||||||
|
narrated articles who would like to provide feedback regarding this and/or tips on how to make them more easily, please reach out!
|
||||||
|
|
||||||
|
I *do* want to implement my comments seciton idea at some point, but that will require a lot of work, so what I'm going to do is make a new branch
|
||||||
|
in my repo for implementing it **after** I publish this post so all of that mess is on its own separated verison of the codebase. As always, you can
|
||||||
|
see what exactly I'm doing by visiting [the repo on my Forgejo instance](https://forgejo.eleboog.com/kebokyo/eleboog-refresh).
|
||||||
|
|
||||||
|
Finally, I still really like the idea of the sharefeed, but the reality is that I have such a flood of stuff that could be cool coming in that it's
|
||||||
|
hard to choose what to read. Most of my sharefeed posts come from a Telegram bot that showcases top Hacker News posts, and while I really
|
||||||
|
appreciate that resource... it's a lot to take in. I need to start currating this stuff more, and I think what I really need is to turn NetNewsWire
|
||||||
|
into a Cool Shit from People I Like & Trust Aggregator™. I'm not sure how, but I want to make it work. And hey, if I do end up getting it working,
|
||||||
|
I could turn *that* into a blog post! Eh? Eeehh???
|
||||||
|
|
||||||
|
## ...okay, so where *have* you been posting?
|
||||||
|
|
||||||
|
Here, mostly, just in [my journal page](/journal/) instead of actual blog posts.
|
||||||
|
|
||||||
|
I feel less pressure writing there because that page is
|
||||||
|
specifically designed for shorter, less professional-looking posts. This kinda ends up being a self-fulfilling prophecy, though, because
|
||||||
|
if I keep writing in my journal, I keep reinforcing that the place where I can write anything is in the journal, and actual posts need
|
||||||
|
to be reserved for Big Stuff™... which isn't true. Hell, there's [a whole hashtag about how that isn't true](https://100daystooffload.com)
|
||||||
|
(that I won't be participating in just yet because 100 posts in a year sounds like a lot lmao).
|
||||||
|
|
||||||
|
Still, I find writing in this journal a lot better than posting to social media such as Fedi or Bluesky.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Not sure if I shared this album yet in my journal, but
|
||||||
|
[here's a really cool J-rock album](https://www.youtube.com/playlist?list=OLAK5uy_lI4xDv36-meW-Z8s1nxRvpH1OaHG5DbzM)
|
||||||
|
with a surprising amount of English lyrics and one song that got featured in an (imo) underated anime.
|
495
src/content/posts/2024-10-01-python-lab.mdx
Normal file
|
@ -0,0 +1,495 @@
|
||||||
|
---
|
||||||
|
title: Here's a Python Function I Wrote Out of Spite
|
||||||
|
summary: Importing LoggerPro data into Google Colab is weridly complicated, so I wrote a Python function to fix that. I break down how it works so you can understand how it works even with minimal Python experience.
|
||||||
|
date: 2024-10-01 6:00:00
|
||||||
|
cover: /blog/python-lab/banner-explode2.png
|
||||||
|
cover_alt: An old laptop exploding. Sloppily edited from a photo by Hugo Clément on Unsplash & a green screen explosion video on YouTube.
|
||||||
|
toc: true
|
||||||
|
---
|
||||||
|
|
||||||
|
Last week, I did a lab for my intro to physics course. This was the first experimental lab, and so I had to learn how to work a
|
||||||
|
piece of software named [Vernier LoggerPro](https://www.vernier.com/product/logger-pro-3/). Since we use Vernier sensors for all of our
|
||||||
|
data collection, we use Vernier's LoggerPro software to actually record the data.
|
||||||
|
|
||||||
|
The lab manual for this particular lab specifically stated that we needed to log all of the data we collected for nine different trials in a
|
||||||
|
document made using [Google's Colab service](https://colab.research.google.com). Colab (one 'l', not two) is a cloud-based webapp that
|
||||||
|
allows you to make Jupyter-compatible Python notebooks online and store those notebooks in your Google Drive. In non-tech speak, it lets
|
||||||
|
you write blocks of Python code, run those blocks of code individually so you can immediately see the results of that code, and insert
|
||||||
|
rich text inbetween those blocks of code so you can give further context as to what you are doing.
|
||||||
|
|
||||||
|
When you copy data from LoggerPro into a Colab document, you end up with a blob of text formatted as a vertically-oriented table.
|
||||||
|
Each column represents a different type of data (in my case: time, position, & velocity), and each row represents a specific sample of data
|
||||||
|
(like, in my case, the position & velocity of an object 0.5 seconds after the start data collection).
|
||||||
|
|
||||||
|
```
|
||||||
|
0.85 0.1932805 0.446948055556
|
||||||
|
0.9 0.227066 0.668087777778
|
||||||
|
0.95 0.265139 0.786518055556
|
||||||
|
1 0.3068135 0.867027777778
|
||||||
|
1.05 0.3517465 0.940296388889
|
||||||
|
1.1 0.4007955 1.01394611111
|
||||||
|
1.15 0.4532745 1.085595
|
||||||
|
1.2 0.509355 1.15772027778
|
||||||
|
1.25 0.5688655 1.23432361111
|
||||||
|
1.3 0.632492 1.32293194444
|
||||||
|
1.35 0.7002345 1.41468444444
|
||||||
|
1.4 0.773465 1.46260916667
|
||||||
|
1.45 0.859901 1.2219375
|
||||||
|
1.5 0.9139235 0.638075277778
|
||||||
|
```
|
||||||
|
|
||||||
|
To use this data in graphing libraries such as [Matplotlib](https://matplotlib.org), I need to convert this data into a set of arrays, or
|
||||||
|
"lists" as they are called in Python. Problem: Matplotlib wants each *type* of data to be in its *own list*... which means I need to rotate
|
||||||
|
this graph 90° (or, as the math kids call it, "transpose" the table).
|
||||||
|
|
||||||
|
I've talked to other students who took this class about it, and their solutions were something like this:
|
||||||
|
|
||||||
|
1. Make an Excel spreadsheet.
|
||||||
|
2. Put the data in that spreadsheet.
|
||||||
|
3. Use Excel's transpose tool to rotate the table.
|
||||||
|
4. Export the spreadsheet as a CSV file or just leave it as is.
|
||||||
|
5. Upload the spreadsheet to Google Drive and read it using file management functions, either by Google or by a third-party library.
|
||||||
|
|
||||||
|
I originally did the same... but I put the data into a code editor first....... and then I converted it into a CSV..............
|
||||||
|
and then I imported the CSV into Excel................... and then I translated the graph......................... and then I copy and pasted
|
||||||
|
the text into Colab to manually convert into lists........................................
|
||||||
|
|
||||||
|
Obviously, all of this was incredibly tedious and convoluted and absolutely **not** the right way to do any of this. There's got to be a better way!!1!1!!!!
|
||||||
|
|
||||||
|
So, I made a function in Python to do all of this for me. It's actually way simpler than I thought it would be and I feel incredibly dumb
|
||||||
|
both for myself and everyone else who tried to rope Excel into being the solution.
|
||||||
|
|
||||||
|
I'm going to be annoying and not show you the actual function until the very end of this post. However, I will be *more* than happy to
|
||||||
|
walk you through how I wrote it!
|
||||||
|
|
||||||
|
# How I Did the Thing
|
||||||
|
|
||||||
|
This function does not require **any** third-party libraries or even any standard libraries/modules. All you need is some basic knowledge
|
||||||
|
of Python and a healthy software engineering spirit.
|
||||||
|
|
||||||
|
Also, quick disclaimer: I am assuming that you are like my fellow physics students — *not* good at Python or programming in general.
|
||||||
|
Thus, I will overexplain a lot of stuff so that I can help make you *somewhat* good at Python. If you are already fluent in programming
|
||||||
|
mumbo-jumbo, I will kindly suggest to just skip to the end of this article (the table of contents can take you right there) and just
|
||||||
|
look up the parts of the code that you don't understand. If you know other programming languages but *don't* know Python and you're still
|
||||||
|
just getting started with this whole "programming" thing, I still recommend you read through the whole article just to get familiar with
|
||||||
|
Python and its [quirks and features](https://www.youtube.com/watch?v=2aiopbNnyF8).
|
||||||
|
|
||||||
|
## At the beginining
|
||||||
|
|
||||||
|
We start off by defining the variable we will use to store our new set of lists:
|
||||||
|
|
||||||
|
```python
|
||||||
|
out = []
|
||||||
|
```
|
||||||
|
|
||||||
|
It is given the name "out" because it is the "*out*put" of the function. Get it? Eh? Eh????
|
||||||
|
|
||||||
|
I initialize this variable by making the variable equal to an empty list. If I wanted to put stuff in the list from the getgo, I could put
|
||||||
|
numbers or values inbetween the square brackets (so, doing `out = [1, 2, 3]` would make `out` a list with three numbers: 1, 2, & 3).
|
||||||
|
But I don't want to do that here because I don't actually *know* how much stuff we're actually going to put in here. All I want to do is
|
||||||
|
establish that `out` is a list for use later when we actually start filling it up. Thus, I make it equal to an empty list with nothing
|
||||||
|
inbetween the brackets.
|
||||||
|
|
||||||
|
In my actual function, I put a type hint on this variable so that it's obvious what's supposed to go in it. You don't have to do this,
|
||||||
|
but I think it's a good idea regardless. For this variable, it will be a list containing lists of floating point numbers (yes, I know,
|
||||||
|
Listception, get your [bwwaaaahhh](https://www.youtube.com/watch?v=Vl1b1sXMyRE)'s out now cause I'm not allowing you to do it again).
|
||||||
|
So, the type hint for this variable should be `list[list[float]]`.
|
||||||
|
|
||||||
|
```python
|
||||||
|
out: list[list[float]] = []
|
||||||
|
```
|
||||||
|
|
||||||
|
In most other programming languages, we would call this list of lists
|
||||||
|
a "two-dimensional array", or "2D array" for short. I'm going to be using that term interchangably with "table" from now on —
|
||||||
|
just wanted to let you know so I don't confuse you.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
But how do we put stuff *in* this variable? That's what the rest of this function is for: to parse through the blob of text from LoggerPro
|
||||||
|
and store it as our desired transposed 2D array in the `out` variable.
|
||||||
|
|
||||||
|
We need to solve a small problem first: how do we store our raw LoggerPro dump? Luckily, Python lets us define a string with multiple lines
|
||||||
|
in it pretty easily: just flank the text with three quote marks (single or double, doesn't matter as long as both ends match).
|
||||||
|
|
||||||
|
```python
|
||||||
|
test_dump = '''0.85 0.1932805 0.446948055556
|
||||||
|
0.9 0.227066 0.668087777778
|
||||||
|
0.95 0.265139 0.786518055556
|
||||||
|
1 0.3068135 0.867027777778
|
||||||
|
1.05 0.3517465 0.940296388889
|
||||||
|
1.1 0.4007955 1.01394611111
|
||||||
|
1.15 0.4532745 1.085595
|
||||||
|
1.2 0.509355 1.15772027778
|
||||||
|
1.25 0.5688655 1.23432361111
|
||||||
|
1.3 0.632492 1.32293194444
|
||||||
|
1.35 0.7002345 1.41468444444
|
||||||
|
1.4 0.773465 1.46260916667
|
||||||
|
1.45 0.859901 1.2219375
|
||||||
|
1.5 0.9139235 0.638075277778'''
|
||||||
|
```
|
||||||
|
|
||||||
|
We're doing it this way because Python has built-in functions for iterating over multi-line strings pretty easily. I'll show you what they
|
||||||
|
are later. Plus, it's the easiest way (imo) to go from raw dump to usable input: just add a variable name, an equals sign, and three quote
|
||||||
|
marks around the data. Boom. Shrimple.
|
||||||
|
|
||||||
|
Now that we've got our data stored in a way that's easily parsable, all we need to do is feed it into our function as an argument.
|
||||||
|
I decided to add type hints to the top of the function so it is super duper obvious what we're putting into it and what we're getting
|
||||||
|
out of it. We're feeding the function a single string (which has the type name `str` for some reason) and getting back our 2D array of
|
||||||
|
floating-point numbers (type name `float`) that we defined at the top of the function. So, the header line for the function would look
|
||||||
|
something like this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def LoggerProToArray(text: str) -> list[list[float]]:
|
||||||
|
```
|
||||||
|
|
||||||
|
Again, we don't have to do fancy type hinting like this. We could just do...
|
||||||
|
|
||||||
|
```python
|
||||||
|
def LoggerProToArray(text):
|
||||||
|
```
|
||||||
|
|
||||||
|
and that's it. The function will still work the same.
|
||||||
|
|
||||||
|
The type hints are only there to make it obvious what **types** of data we are working
|
||||||
|
with and should expect. Sadly, Google Colab doesn't do type checking with its interpreter, but other apps that let you write Python code
|
||||||
|
may have support for "linting" utilities that take type hints into account and yell at you if you're passing in conflicting types of
|
||||||
|
data. I won't go into how they work here, but if you really want to know, I suggest you look it up.
|
||||||
|
|
||||||
|
## Let's start parsing!
|
||||||
|
|
||||||
|
First, let's take our text and divide it up by its lines. To do this, we can just slap `.splitlines()` at the end of our input variable.
|
||||||
|
|
||||||
|
```python
|
||||||
|
lines: list[str] = text.splitlines()
|
||||||
|
```
|
||||||
|
|
||||||
|
This will take our single string and split it up into a *list* of strings, where each line becomes its own entry in the list. That's pretty easy, right?
|
||||||
|
|
||||||
|
We also want to keep track of whether we are currently reading the **first line** of our data or not. Why? I'll get to that soon.
|
||||||
|
I used to do this by keeping track of the line number through a function we're going to learn about later (`enumerate()`) but I
|
||||||
|
realized that was dumb if I only want to focus on the very first line. So, I made a boolean for it.
|
||||||
|
|
||||||
|
```python
|
||||||
|
first: bool = True
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't know already, a "boolean" (type name `bool` in Python) is a value that is either true or false. On or off. 1 or 0. Yes or no.
|
||||||
|
Here, we set our variable to `True` since, when we start iterating through our lines, the first line we read... will be the first line
|
||||||
|
of the data. Duh.
|
||||||
|
|
||||||
|
Note that the 'T' in `True` is capitalized. This is a quirk of Python compared to other languages: the names for the base values of
|
||||||
|
"true" and "false" are capitalized. So, if you want to define a boolean to be true, you can't use `var = true` — you have to use
|
||||||
|
`var = True`. I learned that the hard way. Whoops.
|
||||||
|
|
||||||
|
Alternatively, you could just say screw it and use numbers instead:
|
||||||
|
|
||||||
|
```python
|
||||||
|
first: bool = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember that third example I gave? "1 or 0"? That works here. `1` is always read as equivalent to `True` (in other words, it is a "truthy"
|
||||||
|
value). On the other hand, `0` is always read as equivalent to `False` (in other words, it is a "falsy" value). So, whenever you need to use
|
||||||
|
`True` and `False`, you can use `1` and `0` respectively. This is very helpful if you are allergic to capital letters.
|
||||||
|
|
||||||
|
I don't do this in my function as I feel like `True` and `False` are easier to read from an outside persepctive, but for your own code, it
|
||||||
|
doesn't really matter as long as *you* understand it and it gets the job done.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Anyways, now it's time to go through the text line-by-line. To do this, we can use what is called a `for` loop.
|
||||||
|
|
||||||
|
```python
|
||||||
|
for line in lines:
|
||||||
|
```
|
||||||
|
|
||||||
|
There are two main types of "for" loops in programming. There's the one you mostly see in C, C++, and other languages like it with
|
||||||
|
`i=0; i>n; i++` or something like that, but that's not what we're doing here.
|
||||||
|
|
||||||
|
This version lets us loop through a list of values and do something with each value in the list. This is what is usually called an
|
||||||
|
"iterator-based" for loop and is also included in some languages with the keyword `foreach` instead of `for`.
|
||||||
|
|
||||||
|
So, `for` each `line` contained `in` our list of `lines`, we need to parse that line and plop the data within it into our output array.
|
||||||
|
|
||||||
|
How do we do that? *WE SPLIT IT AGAIN, HAHAHAHAHA*
|
||||||
|
|
||||||
|
But this time, we can't just use `.splitlines()` because the data within each line isn't separated by a line break. We know what each
|
||||||
|
value *is* separated by, though...
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Scroll back up to the raw dump as pasted from LoggerPro. Try highlighting the space inbetween each value. Notice how the highlight can't
|
||||||
|
end halfway inside the whitespace — it always highlights the full amount of space present.
|
||||||
|
|
||||||
|
The reason why is because each value is separated from the others by a "tab" character, i.e. the whitespace character that (should) come out
|
||||||
|
when you press the "tab" key on your keyboard. If they were separated by space characters (the whitespace character that comes out when you press
|
||||||
|
the spacebar), you would be able to select only *part* of the space inbetween values since it would usually take multiple space characters to
|
||||||
|
fill up the space taken up by a tab.
|
||||||
|
|
||||||
|
Tab characters can change in size so that it is easier to vertically orient text. Space characters are always the same size.
|
||||||
|
|
||||||
|
Here's a little example to show you the difference:
|
||||||
|
|
||||||
|
```
|
||||||
|
This sentence is broken up by a tab.
|
||||||
|
This sentence is broken up by spaces.
|
||||||
|
```
|
||||||
|
|
||||||
|
Try highlighting only *part* of the space that breaks up both sentences. In the first sentence, you *can't* because all of that space is being
|
||||||
|
taken up by a single tab character. In the second sentence. you *can* because that space is being filled with multiple space characters.
|
||||||
|
In this instance, it takes four space characters to fill the same space that a single tab character can.
|
||||||
|
|
||||||
|
What if we changed the actual text of both sentences?
|
||||||
|
|
||||||
|
```
|
||||||
|
This sentence isn't broken up by a tab.
|
||||||
|
This sentence isn't broken up by spaces... wait a second...
|
||||||
|
```
|
||||||
|
|
||||||
|
Look at that magic! In both of these scenarios, I converted `is` into `isn't` by adding the `n't` to the end of `is` in each sentence.
|
||||||
|
In the sentence with a tab, the tab *shrunk* automatically to keep the second half of the sentence in the same place.
|
||||||
|
Now, the tab character taking up the equivalent of a single space character.
|
||||||
|
|
||||||
|
On the other hand, when I did the same thing to the sentence with space characters, all of the spaces were moved to the right, misaligning the
|
||||||
|
second part of the sentence. There are still four spaces, taking up the same amount of space as it did the first time.
|
||||||
|
|
||||||
|
I know all of this sounds dumb (and it kinda is; it's so dumb that [there is an entire nerd war over it](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?utm_source=anzwix&id=d5cf50dafc9dd5faa1e61e7021e3496ddf7fd61e)),
|
||||||
|
but it is actually significant in this case because of the fact that there is only **one** (1) tab character inbetween each value. Feel
|
||||||
|
free to check for yourself again: every time you highlight between the data values, you only highlight the entire width of the space because
|
||||||
|
that space is only being taken up by *one* character.
|
||||||
|
|
||||||
|
What can we do with that infomation? Well, all we need to do to split each line into its own list of values is to do this:
|
||||||
|
|
||||||
|
```python
|
||||||
|
split: list[str] = line.split("\t")
|
||||||
|
```
|
||||||
|
|
||||||
|
This is just like `.splitlines()` from earlier, but instead of splitting the string using line returns, it splits the string by looking for
|
||||||
|
whatever character or other string we want. In our case, we want this function to look for tab characters, so we use the shorthand code
|
||||||
|
`\t` to tell the function that we want to look for tab characters.
|
||||||
|
|
||||||
|
When it does split up the string, all of the values within the table will
|
||||||
|
be perfectly contained without any whitespace around it. Why? The only whitespace that exists per line of data are the tab characters that go
|
||||||
|
between each value... and since there's only one of them per pair of values, we already got rid of it when we did the split!
|
||||||
|
|
||||||
|
Thus, we have ended up with *another* list of strings that we are *again* going to iterate through to do what we want. Yay.
|
||||||
|
|
||||||
|
## The Home Stretch
|
||||||
|
|
||||||
|
First, we need to check if we are on the first line of data. We can check this very easily:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if (first):
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a neat shorthand for `if (first == True)`. If our `first` boolean is true, then we will execute the code indented under this
|
||||||
|
`if` statement. What are we going to do? Another `for` loop!
|
||||||
|
|
||||||
|
```python
|
||||||
|
if (first):
|
||||||
|
for e in split:
|
||||||
|
out.append( [float(e)] )
|
||||||
|
first = False
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's break this down real quick:
|
||||||
|
|
||||||
|
- We want to iterate through our list of values. This time, I got lazy and named our iterator variable `e`, short for "entry".
|
||||||
|
- Our `out` table was previously defined as just an empty list (`[]`). I didn't want to define how *many* lists are inside it, just in case
|
||||||
|
our input had a different amount of columns than the three that we had in our example data. So, to start off inserting our data into our
|
||||||
|
`out` table, we need to first **create** new lists within the table. To do this, we can simply `.append()` a new list to the end of our
|
||||||
|
`out` table containing just one element...
|
||||||
|
- That element is `e`... in a function named `float()`. Wait, what are we doing here?
|
||||||
|
|
||||||
|
We could just put `e` inside this list and call it a day, but if we do that, we will be storing text inside our array, not a number.
|
||||||
|
Why? We started off by storing our
|
||||||
|
data as a big string, so even though we've gradually whittled down that big block of text into the individual values... it's still being stored
|
||||||
|
as text right now.
|
||||||
|
|
||||||
|
This may not seem like a big deal, but in my original version of the function, I ran into issues working with my data because certain library functions were expecting a list
|
||||||
|
of numbers when I was *actually* giving it a list of *strings*. **This is the one instance in which knowing what type a variable is or contains
|
||||||
|
is actually important in this function.**
|
||||||
|
|
||||||
|
So, to fix that, I can use a built-in cheat code: just wrap `e` in a function named after the type
|
||||||
|
I want to convert `e` into — in this case, a `float`. This function will parse the string `e`, see that the text inside the string is
|
||||||
|
indeed a floating point number, and give us that floating point number as an *actual* number of type `float`. That number is exactly what
|
||||||
|
we want: the values within `out` need to be actual numbers we can do actual math with, *not* the text representing those values within our
|
||||||
|
input string.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Anyways, to wrap up: after we have parsed our entire list of values, we want to make sure we know we are moving on to a new line that *isn't*
|
||||||
|
the first line. If we didn't, we would just add new one-member lists to the end of `out` over and over again!! So, at the end of the if
|
||||||
|
statement, we need to set our `first` boolean to `False`.
|
||||||
|
|
||||||
|
One very important thing to note here: our last line for this chunk of code is one indent **behind** the code we are executing in the `for`
|
||||||
|
loop. **Python is dumb and actually cares about indents.** If you use something like an `if`, `for`, or `def` statement, you *must* indent all of
|
||||||
|
the code that is meant to be executed under that statement. If you're not sure whether you need to indent, check to see if the line ends in a colon.
|
||||||
|
**If a line of code ends in a colon, the following code that you want to run under the conditions of that line likely needs to be indented.**
|
||||||
|
|
||||||
|
If that code *isn't* indented far enough, the Python interpreter won't recognize that the code
|
||||||
|
should be executed under that `if`, `for`, or `def` statement, so it won't run that code under those parameters.
|
||||||
|
|
||||||
|
So, wait... why are we intentionally *not* indenting that last line enough? Well, it's simple: that line of code **shouldn't** be executed
|
||||||
|
after every run of our `for` loop. If it *was* indented all the way, that line of code would run after *each value* is processed instead of
|
||||||
|
after we've processed all of the values. It wouldn't change anything if that happened in this specific instance, but since we only need to
|
||||||
|
update this boolean once,
|
||||||
|
it's probably a good idea to make *sure* it only updates once.
|
||||||
|
|
||||||
|
By moving this line of code back an indent, we are telling the interpreter that we only want the code to run under the conditions of our
|
||||||
|
`if` statement and our `def` statement, *not* under the conditions of our `for` statement.
|
||||||
|
|
||||||
|
This idea of code only
|
||||||
|
being ran under a particular set of parameters is called "scope", and it is a very important concept in programming. I suggest that you look
|
||||||
|
up more information about it and how it specifically applies to Python, as mishandling scope is a very easy way
|
||||||
|
for your code to completely fall apart if you do not watch out for it.
|
||||||
|
|
||||||
|
To the CS students in the audience, I know that this should be bare basic knowledge, but the fact that indents actually matter is a quirk of
|
||||||
|
Python that still trips me up sometimes and likely will trip you up as well.
|
||||||
|
Most other programming languages don't care: Python does. That distinction is important to keep in mind.
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
Now that we know what to do if `first` is true... What if it's `false`? Like, after we run that first bit of code that
|
||||||
|
sets `first` to `False` at the end... What do we do after that?
|
||||||
|
|
||||||
|
We can easily define what to do in that case by putting `else:` after our `if` statement's code:
|
||||||
|
|
||||||
|
```python
|
||||||
|
else:
|
||||||
|
```
|
||||||
|
|
||||||
|
The `else` statement only executes if the condition given to the previous `if` statement is *not* true. So, if our `first` boolean is found
|
||||||
|
to be true, the code directly under the `if` statement is ran. However, if our `first` boolean is found to be `false`, then we run the code
|
||||||
|
under `else`. You can think of it like "**If** this is true, do this... or **else**, do *this* instead."
|
||||||
|
|
||||||
|
So, what *else* are we cooking?
|
||||||
|
|
||||||
|
```python
|
||||||
|
else:
|
||||||
|
for i, e in enumerate(split):
|
||||||
|
out[i].append(float(e))
|
||||||
|
```
|
||||||
|
|
||||||
|
Hey, there's that `enumerate()` function I talked about earlier! If we give a `for` statement a list wrapped in this function, we will
|
||||||
|
get *two* variables to work with instead of one: our single value variable `e` and the "index" of that value in the list, which I have
|
||||||
|
shortened to `i`.
|
||||||
|
|
||||||
|
The "index" is the number given to our value's "place" within the order of the list. In Python and most other programming languages
|
||||||
|
(except for the bad ones /j), lists start at an index of `0`. This means that the first entry of a list will always be given the index `0`.
|
||||||
|
That may seem dumb at first, but it is actually really handy when you start working with the other type of `for` loops and other
|
||||||
|
programming tasks like it. I won't get into why here, but if you're curious, say it with me: *"Look it up!"* That's right.
|
||||||
|
|
||||||
|
So, let's say we wanted to get the first element of our list `split`. To do that, all we need to do is call it as `split[0]`. The number in
|
||||||
|
the square brackets tells Python to look through the list and grab the element at that index number. Since the number this time is `0`, we will
|
||||||
|
grab the element at index `0`... the first one!
|
||||||
|
|
||||||
|
The last line of this chunk of code utilizes our `i` variable to determine which of our new rows within `out` we should put this value
|
||||||
|
into. Remember that `out` is now a list *containing other lists*. In the case of Python, the outer list represents the **rows** of our table,
|
||||||
|
while the individual values within that row represent the **columns** of our table. So, if we wanted to get the second row of `out`,
|
||||||
|
all we need to do is call `out[2]`. That will give us a *list* of all of the values in row 2. If we wanted to grab just the third element of that
|
||||||
|
row, we can call `out[2][3]`. Since `out[2]` is itself a list, we can just slap another number in square brackets at the end of it to grab an
|
||||||
|
element within *that* list. I know. I know you really want to do it. I know you really want to say it. But I gave you that one chance. I am not
|
||||||
|
giving you another one. Weep.
|
||||||
|
|
||||||
|
We can use the fact that `out` is a list of lists to our advantage. Also remember that we want the *columns* of our old table to become
|
||||||
|
the *rows* of our new table. In our old table, each row represented a particular set of data, and each column represented a type of data.
|
||||||
|
We want things to be the opposite: the rows should contain individual *types* of data, and the columns should contain individual *samples* of data.
|
||||||
|
|
||||||
|
The easiest way to make this happen is to switch the indexes of our new table around. So, when we are iterating by *columns* through
|
||||||
|
our old table, the index of that *column* will become the index of the new *row* we want to insert our value into. That is why we are
|
||||||
|
using `enumerate()` here: As we go through each value, we are given the index of that value **within our old table's columns**. All we
|
||||||
|
need to do to translate our old table's columns into our new table's rows... is to just use that fancy shmancy index we got to specify
|
||||||
|
which row of `out` to insert our data into.
|
||||||
|
|
||||||
|
For example, if we are on the second value on this particular line of data, when we add it to `out`, we want to add it to the second row
|
||||||
|
of `out`. To do that, we can `.append()` our value to the end of the second list contained within `out` — in other words:
|
||||||
|
|
||||||
|
```python
|
||||||
|
out[1].append(value)
|
||||||
|
```
|
||||||
|
|
||||||
|
Rember that since list indexes start from `0`, to get the second row of `out`, we need to use index `1`. An easy way to keep track of what
|
||||||
|
index number to use is to just subtract the numbered version of your place by one. The `1st` row minus one gives you `0`. The `2nd` row minus one
|
||||||
|
gives you `1`, so that's the index number we need to use.
|
||||||
|
|
||||||
|
Of course, we still don't know how many columns of values we will get in our original data table, so instead of hard-coding each of those
|
||||||
|
index numbers, we will just use our index variable `i`, since we know `i` will represent the index of our data value within our current
|
||||||
|
`for` loop.
|
||||||
|
|
||||||
|
Oh, and again, we want the value we slot in to be an actual number, not text, so we're converting it into a `float` first with `float()`.
|
||||||
|
|
||||||
|
## We did it!
|
||||||
|
|
||||||
|
We are on. The last line. Of our function. We came a long way. But now it is time to finish this.
|
||||||
|
|
||||||
|
At the end of the function, with only one indent (since we're only under our function's `def` statement from the very top now), we
|
||||||
|
need to send out the two-dimensional array we made. To do that, we only need two words:
|
||||||
|
|
||||||
|
```python
|
||||||
|
return out
|
||||||
|
```
|
||||||
|
|
||||||
|
This will make our function "return" our variable `out` when it is ran without issues up to this point. If you were to just call this
|
||||||
|
function by itself, without anything else around it, you could get `out` printed to the standard output.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
You can also store the output into a variable:
|
||||||
|
|
||||||
|
```python
|
||||||
|
test_output = LoggerProToArray(test_dump)
|
||||||
|
```
|
||||||
|
|
||||||
|
Now the data within the `out` array we were working with inside our function is stored within our new variable, `test_output`. And we
|
||||||
|
didn't have to write *all of that code* again! Yay for reduced redundancy!!!
|
||||||
|
|
||||||
|
Hopefully this ramble helped you learn some stuff about coding in Python. I encourage you to try writing this code yourself, step by step,
|
||||||
|
using this article as a guide. Don't copy and paste each line from my code examples; write it by hand. It helps you absorb the information
|
||||||
|
better. I would cite a study here about it but it's midnight and I'm tired and I want to go to beb.
|
||||||
|
|
||||||
|
If you would rather just grab the entire function and use it in your own code...
|
||||||
|
|
||||||
|
# The thing you actually came here for
|
||||||
|
|
||||||
|
Ok fine, here's the code you want. All I ask is that if you use this anywhere, leave a link to either me or this particular webpage
|
||||||
|
in a comment above this. **If you are a student like me, I do not want you to get in trouble for using code that you did not write.**
|
||||||
|
|
||||||
|
```python filename="LoggerProToArray.ipynb"
|
||||||
|
def LoggerProToArray(text: str) -> list[list[float]]:
|
||||||
|
'''Converts a Logger Pro dump into a 2D array / table of values,
|
||||||
|
translated for use in graphing.
|
||||||
|
|
||||||
|
Written by kebokyo - https://eleboog.com/posts/2024-10-01-python-lab
|
||||||
|
|
||||||
|
Args
|
||||||
|
------
|
||||||
|
text : str
|
||||||
|
The Logger Pro dump imported as a multiline string.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
----------
|
||||||
|
list[list[float]]
|
||||||
|
A two-dimensional array that contains lists of floats.
|
||||||
|
|
||||||
|
'''
|
||||||
|
out: list[list[float]] = [] # The 2D array we are returning
|
||||||
|
|
||||||
|
lines: list[str] = text.splitlines() # Let's split our text into lines
|
||||||
|
first: bool = True # & keep track of whether we're on the first line or not
|
||||||
|
|
||||||
|
line: str
|
||||||
|
for line in lines: # Time to go through the text line by line!
|
||||||
|
split: list[str] = line.split("\t") # First, split each line by tab-delimitated values
|
||||||
|
|
||||||
|
if (first): # If we're on the first row...
|
||||||
|
for e in split: # For each value in this row,
|
||||||
|
out.append([float(e)]) # Make a new array for each *new* row
|
||||||
|
first = False # When we're done, make sure we know we're done w/ the first line
|
||||||
|
else:
|
||||||
|
for i, e in enumerate(split): # For all other rows...
|
||||||
|
out[i].append(float(e)) # Append the values to each new row
|
||||||
|
|
||||||
|
return out # Then just return the new array!
|
||||||
|
```
|
||||||
|
|
||||||
|
Oh, and in case you're curious, I found the footage for the silly green screen explosion [here](https://www.youtube.com/watch?v=2zZFmfcLyNA).
|
186
src/data/sharefeed.json
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
{
|
||||||
|
"sharefeed" : [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/dplanitzer/Serena",
|
||||||
|
"author": "Dietmar Planitzer",
|
||||||
|
"title": "Serena - An experimental operating system for 32bit Amiga computers.",
|
||||||
|
"date": "2024-08-14 13:00",
|
||||||
|
"note": "If I had an Amiga, I would be geeking out so much about this. You would not see me for days."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://chuck.is/news/",
|
||||||
|
"author": "Chuck Carrol",
|
||||||
|
"title": "The News is Information Junk Food",
|
||||||
|
"pubDate": "2022-09-16",
|
||||||
|
"date": "2024-08-09 16:00",
|
||||||
|
"note": "A discussion about the negative affects the 24 hour news cycle has on us. I want to dissect this further cause I have a feeling there's some nuggets of bad takes in here, but the overall takeway I agree with. If I had a nickel for every time my social media feed ended up getting overrun by political nonsense, I would have... uh... five nickels? What can you buy for a quarter nowadays? ...hmm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/vpand/icpp",
|
||||||
|
"author": "VPAND Team",
|
||||||
|
"title": "icpp - Running C++ in anywhere like a script.",
|
||||||
|
"date": "2024-08-09 15:00",
|
||||||
|
"note": "I just think it's funny okay"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://jon.bo/posts/digital-tools/",
|
||||||
|
"author": "Jonathan Borichevskiy",
|
||||||
|
"title": "Digital Tools I Wish Existed",
|
||||||
|
"pubDate": "2019-11-28",
|
||||||
|
"date": "2024-08-09 14:00",
|
||||||
|
"note": "Interesting exploration of digital information gathering, consumption, and archive tools that the author wish existed. I want to make a response to this one day with my own ideas and critiques of the particular features he wants (whether they make sense to me or not and if not what alternatives I think would be good instead)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stack-auth.com",
|
||||||
|
"author": "Stackframe Inc.",
|
||||||
|
"title": "Stack Auth - Open-source Clerk/Auth0 alternative",
|
||||||
|
"date": "2024-08-09 13:00",
|
||||||
|
"note": "A really promising open source alternative to WYSIWIG auth solutions like Clerk and Auth0. I would need to confirm its security and its flexibility, but the fact that they specifically say \"hey, you can totally just use your own frontend and use our SDK in the background\" makes me very excited. All of the user-facing elements can be styled my own way, while the internal admin elements can just be the defaults so I don't have to code it all. I might end up using this instead of Auth.js for phase 2 of my site if this lives up to my expectations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tonsky.me/blog/diagrams/",
|
||||||
|
"author": "Niki Tonsky",
|
||||||
|
"title": "Where Should Visual Programming Go?",
|
||||||
|
"pubDate": "2024-07-18",
|
||||||
|
"date": "2024-07-27 14:00",
|
||||||
|
"note": "A discussion about what incorporating diagrams and other visualizations into your codebase could and should look like. I think the idea of embedding diagrams into your comments is a very good idea (in fact, we should be using markdown in our docs already), but I'm not sure how useful creating parsable visual state machines and etc. would be. Fun fact: there seems to be a pseudo-collab-tool built into the site where you can see where other readers' cursors are. I kept trying to hold digital hands with the other cursor but it kept running away from me. Why are you so mean ;w;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://blog.singleton.io/posts/2022-10-17-otp-on-wrist/",
|
||||||
|
"author": "David Singleton",
|
||||||
|
"title": "TOTP tokens on my wrist with the smartest dumb watch.",
|
||||||
|
"pubDate": "2022-10-17",
|
||||||
|
"date": "2024-07-27 13:00",
|
||||||
|
"note": "A demonstration of the Sensor Watch, a replacement motherboard for the famous Casio F-91W digital watch that turns it into a programmable ARM Cortex M0+ powered smartwatch. It is absolutely wild, and I want one now even though the motherboard costs more than the watch itself. The idea of a wearable version of those Square Enix TOTP devices is so fascinating to me."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://semanticdiff.com/blog/language-aware-diff-how-far/",
|
||||||
|
"author": "Michael Müller",
|
||||||
|
"title": "How far should a programming language aware diff go?",
|
||||||
|
"pubDate": "2024-07-17",
|
||||||
|
"date": "2024-07-25",
|
||||||
|
"note": "A fun look on what differences in code could be considered \"irrelevant changes\" and the consequences of ignoring such changes. Note: This is a corporate blog post made for a VSCode and GitHub extension for \"language aware diff\" that includes premium software support."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://scrapscript.org",
|
||||||
|
"author": "Max Bernstein",
|
||||||
|
"title": "scrapscript - A functional, content-addressable programming language",
|
||||||
|
"date": "2024-07-24",
|
||||||
|
"note": "An interesting new scripting language that's basically Haskell but if it was even weirder. I want to make a blog post about this one day discussing how it compares to other programming languages and how it could be used."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://thetinypod.com",
|
||||||
|
"title": "tinypod - an iPod-style case for the Apple Watch",
|
||||||
|
"date": "2024-07-20",
|
||||||
|
"note": "A really cute concept for taking a computer on your wrist and adapting it into a digital detox device. The regular model is a little too expensive for my liking, but the \"lite\" model is cheap enough that I could justify trying it out with my own Apple Watch (once I find it again lol)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://simonwillison.net/2024/Jul/13/give-people-something-to-link-to/",
|
||||||
|
"author": "Simon Willson",
|
||||||
|
"title": "Give people something to link to so they can talk about your features and ideas",
|
||||||
|
"pubDate": "2024-07-13",
|
||||||
|
"date": "2024-07-14",
|
||||||
|
"note": "If you want people to be able to talk about an idea, concept, or feature that you develop, you should have a resource about it for people to easily reference. ChatGPT being used as an example is slightly cringe, but it works to show what not to do."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://openpipe.ai/blog/hn-ai-crypto",
|
||||||
|
"author": "Kyle Corbitt",
|
||||||
|
"title": "Is AI the Next Crypto? Insights from 2M HN comments",
|
||||||
|
"pubDate": "2023-11-08",
|
||||||
|
"date": "2023-11-08",
|
||||||
|
"note": "An analysis of Hacker News' sentiment towards AI compared to NFT's... using AI. Big note: this is a very cleverly disguised ad for OpenPipe, the service which is hosting this article, but I actually appreciated how it broke down the details of how the author computed his data. I wish there was a bit more analysis of the results, though."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://mariusbancila.ro/blog/2023/09/12/formatting-text-in-c-the-old-and-the-new-ways/",
|
||||||
|
"author": "Marius Brancila",
|
||||||
|
"title": "Formatting Text in C++: The Old and The New Ways",
|
||||||
|
"pubDate": "2023-09-12",
|
||||||
|
"date": "2023-11-07 14:00:00",
|
||||||
|
"note": "C++20 introduced some new std functions to format strings: std::format and std::format_to. In practice, they work much like a streamlined version of C's printf. However, their implementation (based on the library {fmt}) results in much higher efficiency! Apparently the guys at C++ really like it because the new std::print function in C++23 has the functionality of std::format built in. I'm curious to see how this will change C++ devs' workflows, including newbies coming to C++ from other languages like Java or Python."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/jesseduffield/horcrux",
|
||||||
|
"author": "Jesse Duffield",
|
||||||
|
"title": "horcrux - Split your file into encrypted fragments so that you don't need to remember a passcode",
|
||||||
|
"date": "2023-11-07 13:00:00",
|
||||||
|
"note": "A simple terminal program that allows you to split files into encrypted chunks that can later be re-combined into the original file. Think of it like a RAID setup but for individual files or archives, inspired by an everything-phobic fantasy media franchise. I actually find the concept really facinating: it could be a good way to split up large media files and share them over the internet. Even if one horcrux gets corrupted, the others can still make up the original file, and since it is encrypted, it's tougher to discern what the original file is without enough pieces to put it together."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://stytch.com/blog/open-sourcing-sqx-a-way-to-build-flexible-database-models-in-go/",
|
||||||
|
"author": "Logan Gore",
|
||||||
|
"title": "Open-sourcing SQX, a way to build flexible database models in Go",
|
||||||
|
"pubDate": "2023-08-31",
|
||||||
|
"date": "2023-09-09 14:00:00",
|
||||||
|
"note": "A really cool SQL interface for Go that both simplifies a lot of the boilerplate and allows for quickly building complex models through OOP. I don't have any experience in Go, but I'm honestly tempted to start learning Go just to try this library out and see if a port of it to other languages like C++ or Rust is possible."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://muldoon.cloud/2023/09/06/software-keeps-failing.html",
|
||||||
|
"author": "Mickey Muldoon",
|
||||||
|
"title": "Every Software Project is a Startup That Will Probably Fail",
|
||||||
|
"pubDate": "2023-09-06",
|
||||||
|
"date": "2023-09-09 13:00:00",
|
||||||
|
"note": "A short retrospective on the reality of software projects: the value you get out of most of them is the lessons you learn when they crumble. I feel like this lesson could be told in a way less intrinsic to our dumb economic system, but I don't have enough brain capacity to write that up right now."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://ploum.net/2023-07-06-stop-trying-to-make-social-networks-succeed.html",
|
||||||
|
"author": "Lionel Dricot",
|
||||||
|
"title": "Stop Trying to Make Social Networks Succeed",
|
||||||
|
"pubDate": "2023-07-06",
|
||||||
|
"date": "2023-07-06",
|
||||||
|
"note": "Social networks will never become universal or ubiquitous in the way companies and VC's want them to. Every social network will appeal to certain people more than others, so the best social network is the one that works best for you and your contacts. If one doesn't exist... make one! (or have someone else make it for you)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.reproof.app/blog/notes-apps-help-us-forget",
|
||||||
|
"author": "Matthew Guay",
|
||||||
|
"title": "Notes apps are where ideas go to die. And that’s good.",
|
||||||
|
"pubDate": "2022-02-15",
|
||||||
|
"date": "2023-06-03 14:00:00",
|
||||||
|
"note": "Notes apps always market themselves as tools to remember important things before they leave your mind. But maybe we should think of it the other way instead: notes afford us the ability to let thoughts leave our mind since we have already saved the information elsewhere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://kapeli.com/dash",
|
||||||
|
"title": "Dash for macOS - an API Documentation Browser and Code Snippet Manager",
|
||||||
|
"date": "2023-06-03 13:00:00",
|
||||||
|
"note": "Really cool offline documentation viewer for macOS... that's also $30 for some reason. DESPITE THERE BEING AN OFFICAL OPEN SOURCE PORT TO WINDOWS AND LINUX. I AM GOING TO COMBUST"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://blog.jim-nielsen.com/2023/deadlines-as-technology/",
|
||||||
|
"author": "Jim Nielsen",
|
||||||
|
"title": "Deadlines as Technology",
|
||||||
|
"pubDate": "2023-03-09",
|
||||||
|
"date": "2023-05-20",
|
||||||
|
"note": "My ADHD means I always procrastinate things until the last minute. Well, if there is no 'last minute' -- i.e. there is no deadline -- shit won't get done. If you try to pace yourself to hit certain self-set deadlines, you may find that work becomes easier."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tech.news.am/eng/news/510/worlds-first-portable-quantum-computers-on-sale-in-japan-prices-start-at-$8700.html",
|
||||||
|
"author": "News.am",
|
||||||
|
"title": "World's first portable quantum computers on sale in Japan: Prices start at $8,700",
|
||||||
|
"pubDate": "2022-12-21",
|
||||||
|
"date": "2023-05-20 15:00:00",
|
||||||
|
"note": "they put an android tablet on a fucking quantum computer, i'm shitting and cumming rn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://userinyerface.com/",
|
||||||
|
"author": "Verhaert",
|
||||||
|
"title": "User Inyerface",
|
||||||
|
"date": "2023-05-20 14:00:00",
|
||||||
|
"note": "Want to hate technology and how all of its innovations can be used for great evil? Try to work your way through this."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://walkingtheworld.substack.com/p/why-the-us-cant-have-nice-things",
|
||||||
|
"author": "Chris Arnade",
|
||||||
|
"title": "Why the US can't have nice things - A rant on bus stops",
|
||||||
|
"pubDate": "2023-05-20",
|
||||||
|
"date": "2023-05-20 13:00:00",
|
||||||
|
"note": "A blog post on why US public works suck so much: the government doesn't trust its citizens to utilize public works properly nor make their own organic solutions for public issues. Really good read!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://fortelabs.com/blog/the-secret-power-of-read-it-later-apps/",
|
||||||
|
"author": "Tiago Forte",
|
||||||
|
"title": "The Secret Power of 'Read-It-Later' Apps",
|
||||||
|
"pubDate": "2022-12-06",
|
||||||
|
"date": "2023-05-19",
|
||||||
|
"note": "An article on the benefits of using 'read-it-later' apps. There are so many things wrong with this article that I'm definitely going to make a blog post responding to it at some point. Despite that, it is part of the inspiration for this feed... so thanks, I guess?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
30
src/layouts/BaseLayout.astro
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import '@fontsource/nunito'
|
||||||
|
import '@fontsource/libre-baskerville'
|
||||||
|
import '../styles/globals.css'
|
||||||
|
|
||||||
|
import Header from '../components/Header.astro'
|
||||||
|
import Footer from '../components/Footer.astro'
|
||||||
|
|
||||||
|
const { title } = Astro.props
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link href="/moffsoft_light_64.png" rel="icon" type="image/png" media="(prefers-color-scheme: light)"/>
|
||||||
|
<link href="/moffsoft_dark_64.png" rel="icon" type="image/png" media="(prefers-color-scheme: dark)"/>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>{title ? title + ' - eleboog.com' : 'eleboog.com'}</title>
|
||||||
|
</head>
|
||||||
|
<body class="flex-col mx-auto min-h-screen max-w-screen-md p-8 py-4"">
|
||||||
|
<Header current={title}/>
|
||||||
|
<main>
|
||||||
|
<slot/>
|
||||||
|
</main>
|
||||||
|
<Footer/>
|
||||||
|
</body>
|
||||||
|
</html>
|
95
src/layouts/PostLayout.astro
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import '@fontsource/nunito'
|
||||||
|
import '@fontsource/libre-baskerville'
|
||||||
|
import '../styles/markdown.css'
|
||||||
|
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { toZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
|
import Header from '../components/Header.astro'
|
||||||
|
import Footer from '../components/Footer.astro'
|
||||||
|
import MDXImage from '../components/mdx/MDXImage.astro'
|
||||||
|
|
||||||
|
import HR from '../components/HR.astro'
|
||||||
|
|
||||||
|
import { mdxComponents } from '@l/mdx.tsx';
|
||||||
|
|
||||||
|
const fm = Astro.props.frontmatter
|
||||||
|
const headings = await Astro.props.headings
|
||||||
|
|
||||||
|
const title = fm.title ?? 'blog post'
|
||||||
|
|
||||||
|
const numberToWord = (num: number) => {
|
||||||
|
switch(num) {
|
||||||
|
case 1: return "one";
|
||||||
|
case 2: return "two";
|
||||||
|
case 3: return "three";
|
||||||
|
case 4: return "four";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link href="/moffsoft_light_64.png" rel="icon" type="image/png" media="(prefers-color-scheme: light)"/>
|
||||||
|
<link href="/moffsoft_dark_64.png" rel="icon" type="image/png" media="(prefers-color-scheme: dark)"/>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>{title ? title + ' - eleboog.com' : 'eleboog.com'}</title>
|
||||||
|
</head>
|
||||||
|
<body class="flex-col mx-auto min-h-screen max-w-screen-md p-8 py-4"">
|
||||||
|
<Header current='blog post'/>
|
||||||
|
<main>
|
||||||
|
{ ( fm.cover && fm.cover_alt ) ? (
|
||||||
|
<div class="mb-4">
|
||||||
|
<MDXImage src={fm.cover} alt={fm.cover_alt}/>
|
||||||
|
</div>
|
||||||
|
) : '' }
|
||||||
|
|
||||||
|
<h1 class="text-4xl font-serif font-bold text-title">{fm.title}</h1>
|
||||||
|
<h2 class="font-mono">— {format(toZonedTime(fm.date), 'LLLL d, yyyy')}
|
||||||
|
{(fm.updated) ? (
|
||||||
|
<span>, <span class="text-subtitle">updated {format(toZonedTime(fm.updated), 'LLLL d, yyyy')}</span></span>
|
||||||
|
) : ''}
|
||||||
|
</h2>
|
||||||
|
<h2 class="text-sm italic mb-4 mt-2 text-subtitle">{fm.summary}</h2>
|
||||||
|
|
||||||
|
<!-- shelving this feature for now
|
||||||
|
{(fm.narration) ? (<>
|
||||||
|
<h3 class="text-xl font-serif mb-2">listen to this article</h3>
|
||||||
|
<audio class="mb-2 w-full" controls src={fm.narration}/>
|
||||||
|
<p class="text-sm italic text-subtitle">
|
||||||
|
{(fm.narration_date) ? (`audio published ${format(parseISO(fm.narration_date), 'LLLL d, yyyy')}`) : ''}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm mb-4 ">alternatively, subscribe to the <a href="/feeds" class="text-subtitle hover:underline font-serif">podcast feed</a> on your podcast player of choice</p>
|
||||||
|
</>) :''}
|
||||||
|
-->
|
||||||
|
|
||||||
|
{(fm.toc) ? ( <>
|
||||||
|
<h3 class="font-mono text-xl text-title">table of contents</h3>
|
||||||
|
<div id="toc" class="mb-4 ml-2 pl-2 border-l border-neutral-400 ">
|
||||||
|
{headings.map((heading: {depth: number, slug: string, text: string}) => {
|
||||||
|
return (
|
||||||
|
<div key={`#${heading.slug}`}>
|
||||||
|
<a class="font-serif text-sm text-subtitle hover:underline data-[level=two]:pl-3 data-[level=three]:pl-6" data-level={numberToWord(heading.depth)} href={'#' + heading.slug}>
|
||||||
|
{(heading.depth > 1) ? '> ' : ''}
|
||||||
|
{heading.text}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>):''}
|
||||||
|
|
||||||
|
<HR/>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<slot/>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<Footer/>
|
||||||
|
</body>
|
||||||
|
</html>
|
41
src/layouts/StandaloneMDXLayout.astro
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import '@fontsource/nunito'
|
||||||
|
import '@fontsource/libre-baskerville'
|
||||||
|
import '../styles/markdown.css'
|
||||||
|
|
||||||
|
import Header from '../components/Header.astro'
|
||||||
|
import Footer from '../components/Footer.astro'
|
||||||
|
|
||||||
|
import HR from '../components/HR.astro'
|
||||||
|
|
||||||
|
import { mdxComponents } from '@l/mdx.tsx';
|
||||||
|
|
||||||
|
const { frontmatter } = Astro.props
|
||||||
|
|
||||||
|
const title = frontmatter.slug ?? 'me'
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link href="/moffsoft_light_64.png" rel="icon" type="image/png" media="(prefers-color-scheme: light)"/>
|
||||||
|
<link href="/moffsoft_dark_64.png" rel="icon" type="image/png" media="(prefers-color-scheme: dark)"/>
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
<title>{title ? title + ' - eleboog.com' : 'eleboog.com'}</title>
|
||||||
|
</head>
|
||||||
|
<body class="flex-col mx-auto min-h-screen max-w-screen-md p-8 py-4"">
|
||||||
|
<Header current={title}/>
|
||||||
|
<main>
|
||||||
|
<h1 class="text-3xl font-serif my-2">{frontmatter.title}</h1>
|
||||||
|
<h2 class="text-sm mb-4 text-subtitle">{frontmatter.summary}</h2>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<slot components={{hr: HR}}/>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
<Footer/>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
src/lib/blog.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export allTags = [
|
||||||
|
// type of content
|
||||||
|
"meta", "reply", "self-promo", "rant", "critique", "tutorial"
|
||||||
|
// mood
|
||||||
|
"silly", "angry", "curious", "happy", "sad", "introspective",
|
||||||
|
// topic
|
||||||
|
"astro", "nextjs", "mac", "macos", "linux", "windows", "pc", "mobile", "ios", "android", "linux-mobile", "csci", "university",
|
||||||
|
"python", "c", "cxx", "rust",
|
||||||
|
//
|
||||||
|
]
|
105
src/lib/feed.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { getCollection, getEntry } from "astro:content";
|
||||||
|
import { Feed } from "feed";
|
||||||
|
|
||||||
|
import { compareDesc } from "date-fns"
|
||||||
|
import { toZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
|
import sfjson from '/src/data/sharefeed.json'
|
||||||
|
import type { ShareFeedEntry } from "/src/lib/utils";
|
||||||
|
|
||||||
|
export const baseURL = "localhost:4321"//"https://eleboog.com"
|
||||||
|
export const author = {
|
||||||
|
name: "Kebo Kitanari",
|
||||||
|
email: "kebokyo@eleboog.com",
|
||||||
|
link: baseURL
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function computeFeed() {
|
||||||
|
const mainFeed = new Feed({
|
||||||
|
title: "eleboog.com feed",
|
||||||
|
description: "The feed for Kebo Kitanari's writing and other content. Contains blog posts, sharefeed entries, and more.",
|
||||||
|
id: baseURL,
|
||||||
|
link: baseURL,
|
||||||
|
language: "en",
|
||||||
|
copyright: "All Rights Reserved 2024 Kebo Kitanari",
|
||||||
|
feedLinks: {
|
||||||
|
atom: `${baseURL}/feeds/feed.xml`,
|
||||||
|
json: `${baseURL}/feeds/feed.json`
|
||||||
|
},
|
||||||
|
author
|
||||||
|
})
|
||||||
|
|
||||||
|
await addPostsToFeed(mainFeed, true);
|
||||||
|
addSharefeedEntriesToFeed(mainFeed, true);
|
||||||
|
|
||||||
|
return mainFeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addSharefeedEntriesToFeed(feed: Feed, main?: boolean) {
|
||||||
|
const entries = sfjson.sharefeed
|
||||||
|
.sort((a:ShareFeedEntry, b:ShareFeedEntry) => compareDesc(new Date(a.date), new Date(b.date)))
|
||||||
|
.filter((entry:ShareFeedEntry) => !(entry.draft))
|
||||||
|
.slice(0,15);
|
||||||
|
|
||||||
|
entries.forEach((entry: ShareFeedEntry) => {
|
||||||
|
let title: string = '';
|
||||||
|
if (main) {
|
||||||
|
/*if (compareDesc(new Date(toZonedTime(entry.date)), new Date("2024-07-13")) == 1) {
|
||||||
|
return;
|
||||||
|
}*/
|
||||||
|
title = title.concat('[sf] ');
|
||||||
|
}
|
||||||
|
if (entry.author) {
|
||||||
|
title = title.concat(`${entry.author}: `);
|
||||||
|
}
|
||||||
|
title = title.concat(entry.title);
|
||||||
|
|
||||||
|
feed.addItem({
|
||||||
|
title: title,
|
||||||
|
id: entry.url,
|
||||||
|
link: entry.url,
|
||||||
|
description: entry.note,
|
||||||
|
content: entry.note,
|
||||||
|
author: [{
|
||||||
|
name: entry.author
|
||||||
|
}],
|
||||||
|
date: toZonedTime(entry.date)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addPostsToFeed(feed: Feed, main?: boolean) {
|
||||||
|
const posts = await getCollection('posts')
|
||||||
|
|
||||||
|
posts.sort((a:Post,b:Post) => compareDesc(new Date(a.data.date), new Date(b.data.date)))
|
||||||
|
.filter((post:Post) => !post.data.draft)
|
||||||
|
.slice(0,20);
|
||||||
|
|
||||||
|
posts.forEach((post: Post) => {
|
||||||
|
const url: string = `${baseURL}/posts/${post.id}`
|
||||||
|
let title: string = '';
|
||||||
|
if (main) {
|
||||||
|
title = title.concat('[blog] ');
|
||||||
|
}
|
||||||
|
title = title.concat(post.data.title);
|
||||||
|
|
||||||
|
feed.addItem({
|
||||||
|
title: title,
|
||||||
|
id: url,
|
||||||
|
link: url,
|
||||||
|
description: post.data.summary ?? "Whoops, I didn't write a summary for this. Yell at me that I need to do so.",
|
||||||
|
content: post.data.summary ?? "Whoops, I didn't write a summary for this. Yell at me that I need to do so.",
|
||||||
|
author: [author],
|
||||||
|
date: toZonedTime(post.data.date),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// stolen from Jordan Webb - https://github.com/jordemort/jordemort.github.io/blob/main/src/utils/makeFeed.ts
|
||||||
|
export function injectXSL(xml: string, xsl: string) {
|
||||||
|
let i = xml.indexOf('?>');
|
||||||
|
let before = xml.slice(0, i + 2);
|
||||||
|
let after = xml.slice(i + 2);
|
||||||
|
|
||||||
|
return before + `<?xml-stylesheet href="${xsl}" type="text/xsl"?>` + after;
|
||||||
|
}
|
11
src/lib/utils.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
export type ShareFeedEntry = {
|
||||||
|
url: string,
|
||||||
|
author?: string,
|
||||||
|
email?: string,
|
||||||
|
baseUrl?: string,
|
||||||
|
date: string,
|
||||||
|
pubDate?: string,
|
||||||
|
title: string,
|
||||||
|
note: string,
|
||||||
|
draft?: boolean
|
||||||
|
}
|
57
src/pages/blog.astro
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import { compareDesc, format, parseISO } from 'date-fns'
|
||||||
|
import { fromZonedTime, toZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import cri from "./chills_going_waaaaah.png"
|
||||||
|
import { Picture } from 'astro:assets';
|
||||||
|
|
||||||
|
import HR from "../components/HR.astro"
|
||||||
|
|
||||||
|
import { getCollection, getEntry } from 'astro:content';
|
||||||
|
|
||||||
|
const posts = await getCollection('posts');
|
||||||
|
const archives = await getCollection('archives');
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="archives">
|
||||||
|
<h1 class="font-serif text-3xl my-2">Blog Archive</h2>
|
||||||
|
<p>This is a list of all blog posts that have ever been posted on this site.</p>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-serif text-crusta-600 dark:text-night-200 my-2">current content</h2>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
{posts.sort((a, b) => compareDesc(new Date(a.data.date), new Date(b.data.date))).map((post, idx, array) => {
|
||||||
|
if (post.data.archive) return;
|
||||||
|
console.log(toZonedTime(post.data.date));
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
<a href={'/posts/' + post.slug} class="font-serif text-lg text-crusta-400 dark:text-night-300 hover:underline">{post.data.title}</a>
|
||||||
|
 <span class="font-mono text-md">{format(toZonedTime(post.data.date), 'LLL d, yyyy')}</span>
|
||||||
|
<p>{post.data.summary}</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<HR/>
|
||||||
|
|
||||||
|
<h2 class="text-2xl font-serif text-crusta-600 dark:text-night-200 my-2">archived content</h2>
|
||||||
|
<p class="mb-2">I have a lot of old posts from multiple different iterations of my blog. If I come across one I think is still neat, I may put it here.<br/>
|
||||||
|
Please check the date of publication before making any comments.</p>
|
||||||
|
|
||||||
|
<ul class="space-y-2">
|
||||||
|
{archives.sort((a, b) => compareDesc(new Date(a.data.date), new Date(b.data.date))).map((post, idx, array) => {
|
||||||
|
if (post.data.archive) return;
|
||||||
|
console.log(toZonedTime(post.data.date));
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
<a href={'/posts/' + post.slug} class="font-serif text-lg text-crusta-400 dark:text-night-300 hover:underline">{post.data.title}</a>
|
||||||
|
 <span class="font-mono text-md">{format(toZonedTime(post.data.date), 'LLL d, yyyy')}</span>
|
||||||
|
<p>{post.data.summary}</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</BaseLayout>
|
BIN
src/pages/chills_going_waaaaah.png
Normal file
After Width: | Height: | Size: 81 KiB |
8
src/pages/feeds/feed.json.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { computeFeed } from "/src/lib/feed.ts";
|
||||||
|
import type { APIContext } from 'astro';
|
||||||
|
|
||||||
|
export async function GET({request, url, cookies}: APIContext): Promise<Response> {
|
||||||
|
const feed = await computeFeed();
|
||||||
|
|
||||||
|
return new Response(feed.json1())
|
||||||
|
}
|
8
src/pages/feeds/feed.xml.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { computeFeed, injectXSL } from "/src/lib/feed.ts";
|
||||||
|
import type { APIContext } from 'astro';
|
||||||
|
|
||||||
|
export async function GET({request, url, cookies}: APIContext): Promise<Response> {
|
||||||
|
const feed = await computeFeed();
|
||||||
|
|
||||||
|
return new Response(injectXSL(feed.atom1(), "/feed.xsl"))
|
||||||
|
}
|
|
@ -1,16 +1,69 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
|
import { compareDesc, format, parseISO } from 'date-fns'
|
||||||
|
import { fromZonedTime, toZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import cri from "./chills_going_waaaaah.png"
|
||||||
|
import { Picture } from 'astro:assets';
|
||||||
|
|
||||||
|
import MDXCallout from "../components/mdx/MDXCallout.astro"
|
||||||
|
|
||||||
|
import { getCollection, getEntry } from 'astro:content';
|
||||||
|
|
||||||
|
const posts = await getCollection('posts')
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<BaseLayout title="home">
|
||||||
<head>
|
<h2 class="font-serif text-3xl my-2">Hey. Welcome to City 23.</h2>
|
||||||
<meta charset="utf-8" />
|
<Picture
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
src={cri}
|
||||||
<meta name="viewport" content="width=device-width" />
|
alt="A scrunkly picture of my sona crying"
|
||||||
<meta name="generator" content={Astro.generator} />
|
class="w-20 mr-4 inline-flex float-left"
|
||||||
<title>Astro</title>
|
/>
|
||||||
</head>
|
<p class="mb-2">
|
||||||
<body>
|
This is my personal website, containing blog posts, a mini journal, and a whole bunch of other nicknacks and projects that I'm working on.
|
||||||
<h1>Astro</h1>
|
Essentially, this is my fullstack webdev playground where everything is possible and nothing makes sense... the dark side of the sauce.
|
||||||
</body>
|
</p>
|
||||||
</html>
|
<p class="mb-2">Expect posts once or twice a week, I guess.</p>
|
||||||
|
|
||||||
|
<MDXCallout preset="new">
|
||||||
|
This site is now using the <a href="https://astro.build">Astro web framework</a>!
|
||||||
|
Previously, this site was using <a href="https://nextjs.org">Next.js</a>, a fullstack framework built for big whig companies
|
||||||
|
with big whig webapps... and it didn't really fit this site. Hopefully, the move to Astro will mean a more responsive and performant
|
||||||
|
blog that still has room to expand in the future. To learn more about this, check out my <a href="/journal#2024-11-12"
|
||||||
|
class="text-subtitle hover:underline">latest journal entry</a>.
|
||||||
|
<br/><br/>
|
||||||
|
I also want to note that there are now only <b>two</b> content feeds, Atom and JSON,
|
||||||
|
accessed via <a href="/feeds/feed.xml" class="text-subtitle hover:underline"><code>/feeds/feed.xml</code></a> and <a href="/feeds/feed.json" class="text-subtitle hover:underline"><code>/feeds/feed.json</code></a> respectively. This was done to simplify my RSS feeds and give myself
|
||||||
|
less work lmao.
|
||||||
|
<br/><br/>
|
||||||
|
If you have any questions (or bug reports) regarding the transition to Astro, please reach out via email. Hope you enjoy the new site!
|
||||||
|
</MDXCallout>
|
||||||
|
|
||||||
|
<h2 class="font-serif text-2xl mb-2">recent posts</h2>
|
||||||
|
<ul class="space-y-2">
|
||||||
|
{posts.sort((a, b) => compareDesc(new Date(a.data.date), new Date(b.data.date))).map((post, idx, array) => {
|
||||||
|
if (idx >= 3 || post.data.draft) return;
|
||||||
|
console.log(toZonedTime(post.data.date));
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
<a href={'/posts/' + post.slug} class="font-serif text-lg text-crusta-400 dark:text-night-300 hover:underline">{post.data.title}</a>
|
||||||
|
 <span class="font-mono text-md">{format(toZonedTime(post.data.date), 'LLL d, yyyy')}</span>
|
||||||
|
<p>{post.data.summary}</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
<h2 class="font-serif text-2xl mb-2">webrings</h2>
|
||||||
|
<div class="flex items-baseline">
|
||||||
|
<a class="text-subtitle hover:text-current" href="https://fediring.net/previous?host=eleboog.com">← </a>
|
||||||
|
<a class="text-subtitle hover:text-current" href="https://fediring.net/">Fediring</a>
|
||||||
|
<a class="text-subtitle hover:text-current" href="https://fediring.net/next?host=eleboog.com"> →</a>
|
||||||
|
<div class="flex w-4 self-baseline h-1 border-t border-crusta-200 dark:border-night-800 mx-2"></div>
|
||||||
|
<a class="text-subtitle hover:text-current" href="https://webring.bucketfish.me/redirect.html?to=prev&name=kebokyo">← </a>
|
||||||
|
<a class="text-subtitle hover:text-current" href="https://webring.bucketfish.me">bucket webring</a>
|
||||||
|
<a class="text-subtitle hover:text-current" href="https://webring.bucketfish.me/redirect.html?to=next&name=kebokyo"> →</a>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
|
|
446
src/pages/journal.mdx
Normal file
|
@ -0,0 +1,446 @@
|
||||||
|
---
|
||||||
|
layout: '../layouts/StandaloneMDXLayout.astro'
|
||||||
|
title: Journal
|
||||||
|
slug: journal
|
||||||
|
date: 1970-01-01
|
||||||
|
summary: "My journal for smaller posts & updates."
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
import MDXCallout from '../components/mdx/MDXCallout.astro';
|
||||||
|
import { MdOpenInNew } from "react-icons/md"
|
||||||
|
|
||||||
|
## now
|
||||||
|
|
||||||
|
Cramming an entire overhaul of my site before class like a boss
|
||||||
|
|
||||||
|
( below is an archive of a "now" post from i don't freaking know, i just haven't updated my site in a while so i wanted to make sure this one could actually be seen for a bit lol )
|
||||||
|
|
||||||
|
I am currently sitting at my local library. I forgor to bring my library card, so I just browsed through a couple of books and took
|
||||||
|
pictures of them so I can find them through *completely above-water legit means* in the near future.
|
||||||
|
|
||||||
|
I want to get better at drawing, but it's very hard to start. Hopefully the books that I checked out will help a little bit with
|
||||||
|
getting the ball rolling.
|
||||||
|
|
||||||
|
{/*
|
||||||
|
---
|
||||||
|
|
||||||
|
## todo
|
||||||
|
|
||||||
|
- [ ] Make external links open in a new tab. This should be pretty easy to implement.
|
||||||
|
- [ ] Potentially add an extra "new-tab" flag to MDX links (a space & "+" after the link in the parentheses? that might be too complicated
|
||||||
|
to implement nicely tho)
|
||||||
|
- [ ] Automatically insert an "external link" icon (<MdOpenInNew size='16' className="inline-flex"/>) on links that open in a new tab.
|
||||||
|
- [ ] Set up a dev version of this site & start implementing the account system!
|
||||||
|
*/}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2024-11-12
|
||||||
|
|
||||||
|
I did it. This entire site is now running on the [Astro web framework](https://astro.build) instead of Next.js.
|
||||||
|
|
||||||
|
There were two main reasons why I chose Next.js for this site originally:
|
||||||
|
1. I already had *some* experience with it in an older iteration of my website refresh (which you can see in [this blog post](https://eleboog.com/posts/2024-06-16-hello-again)), and
|
||||||
|
2. It is by far [the most popular metaframework on the web](https://share.stateofjs.com/share/prerendered?localeId=en-US&surveyId=state_of_js&editionId=js2023&blockId=meta_frameworks_ratios¶ms=§ionId=libraries&subSectionId=meta_frameworks) right now.
|
||||||
|
|
||||||
|
Both of these made it easier to get started with Next.js, and #2 made it more appealing as a learning / portfolio-enrichment experience.
|
||||||
|
|
||||||
|
However, along with these two upsides, two big problems conversely arose with Next.js:
|
||||||
|
1. Building a Next.js website (which I didn't think I had to do when I chose Next.js!!!) takes a lot more resources than I thought it would. In order to rebuild my site and make my changes live, I *need* to take the website down first on my basic bitch $7 VPS from Hetzner (put a pin on this, I'm going to talk about it later).
|
||||||
|
2. Because Next.js is designed primarly for single-page applications (SPA's), when you click on a link to another page on the site... **nothing happens**. No loading bar shows up in the browser. The site itself doesn't even show its spinner for a bit. This garbage time can be a fraction of a second or *multiple seconds* depending on how shitty your internet is. And I hate it with a passion. [**And there's no way to fix it without hacky workarounds.**](https://github.com/vercel/next.js/issues/43548) Pain.
|
||||||
|
|
||||||
|
The second problem can absolutely be fixed with Astro since it primarily relies on either static page generation (SSG) or server-side rendering (SSR), both of which make the browser show a loading bar immediately upon requesting a new page *like it should*. The first problem is still TBD because I haven't pushed this site to prod yet, but I have a feeling since Astro's whole entire shtick is SSG, it should have better optimization on that front.
|
||||||
|
|
||||||
|
Since I deliberately made this site text-focused and light on visual complexity, making everything work on Astro was surprisingly easy. There was of course a learning curve with all the ways Astro does things differently, but most problems I ran into were able to be resolved in the same sitting I found them in. I was even able to reuse some of the libraries I was already using to make the porting process easier. All in all, this site should work pretty much the same as it did under Next.js... [but better](https://youtu.be/aaZHCB5d6JI?si=1THiwFWcDyyBHZ7R&t=3). <span class="text-sm italic">(I know, fuck this character and fuck this show but i think this meme fits here ironically lol)</span>
|
||||||
|
|
||||||
|
The true test for Astro will come when I deploy it, though. I have never actually deployed Astro before, but since it uses (p)npm for running deployment commands, it should be easy to automate with [PM2](https://pm2.keymetrics.io) like I already do with Next.js. I *hope* it uses less resources than Next.js so I can actually leave my site running while it's updating, but time will tell.
|
||||||
|
|
||||||
|
The simplest way to fix that problem, of course, is to throw more money at the problem by upgrading to a better VPS. However, due to [issues with censorship of "pornographic content"](https://tenforward.blog/hetzner-considered-hostile-a-psa/) (parts of this article are a little sus but the big idea of "if you host a fediverse instance that doesn't completely ban nsfw content, you could get rocked by Hetzner" is scary to me), I don't really feel comfortable staying with Hetzner. I've used Vultr before but they're pretty expensive, so I might just say fuck it and move back to Linode lmao.
|
||||||
|
|
||||||
|
Hopefully, all of this change will be for the better and not for the worse. I'm rapidly running out of energy and I'm suddenly feeling very weird for no reason so I think I need to stop writing lol. also SOMEONE'S USING BONE CONDUCTION HEADPHONES RIGHT NEXT OT ME AND THEY'RE ORANGE AND THEY'RE USING EAR PLUGS WHAT IS THIS ABSOLUTELY INSANE STRAT I NEED TO INVESTIGATE THIS (one day)
|
||||||
|
|
||||||
|
right music link uuhhhhh [here's something that was in my watch later feed for a billion years](https://www.youtube.com/watch?v=EEHZqNkfcoc) ktnxbye
|
||||||
|
|
||||||
|
# 2024-10-02
|
||||||
|
|
||||||
|
[ this is not finished. it will never be finished. fuck you ]
|
||||||
|
|
||||||
|
(note to self: if this becomes 100+ lines, just make it a fucking blog post jesus christ)
|
||||||
|
|
||||||
|
Yeah, it's been a while since I made an update to this site. But hey! I got an actual post this time!!!!
|
||||||
|
|
||||||
|
Since I'm writing this while I am still working on editing the post, I'm going to leave a checklist here of things I want
|
||||||
|
to work on surrounding the site before I publish a new article.
|
||||||
|
|
||||||
|
## Syntax Highlighting
|
||||||
|
|
||||||
|
This post uses a *lot* of code blocks and `code strips` (what I'm calling "monospace" formatting). I'm really proud of where my
|
||||||
|
syntax highlighting and code block functionality is right now. However, I want to make a couple of tweaks and updates to make
|
||||||
|
my implementation first-class.
|
||||||
|
|
||||||
|
First, I need to do a quick check on whether syntax highlighting in code blocks works without JavaScript. In my eyes, it *should*, since
|
||||||
|
all of the processing happens on the server-side and all the client gets is a bunch of `<span>`'s with different colors attached to them.
|
||||||
|
If it doesn't, I need to figure out why and fix that before publishing.
|
||||||
|
|
||||||
|
Ideally, my site should require as little client-side scripting as
|
||||||
|
possible, if any. The main reason I moved away from Zonelets (a popular blog template for Neocities) is because of how it required client-side
|
||||||
|
scripting to even function properly and let you see what posts are on the site. Especially after I moved away from Neocities, a platform
|
||||||
|
that is built around banning server-side scripting, that reliance on client-side scripting to make up for that slack seemed kinda silly
|
||||||
|
now that I *can* use server-side scripting.
|
||||||
|
|
||||||
|
I know it's kinda silly to want as little JS as possible on a site powered *by* JS/TS in the backend and on a platform designed around
|
||||||
|
single page applications & routing... but hey, a girl can have her cake and eat it too sometimes.
|
||||||
|
|
||||||
|
The other main thing regarding syntax highlighting I wanted to check is how to get it to work with Tailwind's automatic theming.
|
||||||
|
Currently, the site switches from light to dark mode with the browser / OS — there is no way for the user to set it differently.
|
||||||
|
That's something I want to change in the future, but for now it makes things Just Work™ a little better because I can use Tailwind's
|
||||||
|
automatic light/dark theming system to change colors based on theme.
|
||||||
|
|
||||||
|
Before, I just had the `MDXCodeBlock` component change its entire theme depending on whether the browser is in light or dark mode.
|
||||||
|
This had two problems: the theme only updated once, and it updated slightly *after* the rest of the site is themed properly. The theme
|
||||||
|
is set to light mode by default, so in dark mode, the theme would be too dark for half a second before the theme check actually goes through
|
||||||
|
and the code block theming fixes itself. That is very bad.
|
||||||
|
|
||||||
|
My thinking is that since what gets sent to the browser is a bunch of `<span>`'s with colors slapped on them anyway... what if I make
|
||||||
|
those individual *colors* themed instead of changing what colors to slap on depending on the theme? The easiest way to do this would be
|
||||||
|
to make a new theme where instead of defining the color by a `color:` attribute in `style`, the color is defined by classes. That way,
|
||||||
|
not only can I build a theme with my existing Tailwind colors and themed around my site, but I can also give all tokens *two* colors
|
||||||
|
— one for light, and one for dark — using the `dark:` prefix I already use in the rest of my site.
|
||||||
|
|
||||||
|
|
||||||
|
# 2024-08-30
|
||||||
|
|
||||||
|
I am slowly realizing that I will almost forever be struck with the YGS curse until I stop having things to tweak on my blog.
|
||||||
|
|
||||||
|
I'm not sure how I feel about that. On one hand, I want to make this site into another platform of sorts for smallweb tech people.
|
||||||
|
On the other hand, I want to actually write blog posts once in a while. So... I think I may be putting that former aspect on hold until I get
|
||||||
|
into a good rhythm with my... ugh... "content". I hate that word so much.
|
||||||
|
|
||||||
|
Also I just now realized you can totally get away with just using HTML's bult-in `<audio controls>` player for embedding audio into your site.
|
||||||
|
So, I'm probably just going to do that for my narrated articles until I get enough complaints about it not having things like speed controls or
|
||||||
|
etc. And there will always be the podcast feed for people who want more controls.
|
||||||
|
|
||||||
|
I just pushed a whole bunch of updates to the blog so I think my next blog post will be about all of that and how I feel about having had this site up
|
||||||
|
for just about two months (I think). I need to write. Aaaaaaa.
|
||||||
|
|
||||||
|
[I heard this in a Starbucks and I had a hell of a time looking it up so here it is for you to enjoy]. Thanks!
|
||||||
|
|
||||||
|
# 2024-08-14
|
||||||
|
|
||||||
|
I survived my summer class. I think. I haven't checked my grades yet but last I saw it I got a C. C's get degrees.
|
||||||
|
Especially when you do half of the classwork within three days holy mother of god how did i live
|
||||||
|
|
||||||
|
Right now, I'm in Kentucky visiting some very special people. I might make a blog post about the journey, but I'm not sure how much detail I want
|
||||||
|
to give in a fully public space like this site. I'm also not sure how much detail I can really give in general. It's pretty much just "I got an emergency
|
||||||
|
lift to the bus stop, rode Greyhound for eight hours, I shat in a McDonalds, and that's the story." Actually now that I list all of that out I
|
||||||
|
probably could make a decent sized post about all of that, hmm. Maybe I'll do that. But now I've got so many ideas for blog posts but never really
|
||||||
|
committing to any of them that I feel halfway to Malamaroo again. I need to square my sights on *one* idea and stick with it.
|
||||||
|
|
||||||
|
Now that I have a bunch of free time finally, I'm going to try working on the narrated article system for real this time.
|
||||||
|
I want to make it work with *and* without Javascript: my site is currently fully functional without Javascript and I want to keep it that way for
|
||||||
|
the two people who like that. I also want the UX to blend in nicely with the text-first minimal aesthetic I've got going on.
|
||||||
|
|
||||||
|
I also probably need to do some updates to the header, especially on mobile, as well as add some enhancements to the site for mobile in general.
|
||||||
|
The header is slightly jank on my iPhone 12 mini and I think the best solution is to make a version of the header that feels "designed" for mobile
|
||||||
|
like it does for desktop. I also have noticed that blog post images are a little wonky on mobile especially if I've resized them (what works on desktop
|
||||||
|
does **not** work on mobile lmao), so I think what I'll do is just disable that sizing if you're on mobile... I need to figure out how to do that
|
||||||
|
through JSX though.
|
||||||
|
|
||||||
|
So yeah, this next week or so is looking really bright both for my personal life and my projects. Hopefully you actually get to see some stuff from me
|
||||||
|
other than journal entries within a few days.
|
||||||
|
|
||||||
|
and in case you didn't catch my last "now" entry [LISTEN TO THIS RIGHT FUCKING NOW](https://www.youtube.com/watch?v=yhtcG79kPNw)
|
||||||
|
|
||||||
|
# 2024-08-09
|
||||||
|
|
||||||
|
I think I finally fixed my sleep schedule. For now. I'm actually really proud of myself for finally getting into a morning groove.
|
||||||
|
I feel way better when I'm actually up in the mornings: waking up in the afternoon seems to lay some weird sort of lethargy on my day and it's hard
|
||||||
|
to motivate myself to get anything productive done.
|
||||||
|
|
||||||
|
I'm also about to finish my summer class catch up apocalypse. Got to read a bunch of cool stories written by fellow classmates, and imo
|
||||||
|
no matter what grade I get, that alone makes this class worth it for me.
|
||||||
|
|
||||||
|
Currently sitting in my local Starbucks getting comfortable before starting back up on my work. I have some plans with an IRL friend later today,
|
||||||
|
so hopefully that will be a good self-reward for making it through my summer class. I've been *reeeeaaaally* stir-crazy this summer and I can't wait
|
||||||
|
for it to end lmao. Once the fall semester starts up, there will be a lot more excuses to be out and about and I'll feel like myself again.
|
||||||
|
|
||||||
|
I think during the free couple of weeks after this, I'll start working on a couple of things that are holding me back from pursuing the next
|
||||||
|
phase of my website:
|
||||||
|
|
||||||
|
- The narrated article implementation is long overdue. I'll probably use it to double as my next blog post (I'll talk about all the updates I've
|
||||||
|
done to this website and go into detail on how I implemented the narrated articles & the podcast Feed... with the article itself coming out
|
||||||
|
with narration on day one :3)
|
||||||
|
- Still, I want to get one more blog post under my belt on top of that. I think the idea I had for a blog post on Geminispace is the best lead
|
||||||
|
to follow. I really really want to make an article exploring Geminispace, and following up on this hyperfixation might be my best chance to actually
|
||||||
|
buck YGS Every Friday Syndrome, at least for a bit.
|
||||||
|
- This will also be a good opportunity to implement a couple of miscelaneous pages under an "extras" hub on the navbar (which, yes, is getting
|
||||||
|
progressively longer and longer to the point where I might need to rework the header layout for mobile finally lol). I want to bring back the
|
||||||
|
Battlefield 4 Chat Simulator (cause it would be funny), but I also want to add a page for cool Geminispace resources, a "starwall" where I shout out
|
||||||
|
cool indie web sites that I find on my travels, and a "/slash" page where I compile multiple ["slash pages"](https://slashpages.net) into one big
|
||||||
|
page (so I don't have 50 billion little tiny pages that serve only one very niche purpose lmao). Next.js's routing & middleware features, gob dless.
|
||||||
|
|
||||||
|
Finally, whoops, I ever even published my last journal & sharefeed update. To make up for that, I'm gonna add a ton of sharefeed posts this time around.
|
||||||
|
One day, I should make a system that lets me set sharefeed posts in the future, and if I do so, they only show up on the feed once that date has arrived
|
||||||
|
(basically a barebones scheduling system for sharefeed entries). That will make it a lot easier to deliver... "cOnTEnT" (ugh hate that word)
|
||||||
|
on a consistent basis without having to have a superhuman noggin.
|
||||||
|
|
||||||
|
[Here's an album I've been listening too for a hot minute.](https://www.youtube.com/watch?v=iavAnEVGIaQ&list=PLQvL6GLJiX91I77SnUTD69KgKpIB5AAUR).
|
||||||
|
Funny story about this one: I first discovered this band from a CD left in the CD player of my current car when I first got it. I'm glad I did lmao
|
||||||
|
|
||||||
|
# 2024-08-03
|
||||||
|
|
||||||
|
I finally got the opportunity to have breakfast. And of course, I decided to make it Taco Bell breakfast because I'm a gremlin like that.
|
||||||
|
|
||||||
|
This morning feels way better than most of the mornings that I usually have... probably because I actually made it to the morning.
|
||||||
|
I absolutely crashed yesterday, sleeping over 14 hours and waking up at midnight.
|
||||||
|
So even though I have currenlty been awake for eight hours, I still get to exist in the morning hours that I would usually sleep though because
|
||||||
|
of my body's lethargy and desire to sleep in.
|
||||||
|
|
||||||
|
So now I get to sit in a Taco Bell and watch videos about how [Modern Warfare 2019 somehow looks better than a game that came out in 2023](https://www.youtube.com/watch?v=-gk4ojGwruc).
|
||||||
|
It's funny how multiple animations in MWIII were clearly re-used from MW19... it's almost like Sledgehammer were on a massive crunch or something.
|
||||||
|
|
||||||
|
(08/09: I never finished this entry, so [here's a jungle mix as an apology lol](https://www.youtube.com/watch?v=yvsRrpIPCoE))
|
||||||
|
|
||||||
|
# 2024-07-27
|
||||||
|
|
||||||
|
I'm currently editing this MDX document on a 7th generation iPad. I'm serious.
|
||||||
|
|
||||||
|
In an attempt to reduce distractions, I finally charged up my iPad and brought it and only it
|
||||||
|
(…okay, *and* a notebook and pencils but ***that's it***) to Starbucks. I wish I had decent headphones with me to listen to music on but
|
||||||
|
for some reason all of my ~~food~~ headphones keep ~~blowing up~~ breaking down way quicker than they have any right to. Pain.
|
||||||
|
|
||||||
|
I forgot how surprisingly usable this Logitech Combo Touch keyboard & trackpad are.
|
||||||
|
The trackpad is definitely not the best (hell, it still has an actual hinge that is really awkward to press down on),
|
||||||
|
and the keyboard is slightly cramped, but all absolutely works. In fact, in terms of the key travel and feel in general…
|
||||||
|
it's honestly comparable in quality to my M1 MacBook Air. Wild.
|
||||||
|
|
||||||
|
For some additional context, I first bought this iPad in Christmas of 2019.
|
||||||
|
That means that this iPad is 4 years old getting *very fucking close* to 5. And it's still kickin'.
|
||||||
|
My older Intel MacBook Air is either from 2017 or 2019 (can't remember which right now but leaning 2017) and that felt absolutely ancient.
|
||||||
|
This iPad? It's fine. It's definitely long in the tooth — but considering the closest upgrade with ideal accessories
|
||||||
|
would make me pay just as much as I did for my M1 Air… I absolutely don't feel like I *need* to upgrade any time soon.
|
||||||
|
And if I do? I might just go for the mini… when it gets the M-series update (pretty please Timmothy Microwave??? 🥺👉👈).
|
||||||
|
|
||||||
|
I'm gonna try to push a sharefeed update too, but I'm not sure how that is going to go.
|
||||||
|
I'm currently using a text editor built into the git client Working Copy.
|
||||||
|
It works… but it definitely is not feature rich nor very language sensitive.
|
||||||
|
Hopefully it will do just as fine editing JSON as it is editing markdown though.
|
||||||
|
|
||||||
|
By the way, if you're reading this, please check out my RSS feeds and shoot me an email about what you think of them.
|
||||||
|
I haven't had anyone comment to me about them yet, and I wanna make sure they're working good for people other than me.
|
||||||
|
Also, I want to start working on making journal updates crosspost on both my RSS feed *and* my social medias (cohost, fedi, & bsky).
|
||||||
|
Just a little bit of that Al Gore Rhythm goodness never hurt anyone! 0:3
|
||||||
|
|
||||||
|
It might also be a good idea to turn some of my longer threads on my Telegram channel (yes, I have one, you can't have it lol)
|
||||||
|
into journal updates on here when I don't have a lot to say. I wanna keep up this weird streak of actually writing shit going,
|
||||||
|
and repurposing some of my older content here would help a lot with that.
|
||||||
|
|
||||||
|
Anyways thanks for reading, next blog post coming never I only write journal posts now sorry not sorry ex dee
|
||||||
|
|
||||||
|
oh and [here's some lesbian stoner rock just for you](https://music.youtube.com/playlist?list=OLAK5uy_nQdPagO9sw54iXmPZzUy9MWcU-p91fIr0&si=ByF52bpveQQt28iV)
|
||||||
|
|
||||||
|
# 2024-07-25
|
||||||
|
|
||||||
|
I accidentally a YouTube channel. Again.
|
||||||
|
|
||||||
|
The first time I accidentally a YouTube channel, I wanted to comment on [a video about making a clone of Flappy Bird for the Nintendo DS](https://www.youtube.com/watch?v=h7pq9hUMnog).
|
||||||
|
This time, it was also to comment on videos, but I also wanted to upload random videos that no one would care about.
|
||||||
|
Turns out, [my first video that I ever published on my new channel](https://www.youtube.com/watch?v=rjOU0UjsXAg) got 300-something views. Whoops.
|
||||||
|
|
||||||
|
So, I decided to make a Valorant funny moments compilation and upload it to my channel.
|
||||||
|
|
||||||
|
<MDXCallout>
|
||||||
|
The video is gone because the video no longer exists. I'll be talking about this in my newest blogpost coming soon.
|
||||||
|
</MDXCallout>
|
||||||
|
|
||||||
|
<p className="text-sm italic text-subtitle mb-2">Yes, I know, a direct YouTube embed is stinky. One day I want to implement a system where you have to click on an image first in order to load the embed. I would also like to host my own Invidious instance one day, but that's for when I have more disposable income to throw at side projects.</p>
|
||||||
|
|
||||||
|
I've been thinking about doing this for a while. I've been collecting clips on my [medal.tv page](https://medal.tv/u/kebokyo) for over two years at this point.
|
||||||
|
Over time, I've slowly gotten better at the game and grown to appreciate it more and more. I want to start making content for Valorant, not just
|
||||||
|
"funny moments" but also analysis videos and tutorial videos. I have an idea for a video about "map control" and how important it is to winning rounds.
|
||||||
|
I may not be the best person to give advice about the game (I'm only a Silver 2 after all), but I feel like I have had enough experience with not just
|
||||||
|
Valorant but Counter-Strike as well to start giving my two cents about things. It would also help me ease into making more scripted content like videos
|
||||||
|
or even my narrated articles that I keep bringing up but never actually making.
|
||||||
|
|
||||||
|
So, yeah. I'm back on YouTube. I don't want to get famous.
|
||||||
|
I just wanna keep exploring the medium of video cause for some reason it is my most comfortable.
|
||||||
|
|
||||||
|
[You ever listened to a band named Andrew Jackson Jihad](https://www.youtube.com/watch?v=IX7TRD6XaRs)? No? Now you can. See ya in a few days I guess.
|
||||||
|
|
||||||
|
# 2024-07-20
|
||||||
|
|
||||||
|
I've been watching CDL Champs over the past couple days for the reason most people are: the in-game rewards you get for watching it.
|
||||||
|
As I've been watching this event, I've noticed that the competitive meta for the game seems very... *very* stale.
|
||||||
|
|
||||||
|
There are only three weapons that I've seen used throughout the entirety of CDL Champs:
|
||||||
|
The MCW (ACR), Rival 9 (Scorpion EVO 3), and Renetti (93 Raffica). That's it. The Rival 9 never has any optics on it, while the MCW almost always has
|
||||||
|
the Mk. 3 Reflector (seemingly based off of the red dot sight from Advanced Warfare like the JAK Glassless Optic). Some players run the Renetti without
|
||||||
|
an optic while others slap the Slate Reflector (a callback to Vanguard) on it.
|
||||||
|
|
||||||
|
The individual players may put different attachments on each of their guns, but they never differed enough for me to immediately notice.
|
||||||
|
Whenever I look at the gameplay, I either see an MCW or a Rival 9 paired up with a Renetti, and all that seems different between them are the special
|
||||||
|
camos that are given to each team. And that kinda sucks.
|
||||||
|
|
||||||
|
MWIII as a casual game is most notable for having the most amount of base weapons in any Call of Duty game -- over 100! And yet only three of them
|
||||||
|
seem to show up in professional play. The pros have either banned or agreed not to use entire categories of weapons such as shotguns and snipers,
|
||||||
|
and no-one seems to pull out alternative weapons in the categories that *aren't* banned. It was this way in MWII as well: remember how many people
|
||||||
|
complained about the TAQ-56 (SCAR-L), TAQ-V (SCAR-H), and Vaznev 9K (PP19 Vityaz) being the top meta guns? But here, it seems like the competitive
|
||||||
|
meta hasn't fully reflected the casual meta. Sure, lots of people still use the MCW and Rival 9, but there are a wide variety of guns being used in
|
||||||
|
casual play. So why aren't those guns being used in CDL? Why does a game with 100+ weapons only have 3 being featured on the big stage???
|
||||||
|
|
||||||
|
It just really infuriates me and just makes me hope even more that Black Ops 6 ends up being a hit. We need to have a more interesting competitive
|
||||||
|
scene please Bobby i beg you
|
||||||
|
|
||||||
|
Anyways, [House mix pogchamp, house mix pogchamp.](https://soundcloud.com/syndicator/the-unknown-house-djs-dj).
|
||||||
|
I need to catch up hard with school stuff so I'll mostly be focusing on that for now.
|
||||||
|
As such, I don't know when my next blog post will come out, but hopefully I can get it up by the end of this month. See y'all then!
|
||||||
|
|
||||||
|
# 2024-07-16
|
||||||
|
|
||||||
|
You know when I said I should stop working on improving my site and start actually writing stuff? Turns out I forgot to follow my own advice. Whoops.
|
||||||
|
|
||||||
|
The sharefeed has been officially implemented again. All the old journal entries I had on the old website are back,
|
||||||
|
and most of the old sharefeed entries have been ported over too. I dropped a couple due to the power of hindsight, but you won't remember them anyway lol.
|
||||||
|
|
||||||
|
I also changed up how the RSS feeds work. Now, when you click on the "rss" link, it will take you to a page listing all the feeds you can subscribe to.
|
||||||
|
I've also added a prefix to each post in the main feed so you can tell what type of post it is at a glance.
|
||||||
|
For example, blog posts will be marked `[blog]`, To everyone who already subscribed, I am so
|
||||||
|
greatful for your support and I am so sorry this will probably screw up your feed lol. It won't be the last time this happens either...
|
||||||
|
I may do a *little* more trolling over the next couple of weeks, including adding journal entries to the feed.
|
||||||
|
|
||||||
|
The last thing I am going to do tonight is attempt to record a narration for my first blog post. Hopefully it goes well.
|
||||||
|
I'm really excited about narrated articles, and I hope I can inspire others to try similar things with their blogs.
|
||||||
|
|
||||||
|
[Another album recommendation cause I'm awesome.](https://www.youtube.com/watch?v=4VIkQ6zHjyw) See y'all soon!
|
||||||
|
|
||||||
|
# 2024-07-08
|
||||||
|
|
||||||
|
I'm starting to do actual reseach for my next article, which, i lied, it's actually going to be about Geminispace.
|
||||||
|
It seems like everyone stopped talking about it after its boom a couple years ago, but it seems to be thriving in its own sense, with activity
|
||||||
|
every day on places like Antenna and the Geminispace BBS.
|
||||||
|
|
||||||
|
I made a random post asking people for cool sites that show what you can do under the limitations of the Gemini protocol, and I got a bunch of responses.
|
||||||
|
I even got a pretty detailed list with categories from someone who wished not to be documented on the web.
|
||||||
|
|
||||||
|
Still, that doesn't mean that everything is sunshine and rainbows. Two days in and I already found someone promoting
|
||||||
|
alt-right anti-Palestine behavior on Station, another bulletin board like platform for Gemini. It sucks that sort of stuff has popped up here, but
|
||||||
|
one has to remember that even if this is the "smallnet", this is still the internet. There will still be assholes.
|
||||||
|
|
||||||
|
[Song recommendation numero tres.](https://www.youtube.com/watch?v=C27Y4aW1-r0&pp=ygUSd2F0ZXJ3ZWVkIGRheWJyZWFr). Hopefully I'll have more to say tomorrow.
|
||||||
|
|
||||||
|
# 2024-07-06
|
||||||
|
|
||||||
|
I have a problem: if I want to update my site, I have to shut both it *and* my Forgejo instance down in order to have enough *dedodated wham*
|
||||||
|
headroom to actually run `pnpm build` without making it crash under error code 137. Whoops. I traded one problem (having to do a compile process
|
||||||
|
every time I want to change my website) for a worse one (the same thing but i also have to explicitly shut down both it *and something unrelated*
|
||||||
|
in order for it to run without crashing).
|
||||||
|
|
||||||
|
I could, of course, just throw more money at the problem by upgrading my VPS.
|
||||||
|
|
||||||
|
I have $50 in my bank account. That's not an option.
|
||||||
|
|
||||||
|
Another way to fix this problem is to move to something like Astro that's specifically designed for my use case. I had some trouble wrapping my head
|
||||||
|
around Astro last time I tried it out, but it may be something I have to do in the future. I'm putting a pin on it for now, but I'm not going to
|
||||||
|
hop over just yet. The future stuff I want to do (such as auth & comments systems) will likely be easier on Next than on Astro since Next is the
|
||||||
|
McDonald's of React-based metaframeworks right now.
|
||||||
|
|
||||||
|
This journal update is an attempt at seeing if I can make use of the secret third option: only compiling the contentlayer MDX files unless there's
|
||||||
|
something I want to change outside of that. If that's an option I can utilize, then I can probably just make a git action to automatically
|
||||||
|
recompile the MDX whenever I push a commit containing an MDX file. That'll do for now.
|
||||||
|
|
||||||
|
**UPDATE: NO. NO IT DOESN'T. THE SITE DOESN'T UPDATE THE PAGES WHEN I JUST COMPILE CONTENTLAYER. I HAVE TO DO EVERYTHING EVERY TIME. IM SCREAMING**
|
||||||
|
|
||||||
|
Anyways [please listen to these jungle sets, they are amazing and i love them and i want to make out with them](https://youtu.be/n04zbWo5obA?si=6rD3RPttlkT3NiuH&t=139). Thanks.
|
||||||
|
|
||||||
|
# 2024-07-05
|
||||||
|
|
||||||
|
Welp, I did it. I got the journal implemented.
|
||||||
|
|
||||||
|
I decided to make it its own MDX file stuffed in its own special folder and grabbed by its own special TSX page... because I want to be able to use
|
||||||
|
MDX to write this stuff. I don't wanna have to put `<p>`'s everywhere lol.
|
||||||
|
|
||||||
|
I've thought about implementing a table of contents so you can jump to the day you want to see, but I'm deciding against that for now. One day I'll
|
||||||
|
update the TOC to be collapsible, and once I do, I'll add it to this page, automatically collapsed (unless you unfurl it, in which case that state
|
||||||
|
will be saved via cookie for later).
|
||||||
|
|
||||||
|
I also ***finally*** gave some much-needed love to my [Hello Again](/posts/2024-06-16-hello-again) article.
|
||||||
|
I was not very satisfied with how it got published. I rushed so fast to get my site ready and out the door that I forgot to properly proofread
|
||||||
|
my article. So, I'm doing that now. Hopefully these changes will make the article flow better and easier to parse.
|
||||||
|
|
||||||
|
Finally, I think it would be a good idea to post the old entries of this journal on here. I don't have them on me right now,
|
||||||
|
but once I figured out where I put them, I'll throw them on here for archival purposes.
|
||||||
|
|
||||||
|
As usual, [here's my song recommendation of the whenever](https://www.youtube.com/watch?v=RudYJD8GJZE). See ya in a week or so, I guess.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<MDXCallout preset="note">The rest of this page contains older journal entries ported over from the previous version of this site.</MDXCallout>
|
||||||
|
|
||||||
|
# 2023-08-20
|
||||||
|
|
||||||
|
My CalcKey instance has been perpetually upset at me. It sucks because if I undo my migration, I'll likely lose all of my follows and followers. I think I might need to upgrade my VPS so it isn't using up all my server's resources... but I'm not sure if I can afford it right now. If you have some suggestions as to how I can fix this, email me lol
|
||||||
|
|
||||||
|
Also, sorry for fucking up the sharefeed and not realizing until more than a month later. whoops.
|
||||||
|
|
||||||
|
# 2023-07-05
|
||||||
|
|
||||||
|
I've been meaning to create an online forums for a while now to serve as my blog's comment section and provide an alternative social space for queer nerds like me. I thought I found the perfect solution in Discourse... but it eats up so much ram. Only around 70MB is left of my VPS's 2GB of ram. Ouch.
|
||||||
|
|
||||||
|
Thus, even though you may have noticed that I had Discourse serve as the comments section of my site for about a month, I am disabling it for now to make room for a Calckey instance. Sorry about that!
|
||||||
|
|
||||||
|
As an apology, more sharefeed stuffs coming your way! :3
|
||||||
|
|
||||||
|
also listen to this album plz: [This Town Needs Guns - Animals](https://www.youtube.com/watch?v=DOHGTJoh498)
|
||||||
|
|
||||||
|
# 2023-06-03
|
||||||
|
|
||||||
|
Tried to wrangle IndieWeb stuff like h-entries and h-cards. I want to murder IndieWeb now.
|
||||||
|
|
||||||
|
In better news, I decided to roll my own social links in the footer instead of the side bar, and I think it works well!
|
||||||
|
|
||||||
|
Gonna throw out some more sharefeed posts in a bit. yay.
|
||||||
|
|
||||||
|
# 2023-06-02
|
||||||
|
|
||||||
|
I found out something funny about the new Etrian Odyssey HD remasters.
|
||||||
|
|
||||||
|
The digital release of all three games is priced at $80. There is no physical release of the game except in Asian markets.
|
||||||
|
|
||||||
|
However, PlayAsia is selling said physical release (for the Nintendo Switch) at *$70*. With standard shipping to the US, the total price is $77.90... ***cheaper than the digital release.***
|
||||||
|
|
||||||
|
Guess me being a weirdo who likes collecting physical Switch games wasn't dumb after all.
|
||||||
|
|
||||||
|
Also, I have a bunch of shit lined up for the sharefeed. Going to be spreading them out over the weekend. Happy reading!
|
||||||
|
|
||||||
|
# 2023-06-01
|
||||||
|
|
||||||
|
First of all, happy pride month. Be gay. Do crime.
|
||||||
|
|
||||||
|
Second, you may notice my blog is now Evil PewDiePie flavored. Figured it would be a cool way to spice up the visual design. If there are any elements that still look messed up with this change, let me know.
|
||||||
|
|
||||||
|
Finally, link emebeds now work. I may pretty up the embed image at some point for articles, but for now, I'm pretty excited!
|
||||||
|
|
||||||
|
# 2023-05-19
|
||||||
|
|
||||||
|
I follow a Hacker News bot on Telegram. Whenever I see something interesting, I forward the post to my "Saved Messages" to read later... which I never do.
|
||||||
|
|
||||||
|
I had an idea to turn this pipeline into something that is actually useful for not just me but for anyone else who is as much of a fucking nerd as I am. Basically, it would be a webpage filled with links to blog posts or articles or other websites that I find interesting (note: interesting != good) that can also be viewed as an Atom feed. I would attach summaries/notes to each link (which would be stuffed under the \<summary\> tag on the Atom feed) so I can give some more detials of what I think about it and possibly what I might do with it in the future (e. make a response to it on my blog, use the project for my own purposes, etc).
|
||||||
|
|
||||||
|
I'm sure there's already solutions for "read later" feeds like what I wanna do, but I wanna roll my own because I think it would be pretty simple to make a basic rendition of this (especially in Jekyll thanks to Liquid markup allowing you to literally roll your own custom Atom feed if you wanna). Plus... if this kicks off, maybe I can develop it further! Comments, a framework to make something like it for your own site... maybe even ActivityPub support.
|
||||||
|
|
||||||
|
Now, for something completely different... [Listen to this right the fuck now.](
|
||||||
|
https://www.youtube.com/watch?v=DZ2jgJi54So)
|
||||||
|
|
||||||
|
# 2023-05-02
|
||||||
|
|
||||||
|
I just updated the theme of this blog from Minima to Chirpy. Find this a lot better than I had before! Gonna make a blog post on it once I finalize some shit.
|
||||||
|
|
||||||
|
I also need to fix up this journal to actually work as an HTML journal. Right now, there's some elements missing, and I need to make a jekyll plugin to parse through this page's content and put in the proper elements for each entry. Might do that as a warm up to [jekyll-to-gemini](../posts/jekyll-to-gemini).
|
||||||
|
|
||||||
|
oh also pro tip that i just learned: adding two or more spaces to a line and then inserting a newline lets you insert a line break. donno why it doesn't just format it the right way in the first place but I didn't make Markdown.
|
||||||
|
|
||||||
|
# 2023-03-28
|
||||||
|
|
||||||
|
Gonna try to use this as a space to put stuff that doesn't feel "big enough" for a blog post. Hopefully this encourages me to actually use this site more lol
|
||||||
|
|
110
src/pages/me.mdx
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
---
|
||||||
|
layout: '../layouts/StandaloneMDXLayout.astro'
|
||||||
|
title: Me!
|
||||||
|
date: 1970-01-01
|
||||||
|
summary: "A page for stuff about me and what I'm doing."
|
||||||
|
draft: true
|
||||||
|
---
|
||||||
|
|
||||||
|
import MDXCallout from '../components/mdx/MDXCallout.astro'
|
||||||
|
|
||||||
|
Hello! My name is Kebo. I have a different name out in the real world, but online I usually go by Kebo or my username `kebokyo`.
|
||||||
|
|
||||||
|
I am currently an undergraduate student studying computer science, supplementing my education by building and maintaining a silly
|
||||||
|
blog and the VPS that it runs on.
|
||||||
|
|
||||||
|
This `/me` page is slightly different than the typical "about me" page as it's basically multiple pages in one.
|
||||||
|
The page was inspired by the website [slashpages.net](https://slashpages.net), which details many single-purpose pages
|
||||||
|
others can use to learn more about you. I decided instead of a traditional `/about` page (which still works here, btw —
|
||||||
|
feel free to try it!), I could combine the typical "about me" page with a whole bunch of sections dedicated to ideas and concepts
|
||||||
|
found on slashpages.net. That way, you can learn a lot more about me... right here on one page!
|
||||||
|
|
||||||
|
# contact
|
||||||
|
|
||||||
|
The best way to cold-contact me is through my email, `kebokyo (at) eleboog (dot) com`. There is also a widget for it in the footer.
|
||||||
|
|
||||||
|
The other places you can contact me linked in the footer are:
|
||||||
|
|
||||||
|
- Bluesky (`@kebokyo.eleboog.com`)
|
||||||
|
- Mastodon / the Fediverse (`@kebokyo@plush.city`)
|
||||||
|
|
||||||
|
I don't use social media too often, though, so I recommend trying my email first.
|
||||||
|
|
||||||
|
Finally, I do have accounts on both Discord and Telegram. My Discord username is pretty obvious (`@kebokyo`), however my Telegram
|
||||||
|
is intentionally *not*. My Telegram is for more personal contact, so it's on a "ask if you already know me" basis. For cold contacts,
|
||||||
|
I still recommend reaching out via email first as I will likely not accept your friend request on Discord unless I already know you from somewhere.
|
||||||
|
|
||||||
|
There used to be a link to my Cohost (`@kebokyo`), but Cohost is unfortunately shutting down and I didn't use it too much anyway.
|
||||||
|
|
||||||
|
# tip
|
||||||
|
|
||||||
|
If you like what I do and want to help me make more of it, currently the only way I have for you to give me money is through [Ko-fi](https://ko-fi.com/kebokyo). I am a very very broke college student and any little bit of help means a lot to me!
|
||||||
|
|
||||||
|
# why
|
||||||
|
|
||||||
|
Why do I even have this website? Well, it's for mulitple reasons:
|
||||||
|
|
||||||
|
- I don't really like social media. The algorithms encourage ragebaiting and constantly bringing up the worst aspects of life, and generally
|
||||||
|
everyone is at each others' throats from the word go for no particular reason other than "that's what we do here". My own personal website
|
||||||
|
feels like a better space to exist in simply because there aren't other people to yell at me for saying something they don't like.
|
||||||
|
|
||||||
|
- It's also a way for me to learn how to build websites and web services using modern frameworks and methods. ~~That's the big reason this site is using Next.js instead of another framework like Astro - since Next.js is by far the most popular metaframework (that I don't hate), it would be best to learn how to use Next.js for my future career.~~ scratch that i'm using Astro now because it works way better for a light personal blog lmaoooo
|
||||||
|
|
||||||
|
- Finally... I think it's neat. Having your own space on the internet is a really cool feeling, and I encourage others to do the same! You can start on [Neocities](https://neocities.org) like I did or jump straight into using a Linux VPS and your own domain.
|
||||||
|
|
||||||
|
Hopefully, this website can grow one day into a safe space for people of all different walks of life... but that goal probably won't be reached any time soon. Right now, I just hope that this site can inspire people to do their own thing like this.
|
||||||
|
|
||||||
|
# everyday
|
||||||
|
|
||||||
|
This section is a smashup of `/carry` and `/uses`. Basically, this section is for the things I use every day.
|
||||||
|
|
||||||
|
My backpack for school is a [Fjällräven Kånken Laptop 13"](https://www.fjallraven.com/us/en-us/bags-gear/kanken/kanken-bags/kanken-laptop-132/) that I got off Depop one day for $30. Considering a new one is $115... I think that's a pretty good steal. It has been my main go-bag for going literally anywhere for two to three years at this point, and while it's needing some repairs, it has served me faithfully since the very first day I got it.
|
||||||
|
|
||||||
|
The laptop that usually resides inside of this backpack is the [base model M1 MacBook Air](https://support.apple.com/en-us/111883). I got this off of eBay for around $750, but for the price I got a skin, laptop sleeve, and charger case (which I've since lost ;w;) included. Not bad! While I definitely need to upgrade to a more powerful machine at some point (likely a ThinkPad or a MacBook Pro 14"), this thing has been my main college laptop since February 2023 and hasn't really slowed down since.
|
||||||
|
|
||||||
|
My current tech pouch for storing chargers and cables is the [Chrome Industries Tech Accessory Pouch](https://chromeindustries.com/products/tech-accessory-pouch), but it's a bit too small for my liking. I might use it for something else one day, but I don't have the money to get anything else to replace it right now... so it's still going as my tech pouch for now.
|
||||||
|
|
||||||
|
The other devices I usually charge with those cables include my phone, the [iPhone 12 mini](https://www.apple.com/by/iphone-12/specs/). It's getting a bit long in the tooth now (it's starting to get stability issues that my last phone had from day one lmao), but I can't afford to upgrade right now. If I *were* to upgrade, the two options I'm looking at are the base model iPhone 16 and the [Nothing Phone (2a)](https://us.nothing.tech/pages/phone-2a).
|
||||||
|
|
||||||
|
I also carry my [Nintendo 3DS](https://en.wikipedia.org/wiki/Nintendo_3DS) almost everywhere I go, even though I don't use it much. It's handy for when I need a quick game break, or if I need to pace myself with Pomodoro (I literally used a timer app & Ridge Racer 3D to get through midnight assignments before lmao).
|
||||||
|
|
||||||
|
# colophon
|
||||||
|
|
||||||
|
This website runs on a [Hetzner VPS](https://www.hetzner.com), Nginx web server, and [~~Next.js metaframework~~](https://nextjs.org) [Astro web framework](https://astro.build).
|
||||||
|
|
||||||
|
The reason this site looks so fancy in both light & dark mode is because of [Tailwind CSS](https://tailwindcss.com) and its built-in light & dark mode features. I may build a special toggle so you can choose which version of the site you like more one day, but for now, it's just based on whether your browser / OS is in light or dark mode.
|
||||||
|
|
||||||
|
My blog posts and most of my longer pages (including this one!) are written using [MDX](https://mdxjs.com). That way, I can just plop JSX components into my Markdown documents, like this:
|
||||||
|
|
||||||
|
<MDXCallout>
|
||||||
|
ooooooooooo fancy
|
||||||
|
</MDXCallout>
|
||||||
|
|
||||||
|
I render out the MDX files using [~~Contentlayer~~](https://contentlayer.dev)~~, which is the best thing since sliced bread.~~ Astro's built-in MDX integration and Content Collection feature, which actually work quite well! It's a bit simpler than Contentlayer but gets things that would have been complicated in Contentlayer done in simpler ways, which is pretty nice.
|
||||||
|
|
||||||
|
When I insert code into my blog, I use [React Code Block](https://react-code-block.netlify.app) as a quick template to set things up. I will probably just move to using [Prism React Renderer](https://github.com/FormidableLabs/prism-react-renderer) directly in the near future, though. I may also try to find a completely different solution later since I want to reduce the amount of React used on my Astro site.
|
||||||
|
|
||||||
|
I use the <a href="https://www.npmjs.com/package/feed" class="text-subtitle text-base hover:underline px-1 bg-neutral-300 bg-opacity-50 dark:bg-slate-600 dark:bg-opacity-100 rounded-md font-mono">feed</a> library to generate my Atom & JSON feeds ~~for everything except narrated articles. For that, I use <a href="https://www.npmjs.com/package/feed" class="text-subtitle text-base hover:underline px-1 bg-neutral-300 bg-opacity-50 dark:bg-slate-600 dark:bg-opacity-100 rounded-md font-mono">podcast-rss</a> since it has more features specifically to comply with platforms' weird-ass rules
|
||||||
|
regarding podcast feeds.~~ Right now, narrated articles have been scrapped, but when I do start making audio content, I'll use `podcast-rss` to generate a feed specifically for that audio content.
|
||||||
|
|
||||||
|
Finally, for the software I use to actually write the code... I used to use [Visual Studio Code](https://code.visualstudio.com), but since it's an Electron app (basically Google Chrome in a fancy native-app-shaped box), its performance and resource use is very high. Thus, I switched *back* to [Sublime Text](https://www.sublimetext.com). If anyone has a FOSS alternative to Sublime that works just as well, let me know cause that would be awesome.
|
||||||
|
|
||||||
|
# ideas
|
||||||
|
|
||||||
|
For this particular website, the main thing I want to add in the future is a **comments system** powered by my own eleboog.com account system. That way, I'm not reliant on a third-party comment system like Disqus.
|
||||||
|
|
||||||
|
I also have multiple ideas for open-source iOS and macOS apps, including a Pomodoro timer, a KeePass client, and a read later app specifically designed for iCloud sync.
|
||||||
|
|
||||||
|
It's hard for me to put all of my ideas into one place at the moment, so expect this section to be updated often.
|
||||||
|
|
||||||
|
# chipotle
|
||||||
|
|
||||||
|
Qdoba is better. Fight me.
|
||||||
|
|
||||||
|
(plus I'm poor so I don't get either of them very often lmao)
|
||||||
|
|
||||||
|
# ai
|
||||||
|
|
||||||
|
I do not use generative AI on this website. Period.
|
||||||
|
|
||||||
|
One day, I need to add a `robots.txt` and other such protections to prevent this website from being scraped... but no-one is going to listen to me anyway because my rights don't matter to them in that regard.
|
16
src/pages/newest/index.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { compareDesc, format, parseISO } from 'date-fns'
|
||||||
|
import { fromZonedTime, toZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
|
import { getCollection, getEntry } from 'astro:content';
|
||||||
|
|
||||||
|
import type { APIRoute } from 'astro';
|
||||||
|
|
||||||
|
const posts = await getCollection('posts')
|
||||||
|
|
||||||
|
const newestPost = posts.sort((a, b) => compareDesc(new Date(a.data.date), new Date(b.data.date)))[0];
|
||||||
|
|
||||||
|
console.log(newestPost.slug)
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ redirect }) => {
|
||||||
|
return redirect('http://localhost:4321/posts/' + newestPost.slug, 307);
|
||||||
|
}
|
20
src/pages/posts/[...slug].astro
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
export const prerender = true;
|
||||||
|
|
||||||
|
import {getCollection} from 'astro:content'
|
||||||
|
import PostLayout from '../../layouts/PostLayout.astro'
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const posts = await getCollection('posts');
|
||||||
|
return posts.map(p => ({
|
||||||
|
params: { slug: p.slug }, props: { p },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { p } = Astro.props;
|
||||||
|
const { Content } = await p.render();
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Content />
|
||||||
|
|
50
src/pages/sharefeed.astro
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
import { compareDesc, format, parseISO } from 'date-fns'
|
||||||
|
import { toZonedTime } from 'date-fns-tz'
|
||||||
|
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
|
||||||
|
import HR from "../components/HR.astro";
|
||||||
|
import MDXCallout from '../components/mdx/MDXCallout.astro';
|
||||||
|
|
||||||
|
import { getCollection, getEntry } from 'astro:content';
|
||||||
|
|
||||||
|
import sfjson from "../data/sharefeed.json";
|
||||||
|
import type { ShareFeedEntry } from "../lib/utils";
|
||||||
|
|
||||||
|
const entries = sfjson.sharefeed
|
||||||
|
.sort((a:ShareFeedEntry,b:ShareFeedEntry) => compareDesc(new Date(a.posted), new Date(b.posted)))
|
||||||
|
.filter((entry: ShareFeedEntry) => !(entry.draft))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="sharefeed">
|
||||||
|
<h1 class="font-serif text-3xl my-2">Sharefeed</h2>
|
||||||
|
<p class="mb-2">Sometimes, I come across other people's blog posts, articles, or other resources that I find interesting. This is my space to share those resources with you.</p>
|
||||||
|
<p class="mb-2">Every link includes a brief note or summary that I wrote up describing that specific entry and why I find it interesting or noteworthy.</p>
|
||||||
|
<MDXCallout>"interesting" does not necessarily mean "good", "factual", or even "ethical". Sometimes, I may link something here because I <b>don't</b> like it and want to debunk it later. In these cases, I will make sure to specify that fact. Be sure to keep an eye on my journal or blog when I post one of those.</MDXCallout>
|
||||||
|
<p class="mb-2">If you want to subscribe to this feed on your RSS reader, click the <a href="/feeds" class="text-subtitle text-sm font-serif">rss</a> button.</p>
|
||||||
|
|
||||||
|
<HR/>
|
||||||
|
|
||||||
|
<ul classs="space-y-2">
|
||||||
|
{entries.map((entry: ShareFeedEntry, idx: number, array: ShareFeedEntry[]) => (
|
||||||
|
<>
|
||||||
|
<li key={idx} class="mb-2">
|
||||||
|
<a href={entry.url} class="font-serif text-lg text-crusta-400 dark:text-night-300 hover:underline">
|
||||||
|
{entry.author ? `${entry.author}: ` : ''}
|
||||||
|
{entry.title}
|
||||||
|
</a>
|
||||||
|
<p class="text-sm text-subtitle">
|
||||||
|
<span class="italic">< {entry.url} ></span><br/>
|
||||||
|
{entry.pubDate ? `originally published on ${format(entry.pubDate, "LLLL d, yyyy")} - ` : ''}
|
||||||
|
retrieved {format(entry.date, "LLLL d, yyyy")}
|
||||||
|
</p>
|
||||||
|
<p>{entry.note}</p>
|
||||||
|
</li>
|
||||||
|
{((idx + 1) % 10 == 0) ? (<HR/>) : ''}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</BaseLayout>
|
270
src/styles/globals.css
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
@font-face {
|
||||||
|
font-family: "Monofur for Powerline";
|
||||||
|
src: url('/fonts/monofur.ttf') format('truetype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--primary: 0 0% 9%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--secondary: 0 0% 96.1%;
|
||||||
|
--secondary-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--muted: 0 0% 96.1%;
|
||||||
|
--muted-foreground: 0 0% 45.1%;
|
||||||
|
|
||||||
|
--accent: 0 0% 96.1%;
|
||||||
|
--accent-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 0 0% 89.8%;
|
||||||
|
--input: 0 0% 89.8%;
|
||||||
|
--ring: 0 0% 3.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
--foreground-rgb: 0, 0, 0;
|
||||||
|
--background-start-rgb: 255, 242, 215;
|
||||||
|
--background-end-rgb: 255, 249, 237;
|
||||||
|
|
||||||
|
--foreground-accent-rgb: 255, 127, 62;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--card: 0 0% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--popover: 0 0% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 0 0% 9%;
|
||||||
|
|
||||||
|
--secondary: 0 0% 14.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--muted: 0 0% 14.9%;
|
||||||
|
--muted-foreground: 0 0% 63.9%;
|
||||||
|
|
||||||
|
--accent: 0 0% 14.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 0 0% 14.9%;
|
||||||
|
--input: 0 0% 14.9%;
|
||||||
|
--ring: 0 0% 83.1%;
|
||||||
|
|
||||||
|
--foreground-rgb: 255, 255, 255;
|
||||||
|
--background-start-rgb: 0, 0, 0;
|
||||||
|
--background-end-rgb: 0, 0, 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
color: rgb(var(--foreground-rgb));
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
rgb(var(--background-end-rgb))
|
||||||
|
)
|
||||||
|
rgb(var(--background-start-rgb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.title-gradient {
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: #cbd0fa;
|
||||||
|
background-image: linear-gradient(to right, #cbd0fa, #8989f0);
|
||||||
|
}
|
||||||
|
background-color: #ff7f3e;
|
||||||
|
background-image: linear-gradient(to right, #ff7f3e, #af4261);
|
||||||
|
background-size: 100%;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-moz-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
-moz-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-dot {
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
background-color: #fba63c;
|
||||||
|
}
|
||||||
|
background-color: #1c78ad;
|
||||||
|
background-size: 100%;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-moz-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
-moz-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-title {
|
||||||
|
@apply text-crusta-400
|
||||||
|
}
|
||||||
|
.text-subtitle {
|
||||||
|
@apply text-crusta-800;
|
||||||
|
}
|
||||||
|
.text-disabled {
|
||||||
|
@apply text-crusta-300
|
||||||
|
}
|
||||||
|
.text-paragraph {
|
||||||
|
@apply text-black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.text-title {
|
||||||
|
@apply text-night-300
|
||||||
|
}
|
||||||
|
.text-subtitle {
|
||||||
|
@apply text-night-400;
|
||||||
|
}
|
||||||
|
.text-disabled {
|
||||||
|
@apply text-night-600
|
||||||
|
}
|
||||||
|
.text-paragraph {
|
||||||
|
@apply text-night-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***** PRISM *****/
|
||||||
|
.token.plain {
|
||||||
|
@apply text-slate-100
|
||||||
|
}
|
||||||
|
.token.keyword {
|
||||||
|
@apply text-selenized-dark-brMagenta
|
||||||
|
}
|
||||||
|
.token.builtin {
|
||||||
|
@apply text-selenized-dark-cyan
|
||||||
|
}
|
||||||
|
.token.class-name,
|
||||||
|
.token.function {
|
||||||
|
@apply text-selenized-dark-customYellow
|
||||||
|
}
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.symbol {
|
||||||
|
@apply text-selenized-dark-brBlue
|
||||||
|
}
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.url {
|
||||||
|
@apply text-selenized-dark-yellow
|
||||||
|
}
|
||||||
|
.token.constant,
|
||||||
|
.token.property,
|
||||||
|
.token.tag {
|
||||||
|
@apply text-selenized-dark-blue
|
||||||
|
}
|
||||||
|
.token.attr-name {
|
||||||
|
@apply text-selenized-dark-cyan
|
||||||
|
}
|
||||||
|
.token.attr-value {
|
||||||
|
@apply text-selenized-dark-green
|
||||||
|
}
|
||||||
|
.token.regex ,
|
||||||
|
.token.variable {
|
||||||
|
@apply text-selenized-dark-red
|
||||||
|
}
|
||||||
|
.token.punctuation,
|
||||||
|
.token.operator {
|
||||||
|
@apply text-slate-100
|
||||||
|
}
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
@apply text-selenized-dark-brGreen italic
|
||||||
|
}
|
||||||
|
.token.triple-quoted-string {
|
||||||
|
@apply text-selenized-dark-brOrange
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
.token.plain {
|
||||||
|
@apply text-neutral-800
|
||||||
|
}
|
||||||
|
.token.keyword {
|
||||||
|
@apply text-selenized-light-brMagenta
|
||||||
|
}
|
||||||
|
.token.builtin {
|
||||||
|
@apply text-selenized-light-cyan
|
||||||
|
}
|
||||||
|
.token.class-name,
|
||||||
|
.token.function {
|
||||||
|
@apply text-selenized-light-yellow
|
||||||
|
}
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.symbol {
|
||||||
|
@apply text-selenized-light-brBlue
|
||||||
|
}
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.url {
|
||||||
|
@apply text-selenized-light-yellow
|
||||||
|
}
|
||||||
|
.token.constant,
|
||||||
|
.token.property,
|
||||||
|
.token.tag {
|
||||||
|
@apply text-selenized-light-blue
|
||||||
|
}
|
||||||
|
.token.attr-name {
|
||||||
|
@apply text-selenized-light-cyan
|
||||||
|
}
|
||||||
|
.token.attr-value {
|
||||||
|
@apply text-selenized-light-green
|
||||||
|
}
|
||||||
|
.token.regex ,
|
||||||
|
.token.variable {
|
||||||
|
@apply text-selenized-light-red
|
||||||
|
}
|
||||||
|
.token.punctuation,
|
||||||
|
.token.operator {
|
||||||
|
@apply text-neutral-800
|
||||||
|
}
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
@apply text-selenized-light-brGreen italic
|
||||||
|
}
|
||||||
|
.token.triple-quoted-string {
|
||||||
|
@apply text-selenized-light-brOrange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer utilities {
|
||||||
|
.text-balance {
|
||||||
|
text-wrap: balance;
|
||||||
|
}
|
||||||
|
}
|
82
src/styles/markdown.css
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
=== MARKDOWN STYLING ===
|
||||||
|
The styling for all markdown text.
|
||||||
|
*/
|
||||||
|
@import url('./globals.css');
|
||||||
|
|
||||||
|
@tailwind components;
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.text-title {
|
||||||
|
@apply text-crusta-400
|
||||||
|
}
|
||||||
|
.text-subtitle {
|
||||||
|
@apply text-crusta-800;
|
||||||
|
}
|
||||||
|
.text-disabled {
|
||||||
|
@apply text-crusta-300
|
||||||
|
}
|
||||||
|
.text-paragraph {
|
||||||
|
@apply text-black;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.text-title {
|
||||||
|
@apply text-night-300
|
||||||
|
}
|
||||||
|
.text-subtitle {
|
||||||
|
@apply text-night-400;
|
||||||
|
}
|
||||||
|
.text-disabled {
|
||||||
|
@apply text-night-600
|
||||||
|
}
|
||||||
|
.text-paragraph {
|
||||||
|
@apply text-night-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
article > h1 {
|
||||||
|
@apply text-3xl font-serif font-bold text-title mb-3 mt-1
|
||||||
|
}
|
||||||
|
article > h2 {
|
||||||
|
@apply text-2xl font-serif font-semibold text-title mb-2
|
||||||
|
}
|
||||||
|
article > h3 {
|
||||||
|
@apply text-xl font-serif text-title mb-2
|
||||||
|
}
|
||||||
|
article > h4 {
|
||||||
|
@apply text-lg font-serif italic text-title mb-2
|
||||||
|
}
|
||||||
|
article > p {
|
||||||
|
@apply text-black dark:text-night-100 mb-2
|
||||||
|
}
|
||||||
|
article > p > a,
|
||||||
|
article > ul > li > a,
|
||||||
|
article > ol > li > a {
|
||||||
|
@apply font-serif text-subtitle text-sm hover:underline data-[level=two]:pl-2 data-[level=three]:pl-4
|
||||||
|
}
|
||||||
|
article > blockquote {
|
||||||
|
@apply p-4 border-s-4 text-muted-foreground bg-muted
|
||||||
|
}
|
||||||
|
article > pre {
|
||||||
|
@apply p-2 bg-neutral-300 bg-opacity-50 dark:bg-slate-800 dark:bg-opacity-100 rounded-lg font-mono overflow-y-scroll mb-2
|
||||||
|
}
|
||||||
|
article > p > code {
|
||||||
|
@apply px-1 bg-neutral-300 bg-opacity-50 dark:bg-slate-600 dark:bg-opacity-100 rounded-md font-mono
|
||||||
|
}
|
||||||
|
article > pre > code {
|
||||||
|
@apply px-0 bg-transparent rounded-none
|
||||||
|
}
|
||||||
|
article > ul {
|
||||||
|
@apply p-2 pl-4 mb-2 list-disc text-black dark:text-night-100
|
||||||
|
}
|
||||||
|
article > ol {
|
||||||
|
@apply ml-2 p-2 pl-4 mb-2 list-decimal text-black dark:text-night-100
|
||||||
|
}
|
||||||
|
article > li {
|
||||||
|
@apply mb-1
|
||||||
|
}
|
||||||
|
article > img {
|
||||||
|
@apply relative -z-10 border-4 border-crusta-200 dark:border-night-800 rounded-lg shadow-lgr shadow-crusta-400/20 dark:shadow-night-400/50 my-2
|
||||||
|
}
|
1
src/svg/FaBluesky.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="24px" fill="currentColor"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M407.8 294.7c-3.3-.4-6.7-.8-10-1.3c3.4 .4 6.7 .9 10 1.3zM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3C61.6-9.4 37.5-1.7 21.6 5.5C3.3 13.8 0 41.9 0 58.4S9.1 194 15 213.9c19.5 65.7 89.1 87.9 153.2 80.7c3.3-.5 6.6-.9 10-1.4c-3.3 .5-6.6 1-10 1.4C74.3 308.6-9.1 342.8 100.3 464.5C220.6 589.1 265.1 437.8 288 361.1c22.9 76.7 49.2 222.5 185.6 103.4c102.4-103.4 28.1-156-65.8-169.9c-3.3-.4-6.7-.8-10-1.3c3.4 .4 6.7 .9 10 1.3c64.1 7.1 133.6-15.1 153.2-80.7C566.9 194 576 75 576 58.4s-3.3-44.7-21.6-52.9c-15.8-7.1-40-14.9-103.2 29.8C385.1 81.9 314.1 176.4 288 227.1z"/></svg>
|
After Width: | Height: | Size: 819 B |
1
src/svg/MdMail.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280 320-200v-80L480-520 160-720v80l320 200Z"/></svg>
|
After Width: | Height: | Size: 296 B |
1
src/svg/PiFediverseLogoFill.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24px" fill="currentColor" viewBox="0 0 256 256"><path d="M212,96a27.84,27.84,0,0,0-10.51,2L171,59.94A28,28,0,1,0,120,44a28.65,28.65,0,0,0,.15,2.94L73.68,66.3a28,28,0,1,0-28.6,44.83l1.85,46.38a28,28,0,1,0,32.74,41.42L128,212.47a28,28,0,1,0,49.13-18.79l27.21-42.75A28,28,0,1,0,212,96ZM71.19,104.36,113.72,129,72.26,161.22a28,28,0,0,0-9.34-4.35l-1.85-46.38A28,28,0,0,0,71.19,104.36ZM149.57,72a27.8,27.8,0,0,0,8.94-2L189,108.06a27.86,27.86,0,0,0-4.18,9.22l-46.57,2.22ZM82.09,173.85,124,141.26l15.94,47.83a28.2,28.2,0,0,0-7.6,8L84,183.53A28,28,0,0,0,82.09,173.85ZM156,184l-.89,0-16.18-48.53,46.65-2.22a27.94,27.94,0,0,0,5.28,9l-27.21,42.75A28,28,0,0,0,156,184ZM126.32,61.7A28.44,28.44,0,0,0,134,68.24l-11.3,47.45L79.23,90.52A28,28,0,0,0,80,84a28.65,28.65,0,0,0-.15-2.94Z"></path></svg>
|
After Width: | Height: | Size: 827 B |
|
@ -1,8 +0,0 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
}
|
|
214
tailwind.config.ts
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
import type { Config } from "tailwindcss"
|
||||||
|
import defaultTheme from "tailwindcss/defaultTheme"
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
//darkMode: ["class"],
|
||||||
|
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||||
|
prefix: "",
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
screens: {
|
||||||
|
"xs": "440px",
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
// palletes
|
||||||
|
'crusta': {
|
||||||
|
'50': '#fff5ed',
|
||||||
|
'100': '#ffe8d4',
|
||||||
|
'200': '#ffcda8',
|
||||||
|
'300': '#ffaa71',
|
||||||
|
'400': '#ff7f3e',
|
||||||
|
'500': '#fe5711',
|
||||||
|
'600': '#ef3d07',
|
||||||
|
'700': '#c62908',
|
||||||
|
'800': '#9d230f',
|
||||||
|
'900': '#7e2010',
|
||||||
|
'950': '#440c06',
|
||||||
|
},
|
||||||
|
'beach': {
|
||||||
|
'50': '#fff9ed',
|
||||||
|
'100': '#fff2d7',
|
||||||
|
'200': '#fee0aa',
|
||||||
|
'300': '#fdc874',
|
||||||
|
'400': '#fba63c',
|
||||||
|
'500': '#f98b16',
|
||||||
|
'600': '#ea6f0c',
|
||||||
|
'700': '#c2540c',
|
||||||
|
'800': '#9a4212',
|
||||||
|
'900': '#7c3812',
|
||||||
|
'950': '#431a07',
|
||||||
|
},
|
||||||
|
'prune': {
|
||||||
|
'50': '#fbf4f7',
|
||||||
|
'100': '#f8ebf0',
|
||||||
|
'200': '#f3d7e3',
|
||||||
|
'300': '#eab7cb',
|
||||||
|
'400': '#dc8aa8',
|
||||||
|
'500': '#ce6688',
|
||||||
|
'600': '#af4261',
|
||||||
|
'700': '#a03651',
|
||||||
|
'800': '#843045',
|
||||||
|
'900': '#6f2c3c',
|
||||||
|
'950': '#43141f',
|
||||||
|
},
|
||||||
|
'malibu': {
|
||||||
|
'50': '#f2f9fd',
|
||||||
|
'100': '#e4f0fa',
|
||||||
|
'200': '#c3e2f4',
|
||||||
|
'300': '#80c4e9',
|
||||||
|
'400': '#51afdf',
|
||||||
|
'500': '#2b95cc',
|
||||||
|
'600': '#1c78ad',
|
||||||
|
'700': '#18608c',
|
||||||
|
'800': '#185174',
|
||||||
|
'900': '#194561',
|
||||||
|
'950': '#112c40',
|
||||||
|
},
|
||||||
|
'night': {
|
||||||
|
'50': '#eff2fe',
|
||||||
|
'100': '#e2e5fd',
|
||||||
|
'200': '#cbd0fa',
|
||||||
|
'300': '#abb1f6',
|
||||||
|
'400': '#8989f0',
|
||||||
|
'500': '#756de7',
|
||||||
|
'600': '#6651da',
|
||||||
|
'700': '#604cc3',
|
||||||
|
'800': '#47389b',
|
||||||
|
'900': '#3d347b',
|
||||||
|
'950': '#251e48',
|
||||||
|
},
|
||||||
|
// selenized
|
||||||
|
'selenized': {
|
||||||
|
'dark': {
|
||||||
|
"bg0": "#103c48",
|
||||||
|
"bg1": "#184956",
|
||||||
|
"bg2": "#2d5b69",
|
||||||
|
"dim0": "#72898f",
|
||||||
|
"fg0": "#adbcbc",
|
||||||
|
"fg1": "#cad8d9",
|
||||||
|
|
||||||
|
"red": "#fa5750",
|
||||||
|
"green": "#75b938",
|
||||||
|
"yellow": "#dbb32d",
|
||||||
|
"blue": "#4695f7",
|
||||||
|
"magenta": "#f275be",
|
||||||
|
"cyan": "#41c7b9",
|
||||||
|
"mint": "#8FDDC0",
|
||||||
|
"orange": "#ed8649",
|
||||||
|
"violet": "#af88eb",
|
||||||
|
|
||||||
|
"brRed": "#ff665c",
|
||||||
|
"brGreen": "#84c747",
|
||||||
|
"brYellow": "#ebc13d",
|
||||||
|
"brBlue": "#58a3ff",
|
||||||
|
"brMagenta": "#ff84cd",
|
||||||
|
"brCyan": "#53d6c7",
|
||||||
|
"brMint": "#CDF0E5",
|
||||||
|
"brOrange": "#fd9456",
|
||||||
|
"brViolet": "#bd96fa",
|
||||||
|
|
||||||
|
"customYellow": "#F8ED91",
|
||||||
|
},
|
||||||
|
'light': {
|
||||||
|
"bg0": "#fbf3db",
|
||||||
|
"bg1": "#ece3cc",
|
||||||
|
"bg2": "#d5cdb6",
|
||||||
|
"dim0": "#909995",
|
||||||
|
"fg0": "#53676d",
|
||||||
|
"fg1": "#3a4d53",
|
||||||
|
|
||||||
|
"red": "#d2212d",
|
||||||
|
"green": "#489100",
|
||||||
|
"yellow": "#ad8900",
|
||||||
|
"blue": "#0072d4",
|
||||||
|
"magenta": "#ca4898",
|
||||||
|
"cyan": "#009c8f",
|
||||||
|
"orange": "#c25d1e",
|
||||||
|
"violet": "#8762c6",
|
||||||
|
|
||||||
|
"brRed": "#cc1729",
|
||||||
|
"brGreen": "#428b00",
|
||||||
|
"brYellow": "#a78300",
|
||||||
|
"brBlue": "#006dce",
|
||||||
|
"brMagenta": "#c44392",
|
||||||
|
"brCyan": "#00978a",
|
||||||
|
"brOrange": "#bc5819",
|
||||||
|
"brViolet": "#825dc0",
|
||||||
|
|
||||||
|
"customYellow": "#F8ED91",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"accordion-down": {
|
||||||
|
from: { height: "0" },
|
||||||
|
to: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
},
|
||||||
|
"accordion-up": {
|
||||||
|
from: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
to: { height: "0" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ["Nunito", ...defaultTheme.fontFamily.sans],
|
||||||
|
serif: ["Libre Baskerville", ...defaultTheme.fontFamily.serif],
|
||||||
|
mono: ["Monofur for Powerline", ...defaultTheme.fontFamily.mono]
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
'lgr': '10px 10px 15px -3px rgb(0 0 0 / 0.1), 4px 4px 6px -4px rgb(0 0 0 / 0.1)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
} satisfies Config
|
||||||
|
|
||||||
|
export default config
|
|
@ -2,6 +2,14 @@
|
||||||
"extends": "astro/tsconfigs/strict",
|
"extends": "astro/tsconfigs/strict",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "react"
|
"jsxImportSource": "react",
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./*"],
|
||||||
|
"@c/*": ["./src/components/*"],
|
||||||
|
"@l/*": ["./lib/*"],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|