Skip to content

Commit 49f0d2a

Browse files
committed
first
0 parents  commit 49f0d2a

File tree

11 files changed

+661
-0
lines changed

11 files changed

+661
-0
lines changed

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Node.js
2+
3+
node_modules/
4+
npm-debug.log
5+
yarn-error.log
6+
package-lock.json
7+
Environment variables
8+
9+
.env
10+
.env.local
11+
IDE and OS files
12+
13+
.vscode/
14+
.idea/
15+
.DS_Store
16+
*.suo
17+
*.user
18+
Build output
19+
20+
dist/
21+
build/

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Personal Portfolio Website
2+
3+
This repository contains the source code for my personal portfolio website, built with **HTML**, **CSS**, and **vanilla JavaScript**.
4+
5+
## ✨ Features
6+
7+
- **Apple-inspired Landing Page** – Clean, minimalist entry point with a subtle animated background.
8+
- **Interactive XP Desktop** – Nostalgic Windows XP–themed portfolio page with draggable windows and icons.
9+
- **Dynamic Project Display** – Fetches and displays project information directly from the GitHub API.
10+
- **Classic Snake Game** – A fun, playable version of the classic Snake game.
11+
- **Secure Contact Form** – Uses [Formspree](https://formspree.io) for secure submissions without exposing personal email addresses.
12+
- **GDPR Compliant** – Includes a privacy notice and requires user consent for data handling.
13+
- **Fully Responsive** – Optimized for both desktop and mobile devices.
14+
15+
## 🚀 Deployment
16+
17+
The site is deployed using **GitHub Pages**.
18+
🔗 Live version: [https://eudk.github.io](https://eudk.github.io)
19+
20+
## 🔧 Setup
21+
22+
To run this project locally:
23+
24+
```bash
25+
# Clone the repository
26+
git clone https://github.com/your-username/eudk.github.io.git
27+
28+
# Open index.html in your browser

assets/img/img.png

13.4 KB
Loading

css/portfolio.css

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
body {
2+
font-family: "Tahoma", "MS Sans Serif", Arial, sans-serif;
3+
font-size: 11px;
4+
margin: 0;
5+
overflow: hidden;
6+
user-select: none;
7+
}
8+
#desktop {
9+
width: 100vw;
10+
height: 100vh;
11+
background: radial-gradient(ellipse at center, #2c3e50 0%, #1a1a1a 100%);
12+
position: relative;
13+
padding: 10px;
14+
}
15+
.desktop-icon {
16+
width: 80px; height: 80px; margin: 5px;
17+
display: flex; flex-direction: column; align-items: center; justify-content: center;
18+
color: white; text-shadow: 1px 1px 2px rgba(0,0,0,0.8);
19+
cursor: pointer; position: absolute; z-index: 10;
20+
}
21+
.desktop-icon i { font-size: 3.5em; margin-bottom: 5px; color: #f1f1f1; }
22+
.desktop-icon img { width: 50px; height: 50px; margin-bottom: 5px; }
23+
.desktop-icon p { font-size: 1em; text-align: center; margin: 0; }
24+
.desktop-icon:hover { background-color: rgba(70, 130, 180, 0.4); outline: 1px dashed white; }
25+
.desktop-icon.active { background-color: rgba(70, 130, 180, 0.6); outline: 1px solid white; }
26+
.xp-window {
27+
min-width: 300px; min-height: 200px;
28+
border: 1px solid #005cff; box-shadow: 2px 2px 10px rgba(0,0,0,0.5);
29+
position: absolute; z-index: 100;
30+
display: none; flex-direction: column;
31+
resize: both; overflow: auto;
32+
}
33+
.xp-window.show { display: flex; }
34+
.xp-window .title-bar {
35+
background: linear-gradient(to right, #005cff, #004fb6);
36+
color: white; padding: 4px 6px; font-weight: bold;
37+
display: flex; justify-content: space-between; align-items: center;
38+
cursor: grab;
39+
}
40+
.xp-window .title-bar.grabbing { cursor: grabbing; }
41+
.xp-window .title-bar-buttons { font-family: "Webdings", sans-serif; }
42+
.xp-window .title-bar-buttons span {
43+
display: inline-block; width: 16px; height: 16px;
44+
background-color: #c0c0c0; border: 1px outset #fff;
45+
color: black; text-align: center; line-height: 14px;
46+
font-size: 12px; cursor: pointer; margin-left: 2px;
47+
}
48+
.xp-window .window-body {
49+
background-color: #ece9d8; border-top: 1px solid white;
50+
padding: 10px; flex-grow: 1;
51+
display: flex; flex-direction: column; overflow: auto;
52+
}
53+
#project-icons { display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 15px; }
54+
.project-icon-in-window { text-align: center; cursor: pointer; width: 80px; color: black; border: 1px solid transparent; padding: 5px; border-radius: 3px; }
55+
.project-icon-in-window .fa-github { font-size: 3em; color: #333; margin-bottom: 5px; }
56+
.project-icon-in-window p { margin: 0; font-size: 0.9em; }
57+
.project-icon-in-window:hover { background-color: rgba(0,0,128,0.1); border: 1px dashed #888; }
58+
.project-icon-in-window.active { background-color: rgba(0,0,128,0.2); border: 1px solid #005cff; }
59+
#info-display { border: 2px inset #fff; background-color: #fff; padding: 10px; flex-grow: 1; }
60+
#info-display h3 { margin: 0 0 5px 0; }
61+
#info-display a { color: #005cff; text-decoration: none; }
62+
.stats { margin-top: 10px; display: flex; gap: 15px; }
63+
#snake-game-window { width: 320px; height: 400px; }
64+
#snake-game-window .window-body { align-items: center; justify-content: center; background-color: #000; padding: 0; }
65+
#snake-canvas { border: 2px solid #0f0; background-color: #000; }
66+
#game-controls { margin-top: 10px; color: white; text-align: center; }
67+
#game-controls button { background-color: #005cff; color: white; border: 1px outset #c0c0c0; padding: 5px 10px; cursor: pointer; }

css/style.css

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
body {
2+
background-color: #f8f8fa; color: #1d1d1f; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
3+
display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; text-align: center; overflow: hidden;
4+
}
5+
#matrix-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; }
6+
.profile-img { position: absolute; top: 30px; right: 30px; width: 100px; height: 100px; border-radius: 18px; object-fit: cover; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); border: 1px solid rgba(0, 0, 0, 0.05); }
7+
.content { width: 90%; max-width: 650px; padding: 40px 50px; background-color: rgba(255, 255, 255, 0.7); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-radius: 24px; border: 1px solid rgba(0, 0, 0, 0.05); }
8+
h1 { font-size: 3.5rem; font-weight: 600; margin-bottom: 1rem; letter-spacing: -1px; }
9+
10+
#bio-text {
11+
font-size: 1.25rem; color: #555; line-height: 1.7; margin-bottom: 2rem; font-weight: 400;
12+
min-height: 84px; /* Prevents layout shift */
13+
}
14+
#typewriter-cursor { display: inline-block; background-color: #555; width: 2px; animation: blink 1s step-end infinite; }
15+
@keyframes blink { from, to { background-color: transparent } 50% { background-color: #555; } }
16+
17+
.action-buttons { display: flex; justify-content: center; gap: 15px; margin-top: 1.5rem; flex-wrap: wrap; }
18+
.action-buttons a, .action-buttons button {
19+
background-color: #fff; color: #333; border: 1px solid #ddd; border-radius: 8px;
20+
padding: 10px 20px; font-size: 1rem; font-weight: 500; text-decoration: none;
21+
display: flex; align-items: center; gap: 8px; cursor: pointer;
22+
transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.05);
23+
}
24+
.action-buttons a:hover, .action-buttons button:hover { border-color: #bbb; transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
25+
.action-buttons i { font-size: 1.2rem; }
26+
27+
.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: none; align-items: center; justify-content: center; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; }
28+
.modal-overlay.show { display: flex; opacity: 1; }
29+
.modal-content { background: #fff; padding: 40px; border-radius: 16px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); width: 90%; max-width: 500px; position: relative; transform: scale(0.95); transition: transform 0.3s ease; text-align: left; }
30+
.modal-overlay.show .modal-content { transform: scale(1); }
31+
.modal-content h2 { margin-top: 0; margin-bottom: 8px; color: #1d1d1f; font-size: 2rem; font-weight: 600; }
32+
.modal-subtitle { font-size: 1rem; color: #666; margin-top: 0; margin-bottom: 25px; line-height: 1.5; }
33+
.close-btn { position: absolute; top: 15px; right: 20px; font-size: 2.5rem; color: #aaa; cursor: pointer; transition: color 0.2s; }
34+
.close-btn:hover { color: #333; }
35+
.contact-form .form-group { margin-bottom: 20px; }
36+
.contact-form label { display: block; margin-bottom: 6px; font-size: 0.9rem; font-weight: 500; color: #333; }
37+
.contact-form input, .contact-form textarea { width: 100%; padding: 12px; border: 1px solid #e5e5e5; border-radius: 8px; background-color: #f9f9f9; font-family: inherit; font-size: 1rem; box-sizing: border-box; transition: border-color 0.2s ease, box-shadow 0.2s ease; }
38+
.contact-form input:focus, .contact-form textarea:focus { outline: none; border-color: #007aff; box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.2); }
39+
.contact-form textarea { min-height: 120px; resize: vertical; }
40+
.contact-form button { width: 100%; background-color: #007aff; color: white; font-weight: 600; border: none; padding: 14px 20px; border-radius: 8px; cursor: pointer; font-size: 1rem; transition: background-color 0.2s ease; }
41+
.contact-form button:hover { background-color: #005ecb; }
42+
.contact-form button:disabled { background-color: #cce4ff; cursor: not-allowed; }
43+
.form-group-consent { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 20px; }
44+
.form-group-consent input { width: auto; margin-top: 3px; }
45+
.form-group-consent label { margin-bottom: 0; font-size: 0.8rem; color: #666; }
46+
.form-group-consent label a { color: #007aff; text-decoration: none; }
47+
.form-group-consent label a:hover { text-decoration: underline; }
48+
49+
/* --- Mobile Responsiveness --- */
50+
@media (max-width: 768px) {
51+
.profile-img { width: 80px; height: 80px; top: 20px; right: 20px; }
52+
.content { padding: 30px 25px; }
53+
h1 { font-size: 2.5rem; }
54+
#bio-text { font-size: 1.1rem; min-height: 100px; }
55+
.action-buttons { flex-direction: column; align-items: center; }
56+
.action-buttons a, .action-buttons button { width: 100%; max-width: 300px; justify-content: center; }
57+
.modal-content { padding: 30px 25px; }
58+
}

index.html

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Evgeniy G | IT Security Portfolio</title>
7+
<meta name="referrer" content="strict-origin-when-cross-origin">
8+
<meta http-equiv="Content-Security-Policy" content="
9+
default-src 'self';
10+
script-src 'self';
11+
style-src 'self' https://cdnjs.cloudflare.com;
12+
img-src 'self' data:;
13+
font-src https://cdnjs.cloudflare.com;
14+
connect-src 'self';
15+
form-action https://formspree.io;
16+
frame-ancestors 'none';
17+
base-uri 'self';
18+
object-src 'none';
19+
upgrade-insecure-requests;
20+
">
21+
22+
<meta name="description" content="Portfolio for Evgeniy, an IT Security student specializing in GRC">
23+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
24+
<link rel="stylesheet" href="css/style.css">
25+
</head>
26+
<body>
27+
<canvas id="matrix-canvas"></canvas>
28+
<img src="assets/img/img.png" alt="eudk" class="profile-img">
29+
<main class="content">
30+
<h1>Evgeniy Gordienko</h1>
31+
32+
<p id="bio-text"><span id="typewriter-text"></span><span id="typewriter-cursor">&nbsp;</span></p>
33+
34+
<div class="action-buttons">
35+
<a href="https://github.com/eudk" target="_blank" rel="noopener noreferrer"><i class="fab fa-github"></i> GitHub</a>
36+
<a href="https://linkedin.com/in/eudk" target="_blank" rel="noopener noreferrer"><i class="fab fa-linkedin"></i> LinkedIn</a>
37+
<a href="portfolio.html" id="projects-link"><i class="fas fa-briefcase"></i> Projects</a>
38+
<button id="open-contact-modal"><i class="fas fa-envelope"></i> Contact Me</button>
39+
</div>
40+
</main>
41+
42+
<div class="modal-overlay" id="contact-modal-overlay" role="dialog" aria-modal="true">
43+
<div class="modal-content">
44+
<span class="close-btn" id="close-contact-modal">&times;</span>
45+
<form id="contact-form" class="contact-form" action="https://formspree.io/f/xqayznjr" method="POST">
46+
<h2>Contact Me</h2>
47+
<p class="modal-subtitle">I'm actively seeking opportunities in IT. For inquiries about my work or potential roles, please feel free to reach out here or through LinkedIn.</p>
48+
<div class="form-group">
49+
<label for="email">Your Email</label>
50+
<input type="email" id="email" name="email" required>
51+
</div>
52+
<div class="form-group">
53+
<label for="message">Your Message</label>
54+
<textarea id="message" name="message" required></textarea>
55+
</div>
56+
<div class="form-group-consent">
57+
<input type="checkbox" id="consent" name="consent" required>
58+
<label for="consent">I consent to my data being handled as described in the <a href="./privacy.html" target="_blank" rel="noopener noreferrer">Privacy Notice</a>.</label>
59+
</div>
60+
<button type="submit">Send Message</button>
61+
</form>
62+
</div>
63+
</div>
64+
65+
<script src="js/main.js" defer></script>
66+
</body>
67+
</html>

js/main.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
// --- Mobile Redirect for Projects Link ---
3+
// This code checks if the user is on a mobile device.
4+
const isMobile = /Mobi|Android/i.test(navigator.userAgent);
5+
const projectsLink = document.getElementById('projects-link');
6+
7+
if (isMobile && projectsLink) {
8+
projectsLink.href = "https://github.com/eudk";
9+
projectsLink.target = "_blank";
10+
projectsLink.rel = "noopener noreferrer";
11+
}
12+
13+
// --- Typewriter Effect Logic ---
14+
const typewriterTextElement = document.getElementById('typewriter-text');
15+
if (typewriterTextElement) {
16+
const phrases = [
17+
"IT security student DK .",
18+
"Learning governance, risk and compliance.",
19+
"Hands-on with threat modeling and vuln management.",
20+
"Building small tools to automate tasks.",
21+
"Getting into cloud security and infrastructure as code.",
22+
"Open for internship opportunities."
23+
];
24+
let phraseIndex = 0;
25+
let charIndex = 0;
26+
let isDeleting = false;
27+
28+
function typeWriter() {
29+
const currentPhrase = phrases[phraseIndex];
30+
let typeSpeed = 70;
31+
if (isDeleting) {
32+
typeSpeed = 40;
33+
typewriterTextElement.textContent = currentPhrase.substring(0, charIndex - 1);
34+
charIndex--;
35+
} else {
36+
typewriterTextElement.textContent = currentPhrase.substring(0, charIndex + 1);
37+
charIndex++;
38+
}
39+
if (!isDeleting && charIndex === currentPhrase.length) {
40+
isDeleting = true;
41+
typeSpeed = 2000;
42+
} else if (isDeleting && charIndex === 0) {
43+
isDeleting = false;
44+
phraseIndex = (phraseIndex + 1) % phrases.length;
45+
typeSpeed = 500;
46+
}
47+
setTimeout(typeWriter, typeSpeed);
48+
}
49+
if (phrases.length > 0) {
50+
typeWriter();
51+
}
52+
}
53+
54+
// --- Formspree Success Message Handler ---
55+
const urlParams = new URLSearchParams(window.location.search);
56+
if (urlParams.has('submission') && urlParams.get('submission') === 'success') {
57+
const successBanner = document.getElementById('success-banner');
58+
if (successBanner) {
59+
successBanner.classList.add('show');
60+
setTimeout(() => {
61+
successBanner.classList.remove('show');
62+
}, 5000); // Hide banner after 5 seconds
63+
}
64+
}
65+
});
66+
67+
const canvas = document.getElementById('matrix-canvas');
68+
const ctx = canvas.getContext('2d');
69+
let animationFrameId;
70+
function setupCanvas() {
71+
canvas.width = window.innerWidth;
72+
canvas.height = window.innerHeight;
73+
const columns = canvas.width / 14;
74+
const drops = [];
75+
for (let x = 0; x < columns; x++) drops[x] = 1;
76+
return { drops };
77+
}
78+
let { drops } = setupCanvas();
79+
function draw() {
80+
ctx.fillStyle = 'rgba(248, 248, 250, 0.08)';
81+
ctx.fillRect(0, 0, canvas.width, canvas.height);
82+
ctx.fillStyle = '#e8e8ed';
83+
ctx.font = '14px arial';
84+
for (let i = 0; i < drops.length; i++) {
85+
const text = Math.random() > 0.5 ? '0' : '1';
86+
ctx.fillText(text, i * 14, drops[i] * 14);
87+
if (drops[i] * 14 > canvas.height && Math.random() > 0.99) drops[i] = 0;
88+
drops[i]++;
89+
}
90+
}
91+
function animate() {
92+
draw();
93+
animationFrameId = requestAnimationFrame(animate);
94+
}
95+
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
96+
animate();
97+
}
98+
window.addEventListener('resize', () => {
99+
if (animationFrameId) {
100+
cancelAnimationFrame(animationFrameId);
101+
({ drops } = setupCanvas());
102+
animate();
103+
}
104+
});
105+
106+
// --- Accessible Modal & Form Logic ---
107+
const openModalBtn = document.getElementById('open-contact-modal');
108+
const closeModalBtn = document.getElementById('close-contact-modal');
109+
const modalOverlay = document.getElementById('contact-modal-overlay');
110+
const consentCheckbox = document.getElementById('consent');
111+
const submitButton = document.querySelector('#contact-form button[type="submit"]');
112+
function updateSubmitButtonState() {
113+
submitButton.disabled = !consentCheckbox.checked;
114+
}
115+
consentCheckbox.addEventListener('change', updateSubmitButtonState);
116+
openModalBtn.addEventListener('click', (e) => {
117+
e.preventDefault();
118+
modalOverlay.classList.add('show');
119+
consentCheckbox.checked = false;
120+
updateSubmitButtonState();
121+
});
122+
const closeModal = () => {
123+
modalOverlay.classList.remove('show');
124+
};
125+
closeModalBtn.addEventListener('click', closeModal);
126+
modalOverlay.addEventListener('click', (e) => {
127+
if (e.target === modalOverlay) closeModal();
128+
});
129+
document.addEventListener('keydown', (e) => {
130+
if (e.key === "Escape" && modalOverlay.classList.contains('show')) closeModal();
131+
});
132+
updateSubmitButtonState();
133+

0 commit comments

Comments
 (0)