diff --git a/README.md b/README.md index d54f8d9..d39a697 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,78 @@ -# jaeswift-website +# JAESWIFT.XYZ -Personal website for jaeswift.xyz - Sci-fi dashboard aesthetic \ No newline at end of file +> Sci-fi dashboard-inspired personal website + +![Status](https://img.shields.io/badge/STATUS-OPERATIONAL-00ffc8) +![Version](https://img.shields.io/badge/VERSION-1.0.0-00ffc8) + +## Overview + +A cyberpunk/sci-fi themed personal website inspired by satellite monitoring dashboards. Built with vanilla HTML, CSS, and JavaScript — no frameworks, no bloat. + +## Features + +- **HUD-style navigation** with dropdown menus and live clock +- **Particle system** with mouse interaction and connection lines +- **Glitch text effect** on hero title +- **Typing animation** cycling through descriptions +- **Scroll-reveal animations** for content sections +- **Animated skill bars** with glow effects +- **Live terminal emulator** with typed commands +- **Panel hover glow tracking** (follows cursor) +- **Scanline overlay** and grid background +- **HUD data flickers** for authentic dashboard feel +- **Signal strength animation** in footer +- **Fully responsive** — mobile hamburger menu with accordion dropdowns + +## Sections + +| # | Section | Description | +|---|---------|-------------| +| 00 | Hero | Animated HUD frame with glitch title, typing subtitle, status readouts | +| 01 | About | Operator profile panel + animated skill matrix bars | +| 02 | Blog | Card grid for blog posts with hover animations | +| 03 | Development | Project cards with tech tags and status indicators | +| 04 | Links | External links grid (Gitea, GitLab, Reddit, Email) | +| 05 | Contact | Transmission form + signal info + live terminal emulator | + +## Tech Stack + +- **HTML5** — Semantic markup +- **CSS3** — Custom properties, grid, flexbox, animations, backdrop-filter +- **Vanilla JS** — Particle system, IntersectionObserver, typing effect +- **Google Fonts** — JetBrains Mono, Orbitron, Share Tech Mono + +## Palette + +| Colour | Hex | Usage | +|--------|-----|-------| +| Background | `#060608` | Primary background | +| Panel | `#0c1018` | Card/panel backgrounds | +| Accent | `#00ffc8` | Primary accent (cyan/teal) | +| Text | `#c8d6e5` | Primary text | +| Muted | `#5a6a7a` | Secondary text | +| Border | `#1a2a3a` | Panel borders | +| Danger | `#ff3a5c` | Glitch/error accent | + +## Local Development + +```bash +# Clone +git clone https://git.jaeswift.xyz/jae/jaeswift-website.git +cd jaeswift-website + +# Serve (any static server works) +python3 -m http.server 8080 +# or +npx serve . +``` + +Open `http://localhost:8080` in your browser. + +## Deployment + +Static files — deploy anywhere: Nginx, Apache, Caddy, Netlify, Cloudflare Pages, etc. + +## License + +© 2026 jae — All rights reserved. diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..9f48bc4 --- /dev/null +++ b/css/style.css @@ -0,0 +1,1135 @@ +/* =================================================== + JAESWIFT.XYZ — Sci-Fi Dashboard Theme + Palette: #0a0a0f (bg), #00ffc8 (accent), #0d1117 (panels) + =================================================== */ + +/* --- Reset & Base --- */ +*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } + +:root { + --bg-primary: #060608; + --bg-secondary: #0a0e14; + --bg-panel: #0c1018; + --bg-panel-hover: #101820; + --accent: #00ffc8; + --accent-dim: #00ffc840; + --accent-glow: #00ffc880; + --accent-subtle: #00ffc815; + --text-primary: #c8d6e5; + --text-secondary: #5a6a7a; + --text-muted: #2a3a4a; + --border: #1a2a3a; + --border-accent: #00ffc830; + --danger: #ff3a5c; + --warning: #ffaa00; + --font-mono: 'JetBrains Mono', 'Share Tech Mono', 'Courier New', monospace; + --font-display: 'Orbitron', sans-serif; + --nav-height: 60px; +} + +html { + scroll-behavior: smooth; + scrollbar-width: thin; + scrollbar-color: var(--accent-dim) var(--bg-primary); +} + +html::-webkit-scrollbar { width: 6px; } +html::-webkit-scrollbar-track { background: var(--bg-primary); } +html::-webkit-scrollbar-thumb { background: var(--accent-dim); border-radius: 3px; } +html::-webkit-scrollbar-thumb:hover { background: var(--accent); } + +body { + font-family: var(--font-mono); + background: var(--bg-primary); + color: var(--text-primary); + line-height: 1.6; + overflow-x: hidden; + -webkit-font-smoothing: antialiased; +} + +a { color: var(--accent); text-decoration: none; transition: all 0.3s ease; } +a:hover { color: #fff; text-shadow: 0 0 10px var(--accent-glow); } + +.accent { color: var(--accent); } + +/* --- Scanline Overlay --- */ +.scanline-overlay { + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + pointer-events: none; + z-index: 9999; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 255, 200, 0.008) 2px, + rgba(0, 255, 200, 0.008) 4px + ); +} + +/* --- Grid Background --- */ +.grid-bg { + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + pointer-events: none; + z-index: -2; + background-image: + linear-gradient(var(--accent-subtle) 1px, transparent 1px), + linear-gradient(90deg, var(--accent-subtle) 1px, transparent 1px); + background-size: 60px 60px; + opacity: 0.3; +} + +/* --- Particle Canvas --- */ +#particles { + position: fixed; + top: 0; left: 0; + width: 100%; height: 100%; + pointer-events: none; + z-index: -1; +} + +/* === NAVIGATION === */ +.nav-main { + position: fixed; + top: 0; left: 0; + width: 100%; + height: var(--nav-height); + background: rgba(6, 6, 8, 0.92); + backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + z-index: 1000; + transition: all 0.3s ease; +} + +.nav-main.scrolled { + background: rgba(6, 6, 8, 0.98); + border-bottom-color: var(--accent-dim); + box-shadow: 0 4px 30px rgba(0, 255, 200, 0.05); +} + +.nav-container { + max-width: 1400px; + margin: 0 auto; + padding: 0 2rem; + height: 100%; + display: flex; + align-items: center; + justify-content: space-between; +} + +.nav-logo { + display: flex; + align-items: center; + gap: 0.15rem; + font-family: var(--font-display); + font-size: 0.9rem; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 2px; + text-decoration: none; +} + +.nav-logo:hover { color: var(--text-primary); text-shadow: none; } +.logo-bracket { color: var(--accent); font-weight: 300; } +.logo-accent { color: var(--accent); } +.logo-status { + font-size: 0.55rem; + color: var(--accent); + margin-left: 0.75rem; + animation: pulse-status 2s ease-in-out infinite; +} + +@keyframes pulse-status { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +/* Nav Menu */ +.nav-menu { + display: flex; + list-style: none; + gap: 0; + align-items: center; +} + +.nav-item { + position: relative; +} + +.nav-link { + display: block; + padding: 0 1.25rem; + height: var(--nav-height); + line-height: var(--nav-height); + font-family: var(--font-display); + font-size: 0.7rem; + font-weight: 500; + letter-spacing: 2px; + color: var(--text-secondary); + transition: all 0.3s ease; + position: relative; +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: 0; left: 50%; + transform: translateX(-50%); + width: 0; + height: 2px; + background: var(--accent); + transition: width 0.3s ease; + box-shadow: 0 0 10px var(--accent-glow); +} + +.nav-link:hover, .nav-link.active { + color: var(--accent); + text-shadow: 0 0 15px var(--accent-glow); +} + +.nav-link:hover::after, .nav-link.active::after { + width: 100%; +} + +/* Dropdown */ +.dropdown { + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + background: rgba(10, 14, 20, 0.98); + border: 1px solid var(--border); + border-top: 2px solid var(--accent); + list-style: none; + opacity: 0; + visibility: hidden; + transform: translateY(-10px); + transition: all 0.3s ease; + backdrop-filter: blur(20px); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); +} + +.nav-item:hover > .dropdown { + opacity: 1; + visibility: visible; + transform: translateY(0); +} + +.dropdown li a { + display: block; + padding: 0.75rem 1.25rem; + font-size: 0.7rem; + font-family: var(--font-mono); + color: var(--text-secondary); + letter-spacing: 1px; + border-bottom: 1px solid var(--border); + transition: all 0.2s ease; +} + +.dropdown li:last-child a { border-bottom: none; } + +.dropdown li a:hover { + background: var(--accent-subtle); + color: var(--accent); + padding-left: 1.5rem; + text-shadow: 0 0 10px var(--accent-glow); +} + +.dropdown li a::before { + content: '> '; + opacity: 0; + transition: opacity 0.2s ease; +} + +.dropdown li a:hover::before { opacity: 1; } + +/* Nav Status */ +.nav-status { + display: flex; + align-items: center; + gap: 1rem; + font-size: 0.65rem; + color: var(--text-secondary); +} + +.nav-time { + font-family: var(--font-mono); + color: var(--accent); + letter-spacing: 2px; +} + +.nav-signal { + color: var(--accent); + letter-spacing: 1px; + font-size: 0.55rem; +} + +/* Mobile Toggle */ +.nav-toggle { + display: none; + flex-direction: column; + gap: 5px; + background: none; + border: none; + cursor: pointer; + padding: 5px; +} + +.nav-toggle span { + display: block; + width: 24px; + height: 2px; + background: var(--accent); + transition: all 0.3s ease; +} + +/* === HERO SECTION === */ +.hero { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: calc(var(--nav-height) + 2rem) 2rem 2rem; + position: relative; +} + +.hero-hud { + position: relative; + width: 100%; + max-width: 800px; + padding: 4rem; + border: 1px solid var(--border); + background: rgba(10, 14, 20, 0.5); +} + +/* HUD Corners */ +.hud-corner { + position: absolute; + width: 20px; + height: 20px; +} + +.hud-tl { top: -1px; left: -1px; border-top: 2px solid var(--accent); border-left: 2px solid var(--accent); } +.hud-tr { top: -1px; right: -1px; border-top: 2px solid var(--accent); border-right: 2px solid var(--accent); } +.hud-bl { bottom: -1px; left: -1px; border-bottom: 2px solid var(--accent); border-left: 2px solid var(--accent); } +.hud-br { bottom: -1px; right: -1px; border-bottom: 2px solid var(--accent); border-right: 2px solid var(--accent); } + +.hero-content { + text-align: center; +} + +.hero-label { + font-family: var(--font-mono); + font-size: 0.65rem; + letter-spacing: 4px; + color: var(--text-secondary); + margin-bottom: 1.5rem; + animation: fadeInDown 1s ease 0.2s both; +} + +/* Glitch Effect */ +.hero-title { + font-family: var(--font-display); + font-size: clamp(2.5rem, 8vw, 5rem); + font-weight: 900; + letter-spacing: 8px; + margin-bottom: 1.5rem; + animation: fadeInUp 1s ease 0.4s both; +} + +.glitch { + position: relative; + color: var(--text-primary); + display: inline-block; +} + +.glitch::before, +.glitch::after { + content: attr(data-text); + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + opacity: 0; +} + +.glitch::before { + color: var(--accent); + animation: glitch-1 4s infinite linear; +} + +.glitch::after { + color: var(--danger); + animation: glitch-2 4s infinite linear; +} + +@keyframes glitch-1 { + 0%, 90%, 100% { opacity: 0; transform: none; } + 91% { opacity: 0.8; transform: translate(-2px, -1px); clip-path: inset(20% 0 60% 0); } + 93% { opacity: 0.8; transform: translate(2px, 1px); clip-path: inset(50% 0 10% 0); } + 95% { opacity: 0; } +} + +@keyframes glitch-2 { + 0%, 92%, 100% { opacity: 0; transform: none; } + 93% { opacity: 0.6; transform: translate(2px, 2px); clip-path: inset(10% 0 70% 0); } + 95% { opacity: 0.6; transform: translate(-1px, -2px); clip-path: inset(40% 0 20% 0); } + 97% { opacity: 0; } +} + +/* Typing Effect */ +.hero-subtitle { + font-family: var(--font-mono); + font-size: 1rem; + color: var(--accent); + margin-bottom: 3rem; + animation: fadeIn 1s ease 0.8s both; +} + +.typing-prefix { color: var(--text-secondary); } +.typing-cursor { + animation: blink-cursor 0.8s step-end infinite; + color: var(--accent); +} + +@keyframes blink-cursor { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* Hero Stats */ +.hero-stats { + display: flex; + justify-content: center; + gap: 3rem; + animation: fadeInUp 1s ease 1s both; +} + +.stat-item { + text-align: center; +} + +.stat-label { + display: block; + font-size: 0.55rem; + letter-spacing: 3px; + color: var(--text-secondary); + margin-bottom: 0.4rem; +} + +.stat-value { + font-size: 0.75rem; + letter-spacing: 1px; + color: var(--text-primary); +} + +.stat-online { color: var(--accent); } + +/* Scan Line Animation */ +.scan-line { + position: absolute; + top: 0; left: 0; + width: 100%; + height: 2px; + background: linear-gradient(90deg, transparent, var(--accent), transparent); + opacity: 0.6; + animation: scan 4s linear infinite; +} + +@keyframes scan { + 0% { top: 0; opacity: 0; } + 10% { opacity: 0.6; } + 90% { opacity: 0.6; } + 100% { top: 100%; opacity: 0; } +} + +/* === SECTIONS === */ +.section { + padding: 6rem 2rem; + position: relative; +} + +.section-container { + max-width: 1200px; + margin: 0 auto; +} + +.section-header { + display: flex; + align-items: center; + gap: 1.5rem; + margin-bottom: 3rem; +} + +.section-tag { + font-family: var(--font-display); + font-size: 0.7rem; + color: var(--accent); + letter-spacing: 2px; + padding: 0.3rem 0.6rem; + border: 1px solid var(--accent-dim); +} + +.section-title { + font-family: var(--font-display); + font-size: clamp(1.2rem, 3vw, 1.6rem); + font-weight: 700; + letter-spacing: 4px; + color: var(--text-primary); + white-space: nowrap; +} + +.section-line { + flex: 1; + height: 1px; + background: linear-gradient(90deg, var(--border-accent), transparent); +} + +/* === PANELS === */ +.panel { + background: var(--bg-panel); + border: 1px solid var(--border); + position: relative; + overflow: hidden; + transition: all 0.4s ease; +} + +.panel::before { + content: ''; + position: absolute; + top: 0; left: 0; + width: 100%; height: 100%; + background: linear-gradient(135deg, var(--accent-subtle), transparent); + opacity: 0; + transition: opacity 0.4s ease; +} + +.panel:hover { border-color: var(--accent-dim); } +.panel:hover::before { opacity: 1; } + +.panel-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border); + background: rgba(0, 255, 200, 0.02); +} + +.panel-title { + font-family: var(--font-display); + font-size: 0.65rem; + letter-spacing: 2px; + color: var(--text-secondary); +} + +.panel-icon { + color: var(--accent); + font-size: 0.8rem; +} + +.panel-content { + padding: 1.5rem 1.25rem; +} + +.panel-content p { + font-size: 0.8rem; + color: var(--text-secondary); + margin-bottom: 1rem; + line-height: 1.8; +} + +/* Panel Data Rows */ +.panel-data { margin-top: 1.5rem; } + +.data-row { + display: flex; + justify-content: space-between; + padding: 0.6rem 0; + border-bottom: 1px solid var(--border); + font-size: 0.7rem; +} + +.data-key { color: var(--text-secondary); letter-spacing: 2px; } +.data-val { color: var(--accent); font-weight: 500; } + +/* === ABOUT: Panels Grid === */ +.panels-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; +} + +/* === SKILL BARS === */ +.skill-bar { + display: grid; + grid-template-columns: 140px 1fr 40px; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; +} + +.skill-name { + font-size: 0.6rem; + letter-spacing: 1px; + color: var(--text-secondary); +} + +.bar-track { + height: 4px; + background: var(--bg-primary); + border-radius: 2px; + overflow: hidden; + position: relative; +} + +.bar-fill { + height: 100%; + width: 0; + background: linear-gradient(90deg, var(--accent), var(--accent-dim)); + border-radius: 2px; + transition: width 1.5s ease; + position: relative; + box-shadow: 0 0 10px var(--accent-glow); +} + +.bar-fill.animated { width: var(--target-width); } + +.skill-pct { + font-size: 0.65rem; + color: var(--accent); + text-align: right; +} + +/* === BLOG GRID === */ +.blog-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; +} + +.blog-card { + background: var(--bg-panel); + border: 1px solid var(--border); + padding: 1.5rem; + transition: all 0.4s ease; + position: relative; + overflow: hidden; +} + +.blog-card::after { + content: ''; + position: absolute; + top: 0; left: 0; + width: 100%; height: 2px; + background: var(--accent); + transform: scaleX(0); + transform-origin: left; + transition: transform 0.4s ease; +} + +.blog-card:hover { + border-color: var(--accent-dim); + transform: translateY(-4px); + box-shadow: 0 8px 30px rgba(0, 255, 200, 0.08); +} + +.blog-card:hover::after { transform: scaleX(1); } + +.blog-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.blog-date { + font-size: 0.6rem; + color: var(--text-secondary); + letter-spacing: 2px; +} + +.blog-tag { + font-size: 0.55rem; + letter-spacing: 1px; + color: var(--accent); + padding: 0.2rem 0.5rem; + border: 1px solid var(--accent-dim); +} + +.blog-title { + font-family: var(--font-mono); + font-size: 0.9rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 0.75rem; + line-height: 1.4; +} + +.blog-excerpt { + font-size: 0.75rem; + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 1.5rem; +} + +.blog-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--border); +} + +.blog-read-time { + font-size: 0.6rem; + color: var(--text-muted); +} + +.blog-link { + font-size: 0.65rem; + font-family: var(--font-display); + letter-spacing: 2px; + color: var(--accent); +} + +.blog-link:hover { + text-shadow: 0 0 15px var(--accent-glow); +} + +/* === DEVELOPMENT GRID === */ +.dev-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; +} + +.dev-card { + background: var(--bg-panel); + border: 1px solid var(--border); + padding: 1.75rem; + transition: all 0.4s ease; + position: relative; +} + +.dev-card::before { + content: ''; + position: absolute; + top: 0; left: 0; + width: 3px; height: 0; + background: var(--accent); + transition: height 0.4s ease; +} + +.dev-card:hover { + border-color: var(--accent-dim); + transform: translateY(-4px); + box-shadow: 0 8px 30px rgba(0, 255, 200, 0.06); +} + +.dev-card:hover::before { height: 100%; } + +.dev-card-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.25rem; +} + +.dev-icon { + font-size: 1.5rem; + color: var(--accent); +} + +.dev-status { + font-size: 0.55rem; + letter-spacing: 1px; + color: var(--accent); +} + +.status-dev { color: var(--warning); } + +.dev-title { + font-family: var(--font-display); + font-size: 0.85rem; + font-weight: 600; + letter-spacing: 1px; + color: var(--text-primary); + margin-bottom: 0.75rem; +} + +.dev-desc { + font-size: 0.75rem; + color: var(--text-secondary); + line-height: 1.7; + margin-bottom: 1.25rem; +} + +.dev-tech { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin-bottom: 1.25rem; +} + +.tech-tag { + font-size: 0.55rem; + letter-spacing: 1px; + padding: 0.25rem 0.6rem; + border: 1px solid var(--border); + color: var(--text-secondary); + transition: all 0.3s ease; +} + +.dev-card:hover .tech-tag { + border-color: var(--accent-dim); + color: var(--accent); +} + +.dev-link { + font-size: 0.65rem; + font-family: var(--font-display); + letter-spacing: 2px; +} + +/* === LINKS GRID === */ +.links-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} + +.link-card { + display: flex; + align-items: center; + gap: 1.25rem; + padding: 1.5rem; + background: var(--bg-panel); + border: 1px solid var(--border); + transition: all 0.4s ease; + text-decoration: none; +} + +.link-card:hover { + border-color: var(--accent-dim); + background: var(--bg-panel-hover); + transform: translateX(8px); + text-shadow: none; +} + +.link-icon { + font-size: 1.5rem; + color: var(--accent); + width: 40px; + text-align: center; +} + +.link-info { flex: 1; } + +.link-name { + display: block; + font-family: var(--font-display); + font-size: 0.7rem; + letter-spacing: 2px; + color: var(--text-primary); + margin-bottom: 0.2rem; +} + +.link-url { + font-size: 0.65rem; + color: var(--text-secondary); +} + +.link-arrow { + font-size: 1.2rem; + color: var(--text-muted); + transition: all 0.3s ease; +} + +.link-card:hover .link-arrow { + color: var(--accent); + transform: translateX(4px); +} + +/* === CONTACT === */ +.contact-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; +} + +.contact-info { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +/* Form */ +.contact-form { + padding: 1.5rem 1.25rem; +} + +.form-group { margin-bottom: 1.25rem; } + +.form-label { + display: block; + font-size: 0.55rem; + letter-spacing: 2px; + color: var(--text-secondary); + margin-bottom: 0.5rem; +} + +.form-input { + width: 100%; + padding: 0.75rem 1rem; + background: var(--bg-primary); + border: 1px solid var(--border); + color: var(--text-primary); + font-family: var(--font-mono); + font-size: 0.75rem; + transition: all 0.3s ease; + outline: none; +} + +.form-input:focus { + border-color: var(--accent); + box-shadow: 0 0 0 1px var(--accent-dim), 0 0 20px rgba(0, 255, 200, 0.05); +} + +.form-input::placeholder { color: var(--text-muted); } + +.form-textarea { resize: vertical; min-height: 120px; } + +.form-submit { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.85rem 2rem; + background: transparent; + border: 1px solid var(--accent); + color: var(--accent); + font-family: var(--font-display); + font-size: 0.7rem; + letter-spacing: 3px; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.form-submit::before { + content: ''; + position: absolute; + top: 0; left: -100%; + width: 100%; height: 100%; + background: var(--accent); + transition: left 0.3s ease; + z-index: -1; +} + +.form-submit:hover { + color: var(--bg-primary); + box-shadow: 0 0 30px var(--accent-glow); +} + +.form-submit:hover::before { left: 0; } + +.submit-icon { + font-size: 0.6rem; + transition: transform 0.3s ease; +} + +.form-submit:hover .submit-icon { transform: translateX(4px); } + +/* Info Blocks */ +.info-block { + padding: 1rem 0; + border-bottom: 1px solid var(--border); +} + +.info-block:last-child { border-bottom: none; } + +.info-label { + display: block; + font-size: 0.55rem; + letter-spacing: 2px; + color: var(--text-muted); + margin-bottom: 0.3rem; +} + +.info-value { + font-size: 0.8rem; + color: var(--accent); +} + +/* Terminal */ +.terminal-panel { + flex: 1; +} + +.terminal-content { + padding: 1rem 1.25rem; + font-size: 0.7rem; + line-height: 1.8; +} + +.term-line { white-space: nowrap; } +.term-prompt { color: var(--accent); } +.term-cmd { color: var(--text-primary); } +.term-output { color: var(--text-secondary); padding-left: 0; } +.term-cursor { animation: blink-cursor 0.8s step-end infinite; color: var(--accent); } + +/* === FOOTER === */ +.footer { + padding: 2rem; + border-top: 1px solid var(--border); + background: rgba(6, 6, 8, 0.8); +} + +.footer-container { + max-width: 1200px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; +} + +.footer-left, .footer-right { + display: flex; + align-items: center; + gap: 2rem; +} + +.footer-logo { + font-family: var(--font-display); + font-size: 0.75rem; + letter-spacing: 2px; + color: var(--text-primary); +} + +.footer-copy { + font-size: 0.6rem; + color: var(--text-muted); + letter-spacing: 1px; +} + +.footer-coords { + font-size: 0.6rem; + color: var(--text-muted); + letter-spacing: 1px; +} + +.footer-signal { + font-size: 0.6rem; + color: var(--accent); + letter-spacing: 1px; +} + +.footer-bar { + margin-top: 1.5rem; + height: 1px; + background: linear-gradient(90deg, transparent, var(--accent-dim), transparent); +} + +/* === ANIMATIONS === */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes fadeInUp { + from { opacity: 0; transform: translateY(30px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes fadeInDown { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Scroll reveal */ +[data-animate] { + opacity: 0; + transform: translateY(40px); + transition: all 0.8s cubic-bezier(0.16, 1, 0.3, 1); +} + +[data-animate].visible { + opacity: 1; + transform: translateY(0); +} + +/* === RESPONSIVE === */ +@media (max-width: 1024px) { + .blog-grid, .dev-grid { grid-template-columns: repeat(2, 1fr); } + .panels-grid { grid-template-columns: 1fr; } + .contact-grid { grid-template-columns: 1fr; } +} + +@media (max-width: 768px) { + .nav-toggle { display: flex; } + .nav-status { display: none; } + + .nav-menu { + position: fixed; + top: var(--nav-height); + left: 0; + width: 100%; + flex-direction: column; + background: rgba(6, 6, 8, 0.98); + backdrop-filter: blur(20px); + border-bottom: 1px solid var(--border); + padding: 1rem 0; + transform: translateY(-100%); + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + } + + .nav-menu.active { + transform: translateY(0); + opacity: 1; + visibility: visible; + } + + .nav-link { + height: auto; + line-height: 1; + padding: 1rem 2rem; + } + + .nav-link::after { display: none; } + + .dropdown { + position: static; + border: none; + border-top: none; + background: rgba(0, 0, 0, 0.3); + max-height: 0; + overflow: hidden; + opacity: 1; + visibility: visible; + transform: none; + transition: max-height 0.3s ease; + } + + .nav-item.active .dropdown { max-height: 300px; } + + .hero-hud { padding: 2rem; } + .hero-stats { flex-direction: column; gap: 1rem; } + .blog-grid, .dev-grid { grid-template-columns: 1fr; } + .links-grid { grid-template-columns: 1fr; } + .skill-bar { grid-template-columns: 1fr; gap: 0.3rem; } + .skill-name { margin-bottom: 0; } + .section { padding: 4rem 1rem; } + .footer-container { flex-direction: column; gap: 1rem; text-align: center; } + .footer-left, .footer-right { flex-direction: column; gap: 0.5rem; } +} + +@media (max-width: 480px) { + .hero-title { letter-spacing: 4px; } + .section-header { flex-direction: column; align-items: flex-start; gap: 0.75rem; } + .section-line { display: none; } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..dae07b8 --- /dev/null +++ b/index.html @@ -0,0 +1,443 @@ + + + + + + JAESWIFT // SYSTEMS ONLINE + + + + + + + +
+ + +
+ + + + + + + + +
+
+
+
+
+
+ +
+
SYSTEM IDENTIFICATION
+

+ JAESWIFT +

+
+ > + + +
+
+
+ STATUS + ● OPERATIONAL +
+
+ UPTIME + 0d 00h 00m +
+
+ LOCATION + EARTH // LEO +
+
+
+ + +
+
+
+ + +
+
+
+ +

ABOUT_ME

+
+
+
+
+
+ OPERATOR PROFILE + +
+
+

Developer, tinkerer, and digital architect. I build things that live on the internet — from self-hosted infrastructure to custom web applications.

+

Passionate about open source, cybersecurity, and making technology work the way it should.

+
+
+ CODENAME + JAE +
+
+ DOMAIN + JAESWIFT.XYZ +
+
+ CLEARANCE + ADMIN +
+
+
+
+
+
+ SKILL MATRIX + +
+
+
+ LINUX / SYSADMIN +
+ 92% +
+
+ WEB DEVELOPMENT +
+ 85% +
+
+ PYTHON +
+ 88% +
+
+ DOCKER / INFRA +
+ 80% +
+
+ CYBERSECURITY +
+ 75% +
+
+
+
+
+
+ + +
+
+
+ +

BLOG_FEED

+
+
+
+
+
+ 2026.03.31 + INFRASTRUCTURE +
+

Self-Hosting Everything: A Complete Guide

+

How I migrated away from cloud services and built a fully self-hosted infrastructure stack...

+ +
+
+
+ 2026.03.28 + SECURITY +
+

Hardening Your VPS: Beyond the Basics

+

Essential security configurations that most tutorials skip — from kernel parameters to network isolation...

+ +
+
+
+ 2026.03.22 + DEV +
+

Building AI Agents That Actually Work

+

My journey deploying Agent Zero and customising autonomous AI assistants for real-world tasks...

+ +
+
+
+
+ + +
+
+
+ +

DEV_OPS

+
+
+
+
+
+ + ● ACTIVE +
+

Agent Zero

+

Custom AI agent framework deployment with autonomous task execution capabilities.

+
+ Python + Docker + LLM +
+ +
+
+
+ + ● ACTIVE +
+

Infrastructure Stack

+

Self-hosted services including Gitea, monitoring, reverse proxies, and automated deployments.

+
+ Linux + Nginx + Docker +
+ +
+
+
+ + ◌ IN DEV +
+

JAESWIFT.XYZ

+

This website — a sci-fi dashboard-inspired personal hub with custom animations and HUD elements.

+
+ HTML + CSS + JavaScript +
+ +
+
+
+
+ + + + + +
+
+
+ +

COMMS_LINK

+
+
+
+
+
+ TRANSMIT MESSAGE + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+
+ SIGNAL INFO + +
+
+
+ PRIMARY CHANNEL + jae@jaeswift.xyz +
+
+ RESPONSE TIME + ~24 HOURS +
+
+ ENCRYPTION + PGP AVAILABLE +
+
+
+
+
+ TERMINAL + _ +
+
+
jae@swift:~$ whoami
+
jae
+
jae@swift:~$ cat /etc/motd
+
Welcome to jaeswift.xyz
+
All systems operational.
+
jae@swift:~$
+
+
+
+
+
+
+ + + + + + + diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..3c5e259 --- /dev/null +++ b/js/main.js @@ -0,0 +1,572 @@ +/* =================================================== + JAESWIFT.XYZ — Main JavaScript + Animations, particles, typing, scroll effects + =================================================== */ + +(function () { + 'use strict'; + + // ─── CONFIG ─── + const CONFIG = { + typingStrings: [ + 'Developer // Tinkerer // Builder', + 'Self-hosted everything.', + 'Linux enthusiast since day one.', + 'Building the future, one commit at a time.', + 'root@jaeswift:~# echo "Hello, World"', + 'Cybersecurity & Infrastructure.', + 'AI Agent Operator.', + ], + typingSpeed: 60, + typingDeleteSpeed: 30, + typingPause: 2500, + particleCount: 80, + particleMaxSpeed: 0.3, + particleConnectionDist: 120, + startTime: Date.now(), + }; + + // ─── UTILITIES ─── + const $ = (sel, ctx = document) => ctx.querySelector(sel); + const $$ = (sel, ctx = document) => [...ctx.querySelectorAll(sel)]; + + // ─── NAVBAR ─── + function initNavbar() { + const navbar = $('#navbar'); + const toggle = $('#navToggle'); + const menu = $('#navMenu'); + const navItems = $$('.nav-item'); + + // Scroll effect + let lastScroll = 0; + window.addEventListener('scroll', () => { + const scrollY = window.scrollY; + navbar.classList.toggle('scrolled', scrollY > 50); + lastScroll = scrollY; + }, { passive: true }); + + // Mobile toggle + toggle.addEventListener('click', () => { + menu.classList.toggle('active'); + toggle.classList.toggle('active'); + }); + + // Mobile dropdown toggle + if (window.innerWidth <= 768) { + navItems.forEach(item => { + const link = item.querySelector('.nav-link'); + link.addEventListener('click', (e) => { + if (item.querySelector('.dropdown')) { + e.preventDefault(); + item.classList.toggle('active'); + } + }); + }); + } + + // Active link on scroll + const sections = $$('section[id]'); + window.addEventListener('scroll', () => { + const scrollPos = window.scrollY + 100; + sections.forEach(section => { + const top = section.offsetTop; + const height = section.offsetHeight; + const id = section.getAttribute('id'); + if (scrollPos >= top && scrollPos < top + height) { + $$('.nav-link').forEach(l => l.classList.remove('active')); + const activeLink = $(`.nav-link[href="#${id}"]`); + if (activeLink) activeLink.classList.add('active'); + } + }); + }, { passive: true }); + + // Smooth scroll for nav links + $$('.nav-link, .dropdown a[href^="#"]').forEach(link => { + link.addEventListener('click', (e) => { + const href = link.getAttribute('href'); + if (href && href.startsWith('#')) { + e.preventDefault(); + const target = $(href); + if (target) { + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); + menu.classList.remove('active'); + toggle.classList.remove('active'); + } + } + }); + }); + } + + // ─── LIVE CLOCK ─── + function initClock() { + const navTime = $('#navTime'); + if (!navTime) return; + + function update() { + const now = new Date(); + const h = String(now.getHours()).padStart(2, '0'); + const m = String(now.getMinutes()).padStart(2, '0'); + const s = String(now.getSeconds()).padStart(2, '0'); + navTime.textContent = `${h}:${m}:${s}`; + } + + update(); + setInterval(update, 1000); + } + + // ─── UPTIME COUNTER ─── + function initUptime() { + const uptimeEl = $('#uptime'); + if (!uptimeEl) return; + + function update() { + const elapsed = Date.now() - CONFIG.startTime; + const totalSec = Math.floor(elapsed / 1000); + const days = Math.floor(totalSec / 86400); + const hours = Math.floor((totalSec % 86400) / 3600); + const mins = Math.floor((totalSec % 3600) / 60); + uptimeEl.textContent = `${days}d ${String(hours).padStart(2, '0')}h ${String(mins).padStart(2, '0')}m`; + } + + update(); + setInterval(update, 60000); + } + + // ─── TYPING EFFECT ─── + function initTyping() { + const el = $('#typingText'); + if (!el) return; + + let stringIndex = 0; + let charIndex = 0; + let isDeleting = false; + + function type() { + const current = CONFIG.typingStrings[stringIndex]; + + if (isDeleting) { + el.textContent = current.substring(0, charIndex - 1); + charIndex--; + } else { + el.textContent = current.substring(0, charIndex + 1); + charIndex++; + } + + let delay = isDeleting ? CONFIG.typingDeleteSpeed : CONFIG.typingSpeed; + + if (!isDeleting && charIndex === current.length) { + delay = CONFIG.typingPause; + isDeleting = true; + } else if (isDeleting && charIndex === 0) { + isDeleting = false; + stringIndex = (stringIndex + 1) % CONFIG.typingStrings.length; + delay = 400; + } + + setTimeout(type, delay); + } + + setTimeout(type, 1200); + } + + // ─── SCROLL REVEAL ─── + function initScrollReveal() { + const elements = $$('[data-animate]'); + if (!elements.length) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry, index) => { + if (entry.isIntersecting) { + setTimeout(() => { + entry.target.classList.add('visible'); + }, index * 100); + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }); + + elements.forEach(el => observer.observe(el)); + } + + // ─── SKILL BARS ANIMATION ─── + function initSkillBars() { + const bars = $$('.bar-fill'); + if (!bars.length) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const bar = entry.target; + const width = bar.getAttribute('data-width'); + bar.style.setProperty('--target-width', width + '%'); + bar.classList.add('animated'); + observer.unobserve(bar); + } + }); + }, { threshold: 0.5 }); + + bars.forEach(bar => observer.observe(bar)); + } + + // ─── PARTICLE SYSTEM ─── + function initParticles() { + const canvas = $('#particles'); + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + let particles = []; + let animationId; + let mouseX = -1000; + let mouseY = -1000; + + function resize() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } + + function createParticle() { + return { + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vx: (Math.random() - 0.5) * CONFIG.particleMaxSpeed, + vy: (Math.random() - 0.5) * CONFIG.particleMaxSpeed, + size: Math.random() * 1.5 + 0.5, + opacity: Math.random() * 0.5 + 0.1, + }; + } + + function initParticleArray() { + particles = []; + for (let i = 0; i < CONFIG.particleCount; i++) { + particles.push(createParticle()); + } + } + + function drawParticle(p) { + ctx.beginPath(); + ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); + ctx.fillStyle = `rgba(0, 255, 200, ${p.opacity})`; + ctx.fill(); + } + + function drawConnections() { + for (let i = 0; i < particles.length; i++) { + for (let j = i + 1; j < particles.length; j++) { + const dx = particles[i].x - particles[j].x; + const dy = particles[i].y - particles[j].y; + const dist = Math.sqrt(dx * dx + dy * dy); + + if (dist < CONFIG.particleConnectionDist) { + const opacity = (1 - dist / CONFIG.particleConnectionDist) * 0.15; + ctx.beginPath(); + ctx.moveTo(particles[i].x, particles[i].y); + ctx.lineTo(particles[j].x, particles[j].y); + ctx.strokeStyle = `rgba(0, 255, 200, ${opacity})`; + ctx.lineWidth = 0.5; + ctx.stroke(); + } + } + } + + // Mouse connections + particles.forEach(p => { + const dx = p.x - mouseX; + const dy = p.y - mouseY; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 200) { + const opacity = (1 - dist / 200) * 0.3; + ctx.beginPath(); + ctx.moveTo(p.x, p.y); + ctx.lineTo(mouseX, mouseY); + ctx.strokeStyle = `rgba(0, 255, 200, ${opacity})`; + ctx.lineWidth = 0.8; + ctx.stroke(); + } + }); + } + + function update() { + particles.forEach(p => { + p.x += p.vx; + p.y += p.vy; + + // Wrap around + if (p.x < 0) p.x = canvas.width; + if (p.x > canvas.width) p.x = 0; + if (p.y < 0) p.y = canvas.height; + if (p.y > canvas.height) p.y = 0; + + // Mouse repulsion + const dx = p.x - mouseX; + const dy = p.y - mouseY; + const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < 100) { + const force = (100 - dist) / 100 * 0.02; + p.vx += (dx / dist) * force; + p.vy += (dy / dist) * force; + } + + // Speed limit + const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy); + if (speed > CONFIG.particleMaxSpeed * 2) { + p.vx *= 0.98; + p.vy *= 0.98; + } + }); + } + + function animate() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + update(); + drawConnections(); + particles.forEach(drawParticle); + animationId = requestAnimationFrame(animate); + } + + // Event listeners + window.addEventListener('resize', () => { + resize(); + initParticleArray(); + }); + + window.addEventListener('mousemove', (e) => { + mouseX = e.clientX; + mouseY = e.clientY; + }, { passive: true }); + + window.addEventListener('mouseleave', () => { + mouseX = -1000; + mouseY = -1000; + }); + + // Visibility API - pause when tab hidden + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + cancelAnimationFrame(animationId); + } else { + animate(); + } + }); + + resize(); + initParticleArray(); + animate(); + } + + // ─── GLITCH EFFECT (random intensification) ─── + function initGlitchEffect() { + const glitch = $('.glitch'); + if (!glitch) return; + + setInterval(() => { + glitch.classList.add('glitch-active'); + setTimeout(() => glitch.classList.remove('glitch-active'), 200); + }, 5000 + Math.random() * 5000); + } + + // ─── TERMINAL ANIMATION ─── + function initTerminal() { + const terminal = $('#terminal'); + if (!terminal) return; + + const commands = [ + { prompt: 'uptime', output: ' up 47 days, 3:22, 1 user, load: 0.12' }, + { prompt: 'df -h / | tail -1', output: '/dev/sda1 50G 12G 36G 25% /' }, + { prompt: 'curl -s ifconfig.me', output: '███.███.███.███' }, + { prompt: 'docker ps --format "table {{.Names}}"', output: 'gitea\nnginx-proxy\nagent-zero\nmonitoring' }, + { prompt: 'echo $SHELL', output: '/bin/zsh' }, + { prompt: 'neofetch --off | head -3', output: 'OS: Debian GNU/Linux 12\nKernel: 6.1.0-18-amd64\nUptime: 47 days, 3 hours' }, + ]; + + let cmdIndex = 0; + + function addTermLine(content, isOutput = false) { + const line = document.createElement('div'); + line.className = 'term-line' + (isOutput ? ' term-output' : ''); + if (isOutput) { + line.textContent = content; + } else { + line.innerHTML = `jae@swift:~$ ${content}`; + } + // Insert before the last line (cursor line) + const cursorLine = terminal.lastElementChild; + terminal.insertBefore(line, cursorLine); + } + + function typeCommand() { + if (cmdIndex >= commands.length) cmdIndex = 0; + const cmd = commands[cmdIndex]; + let charIdx = 0; + + // Remove old cursor line, add new command line being typed + const cursorLine = terminal.lastElementChild; + + const typingLine = document.createElement('div'); + typingLine.className = 'term-line'; + typingLine.innerHTML = `jae@swift:~$ `; + terminal.insertBefore(typingLine, cursorLine); + cursorLine.remove(); + + const cmdSpan = typingLine.querySelector('.term-cmd'); + + function typeChar() { + if (charIdx < cmd.prompt.length) { + cmdSpan.textContent += cmd.prompt[charIdx]; + charIdx++; + setTimeout(typeChar, 50 + Math.random() * 80); + } else { + // Remove cursor from typing line + const cursor = typingLine.querySelector('.term-cursor'); + if (cursor) cursor.remove(); + + // Show output + setTimeout(() => { + const outputLines = cmd.output.split('\n'); + outputLines.forEach(line => { + const outputEl = document.createElement('div'); + outputEl.className = 'term-line term-output'; + outputEl.textContent = line; + terminal.appendChild(outputEl); + }); + + // Add new cursor line + const newCursor = document.createElement('div'); + newCursor.className = 'term-line'; + newCursor.innerHTML = `jae@swift:~$ `; + terminal.appendChild(newCursor); + + // Scroll terminal + terminal.scrollTop = terminal.scrollHeight; + + // Keep only last ~15 lines + while (terminal.children.length > 15) { + terminal.removeChild(terminal.firstChild); + } + + cmdIndex++; + }, 300); + } + } + + typeChar(); + } + + // Run terminal animation every 6-10 seconds + setInterval(typeCommand, 8000 + Math.random() * 4000); + } + + // ─── CONTACT FORM ─── + function initContactForm() { + const form = $('#contactForm'); + if (!form) return; + + form.addEventListener('submit', (e) => { + e.preventDefault(); + const btn = form.querySelector('.form-submit'); + const originalText = btn.querySelector('.submit-text').textContent; + + btn.querySelector('.submit-text').textContent = 'TRANSMITTING...'; + btn.disabled = true; + btn.style.borderColor = 'var(--warning)'; + btn.style.color = 'var(--warning)'; + + setTimeout(() => { + btn.querySelector('.submit-text').textContent = '✓ TRANSMITTED'; + btn.style.borderColor = 'var(--accent)'; + btn.style.color = 'var(--accent)'; + + setTimeout(() => { + btn.querySelector('.submit-text').textContent = originalText; + btn.disabled = false; + btn.style.borderColor = ''; + btn.style.color = ''; + form.reset(); + }, 2000); + }, 1500); + }); + } + + // ─── FOOTER SIGNAL ANIMATION ─── + function initFooterSignal() { + const signal = $('#footerSignal'); + if (!signal) return; + + const blocks = ['░', '█']; + function update() { + const strength = 6 + Math.floor(Math.random() * 4); // 6-9 out of 10 + let bar = ''; + for (let i = 0; i < 10; i++) { + bar += i < strength ? blocks[1] : blocks[0]; + } + signal.textContent = `SIGNAL: ${bar} ${strength * 10}%`; + } + + setInterval(update, 3000); + } + + // ─── RANDOM HUD DATA FLICKERS ─── + function initHUDFlickers() { + const statValues = $$('.stat-value:not(.stat-online)'); + + setInterval(() => { + statValues.forEach(el => { + if (Math.random() > 0.7) { + el.style.opacity = '0.3'; + setTimeout(() => { + el.style.opacity = '1'; + }, 100 + Math.random() * 200); + } + }); + }, 2000); + } + + // ─── PANEL HOVER GLOW TRAIL ─── + function initPanelGlow() { + $$('.panel, .blog-card, .dev-card, .link-card').forEach(card => { + card.addEventListener('mousemove', (e) => { + const rect = card.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + card.style.setProperty('--mouse-x', `${x}px`); + card.style.setProperty('--mouse-y', `${y}px`); + card.style.background = `radial-gradient(circle 200px at ${x}px ${y}px, rgba(0, 255, 200, 0.04), var(--bg-panel))`; + }); + + card.addEventListener('mouseleave', () => { + card.style.background = ''; + }); + }); + } + + // ─── INIT ─── + document.addEventListener('DOMContentLoaded', () => { + initNavbar(); + initClock(); + initUptime(); + initTyping(); + initScrollReveal(); + initSkillBars(); + initParticles(); + initGlitchEffect(); + initTerminal(); + initContactForm(); + initFooterSignal(); + initHUDFlickers(); + initPanelGlow(); + + // Page load animation + document.body.style.opacity = '0'; + document.body.style.transition = 'opacity 0.8s ease'; + requestAnimationFrame(() => { + document.body.style.opacity = '1'; + }); + + console.log('%c[JAESWIFT] %cSystems Online', + 'color: #00ffc8; font-weight: bold; font-size: 14px;', + 'color: #c8d6e5; font-size: 12px;'); + }); + +})();