From 51b6ef9710975b0ab0cc5d13973c9a1fca28c6fe Mon Sep 17 00:00:00 2001 From: Adni Onoh Date: Tue, 12 May 2026 12:19:18 -0400 Subject: [PATCH 1/4] split the original code into 3 files and added a some extra feautures and projects I've been working on --- index.html | 1174 ++++++++++++---------------------------------------- index.js | 326 +++++++++++++++ style.css | 1045 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1634 insertions(+), 911 deletions(-) create mode 100644 index.js create mode 100644 style.css diff --git a/index.html b/index.html index e5a22e3..95e4bb6 100644 --- a/index.html +++ b/index.html @@ -4,746 +4,42 @@ adni onoh — ~/portfolio + - + - + + +
-
adni@portfolio: ~/
-
- -
- + +
+
open to new-grad SWE roles · open to learn @@ -762,6 +58,13 @@ buffalo, ny · ET · graduating spring 2026 + · + + + · + —:—:— + ET +

hi, I'm
@@ -778,17 +81,17 @@

github

- -
+ +
cat about.md # readme · markdown

// the short version

I'm graduating from the University at Buffalo in Spring 2026 with a B.A. in Computer Science and a concentration in Communication.

-

My coursework and side work split between two things that pull on each other: distributed systems & backend (where I've built a Raft consensus cluster, a Kademlia DHT, and a concurrent memory allocator) and full-stack web development (where I currently ship production features at Nara Therapy).

+

My coursework and side work split between two things that pull on each other: distributed systems & backend (where I've built a Raft consensus cluster, a Kademlia DHT, and a concurrent memory allocator) and full-stack web development (where I currently ship production features at Nara Therapy).

The communication concentration shows up in how I work — writing user stories, reviewing teammates' code, and explaining trade-offs to non-engineers. It's been more useful than I expected.

// status: open to learn, open to roles starting summer 2026

- +
degree
@@ -808,10 +111,10 @@

- -
+ +
ls -la work/ # current role
- +
@@ -819,16 +122,16 @@

healthtech · agile · full-stack
Building production features on a healthcare web app inside an agile team. Recent work: -
    -
  • Developed a responsive feedback modal that captures user insights while keeping interface consistency across mobile and desktop.
  • -
  • Added dashboard features — organization contact dates, organization deletion, and rating-based filters — following Figma designs and QA criteria so providers can spot low-rated sessions efficiently.
  • +
      +
    • Developed a responsive feedback modal that captures user insights while keeping interface consistency across mobile and desktop.
    • +
    • Added dashboard features — organization contact dates, organization deletion, and rating-based filters — following Figma designs and QA criteria so providers can spot low-rated sessions efficiently.

- -
+ +
grep -r skills/ # grouped by frequency of use
@@ -883,10 +186,9 @@

// practices

- - +
-
./animate.sh # css demo · not a self-assessment
+
./animate.sh # css demo · not a self-assessment
// note: the bars below are a CSS animation demo — a chance to play with transition, transform, and @keyframes. they show how much I've used each language while building this site, not any claim of skill level. for an honest proficiency picture, see the tags above. @@ -922,24 +224,129 @@

// practices

- -
-
tree projects/ # 11 entries · filter by category
- -
- filter: - - - - - + +
+
tree projects/ # 16 entries · filter by category
+ +
+ + + + + + +
- -

// note: OOP principles and Git/GitHub version control applied across all projects.

- + +

// note: OOP principles and Git/GitHub version control applied across all projects. ● current marks projects I'm actively building right now.

+
- - + + + +
+
+
+ Distributed Task Scheduler + systems + current +
+
Go · goroutines · fault tolerance
+
+
+ A small Go service that schedules and dispatches tasks across multiple worker nodes, with retries and failover when a worker drops out. Building it to deepen what I learned from the Raft project — coordination, partial failure, and keeping work moving when something goes wrong. + +
+
+ +
+
+
+ Real-Time Resource Monitor + systems + current +
+
C · system calls · WebSocket dashboard
+
+
+ A lightweight monitor in C that samples CPU and memory usage in real time and pushes the readings to a browser dashboard over WebSockets. Configurable thresholds trigger live web notifications — useful for catching runaway processes without a heavyweight tool like htop or Grafana. + +
+
+ +
+
+
+ Minimalist API Gateway + systems + current +
+
Go · HTTP routing · middleware
+
+
+ A small reverse-proxy / gateway that routes incoming requests to different mock downstream services based on URL path. Designed to be a teaching tool for myself first: middleware chaining, request/response transformation, and graceful handling when a downstream is unavailable. + +
+
+ +
+
+
+ Business Process Automation Tool + web + current +
+
Python · Flask · Google Sheets API · PDF generation
+
+
+ A Python-based dashboard that automates a recurring business task end-to-end — for example, pulling line items from a Google Sheet and generating a styled invoice PDF, or maintaining a simple inventory view that updates as stock changes. Built with small Buffalo-area businesses in mind. + +
+
+ +
+
+
+ Nara Therapy — Production Features + web + current +
+
JavaScript · React · Figma · QA
+
+
+ Current production-side work as a Software Engineer at a healthtech startup. +
    +
  • Responsive feedback modal that maintains consistency across mobile and desktop
  • +
  • Dashboard features: organization contact dates, deletion controls, rating-based filters
  • +
  • All work follows Figma designs and team QA criteria
  • +
+
+
+ +
+
+
+ UB CSE Scrum Board + web + current +
+
Bug fixes · feature work · internal tooling
+
+
+ Contributing to the University at Buffalo CSE department's in-house scrum board — a tool the department built and uses to track its own engineering work. Fixing live bugs and adding new features for an existing user base, which means reading other people's code first, working within the existing architecture, and shipping changes carefully. +
+
+ + +
Consensus Server Cluster systems
@@ -952,9 +359,12 @@

// practices

  • Log replication with consistency checks across follower nodes
  • Recovery logic that handles partial state loss on restart
  • +
    - +
    Distributed Hash Table (Kademlia) systems
    @@ -967,12 +377,15 @@

    // practices

  • Implemented RPC-based node communication for iterative lookups
  • Content-addressing and fault-tolerant data storage across the network
  • +
    - +
    -
    Message Service & Failure Detector systems
    +
    Message Service & Failure Detector systems
    Go · TCP sockets · Protobuf · channels
    @@ -982,9 +395,12 @@

    // practices

  • Go channels for in-process communication and coordination
  • Protobuf protocol buffers for data serialization between processes
  • +
    - +
    Dynamic Memory Allocator systems
    @@ -996,41 +412,33 @@

    // practices

  • Thread-safe alloc / free / realloc via mutex-guarded data structures
  • Optimized for low fragmentation and reuse of freed blocks
  • +
    - - + + +
    -
    Nara Therapy — Production Features web
    -
    JavaScript · React · Figma · QA
    -
    -
    - Current production-side work as a Software Engineer at a healthtech startup. -
      -
    • Responsive feedback modal that maintains consistency across mobile and desktop
    • -
    • Dashboard features: organization contact dates, deletion controls, rating-based filters
    • -
    • All work follows Figma designs and team QA criteria
    • -
    -
    -
    - -
    -
    -
    ChatAppX — Real-time Chat & Video web
    +
    ChatAppX — Real-time Chat & Video web
    Python · Node · Express · PostgreSQL · MongoDB · WebSockets
    A real-time web app for text and video chat, with security as a first-class concern.
      -
    • HTML injection prevention, salted password hashing, XSRF & auth tokens
    • +
    • HTML injection prevention, salted password hashing, XSRF & auth tokens
    • Direct peer-to-peer connections via WebSockets — video chat keeps working through server crashes
    • Secure file uploads with content sanitization and validation
    • Comprehensive form validation and error handling
    +
    - +
    Fresumes — Free Resume Platform web
    @@ -1043,10 +451,14 @@

    // practices

  • Built real-time multi-resume upload
  • Integrated a feedback system with automated email routing
  • +
    - - + + +
    Simon's Game fun
    @@ -1054,10 +466,12 @@

    // practices

    The classic memory game — repeat the sequence of lights and sounds. Built to practice event-driven JS and DOM manipulation. - ▶ play game +
    - +
    Dice Game fun
    @@ -1065,10 +479,12 @@

    // practices

    Two-player dice roll — the higher number wins. A small exercise in randomness, image swapping, and clean conditional UI. - ▶ play game +
    - +
    Drum Kit fun
    @@ -1076,25 +492,65 @@

    // practices

    An interactive drum kit — make beats with your keyboard or by clicking the pads. Covers keyboard events, audio playback, and animated button feedback. - ▶ try it +
    - - + + +
    This Interactive Resume tools
    HTML · CSS · JavaScript
    - The site you're reading. Hand-built layout with custom animations, filter system, and an image lightbox — no frameworks, no templates. + The site you're reading. Hand-built layout with custom animations, a filter system, a command-palette top bar, and a light/dark theme toggle — no frameworks, no templates.
    - + +
    +
    + +
    +
    cat process.md # how this site was built
    +

    + This site itself is a project. So I built it the way I'd build anything else — paying attention to how as much as what. Below are the practices that shaped it. +

    + +
    +
    +

    + // process + Agile, scaled to one +

    +

    Solo project, but built like a real one: scoped iterations and feedback-driven rework over big-bang releases.

    +
      +
    • Iterative scope — features shipped in small, reviewable slices (content fixes → layout → projects → polish), not one giant rewrite.
    • +
    • Feedback loops — every visible feature went through a "build, review, refine" pass before being kept. The filter chips, command input, and color scheme all changed mid-build because of testing.
    • +
    • Honest scoping — features that weren't worth the cost (autoplay video, full image lightbox) were cut rather than half-shipped.
    • +
    • Coursework — formal agile practice from UB's Software Engineering Concepts course: user stories, sprints, acceptance tests, Scrum ceremonies.
    • +
    +
    + +
    +

    + // a11y + Accessibility, not as an afterthought +

    +

    Informed by UB's Applied Human-Computer Interaction Design course. You can test these — they aren't claims.

    +
      +
    • Keyboard navigable — try Tab through the page; every interactive element is reachable and has a visible focus ring. / jumps straight to the command input.
    • +
    • Semantic HTML — proper landmarks (<nav>, <main>, <section>, <footer>), heading hierarchy, ARIA labels on icon-only controls.
    • +
    • Reduced motion — if your OS has "reduce motion" enabled, the section fade-ins, status pulses, and shimmer effects shorten or disable automatically.
    • +
    • Contrast & legibility — both themes meet WCAG AA contrast for body text. Skip link at the top of the DOM for screen-reader users.
    • +
    +
    - -
    + +
    cat certifications.txt # verified credentials
    - -
    + +
    echo "let's build something"
    - +
    -
    © 2026 adni onoh · built from scratch with html/css/js
    -
    last commit: today
    +
    © 2026 adni onoh · buffalo, ny · handwritten html / css / js
    +
    session uptime: 0:00
    - - - + + + - + \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..8ed68e8 --- /dev/null +++ b/index.js @@ -0,0 +1,326 @@ +/* ============================================================ + Adni Onoh — Interactive Resume · index.js + ------------------------------------------------------------ + Sections: + 1. Skill bar animation (click to fill / reset) + 2. Theme toggle (dark ↔ light, persisted) + 3. Project category filter + 4. Live clock (Buffalo ET) + 5. Command palette (top-bar input) + 6. Footer uptime ticker + ============================================================ */ + +(function () { + 'use strict'; + + /* ============================================================ + 1. Skill bar animation + ============================================================ */ + const animateBtn = document.getElementById('animateBtn'); + const bars = document.querySelectorAll('.bar-fill'); + + if (animateBtn && bars.length) { + let isFilled = false; + let busy = false; + + const fillBars = () => { + bars.forEach((bar, i) => { + const w = bar.dataset.width; + setTimeout(() => { + bar.style.width = w + '%'; + bar.classList.add('animated'); + }, i * 120); + }); + }; + + const resetBars = () => { + bars.forEach(bar => { + bar.style.width = '0%'; + bar.classList.remove('animated'); + }); + }; + + const setButtonState = (filled) => { + if (filled) { + animateBtn.classList.add('filled'); + animateBtn.querySelector('.btn-prompt').textContent = '↺'; + animateBtn.querySelector('.btn-label').textContent = 'reset'; + } else { + animateBtn.classList.remove('filled'); + animateBtn.querySelector('.btn-prompt').textContent = '$'; + animateBtn.querySelector('.btn-label').textContent = 'run animation'; + } + }; + + animateBtn.addEventListener('click', () => { + if (busy) return; + busy = true; + if (!isFilled) { + fillBars(); + isFilled = true; + setButtonState(true); + } else { + resetBars(); + isFilled = false; + setButtonState(false); + } + setTimeout(() => { busy = false; }, bars.length * 120 + 200); + }); + } + + /* ============================================================ + 2. Theme toggle + ============================================================ */ + const themeToggle = document.getElementById('themeToggle'); + const root = document.documentElement; + + if (themeToggle) { + const themeLabel = themeToggle.querySelector('.theme-label'); + const themeIcon = themeToggle.querySelector('.theme-icon'); + + let savedTheme = 'dark'; + try { savedTheme = localStorage.getItem('theme') || 'dark'; } + catch (e) { /* storage blocked, default applies */ } + + const applyTheme = (theme) => { + if (theme === 'light') { + root.setAttribute('data-theme', 'light'); + themeLabel.textContent = 'dark'; + themeIcon.textContent = '◑'; + } else { + root.removeAttribute('data-theme'); + themeLabel.textContent = 'light'; + themeIcon.textContent = '◐'; + } + }; + + applyTheme(savedTheme); + + themeToggle.addEventListener('click', () => { + const current = root.getAttribute('data-theme') === 'light' ? 'light' : 'dark'; + const next = current === 'light' ? 'dark' : 'light'; + applyTheme(next); + try { localStorage.setItem('theme', next); } catch (e) { /* ignore */ } + }); + } + + /* ============================================================ + 3. Project category filter + ============================================================ */ + const chips = document.querySelectorAll('.filter-chip'); + const projects = document.querySelectorAll('.project[data-category]'); + + chips.forEach(chip => { + chip.addEventListener('click', () => { + const filter = chip.dataset.filter; + + chips.forEach(c => { + c.classList.remove('active'); + c.setAttribute('aria-pressed', 'false'); + }); + chip.classList.add('active'); + chip.setAttribute('aria-pressed', 'true'); + + projects.forEach(p => { + let match; + if (filter === 'all') { + match = true; + } else if (filter === 'current') { + // "current" cross-cuts categories — match any project with a current-tag + match = p.querySelector('.current-tag') !== null; + } else { + match = p.dataset.category === filter; + } + p.classList.toggle('hidden', !match); + }); + }); + }); + + // Initialize aria-pressed state on the active chip + document.querySelector('.filter-chip.active')?.setAttribute('aria-pressed', 'true'); + + /* ============================================================ + 4. Live clock — Buffalo ET, updates every second + ============================================================ */ + const clockTime = document.getElementById('clockTime'); + const clockDate = document.getElementById('clockDate'); + + if (clockTime && clockDate) { + const tzOptions = { timeZone: 'America/New_York' }; + const timeFmt = new Intl.DateTimeFormat('en-US', { + ...tzOptions, + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false, + }); + const dateFmt = new Intl.DateTimeFormat('en-US', { + ...tzOptions, + weekday: 'short', + month: 'short', + day: 'numeric', + }); + + const tick = () => { + const now = new Date(); + clockTime.textContent = timeFmt.format(now); + clockDate.textContent = dateFmt.format(now).toLowerCase(); + }; + tick(); + setInterval(tick, 1000); + } + + /* ============================================================ + 5. Command palette — type in the top bar to navigate + ------------------------------------------------------------ + Accepted commands map to sections. Plain section names work + too (e.g. "projects" === "tree projects"). Special: + help — show available commands inline + clear — empty the input + ============================================================ */ + const cmdInput = document.getElementById('commandInput'); + + if (cmdInput) { + // Map: command (lowercase) → target section id + const commandMap = { + // canonical commands (mirror the section headers) + 'cat about.md': 'about', + 'cat about': 'about', + 'ls work': 'work', + 'ls -la work': 'work', + 'ls work/': 'work', + 'grep skills': 'skills', + 'grep -r skills': 'skills', + 'grep -r skills/': 'skills', + './animate.sh': 'skills', + 'animate.sh': 'skills', + 'tree projects': 'projects', + 'tree projects/': 'projects', + 'cat process.md': 'process', + 'cat process': 'process', + 'cat certifications.txt': 'certs', + 'cat certifications': 'certs', + 'echo contact': 'contact', + // short aliases — just type the section name + 'about': 'about', + 'work': 'work', + 'skills': 'skills', + 'projects': 'projects', + 'process': 'process', + 'certs': 'certs', + 'certifications': 'certs', + 'contact': 'contact', + }; + + const helpText = 'try: projects · about · work · skills · process · certs · contact · help'; + + const flash = (cls, ms = 600) => { + cmdInput.classList.add(cls); + setTimeout(() => cmdInput.classList.remove(cls), ms); + }; + + const runCommand = (raw) => { + // Normalize: lowercase, collapse runs of whitespace, then close the gap + // between './' or '/' and any following text so './ animate.sh' → './animate.sh'. + const cmd = raw + .trim() + .toLowerCase() + .replace(/\s+/g, ' ') + .replace(/(\.?\/)\s+/g, '$1'); + if (!cmd) return; + + if (cmd === 'help' || cmd === '?' || cmd === 'ls' || cmd === 'ls -la') { + cmdInput.value = ''; + cmdInput.placeholder = helpText; + flash('success'); + // Restore the regular placeholder after a few seconds + setTimeout(() => { cmdInput.placeholder = defaultPlaceholder; }, 4500); + return; + } + + if (cmd === 'clear' || cmd === 'cls') { + cmdInput.value = ''; + flash('success'); + return; + } + + if (cmd === 'whoami') { + cmdInput.value = ''; + cmdInput.placeholder = 'adni — see About section ↓'; + flash('success'); + setTimeout(() => { cmdInput.placeholder = defaultPlaceholder; }, 3500); + document.getElementById('about')?.scrollIntoView({ behavior: 'smooth' }); + return; + } + + const target = commandMap[cmd]; + if (target) { + const el = document.getElementById(target); + if (el) { + flash('success'); + el.scrollIntoView({ behavior: 'smooth' }); + // Clear input after a beat so the success flash is visible + setTimeout(() => { cmdInput.value = ''; }, 700); + } + } else { + flash('error', 700); + cmdInput.placeholder = `command not found: ${raw.trim().slice(0, 30)} — try "help"`; + setTimeout(() => { cmdInput.placeholder = defaultPlaceholder; }, 3000); + } + }; + + // Default placeholder rotates between a few hints + const placeholders = [ + 'type "tree projects" to see what I\'ve built ↓', + 'try "tree projects" — that\'s where the good stuff is', + 'type a command (try "help" or "projects")', + 'type "projects" — recruiter shortcut', + ]; + let defaultPlaceholder = placeholders[0]; + + // Pick a placeholder per page load, weighted toward the projects nudge + const pickPlaceholder = () => { + const idx = Math.floor(Math.random() * placeholders.length); + defaultPlaceholder = placeholders[idx]; + cmdInput.placeholder = defaultPlaceholder; + }; + pickPlaceholder(); + + cmdInput.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + e.preventDefault(); + runCommand(cmdInput.value); + } else if (e.key === 'Escape') { + cmdInput.value = ''; + cmdInput.blur(); + } + }); + + // Keyboard shortcut: "/" focuses the input from anywhere on the page + document.addEventListener('keydown', (e) => { + if (e.key === '/' && document.activeElement !== cmdInput + && !/^(input|textarea|select)$/i.test(document.activeElement?.tagName || '')) { + e.preventDefault(); + cmdInput.focus(); + } + }); + } + + /* ============================================================ + 6. Footer "uptime" — a small honest detail, no fake commits + ============================================================ */ + const uptimeEl = document.getElementById('footerUptime'); + if (uptimeEl) { + // Calculate how long ago an arbitrary "session start" was — + // resets per page load, so it really is the visitor's session. + const start = Date.now(); + const fmt = () => { + const s = Math.floor((Date.now() - start) / 1000); + const m = Math.floor(s / 60); + const ss = String(s % 60).padStart(2, '0'); + uptimeEl.textContent = `${m}:${ss}`; + }; + fmt(); + setInterval(fmt, 1000); + } +})(); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..3ffa312 --- /dev/null +++ b/style.css @@ -0,0 +1,1045 @@ +/* ============================================================ + Adni Onoh — Interactive Resume + Theme: Terminal · Green primary · Amber secondary + ============================================================ */ + +:root { + --bg: #0d0d0c; + --bg-elev: #161614; + --bg-soft: #1c1c1a; + --fg: #e8e6df; + --fg-dim: #9a958a; + --fg-faint: #56524a; + --accent: #7ec699; /* sage green — primary (systems, current category indicator) */ + --accent-dim: #5fa97a; + --magenta: #d96f9e; /* tools */ + --cyan: #7fb3c4; /* web */ + --violet: #b794d9; /* fun & demos */ + --secondary: #ffb454; /* warm amber — current status badge, play buttons */ + --red: #e06c75; + --border: rgba(126, 198, 153, 0.15); + --grid: rgba(255, 255, 255, 0.025); +} + +/* Light mode — HackerRank-inspired editor palette */ +[data-theme="light"] { + --bg: #f8f9fa; + --bg-elev: #ffffff; + --bg-soft: #eef0f3; + --fg: #1a2734; + --fg-dim: #5b6573; + --fg-faint: #8a93a0; + --accent: #2c8d4f; + --accent-dim: #1f6a3a; + --magenta: #b13f73; + --cyan: #2d7a8e; + --violet: #7c4dac; /* darker violet for AA contrast on white */ + --secondary: #c97d2a; + --red: #c44a4a; + --border: rgba(44, 141, 79, 0.18); + --grid: rgba(0, 0, 0, 0.025); +} + +/* Smooth theme transitions */ +body, .window-chrome, .skill-block, .project, .entry, .stat, +.contact-card, .cert-card, .demo-note, .bar-track, .bar-fill, +.filter-chip, .animate-btn, .cat-tag, .play-link, .repo-link, +.hero-links a, .command-input, .process-col { + transition: background-color 0.25s ease, color 0.25s ease, + border-color 0.25s ease, box-shadow 0.25s ease; +} + +/* ============================================================ + Accessibility + ============================================================ */ + +/* Skip-to-content link: invisible until tabbed-to */ +.skip-link { + position: absolute; + top: -100px; + left: 12px; + z-index: 200; + padding: 10px 16px; + background: var(--accent); + color: var(--bg); + text-decoration: none; + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; + border-radius: 4px; + transition: top 0.18s ease; +} +.skip-link:focus { + top: 12px; + outline: 2px solid var(--secondary); + outline-offset: 2px; +} + +/* Visible focus indicators on every interactive element. + :focus-visible only shows the ring for keyboard users, not mouse clicks. */ +a:focus-visible, +button:focus-visible, +input:focus-visible, +.contact-card:focus-visible, +.cert-card:focus-visible, +.skill-list li:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: 2px; +} +.command-input:focus-visible { + outline: none; /* caret + colored border is enough; ring would crowd the chrome */ +} + +/* Respect prefers-reduced-motion: shut off / shorten anything that loops or sweeps */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.001ms !important; + scroll-behavior: auto !important; + } + /* Section rise-in: just appear, don't slide */ + .section { opacity: 1; transform: none; } + /* Pulse dot: hold steady instead of fading */ + .status::before, .current-tag::before { animation: none; } + /* Theme icon: no spin on hover */ + .theme-toggle:hover .theme-icon { transform: none; } + /* Bar fill: still works, just instantaneous */ + .bar-fill { transition: width 0.001ms; } + .bar-fill::after { display: none; } +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html { scroll-behavior: smooth; } + +body { + background: var(--bg); + background-image: + linear-gradient(var(--grid) 1px, transparent 1px), + linear-gradient(90deg, var(--grid) 1px, transparent 1px); + background-size: 32px 32px; + color: var(--fg); + font-family: 'JetBrains Mono', ui-monospace, monospace; + font-size: 14px; + line-height: 1.65; + min-height: 100vh; + letter-spacing: 0.01em; + overflow-x: hidden; +} + +/* Subtle scanline / vignette */ +body::before { + content: ''; + position: fixed; + inset: 0; + background: radial-gradient(ellipse at center, transparent 0%, transparent 60%, rgba(0,0,0,0.5) 100%); + pointer-events: none; + z-index: 100; + transition: background 0.25s ease; +} +[data-theme="light"] body::before { + background: radial-gradient(ellipse at center, transparent 0%, transparent 70%, rgba(0,0,0,0.04) 100%); +} + +/* ============================================================ + Window chrome (top bar) + ============================================================ */ +.window-chrome { + position: sticky; + top: 0; + z-index: 50; + background: var(--bg-elev); + border-bottom: 1px solid var(--border); + padding: 12px 20px; + display: flex; + align-items: center; + gap: 16px; + backdrop-filter: blur(8px); +} + +.dots { display: flex; gap: 7px; flex-shrink: 0; } +.dot { width: 11px; height: 11px; border-radius: 50%; } +.dot-r { background: #ff5f56; } +.dot-y { background: #ffbd2e; } +.dot-g { background: #27c93f; } + +/* Command input — replaces the static cursor with a real interactive prompt */ +.window-title { + flex: 1; + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + font-size: 12px; + color: var(--fg-dim); +} +.window-title .prefix { color: var(--fg-dim); } +.window-title .prefix .path { color: var(--fg); } +.window-title .prompt-glyph { color: var(--magenta); margin-right: 2px; } + +.command-input-wrap { + position: relative; + display: inline-flex; + align-items: center; + gap: 4px; + flex: 1 1 auto; + min-width: 0; + max-width: 480px; +} +.command-input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: 'JetBrains Mono', ui-monospace, monospace; + font-size: 12px; + letter-spacing: 0.01em; + padding: 4px 0; + width: 100%; + min-width: 0; + caret-color: var(--accent); +} +.command-input::placeholder { + color: var(--fg-faint); + font-style: italic; + opacity: 0.85; +} +.command-input.error { + color: var(--red); + animation: shake 0.35s; +} +.command-input.success { + color: var(--accent); +} +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-3px); } + 75% { transform: translateX(3px); } +} + +.window-tabs { + display: flex; + gap: 20px; + font-size: 12px; + flex-shrink: 0; +} +.window-tabs a { + color: var(--fg-dim); + text-decoration: none; + transition: color 0.2s; +} +.window-tabs a:hover { color: var(--accent); } + +/* Theme toggle */ +.theme-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + margin-left: 8px; + padding: 5px 10px; + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + color: var(--fg-dim); + background: transparent; + border: 1px solid var(--border); + cursor: pointer; + letter-spacing: 0.05em; + text-transform: lowercase; + transition: all 0.2s; + flex-shrink: 0; +} +.theme-toggle:hover { + color: var(--accent); + border-color: var(--accent); + background: var(--bg-soft); +} +.theme-toggle .theme-icon { + font-size: 13px; + display: inline-block; + transition: transform 0.4s ease; +} +.theme-toggle:hover .theme-icon { transform: rotate(180deg); } + +/* ============================================================ + Main container + ============================================================ */ +.shell { + max-width: 980px; + margin: 0 auto; + padding: 48px 32px 96px; +} + +.prompt { + color: var(--accent); + font-weight: 500; + user-select: none; +} +.prompt::before { content: '$ '; color: var(--magenta); } + +.section { + margin: 64px 0; + opacity: 0; + transform: translateY(8px); + animation: rise 0.7s ease forwards; +} +.section:nth-child(1) { animation-delay: 0.05s; } +.section:nth-child(2) { animation-delay: 0.15s; } +.section:nth-child(3) { animation-delay: 0.25s; } +.section:nth-child(4) { animation-delay: 0.35s; } +.section:nth-child(5) { animation-delay: 0.45s; } + +@keyframes rise { + to { opacity: 1; transform: translateY(0); } +} + +.cmd { + font-size: 13px; + color: var(--fg-dim); + margin-bottom: 20px; + display: flex; + align-items: baseline; + gap: 8px; +} +.cmd .out { color: var(--fg-faint); margin-left: 12px; font-size: 11px; } +.cmd-demo { margin-top: 64px; } + +/* ============================================================ + Hero + ============================================================ */ +.hero { + margin-top: 24px; + padding: 40px 0 32px; + border-bottom: 1px dashed var(--border); +} + +.hero-meta { + display: flex; + gap: 14px; + align-items: center; + font-size: 11px; + color: var(--fg-faint); + margin-bottom: 24px; + flex-wrap: wrap; +} +.status { + display: inline-flex; + align-items: center; + gap: 6px; +} +.status::before { + content: ''; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--secondary); + box-shadow: 0 0 8px var(--secondary); + animation: pulse 2s ease infinite; +} +@keyframes pulse { 50% { opacity: 0.4; } } + +/* Live clock & date — sits in the hero meta line, green */ +.clock { + color: var(--accent); + display: inline-flex; + align-items: center; + gap: 8px; + font-family: 'JetBrains Mono', monospace; +} +.clock .clock-sep { color: var(--fg-faint); } +.clock .clock-tz { color: var(--accent-dim); font-size: 10px; } + +.hero h1 { + font-family: 'JetBrains Mono', monospace; + font-size: clamp(2.4rem, 6vw, 4rem); + font-weight: 700; + line-height: 1.05; + letter-spacing: -0.03em; + margin-bottom: 16px; +} +.hero h1 .accent { color: var(--accent); } +.hero h1 .dim { color: var(--fg-faint); font-weight: 300; } + +.hero-tagline { + color: var(--fg-dim); + font-size: 15px; + max-width: 580px; + margin-bottom: 28px; + font-family: 'IBM Plex Mono', monospace; +} +.hero-tagline em { color: var(--accent); font-style: normal; } + +.hero-links { + display: flex; + gap: 8px; + flex-wrap: wrap; + font-size: 12px; +} +.hero-links a { + color: var(--fg); + text-decoration: none; + padding: 8px 14px; + border: 1px solid var(--border); + background: var(--bg-elev); + transition: all 0.2s; +} +.hero-links a:hover { + border-color: var(--accent); + color: var(--accent); + transform: translate(-1px, -1px); + box-shadow: 2px 2px 0 var(--accent-dim); +} +.hero-links a.primary { + background: var(--accent); + color: var(--bg); + border-color: var(--accent); +} +.hero-links a.primary:hover { + background: var(--bg); + color: var(--accent); +} + +/* ============================================================ + About + ============================================================ */ +.about-block { + padding-left: 24px; + border-left: 2px solid var(--accent-dim); + font-family: 'IBM Plex Mono', monospace; + color: var(--fg); + max-width: 700px; +} +.about-block p { margin-bottom: 12px; } +.about-block .comment { color: var(--fg-faint); font-style: italic; font-size: 12px; } +.about-block .inline-accent { color: var(--accent); } +.inline-accent { color: var(--accent); } +.inline-current { color: var(--secondary); font-weight: 500; } + +/* Stats / quick facts */ +.stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 1px; + background: var(--border); + margin-top: 32px; + border: 1px solid var(--border); +} +.stat { + background: var(--bg-elev); + padding: 18px 20px; +} +.stat-label { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--fg-faint); + margin-bottom: 6px; +} +.stat-value { color: var(--accent); font-size: 15px; font-weight: 500; } + +/* ============================================================ + Experience entries + ============================================================ */ +.entry { + padding: 24px 0; + border-bottom: 1px dashed var(--border); + display: grid; + grid-template-columns: 140px 1fr; + gap: 24px; +} +.entry:last-child { border-bottom: none; } +.entry-meta { + color: var(--fg-faint); + font-size: 11px; + letter-spacing: 0.05em; +} +.entry-title { + font-size: 16px; + color: var(--fg); + margin-bottom: 4px; + font-weight: 500; +} +.entry-title .at { color: var(--accent); } +.entry-role { color: var(--fg-dim); font-size: 13px; margin-bottom: 10px; } +.entry-desc { color: var(--fg-dim); font-size: 13px; max-width: 560px; } +.entry-desc ul { margin-top: 8px; list-style: none; padding-left: 4px; } +.entry-desc li { + padding-left: 16px; + position: relative; + margin-bottom: 4px; +} +.entry-desc li::before { + content: '→'; + position: absolute; + left: 0; + color: var(--accent); +} + +/* ============================================================ + Skills + ============================================================ */ +.skill-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 24px; + margin-top: 12px; +} +.skill-block { padding: 20px; background: var(--bg-elev); border: 1px solid var(--border); } +.skill-block h4 { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--accent); + margin-bottom: 14px; + font-weight: 500; +} +.skill-list { + list-style: none; + display: flex; + flex-wrap: wrap; + gap: 6px; +} +.skill-list li { + padding: 4px 10px; + font-size: 12px; + color: var(--fg); + background: var(--bg-soft); + border: 1px solid transparent; + transition: all 0.15s; +} +.skill-list li:hover { + border-color: var(--accent); + color: var(--accent); +} + +/* CSS Demo subsection */ +.css-demo { margin-top: 16px; } +.demo-note { + background: var(--bg-elev); + border: 1px dashed var(--border); + border-left: 3px solid var(--accent); + padding: 16px 20px; + font-size: 13px; + color: var(--fg-dim); + font-family: 'IBM Plex Mono', monospace; + margin-bottom: 28px; + max-width: 760px; + line-height: 1.6; +} +.demo-note-label { color: var(--accent); font-weight: 500; margin-right: 4px; } +.demo-note code { + color: var(--magenta); + background: var(--bg-soft); + padding: 1px 6px; + font-size: 12px; + border-radius: 2px; +} +.demo-note strong { color: var(--fg); font-weight: 500; } + +.skill-bars { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px 32px; + max-width: 760px; + margin-bottom: 24px; +} +.bar-item { display: flex; flex-direction: column; gap: 6px; } +.bar-row { display: flex; justify-content: space-between; font-size: 12px; } +.bar-name { color: var(--fg); } +.bar-pct { color: var(--fg-faint); font-family: 'JetBrains Mono', monospace; } +.bar-track { + height: 6px; + background: var(--bg-elev); + border: 1px solid var(--border); + overflow: hidden; + position: relative; +} +.bar-fill { + height: 100%; + width: 0; + background: linear-gradient(90deg, var(--accent-dim) 0%, var(--accent) 100%); + transition: width 1.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; +} +.bar-fill::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.15), transparent); + transform: translateX(-100%); +} +.bar-fill.animated::after { animation: shimmer 1.4s ease-out; } +@keyframes shimmer { to { transform: translateX(100%); } } + +.animate-btn { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + color: var(--bg); + background: var(--accent); + border: 1px solid var(--accent); + padding: 10px 18px; + cursor: pointer; + transition: all 0.2s; + letter-spacing: 0.02em; + display: inline-flex; + align-items: center; + gap: 8px; +} +.animate-btn:hover { + background: transparent; + color: var(--accent); + transform: translate(-1px, -1px); + box-shadow: 2px 2px 0 var(--accent-dim); +} +.animate-btn .btn-prompt { color: var(--bg); opacity: 0.7; font-weight: 500; } +.animate-btn:hover .btn-prompt { color: var(--magenta); opacity: 1; } +.animate-btn.filled { + background: transparent; + color: var(--accent); + border-color: var(--accent-dim); +} +.animate-btn.filled .btn-prompt { color: var(--magenta); opacity: 1; } +.animate-btn.filled:hover { + background: var(--bg-elev); + border-color: var(--accent); +} + +/* ============================================================ + Project filter bar + ============================================================ */ +.filter-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + margin-bottom: 14px; +} +.filter-label { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: var(--fg-faint); + margin-right: 4px; +} +.filter-chip { + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + color: var(--fg-dim); + background: var(--bg-elev); + border: 1px solid var(--border); + padding: 6px 12px; + cursor: pointer; + transition: all 0.18s; + letter-spacing: 0.02em; + display: inline-flex; + align-items: center; + gap: 6px; +} +.filter-chip:hover { + color: var(--accent); + border-color: var(--accent-dim); +} +.filter-chip.active { + background: var(--accent); + color: var(--bg); + border-color: var(--accent); +} +.filter-chip .count { + font-size: 10px; + opacity: 0.7; + padding: 1px 5px; + background: rgba(0,0,0,0.18); + border-radius: 2px; +} +.filter-chip.active .count { background: rgba(255,255,255,0.18); } + +/* Current filter — amber to match the current-tag (cross-cuts categories) */ +.filter-chip-current { + color: var(--secondary); + border-color: rgba(255, 180, 84, 0.35); +} +.filter-chip-current:hover { + color: var(--secondary); + border-color: var(--secondary); +} +.filter-chip-current.active { + background: var(--secondary); + color: var(--bg); + border-color: var(--secondary); +} + +.projects-note { + font-family: 'IBM Plex Mono', monospace; + font-size: 12px; + color: var(--fg-faint); + margin-bottom: 20px; + font-style: italic; +} + +/* ============================================================ + Project cards + ============================================================ */ +.projects { display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); } +.project { + background: var(--bg-elev); + padding: 28px 28px; + transition: background 0.2s; +} +.project:hover { background: var(--bg-soft); } +.project-header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 16px; + margin-bottom: 8px; + flex-wrap: wrap; +} +.project-name { + font-size: 17px; + color: var(--fg); + font-weight: 500; +} +.project-name::before { content: '└─ '; color: var(--accent); margin-right: 6px; } +.project-tech { + font-size: 11px; + color: var(--fg-faint); + letter-spacing: 0.05em; +} +.project-desc { color: var(--fg-dim); font-size: 13px; max-width: 680px; margin-top: 6px; } +.project-desc ul { margin-top: 8px; list-style: none; padding-left: 4px; } +.project-desc li { + padding-left: 16px; + position: relative; + margin-bottom: 3px; +} +.project-desc li::before { + content: '→'; + position: absolute; + left: 0; + color: var(--accent); +} + +/* Category tags */ +.cat-tag { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--accent); + background: var(--bg-soft); + border: 1px solid var(--border); + padding: 2px 8px; + margin-left: 10px; + vertical-align: middle; + font-weight: 400; + border-radius: 2px; +} +.project[data-category="systems"] .cat-tag { color: var(--accent); border-color: rgba(126, 198, 153, 0.3); } +.project[data-category="web"] .cat-tag { color: var(--cyan); border-color: rgba(127, 179, 196, 0.3); } +.project[data-category="fun"] .cat-tag { color: var(--violet); border-color: rgba(183, 148, 217, 0.3); } +.project[data-category="tools"] .cat-tag { color: var(--magenta); border-color: rgba(217, 111, 158, 0.3); } + +/* "Current" status badge — cross-cuts categories, sits next to cat-tag. + Amber so it's visually distinct from the green "systems" tag. */ +.current-tag { + display: inline-flex; + align-items: center; + gap: 5px; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--secondary); + background: rgba(255, 180, 84, 0.08); + border: 1px solid rgba(255, 180, 84, 0.35); + padding: 2px 8px; + margin-left: 6px; + vertical-align: middle; + font-weight: 500; + border-radius: 2px; +} +.current-tag::before { + content: ''; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--secondary); + box-shadow: 0 0 6px var(--secondary); + animation: pulse 2s ease infinite; +} +[data-theme="light"] .current-tag { + background: rgba(201, 125, 42, 0.08); + border-color: rgba(201, 125, 42, 0.4); +} + +/* Project action buttons (play / repo) */ +.project-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; +} +.play-link, .repo-link { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 14px; + font-family: 'JetBrains Mono', monospace; + font-size: 12px; + text-decoration: none; + letter-spacing: 0.02em; + transition: all 0.2s; +} +.play-link { + color: var(--secondary); + background: var(--bg-soft); + border: 1px solid rgba(255, 180, 84, 0.25); +} +.play-link:hover { + background: var(--secondary); + color: var(--bg); + border-color: var(--secondary); + transform: translate(-1px, -1px); + box-shadow: 2px 2px 0 rgba(255, 180, 84, 0.3); +} +.repo-link { + color: var(--accent); + background: var(--bg-soft); + border: 1px solid var(--border); +} +.repo-link:hover { + background: var(--accent); + color: var(--bg); + border-color: var(--accent); + transform: translate(-1px, -1px); + box-shadow: 2px 2px 0 var(--accent-dim); +} + +.project[data-category] { + transition: opacity 0.25s, transform 0.25s, max-height 0.3s; +} +.project.hidden { display: none; } + +/* ============================================================ + Process & Practice + ============================================================ */ +.process-intro { + font-family: 'IBM Plex Mono', monospace; + font-size: 14px; + color: var(--fg-dim); + max-width: 740px; + margin-bottom: 32px; + padding-left: 24px; + border-left: 2px solid var(--accent-dim); +} + +.process-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 1px; + background: var(--border); + border: 1px solid var(--border); +} + +.process-col { + background: var(--bg-elev); + padding: 28px 30px; +} + +.process-col-title { + font-size: 17px; + color: var(--fg); + font-weight: 500; + margin-bottom: 14px; + letter-spacing: -0.005em; + display: flex; + flex-direction: column; + gap: 4px; +} +.process-col-tag { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.18em; + color: var(--accent); + font-weight: 500; +} + +.process-col-blurb { + font-size: 13px; + color: var(--fg-dim); + margin-bottom: 18px; + font-family: 'IBM Plex Mono', monospace; +} +.process-col-blurb em { + color: var(--accent); + font-style: normal; +} + +.process-list { + list-style: none; + padding: 0; +} +.process-list li { + position: relative; + padding-left: 20px; + margin-bottom: 12px; + font-size: 13px; + color: var(--fg-dim); + line-height: 1.55; +} +.process-list li:last-child { margin-bottom: 0; } +.process-list li::before { + content: '→'; + position: absolute; + left: 0; + top: 0; + color: var(--accent); + font-weight: 500; +} +.process-list li strong { + color: var(--fg); + font-weight: 500; +} +.process-list li em { + color: var(--accent); + font-style: normal; +} +.process-list li code { + color: var(--magenta); + background: var(--bg-soft); + padding: 1px 5px; + font-size: 11px; + border-radius: 2px; + font-family: 'JetBrains Mono', monospace; +} +.process-list li kbd { + color: var(--fg); + background: var(--bg-soft); + border: 1px solid var(--border); + padding: 1px 6px; + font-size: 11px; + border-radius: 3px; + font-family: 'JetBrains Mono', monospace; + box-shadow: 0 1px 0 var(--border); +} + +/* ============================================================ + Certifications + ============================================================ */ +.cert-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1px; + background: var(--border); + border: 1px solid var(--border); +} +.cert-card { + background: var(--bg-elev); + padding: 22px 24px; + text-decoration: none; + color: inherit; + display: block; + transition: background 0.2s; + position: relative; +} +.cert-card:hover { background: var(--bg-soft); } +.cert-card:hover .cert-link { color: var(--accent); } +.cert-issuer { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.15em; + color: var(--fg-faint); + margin-bottom: 8px; +} +.cert-title { + font-size: 16px; + color: var(--fg); + font-weight: 500; + margin-bottom: 14px; +} +.cert-link { + font-family: 'JetBrains Mono', monospace; + font-size: 11px; + color: var(--fg-dim); + transition: color 0.2s; + letter-spacing: 0.02em; +} + +/* ============================================================ + Contact + ============================================================ */ +.contact-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 12px; + margin-top: 16px; +} +.contact-card { + display: block; + padding: 18px 20px; + background: var(--bg-elev); + border: 1px solid var(--border); + text-decoration: none; + color: var(--fg); + transition: all 0.2s; +} +.contact-card:hover { + border-color: var(--accent); + transform: translate(-2px, -2px); + box-shadow: 4px 4px 0 var(--accent-dim); +} +.contact-card-label { + font-size: 10px; + color: var(--fg-faint); + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 4px; +} +.contact-card-value { font-size: 13px; color: var(--accent); } + +/* ============================================================ + Footer + ============================================================ */ +footer { + margin-top: 80px; + padding-top: 20px; + border-top: 1px dashed var(--border); + color: var(--fg-faint); + font-size: 11px; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + font-family: 'JetBrains Mono', monospace; +} +footer .accent { color: var(--accent); } +footer .uptime-marker { color: var(--secondary); } + +/* ============================================================ + Responsive + ============================================================ */ +@media (max-width: 880px) { + .window-tabs { display: none; } + .window-title { + flex: 1; + justify-content: flex-start; + } + .command-input-wrap { + flex: 1 1 auto; + max-width: none; + } +} + +@media (max-width: 640px) { + .shell { padding: 32px 20px 64px; } + .entry { grid-template-columns: 1fr; gap: 8px; } + .hero-meta { font-size: 10px; } + .theme-toggle .theme-label { display: none; } + .window-title .prefix { display: none; } +} \ No newline at end of file From d2ad28e756e5a99b083b778e2fbd621a5938e709 Mon Sep 17 00:00:00 2001 From: Adni Onoh Date: Wed, 13 May 2026 19:48:00 -0400 Subject: [PATCH 2/4] improved wording. fleshed out project details --- index.html | 346 +++++++++++++++++++++++++---------------------------- style.css | 115 +++++++++++++++++- 2 files changed, 275 insertions(+), 186 deletions(-) diff --git a/index.html b/index.html index 95e4bb6..8486232 100644 --- a/index.html +++ b/index.html @@ -4,16 +4,16 @@ adni onoh — ~/portfolio - + - + - +
    @@ -48,50 +48,50 @@ light
    - +
    - +
    - open to new-grad SWE roles · open to learn + open to learn · - buffalo, ny · ET + + buffalo, ny · + + + · + —:—:— + ET + + · graduating spring 2026 - · - - - · - —:—:— - ET -

    hi, I'm
    adni onoh.

    - software engineer working across distributed systems and full-stack web. recently: built a Raft consensus cluster in Go, a Kademlia-based DHT, and shipped production features at a healthtech startup. + Distributed systems engineer and full-stack web developer. Currently interning at a healthtech startup writing software that's helping people track and care for their mental health, building a personal project that combines my web and distributed systems experience (see Distributed Microservices Demo Suite ↓), and interview prepping

    - +
    cat about.md # readme · markdown

    // the short version

    -

    I'm graduating from the University at Buffalo in Spring 2026 with a B.A. in Computer Science and a concentration in Communication.

    -

    My coursework and side work split between two things that pull on each other: distributed systems & backend (where I've built a Raft consensus cluster, a Kademlia DHT, and a concurrent memory allocator) and full-stack web development (where I currently ship production features at Nara Therapy).

    -

    The communication concentration shows up in how I work — writing user stories, reviewing teammates' code, and explaining trade-offs to non-engineers. It's been more useful than I expected.

    -

    // status: open to learn, open to roles starting summer 2026

    +

    For the last four years I've focused on systems engineering (more recently distributed systems in particular) and web development, alongside a concentration in communication.

    +

    In that time, I've gotten much better thinking about and understanding ideas/tasks from a software perspective, while still being able to communicate effectively with non-technical people. I like to think of it as being bilingual! (this is most likely cope because the only language I can speak is English)

    +

    // status: open to roles starting fall 2026

    - +
    degree
    @@ -111,112 +111,108 @@

    - +
    ls -la work/ # current role
    - +
    Software Engineer @ Nara Therapy
    healthtech · agile · full-stack
    - Building production features on a healthcare web app inside an agile team. Recent work: + Working at a fast-paced startup with an agile team building features to help people care for their mental health better. +

    (see Nara Therapy - Production Features in the projects section below ↓)

    +
    - +
    grep -r skills/ # grouped by frequency of use

    // daily use

      -
    • Go
    • JavaScript
    • Python
    • -
    • HTML/CSS
    • -
    • Node.js
    • -
    • Git
    • +
    • Go
    • +
    • C
    • +
    • HTML&CSS
    • +
    • Node.js & Express & EJS
    • +
    • Git/GitHub
    • +
    • Figma
    • +

    // comfortable with

    • TypeScript
    • -
    • C
    • React
    • -
    • Express
    • -
    • Flask
    • -
    • PostgreSQL
    • MongoDB
    • MySQL
    • -
    • Java
    • -
    • Figma
    • +
    • PostgreSQL
    • +
    • jQuery

    // familiar with

      +
    • Flask
    • OCaml
    • -
    • Scala
    • PHP
    • -
    • EJS
    • +
    • Scala
    • +
    • Java
    • R
    • -
    • jQuery
    • -
    • Protobuf
    • -
    • RPC

    // practices

    • Scrum/Agile
    • -
    • User stories
    • Shell scripting
    • Debugging
    • Code review
    • -
    • Jira
    • -
    • Trello
    - +
    ./animate.sh # css demo · not a self-assessment
    // note: - the bars below are a CSS animation demo — a chance to play with transition, transform, and @keyframes. they show how much I've used each language while building this site, not any claim of skill level. for an honest proficiency picture, see the tags above. + the bars below are a simple CSS animation demo, a chance to play with transition, transform, and @keyframes. not a claim of skill level. for an honest proficiency picture, see the tags above.
    -
    CSS90%
    -
    +
    CSS92%
    +
    -
    HTML85%
    -
    +
    JavaScript68%
    +
    -
    JavaScript75%
    -
    +
    HTML81%
    +
    -
    Go70%
    -
    +
    Go40%
    +
    -
    Python60%
    -
    +
    Python55%
    +
    -
    C45%
    -
    +
    C82%
    +
    - +
    -
    tree projects/ # 16 entries · filter by category
    - +
    tree projects/ # 13 entries · filter by category
    +
    - - - - + + + +
    - +

    // note: OOP principles and Git/GitHub version control applied across all projects. ● current marks projects I'm actively building right now.

    - +
    - - - -
    -
    -
    - Distributed Task Scheduler - systems - current -
    -
    Go · goroutines · fault tolerance
    -
    -
    - A small Go service that schedules and dispatches tasks across multiple worker nodes, with retries and failover when a worker drops out. Building it to deepen what I learned from the Raft project — coordination, partial failure, and keeping work moving when something goes wrong. - -
    -
    - -
    -
    -
    - Real-Time Resource Monitor - systems - current -
    -
    C · system calls · WebSocket dashboard
    -
    -
    - A lightweight monitor in C that samples CPU and memory usage in real time and pushes the readings to a browser dashboard over WebSockets. Configurable thresholds trigger live web notifications — useful for catching runaway processes without a heavyweight tool like htop or Grafana. - -
    -
    - -
    + + + + - -
    -
    -
    - Business Process Automation Tool - web - current + Pulling everything I've worked on into one system: independent services in different languages, talking through a secure gateway, health monitoring for services, and graceful degredation when something fails. +
    +
    +
    + + Gateway +
    +
    Single entry point for incoming requests. Routes traffic to the right service and logs request metadata to MongoDB.
    +
    +
    +
    + + Health Node +
    +
    Watches every other service and works with the Gateway to keep the system honest about what's online and responsive.
    +
    +
    +
    + + Invoice Service +
    +
    for creating and formatting invoices from raw data.
    +
    +
    +
    + + Performance Monitor +
    +
    Tracks and reports system resource usage.
    +
    -
    Python · Flask · Google Sheets API · PDF generation
    -
    -
    - A Python-based dashboard that automates a recurring business task end-to-end — for example, pulling line items from a Google Sheet and generating a styled invoice PDF, or maintaining a simple inventory view that updates as stock changes. Built with small Buffalo-area businesses in mind. +

    + On reliability: The Gateway and Health Node are single points of failure in this basic setup. The real-world fix is redundancy; multiple Gateways with instant failover, and a consensus-based Health Node cluster that votes on a leader (directly applying what I learned from the Raft project). Future work will include timeouts and retries, circuit breakers for repeatedly-failing downstreams, and a database fallback so the Gateway keeps running even when MongoDB is down. +

    +

    + Repo structure: /api-gateway · /health-node · /invoice-service · /performance-monitor +

    - + + +
    @@ -322,15 +306,15 @@

    // practices

    JavaScript · React · Figma · QA
    - Current production-side work as a Software Engineer at a healthtech startup. + Current production-side work as a Software Engineer at a healthtech startup. Here are a few things I've contributed to:
    • Responsive feedback modal that maintains consistency across mobile and desktop
    • -
    • Dashboard features: organization contact dates, deletion controls, rating-based filters
    • +
    • Dashboard features: organization contact dates, editing & deletion controls, rating-based filters to help providers identify low-rated sessions efficiently
    • All work follows Figma designs and team QA criteria
    - +
    @@ -341,30 +325,30 @@

    // practices

    Bug fixes · feature work · internal tooling
    - Contributing to the University at Buffalo CSE department's in-house scrum board — a tool the department built and uses to track its own engineering work. Fixing live bugs and adding new features for an existing user base, which means reading other people's code first, working within the existing architecture, and shipping changes carefully. + Contributing to the University at Buffalo CSE department's in-house scrum board. Fixing live bugs and adding new features for an existing user base, which means reading other people's code & documentation first, working within the existing architecture, and shipping changes carefully.
    - + - +
    Consensus Server Cluster systems
    Go · Raft · RPC
    - Built a Raft consensus cluster in Go with full leader election, log replication, and crash-recovery — tolerant of both clean restarts and partial state loss. + Built a Raft-inspired consensus cluster in Go with full leader election, log replication, and crash-recovery. Tolerant of both clean restarts and partial state loss.
    • Implemented leader election with election timeouts and term-based voting
    • Log replication with consistency checks across follower nodes
    • Recovery logic that handles partial state loss on restart
    - +
    Distributed Hash Table (Kademlia) systems
    @@ -378,48 +362,48 @@

    // practices

  • Content-addressing and fault-tolerant data storage across the network
  • - +
    Message Service & Failure Detector systems
    Go · TCP sockets · Protobuf · channels
    - Implemented a multi-process message service, then used that service to build a heartbeat-based failure detector on top of it. + Implemented a multi-process message service, then used that service to build a heartbeat-based failure detector.
    • TCP sockets for cross-network process communication
    • Go channels for in-process communication and coordination
    • Protobuf protocol buffers for data serialization between processes
    - +
    Dynamic Memory Allocator systems
    C · mutexes · concurrency
    - Developed a dynamic, concurrent memory allocator using mutexes and locks to manage allocation, deallocation, and reallocation safely under contention. + Developed a dynamic, concurrent memory allocator using mutexes and locks to manage allocation, deallocation, and reallocation.
      -
    • Thread-safe alloc / free / realloc via mutex-guarded data structures
    • +
    • Thread-safe allocation, freeing, and reallocation under contention
    • Optimized for low fragmentation and reuse of freed blocks
    - + - +
    ChatAppX — Real-time Chat & Video web
    @@ -429,77 +413,76 @@

    // practices

    A real-time web app for text and video chat, with security as a first-class concern.
    • HTML injection prevention, salted password hashing, XSRF & auth tokens
    • -
    • Direct peer-to-peer connections via WebSockets — video chat keeps working through server crashes
    • +
    • Direct peer-to-peer connections via WebSockets so video chat keeps working through server crashes
    • Secure file uploads with content sanitization and validation
    • Comprehensive form validation and error handling
    - +
    Fresumes — Free Resume Platform web
    Node · MongoDB · JSX · Figma
    - Cross-sprint contributions on a real product. Worked the full feature loop — user stories, Figma wireframes, acceptance tests, then implementation. + For the Experiential Learning & Research class at UB. Worked on code documentation, user stories & acceptance tests, Figma wireframes mostly and a few implementations.
      -
    • Designed key features: multi-page resume view, automatic resume display, filters, ad slots, AI interview interface
    • -
    • Built real-time multi-resume upload
    • -
    • Integrated a feedback system with automated email routing
    • +
    • Designed key features: multiple resume uploads with automatic resume display, multi-page resume view, resume filters, ad slots, AI interview interface
    • +
    • Implemented real-time multiple resume uploads and integrated a feedback system with automated email routing
    - + - +
    Simon's Game fun
    HTML · CSS · JavaScript
    - The classic memory game — repeat the sequence of lights and sounds. Built to practice event-driven JS and DOM manipulation. + Repeat the sequence of lights and sounds. Built to practice event-driven JS and DOM manipulation.
    - +
    Dice Game fun
    HTML · CSS · JavaScript
    - Two-player dice roll — the higher number wins. A small exercise in randomness, image swapping, and clean conditional UI. + Two-player dice roll — the higher number wins. You and your buddy choose your player, and whoever wins pays for lunch or something.
    - +
    Drum Kit fun
    HTML · CSS · JavaScript
    - An interactive drum kit — make beats with your keyboard or by clicking the pads. Covers keyboard events, audio playback, and animated button feedback. + Make beats with your keyboard or by clicking the pads. Covers keyboard events, audio playback, and animated button feedback.
    - + - +
    This Interactive Resume tools
    @@ -509,37 +492,36 @@

    // practices

    The site you're reading. Hand-built layout with custom animations, a filter system, a command-palette top bar, and a light/dark theme toggle — no frameworks, no templates.
    - +
    - +
    cat process.md # how this site was built

    - This site itself is a project. So I built it the way I'd build anything else — paying attention to how as much as what. Below are the practices that shaped it. + There are many things to consider when creating anything. Below are the practices that shaped the building of this website

    - +

    // process - Agile, scaled to one + Somewhat Agile

    -

    Solo project, but built like a real one: scoped iterations and feedback-driven rework over big-bang releases.

    +

    I did my best to put the skills I learned to practice without being too stiff considering that this is a solo project

      -
    • Iterative scope — features shipped in small, reviewable slices (content fixes → layout → projects → polish), not one giant rewrite.
    • -
    • Feedback loops — every visible feature went through a "build, review, refine" pass before being kept. The filter chips, command input, and color scheme all changed mid-build because of testing.
    • -
    • Honest scoping — features that weren't worth the cost (autoplay video, full image lightbox) were cut rather than half-shipped.
    • -
    • Coursework — formal agile practice from UB's Software Engineering Concepts course: user stories, sprints, acceptance tests, Scrum ceremonies.
    • +
    • Iterative — I shipped features in small, reviewable slices (content fixes → layout → projects → polish). No one giant rewrite.
    • +
    • Feedback loops — most features went through a "build, review, refine" pass before being kept. I was able to get feedback from some faculty and friends with usability testing.
    • +
    • Coursework — used agile/scrum practice from UB's Software Engineering Concepts course
    - +

    // a11y - Accessibility, not as an afterthought + Making Accessibility a habit

    -

    Informed by UB's Applied Human-Computer Interaction Design course. You can test these — they aren't claims.

    +

    Informed by UB's Applied Human-Computer Interaction and Interface Design course, I picked a few accessibility metrics and made the website comply with them.

    • Keyboard navigable — try Tab through the page; every interactive element is reachable and has a visible focus ring. / jumps straight to the command input.
    • Semantic HTML — proper landmarks (<nav>, <main>, <section>, <footer>), heading hierarchy, ARIA labels on icon-only controls.
    • @@ -549,7 +531,7 @@

    - +
    cat certifications.txt # verified credentials
    @@ -565,7 +547,7 @@

    - +
    echo "let's build something"
    @@ -587,14 +569,14 @@

    - +
    -
    © 2026 adni onoh · buffalo, ny · handwritten html / css / js
    +
    © 2026 adni onoh · buffalo, ny · mostly handwritten html / css / js
    session uptime: 0:00
    - + - + \ No newline at end of file diff --git a/style.css b/style.css index 3ffa312..362f519 100644 --- a/style.css +++ b/style.css @@ -342,16 +342,21 @@ body::before { } @keyframes pulse { 50% { opacity: 0.4; } } -/* Live clock & date — sits in the hero meta line, green */ +/* Location + live clock — clock sits inline after "buffalo, ny ·" */ +.location-clock { + display: inline-flex; + align-items: baseline; + gap: 6px; + flex-wrap: wrap; +} .clock { color: var(--accent); display: inline-flex; - align-items: center; - gap: 8px; + align-items: baseline; + gap: 6px; font-family: 'JetBrains Mono', monospace; } .clock .clock-sep { color: var(--fg-faint); } -.clock .clock-tz { color: var(--accent-dim); font-size: 10px; } .hero h1 { font-family: 'JetBrains Mono', monospace; @@ -372,6 +377,17 @@ body::before { font-family: 'IBM Plex Mono', monospace; } .hero-tagline em { color: var(--accent); font-style: normal; } +.tagline-link { + color: var(--secondary); + text-decoration: underline; + text-decoration-color: rgba(255, 180, 84, 0.4); + text-underline-offset: 3px; + transition: text-decoration-color 0.2s, color 0.2s; +} +.tagline-link:hover { + color: var(--secondary); + text-decoration-color: var(--secondary); +} .hero-links { display: flex; @@ -743,6 +759,97 @@ body::before { .project[data-category="fun"] .cat-tag { color: var(--violet); border-color: rgba(183, 148, 217, 0.3); } .project[data-category="tools"] .cat-tag { color: var(--magenta); border-color: rgba(217, 111, 158, 0.3); } +/* Secondary cat-tag — used when a project genuinely spans two categories. + Always renders in the cyan "web" color since that's the only place we use it now, + but the rule overrides whatever the parent's data-category styling sets. */ +.cat-tag-secondary { + color: var(--cyan) !important; + border-color: rgba(127, 179, 196, 0.3) !important; + margin-left: 4px; +} + +/* Featured project — slightly elevated treatment for the showcase card */ +.project.project-featured { + border-left: 1.5px solid var(--secondary); + padding-left: 32px; +} +[data-theme="light"] .project.project-featured { + border-left-color: var(--secondary); +} + +/* Microservices breakdown grid inside the featured card */ +.microservices { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 1px; + background: var(--border); + border: 1px solid var(--border); + margin: 20px 0 18px; +} +.microservice { + background: var(--bg); + padding: 14px 16px; +} +.microservice-name { + font-size: 13px; + color: var(--fg); + font-weight: 500; + margin-bottom: 6px; + display: flex; + align-items: center; + gap: 8px; +} +.microservice-lang { + display: inline-block; + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--secondary); + background: var(--bg-soft); + border: 1px solid rgba(255, 180, 84, 0.25); + padding: 1px 7px; + border-radius: 2px; + font-weight: 400; +} +.microservice-role { + font-size: 12px; + color: var(--fg-dim); + line-height: 1.5; +} + +.microservices-footnote { + font-size: 12.5px; + color: var(--fg-dim); + padding: 14px 16px; + background: var(--bg); + border: 1px dashed var(--border); + border-left: 1px solid var(--secondary); + margin: 14px 0 12px; + line-height: 1.6; + font-family: 'IBM Plex Mono', monospace; +} +.microservices-footnote strong { + color: var(--secondary); + font-weight: 500; +} + +.microservices-folders { + font-size: 11px; + color: var(--fg-faint); + font-family: 'JetBrains Mono', monospace; + margin-bottom: 12px; + letter-spacing: 0.02em; +} +.microservices-folders code { + color: var(--fg-dim); + background: var(--bg-soft); + padding: 1px 6px; + border-radius: 2px; + font-size: 11px; + border: 1px solid var(--border); +} + /* "Current" status badge — cross-cuts categories, sits next to cat-tag. Amber so it's visually distinct from the green "systems" tag. */ .current-tag { From 87052e6393a8dec00c764de58b49e41bd272c8f5 Mon Sep 17 00:00:00 2001 From: Adni Onoh Date: Wed, 13 May 2026 20:51:16 -0400 Subject: [PATCH 3/4] more wording tweaks. working on the bloated internship project descriptions --- index.html | 121 ++++++++++++++++++++++++++++++++++------------- style.css | 135 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 186 insertions(+), 70 deletions(-) diff --git a/index.html b/index.html index 8486232..def1698 100644 --- a/index.html +++ b/index.html @@ -88,7 +88,7 @@

    // the short version

    For the last four years I've focused on systems engineering (more recently distributed systems in particular) and web development, alongside a concentration in communication.

    -

    In that time, I've gotten much better thinking about and understanding ideas/tasks from a software perspective, while still being able to communicate effectively with non-technical people. I like to think of it as being bilingual! (this is most likely cope because the only language I can speak is English)

    +

    In this time, I've gotten much better thinking about and understanding ideas/tasks from a software perspective, while still being able to communicate effectively with non-technical people. I like to think of it as being bilingual! (most likely cope because I have been unsuccessful at learning any languages other than English)

    // status: open to roles starting fall 2026

    @@ -121,8 +121,8 @@

    Software Engineer @ Nara Therapy
    healthtech · agile · full-stack
    - Working at a fast-paced startup with an agile team building features to help people care for their mental health better. -

    (see Nara Therapy - Production Features in the projects section below ↓)

    + Working at a fast-paced startup with an agile team building features to give people continuous mental health tools that help before, during, and after a session. +

    (see Nara Therapy - Production Features in the projects section below)

    -
    +

    // private repo

    @@ -361,9 +425,7 @@

    // practices

  • Implemented RPC-based node communication for iterative lookups
  • Content-addressing and fault-tolerant data storage across the network
  • -
    - -
    +

    // private repo

    @@ -379,9 +441,7 @@

    // practices

  • Go channels for in-process communication and coordination
  • Protobuf protocol buffers for data serialization between processes
  • -
    - -
    +

    // private repo

    @@ -396,9 +456,7 @@

    // practices

  • Thread-safe allocation, freeing, and reallocation under contention
  • Optimized for low fragmentation and reuse of freed blocks
  • -
    - -
    +

    // private repo

    @@ -417,9 +475,7 @@

    // practices

  • Secure file uploads with content sanitization and validation
  • Comprehensive form validation and error handling
  • -
    - -
    +

    // private repo

    @@ -434,9 +490,7 @@

    // practices

  • Designed key features: multiple resume uploads with automatic resume display, multi-page resume view, resume filters, ad slots, AI interview interface
  • Implemented real-time multiple resume uploads and integrated a feedback system with automated email routing
  • -
    - -
    +

    // private repo

    @@ -448,7 +502,7 @@

    // practices

    HTML · CSS · JavaScript
    - Repeat the sequence of lights and sounds. Built to practice event-driven JS and DOM manipulation. + Repeat the sequence of lights and sounds. Built to practice event-driven JS and DOM manipulation. Try beating my personal record of 21 rounds. @@ -489,7 +543,10 @@

    // practices

    HTML · CSS · JavaScript
    - The site you're reading. Hand-built layout with custom animations, a filter system, a command-palette top bar, and a light/dark theme toggle — no frameworks, no templates. + The site you're reading. I hope you enjoy using it! Try the command input at the top: tree projects works. +
    @@ -511,7 +568,7 @@

    I did my best to put the skills I learned to practice without being too stiff considering that this is a solo project

    • Iterative — I shipped features in small, reviewable slices (content fixes → layout → projects → polish). No one giant rewrite.
    • -
    • Feedback loops — most features went through a "build, review, refine" pass before being kept. I was able to get feedback from some faculty and friends with usability testing.
    • +
    • Feedback loops — most features went through a "build, review, refine" pass before being kept. I was able to get feedback from some faculty and friends along the way (and some usability testing).
    • Coursework — used agile/scrum practice from UB's Software Engineering Concepts course
    @@ -571,7 +628,7 @@

    -
    © 2026 adni onoh · buffalo, ny · mostly handwritten html / css / js
    +
    © 2026 adni onoh · buffalo, ny · mostly self-written html / css / js
    session uptime: 0:00

    diff --git a/style.css b/style.css index 362f519..bd96b1c 100644 --- a/style.css +++ b/style.css @@ -9,34 +9,35 @@ --bg-soft: #1c1c1a; --fg: #e8e6df; --fg-dim: #9a958a; - --fg-faint: #56524a; - --accent: #7ec699; /* sage green — primary (systems, current category indicator) */ + --fg-faint: #727064; /* darkened from #56524a for AA contrast on UI labels */ + --accent: #7ec699; /* sage green — primary */ --accent-dim: #5fa97a; - --magenta: #d96f9e; /* tools */ - --cyan: #7fb3c4; /* web */ - --violet: #b794d9; /* fun & demos */ - --secondary: #ffb454; /* warm amber — current status badge, play buttons */ + --magenta: #d96f9e; /* tools */ + --cyan: #7fb3c4; /* web */ + --violet: #b794d9; /* fun & demos */ + --secondary: #ffb454; /* warm amber — current status badge, play buttons */ --red: #e06c75; --border: rgba(126, 198, 153, 0.15); --grid: rgba(255, 255, 255, 0.025); } -/* Light mode — HackerRank-inspired editor palette */ +/* Light mode — HackerRank-inspired editor palette + All foreground colors meet WCAG AA (4.5:1 for text, 3:1 for UI). */ [data-theme="light"] { --bg: #f8f9fa; --bg-elev: #ffffff; --bg-soft: #eef0f3; --fg: #1a2734; --fg-dim: #5b6573; - --fg-faint: #8a93a0; - --accent: #2c8d4f; + --fg-faint: #737b87; /* darkened from #8a93a0 for AA on white surfaces */ + --accent: #247a44; /* darkened from #2c8d4f to pass AA (4.5+:1 vs white) */ --accent-dim: #1f6a3a; --magenta: #b13f73; --cyan: #2d7a8e; - --violet: #7c4dac; /* darker violet for AA contrast on white */ - --secondary: #c97d2a; + --violet: #7c4dac; + --secondary: #9e5d10; /* darkened from #c97d2a to pass AA (4.95:1 vs white) */ --red: #c44a4a; - --border: rgba(44, 141, 79, 0.18); + --border: rgba(36, 122, 68, 0.18); --grid: rgba(0, 0, 0, 0.025); } @@ -53,7 +54,6 @@ body, .window-chrome, .skill-block, .project, .entry, .stat, Accessibility ============================================================ */ -/* Skip-to-content link: invisible until tabbed-to */ .skip-link { position: absolute; top: -100px; @@ -76,8 +76,6 @@ body, .window-chrome, .skill-block, .project, .entry, .stat, outline-offset: 2px; } -/* Visible focus indicators on every interactive element. - :focus-visible only shows the ring for keyboard users, not mouse clicks. */ a:focus-visible, button:focus-visible, input:focus-visible, @@ -89,10 +87,9 @@ input:focus-visible, border-radius: 2px; } .command-input:focus-visible { - outline: none; /* caret + colored border is enough; ring would crowd the chrome */ + outline: none; } -/* Respect prefers-reduced-motion: shut off / shorten anything that loops or sweeps */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.001ms !important; @@ -100,13 +97,9 @@ input:focus-visible, transition-duration: 0.001ms !important; scroll-behavior: auto !important; } - /* Section rise-in: just appear, don't slide */ .section { opacity: 1; transform: none; } - /* Pulse dot: hold steady instead of fading */ .status::before, .current-tag::before { animation: none; } - /* Theme icon: no spin on hover */ .theme-toggle:hover .theme-icon { transform: none; } - /* Bar fill: still works, just instantaneous */ .bar-fill { transition: width 0.001ms; } .bar-fill::after { display: none; } } @@ -130,7 +123,6 @@ body { overflow-x: hidden; } -/* Subtle scanline / vignette */ body::before { content: ''; position: fixed; @@ -166,7 +158,6 @@ body::before { .dot-y { background: #ffbd2e; } .dot-g { background: #27c93f; } -/* Command input — replaces the static cursor with a real interactive prompt */ .window-title { flex: 1; min-width: 0; @@ -235,7 +226,6 @@ body::before { } .window-tabs a:hover { color: var(--accent); } -/* Theme toggle */ .theme-toggle { display: inline-flex; align-items: center; @@ -342,7 +332,6 @@ body::before { } @keyframes pulse { 50% { opacity: 0.4; } } -/* Location + live clock — clock sits inline after "buffalo, ny ·" */ .location-clock { display: inline-flex; align-items: baseline; @@ -435,7 +424,6 @@ body::before { .inline-accent { color: var(--accent); } .inline-current { color: var(--secondary); font-weight: 500; } -/* Stats / quick facts */ .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); @@ -532,7 +520,6 @@ body::before { color: var(--accent); } -/* CSS Demo subsection */ .css-demo { margin-top: 16px; } .demo-note { background: var(--bg-elev); @@ -673,7 +660,6 @@ body::before { } .filter-chip.active .count { background: rgba(255,255,255,0.18); } -/* Current filter — amber to match the current-tag (cross-cuts categories) */ .filter-chip-current { color: var(--secondary); border-color: rgba(255, 180, 84, 0.35); @@ -726,6 +712,7 @@ body::before { letter-spacing: 0.05em; } .project-desc { color: var(--fg-dim); font-size: 13px; max-width: 680px; margin-top: 6px; } +.project-desc em { color: var(--accent); font-style: normal; } .project-desc ul { margin-top: 8px; list-style: none; padding-left: 4px; } .project-desc li { padding-left: 16px; @@ -739,6 +726,86 @@ body::before { color: var(--accent); } +/* Impact groups inside the Nara card — three subsections (providers / org admins / team) + each with a small uppercase label and a list of contributions with "why this matters" details */ +.impact-group { + margin-top: 18px; + padding: 16px 18px; + background: var(--bg); + border: 1px solid var(--border); + border-left: 2px solid var(--accent-dim); +} +.impact-group-label { + font-family: 'JetBrains Mono', monospace; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.18em; + color: var(--accent); + margin-bottom: 10px; + font-weight: 500; +} +.impact-list { + list-style: none !important; + padding-left: 0 !important; + margin-top: 0 !important; +} +.impact-list li { + padding-left: 16px; + margin-bottom: 12px; + line-height: 1.55; +} +.impact-list li:last-child { margin-bottom: 0; } +.impact-list li::before { + content: '→'; + color: var(--accent); + position: absolute; + left: 0; +} +.impact-list li strong { + color: var(--fg); + font-weight: 500; + display: block; + margin-bottom: 2px; +} +.impact-detail { + display: block; + color: var(--fg-dim); + font-size: 12.5px; + font-style: italic; + padding-left: 0; +} +.impact-detail em { + color: var(--accent); + font-style: normal; +} +.impact-process { + margin-top: 16px; + font-size: 12px; + color: var(--fg-faint); + font-family: 'IBM Plex Mono', monospace; +} + +/* Repo-private note for academic projects without a public repo link */ +.repo-note { + margin-top: 14px; + font-size: 11px; + color: var(--fg-faint); + font-family: 'IBM Plex Mono', monospace; + font-style: italic; + opacity: 0.85; +} + +/* Inline kbd for in-prose keyboard hints */ +.inline-kbd { + color: var(--fg); + background: var(--bg-soft); + border: 1px solid var(--border); + padding: 1px 6px; + font-size: 11px; + border-radius: 3px; + font-family: 'JetBrains Mono', monospace; +} + /* Category tags */ .cat-tag { font-family: 'JetBrains Mono', monospace; @@ -759,16 +826,12 @@ body::before { .project[data-category="fun"] .cat-tag { color: var(--violet); border-color: rgba(183, 148, 217, 0.3); } .project[data-category="tools"] .cat-tag { color: var(--magenta); border-color: rgba(217, 111, 158, 0.3); } -/* Secondary cat-tag — used when a project genuinely spans two categories. - Always renders in the cyan "web" color since that's the only place we use it now, - but the rule overrides whatever the parent's data-category styling sets. */ .cat-tag-secondary { color: var(--cyan) !important; border-color: rgba(127, 179, 196, 0.3) !important; margin-left: 4px; } -/* Featured project — slightly elevated treatment for the showcase card */ .project.project-featured { border-left: 1.5px solid var(--secondary); padding-left: 32px; @@ -777,7 +840,6 @@ body::before { border-left-color: var(--secondary); } -/* Microservices breakdown grid inside the featured card */ .microservices { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); @@ -850,8 +912,6 @@ body::before { border: 1px solid var(--border); } -/* "Current" status badge — cross-cuts categories, sits next to cat-tag. - Amber so it's visually distinct from the green "systems" tag. */ .current-tag { display: inline-flex; align-items: center; @@ -879,11 +939,10 @@ body::before { animation: pulse 2s ease infinite; } [data-theme="light"] .current-tag { - background: rgba(201, 125, 42, 0.08); - border-color: rgba(201, 125, 42, 0.4); + background: rgba(158, 93, 16, 0.08); + border-color: rgba(158, 93, 16, 0.4); } -/* Project action buttons (play / repo) */ .project-actions { display: flex; flex-wrap: wrap; From 633c6c5950340cd3ea2d5e0a10e38be8f87e375b Mon Sep 17 00:00:00 2001 From: Adni Onoh Date: Thu, 14 May 2026 12:11:09 -0400 Subject: [PATCH 4/4] almost ready for main --- index.html | 101 ++++++++++++++--------------------------------------- index.js | 3 +- style.css | 8 +++++ 3 files changed, 35 insertions(+), 77 deletions(-) diff --git a/index.html b/index.html index def1698..8b21e00 100644 --- a/index.html +++ b/index.html @@ -53,7 +53,7 @@
    - open to learn + open to new-grad SWE roles starting fall 2026 · buffalo, ny · @@ -72,7 +72,7 @@

    adni onoh.

    - Distributed systems engineer and full-stack web developer. Currently interning at a healthtech startup writing software that's helping people track and care for their mental health, building a personal project that combines my web and distributed systems experience (see Distributed Microservices Demo Suite ↓), and interview prepping + Distributed systems engineer and full-stack web developer. Currently interning at a healthtech startup writing software that's helping people track and care for their mental health, building a personal project that combines my web and distributed systems experience (see Distributed Microservices Demo Suite ↓), and interview prepping.

    @@ -143,7 +139,7 @@

    // daily use

  • Python
  • Go
  • C
  • -
  • HTML&CSS
  • +
  • HTML & CSS
  • Node.js & Express & EJS
  • Git/GitHub
  • Figma
  • @@ -251,7 +247,7 @@

    // practices

    Node.js · Go · Python · C · MongoDB · Docker
    - Building everything I've worked on into one system: independent services in different languages, talking through a secure gateway, health monitoring for services, and graceful degredation when something fails. + Building everything I've worked on into one system: independent services in different languages, talking through a secure gateway, health monitoring for services, and graceful degradation when something fails.
    @@ -303,79 +299,34 @@

    // practices

    web current
    -
    JavaScript · React · Figma · QA
    +
    TypeScript · React · Figma
    - Nara's whole pitch is continuous mental health care. Three groups have to trust the platform for that to work: the members getting care, the providers delivering it, and the org admins paying for it. My tickets have spanned all three. + Nara's pitch is continuous mental health care. I haven't worked much on the member-facing side of the product yet, but I've contributed across the parts that make it run: the tools providers use to deliver care, the dashboards org admins use to manage it, and the systems the Nara team uses to keep everything moving.
    -
    For providers — sharpening the clinical signal
    -
      -
    • - Display & filter client AI-chat ratings on the provider dashboard. - Providers can now sort their AI-therapy sessions by client rating and surface low-rated ones immediately. The whole product is built on continuity of care — this lets a provider catch a struggling client before the next session, not after. -
    • -
    • - Surface high-risk journal entries. - Patient journals can contain critical risk indicators. Now those entries get flagged consistently with other high-risk sources so providers don't miss them. -
    • -
    • - Show client feedback on the provider detail page. - Gives admins visibility into per-provider quality, client experience, and reported concerns in one place — so quality issues surface fast. -
    • -
    • - Help the AI chatbot reference user data. - Worked on letting the chatbot pull from journal entries, mood check-ins, and gratitude logs. Conversations stop being generic and start feeling like the AI actually knows the person. -
    • -
    +
    For providers
    +

    + I added a way to filter AI-chat sessions by client rating so low-rated interactions are noticed immediately, which lets a provider catch a struggling client before the next session. I also flagged high-risk journal entries alongside other risk indicators, displayed client feedback on the provider detail page, and helped the AI chatbot reference user journals, mood check-ins, and gratitude logs to tailor conversations to the user. +

    -
    For org admins — reducing friction for the customer
    -
      -
    • - Plan selection & default session count on org creation. - Organizations have different billing models (bulk vs. pay-per-invite). Supporting both in the dashboard, plus a default session count, removes manual input from the invite flow. -
    • -
    • - Required organization-type field at creation. - A new required field that captures what kind of org is being onboarded — sets up future segmentation, reporting, and customization. -
    • -
    • - Minimum seats & adjustable annual mobile app fee. - Defines baseline billing rules so admins don't have to set them manually for every new org. -
    • -
    • - Gate org dashboard metrics behind a minimum user threshold. - Hides organization-level metrics until there's enough data to mean something. Protects against misleading insights and creates a clear activation moment that nudges admins to invite more users. -
    • -
    • - In-context feedback popup on the org dashboard. - Star rating + written feedback captured where admins already are, centralized into the admin feedback table. Closes the loop between paying customers and the team building the product their employees rely on. -
    • -
    +
    For org admins
    +

    + Plan selection and a default session count on org creation let the dashboard handle both billing models (bulk and pay-per-invite) without manual input. A required organization-type field captures what kind of org is being onboarded, and minimum seats with an adjustable annual fee set baseline billing automatically. I also gated dashboard metrics behind a user threshold so admins don't see misleading numbers before there's real data, and built an in-context feedback popup that centralizes admin feedback into the main table. +

    -
    For the Nara team — better internal velocity
    -
      -
    • - Organization contact dates on the admin dashboard. - Shows when an org first contacted Nara. Helps prioritize warm leads — every onboarded org is more employees getting mental health support. -
    • -
    • - Org deletion controls. - Lets admins remove duplicate or accidental org entries instead of just rejecting them. Keeps the admin dashboard usable as it scales. -
    • -
    • - User type column on the feedback table. - Tags feedback by who sent it — member, org admin, or provider — so the team can route, pattern-spot, and respond effectively instead of treating every comment the same. -
    • -
    +
    For the Nara team
    +

    + Contact-date tracking shows when an org first reached out, helping the team prioritize warm leads. Deletion controls let admins clean up duplicate or accidental org entries instead of just rejecting them. And a user-type column on the feedback table tags every submission as coming from a member, org admin, or provider, so feedback can actually be routed and patterned instead of treated as one bucket. +

    - // process: all work follows Figma designs and team QA criterias. + // process: all work follows Figma designs and team QA criteria.

    @@ -543,7 +494,7 @@

    // practices

    HTML · CSS · JavaScript
    - The site you're reading. I hope you enjoy using it! Try the command input at the top: tree projects works. + The site you're reading, built with just HTML/CSS/JS. I hope you enjoy using it! Try the command input at the top: tree projects works. diff --git a/index.js b/index.js index 8ed68e8..681e248 100644 --- a/index.js +++ b/index.js @@ -272,9 +272,8 @@ // Default placeholder rotates between a few hints const placeholders = [ 'type "tree projects" to see what I\'ve built ↓', - 'try "tree projects" — that\'s where the good stuff is', 'type a command (try "help" or "projects")', - 'type "projects" — recruiter shortcut', + 'type "projects"; recruiter shortcut', ]; let defaultPlaceholder = placeholders[0]; diff --git a/style.css b/style.css index bd96b1c..3b3ebcf 100644 --- a/style.css +++ b/style.css @@ -778,6 +778,14 @@ body::before { color: var(--accent); font-style: normal; } +/* Prose-paragraph version of an impact group (replaces the bullet list). + Slightly smaller and looser than body so it reads inside the bordered box. */ +.impact-paragraph { + font-size: 13px; + color: var(--fg-dim); + line-height: 1.65; + margin: 0; +} .impact-process { margin-top: 16px; font-size: 12px;