This commit is contained in:
2026-07-20 10:49:30 -05:00
parent 10bacc5a3c
commit 8f344ea37e
4 changed files with 277 additions and 65 deletions
+7 -31
View File
@@ -3,7 +3,7 @@ export type Role = {
company: string; company: string;
years: string; years: string;
current?: boolean; current?: boolean;
bullets: string[]; description: string;
}; };
export const work: Role[] = [ export const work: Role[] = [
@@ -11,60 +11,36 @@ export const work: Role[] = [
title: "Owner & Operator", title: "Owner & Operator",
company: "NixLabs Networks", company: "NixLabs Networks",
years: "2021 - 2024", years: "2021 - 2024",
bullets: [ description: "Designed, deployed, and maintained secure network infrastructure, hosting platforms, and Linux-based systems for clients. Automated infrastructure management and operational workflows using Bash, Python, and open-source technologies. Provided technical support, troubleshooting, and systems administration for production server environments.",
"Designed, deployed, and maintained secure network infrastructure, hosting platforms, and Linux-based systems for clients.",
"Automated infrastructure management and operational workflows using Bash, Python, and open-source technologies.",
"Provided technical support, troubleshooting, and systems administration for production server environments.",
],
}, },
{ {
title: "Technical Specialist", title: "Technical Specialist",
company: "Computer House · Part-time", company: "Computer House · Part-time",
years: "Jun - Aug 2023", years: "Jun - Aug 2023",
bullets: [ description: "Diagnosed and repaired customer computers, including hardware failures and software issues. Set up enterprise networks and servers for contract clients, while also handling storefront sales, customer service, and daily shop operations.",
"Diagnosed and repaired customer computers, including hardware failures and software issues.",
"Set up enterprise networks and servers for contract clients.",
"Handled storefront sales, customer service, and day-to-day shop operations.",
],
}, },
{ {
title: "IT Support Intern", title: "IT Support Intern",
company: "Metro Nashville Public Schools · Internship", company: "Metro Nashville Public Schools · Internship",
years: "Jan - Jul 2023", years: "Jan - Jul 2023",
bullets: [ description: "Deployed and configured Windows computers using Windows Deployment Services (WDS). Diagnosed hardware and software problems for staff and students across district facilities, tracked devices, and prepared equipment for technology rollouts.",
"Deployed and configured Windows computers using Windows Deployment Services (WDS).",
"Diagnosed hardware and software problems for staff and students across district facilities.",
"Helped track devices and prepare equipment for district technology rollouts.",
],
}, },
{ {
title: "System Architect", title: "System Architect",
company: "Fyra Labs · Part-time", company: "Fyra Labs · Part-time",
years: "Mar - Oct 2022", years: "Mar - Oct 2022",
bullets: [ description: "Designed and maintained the systems architecture behind the company's hosted services. Configured Cloudflare for DNS, traffic routing, and application security, and set up load balancing to keep services available during demand spikes.",
"Designed and maintained the systems architecture behind the company's hosted services.",
"Configured Cloudflare services for DNS, traffic routing, and application security.",
"Set up load balancing to distribute traffic and keep services available during demand spikes.",
],
}, },
{ {
title: "Technical Specialist", title: "Technical Specialist",
company: "Computer House Inc · Seasonal", company: "Computer House Inc · Seasonal",
years: "Jun - Aug 2022", years: "Jun - Aug 2022",
bullets: [ description: "Troubleshot and repaired desktops, laptops, and other computer hardware. Helped customers choose systems, replacement parts, and upgrades, prepared new computers for sale, and supported daily storefront operations.",
"Troubleshot and repaired desktops, laptops, and computer hardware for customers.",
"Helped customers choose systems, replacement parts, and upgrades based on their needs.",
"Prepared new computers for sale and supported daily storefront operations.",
],
}, },
{ {
title: "Software Engineer", title: "Software Engineer",
company: "Innatical · Part-time", company: "Innatical · Part-time",
years: "Apr - Oct 2021", years: "Apr - Oct 2021",
bullets: [ description: "Built and maintained back-end services in TypeScript, including APIs and server-side application features. Diagnosed bugs, reviewed changes, and tested releases with a remote development team.",
"Built and maintained back-end services in TypeScript.",
"Worked on APIs and server-side application features for the company's software projects.",
"Diagnosed bugs, reviewed changes, and tested releases with a remote development team.",
],
}, },
]; ];
+38 -17
View File
@@ -10,12 +10,17 @@ const description =
<script> <script>
import "../scripts/toolset-drag"; import "../scripts/toolset-drag";
import "../scripts/page-play";
</script> </script>
<Layout title="Owen Rummage" description={description}> <Layout title="Owen Rummage" description={description}>
<div class="page"> <div class="page">
<header class="hero"> <header class="hero">
<h1 class="hero__name">Owen Rummage</h1> <h1 class="hero__name">
<button class="hero__name-button" type="button" data-name-play>
Owen Rummage
</button>
</h1>
<p class="hero__statement"> <p class="hero__statement">
Systems engineer and founder building open, scalable Systems engineer and founder building open, scalable
infrastructure, cloud platforms, and networks that prioritize infrastructure, cloud platforms, and networks that prioritize
@@ -47,24 +52,31 @@ const description =
work.map((role) => ( work.map((role) => (
<article <article
class:list={["role", role.current && "role--current"]} class:list={["role", role.current && "role--current"]}
data-role-play
> >
<div class="role__meta"> <div class="role__meta">
<button
class="role__pin"
type="button"
aria-label={`Pin ${role.title} at ${role.company}`}
aria-pressed="false"
data-role-pin
>
{role.current ? ( {role.current ? (
<span class="role__live"> <span class="role__live">
<span>{role.years}</span> <span>{role.years}</span>
</span> </span>
) : ( ) : (
<span class="role__years">{role.years}</span> <span class="role__years">
{role.years}
</span>
)} )}
</button>
</div> </div>
<div class="role__body"> <div class="role__body">
<h2 class="role__title">{role.title}</h2> <h2 class="role__title">{role.title}</h2>
<p class="role__company">{role.company}</p> <p class="role__company">{role.company}</p>
<ul class="role__bullets"> <p class="role__description">{role.description}</p>
{role.bullets.map((line) => (
<li>{line}</li>
))}
</ul>
</div> </div>
</article> </article>
)) ))
@@ -86,13 +98,10 @@ const description =
<p class="role__company"> <p class="role__company">
Nashville State Community College Nashville State Community College
</p> </p>
<ul class="role__bullets"> <p class="role__description">
<li> Concentration in Systems Administration and Management.
Concentration in Systems Administration and Graduated May 2026.
Management. </p>
</li>
<li>Graduated May 2026.</li>
</ul>
</div> </div>
</article> </article>
</section> </section>
@@ -128,16 +137,28 @@ const description =
<aside class="motto" aria-label="Closing note"> <aside class="motto" aria-label="Closing note">
<div class="motto__meta" aria-hidden="true">motto</div> <div class="motto__meta" aria-hidden="true">motto</div>
<p class="motto__text"> <button
class="motto__text"
type="button"
aria-label="Show another motto"
data-motto-play
>
Stay humble enough to learn, and confident enough to build. Stay humble enough to learn, and confident enough to build.
</p> </button>
</aside> </aside>
<hr class="rule" /> <hr class="rule" />
<footer class="foot"> <footer class="foot">
<span class="foot__build">build 0.1 / Nashville</span> <span class="foot__build">build 0.1 / Nashville</span>
<span class="foot__home">rummage.cc</span> <button
class="foot__home"
type="button"
aria-label="Launch a small surprise"
data-home-play
>
rummage.cc
</button>
</footer> </footer>
</div> </div>
</Layout> </Layout>
+123
View File
@@ -0,0 +1,123 @@
const mottos = [
"Stay humble enough to learn, and confident enough to build.",
"Make it work. Then make it quiet.",
"Good systems leave room for people.",
"Read the logs before blaming the network.",
];
const glyphs = "01#@$%&*+<>/\\";
function prefersReducedMotion(): boolean {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function initName(): void {
const button = document.querySelector<HTMLButtonElement>("[data-name-play]");
if (!button) return;
const original = button.textContent?.trim() || "Owen Rummage";
let timer = 0;
button.addEventListener("click", () => {
window.clearInterval(timer);
if (prefersReducedMotion()) {
button.classList.toggle("is-glitched");
return;
}
let frame = 0;
timer = window.setInterval(() => {
button.textContent = [...original]
.map((letter, index) => {
if (letter === " " || index < frame / 2) return letter;
return glyphs[Math.floor(Math.random() * glyphs.length)];
})
.join("");
frame += 1;
if (frame > original.length * 2) {
window.clearInterval(timer);
button.textContent = original;
}
}, 34);
});
}
function initRoles(): void {
document.querySelectorAll<HTMLElement>("[data-role-play]").forEach((role) => {
const pin = role.querySelector<HTMLButtonElement>("[data-role-pin]");
const toggle = () => {
const pinned = role.classList.toggle("is-pinned");
pin?.setAttribute("aria-pressed", String(pinned));
};
role.addEventListener("dblclick", toggle);
pin?.addEventListener("click", toggle);
});
}
function initMotto(): void {
const button = document.querySelector<HTMLButtonElement>("[data-motto-play]");
if (!button) return;
let index = 0;
button.addEventListener("click", () => {
index = (index + 1) % mottos.length;
button.classList.remove("is-swapping");
void button.offsetWidth;
button.textContent = mottos[index];
button.classList.add("is-swapping");
});
}
function celebrate(): void {
const layer = document.createElement("div");
layer.className = "play-layer";
layer.setAttribute("aria-hidden", "true");
const count = prefersReducedMotion() ? 8 : 28;
for (let i = 0; i < count; i += 1) {
const bit = document.createElement("i");
bit.textContent = i % 3 === 0 ? "#" : i % 3 === 1 ? "0" : "1";
bit.style.setProperty("--x", `${Math.random() * 100}vw`);
bit.style.setProperty("--delay", `${Math.random() * 0.6}s`);
bit.style.setProperty("--drift", `${Math.random() * 80 - 40}px`);
layer.appendChild(bit);
}
document.body.appendChild(layer);
window.setTimeout(() => layer.remove(), 2600);
}
function initSecrets(): void {
const home = document.querySelector<HTMLButtonElement>("[data-home-play]");
home?.addEventListener("click", celebrate);
const sequence = [
"ArrowUp",
"ArrowUp",
"ArrowDown",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
"ArrowLeft",
"ArrowRight",
"b",
"a",
];
let position = 0;
window.addEventListener("keydown", (event) => {
position = event.key.toLowerCase() === sequence[position]?.toLowerCase() ? position + 1 : 0;
if (position === sequence.length) {
celebrate();
position = 0;
}
});
}
function init(): void {
initName();
initRoles();
initMotto();
initSecrets();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init, { once: true });
} else {
init();
}
+103 -11
View File
@@ -227,6 +227,24 @@ button {
font-feature-settings: "kern", "ss01"; font-feature-settings: "kern", "ss01";
font-variation-settings: "wght" 540; font-variation-settings: "wght" 540;
} }
.hero__name-button {
display: inline-block;
color: inherit;
font: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
line-height: inherit;
text-align: left;
cursor: pointer;
transition:
color var(--dur-fast) var(--ease-out-quint),
text-shadow var(--dur-base) var(--ease-out-quint);
}
.hero__name-button:hover,
.hero__name-button.is-glitched {
color: var(--color-accent-strong);
text-shadow: 0.04em 0 color-mix(in oklch, var(--color-accent) 35%, transparent);
}
.hero__statement { .hero__statement {
font-size: var(--fs-statement); font-size: var(--fs-statement);
line-height: var(--leading-statement); line-height: var(--leading-statement);
@@ -310,6 +328,7 @@ button {
flex-direction: column; flex-direction: column;
} }
.role { .role {
position: relative;
display: grid; display: grid;
grid-template-columns: var(--col-meta) 1fr; grid-template-columns: var(--col-meta) 1fr;
gap: var(--gap-meta); gap: var(--gap-meta);
@@ -317,6 +336,9 @@ button {
border-top: 1px solid transparent; border-top: 1px solid transparent;
transition: border-color var(--dur-base) var(--ease-out-quint); transition: border-color var(--dur-base) var(--ease-out-quint);
} }
.role.is-pinned {
border-top-color: var(--color-accent);
}
.role + .role { .role + .role {
border-top-color: var(--color-rule); border-top-color: var(--color-rule);
} }
@@ -340,6 +362,18 @@ button {
.role__years { .role__years {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.role__pin {
color: inherit;
font: inherit;
letter-spacing: inherit;
text-align: left;
white-space: inherit;
transition: color var(--dur-fast) var(--ease-out-quint);
}
.role__pin:hover,
.role__pin[aria-pressed="true"] {
color: var(--color-accent);
}
.role__live { .role__live {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -383,19 +417,13 @@ button {
letter-spacing: var(--tracking-tight); letter-spacing: var(--tracking-tight);
line-height: 1.35; line-height: 1.35;
} }
.role__bullets { .role__description {
list-style: none; margin-top: 0.25rem;
padding: 0;
margin: 0.5rem 0 0;
display: flex;
flex-direction: column;
gap: 0.75rem;
color: var(--color-ink-soft); color: var(--color-ink-soft);
}
.role__bullets li {
font-size: var(--fs-body); font-size: var(--fs-body);
line-height: var(--leading-body); line-height: var(--leading-body);
text-wrap: pretty; text-wrap: pretty;
max-width: 62ch;
} }
.role--current .role__title { .role--current .role__title {
color: var(--color-ink); color: var(--color-ink);
@@ -572,6 +600,7 @@ body.is-toolset-dragging * {
user-select: none; user-select: none;
} }
.motto__text { .motto__text {
display: block;
font-family: var(--font-sans); font-family: var(--font-sans);
font-style: italic; font-style: italic;
font-size: var(--fs-statement); font-size: var(--fs-statement);
@@ -583,6 +612,15 @@ body.is-toolset-dragging * {
font-variation-settings: font-variation-settings:
"wght" 420, "wght" 420,
"slnt" -10; "slnt" -10;
text-align: left;
cursor: pointer;
transition: color var(--dur-fast) var(--ease-out-quint);
}
.motto__text:hover {
color: var(--color-accent);
}
.motto__text.is-swapping {
animation: motto-in 420ms var(--ease-out-expo);
} }
/* ============================================================ /* ============================================================
@@ -613,6 +651,12 @@ body.is-toolset-dragging * {
} }
.foot__home { .foot__home {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
color: inherit;
cursor: pointer;
transition: color var(--dur-fast) var(--ease-out-quint);
}
.foot__home:hover {
color: var(--color-accent);
} }
.foot__handle { .foot__handle {
color: var(--color-muted); color: var(--color-muted);
@@ -636,6 +680,50 @@ body.is-toolset-dragging * {
} }
} }
@keyframes motto-in {
from {
opacity: 0;
transform: translateY(0.35rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Full-page easter egg, triggered from the footer or keyboard sequence. */
.play-layer {
position: fixed;
z-index: var(--z-focus);
inset: 0;
overflow: hidden;
pointer-events: none;
}
.play-layer i {
position: absolute;
left: var(--x);
bottom: -2rem;
color: var(--color-accent);
font-family: var(--font-mono);
font-size: clamp(0.75rem, 0.5rem + 1vw, 1.25rem);
font-style: normal;
opacity: 0;
animation: bit-rise 2s var(--delay) var(--ease-out-expo) both;
}
@keyframes bit-rise {
0% {
opacity: 0;
transform: translate3d(0, 0, 0) rotate(0);
}
18% {
opacity: 0.9;
}
100% {
opacity: 0;
transform: translate3d(var(--drift), -105vh, 0) rotate(150deg);
}
}
/* ============================================================ /* ============================================================
* Responsive: stack the marginalia grid on narrow screens * Responsive: stack the marginalia grid on narrow screens
* ============================================================ */ * ============================================================ */
@@ -696,8 +784,11 @@ body.is-toolset-dragging * {
color-mix(in oklch, var(--color-accent) 25%, transparent); color-mix(in oklch, var(--color-accent) 25%, transparent);
} }
.toolset__list li:hover, .toolset__list li:hover,
.toolset__list li:hover::before { .toolset__list li:hover::before,
.motto__text.is-swapping,
.play-layer i {
transform: none; transform: none;
animation: none;
} }
} }
@@ -713,7 +804,8 @@ body.is-toolset-dragging * {
--color-accent: #6b2d7a; --color-accent: #6b2d7a;
--color-rule: #cccccc; --color-rule: #cccccc;
} }
.theme-toggle { .theme-toggle,
.play-layer {
display: none; display: none;
} }
.role__dot { .role__dot {