<silentCompiler/>
Personal Portfolio — Yeabtsega Tesfaye

A handcrafted, zero-dependency portfolio built from scratch with vanilla HTML, CSS, and JavaScript — no frameworks, no build tools, no shortcuts. ~7,000 lines of deliberate front-end engineering.
Preview
Live Demo
→ yeab-tsega.netlify.app
Deployed on Netlify with continuous deployment from the main branch. The site loads with a terminal-style intro sequence that sets the tone before giving way to the full portfolio experience.
Table of Contents
Overview
This portfolio is not a template or a cloned starter. It is a ground-up frontend project built to serve two purposes simultaneously: to function as a professional introduction for potential clients and employers, and to serve as a serious technical exercise in what is achievable without a JavaScript framework or build pipeline.
The site encompasses a multi-section single-page layout with a custom terminal welcome sequence, physics-inspired magnetic button interactions, a real-time Canvas-based code rain background, an animated skills marquee with interactive popups, a structured projects/blog section, and a complete CSS design system defined through custom properties.
The separation of concerns across HTML (structure), CSS (presentation), and JavaScript (behavior) is strict throughout — an architectural discipline that makes the ~7,000-line codebase navigable without tooling.
Tech Stack
| Layer | Technology | Notes |
|---|
| Structure | HTML5 | Semantic elements, ARIA labels, SEO meta tags |
| Styling | CSS3 | Custom properties, Grid, Flexbox, @keyframes, no preprocessor |
| Behavior | Vanilla JavaScript (ES6+) | No frameworks, no bundler |
| Typography | Syne (Google Fonts) | 400 / 600 / 700 / 800 weights |
| Icons | Font Awesome 6 + Devicons | Loaded via CDN |
| Deployment | Netlify | CD from main |
| Version Control | Git / GitHub | — |
Deliberately excluded: React, Vue, Sass, Webpack, Vite, jQuery, Bootstrap, Tailwind. Every visual effect and interaction is hand-implemented.
Features
Terminal Welcome System
A fully scripted terminal simulation runs before the main site loads. Lines of mock shell output are typed character-by-character using a sequenced queue, with a blinking cursor, realistic timing delays, and a "Skip Intro" escape hatch. The overlay fades out after the sequence completes, transitioning into the main site with a branded loader animation (<silentCompiler/>).
Magnetic Button Physics
The hero CTA buttons implement a magnetic attraction effect: mousemove events calculate the cursor's offset from the button's center, and the button translates toward the cursor using CSS transform. On mouseleave, the button springs back to origin. Particle decorations orbit each button independently.
Canvas Code Rain
A <canvas> element sits as a fixed background layer. A JavaScript engine renders a Matrix-inspired falling-character effect using the Canvas 2D API — managing column positions, character sets, random speeds, fade trails, and frame rate control via requestAnimationFrame. The effect is togglable without page reload, and respects prefers-reduced-motion.
Skills Marquee with Interactive Popups
Technology skills are rendered in two infinitely looping rows (scrolling in opposite directions) using CSS animation and translateX. Each skill chip is clickable and triggers a centered popup card with skill metadata: category, experience level, and a deep-link to relevant GitHub repositories.
Sound Effects System
UI interactions (hover, click, navigation) optionally trigger short audio cues. A toggle control persists the user's sound preference and shows an animated equalizer wave when active — a small but considered detail.
Semantic HTML & Accessibility
Every interactive element carries an aria-label. Images have descriptive alt text. Navigation uses a proper <ul>/<li> list structure. Heading hierarchy (h1 → h3) is maintained throughout sections.
Blog Section
A structured blog section with article cards, timestamps in <time> elements with datetime attributes, and read-time estimates. Blog posts are external .html pages linked from the main portfolio.
Project Structure
Personal-Website/
│
├── index.html # Single-page application entry point (~713 lines)
│
├── favicon.ico
├── style-guide.md # Design token reference (colors, typography, spacing)
│
├── assets/
│ ├── css/
│ │ └── style.css # Full stylesheet (~4,000 lines)
│ │
│ ├── js/
│ │ └── script.js # All JS behavior (~2,000 lines)
│ │
│ ├── images/ # Site photography and project thumbnails
│ │ ├── hero-banner.jpg
│ │ ├── about.jpg
│ │ ├── about-detail.jpg
│ │ ├── portfolio-*.jpg
│ │ └── ...
│ │
│ ├── blogs/
│ │ ├── blog1.html # "Building My First Animated Website with GSAP"
│ │ └── blog2.html # "What I Learned as a Beginner Freelancer"
│ │
│ └── cv/
│ └── CV.pdf # Downloadable resume
│
└── readme-images/
└── desktop.png # README screenshot
The flat asset structure keeps everything discoverable without deep nesting. The style-guide.md serves as a living design token reference — a practice borrowed from component library documentation and useful for maintaining visual consistency across future additions.
Engineering & Design Philosophy
Vanilla-First as a Deliberate Constraint
The choice to avoid frameworks is not a limitation — it is the point. Building without React or Vue forces you to understand what those abstractions are hiding: DOM diffing, event delegation, state synchronization, animation timing. Writing these systems by hand produces a much clearer mental model of the browser runtime.
CSS Custom Properties as a Design System
The entire visual system is defined through CSS custom properties at the :root level. Colors (--raisin-black, --roman-silver, --eerie-black), typography scale (--fs-1 through --fs-8), spacing (--section-padding), border radii, and transition curves (--cubic-bounce, --cubic-ease-out) are all tokenized. This means a complete theme change requires editing one block, not hunting through thousands of lines.
Behavioral Encapsulation in JavaScript
Despite being a single script file, script.js is organized into discrete, self-contained systems: the terminal engine, the magnetic effect handler, the Canvas rain renderer, the skills popup controller, and the sound system. Each system manages its own state and does not reach into another's DOM. This is manual module discipline — the same separation you would enforce with ES modules or a framework, achieved through convention.
Separation of Concerns, Respected Literally
- HTML contains no
style="" inline attributes (with rare justified exceptions)
- CSS contains no business logic
- JavaScript does not embed style strings — it toggles classes and reads CSS custom properties
Performance-Conscious Animation
Animations prioritize transform and opacity — the two properties the browser can composite without layout recalculation. The Canvas rain uses requestAnimationFrame for smooth 60fps rendering. The skills marquee uses pure CSS @keyframes rather than a JavaScript scroll loop.
CSS Architecture
The stylesheet is organized in a consistent top-down order:
1. :root — Design tokens (colors, fonts, spacing, easing)
2. Reset / base styles
3. Reusable utility classes (.container, .btn, .section, .eyebrow)
4. Component styles, ordered by DOM appearance:
- Loader
- Terminal overlay
- Header / navigation
- Hero section
- About section
- Services section
- Skills (marquee + popup)
- Portfolio grid
- Blog grid
- Footer / contact
- Code rain canvas
- Sound controls
5. Responsive overrides (mobile-first breakpoints)
The blob-radius custom property (52% 48% 59% 41% / 53% 40% 60% 47%) is used for the organic avatar shape on the hero — a deliberate design detail that softens the visual without requiring SVG clip-paths.
JavaScript Systems
A command queue defines the lines to be typed. A recursive typeNextLine() function pulls commands off the queue, types each character with a configurable delay, appends the completed line as a DOM node with output text, then proceeds to the next command. A separate animation loop handles the cursor blink. The overlay is dismissed either when the queue empties or when the user clicks "Skip Intro."
On mousemove over a .btn-magnetic container, the cursor position is measured relative to the button's bounding box center using getBoundingClientRect(). The delta is multiplied by a strength factor and applied as a CSS translate transform. On mouseleave, the transform is reset with a spring-like transition defined by --cubic-bounce. Each button has an independent particle container that animates in the same direction.
The canvas is sized to window.innerWidth × window.innerHeight and resized on window.resize. An array tracks one "drop" per column (column count = Math.floor(width / fontSize)). Each frame: the canvas is partially cleared with a semi-transparent black fill (creating the fade trail), then each drop renders a random Katakana/alphanumeric character at its current row. Drops reset to the top at random intervals to stagger the cascade. The toggle button adds/removes a class that stops the rAF loop.
Skills are defined as a JavaScript data array (name, icon class, category, experience, description, GitHub link). The array is injected into two DOM rows as rendered chips. The infinite scroll is driven entirely by CSS animation: marquee linear infinite on each row, with the second row using animation-direction: reverse. Click events on chips look up the corresponding data object by index and populate the popup card, which is shown with a CSS class toggle.
Web Audio API (or <audio> elements) play short click/hover sounds tied to interaction events. A module-level soundEnabled boolean gates all audio calls. The toggle button updates the icon