🚀 Initial build: sci-fi dashboard website
- HUD-style navbar with dropdown menus (HOME, BLOG, DEV, LINKS, CONTACT) - Particle system with mouse interaction - Glitch text + typing animation hero - Scroll-reveal animations - Animated skill bars - Blog cards, dev project cards, link cards - Contact form with transmission effect - Live terminal emulator - Scanline overlay + grid background - Responsive mobile layout - Live clock + uptime counter + signal animation
This commit is contained in:
parent
51eca53b76
commit
ae1c7e0b71
4 changed files with 2227 additions and 2 deletions
79
README.md
79
README.md
|
|
@ -1,3 +1,78 @@
|
||||||
# jaeswift-website
|
# JAESWIFT.XYZ
|
||||||
|
|
||||||
Personal website for jaeswift.xyz - Sci-fi dashboard aesthetic
|
> Sci-fi dashboard-inspired personal website
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
|
||||||
1135
css/style.css
Normal file
1135
css/style.css
Normal file
File diff suppressed because it is too large
Load diff
443
index.html
Normal file
443
index.html
Normal file
|
|
@ -0,0 +1,443 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>JAESWIFT // SYSTEMS ONLINE</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Scanline overlay -->
|
||||||
|
<div class="scanline-overlay"></div>
|
||||||
|
|
||||||
|
<!-- Grid background -->
|
||||||
|
<div class="grid-bg"></div>
|
||||||
|
|
||||||
|
<!-- Particle canvas -->
|
||||||
|
<canvas id="particles"></canvas>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="nav-main" id="navbar">
|
||||||
|
<div class="nav-container">
|
||||||
|
<a href="#" class="nav-logo">
|
||||||
|
<span class="logo-bracket">[</span>
|
||||||
|
<span class="logo-text">JAE<span class="logo-accent">SWIFT</span></span>
|
||||||
|
<span class="logo-bracket">]</span>
|
||||||
|
<span class="logo-status">● ONLINE</span>
|
||||||
|
</a>
|
||||||
|
<button class="nav-toggle" id="navToggle" aria-label="Toggle navigation">
|
||||||
|
<span></span><span></span><span></span>
|
||||||
|
</button>
|
||||||
|
<ul class="nav-menu" id="navMenu">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#home" class="nav-link active">HOME</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<li><a href="#home">Dashboard</a></li>
|
||||||
|
<li><a href="#about">About Me</a></li>
|
||||||
|
<li><a href="#status">System Status</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#blog" class="nav-link">BLOG</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<li><a href="#blog">Latest Posts</a></li>
|
||||||
|
<li><a href="#blog">Tutorials</a></li>
|
||||||
|
<li><a href="#blog">Write-ups</a></li>
|
||||||
|
<li><a href="#blog">Archive</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#development" class="nav-link">DEVELOPMENT</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<li><a href="#development">Projects</a></li>
|
||||||
|
<li><a href="#development">Repositories</a></li>
|
||||||
|
<li><a href="#development">Tech Stack</a></li>
|
||||||
|
<li><a href="#development">Contributions</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#links" class="nav-link">LINKS</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<li><a href="https://git.jaeswift.xyz/jae" target="_blank">Gitea</a></li>
|
||||||
|
<li><a href="https://gitlab.com/jaeswift" target="_blank">GitLab</a></li>
|
||||||
|
<li><a href="https://reddit.com/u/jaeswift" target="_blank">Reddit</a></li>
|
||||||
|
<li><a href="#links">All Links</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="#contact" class="nav-link">CONTACT</a>
|
||||||
|
<ul class="dropdown">
|
||||||
|
<li><a href="#contact">Send Message</a></li>
|
||||||
|
<li><a href="mailto:jae@jaeswift.xyz">Email</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="nav-status">
|
||||||
|
<span class="nav-time" id="navTime">00:00:00</span>
|
||||||
|
<span class="nav-signal">▮▮▮▮▯</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Hero Section -->
|
||||||
|
<section class="hero" id="home">
|
||||||
|
<div class="hero-hud">
|
||||||
|
<div class="hud-corner hud-tl"></div>
|
||||||
|
<div class="hud-corner hud-tr"></div>
|
||||||
|
<div class="hud-corner hud-bl"></div>
|
||||||
|
<div class="hud-corner hud-br"></div>
|
||||||
|
|
||||||
|
<div class="hero-content">
|
||||||
|
<div class="hero-label">SYSTEM IDENTIFICATION</div>
|
||||||
|
<h1 class="hero-title">
|
||||||
|
<span class="glitch" data-text="JAESWIFT">JAESWIFT</span>
|
||||||
|
</h1>
|
||||||
|
<div class="hero-subtitle">
|
||||||
|
<span class="typing-prefix">> </span>
|
||||||
|
<span class="typing-text" id="typingText"></span>
|
||||||
|
<span class="typing-cursor">█</span>
|
||||||
|
</div>
|
||||||
|
<div class="hero-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">STATUS</span>
|
||||||
|
<span class="stat-value stat-online">● OPERATIONAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">UPTIME</span>
|
||||||
|
<span class="stat-value" id="uptime">0d 00h 00m</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<span class="stat-label">LOCATION</span>
|
||||||
|
<span class="stat-value">EARTH // LEO</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scanning animation -->
|
||||||
|
<div class="scan-line"></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- About Section -->
|
||||||
|
<section class="section" id="about">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-tag">01</span>
|
||||||
|
<h2 class="section-title">ABOUT<span class="accent">_</span>ME</h2>
|
||||||
|
<div class="section-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="panels-grid">
|
||||||
|
<div class="panel panel-main" data-animate>
|
||||||
|
<div class="panel-header">
|
||||||
|
<span class="panel-title">OPERATOR PROFILE</span>
|
||||||
|
<span class="panel-icon">↗</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<p>Developer, tinkerer, and digital architect. I build things that live on the internet — from self-hosted infrastructure to custom web applications.</p>
|
||||||
|
<p>Passionate about open source, cybersecurity, and making technology work the way it should.</p>
|
||||||
|
<div class="panel-data">
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-key">CODENAME</span>
|
||||||
|
<span class="data-val">JAE</span>
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-key">DOMAIN</span>
|
||||||
|
<span class="data-val">JAESWIFT.XYZ</span>
|
||||||
|
</div>
|
||||||
|
<div class="data-row">
|
||||||
|
<span class="data-key">CLEARANCE</span>
|
||||||
|
<span class="data-val">ADMIN</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel" data-animate>
|
||||||
|
<div class="panel-header">
|
||||||
|
<span class="panel-title">SKILL MATRIX</span>
|
||||||
|
<span class="panel-icon">↗</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="skill-bar">
|
||||||
|
<span class="skill-name">LINUX / SYSADMIN</span>
|
||||||
|
<div class="bar-track"><div class="bar-fill" data-width="92"></div></div>
|
||||||
|
<span class="skill-pct">92%</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-bar">
|
||||||
|
<span class="skill-name">WEB DEVELOPMENT</span>
|
||||||
|
<div class="bar-track"><div class="bar-fill" data-width="85"></div></div>
|
||||||
|
<span class="skill-pct">85%</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-bar">
|
||||||
|
<span class="skill-name">PYTHON</span>
|
||||||
|
<div class="bar-track"><div class="bar-fill" data-width="88"></div></div>
|
||||||
|
<span class="skill-pct">88%</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-bar">
|
||||||
|
<span class="skill-name">DOCKER / INFRA</span>
|
||||||
|
<div class="bar-track"><div class="bar-fill" data-width="80"></div></div>
|
||||||
|
<span class="skill-pct">80%</span>
|
||||||
|
</div>
|
||||||
|
<div class="skill-bar">
|
||||||
|
<span class="skill-name">CYBERSECURITY</span>
|
||||||
|
<div class="bar-track"><div class="bar-fill" data-width="75"></div></div>
|
||||||
|
<span class="skill-pct">75%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Blog Section -->
|
||||||
|
<section class="section" id="blog">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-tag">02</span>
|
||||||
|
<h2 class="section-title">BLOG<span class="accent">_</span>FEED</h2>
|
||||||
|
<div class="section-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="blog-grid">
|
||||||
|
<article class="blog-card" data-animate>
|
||||||
|
<div class="blog-card-header">
|
||||||
|
<span class="blog-date">2026.03.31</span>
|
||||||
|
<span class="blog-tag">INFRASTRUCTURE</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="blog-title">Self-Hosting Everything: A Complete Guide</h3>
|
||||||
|
<p class="blog-excerpt">How I migrated away from cloud services and built a fully self-hosted infrastructure stack...</p>
|
||||||
|
<div class="blog-footer">
|
||||||
|
<span class="blog-read-time">◷ 8 MIN READ</span>
|
||||||
|
<a href="#" class="blog-link">READ →</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="blog-card" data-animate>
|
||||||
|
<div class="blog-card-header">
|
||||||
|
<span class="blog-date">2026.03.28</span>
|
||||||
|
<span class="blog-tag">SECURITY</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="blog-title">Hardening Your VPS: Beyond the Basics</h3>
|
||||||
|
<p class="blog-excerpt">Essential security configurations that most tutorials skip — from kernel parameters to network isolation...</p>
|
||||||
|
<div class="blog-footer">
|
||||||
|
<span class="blog-read-time">◷ 12 MIN READ</span>
|
||||||
|
<a href="#" class="blog-link">READ →</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<article class="blog-card" data-animate>
|
||||||
|
<div class="blog-card-header">
|
||||||
|
<span class="blog-date">2026.03.22</span>
|
||||||
|
<span class="blog-tag">DEV</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="blog-title">Building AI Agents That Actually Work</h3>
|
||||||
|
<p class="blog-excerpt">My journey deploying Agent Zero and customising autonomous AI assistants for real-world tasks...</p>
|
||||||
|
<div class="blog-footer">
|
||||||
|
<span class="blog-read-time">◷ 10 MIN READ</span>
|
||||||
|
<a href="#" class="blog-link">READ →</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Development Section -->
|
||||||
|
<section class="section" id="development">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-tag">03</span>
|
||||||
|
<h2 class="section-title">DEV<span class="accent">_</span>OPS</h2>
|
||||||
|
<div class="section-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="dev-grid">
|
||||||
|
<div class="dev-card" data-animate>
|
||||||
|
<div class="dev-card-top">
|
||||||
|
<span class="dev-icon">⬡</span>
|
||||||
|
<span class="dev-status">● ACTIVE</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="dev-title">Agent Zero</h3>
|
||||||
|
<p class="dev-desc">Custom AI agent framework deployment with autonomous task execution capabilities.</p>
|
||||||
|
<div class="dev-tech">
|
||||||
|
<span class="tech-tag">Python</span>
|
||||||
|
<span class="tech-tag">Docker</span>
|
||||||
|
<span class="tech-tag">LLM</span>
|
||||||
|
</div>
|
||||||
|
<div class="dev-links">
|
||||||
|
<a href="https://git.jaeswift.xyz/jae" class="dev-link">REPO →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dev-card" data-animate>
|
||||||
|
<div class="dev-card-top">
|
||||||
|
<span class="dev-icon">◈</span>
|
||||||
|
<span class="dev-status">● ACTIVE</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="dev-title">Infrastructure Stack</h3>
|
||||||
|
<p class="dev-desc">Self-hosted services including Gitea, monitoring, reverse proxies, and automated deployments.</p>
|
||||||
|
<div class="dev-tech">
|
||||||
|
<span class="tech-tag">Linux</span>
|
||||||
|
<span class="tech-tag">Nginx</span>
|
||||||
|
<span class="tech-tag">Docker</span>
|
||||||
|
</div>
|
||||||
|
<div class="dev-links">
|
||||||
|
<a href="#" class="dev-link">DETAILS →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dev-card" data-animate>
|
||||||
|
<div class="dev-card-top">
|
||||||
|
<span class="dev-icon">◉</span>
|
||||||
|
<span class="dev-status status-dev">◌ IN DEV</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="dev-title">JAESWIFT.XYZ</h3>
|
||||||
|
<p class="dev-desc">This website — a sci-fi dashboard-inspired personal hub with custom animations and HUD elements.</p>
|
||||||
|
<div class="dev-tech">
|
||||||
|
<span class="tech-tag">HTML</span>
|
||||||
|
<span class="tech-tag">CSS</span>
|
||||||
|
<span class="tech-tag">JavaScript</span>
|
||||||
|
</div>
|
||||||
|
<div class="dev-links">
|
||||||
|
<a href="https://git.jaeswift.xyz/jae/jaeswift-website" class="dev-link">REPO →</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Links Section -->
|
||||||
|
<section class="section" id="links">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-tag">04</span>
|
||||||
|
<h2 class="section-title">EXT<span class="accent">_</span>LINKS</h2>
|
||||||
|
<div class="section-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="links-grid">
|
||||||
|
<a href="https://git.jaeswift.xyz/jae" target="_blank" class="link-card" data-animate>
|
||||||
|
<div class="link-icon">⟐</div>
|
||||||
|
<div class="link-info">
|
||||||
|
<span class="link-name">GITEA</span>
|
||||||
|
<span class="link-url">git.jaeswift.xyz</span>
|
||||||
|
</div>
|
||||||
|
<span class="link-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://gitlab.com/jaeswift" target="_blank" class="link-card" data-animate>
|
||||||
|
<div class="link-icon">◈</div>
|
||||||
|
<div class="link-info">
|
||||||
|
<span class="link-name">GITLAB</span>
|
||||||
|
<span class="link-url">gitlab.com/jaeswift</span>
|
||||||
|
</div>
|
||||||
|
<span class="link-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
<a href="https://reddit.com/u/jaeswift" target="_blank" class="link-card" data-animate>
|
||||||
|
<div class="link-icon">◎</div>
|
||||||
|
<div class="link-info">
|
||||||
|
<span class="link-name">REDDIT</span>
|
||||||
|
<span class="link-url">reddit.com/u/jaeswift</span>
|
||||||
|
</div>
|
||||||
|
<span class="link-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
<a href="mailto:jae@jaeswift.xyz" class="link-card" data-animate>
|
||||||
|
<div class="link-icon">✉</div>
|
||||||
|
<div class="link-info">
|
||||||
|
<span class="link-name">EMAIL</span>
|
||||||
|
<span class="link-url">jae@jaeswift.xyz</span>
|
||||||
|
</div>
|
||||||
|
<span class="link-arrow">→</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Contact Section -->
|
||||||
|
<section class="section" id="contact">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="section-header">
|
||||||
|
<span class="section-tag">05</span>
|
||||||
|
<h2 class="section-title">COMMS<span class="accent">_</span>LINK</h2>
|
||||||
|
<div class="section-line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="contact-grid">
|
||||||
|
<div class="panel contact-panel" data-animate>
|
||||||
|
<div class="panel-header">
|
||||||
|
<span class="panel-title">TRANSMIT MESSAGE</span>
|
||||||
|
<span class="panel-icon">◇</span>
|
||||||
|
</div>
|
||||||
|
<form class="contact-form" id="contactForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">CALLSIGN</label>
|
||||||
|
<input type="text" class="form-input" placeholder="Enter your name..." required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">FREQUENCY</label>
|
||||||
|
<input type="email" class="form-input" placeholder="your@email.com" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">SUBJECT</label>
|
||||||
|
<input type="text" class="form-input" placeholder="Transmission subject...">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">MESSAGE PAYLOAD</label>
|
||||||
|
<textarea class="form-input form-textarea" placeholder="Enter your message..." rows="5" required></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="form-submit">
|
||||||
|
<span class="submit-text">TRANSMIT</span>
|
||||||
|
<span class="submit-icon">▶</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="contact-info" data-animate>
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<span class="panel-title">SIGNAL INFO</span>
|
||||||
|
<span class="panel-icon">↗</span>
|
||||||
|
</div>
|
||||||
|
<div class="panel-content">
|
||||||
|
<div class="info-block">
|
||||||
|
<span class="info-label">PRIMARY CHANNEL</span>
|
||||||
|
<span class="info-value">jae@jaeswift.xyz</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-block">
|
||||||
|
<span class="info-label">RESPONSE TIME</span>
|
||||||
|
<span class="info-value">~24 HOURS</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-block">
|
||||||
|
<span class="info-label">ENCRYPTION</span>
|
||||||
|
<span class="info-value">PGP AVAILABLE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel terminal-panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<span class="panel-title">TERMINAL</span>
|
||||||
|
<span class="panel-icon">_</span>
|
||||||
|
</div>
|
||||||
|
<div class="terminal-content" id="terminal">
|
||||||
|
<div class="term-line"><span class="term-prompt">jae@swift:~$</span> <span class="term-cmd">whoami</span></div>
|
||||||
|
<div class="term-line term-output">jae</div>
|
||||||
|
<div class="term-line"><span class="term-prompt">jae@swift:~$</span> <span class="term-cmd">cat /etc/motd</span></div>
|
||||||
|
<div class="term-line term-output">Welcome to jaeswift.xyz</div>
|
||||||
|
<div class="term-line term-output">All systems operational.</div>
|
||||||
|
<div class="term-line"><span class="term-prompt">jae@swift:~$</span> <span class="term-cursor">█</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="footer-container">
|
||||||
|
<div class="footer-left">
|
||||||
|
<span class="footer-logo">[JAE<span class="accent">SWIFT</span>]</span>
|
||||||
|
<span class="footer-copy">© 2026 // ALL SYSTEMS NOMINAL</span>
|
||||||
|
</div>
|
||||||
|
<div class="footer-right">
|
||||||
|
<span class="footer-coords">LAT: +51.5° // LONG: -0.1°</span>
|
||||||
|
<span class="footer-signal" id="footerSignal">SIGNAL: ████████░░ 80%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-bar"></div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
572
js/main.js
Normal file
572
js/main.js
Normal file
|
|
@ -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 = `<span class="term-prompt">jae@swift:~$</span> <span class="term-cmd">${content}</span>`;
|
||||||
|
}
|
||||||
|
// 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 = `<span class="term-prompt">jae@swift:~$</span> <span class="term-cmd"></span><span class="term-cursor">█</span>`;
|
||||||
|
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 = `<span class="term-prompt">jae@swift:~$</span> <span class="term-cursor">█</span>`;
|
||||||
|
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;');
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
Loading…
Add table
Reference in a new issue