Compare commits

...

3 Commits

Author SHA1 Message Date
3a33325d3a Adds post tags 2026-03-11 18:29:14 -07:00
0a9f425591 Fixes social link in the sidebar 2026-03-11 18:29:14 -07:00
7445778ecd Adds draft about being a scrub 2026-03-11 18:29:14 -07:00
9 changed files with 224 additions and 85 deletions

View File

@@ -1,13 +1,14 @@
--- ---
interface Props { interface Props {
fillNotches: "left" | "right" | "both" | "none"; fillNotches: "left" | "right" | "both" | "none";
color?: string;
} }
const { fillNotches = "none" } = Astro.props; const { fillNotches = "none", color = "gold" } = Astro.props;
--- ---
<div class={`container notch-${fillNotches}`}> <div class={`container notch-${fillNotches}`} style={`background-color: var(--color-${color})`}>
<div class="content"> <div class="content">
<slot /> <slot />
</div> </div>

View File

@@ -12,7 +12,7 @@ import Blazestar from './Blazestar.astro';
<SidebarLink href="/about">About</SidebarLink> <SidebarLink href="/about">About</SidebarLink>
</div> </div>
<div class="social-links"> <div class="social-links">
<a href="https://m.webtoo.ls/@astro" target="_blank"> <a href="https://mastodon.social/@periodic" target="_blank">
<span class="sr-only">Follow Periodic on Mastodon</span> <span class="sr-only">Follow Periodic on Mastodon</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32" <svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32"
><path ><path

View File

@@ -14,6 +14,7 @@ const blog = defineCollection({
pubDate: z.coerce.date().optional(), pubDate: z.coerce.date().optional(),
updatedDate: z.coerce.date().optional(), updatedDate: z.coerce.date().optional(),
heroImage: image().optional(), heroImage: image().optional(),
tags: z.array(z.string()).optional(),
}), }),
}); });

View File

@@ -0,0 +1,50 @@
---
title: "How to stop being a scrub and achieve your goals"
description: "Devin Sirlin applied the term scrub to a specific type of player he saw in the fighting-game circuit. This person stops improving and fails to win because they create self-imposed rules that get in their way. This concept goes far beyond getting good at video games or winning competitions. It applies to everything in life. How are you creating self-imposed rules that conflict with your goals?"
---
[David Sirlin](https://en.wikipedia.org/wiki/David_Sirlin) wrote a great mini-book from his blog, [sirlin.net](https://sirlin.net), called Playing to Win. It's a treatise about how to learn how to win in competitive pursuits. He made his gaming career playing [Street Fighter](https://en.wikipedia.org/wiki/Street_Fighter), but also designing and balancing competitive video and table-top games, so that's the context he's coming from.
## What is a scrub?
> A scrub is a player who is handicapped by self-imposed rules that the game knows nothing about. A scrub does not play to win.
>
> Now, everyone begins as a poor player—it takes time to learn a game to get to a point where you know what youre doing. There is the mistaken notion, though, that by merely continuing to play or “learn” the game, one can become a top player. In reality, the “scrub” has many more mental obstacles to overcome than anything actually going on during the game. The scrub has lost the game even before it starts. Hes lost the game even before deciding which game to play. His problem? He does not play to win.
>
> ...The first step in becoming a top player is the realization that playing to win means doing whatever most increases your chances of winning. That is true by definition of playing to win. The game knows no rules of “honor” or of “cheapness.” The game only knows winning and losing.
>
> A common call of the scrub is to cry that the kind of play in which one tries to win at all costs is “boring” or “not fun.” Who knows what objective the scrub has, but we know his objective is not truly to win. Yours is. Your objective is good and right and true, and let no one tell you otherwise. You have the power to dispatch those who would tell you otherwise, anyway. Simply beat them.
[Introducing the Scrub](https://sirlin.squarespace.com/ptw-book/introducingthe-scrub) by David Sirlin
## There's more than just winning
Sirlin examines the scrub from a very narrow context: winning the game. This is itself a self-imposed rule. It is deciding that the only thing that matters in the game is your final score and your tournament standings.
This is a very narrow way to play a game. There are so many other things to optimize for: teaching, growing the community, making friends, personal fulfillment.
## Growth and Goals
There are two key aspects of a scrub that make them stand out as undesirable models of behavior: they don't learn or improve and they don't reach their goals. Any non-trivial goal requires that we grow and learn to be able to reach our goals, otherwise you could trivially achieve them and then they aren't goals anymore. Anything worth talking about requires change.
Growth can be the goal in itself, but generally it's in service of a goal. Growth without a goal is really just play. It's exploring and trying new things for their own sake. Children do this really well before they learn to think ahead and compare themselves to others. Adults do it very poorly because we tend to think ahead compare ourselves to others or abstract ideas of what we should be.
## Understanding Failure
> If you never lose, you are never truly tested, and never forced to grow. A loss is an opportunity to learn.
## The danger of always playing to win
TODO
If you focus only on winning you may miss out on goals that actually fulfill your needs.
## How to set good goals
TODO
What makes a good goal? What are you optimizing for?
## Other Quotes
> I once played a scrub who was actually quite good. That is, he knew the rules of the game well, he knew the character matchups well, and he knew what to do in most situations. But his web of mental rules kept him from truly playing to win. He cried cheap as I beat him with “no skill moves” while he performed many difficult dragon punches. He cried cheap when I threw him five times in a row asking, “Is that all you know how to do? Throw?” I gave him the best advice he could ever hear. I told him, “Play to win, not to do difficult moves.’” This was a big moment in that scrubs life. He could either ignore his losses and continue living in his mental prison or analyze why he lost, shed his rules, and reach the next level of play.

View File

@@ -2,6 +2,9 @@
title: "The Forge of God by Greg Bear" title: "The Forge of God by Greg Bear"
description: "A short review" description: "A short review"
pubDate: "Jul 18 2025" pubDate: "Jul 18 2025"
tags:
- Sci-Fi
- Book Review
--- ---
[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. [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.

View File

@@ -2,6 +2,10 @@
title: "I Forgot to Use AI On My Latest Project" 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." 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" pubDate: "Jun 26 2025"
tags:
- AI
- Tech
- Coding
--- ---
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. 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.

140
src/layouts/BlogList.astro Normal file
View File

@@ -0,0 +1,140 @@
---
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';
interface Params {
selectedTag?: string | undefined;
}
const { selectedTag } = Astro.props;
function publishedOnly(p: CollectionEntry<'blog'>): p is (CollectionEntry<'blog'> & { data: { pubDate: Date }}) {
return p.data.pubDate !== undefined;
}
const allPosts = (await getCollection('blog', publishedOnly))
.sort(
(a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),
);
const tags = allPosts.flatMap(p => p.data.tags);
const posts =
selectedTag
? allPosts.filter(p => p.data.tags.includes(selectedTag))
: allPosts;
---
<RootLayout>
<style>
ul.tags {
list-style: none;
margin: 0;
margin-bottom: 1em;
padding: 0;
display: flex;
flex-direction: row;
gap: 8px;
align-items: baseline;
li {
padding: 1px 16px
.container {
background-color: var(--color-gray);
}
}
}
ul.posts {
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 class="tags">
{
tags.map(tag =>
<li>
{
tag === selectedTag
? <a href="/blog">
<NotchedBox color="gray" fillNotches={"left"}>
{tag}
</NotchedBox>
</a>
: <a href={`/blog/tag/${tag}`}>
<NotchedBox color="gray" fillNotches={"none"}>
{tag}
</NotchedBox>
</a>
}
</li>
)
}
</ul>
</section>
<section>
<ul class="posts">
{
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>

View File

@@ -1,85 +1,5 @@
--- ---
import NotchedBox from '../../components/NotchedBox.astro'; import BlogList from '../../layouts/BlogList.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> <BlogList/>
<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>

View File

@@ -0,0 +1,20 @@
---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogList from '../../../layouts/BlogList.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
const tags = posts.flatMap(p => p.data.tags);
return tags.map((tag) => ({
params: { tag: tag },
props: { tag: tag },
}));
}
type Props = { tag: string }
const tag = Astro.props.tag;
---
<BlogList selectedTag={tag} />