This commit is contained in:
Dejvino 2026-05-20 09:29:08 +02:00
parent e7cb2ec37a
commit 319c01c59c

328
index.php
View File

@ -1,8 +1,9 @@
<?php <?php
/** /**
* Content config: * Content config:
*/ */
$cvData = json_decode(file_get_contents(__DIR__.'/cv.json'), true); $cvData = json_decode(file_get_contents(__DIR__ . '/cv.json'), true);
$name = $cvData['name'] ?? ''; $name = $cvData['name'] ?? '';
$site_title = $cvData['subtitle'] ?? ''; $site_title = $cvData['subtitle'] ?? '';
@ -34,7 +35,7 @@ $extra_contact_links = $cvData['contact_links'] ?? [];
* CV Data * CV Data
*/ */
// Map experience to expected structure // Map experience to expected structure
$cv_experience = array_map(function($e){ $cv_experience = array_map(function ($e) {
return [ return [
'title' => $e['title'] ?? '', 'title' => $e['title'] ?? '',
'org' => $e['organization'] ?? '', 'org' => $e['organization'] ?? '',
@ -44,7 +45,7 @@ $cv_experience = array_map(function($e){
}, $cvData['experience'] ?? []); }, $cvData['experience'] ?? []);
// Map education to expected structure // Map education to expected structure
$cv_education = array_map(function($e){ $cv_education = array_map(function ($e) {
return [ return [
'title' => $e['degree'] ?? '', 'title' => $e['degree'] ?? '',
'org' => $e['institution'] ?? '', 'org' => $e['institution'] ?? '',
@ -65,14 +66,15 @@ $cv_skills = $cvData['skills'] ?? [];
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head>
<meta charset="utf-8"> <meta charset="utf-8">
<title><?= $site_title ?></title> <title><?= $site_title ?></title>
<link rel="canonical" href="<?= $site_url ?>"> <link rel="canonical" href="<?= $site_url ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="<?= $site_desc ?>"> <meta name="description" content="<?= $site_desc ?>">
<meta name="author" content="<?= $name ?>"> <meta name="author" content="<?= $name ?>">
<!-- Open Graph / Social Media --> <!-- Open Graph / Social Media -->
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content="<?= $site_url ?>"> <meta property="og:url" content="<?= $site_url ?>">
@ -80,23 +82,24 @@ $cv_skills = $cvData['skills'] ?? [];
<meta property="og:description" content="<?= $subtitle ?>"> <meta property="og:description" content="<?= $subtitle ?>">
<style> <style>
<?php <?php
include 'style.css'; include 'style.css';
?> ?>
</style> </style>
<link rel="shortcut icon" href="favicon.ico"> <link rel="shortcut icon" href="favicon.ico">
</head> </head>
<body class="preload">
<body class="preload">
<main class="bento-grid"> <main class="bento-grid">
<section class="card profile"> <section class="card profile">
<?php <?php
if (file_exists($photo_file)) { if (file_exists($photo_file)) {
$avatarBase64 = base64_encode(file_get_contents($photo_file)); $avatarBase64 = base64_encode(file_get_contents($photo_file));
echo '<img src="data:image/png;base64,' . $avatarBase64 . '" alt="' . $name . '" class="avatar" width="120" height="160" />'; echo '<img src="data:image/png;base64,' . $avatarBase64 . '" alt="' . $name . '" class="avatar" width="120" height="160" />';
} else { } else {
echo '<div class="avatar-placeholder"></div>'; echo '<div class="avatar-placeholder"></div>';
} }
?> ?>
<header class="header-text"> <header class="header-text">
<h1><?= $name ?></h1> <h1><?= $name ?></h1>
@ -119,13 +122,13 @@ $cv_skills = $cvData['skills'] ?? [];
<section class="card contact"> <section class="card contact">
<h2>Remote_Access</h2> <h2>Remote_Access</h2>
<address class="mono-addr"> <address class="mono-addr">
Email: <span id="contact-mail">uncovering...</span> Email: <span id="contact-mail">uncovering...</span>
</address> </address>
<address class="mono-addr"> <address class="mono-addr">
Socials: Socials:
<?php foreach ($extra_contact_links as $label => $url): ?> <?php foreach ($extra_contact_links as $label => $url): ?>
<br><a href="<?= $url ?>" target="_blank"><?= $label ?></a> <br><a href="<?= $url ?>" target="_blank"><?= $label ?></a>
<?php endforeach; ?> <?php endforeach; ?>
</address> </address>
</section> </section>
@ -152,47 +155,44 @@ $cv_skills = $cvData['skills'] ?? [];
<div class="cv-content"> <div class="cv-content">
<?php $file_mod_time = filemtime(__FILE__); ?> <?php $file_mod_time = filemtime(__FILE__); ?>
<p class="cv-modified hidden hidden-toggle">Modified: <?= date("Y-m-d", $file_mod_time) ?></p> <p class="cv-modified hidden hidden-toggle">Modified: <?= date("Y-m-d", $file_mod_time) ?></p>
<?php <?php
$firstJob = $cv_experience[0] ?? null; $firstJob = $cv_experience[0] ?? null;
if ($firstJob): if ($firstJob):
?> ?>
<section class="cv-section first-experience hidden hidden-toggle"> <section class="cv-section first-experience hidden hidden-toggle">
<h2>Latest Experience</h2> <h2>Latest Experience</h2>
<article class="cv-item"> <article class="cv-item">
<header class="cv-item-header"> <header class="cv-item-header">
<h3 class="cv-title"><?= $firstJob['title'] ?></h3> <h3 class="cv-title"><?= $firstJob['title'] ?></h3>
<time class="cv-date"><?= $firstJob['date'] ?></time> <time class="cv-date"><?= $firstJob['date'] ?></time>
</header> </header>
<p class="cv-org"><?= $firstJob['org'] ?></p> <p class="cv-org"><?= $firstJob['org'] ?></p>
<p class="cv-desc"><?= $firstJob['desc'] ?></p> <p class="cv-desc"><?= $firstJob['desc'] ?></p>
</article> </article>
</section> </section>
<?php endif; ?> <?php endif; ?>
<section class="cv-section skills hidden hidden-toggle"> <section class="cv-section skills hidden hidden-toggle">
<h2>Skills</h2> <h2>Skills</h2>
<div class="skill-categories"> <div class="skill-categories">
<?php <?php
$global_i = 0; $global_i = 0;
foreach ($cv_skills as $category => $skills): ?> foreach ($cv_skills as $category => $skills): ?>
<div class="skill-group"> <div class="skill-group">
<h3><?= $category ?></h3> <h3><?= $category ?></h3>
<div class="skill-tags"> <div class="skill-tags">
<?php foreach ($skills as $skill): <?php foreach ($skills as $skill):
$fontSize = 0.85 + (($global_i * 3) % 5) * 0.2; $fontSize = 0.85 + (($global_i * 3) % 5) * 0.2;
$opacity = 0.6 + (($global_i * 2) % 4) * 0.1; $opacity = 0.6 + (($global_i * 2) % 4) * 0.1;
?> ?>
<span class="tag" style="transition-delay: <?= 0.6 + ($global_i * 0.05) ?>s; font-size: <?= $fontSize ?>rem; opacity: <?= $opacity ?>;"><?= $skill ?></span> <span class="tag" style="transition-delay: <?= 0.6 + ($global_i * 0.05) ?>s; font-size: <?= $fontSize ?>rem; opacity: <?= $opacity ?>;"><?= $skill ?></span>
<?php $global_i++; endforeach; ?> <?php $global_i++;
</div> endforeach; ?>
</div> </div>
<?php endforeach; ?> </div>
</div> <?php endforeach; ?>
</section> </div>
</section>
<section class="cv-section experience hidden hidden-toggle"> <section class="cv-section experience hidden hidden-toggle">
<h2>Past Experience</h2> <h2>Past Experience</h2>
<?php foreach (array_slice($cv_experience, 1) as $job): ?> <?php foreach (array_slice($cv_experience, 1) as $job): ?>
@ -219,26 +219,27 @@ if ($firstJob):
<?php if ($edu['desc']): ?> <?php if ($edu['desc']): ?>
<p class="cv-desc"><?= $edu['desc'] ?></p> <p class="cv-desc"><?= $edu['desc'] ?></p>
<?php endif; ?> <?php endif; ?>
<section class="cv-section skills hidden hidden-toggle"> <section class="cv-section skills hidden hidden-toggle">
<h2>Skills</h2> <h2>Skills</h2>
<div class="skill-categories"> <div class="skill-categories">
<?php <?php
$global_i = 0; $global_i = 0;
foreach ($cv_skills as $category => $skills): ?> foreach ($cv_skills as $category => $skills): ?>
<div class="skill-group"> <div class="skill-group">
<h3><?= $category ?></h3> <h3><?= $category ?></h3>
<div class="skill-tags"> <div class="skill-tags">
<?php foreach ($skills as $skill): <?php foreach ($skills as $skill):
$fontSize = 0.85 + (($global_i * 3) % 5) * 0.2; $fontSize = 0.85 + (($global_i * 3) % 5) * 0.2;
$opacity = 0.6 + (($global_i * 2) % 4) * 0.1; $opacity = 0.6 + (($global_i * 2) % 4) * 0.1;
?> ?>
<span class="tag" style="transition-delay: <?= 0.6 + ($global_i * 0.05) ?>s; font-size: <?= $fontSize ?>rem; opacity: <?= $opacity ?>;"><?= $skill ?></span> <span class="tag" style="transition-delay: <?= 0.6 + ($global_i * 0.05) ?>s; font-size: <?= $fontSize ?>rem; opacity: <?= $opacity ?>;"><?= $skill ?></span>
<?php $global_i++; endforeach; ?> <?php $global_i++;
</div> endforeach; ?>
</div> </div>
<?php endforeach; ?> </div>
</div> <?php endforeach; ?>
</section> </div>
</section>
</article> </article>
<?php endforeach; ?> <?php endforeach; ?>
@ -248,92 +249,97 @@ if ($firstJob):
</main> </main>
<script> <script>
((u, d, t) => { ((u, d, t) => {
const addr = (atob(u) + '@' + d + '.' + t).trim(); const addr = (atob(u) + '@' + d + '.' + t).trim();
const el = document.getElementById("contact-mail"); const el = document.getElementById("contact-mail");
if (el) el.innerHTML = `<a href="mailto:${addr}">${addr}</a>`; if (el) el.innerHTML = `<a href="mailto:${addr}">${addr}</a>`;
})('<?= base64_encode($email_user) ?>', 'dejvino', 'cz'); })('<?= base64_encode($email_user) ?>', 'dejvino', 'cz');
const animationDuration = 500; // Matches CSS animation duration (0.5s) const animationDuration = 500; // Matches CSS animation duration (0.5s)
const staggerDelay = 200; // Delay between each panel unrolling (ms) const staggerDelay = 200; // Delay between each panel unrolling (ms)
const toggleCV = (show, updateHash = true, animate = true) => { const toggleCV = (show, updateHash = true, animate = true) => {
const grid = document.querySelector('.bento-grid'); const grid = document.querySelector('.bento-grid');
const labelText = document.getElementById('toggle-label-text'); const labelText = document.getElementById('toggle-label-text');
if (labelText) labelText.innerText = show ? 'SYSTEM ACCESS: CV_DATA' : 'Looking to hire?';
const cvPanels = Array.from(document.querySelectorAll('.hidden-toggle')).filter(el => el); if (labelText) labelText.innerText = show ? 'SYSTEM ACCESS: CV_DATA' : 'Looking to hire?';
if (show) { const cvPanels = Array.from(document.querySelectorAll('.hidden-toggle')).filter(el => el);
grid.classList.add('cv-mode');
cvPanels.forEach((el, index) => {
const reveal = () => {
el.classList.remove('hidden', 'fade-out', 'reveal');
void el.offsetWidth; // Force reflow
el.classList.add('reveal');
};
if (animate) setTimeout(reveal, index * staggerDelay);
else reveal();
});
} else {
const panels = [...cvPanels].reverse();
panels.forEach((el, index) => {
const hide = () => {
el.classList.remove('reveal');
if (animate) {
el.classList.add('fade-out');
setTimeout(() => {
el.classList.add('hidden');
el.classList.remove('fade-out');
}, animationDuration);
} else {
el.classList.add('hidden');
}
};
if (animate) setTimeout(hide, index * staggerDelay);
else hide();
});
if (animate) {
// Remove cv-mode only after the longest-running shrink animation finishes
const totalRollupTime = (cvPanels.length * staggerDelay) + animationDuration;
setTimeout(() => grid.classList.remove('cv-mode'), totalRollupTime);
} else {
grid.classList.remove('cv-mode');
}
}
if (updateHash) { if (show) {
window.location.hash = show ? 'CV' : ''; grid.classList.add('cv-mode');
}
// Keep toggle in sync
const toggle = document.getElementById('cv-toggle');
if (toggle) toggle.checked = show;
};
// Initialize state from URL cvPanels.forEach((el, index) => {
const checkHash = (animate = false) => toggleCV(window.location.hash === '#CV', false, animate); const reveal = () => {
el.classList.remove('hidden', 'fade-out', 'reveal');
window.addEventListener('load', () => { void el.offsetWidth; // Force reflow
checkHash(false); el.classList.add('reveal');
// Small delay to ensure initial styles are applied before enabling transitions };
setTimeout(() => document.body.classList.remove('preload'), 20); if (animate) setTimeout(reveal, index * staggerDelay);
}); else reveal();
window.addEventListener('hashchange', () => checkHash(true)); });
} else {
const panels = [...cvPanels].reverse();
panels.forEach((el, index) => {
const hide = () => {
el.classList.remove('reveal');
if (animate) {
el.classList.add('fade-out');
setTimeout(() => {
el.classList.add('hidden');
el.classList.remove('fade-out');
}, animationDuration);
} else {
el.classList.add('hidden');
}
};
if (animate) setTimeout(hide, index * staggerDelay);
else hide();
});
const cvToggleInput = document.getElementById('cv-toggle'); if (animate) {
cvToggleInput?.addEventListener('change', (e) => { // Remove cv-mode only after the longest-running shrink animation finishes
toggleCV(e.target.checked); const totalRollupTime = (cvPanels.length * staggerDelay) + animationDuration;
setTimeout(() => grid.classList.remove('cv-mode'), totalRollupTime);
// Only scroll to top if we are collapsing the CV and the user is scrolled down } else {
if (!e.target.checked && window.scrollY > 200) { grid.classList.remove('cv-mode');
window.scrollTo({ top: document.querySelector('.cv-container').offsetTop - 20, behavior: 'smooth' }); }
} }
});
if (updateHash) {
window.location.hash = show ? 'CV' : '';
}
// Keep toggle in sync
const toggle = document.getElementById('cv-toggle');
if (toggle) toggle.checked = show;
};
// Initialize state from URL
const checkHash = (animate = false) => toggleCV(window.location.hash === '#CV', false, animate);
window.addEventListener('load', () => {
checkHash(false);
// Small delay to ensure initial styles are applied before enabling transitions
setTimeout(() => document.body.classList.remove('preload'), 20);
});
window.addEventListener('hashchange', () => checkHash(true));
const cvToggleInput = document.getElementById('cv-toggle');
cvToggleInput?.addEventListener('change', (e) => {
toggleCV(e.target.checked);
// Only scroll to top if we are collapsing the CV and the user is scrolled down
if (!e.target.checked && window.scrollY > 200) {
window.scrollTo({
top: document.querySelector('.cv-container').offsetTop - 20,
behavior: 'smooth'
});
}
});
</script> </script>
<!-- Cloudflare Web Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "90827cb740cd4b1db611be059f64daa9"}'></script><!-- End Cloudflare Web Analytics --> <!-- Cloudflare Web Analytics -->
</body> <script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "90827cb740cd4b1db611be059f64daa9"}'></script><!-- End Cloudflare Web Analytics -->
</html> </body>
</html>