feat: refactor

This commit is contained in:
Barrett Ruth 2025-05-22 14:23:22 -05:00
parent b83f17d087
commit 8666e5a169
57 changed files with 5734 additions and 5313 deletions

100
src/pages/[category].astro Normal file
View file

@ -0,0 +1,100 @@
---
import { getCollection } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
export async function getStaticPaths() {
const categories = ['algorithms', 'software', 'operating-systems', 'meditations'];
return categories.map(category => {
return {
params: { category },
props: { category },
};
});
}
const { category } = Astro.props;
const posts = await getCollection('posts', (post) => post.data.category === category);
posts.sort((a, b) => {
const dateA = a.data.date ? new Date(a.data.date) : new Date(0);
const dateB = b.data.date ? new Date(b.data.date) : new Date(0);
return dateB.getTime() - dateA.getTime();
});
const capitalizedCategory = category.charAt(0).toUpperCase() + category.slice(1);
---
<BaseLayout title={capitalizedCategory}>
<div class="content">
<h1>{capitalizedCategory}</h1>
<div class="posts">
{posts.map(post => (
<div class="post">
<a href={`/posts/${category}/${post.slug}`}>
{post.data.title}
</a>
{post.data.date && (
<time datetime={post.data.date}>
{new Date(post.data.date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
})}
</time>
)}
</div>
))}
</div>
</div>
</BaseLayout>
<style>
.content {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1 {
margin-bottom: 30px;
}
.posts {
list-style-type: none;
padding: 0;
}
.post {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.post a {
display: block;
font-size: 1.2em;
text-decoration: underline;
color: var(--topic-color, inherit);
}
time {
display: block;
font-size: 0.9em;
color: #555;
margin-top: 5px;
}
</style>
<script define:vars={{ category }}>
import { getTopicColor } from '../utils/colors.js';
document.addEventListener('DOMContentLoaded', function() {
document.documentElement.style.setProperty(
'--topic-color',
getTopicColor(category)
);
window.getTopicColor = getTopicColor;
});
</script>

45
src/pages/about.astro Normal file
View file

@ -0,0 +1,45 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Barrett Ruth">
<div class="post-container">
<header class="post-header">
<h1 class="post-title">Barrett Ruth</h1>
</header>
<article class="post-article">
<p>
I am a software developer studying computer science at the
University of Virginia.
</p>
<p>
I began working as a software engineer part-time with
<a target="blank" href="https://gotransverse.com/">GoTransverse</a>
in high school. After developing an interest in the
financial/venture capital world, I transitioned to
<a target="blank" href="https://www.nthventure.com/">Nth Venture</a>
in the spring of my second year. I worked at
<a target="blank" href="https://usa.visa.com/">VISA</a> and
<a href="https://trbcap.com" target="_blank">TRB Capital Management</a>
during the summer of 2024. Luckily enough, I'll be joining
<a href="https://drw.com" target="_blank">DRW</a> and
<a href="https://ramp.com" target="_blank">Ramp</a> in the summer
and spring of 2025.
</p>
<p>
I've a developing interest in high-performance computing,
quantitative finance, and open-source software. I am also a
passionate contributor to the (Neo)Vim ecosystem and beyond.
</p>
<p>
You can see my related contributions on
<a target="blank" href="https://github.com/barrett-ruth">GitHub</a>.
</p>
</article>
</div>
<slot name="head" slot="head">
<link rel="stylesheet" href="/styles/post.css" />
</slot>
</BaseLayout>

183
src/pages/index.astro Normal file
View file

@ -0,0 +1,183 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import { getCollection } from "astro:content";
const title = "Barrett Ruth";
const allPosts = await getCollection("posts");
const postsByCategory = allPosts.reduce((acc, post) => {
const category = post.id.split('/')[0];
if (!acc[category]) acc[category] = [];
acc[category].push(post);
return acc;
}, {});
Object.keys(postsByCategory).forEach(category => {
postsByCategory[category].sort((a, b) => {
const dateA = a.data.date ? new Date(a.data.date) : new Date(0);
const dateB = b.data.date ? new Date(b.data.date) : new Date(0);
return dateB.getTime() - dateA.getTime();
});
});
---
<BaseLayout title={title}>
<slot name="head" slot="head">
<link rel="stylesheet" href="/styles/index.css" />
</slot>
<div class="content">
<ul class="topics">
<li class="topic algorithms">
<a href="#algorithms" data-topic="algorithms">algorithms</a>
</li>
<li class="topic software">
<a href="#software" data-topic="software">software</a>
</li>
<li class="topic meditations">
<a href="#meditations" data-topic="meditations">meditations</a>
</li>
</ul>
<div class="posts" id="posts"></div>
</div>
<script slot="scripts" define:vars={{ postsByCategory }}>
function getTopicColor(topicName) {
switch (topicName) {
case "software":
return "#0073e6";
case "operating-systems":
return "#009975";
case "algorithms":
return "#d50032";
case "meditations":
return "#6a0dad";
default:
return "#000000";
}
}
const TERMINAL_PROMPT = "barrett@ruth:~$ ";
let typing = false;
let clearing = false;
function clearPrompt(delay, callback) {
if (clearing) return;
clearing = true;
const terminalPrompt = document.querySelector(".terminal-prompt");
if (!terminalPrompt) {
clearing = false;
return;
}
const topicLength = terminalPrompt.innerHTML.length - TERMINAL_PROMPT.length;
let i = 0;
function removeChar() {
if (i++ < topicLength) {
terminalPrompt.textContent = terminalPrompt.textContent.slice(0, -1);
setTimeout(removeChar, delay / topicLength);
} else {
i = 0;
clearing = false;
callback && callback();
}
}
removeChar();
}
function typechars(e) {
e.preventDefault();
const topicElement = e.target;
if (topicElement.classList.contains("active")) return;
if (typing) return;
typing = true;
const topic = topicElement.dataset.topic;
const terminalText = ` /${topic.toLowerCase()}`;
const terminalPrompt = document.querySelector(".terminal-prompt");
const delay =
terminalPrompt.innerHTML.length > TERMINAL_PROMPT.length ? 250 : 500;
const topics = document.querySelectorAll(".topic a");
topics.forEach((t) => {
t.classList.remove("active");
t.style.color = "";
});
topicElement.classList.add("active");
topicElement.style.color = getTopicColor(topic);
clearPrompt(delay, () => {
let i = 0;
function typechar() {
if (i < terminalText.length) {
terminalPrompt.innerHTML += terminalText.charAt(i++);
setTimeout(typechar, delay / terminalText.length);
} else {
renderPosts(topic);
typing = false;
}
}
typechar();
});
}
function renderPosts(topic) {
const posts = document.getElementById("posts");
posts.innerHTML = "";
const categoryPosts = postsByCategory[topic];
if (!categoryPosts) {
console.error(`No posts found for topic: ${topic}`);
return;
}
categoryPosts.forEach((post) => {
const postDiv = document.createElement("div");
postDiv.classList.add("post");
const link = document.createElement("a");
const slug = post.id.split('/').pop().replace(/\.mdx?$/, '');
link.href = `/posts/${topic}/${slug}`;
link.textContent = post.data.title;
link.style.textDecoration = "underline";
postDiv.appendChild(link);
posts.appendChild(postDiv);
});
}
document.addEventListener('DOMContentLoaded', function() {
const topics = document.querySelectorAll('.topic a');
topics.forEach(topic => {
topic.addEventListener('click', typechars);
const topicName = topic.dataset.topic;
topic.addEventListener('mouseenter', () => {
const color = getTopicColor(topicName);
topic.style.color = color;
});
topic.addEventListener('mouseleave', () => {
if (!topic.classList.contains('active')) {
topic.style.color = "";
}
});
});
window.addEventListener('beforeunload', () => {
const terminalPrompt = document.querySelector('.terminal-prompt');
if (terminalPrompt) {
terminalPrompt.innerHTML = TERMINAL_PROMPT;
}
});
});
</script>
</BaseLayout>

View file

@ -0,0 +1,35 @@
---
import { getCollection } from 'astro:content';
import PostLayout from '../../../layouts/PostLayout.astro';
import path from 'path';
export async function getStaticPaths() {
const allPosts = await getCollection('posts');
const routes = [];
for (const post of allPosts) {
const filePath = post.id;
const pathParts = filePath.split('/');
const category = pathParts[0];
const slug = path.basename(post.id, path.extname(post.id));
routes.push({
params: { category, slug },
props: { post },
});
}
return routes;
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<PostLayout frontmatter={post.data} post={post}>
<Content />
</PostLayout>