hehee
This commit is contained in:
+7
-31
@@ -3,7 +3,7 @@ export type Role = {
|
||||
company: string;
|
||||
years: string;
|
||||
current?: boolean;
|
||||
bullets: string[];
|
||||
description: string;
|
||||
};
|
||||
|
||||
export const work: Role[] = [
|
||||
@@ -11,60 +11,36 @@ export const work: Role[] = [
|
||||
title: "Owner & Operator",
|
||||
company: "NixLabs Networks",
|
||||
years: "2021 - 2024",
|
||||
bullets: [
|
||||
"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.",
|
||||
],
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "Technical Specialist",
|
||||
company: "Computer House · Part-time",
|
||||
years: "Jun - Aug 2023",
|
||||
bullets: [
|
||||
"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.",
|
||||
],
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "IT Support Intern",
|
||||
company: "Metro Nashville Public Schools · Internship",
|
||||
years: "Jan - Jul 2023",
|
||||
bullets: [
|
||||
"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.",
|
||||
],
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "System Architect",
|
||||
company: "Fyra Labs · Part-time",
|
||||
years: "Mar - Oct 2022",
|
||||
bullets: [
|
||||
"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.",
|
||||
],
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "Technical Specialist",
|
||||
company: "Computer House Inc · Seasonal",
|
||||
years: "Jun - Aug 2022",
|
||||
bullets: [
|
||||
"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.",
|
||||
],
|
||||
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.",
|
||||
},
|
||||
{
|
||||
title: "Software Engineer",
|
||||
company: "Innatical · Part-time",
|
||||
years: "Apr - Oct 2021",
|
||||
bullets: [
|
||||
"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.",
|
||||
],
|
||||
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.",
|
||||
},
|
||||
];
|
||||
|
||||
+44
-23
@@ -10,12 +10,17 @@ const description =
|
||||
|
||||
<script>
|
||||
import "../scripts/toolset-drag";
|
||||
import "../scripts/page-play";
|
||||
</script>
|
||||
|
||||
<Layout title="Owen Rummage" description={description}>
|
||||
<div class="page">
|
||||
<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">
|
||||
Systems engineer and founder building open, scalable
|
||||
infrastructure, cloud platforms, and networks that prioritize
|
||||
@@ -47,24 +52,31 @@ const description =
|
||||
work.map((role) => (
|
||||
<article
|
||||
class:list={["role", role.current && "role--current"]}
|
||||
data-role-play
|
||||
>
|
||||
<div class="role__meta">
|
||||
{role.current ? (
|
||||
<span class="role__live">
|
||||
<span>{role.years}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span class="role__years">{role.years}</span>
|
||||
)}
|
||||
<button
|
||||
class="role__pin"
|
||||
type="button"
|
||||
aria-label={`Pin ${role.title} at ${role.company}`}
|
||||
aria-pressed="false"
|
||||
data-role-pin
|
||||
>
|
||||
{role.current ? (
|
||||
<span class="role__live">
|
||||
<span>{role.years}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span class="role__years">
|
||||
{role.years}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<div class="role__body">
|
||||
<h2 class="role__title">{role.title}</h2>
|
||||
<p class="role__company">{role.company}</p>
|
||||
<ul class="role__bullets">
|
||||
{role.bullets.map((line) => (
|
||||
<li>{line}</li>
|
||||
))}
|
||||
</ul>
|
||||
<p class="role__description">{role.description}</p>
|
||||
</div>
|
||||
</article>
|
||||
))
|
||||
@@ -86,13 +98,10 @@ const description =
|
||||
<p class="role__company">
|
||||
Nashville State Community College
|
||||
</p>
|
||||
<ul class="role__bullets">
|
||||
<li>
|
||||
Concentration in Systems Administration and
|
||||
Management.
|
||||
</li>
|
||||
<li>Graduated May 2026.</li>
|
||||
</ul>
|
||||
<p class="role__description">
|
||||
Concentration in Systems Administration and Management.
|
||||
Graduated May 2026.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
@@ -128,16 +137,28 @@ const description =
|
||||
|
||||
<aside class="motto" aria-label="Closing note">
|
||||
<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.
|
||||
</p>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
<hr class="rule" />
|
||||
|
||||
<footer class="foot">
|
||||
<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>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
@@ -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
@@ -227,6 +227,24 @@ button {
|
||||
font-feature-settings: "kern", "ss01";
|
||||
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 {
|
||||
font-size: var(--fs-statement);
|
||||
line-height: var(--leading-statement);
|
||||
@@ -310,6 +328,7 @@ button {
|
||||
flex-direction: column;
|
||||
}
|
||||
.role {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: var(--col-meta) 1fr;
|
||||
gap: var(--gap-meta);
|
||||
@@ -317,6 +336,9 @@ button {
|
||||
border-top: 1px solid transparent;
|
||||
transition: border-color var(--dur-base) var(--ease-out-quint);
|
||||
}
|
||||
.role.is-pinned {
|
||||
border-top-color: var(--color-accent);
|
||||
}
|
||||
.role + .role {
|
||||
border-top-color: var(--color-rule);
|
||||
}
|
||||
@@ -340,6 +362,18 @@ button {
|
||||
.role__years {
|
||||
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 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -383,19 +417,13 @@ button {
|
||||
letter-spacing: var(--tracking-tight);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.role__bullets {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0.5rem 0 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
.role__description {
|
||||
margin-top: 0.25rem;
|
||||
color: var(--color-ink-soft);
|
||||
}
|
||||
.role__bullets li {
|
||||
font-size: var(--fs-body);
|
||||
line-height: var(--leading-body);
|
||||
text-wrap: pretty;
|
||||
max-width: 62ch;
|
||||
}
|
||||
.role--current .role__title {
|
||||
color: var(--color-ink);
|
||||
@@ -572,6 +600,7 @@ body.is-toolset-dragging * {
|
||||
user-select: none;
|
||||
}
|
||||
.motto__text {
|
||||
display: block;
|
||||
font-family: var(--font-sans);
|
||||
font-style: italic;
|
||||
font-size: var(--fs-statement);
|
||||
@@ -583,6 +612,15 @@ body.is-toolset-dragging * {
|
||||
font-variation-settings:
|
||||
"wght" 420,
|
||||
"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 {
|
||||
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 {
|
||||
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
|
||||
* ============================================================ */
|
||||
@@ -696,8 +784,11 @@ body.is-toolset-dragging * {
|
||||
color-mix(in oklch, var(--color-accent) 25%, transparent);
|
||||
}
|
||||
.toolset__list li:hover,
|
||||
.toolset__list li:hover::before {
|
||||
.toolset__list li:hover::before,
|
||||
.motto__text.is-swapping,
|
||||
.play-layer i {
|
||||
transform: none;
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,7 +804,8 @@ body.is-toolset-dragging * {
|
||||
--color-accent: #6b2d7a;
|
||||
--color-rule: #cccccc;
|
||||
}
|
||||
.theme-toggle {
|
||||
.theme-toggle,
|
||||
.play-layer {
|
||||
display: none;
|
||||
}
|
||||
.role__dot {
|
||||
|
||||
Reference in New Issue
Block a user