Compare commits

..

26 Commits

Author SHA1 Message Date
08431fd6c6 Adds white token for thumbnail 2026-02-16 18:04:44 -08:00
7972692eff Adds black thumbnail token 2026-02-16 18:02:28 -08:00
e1b290d6bc Adds colored greater-good tokens 2026-02-16 17:36:16 -08:00
e769bbebff Adds marker tokens 2026-02-16 17:20:17 -08:00
cebf8b08dc Some draft work on vibe-coding-errors 2025-09-25 17:35:14 -07:00
551a9d286d Moves unpublished articles into a drafts folder. 2025-09-25 17:28:35 -07:00
54419e3808 Hides unpublished articles from the RSS 2025-09-15 16:01:17 -07:00
89aec92b2e Fixes compliation error on checking-in-on-2025 2025-09-15 15:54:01 -07:00
80082864a7 Fixes site root URL 2025-09-15 15:48:50 -07:00
3a26219434 Adds stub for vibe-coding article 2025-09-15 15:47:32 -07:00
a2e315413f Drafts for values and Checking in on 2025 2025-08-09 16:23:34 -07:00
89533e006f Fix layout for multiple blog entries. Adds max width. 2025-07-31 17:05:28 -07:00
8a73044c19 Fix pub gate on Forge of God 2025-07-31 15:29:18 -07:00
75c9322fd0 Fixes error due to missing date 2025-07-31 14:59:14 -07:00
96ee56fc1f Fix foooter stats 2025-07-31 14:51:11 -07:00
ae109330d9 Adds Forge of God Review 2025-07-18 20:33:07 -07:00
e73005968d Adds domain to the path for counter requests. 2025-07-10 15:29:09 -07:00
80ea47644c Adds test counter to footer. 2025-07-10 15:03:09 -07:00
f82fe9206f [I Forgot to use AI] Revisions. Publish. 2025-06-26 15:05:35 -07:00
b93123a8c9 Minor post fixes. Makes posts publishable with a date 2025-06-26 14:57:34 -07:00
6a97302539 Fleshes out 'I Forgot to Use AI' 2025-06-26 13:55:32 -07:00
1e35719c3a Fixes responsiveness for small screen sizes 2025-06-23 20:21:22 -07:00
9dcf57a730 Sets up about and home pages. 2025-06-22 10:37:21 -07:00
5a17ff266d Fix some font scaling 2025-06-21 21:51:15 -07:00
0daac5bba4 Basically finishes styling 2025-06-21 21:40:40 -07:00
2ad5e29cad Adds outline for my first post. 2025-06-21 20:28:15 -07:00
59 changed files with 928 additions and 682 deletions

8
.markdownlint.yaml Normal file
View File

@@ -0,0 +1,8 @@
# Indentation
MD007:
indent: 2
# Maximum multiple consecutive blank lines
MD012:
maximum: 2
# Disable the line-length warnings.
MD013: false

View File

@@ -7,11 +7,10 @@ import react from "@astrojs/react";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
site: "https://example.com", site: "https://www.blazestar.net",
integrations: [mdx(), sitemap(), react()], integrations: [mdx(), sitemap(), react()],
vite: { vite: {
plugins: [], plugins: [],
}, },
}); });

199
package-lock.json generated
View File

@@ -3196,6 +3196,20 @@
"integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/copy-anything": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
"integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"is-what": "^3.14.1"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/cross-fetch": { "node_modules/cross-fetch": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
@@ -3403,6 +3417,20 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
"integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"prr": "~1.0.1"
},
"bin": {
"errno": "cli.js"
}
},
"node_modules/es-module-lexer": { "node_modules/es-module-lexer": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
@@ -4016,6 +4044,34 @@
"integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/image-size": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
"integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/import-meta-resolve": { "node_modules/import-meta-resolve": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@@ -4145,6 +4201,14 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-what": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
"integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/is-wsl": { "node_modules/is-wsl": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
@@ -4220,6 +4284,45 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/less": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/less/-/less-4.3.0.tgz",
"integrity": "sha512-X9RyH9fvemArzfdP8Pi3irr7lor2Ok4rOttDXBhlwDg+wKQsXOXgHWduAJE1EsF7JJx0w0bcO6BC6tCKKYnXKA==",
"license": "Apache-2.0",
"optional": true,
"peer": true,
"dependencies": {
"copy-anything": "^2.0.1",
"parse-node-version": "^1.0.1",
"tslib": "^2.3.0"
},
"bin": {
"lessc": "bin/lessc"
},
"engines": {
"node": ">=14"
},
"optionalDependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"needle": "^3.1.0",
"source-map": "~0.6.0"
}
},
"node_modules/less/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/lightningcss": { "node_modules/lightningcss": {
"version": "1.30.1", "version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
@@ -4487,6 +4590,32 @@
"source-map-js": "^1.2.0" "source-map-js": "^1.2.0"
} }
}, },
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"pify": "^4.0.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/markdown-extensions": { "node_modules/markdown-extensions": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz",
@@ -5534,6 +5663,20 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"optional": true,
"peer": true,
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/minipass": { "node_modules/minipass": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -5603,6 +5746,24 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/needle": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
"integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/neotraverse": { "node_modules/neotraverse": {
"version": "0.6.18", "version": "0.6.18",
"resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz",
@@ -5804,6 +5965,17 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/parse-node-version": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
"integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/parse5": { "node_modules/parse5": {
"version": "7.3.0", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
@@ -5834,6 +6006,17 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.4", "version": "8.5.4",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
@@ -5903,6 +6086,14 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
"integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/radix3": { "node_modules/radix3": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz",
@@ -6323,6 +6514,14 @@
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/sax": { "node_modules/sax": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -3,7 +3,6 @@
// all pages through the use of the <BaseHead /> component. // all pages through the use of the <BaseHead /> component.
import '../styles/global.css'; import '../styles/global.css';
import { SITE_TITLE } from '../consts'; import { SITE_TITLE } from '../consts';
import FallbackImage from '../assets/blog-placeholder-1.jpg';
import type { ImageMetadata } from 'astro'; import type { ImageMetadata } from 'astro';
interface Props { interface Props {
@@ -14,7 +13,7 @@ interface Props {
const canonicalURL = new URL(Astro.url.pathname, Astro.site); const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = FallbackImage } = Astro.props; const { title, description, image } = Astro.props;
--- ---
<!-- Global Metadata --> <!-- Global Metadata -->
@@ -47,11 +46,15 @@ const { title, description, image = FallbackImage } = Astro.props;
<meta property="og:url" content={Astro.url} /> <meta property="og:url" content={Astro.url} />
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:description" content={description} /> <meta property="og:description" content={description} />
{ image &&
<meta property="og:image" content={new URL(image.src, Astro.url)} /> <meta property="og:image" content={new URL(image.src, Astro.url)} />
}
<!-- Twitter --> <!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" /> <meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={Astro.url} /> <meta property="twitter:url" content={Astro.url} />
<meta property="twitter:title" content={title} /> <meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} /> <meta property="twitter:description" content={description} />
{ image &&
<meta property="twitter:image" content={new URL(image.src, Astro.url)} /> <meta property="twitter:image" content={new URL(image.src, Astro.url)} />
}

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

View File

@@ -4,50 +4,20 @@ const today = new Date();
<footer> <footer>
&copy; {today.getFullYear()} Periodic. All rights reserved. &copy; {today.getFullYear()} Periodic. All rights reserved.
<div class="social-links"> <script>
<a href="https://m.webtoo.ls/@astro" target="_blank"> window.goatcounter = {
<span class="sr-only">Follow Periodic on Mastodon</span> path: (path) => `${location.host}${path}`,
<svg };
viewBox="0 0 16 16" </script>
aria-hidden="true" <script
width="32" data-goatcounter="https://goatcounter.blazestar.net/count"
height="32" async
astro-icon="social/mastodon" src="https://goatcounter.blazestar.net/count.js"></script>
><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" astro-icon="social/github"
><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>
</footer> </footer>
<style> <style>
footer { footer {
padding: 2em 1em 6em 1em; padding: 2em 1em 6em 1em;
background: linear-gradient(var(--gray-gradient)) no-repeat;
color: rgb(var(--gray));
text-align: center; text-align: center;
} color: var(--color-gray);
.social-links {
display: flex;
justify-content: center;
gap: 1em;
margin-top: 1em;
}
.social-links a {
text-decoration: none;
color: rgb(var(--gray));
}
.social-links a:hover {
color: rgb(var(--gray-dark));
} }
</style> </style>

View File

@@ -15,3 +15,9 @@ const { date } = Astro.props;
}) })
} }
</time> </time>
<style>
time {
font-family: "Fira Code", monospace;
font-size: var(--font-size-sm);
}
</style>

View File

@@ -1,11 +1,11 @@
--- ---
import HeaderLink from './HeaderLink.astro'; import HeaderLink from './HeaderLink.astro';
import { SITE_TITLE } from '../consts'; import Blazestar from './Blazestar.astro';
--- ---
<header> <header>
<nav> <nav>
<h2><a href="/">{SITE_TITLE}</a></h2> <h1><Blazestar /></h1>
<div class="internal-links"> <div class="internal-links">
<HeaderLink href="/">Home</HeaderLink> <HeaderLink href="/">Home</HeaderLink>
<HeaderLink href="/blog">Blog</HeaderLink> <HeaderLink href="/blog">Blog</HeaderLink>
@@ -38,15 +38,15 @@ import { SITE_TITLE } from '../consts';
color: var(--color-light-text); color: var(--color-light-text);
margin: 0; margin: 0;
padding: 0; padding: 0;
box-shadow: 0 2px 8px rgba(var(--black), 5%);
}
h2 {
margin: 0;
font-size: 1em;
} }
h2 a, h1 {
h2 a.active { margin-bottom: 0.5em;
font-size: 2em;
}
h1 a,
h1 a.active {
text-decoration: none; text-decoration: none;
} }
nav { nav {
@@ -56,13 +56,17 @@ import { SITE_TITLE } from '../consts';
} }
nav a { nav a {
padding: 1em 0.5em; padding: 1em 0.5em;
color: var(--black); color: var(--color-light-text);
border-bottom: 4px solid transparent; border-bottom: 4px solid transparent;
text-decoration: none; text-decoration: none;
&:hover {
color: var(--color-accent);
} }
nav a.active { &.active {
text-decoration: none; text-decoration: none;
border-bottom-color: var(--accent); border-bottom-color: var(--color-accent);
}
} }
.social-links, .social-links,
.social-links a { .social-links a {
@@ -73,4 +77,9 @@ import { SITE_TITLE } from '../consts';
display: none; display: none;
} }
} }
@media (max-width: 520px) {
nav {
flex-direction: column;
}
}
</style> </style>

View File

@@ -56,7 +56,6 @@ const { fillNotches = "none" } = Astro.props;
height: calc(100% - 16px); height: calc(100% - 16px);
width: calc(100% - 40px); width: calc(100% - 40px);
background-color: var(--color-space-blue); background-color: var(--color-space-blue);
color: var(--light-gray);
padding: 8px 20px; padding: 8px 20px;
clip-path: polygon( clip-path: polygon(
0 16px, 0 16px,

View File

@@ -1,24 +0,0 @@
.container {
padding: 1px;
background-color: var(--color-gold);
clip-path: polygon(
0 0,
100% 0,
100% calc(100% - 16px),
calc(100% - 16px) 100%,
0 100%
);
}
.content {
background-color: var(--color-space-blue);
color: var(--light-gray);
padding: 16px 20px;
clip-path: polygon(
0 16px,
16px 0,
100% 0,
100% calc(100% - 16px),
calc(100% - 16px) 100%,
0 100%
);
}

View File

@@ -1,11 +1,11 @@
--- ---
import SidebarLink from './SidebarLink.astro'; import SidebarLink from './SidebarLink.astro';
import { SITE_TITLE } from '../consts'; import Blazestar from './Blazestar.astro';
--- ---
<header> <header>
<nav> <nav>
<h2><a href="/">{SITE_TITLE}</a></h2> <h1><Blazestar /></h1>
<div class="internal-links"> <div class="internal-links">
<SidebarLink href="/">Home</SidebarLink> <SidebarLink href="/">Home</SidebarLink>
<SidebarLink href="/blog">Blog</SidebarLink> <SidebarLink href="/blog">Blog</SidebarLink>
@@ -38,13 +38,14 @@ import { SITE_TITLE } from '../consts';
margin: 0; margin: 0;
padding: 0 1em; padding: 0 1em;
} }
h2 {
h1 {
margin-bottom: 0.5em; margin-bottom: 0.5em;
font-size: 1em; font-size: 1.5em;
} }
h2 a, h1 a,
h2 a.active { h1 a.active {
text-decoration: none; text-decoration: none;
} }
@@ -54,15 +55,20 @@ import { SITE_TITLE } from '../consts';
align-items: stretch; align-items: stretch;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 16px;
} a {
nav a { color: var(--color-light-text);
color: var(--black); border-bottom: 4px solid transparent;
text-decoration: none;
&:hover {
color: var(--color-accent);
border-bottom: 4px solid transparent; border-bottom: 4px solid transparent;
text-decoration: none; text-decoration: none;
} }
nav a.active { &.active {
text-decoration: none; text-decoration: none;
} }
}
}
.internal-links { .internal-links {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -10,16 +10,23 @@ const subpath = pathname.match(/[^\/]+/g);
const isActive = href === pathname || href === '/' + (subpath?.[0] || ''); const isActive = href === pathname || href === '/' + (subpath?.[0] || '');
--- ---
<NotchedBox fillNotches={ isActive ? "right" : "none" }>
<a href={href} class:list={[className, { active: isActive }]} {...props}> <a href={href} class:list={[className, { active: isActive }]} {...props}>
<NotchedBox fillNotches={ isActive ? "right" : "none" }>
<slot /> <slot />
</a>
</NotchedBox> </NotchedBox>
</a>
<style> <style>
a { a {
display: inline-block; display: inline-block;
text-decoration: none; text-decoration: none;
color: var(--color-light-text);
font-size: var(--font-size-lg);
} }
a.hover {
color: var(--color-gold);
}
a.active { a.active {
font-weight: bolder; font-weight: bolder;
text-decoration: underline; text-decoration: underline;

View File

@@ -1,18 +1,28 @@
import { glob } from 'astro/loaders'; import { glob } from "astro/loaders";
import { defineCollection, z } from 'astro:content'; import { defineCollection, z } from "astro:content";
const blog = defineCollection({ const blog = defineCollection({
// Load Markdown and MDX files in the `src/content/blog/` directory. // Load Markdown and MDX files in the `src/content/blog/` directory.
loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
// Type-check frontmatter using a schema // Type-check frontmatter using a schema
schema: ({ image }) => z.object({ schema: ({ image }) =>
z.object({
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
// Transform string to Date object // Transform string to Date object
pubDate: z.coerce.date(), // Presence of this value indicates that the post is published
pubDate: z.coerce.date().optional(),
updatedDate: z.coerce.date().optional(), updatedDate: z.coerce.date().optional(),
heroImage: image().optional(), heroImage: image().optional(),
}), }),
}); });
export const collections = { blog }; const page = defineCollection({
loader: glob({ base: "./src/content/page", pattern: "**/*.{md,mdx}" }),
schema: ({ image }) =>
z.object({
title: z.string(),
}),
});
export const collections = { blog, page };

View 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

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

View File

@@ -1,16 +0,0 @@
---
title: 'First post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 08 2022'
heroImage: '../../assets/blog-placeholder-3.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

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

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

View File

@@ -1,214 +0,0 @@
---
title: 'Markdown Style Guide'
description: 'Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.'
pubDate: 'Jun 19 2024'
heroImage: '../../assets/blog-placeholder-1.jpg'
---
Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.
## Headings
The following HTML `<h1>``<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest.
# H1
## H2
### H3
#### H4
##### H5
###### H6
## Paragraph
Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.
Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.
## Images
### Syntax
```markdown
![Alt text](./full/or/relative/path/of/image)
```
### Output
![blog placeholder](/blog-placeholder-about.jpg)
## Blockquotes
The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.
### Blockquote without attribution
#### Syntax
```markdown
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.
```
#### Output
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.
### Blockquote with attribution
#### Syntax
```markdown
> Don't communicate by sharing memory, share memory by communicating.<br>
> — <cite>Rob Pike[^1]</cite>
```
#### Output
> Don't communicate by sharing memory, share memory by communicating.<br>
> — <cite>Rob Pike[^1]</cite>
[^1]: The above quote is excerpted from Rob Pike's [talk](https://www.youtube.com/watch?v=PAAkCSZUG1c) during Gopherfest, November 18, 2015.
## Tables
### Syntax
```markdown
| Italics | Bold | Code |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
```
### Output
| Italics | Bold | Code |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
## Code Blocks
### Syntax
we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntax, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash
````markdown
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example HTML5 Document</title>
</head>
<body>
<p>Test</p>
</body>
</html>
```
````
### Output
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Example HTML5 Document</title>
</head>
<body>
<p>Test</p>
</body>
</html>
```
## List Types
### Ordered List
#### Syntax
```markdown
1. First item
2. Second item
3. Third item
```
#### Output
1. First item
2. Second item
3. Third item
### Unordered List
#### Syntax
```markdown
- List item
- Another item
- And another item
```
#### Output
- List item
- Another item
- And another item
### Nested list
#### Syntax
```markdown
- Fruit
- Apple
- Orange
- Banana
- Dairy
- Milk
- Cheese
```
#### Output
- Fruit
- Apple
- Orange
- Banana
- Dairy
- Milk
- Cheese
## Other Elements — abbr, sub, sup, kbd, mark
### Syntax
```markdown
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
H<sub>2</sub>O
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.
```
### Output
<abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.
H<sub>2</sub>O
X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup>
Press <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>Delete</kbd> to end the session.
Most <mark>salamanders</mark> are nocturnal, and hunt for insects, worms, and other small creatures.

View File

@@ -1,16 +0,0 @@
---
title: 'Second post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 15 2022'
heroImage: '../../assets/blog-placeholder-4.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -1,16 +0,0 @@
---
title: 'Third post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 22 2022'
heroImage: '../../assets/blog-placeholder-2.jpg'
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc. Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra massa massa ultricies mi.
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id cursus metus aliquam eleifend mi.
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.

View File

@@ -1,31 +0,0 @@
---
title: 'Using MDX'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jun 01 2024'
heroImage: '../../assets/blog-placeholder-5.jpg'
---
This theme comes with the [@astrojs/mdx](https://docs.astro.build/en/guides/integrations-guide/mdx/) integration installed and configured in your `astro.config.mjs` config file. If you prefer not to use MDX, you can disable support by removing the integration from your config file.
## Why MDX?
MDX is a special flavor of Markdown that supports embedded JavaScript & JSX syntax. This unlocks the ability to [mix JavaScript and UI Components into your Markdown content](https://docs.astro.build/en/guides/markdown-content/#mdx-features) for things like interactive charts or alerts.
If you have existing content authored in MDX, this integration will hopefully make migrating to Astro a breeze.
## Example
Here is how you import and use a UI component inside of MDX.
When you open this page in the browser, you should see the clickable button below.
import HeaderLink from '../../components/HeaderLink.astro';
<HeaderLink href="#" onclick="alert('clicked!')">
Embedded component in MDX
</HeaderLink>
## More Links
- [MDX Syntax Documentation](https://mdxjs.com/docs/what-is-mdx)
- [Astro Usage Documentation](https://docs.astro.build/en/guides/markdown-content/#markdown-and-mdx-pages)
- **Note:** [Client Directives](https://docs.astro.build/en/reference/directives-reference/#client-directives) are still required to create interactive components. Otherwise, all components in your MDX will render as static HTML (no JavaScript) by default.

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

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

View File

@@ -1,9 +1,8 @@
--- ---
import type { CollectionEntry } from 'astro:content'; import type { CollectionEntry } from 'astro:content';
import BaseHead from '../components/BaseHead.astro'; import RootLayout from '../layouts/RootLayout.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import FormattedDate from '../components/FormattedDate.astro'; import FormattedDate from '../components/FormattedDate.astro';
import NotchedBox from '../components/NotchedBox.astro';
import { Image } from 'astro:assets'; import { Image } from 'astro:assets';
type Props = CollectionEntry<'blog'>['data']; type Props = CollectionEntry<'blog'>['data'];
@@ -11,53 +10,9 @@ type Props = CollectionEntry<'blog'>['data'];
const { title, description, pubDate, updatedDate, heroImage } = Astro.props; const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
--- ---
<html lang="en"> <RootLayout title={title} description={description}>
<head>
<BaseHead title={title} description={description} />
<style>
main {
width: calc(100% - 2em);
max-width: 100%;
margin: 0;
}
.hero-image {
width: 100%;
}
.hero-image img {
display: block;
margin: 0 auto;
border-radius: 12px;
box-shadow: var(--box-shadow);
}
.prose {
width: 720px;
max-width: calc(100% - 2em);
margin: auto;
padding: 1em;
color: rgb(var(--gray-dark));
}
.title {
margin-bottom: 1em;
padding: 1em 0;
text-align: center;
line-height: 1;
}
.title h1 {
margin: 0 0 0.5em 0;
}
.date {
margin-bottom: 0.5em;
color: rgb(var(--gray));
}
.last-updated-on {
font-style: italic;
}
</style>
</head>
<body>
<Header />
<main> <main>
<NotchedBox fillNotches="left">
<article> <article>
<div class="hero-image"> <div class="hero-image">
{heroImage && <Image width={1020} height={510} src={heroImage} alt="" />} {heroImage && <Image width={1020} height={510} src={heroImage} alt="" />}
@@ -65,7 +20,7 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
<div class="prose"> <div class="prose">
<div class="title"> <div class="title">
<div class="date"> <div class="date">
<FormattedDate date={pubDate} /> { pubDate && <FormattedDate date={pubDate} /> }
{ {
updatedDate && ( updatedDate && (
<div class="last-updated-on"> <div class="last-updated-on">
@@ -80,7 +35,6 @@ const { title, description, pubDate, updatedDate, heroImage } = Astro.props;
<slot /> <slot />
</div> </div>
</article> </article>
</NotchedBox>
</main> </main>
<Footer /> </RootLayout>
</body>
</html>

View File

@@ -2,19 +2,21 @@
import BaseHead from '../components/BaseHead.astro'; import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro'; import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro'; import Footer from '../components/Footer.astro';
import NotchedBox from '../components/NotchedBox.tsx';
import Sidebar from '../components/Sidebar.astro'; import Sidebar from '../components/Sidebar.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts'; import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
interface Props { interface Props {
title?: string;
description?: string;
} }
const { title = SITE_TITLE, description = SITE_DESCRIPTION } = Astro.props;
--- ---
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} /> <BaseHead title={title} description={description} />
</head> </head>
<body> <body>
<div class="header"> <div class="header">
@@ -31,14 +33,14 @@ interface Props {
</div> </div>
</body> </body>
<style> <style>
@media (max-width: 720px) { @media (max-width: 800px) {
body { body {
grid-template-areas: grid-template-areas:
"header" "header"
"main" "main"
"footer"; "footer";
grid-template-columns: 1fr; grid-template-columns: 1fr;
margin: 1rem 0.5rem;
} }
.header { .header {
display: block; display: block;
@@ -47,13 +49,15 @@ interface Props {
display: none; display: none;
} }
} }
@media (min-width: 720px) { @media (min-width: 800px) {
body { body {
grid-template-areas: grid-template-areas:
"header header" "header header"
"sidebar main" "sidebar main"
"footer footer"; "footer footer";
grid-template-columns: 20rem 1fr; grid-template-columns: 20rem 1fr;
margin: 2rem 1rem;
max-width: 1600px;
} }
.header { .header {
display: none; display: none;
@@ -64,7 +68,6 @@ interface Props {
} }
body { body {
display: grid; display: grid;
margin: 2rem 3rem;
} }
.header { .header {

View File

@@ -1,60 +1,20 @@
--- ---
import RootLayout from '../layouts/RootLayout.astro'; import RootLayout from '../layouts/RootLayout.astro';
import NotchedBox from '../components/NotchedBox.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> <RootLayout>
<NotchedBox> <NotchedBox fillNotches="left">
<p> <h1>{about.data.title}</h1>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut <Content />
labore et dolore magna aliqua. Vitae ultricies leo integer malesuada nunc vel risus commodo
viverra. Adipiscing enim eu turpis egestas pretium. Euismod elementum nisi quis eleifend quam
adipiscing. In hac habitasse platea dictumst vestibulum. Sagittis purus sit amet volutpat. Netus
et malesuada fames ac turpis egestas. Eget magna fermentum iaculis eu non diam phasellus
vestibulum lorem. Varius sit amet mattis vulputate enim. Habitasse platea dictumst quisque
sagittis. Integer quis auctor elit sed vulputate mi. Dictumst quisque sagittis purus sit amet.
</p>
<p>
Morbi tristique senectus et netus. Id semper risus in hendrerit gravida rutrum quisque non
tellus. Habitasse platea dictumst quisque sagittis purus sit amet. Tellus molestie nunc non
blandit massa. Cursus vitae congue mauris rhoncus. Accumsan tortor posuere ac ut. Fringilla urna
porttitor rhoncus dolor. Elit ullamcorper dignissim cras tincidunt lobortis. In cursus turpis
massa tincidunt dui ut ornare lectus. Integer feugiat scelerisque varius morbi enim nunc.
Bibendum neque egestas congue quisque egestas diam. Cras ornare arcu dui vivamus arcu felis
bibendum. Dignissim suspendisse in est ante in nibh mauris. Sed tempus urna et pharetra pharetra
massa massa ultricies mi.
</p>
<p>
Mollis nunc sed id semper risus in. Convallis a cras semper auctor neque. Diam sit amet nisl
suscipit. Lacus viverra vitae congue eu consequat ac felis donec. Egestas integer eget aliquet
nibh praesent tristique magna sit amet. Eget magna fermentum iaculis eu non diam. In vitae
turpis massa sed elementum. Tristique et egestas quis ipsum suspendisse ultrices. Eget lorem
dolor sed viverra ipsum. Vel turpis nunc eget lorem dolor sed viverra. Posuere ac ut consequat
semper viverra nam. Laoreet suspendisse interdum consectetur libero id faucibus. Diam phasellus
vestibulum lorem sed risus ultricies tristique. Rhoncus dolor purus non enim praesent elementum
facilisis. Ultrices tincidunt arcu non sodales neque. Tempus egestas sed sed risus pretium quam
vulputate. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus ornare. Fringilla
urna porttitor rhoncus dolor purus non. Amet dictum sit amet justo donec enim.
</p>
<p>
Mattis ullamcorper velit sed ullamcorper morbi tincidunt. Tortor posuere ac ut consequat semper
viverra. Tellus mauris a diam maecenas sed enim ut sem viverra. Venenatis urna cursus eget nunc
scelerisque viverra mauris in. Arcu ac tortor dignissim convallis aenean et tortor at. Curabitur
gravida arcu ac tortor dignissim convallis aenean et tortor. Egestas tellus rutrum tellus
pellentesque eu. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Ut enim
blandit volutpat maecenas volutpat blandit aliquam etiam. Id donec ultrices tincidunt arcu. Id
cursus metus aliquam eleifend mi.
</p>
<p>
Tempus quam pellentesque nec nam aliquam sem. Risus at ultrices mi tempus imperdiet. Id porta
nibh venenatis cras sed felis eget velit. Ipsum a arcu cursus vitae. Facilisis magna etiam
tempor orci eu lobortis elementum. Tincidunt dui ut ornare lectus sit. Quisque non tellus orci
ac. Blandit libero volutpat sed cras. Nec tincidunt praesent semper feugiat nibh sed pulvinar
proin gravida. Egestas integer eget aliquet nibh praesent tristique magna.
</p>
</NotchedBox> </NotchedBox>
</RootLayout> </RootLayout>

View File

@@ -1,100 +1,67 @@
--- ---
import BaseHead from '../../components/BaseHead.astro'; import NotchedBox from '../../components/NotchedBox.astro';
import Header from '../../components/Header.astro'; import RootLayout from '../../layouts/RootLayout.astro';
import Footer from '../../components/Footer.astro'; import { getCollection, type CollectionEntry } from 'astro:content';
import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
import { getCollection } from 'astro:content';
import FormattedDate from '../../components/FormattedDate.astro'; import FormattedDate from '../../components/FormattedDate.astro';
import { Image } from 'astro:assets'; import { Image } from 'astro:assets';
const posts = (await getCollection('blog')).sort( 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(), (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
); );
--- ---
<!doctype html> <RootLayout>
<html lang="en">
<head>
<BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
<style> <style>
main {
width: 960px;
}
ul { ul {
display: flex; list-style: none;
flex-wrap: wrap;
gap: 2rem;
list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
}
ul li { display: flex;
width: calc(50% - 1rem); flex-direction: column;
} gap: 16px;
ul li * { align-items: stretch;
text-decoration: none;
transition: 0.2s ease; li {
} /* Required since flex won't render list-elements correctly. */
ul li:first-child {
width: 100%;
margin-bottom: 1rem;
text-align: center;
}
ul li:first-child img {
width: 100%;
}
ul li:first-child .title {
font-size: 2.369rem;
}
ul li img {
margin-bottom: 0.5rem;
border-radius: 12px;
}
ul li a {
display: block; display: block;
} }
.title {
margin: 0; a {
color: rgb(var(--black)); text-decoration: none;
line-height: 1;
.title, .description {
color: var(--color-light-text);
} }
.date { .date {
color: var(--color-gray);
}
.entry {
padding: 0.5em;
}
p {
margin: 0; margin: 0;
color: rgb(var(--gray));
} }
ul li a:hover h4,
ul li a:hover .date {
color: rgb(var(--accent));
} }
ul a:hover img {
box-shadow: var(--box-shadow); a:hover .title {
} color: var(--color-accent);
@media (max-width: 720px) {
ul {
gap: 0.5em;
}
ul li {
width: 100%;
text-align: center;
}
ul li:first-child {
margin-bottom: 0;
}
ul li:first-child .title {
font-size: 1.563em;
} }
} }
</style> </style>
</head>
<body>
<Header />
<main>
<section> <section>
<ul> <ul>
{ {
posts.map((post) => ( posts.map((post) => (
<li> <li>
<a href={`/blog/${post.id}/`}> <a href={`/blog/${post.id}/`}>
<NotchedBox fillNotches="left">
<div class="entry">
{post.data.heroImage && ( {post.data.heroImage && (
<Image width={720} height={360} src={post.data.heroImage} alt="" /> <Image width={720} height={360} src={post.data.heroImage} alt="" />
)} )}
@@ -102,13 +69,17 @@ const posts = (await getCollection('blog')).sort(
<p class="date"> <p class="date">
<FormattedDate date={post.data.pubDate} /> <FormattedDate date={post.data.pubDate} />
</p> </p>
{ post.data.description &&
<p class="description">
{post.data.description}
</p>
}
</div>
</NotchedBox>
</a> </a>
</li> </li>
)) ))
} }
</ul> </ul>
</section> </section>
</main> </RootLayout>
<Footer />
</body>
</html>

View File

@@ -1,14 +1,26 @@
--- ---
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
import NotchedBox from '../components/NotchedBox.astro'; import NotchedBox from '../components/NotchedBox.astro';
import Blazestar from '../components/Blazestar.astro';
import RootLayout from '../layouts/RootLayout.astro'; import RootLayout from '../layouts/RootLayout.astro';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
--- ---
<RootLayout> <RootLayout>
<NotchedBox> <NotchedBox fillNotches="left">
Test content <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> </NotchedBox>
</RootLayout> </RootLayout>

View File

@@ -1,16 +0,0 @@
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
items: posts.map((post) => ({
...post.data,
link: `/blog/${post.id}/`,
})),
});
}

25
src/pages/rss.xml.ts Normal file
View 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}/`,
})),
});
}

View File

@@ -5,17 +5,24 @@
*/ */
:root { :root {
--color-gold: #ffd700; --color-gold: #e99c1a;
--color-space-blue: #0b0f1a; --color-space-blue: #1c2329;
--color-space-blue-light: #2a3138;
--color-gray: #7f8c8d; --color-gray: #7f8c8d;
--color-light-text: rgb(229, 233, 240); --color-light-text: #dcdcc6;
--color-red: #d75422;
--accent: #2337ff; --color-accent: var(--color-red);
--accent-dark: #000d8a; --background: var(--color-space-blue);
--black: 15, 18, 25; --background-light: var(--color-space-blue-light);
--gray: 96, 115, 159;
--gray-light: 229, 233, 240; --font-size-sm: calc(var(--font-size-md) / 1.2);
--gray-dark: 34, 41, 57; --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-face {
@@ -34,6 +41,61 @@
font-display: swap; 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 { body {
font-family: "Atkinson", sans-serif; font-family: "Atkinson", sans-serif;
margin: 0; margin: 0;
@@ -42,7 +104,7 @@ body {
word-wrap: break-word; word-wrap: break-word;
overflow-wrap: break-word; overflow-wrap: break-word;
color: var(--color-light-text); color: var(--color-light-text);
font-size: 20px; font-size: var(--font-size-md);
line-height: 1.7; line-height: 1.7;
background-color: var(--color-space-blue); background-color: var(--color-space-blue);
} }
@@ -59,36 +121,37 @@ h6 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
line-height: 1.2; line-height: 1.2;
} }
/* Progressive 1.2x scaling from the base */
h1 { h1 {
font-size: 3.052em; font-size: var(--font-size-4xl);
} }
h2 { h2 {
font-size: 2.441em; font-size: var(--font-size-3xl);
} }
h3 { h3 {
font-size: 1.953em; font-size: var(--font-size-2xl);
} }
h4 { h4 {
font-size: 1.563em; font-size: var(--font-size-xl);
} }
h5 { h5 {
font-size: 1.25em; font-size: var(--font-size-lg);
} }
strong, strong,
b { b {
font-weight: 700; font-weight: 700;
} }
a { a {
color: var(--accent); color: var(--color-accent);
} }
a:hover { a:hover {
color: var(--accent); color: var(--color-accent);
} }
p { p {
margin-bottom: 1em; margin-bottom: 1em;
} }
.prose p { .prose p {
margin-bottom: 2em; margin-bottom: 1em;
} }
textarea { textarea {
width: 100%; width: 100%;
@@ -106,9 +169,9 @@ img {
border-radius: 8px; border-radius: 8px;
} }
code { code {
padding: 2px 5px; font-family: "Fira Code", monospace;
background-color: rgb(var(--gray-light)); background-color: var(--background-light);
border-radius: 2px; padding: 0.1em 0.2em;
} }
pre { pre {
padding: 1.5em; padding: 1.5em;