diff --git a/src/data/work.ts b/src/data/work.ts index 0206c61..5d43a25 100644 --- a/src/data/work.ts +++ b/src/data/work.ts @@ -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.", }, ]; diff --git a/src/pages/index.astro b/src/pages/index.astro index 98109d1..79c4f94 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -10,12 +10,17 @@ const description =
-

Owen Rummage

+

+ +

Systems engineer and founder building open, scalable infrastructure, cloud platforms, and networks that prioritize @@ -47,24 +52,31 @@ const description = work.map((role) => (

- {role.current ? ( - - {role.years} - - ) : ( - {role.years} - )} +

{role.title}

{role.company}

-
    - {role.bullets.map((line) => ( -
  • {line}
  • - ))} -
+

{role.description}

)) @@ -86,13 +98,10 @@ const description =

Nashville State Community College

-
    -
  • - Concentration in Systems Administration and - Management. -
  • -
  • Graduated May 2026.
  • -
+

+ Concentration in Systems Administration and Management. + Graduated May 2026. +

@@ -128,16 +137,28 @@ const description =
diff --git a/src/scripts/page-play.ts b/src/scripts/page-play.ts new file mode 100644 index 0000000..ce81223 --- /dev/null +++ b/src/scripts/page-play.ts @@ -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("[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("[data-role-play]").forEach((role) => { + const pin = role.querySelector("[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("[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("[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(); +} diff --git a/src/styles/global.css b/src/styles/global.css index b94d572..2b38ebc 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -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 {