Compare commits
29 Commits
cf0a2022c8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 08431fd6c6 | |||
| 7972692eff | |||
| e1b290d6bc | |||
| e769bbebff | |||
| cebf8b08dc | |||
| 551a9d286d | |||
| 54419e3808 | |||
| 89aec92b2e | |||
| 80082864a7 | |||
| 3a26219434 | |||
| a2e315413f | |||
| 89533e006f | |||
| 8a73044c19 | |||
| 75c9322fd0 | |||
| 96ee56fc1f | |||
| ae109330d9 | |||
| e73005968d | |||
| 80ea47644c | |||
| f82fe9206f | |||
| b93123a8c9 | |||
| 6a97302539 | |||
| 1e35719c3a | |||
| 9dcf57a730 | |||
| 5a17ff266d | |||
| 0daac5bba4 | |||
| 2ad5e29cad | |||
| b53c2ee80a | |||
| e2c68b39b4 | |||
| 0f0025219c |
5
.gitignore
vendored
@@ -130,3 +130,8 @@ dist
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# Astro
|
||||
.astro/
|
||||
|
||||
# Direnv
|
||||
.direnv/
|
||||
|
||||
8
.markdownlint.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
# Indentation
|
||||
MD007:
|
||||
indent: 2
|
||||
# Maximum multiple consecutive blank lines
|
||||
MD012:
|
||||
maximum: 2
|
||||
# Disable the line-length warnings.
|
||||
MD013: false
|
||||
14
README.md
@@ -1,2 +1,14 @@
|
||||
# blazestar.net
|
||||
# Blazestar.net
|
||||
|
||||
## Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
16
astro.config.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from "astro/config";
|
||||
import mdx from "@astrojs/mdx";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
|
||||
import react from "@astrojs/react";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
site: "https://www.blazestar.net",
|
||||
integrations: [mdx(), sitemap(), react()],
|
||||
|
||||
vite: {
|
||||
plugins: [],
|
||||
},
|
||||
});
|
||||
42
flake.lock
generated
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-schemas": {
|
||||
"locked": {
|
||||
"lastModified": 1721999734,
|
||||
"narHash": "sha256-G5CxYeJVm4lcEtaO87LKzOsVnWeTcHGKbKxNamNWgOw=",
|
||||
"rev": "0a5c42297d870156d9c57d8f99e476b738dcd982",
|
||||
"revCount": 75,
|
||||
"type": "tarball",
|
||||
"url": "https://api.flakehub.com/f/pinned/DeterminateSystems/flake-schemas/0.1.5/0190ef2f-61e0-794b-ba14-e82f225e55e6/source.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://flakehub.com/f/DeterminateSystems/flake-schemas/%2A"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1749494155,
|
||||
"narHash": "sha256-FG4DEYBpROupu758beabUk9lhrblSf5hnv84v1TLqMc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "88331c17ba434359491e8d5889cce872464052c2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-25.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-schemas": "flake-schemas",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
52
flake.nix
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
description = "Blazestar.net homepage";
|
||||
|
||||
# Flake inputs
|
||||
inputs = {
|
||||
flake-schemas.url = "https://flakehub.com/f/DeterminateSystems/flake-schemas/*";
|
||||
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-25.05";
|
||||
};
|
||||
|
||||
# Flake outputs that other flakes can use
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
flake-schemas,
|
||||
nixpkgs,
|
||||
}:
|
||||
let
|
||||
# Helpers for producing system-specific outputs
|
||||
supportedSystems = [ "x86_64-linux" ];
|
||||
forEachSupportedSystem =
|
||||
f:
|
||||
nixpkgs.lib.genAttrs supportedSystems (
|
||||
system:
|
||||
f {
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
# Schemas tell Nix about the structure of your flake's outputs
|
||||
schemas = flake-schemas.schemas;
|
||||
|
||||
# Development environments
|
||||
devShells = forEachSupportedSystem (
|
||||
{ pkgs }:
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
git # Version control
|
||||
nodejs_22 # Base language utils
|
||||
eslint # Linting
|
||||
astro-language-server # LSP
|
||||
nodePackages.prettier # Formatting
|
||||
nixpkgs-fmt # Formatting
|
||||
drill # Benchmarking
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
7560
package-lock.json
generated
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "blazestar.net",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.0",
|
||||
"@astrojs/react": "^4.3.0",
|
||||
"@astrojs/rss": "^4.0.12",
|
||||
"@astrojs/sitemap": "^3.4.1",
|
||||
"@tailwindcss/vite": "^4.1.8",
|
||||
"@types/react": "^19.1.7",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"astro": "^5.9.2",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"sharp": "^0.34.2",
|
||||
"tailwindcss": "^4.1.8"
|
||||
}
|
||||
}
|
||||
BIN
public/fonts/FiraCode-Bold.woff2
Normal file
BIN
public/fonts/FiraCode-Light.woff2
Normal file
BIN
public/fonts/FiraCode-Medium.woff2
Normal file
BIN
public/fonts/FiraCode-Regular.woff2
Normal file
BIN
public/fonts/FiraCode-SemiBold.woff2
Normal file
BIN
public/fonts/FiraCode-VF.woff2
Normal file
BIN
public/fonts/atkinson-bold.woff
Normal file
BIN
public/fonts/atkinson-regular.woff
Normal file
BIN
public/tts/Greater Good Marker v2 - Observer.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
public/tts/Greater Good Marker v2 - Spotted.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/tts/Observer - Blue.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/tts/Observer - Green.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/tts/Observer - Orange.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/tts/Observer - Red.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/tts/Observer - Yellow.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/tts/Spotted - Black.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/tts/Spotted - Blue.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
public/tts/Spotted - Green.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/tts/Spotted - Orange.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
public/tts/Spotted - Red.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/tts/Spotted - White.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/tts/Spotted - Yellow.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
60
src/components/BaseHead.astro
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
// Import the global.css file here so that it is included on
|
||||
// all pages through the use of the <BaseHead /> component.
|
||||
import '../styles/global.css';
|
||||
import { SITE_TITLE } from '../consts';
|
||||
import type { ImageMetadata } from 'astro';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: ImageMetadata;
|
||||
}
|
||||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
|
||||
const { title, description, image } = Astro.props;
|
||||
---
|
||||
|
||||
<!-- Global Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title={SITE_TITLE}
|
||||
href={new URL('rss.xml', Astro.site)}
|
||||
/>
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
|
||||
<!-- Font preloads -->
|
||||
<link rel="preload" href="/fonts/atkinson-regular.woff" as="font" type="font/woff" crossorigin />
|
||||
<link rel="preload" href="/fonts/atkinson-bold.woff" as="font" type="font/woff" crossorigin />
|
||||
|
||||
<!-- Canonical URL -->
|
||||
<link rel="canonical" href={canonicalURL} />
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>{title}</title>
|
||||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
{ image &&
|
||||
<meta property="og:image" content={new URL(image.src, Astro.url)} />
|
||||
}
|
||||
|
||||
<!-- Twitter -->
|
||||
<meta property="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:url" content={Astro.url} />
|
||||
<meta property="twitter:title" content={title} />
|
||||
<meta property="twitter:description" content={description} />
|
||||
{ image &&
|
||||
<meta property="twitter:image" content={new URL(image.src, Astro.url)} />
|
||||
}
|
||||
21
src/components/Blazestar.astro
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
---
|
||||
<span class="logo">
|
||||
<span class="blazestar">Blazestar</span><span class="dot">.</span><span class="net">net</span>
|
||||
</span>
|
||||
<style>
|
||||
.logo {
|
||||
color: var(--color-light-text);
|
||||
font-family: 'Fira Code', monospace;
|
||||
|
||||
.dot {
|
||||
color: var(--color-red);
|
||||
margin: 0 -0.1em;
|
||||
}
|
||||
|
||||
.net {
|
||||
color: var(--color-red);
|
||||
font-size: 75%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
23
src/components/Footer.astro
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
const today = new Date();
|
||||
---
|
||||
|
||||
<footer>
|
||||
© {today.getFullYear()} Periodic. All rights reserved.
|
||||
<script>
|
||||
window.goatcounter = {
|
||||
path: (path) => `${location.host}${path}`,
|
||||
};
|
||||
</script>
|
||||
<script
|
||||
data-goatcounter="https://goatcounter.blazestar.net/count"
|
||||
async
|
||||
src="https://goatcounter.blazestar.net/count.js"></script>
|
||||
</footer>
|
||||
<style>
|
||||
footer {
|
||||
padding: 2em 1em 6em 1em;
|
||||
text-align: center;
|
||||
color: var(--color-gray);
|
||||
}
|
||||
</style>
|
||||
23
src/components/FormattedDate.astro
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
interface Props {
|
||||
date: Date;
|
||||
}
|
||||
|
||||
const { date } = Astro.props;
|
||||
---
|
||||
|
||||
<time datetime={date.toISOString()}>
|
||||
{
|
||||
date.toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
})
|
||||
}
|
||||
</time>
|
||||
<style>
|
||||
time {
|
||||
font-family: "Fira Code", monospace;
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
</style>
|
||||
85
src/components/Header.astro
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
import HeaderLink from './HeaderLink.astro';
|
||||
import Blazestar from './Blazestar.astro';
|
||||
---
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<h1><Blazestar /></h1>
|
||||
<div class="internal-links">
|
||||
<HeaderLink href="/">Home</HeaderLink>
|
||||
<HeaderLink href="/blog">Blog</HeaderLink>
|
||||
<HeaderLink href="/about">About</HeaderLink>
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a href="https://m.webtoo.ls/@astro" target="_blank">
|
||||
<span class="sr-only">Follow Periodic on Mastodon</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
<a href="https://github.com/periodic" target="_blank">
|
||||
<span class="sr-only">Go to Periodic's GitHub repo</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<style>
|
||||
header {
|
||||
color: var(--color-light-text);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0.5em;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h1 a,
|
||||
h1 a.active {
|
||||
text-decoration: none;
|
||||
}
|
||||
nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
nav a {
|
||||
padding: 1em 0.5em;
|
||||
color: var(--color-light-text);
|
||||
border-bottom: 4px solid transparent;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
&.active {
|
||||
text-decoration: none;
|
||||
border-bottom-color: var(--color-accent);
|
||||
}
|
||||
}
|
||||
.social-links,
|
||||
.social-links a {
|
||||
display: flex;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.social-links {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (max-width: 520px) {
|
||||
nav {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
src/components/HeaderLink.astro
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
|
||||
type Props = HTMLAttributes<'a'>;
|
||||
|
||||
const { href, class: className, ...props } = Astro.props;
|
||||
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
|
||||
const subpath = pathname.match(/[^\/]+/g);
|
||||
const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
|
||||
---
|
||||
|
||||
<a href={href} class:list={[className, { active: isActive }]} {...props}>
|
||||
<slot />
|
||||
</a>
|
||||
<style>
|
||||
a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.active {
|
||||
font-weight: bolder;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
69
src/components/NotchedBox.astro
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
interface Props {
|
||||
fillNotches: "left" | "right" | "both" | "none";
|
||||
}
|
||||
|
||||
const { fillNotches = "none" } = Astro.props;
|
||||
|
||||
---
|
||||
|
||||
<div class={`container notch-${fillNotches}`}>
|
||||
<div class="content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1px;
|
||||
background-color: var(--color-gold);
|
||||
}
|
||||
|
||||
.notch-left {
|
||||
clip-path: polygon(
|
||||
0 0,
|
||||
100% 0,
|
||||
100% calc(100% - 16px),
|
||||
calc(100% - 16px) 100%,
|
||||
0 100%
|
||||
);
|
||||
}
|
||||
|
||||
.notch-right {
|
||||
clip-path: polygon(
|
||||
0 16px,
|
||||
16px 0,
|
||||
100% 0,
|
||||
100% 100%,
|
||||
0 100%
|
||||
);
|
||||
}
|
||||
|
||||
.notch-none {
|
||||
clip-path: polygon(
|
||||
0 16px,
|
||||
16px 0,
|
||||
100% 0,
|
||||
100% calc(100% - 16px),
|
||||
calc(100% - 16px) 100%,
|
||||
0 100%
|
||||
);
|
||||
}
|
||||
|
||||
.content {
|
||||
height: calc(100% - 16px);
|
||||
width: calc(100% - 40px);
|
||||
background-color: var(--color-space-blue);
|
||||
padding: 8px 20px;
|
||||
clip-path: polygon(
|
||||
0 16px,
|
||||
16px 0,
|
||||
100% 0,
|
||||
100% calc(100% - 16px),
|
||||
calc(100% - 16px) 100%,
|
||||
0 100%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
88
src/components/Sidebar.astro
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
import SidebarLink from './SidebarLink.astro';
|
||||
import Blazestar from './Blazestar.astro';
|
||||
---
|
||||
|
||||
<header>
|
||||
<nav>
|
||||
<h1><Blazestar /></h1>
|
||||
<div class="internal-links">
|
||||
<SidebarLink href="/">Home</SidebarLink>
|
||||
<SidebarLink href="/blog">Blog</SidebarLink>
|
||||
<SidebarLink href="/about">About</SidebarLink>
|
||||
</div>
|
||||
<div class="social-links">
|
||||
<a href="https://m.webtoo.ls/@astro" target="_blank">
|
||||
<span class="sr-only">Follow Periodic on Mastodon</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
<a href="https://github.com/periodic" target="_blank">
|
||||
<span class="sr-only">Go to Periodic's GitHub repo</span>
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
|
||||
><path
|
||||
fill="currentColor"
|
||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
||||
></path></svg
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<style>
|
||||
header {
|
||||
margin: 0;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0.5em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h1 a,
|
||||
h1 a.active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
a {
|
||||
color: var(--color-light-text);
|
||||
border-bottom: 4px solid transparent;
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
color: var(--color-accent);
|
||||
border-bottom: 4px solid transparent;
|
||||
text-decoration: none;
|
||||
}
|
||||
&.active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.internal-links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.social-links,
|
||||
.social-links a {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
justify-content: center;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.social-links {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
34
src/components/SidebarLink.astro
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
import type { HTMLAttributes } from 'astro/types';
|
||||
import NotchedBox from './NotchedBox.astro';
|
||||
|
||||
type Props = HTMLAttributes<'a'>;
|
||||
|
||||
const { href, class: className, ...props } = Astro.props;
|
||||
const pathname = Astro.url.pathname.replace(import.meta.env.BASE_URL, '');
|
||||
const subpath = pathname.match(/[^\/]+/g);
|
||||
const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
|
||||
---
|
||||
|
||||
<a href={href} class:list={[className, { active: isActive }]} {...props}>
|
||||
<NotchedBox fillNotches={ isActive ? "right" : "none" }>
|
||||
<slot />
|
||||
</NotchedBox>
|
||||
</a>
|
||||
<style>
|
||||
a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: var(--color-light-text);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
a.hover {
|
||||
color: var(--color-gold);
|
||||
}
|
||||
|
||||
a.active {
|
||||
font-weight: bolder;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
5
src/consts.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Place any global data in this file.
|
||||
// You can import this data from anywhere in your site by using the `import` keyword.
|
||||
|
||||
export const SITE_TITLE = "Blazestar.net";
|
||||
export const SITE_DESCRIPTION = "Welcome to Blazestar.net";
|
||||
28
src/content.config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { glob } from "astro/loaders";
|
||||
import { defineCollection, z } from "astro:content";
|
||||
|
||||
const blog = defineCollection({
|
||||
// Load Markdown and MDX files in the `src/content/blog/` directory.
|
||||
loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
|
||||
// Type-check frontmatter using a schema
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
// Transform string to Date object
|
||||
// Presence of this value indicates that the post is published
|
||||
pubDate: z.coerce.date().optional(),
|
||||
updatedDate: z.coerce.date().optional(),
|
||||
heroImage: image().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const page = defineCollection({
|
||||
loader: glob({ base: "./src/content/page", pattern: "**/*.{md,mdx}" }),
|
||||
schema: ({ image }) =>
|
||||
z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const collections = { blog, page };
|
||||
163
src/content/blog/drafts/checking-in-on-2025.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: "Checking in on 2025 - Where we are and where we are headed."
|
||||
description: "At the beginning of the year I made a few predictions about where things were headed. It's been a chaotic and tumultuous seven months. It's worth reviewing my predictions to see how they have borne out, where they were accurate, where they were incorrect and what I may have missed."
|
||||
---
|
||||
|
||||
I was lost at the start of the year. I couldn't see forward. Donald Trump had just recently won the presidency and was poised to unleash chaos on the U.S. government and global economy. It was hard to imagine what 2025 would look like. So much was uncertain (and still is). I sat down and attempted to gather some of the forces that I could see creating pressure on the systems of the world so that I could get some idea of where things were heading so that I could plan.
|
||||
|
||||
These aren't really predictions as much as an observation of pressures on the system. I didn't write down "the California fire-insurance market will collapse" as much as I wrote that climate change was going to create an increasing number of disasters and strain the insurance market.
|
||||
|
||||
So how well did I recognize these forces? All the ones I mention seem to still be significant. That's not really a surprise. Recognizing large forces is not hard and it's likely that they will continue to exist in six months.
|
||||
|
||||
Where I was most wrong was in what I missed. The things I didn't predict are the ones that will cause the most chaos because they are the ones I couldn't plan for. It's not the predictions that surprise us, it's the things we didn't predict.
|
||||
|
||||
## Predictions for 2025
|
||||
|
||||
### Growing wealth inequality
|
||||
|
||||
Wealth inequality has been growing in the US. This means that there are fewer people who are able to spend and drive the economy. This has a lot of knock-on effects where markets are splitting into low-end and high-end with little in the middle.
|
||||
|
||||
Examples:
|
||||
|
||||
- Lambos
|
||||
- Free-to-play games
|
||||
- <https://www.marketplace.org/2025/01/13/magnificent-seven-ai-stocks-make-up-a-huge-part-of-the-sp-500-artificial-intelligence/>
|
||||
|
||||
### Climate Change
|
||||
|
||||
The world's climate is becoming more extreme and unpredictable. The world also doesn't seem like it's towards addressing it. We are in for more extreme weather situations.
|
||||
|
||||
Examples:
|
||||
|
||||
- Pacific Palisade's fire
|
||||
- <https://arstechnica.com/cars/2025/01/only-5-percent-of-us-car-buyers-want-an-ev-according-to-survey/>
|
||||
- <https://arstechnica.com/science/2025/01/its-official-2024-was-the-warmest-year-on-record/>
|
||||
- [How Climate Denial is Fueling a U.S. Homeowners Insurance Crisis and Risking a 2008-Style Financial Meltdown](https://www.nakedcapitalism.com/2025/02/how-climate-denial-is-fueling-a-u-s-homeowners-insurance-crisis-and-risking-a-2008-style-financial-meltdown.html)
|
||||
- [Blackouts Are Becoming the Norm Can the U.S. Power Grid Be Saved](https://www.nakedcapitalism.com/2025/02/blackouts-are-becoming-the-norm-can-the-u-s-power-grid-be-saved.html)
|
||||
|
||||
Threats:
|
||||
|
||||
- Bigger natural disasters
|
||||
- Strained insurance systems
|
||||
|
||||
### Conservative Themes Increase
|
||||
|
||||
- Xenophobia
|
||||
- Transphobia
|
||||
- Racism
|
||||
- Misogyny
|
||||
- Nationalism
|
||||
- Religious division
|
||||
|
||||
Examples:
|
||||
|
||||
- Twitter
|
||||
- Trump
|
||||
- Facebook
|
||||
- Far-right media
|
||||
- <https://www.foxbusiness.com/media/mark-zuckerberg-praises-benefits-masculine-energy-calls-corporate-america-culturally-neutered>
|
||||
|
||||
### Economic uncertainty
|
||||
|
||||
- <https://www.marketplace.org/2025/02/18/new-car-sales-drop-in-january-as-dealer-lots-fill-up/>
|
||||
- <https://www.marketplace.org/2025/02/17/homebuyer-demand-down-listings-housing-market-confidence-uncertainty-inflation-interest-rates/>
|
||||
|
||||
### Inequality and Extraction
|
||||
|
||||
#### Continued Automation, Off-shoring and AI
|
||||
|
||||
Jobs continue to be off-shored or replaced. Worker productivity is going up, but there are fewer jobs in the US.
|
||||
|
||||
Examples:
|
||||
|
||||
-
|
||||
|
||||
#### Capital's growing power over labor
|
||||
|
||||
The capital owners have more and more leverage over workers. This comes from many people looking for work and companies being able to get away with more and more consolidation, meaning fewer potential employers who can abuse workers more.
|
||||
|
||||
There are some attempts to push back in the form of union drives, but the incoming administration is hostile to worker power.
|
||||
|
||||
Examples:
|
||||
|
||||
- Gig work
|
||||
- Retirement is getting harder
|
||||
- Fight for higher minimum wages
|
||||
|
||||
Effects:
|
||||
|
||||
- Harder to switch jobs
|
||||
- Lower benefits
|
||||
- Growing assets as companies are more profitable
|
||||
|
||||
#### Rising cost-of-living
|
||||
|
||||
Costs will increase, and not come down any time soon.
|
||||
|
||||
The Easton Fire this year will all but ensure that housing prices will remain high due to the lower stock and large number of displaced people. Supplies and labor will be tied up for years.
|
||||
|
||||
Examples:
|
||||
|
||||
- Housing
|
||||
- Eggs
|
||||
- Education
|
||||
|
||||
#### Increasing household debt
|
||||
|
||||
Is there a crash that would happen? Probably not because there's low risk of contagion on wall street.
|
||||
|
||||
#### Deteriorating Health Insurance
|
||||
|
||||
Healthcare in the US is getting harder to get and more expensive. We seem to be reaching the point that it is becoming a national crisis. It seems unlikely that our government will make meaningful reforms since thee area is very profitable.
|
||||
|
||||
Examples:
|
||||
|
||||
- Price of insulin
|
||||
- Increased claim denials
|
||||
- Luigi Mangione
|
||||
|
||||
### Technology
|
||||
|
||||
#### Continued growth of AI
|
||||
|
||||
AI will continue to grow over the next year. It will be searching for use cases and will refine the ones it has already. Agentic AI will continue to improve.
|
||||
|
||||
I think it's unlikely there will be any major leaps forward.
|
||||
|
||||
<https://www.marketplace.org/2025/01/13/magnificent-seven-ai-stocks-make-up-a-huge-part-of-the-sp-500-artificial-intelligence/>
|
||||
|
||||
#### Cryptocurrency
|
||||
|
||||
This will gain strength over the next year, but remain niche. Prices will continue to rise for the major currencies, even as scams and rug-pulls continue.
|
||||
|
||||
## What I missed
|
||||
|
||||
### Economic Chaos
|
||||
|
||||
- Tariffs
|
||||
- Export controls
|
||||
- Dropping regulatory enforcement
|
||||
- Mass layoffs
|
||||
|
||||
Counter-acting forces
|
||||
|
||||
- The Fed has stayed largely independent
|
||||
|
||||
### Crackdown on Facts and Dissenting Opinions
|
||||
|
||||
- CBS capitulation
|
||||
- Trump's war against any who disagree
|
||||
- Firing of BLS director over poor jobs numbers
|
||||
- Rewriting of federal information to align with ideological goals
|
||||
- Installing more partisan oversight in agencies
|
||||
- Attacks on universities and funding
|
||||
- De-funding of CPB
|
||||
- RFK and the health services
|
||||
|
||||
### Decreased rule of law
|
||||
|
||||
- Pardons
|
||||
- Dropping of cases
|
||||
- Bribes
|
||||
- Extortion law suits, e.g. CBS.
|
||||
- Likely illegal firing of appointees
|
||||
36
src/content/blog/drafts/vibe-coding-debt.md
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
title: "5 Types of AI Coding Tech Debt"
|
||||
description: "AI coding assistants, or even vibe-coding agents, can create a lot of code very quickly and get features done fast. However, there are a few common ways in which vibe-coding projects tend to go off the rails and end up mired in tech-debt hell. The AI doesn't prevent you from accumulating tech-debt, in fact it allows you to create more of it more quickly. I go over a few common issues with AI code that causes it to lose it's luster on large or long projects."
|
||||
---
|
||||
|
||||
1. Concepts
|
||||
1. ROI
|
||||
1. Refer to the Glyph's article.
|
||||
1. Net-positive and net-negative programmers
|
||||
1. Simplicity and Complexity
|
||||
1. Transparency
|
||||
1. Some of the ways AI fails
|
||||
1. Testing
|
||||
1. Debugging and logging
|
||||
1. Libraries and common code
|
||||
1. Performance
|
||||
1. Abstract structure
|
||||
1. Real-world/Domain modeling
|
||||
1. How do we fix it?
|
||||
1. Functional tests
|
||||
2. Enforce good debugging practices
|
||||
3. Build your own libraries
|
||||
4. Build real-world performance tests
|
||||
5. You gotta know how to design it
|
||||
|
||||
### Domain Modeling
|
||||
|
||||
While the AI can be very good at getting some code to run. It's like having an assistant who lives in another country that you only interact with through chat. Not even another country. It's like an assistant who was born in a fallout shelter and has only ever interacted with the world through a text terminal. Their entire life they have been sitting, hunched over a black screen with green text reading and reading to learn everything they can. But it's also pitch black down their. They've never _seen_ anything.
|
||||
|
||||
You have to tell them everything you want them to know about your specific case. Let's say you are modeling some sort of supply chain logistics. They've never actually worked on a supply chain. They've never seen a ship. They've never been annoyed by a late order or a project falling behind due to a shipping mishap. All they know is what they have read.
|
||||
|
||||
More than that, they have never read anything about your specific case. They only know what you have told them. You might think that this is at least as good as an intern, but even that intern sits in on meetings, chats with people in the halls, and observes the company functioning around them. They also have an intuitive grasp of things like space and time. They may have seen the ships sitting out in the harbor and realize just how hard those are to turn around.
|
||||
|
||||
The AI doesn't have any of that. It only knows what it's read. You have to tell it _everthing_ it needs to know in order to write code that corresponds to whatever problem you want to solve.
|
||||
|
||||
AI sucks at domain modeling because it has no understanding of the domain. All it can do is imitate models it's read about that might sound similar.
|
||||
27
src/content/blog/forge-of-god-greg-bear.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "The Forge of God by Greg Bear"
|
||||
description: "A short review"
|
||||
pubDate: "Jul 18 2025"
|
||||
---
|
||||
|
||||
[The Forge of God](https://en.wikipedia.org/wiki/The_Forge_of_God) by [Greg Bear](https://en.wikipedia.org/wiki/Greg_Bear) is a bit of an old book to be reviewing now. It was first published in 1987 and it's 2025 now. Did I pick it up without realizing this? Was a stricken with an acute case of nostalgia? Was I trapped in an abandoned house with nothing else to read? Dear reader, I assure you that nothing so exciting happened. I simply saw it on my shelf and decided to read it. I don't need any other reason that that.
|
||||
|
||||
As an aside, this is one of the values of having a shelf of books. I was at home one evening, I wanted something to read, and there it was. I didn't have to go find something to download onto my Kindle. I didn't have to even boot up my phone. I had an honest-to-god paper-bound book right there to choose from. I will always keep good books around for just such an occasion.
|
||||
|
||||
I probably first read the book about 20 years ago. That may or may not have been when I picked up this copy. I had a short Greg Bear kick where I read a many of his novels. It had been long enough that I had completely forgotten the plot. In fact, I picked this up thinking it was going to be a different novel (Eon) and it took me a few dozen pages to realize that I was reading something I had little to no recollection of. What's better than reading a good book? Getting to read it twice!
|
||||
|
||||
Many Sci-Fi novels like to take an established world or trope and tweak it just a bit. What if we had an epic battle of good and evil, but in space? You'd get Star Wars. What if you lived forever and could travel around anywhere in time and space? Doctor Who. What if we could clone animals from fossilized DNA? Jurassic Park. It's a fun little thought experiment to think how things would be if something were familiar but a bit different.
|
||||
|
||||
That's not what Greg Bear does at all. What I love about his novels is that they take a premise and follow it out beyond the logical conclusion. It leads you somewhere uncomfortable and alien. But that's exactly what the universe is. It's easy to imagine something familiar. It's a challenge to the writer and reader to imagine something unfamiliar.
|
||||
|
||||
The Forge of God addresses the usual trope of first contact with aliens. This has been rehashed so many times. I recently watched Arrival and it's a pretty predictable plot where the aliens arrive, there's a struggle to communicate, then the aliens impart a gift and leave and we are left with the impression that humanity is young but has much potential. The Forge of God asks, "what if the aliens were going to destroy the planet and there was nothing we could do about it?"
|
||||
|
||||
It's a frightening prospect, but it's real. Rogue asteroids, climate change, solar flares, the reality is that we have very little control over the world around us. There as many forces that are bigger and more powerful than anything that we can deal with. And that's even if we could all band together as a planet to defend ourselves, which seems unlikely given the state of world politics.
|
||||
|
||||
The book has an ensemble cast that has to confront their own powerlessness in the large and must embrace their power in the small. It is ultimately an uplifting tale about how humans can continue to find their own meaning, purpose and agency even in the face of so many things out of our control. And that's really an honest message.
|
||||
|
||||
Many stories want you to feel good in the end about how we'd all band together and win and everything will be okay. Life isn't like that. We are each one person moving through a vast, powerful, dangerous and wondrous world. There will be many things that we have to accept and it's up to us to figure out how we live, hope and dream within that framework.
|
||||
|
||||
That's what I see as the main theme of this book. It's one that exists in a lot of Greg Bear's work and I think it's what makes him such a compelling author.
|
||||
|
||||
And just as a note, the book mostly holds up after almost four decades. Sure, we have a lot more computing power now, but all the characters are still believable and relatable.
|
||||
55
src/content/blog/i-forgot-to-use-ai.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: "I Forgot to Use AI On My Latest Project"
|
||||
description: "I forgot to use an LLM to assist on my latest coding project. I probably set the progress of humanity back many hours. But hey, I learned something along the way. What did I learn? That I'd rather not use an LLM on this project."
|
||||
pubDate: "Jun 26 2025"
|
||||
---
|
||||
|
||||
I forgot to use AI on my last project. All the tech bros are laughing at me right now. I'm sure in the time I wasted I could have launched a cloud-based passive-income app, but now I'll just have fun staying poor. I'll cry myself to sleep right after I get the satisfaction of learning something new and building something with my own mind.
|
||||
|
||||
## Building something new just like everything else
|
||||
|
||||
I finally decided that I should have a blog. Yes, here we are in 2025, approximately 20 years after the blog craze started and about 10 years after it ended. What better time to start a blog than in an era when content is the most plentiful and simultaneously harder to find and discover?
|
||||
|
||||
It's a perfect time because it's a perfect time for me.
|
||||
|
||||
I've spent the last two decades mostly working in corporate tech. All my energy and productivity has been directed into those corporations. I've written so many detailed design docs, posted many impassioned Slack memos, carefully hand-crafted status reports.
|
||||
|
||||
But guess what? No one really cared. A few years later they were lost in the depths of Google Drive or the Slack archives, waiting for some new employee to stumble upon them when desperately searching for an answer to how things got the way they are. Did they really reach anyone? Did they ever really affect anyone? I would like to think so, but they never had a life of their own. They were never durable, they never lasted.
|
||||
|
||||
I have a lot more creative energy now that I've left my corporate life behind. It's energy I can start directing out into the world instead of inward towards an organization. I'm going to start creating things that I think are meaningful. They will be meaningful to me and hopefully someone else can get some value from them as well.
|
||||
|
||||
## How to build a blog in 2025
|
||||
|
||||
I'm sure the best answer for 99% of people starting a blog is to throw up a Medium or Substack page. I like to do things differently.
|
||||
|
||||
I like to really understand and own my tools. This gives me a greater level of control, but really it just brings me joy. There's a pride that I can take in carefully crafting my solution. There's a joy to understanding all the choices that went into it. Sure, I could take a template off the shelf and slap it on an existing solution. It would probably be good enough, but I wouldn't know a single thing about it. Why did they choose that font or margins? Why does it load data in this way or that? Why is it so slow?
|
||||
|
||||
It's been a while since I did any heavy front-end work. I've spent the last year or so doing back-end work in Rust, so I'm a little rusty on my CSS flex-box and grid syntax. It was time to get back to my roots and get online.
|
||||
|
||||
I've heard some good things about Astro, so I decided to give that one try. I also have to figure out if this Tailwind thing is worth it. I'll also have to remember a bunch of HTML, CSS, and some TypeScript.
|
||||
|
||||
## Time to Learn
|
||||
|
||||
So now I'm ready to sit down and get this started. I _could_ set up an agentic LLM and ask it to set it up for me. I _could_ ask it to explain what it's doing. I _could_ ask it to help me think through my decisions.
|
||||
|
||||
Or I could just RTFM.
|
||||
|
||||
Or I could just start building things.
|
||||
|
||||
So that's what I did. I didn't even reach for the AI.
|
||||
|
||||
I wanted to _understand_ what I was doing. That meant I wanted to have documentation open, I wanted to read a few examples, but most of all I wanted to play with it. I understand things best when I can manipulate them with my own two hands. (My own keyboard? Editor? You get the idea.)
|
||||
|
||||
So I sit down and I try a few things. I load it up in my browser. I try a few other things. I play with it. I get something working, but maybe that's not the best. Let's see if another way works too. I build and iterate. It's like a potter shaping clay. There's a feedback where I can shape the code and see the outcome and try something else. It's putty in my hands. I even looked at some of the source code because I wanted to understand exactly what was going on under the hood.
|
||||
|
||||
## Play = Learning
|
||||
|
||||
I had a lot of fun. I could have had something published faster if I stuck to some common templates and used an AI to generate the code for me, but I wouldn't have enjoyed it. I wouldn't have learned. I wouldn't know anything new.
|
||||
|
||||
Now I'm equipped so that the next one I build will be even better. When something breaks I'll know why and I'll know exactly how to fix it. I understand the code much more deeply than if something had generated it for me. I have a mental model of how all the little pieces fit together.
|
||||
|
||||
That's something the AI can't give me. LLMs are inherently conservative and reinforcing. They are built to tell you the most likely next thing given what you told them before. They are built to agree with you and continue your line of thought, not challenge it.
|
||||
|
||||
The only way to really learn is to try things. It requires failing. It requires playing with it, poking it and seeing which combination of blocks falls over and which stays standing. You'll never build an intuition if you don't build it yourself.
|
||||
|
||||
My goal was never something an LLM could help me with, and that means I wasn't missing out on anything.
|
||||
9
src/content/page/about.md
Normal file
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: About
|
||||
---
|
||||
|
||||
Hi! I'm Drew, but I also go by `Periodic` in most online spaces. I am a human being who exists both in meatspace and cyberspace.
|
||||
|
||||
I do full stack web-development: A little front-end, a little back-end, database optimization, infrastructure automation, the whole deal.
|
||||
|
||||
I have been a software developer from a young age and have spent two decades in the tech industry. I've done freelance, start-ups (Asana, Fossa) and some big corps (Google, Visa).
|
||||
73
src/content/page/values.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
title: Values
|
||||
---
|
||||
|
||||
I am doing an experiment with trying to clearly define my values so that I can
|
||||
know when I am living up to them.
|
||||
|
||||
The psychologist [Carl Rogers](https://en.wikipedia.org/wiki/Carl_Rogers)
|
||||
described the idea of congruence/incongruence. It's roughly the difference
|
||||
between your lived self and your ideal self. It's the difference between "I
|
||||
am" and "I should". Incongruence, a large gap between the two, leads to a
|
||||
tension between what you are doing and what you think you should be doing. I
|
||||
believe that everyone will be happier and experience more of their potential if
|
||||
they are living and growing in line with their ideals.
|
||||
|
||||
## My Values
|
||||
|
||||
### Equity
|
||||
|
||||
- Everyone should have the means to live a healthy and meaningful life.
|
||||
- This applies to people now and in the future.
|
||||
- Everyone should have a fair voice in decisions that affect them.
|
||||
|
||||
### Integrity
|
||||
|
||||
- Take responsibility for your own actions.
|
||||
- Hold others accountable and responsible for their actions.
|
||||
- Give honest feedback.
|
||||
- Take the time to do good work.
|
||||
- Be proud of your work.
|
||||
- Be your authentic self.
|
||||
|
||||
### Sustainability and long-term thinking
|
||||
|
||||
- Think not about the short- or long-term, but about the equilibrium, the forever-term.
|
||||
- Do not sacrifice the future for the present.
|
||||
- Do not sacrifice the present for the future.
|
||||
|
||||
### Cooperation over competition
|
||||
|
||||
- We build better when we work together.
|
||||
- We can challenge each other without having to compete.
|
||||
- Resources should not be scarce.
|
||||
- Share information, materials and goods.
|
||||
- Hold others responsible for sharing alike.
|
||||
|
||||
### Self-improvement
|
||||
|
||||
- Be curious and motivated to learn and understand.
|
||||
- Be open to feedback and learning.
|
||||
- Learn new things.
|
||||
- Seek to understand.
|
||||
- Embrace failure as an opportunity to learn.
|
||||
- Take the time to review and retrospect.
|
||||
- Help others learn and grow by giving them resources, training and feedback.
|
||||
|
||||
### Self-reliance
|
||||
|
||||
- Enable everyone to work with minimal interruption by reducing the dependence on each other.
|
||||
- We should work together but not be dependent on one another.
|
||||
- Many strong units weakly coupled are more resilient than rigid or hierarchical structures.
|
||||
- Invest in your health and stability.
|
||||
|
||||
## Corollaries
|
||||
|
||||
### Environmentalism
|
||||
|
||||
- We must preserve our planet so that future people will have at least the same opportunities we currently do.
|
||||
|
||||
### Anti-capitalism
|
||||
|
||||
- Modern capitalism is a system that is built on and perpetuates inequity.
|
||||
- Modern capitalism creates and exploits dependency.
|
||||
40
src/layouts/BlogPost.astro
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
import RootLayout from '../layouts/RootLayout.astro';
|
||||
import FormattedDate from '../components/FormattedDate.astro';
|
||||
import NotchedBox from '../components/NotchedBox.astro';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
type Props = CollectionEntry<'blog'>['data'];
|
||||
|
||||
const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
|
||||
---
|
||||
|
||||
<RootLayout title={title} description={description}>
|
||||
<main>
|
||||
<NotchedBox fillNotches="left">
|
||||
<article>
|
||||
<div class="hero-image">
|
||||
{heroImage && <Image width={1020} height={510} src={heroImage} alt="" />}
|
||||
</div>
|
||||
<div class="prose">
|
||||
<div class="title">
|
||||
<div class="date">
|
||||
{ pubDate && <FormattedDate date={pubDate} /> }
|
||||
{
|
||||
updatedDate && (
|
||||
<div class="last-updated-on">
|
||||
Last updated on <FormattedDate date={updatedDate} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<h1>{title}</h1>
|
||||
<hr />
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</article>
|
||||
</NotchedBox>
|
||||
</main>
|
||||
</RootLayout>
|
||||
94
src/layouts/RootLayout.astro
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
import BaseHead from '../components/BaseHead.astro';
|
||||
import Header from '../components/Header.astro';
|
||||
import Footer from '../components/Footer.astro';
|
||||
import Sidebar from '../components/Sidebar.astro';
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
const { title = SITE_TITLE, description = SITE_DESCRIPTION } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<BaseHead title={title} description={description} />
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<Header />
|
||||
</div>
|
||||
<div class="sidebar">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<main>
|
||||
<slot>
|
||||
</main>
|
||||
<div class="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</body>
|
||||
<style>
|
||||
@media (max-width: 800px) {
|
||||
body {
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"main"
|
||||
"footer";
|
||||
grid-template-columns: 1fr;
|
||||
margin: 1rem 0.5rem;
|
||||
}
|
||||
.header {
|
||||
display: block;
|
||||
}
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
body {
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"sidebar main"
|
||||
"footer footer";
|
||||
grid-template-columns: 20rem 1fr;
|
||||
margin: 2rem 1rem;
|
||||
max-width: 1600px;
|
||||
}
|
||||
.header {
|
||||
display: none;
|
||||
}
|
||||
.sidebar {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
body {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
grid-area: sidebar;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-area: main;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
justify-content: stretch;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
</style>
|
||||
</html>
|
||||
20
src/pages/about.astro
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
import RootLayout from '../layouts/RootLayout.astro';
|
||||
import NotchedBox from '../components/NotchedBox.astro';
|
||||
import { getEntry, render } from 'astro:content';
|
||||
|
||||
const about = await getEntry('page', 'about');
|
||||
|
||||
if (!about) {
|
||||
throw new Error('Page not found');
|
||||
}
|
||||
|
||||
const { Content } = await render(about);
|
||||
---
|
||||
|
||||
<RootLayout>
|
||||
<NotchedBox fillNotches="left">
|
||||
<h1>{about.data.title}</h1>
|
||||
<Content />
|
||||
</NotchedBox>
|
||||
</RootLayout>
|
||||
21
src/pages/blog/[...slug].astro
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
import { type CollectionEntry, getCollection } from 'astro:content';
|
||||
import BlogPost from '../../layouts/BlogPost.astro';
|
||||
import { render } from 'astro:content';
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const posts = await getCollection('blog');
|
||||
return posts.map((post) => ({
|
||||
params: { slug: post.id },
|
||||
props: post,
|
||||
}));
|
||||
}
|
||||
type Props = CollectionEntry<'blog'>;
|
||||
|
||||
const post = Astro.props;
|
||||
const { Content } = await render(post);
|
||||
---
|
||||
|
||||
<BlogPost {...post.data}>
|
||||
<Content />
|
||||
</BlogPost>
|
||||
85
src/pages/blog/index.astro
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
import NotchedBox from '../../components/NotchedBox.astro';
|
||||
import RootLayout from '../../layouts/RootLayout.astro';
|
||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
||||
import FormattedDate from '../../components/FormattedDate.astro';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
function publishedOnly(p: CollectionEntry<'blog'>): p is (CollectionEntry<'blog'> & { data: { pubDate: Date }}) {
|
||||
return p.data.pubDate !== undefined;
|
||||
}
|
||||
|
||||
const posts = (await getCollection('blog', publishedOnly))
|
||||
.sort(
|
||||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
||||
);
|
||||
---
|
||||
|
||||
<RootLayout>
|
||||
<style>
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
|
||||
li {
|
||||
/* Required since flex won't render list-elements correctly. */
|
||||
display: block;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
.title, .description {
|
||||
color: var(--color-light-text);
|
||||
}
|
||||
.date {
|
||||
color: var(--color-gray);
|
||||
}
|
||||
.entry {
|
||||
padding: 0.5em;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a:hover .title {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<section>
|
||||
<ul>
|
||||
{
|
||||
posts.map((post) => (
|
||||
<li>
|
||||
<a href={`/blog/${post.id}/`}>
|
||||
<NotchedBox fillNotches="left">
|
||||
<div class="entry">
|
||||
{post.data.heroImage && (
|
||||
<Image width={720} height={360} src={post.data.heroImage} alt="" />
|
||||
)}
|
||||
<h4 class="title">{post.data.title}</h4>
|
||||
<p class="date">
|
||||
<FormattedDate date={post.data.pubDate} />
|
||||
</p>
|
||||
{ post.data.description &&
|
||||
<p class="description">
|
||||
{post.data.description}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</NotchedBox>
|
||||
</a>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
</RootLayout>
|
||||
26
src/pages/index.astro
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
import NotchedBox from '../components/NotchedBox.astro';
|
||||
import Blazestar from '../components/Blazestar.astro';
|
||||
import RootLayout from '../layouts/RootLayout.astro';
|
||||
---
|
||||
|
||||
<RootLayout>
|
||||
<NotchedBox fillNotches="left">
|
||||
<h1>Welcome to <Blazestar /></h1>
|
||||
<p>
|
||||
This is the personal homepage for <code>Periodic</code>, a human who spends a lot of time with technology. I believe that our virtual lives are as valuable as our physical ones and that the two are inextricably linked.
|
||||
</p>
|
||||
<p>
|
||||
Topics that may be covered (in no particular order):
|
||||
<ul>
|
||||
<li>Programming</li>
|
||||
<li>Software Engineering</li>
|
||||
<li>Start-ups</li>
|
||||
<li>Cooperative Economies</li>
|
||||
<li>Sustainability</li>
|
||||
<li>Astronomy</li>
|
||||
<li>Nature</li>
|
||||
</ul>
|
||||
</p>
|
||||
</NotchedBox>
|
||||
</RootLayout>
|
||||
25
src/pages/rss.xml.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import rss from "@astrojs/rss";
|
||||
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import type { AstroUserConfig } from "astro";
|
||||
|
||||
function publishedOnly(
|
||||
p: CollectionEntry<"blog">,
|
||||
): p is CollectionEntry<"blog"> & { data: { pubDate: Date } } {
|
||||
return p.data.pubDate !== undefined;
|
||||
}
|
||||
|
||||
export async function GET(context: AstroUserConfig) {
|
||||
const posts = (await getCollection("blog", publishedOnly)).sort(
|
||||
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
|
||||
);
|
||||
return rss({
|
||||
title: SITE_TITLE,
|
||||
description: SITE_DESCRIPTION,
|
||||
site: context.site as string,
|
||||
items: posts.map((post) => ({
|
||||
...post.data,
|
||||
link: `/blog/${post.id}/`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
218
src/styles/global.css
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
The CSS in this style tag is based off of Bear Blog's default CSS.
|
||||
https://github.com/HermanMartinus/bearblog/blob/297026a877bc2ab2b3bdfbd6b9f7961c350917dd/templates/styles/blog/default.css
|
||||
License MIT: https://github.com/HermanMartinus/bearblog/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
:root {
|
||||
--color-gold: #e99c1a;
|
||||
--color-space-blue: #1c2329;
|
||||
--color-space-blue-light: #2a3138;
|
||||
--color-gray: #7f8c8d;
|
||||
--color-light-text: #dcdcc6;
|
||||
--color-red: #d75422;
|
||||
|
||||
--color-accent: var(--color-red);
|
||||
--background: var(--color-space-blue);
|
||||
--background-light: var(--color-space-blue-light);
|
||||
|
||||
--font-size-sm: calc(var(--font-size-md) / 1.2);
|
||||
--font-size-md: 20px;
|
||||
--font-size-lg: calc(var(--font-size-md) * 1.2);
|
||||
--font-size-xl: calc(var(--font-size-lg) * 1.2);
|
||||
--font-size-2xl: calc(var(--font-size-xl) * 1.2);
|
||||
--font-size-3xl: calc(var(--font-size-2xl) * 1.2);
|
||||
--font-size-4xl: calc(var(--font-size-3xl) * 1.2);
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson";
|
||||
src: url("/fonts/atkinson-regular.woff") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson";
|
||||
src: url("/fonts/atkinson-bold.woff") format("woff");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src:
|
||||
url("/fonts/FiraCode-Light.woff2") format("woff2"),
|
||||
url("/fonts/FiraCode-Light.woff") format("woff");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src:
|
||||
url("/fonts/FiraCode-Regular.woff2") format("woff2"),
|
||||
url("/fonts/FiraCode-Regular.woff") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src:
|
||||
url("/fonts/FiraCode-Medium.woff2") format("woff2"),
|
||||
url("/fonts/FiraCode-Medium.woff") format("woff");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src:
|
||||
url("/fonts/FiraCode-SemiBold.woff2") format("woff2"),
|
||||
url("/fonts/FiraCode-SemiBold.woff") format("woff");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
src:
|
||||
url("/fonts/FiraCode-Bold.woff2") format("woff2"),
|
||||
url("/fonts/FiraCode-Bold.woff") format("woff");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code VF";
|
||||
src:
|
||||
url("/fonts/FiraCode-VF.woff2") format("woff2-variations"),
|
||||
url("/fonts/FiraCode-VF.woff") format("woff-variations");
|
||||
/* font-weight requires a range: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide#Using_a_variable_font_font-face_changes */
|
||||
font-weight: 300 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Atkinson", sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
color: var(--color-light-text);
|
||||
font-size: var(--font-size-md);
|
||||
line-height: 1.7;
|
||||
background-color: var(--color-space-blue);
|
||||
}
|
||||
|
||||
main {
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
/* Progressive 1.2x scaling from the base */
|
||||
h1 {
|
||||
font-size: var(--font-size-4xl);
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--font-size-3xl);
|
||||
}
|
||||
h3 {
|
||||
font-size: var(--font-size-2xl);
|
||||
}
|
||||
h4 {
|
||||
font-size: var(--font-size-xl);
|
||||
}
|
||||
h5 {
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
strong,
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
a {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.prose p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
code {
|
||||
font-family: "Fira Code", monospace;
|
||||
background-color: var(--background-light);
|
||||
padding: 0.1em 0.2em;
|
||||
}
|
||||
pre {
|
||||
padding: 1.5em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
pre > code {
|
||||
all: unset;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 0 0 0 20px;
|
||||
margin: 0px;
|
||||
font-size: 1.333em;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid rgb(var(--gray-light));
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
body {
|
||||
font-size: 18px;
|
||||
}
|
||||
main {
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
|
||||
clip: rect(1px 1px 1px 1px);
|
||||
/* maybe deprecated but we need to support legacy browsers */
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
/* modern browsers, clip-path works inwards from each corner */
|
||||
clip-path: inset(50%);
|
||||
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
|
||||
white-space: nowrap;
|
||||
}
|
||||
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
}
|
||||
}
|
||||