CV foldout

This commit is contained in:
Dejvino 2026-05-18 22:37:19 +02:00
parent d3abf042ad
commit 0365b26e67
2 changed files with 170 additions and 96 deletions

104
index.php
View File

@ -19,7 +19,6 @@ $nav_links = [
"~/blog" => "https://wp.dejvino.com/",
"~/projects" => "https://projects.dejvino.com/",
"~/code" => "https://git.dejvino.cz/",
"~/cv" => "#",
];
$hardware_info = "Playing the mandolin, mountain biking, 3D printing and tinkering.";
@ -114,18 +113,37 @@ $cv_skills = ["PHP", "Go", "Rust", "Docker", "Kubernetes", "PostgreSQL", "Redis"
<p><?= $hardware_info ?></p>
</section>
<section class="card links">
<h2>Navigation</h2>
<ul class="link-grid">
<?php foreach ($nav_links as $label => $url): ?>
<li><a href="<?= $url ?>" target="_blank"><span><?= $label ?></span></a></li>
<?php endforeach; ?>
</ul>
</section>
<section class="card contact">
<h2>Remote_Access</h2>
<p class="mono-addr">
Contact email: <span id="contact-mail">uncovering...</span>
</p>
<ul class="link-grid cv-only hidden">
<ul class="link-grid">
<?php foreach ($extra_contact_links as $label => $url): ?>
<li><a href="<?= $url ?>" target="_blank"><span><?= $label ?></span></a></li>
<?php endforeach; ?>
</ul>
</section>
<label class="card hiring-toggle" for="cv-toggle">
<div class="cv-toggle-container">
<span id="toggle-label-text">Looking to hire?</span>
<span class="switch">
<input type="checkbox" id="cv-toggle">
<span class="slider"></span>
</span>
</div>
</label>
<section class="card experience hidden">
<h2>Experience</h2>
<?php foreach ($cv_experience as $job): ?>
@ -143,8 +161,8 @@ $cv_skills = ["PHP", "Go", "Rust", "Docker", "Kubernetes", "PostgreSQL", "Redis"
<section class="card skills hidden">
<h2>Skills</h2>
<div class="skill-tags">
<?php foreach ($cv_skills as $skill): ?>
<span class="tag"><?= $skill ?></span>
<?php foreach ($cv_skills as $i => $skill): ?>
<span class="tag" style="transition-delay: <?= 0.6 + ($i * 0.05) ?>s"><?= $skill ?></span>
<?php endforeach; ?>
</div>
</section>
@ -164,20 +182,6 @@ $cv_skills = ["PHP", "Go", "Rust", "Docker", "Kubernetes", "PostgreSQL", "Redis"
</div>
<?php endforeach; ?>
</section>
<section class="card links">
<h2>Navigation</h2>
<ul class="link-grid nav-default">
<?php foreach ($nav_links as $label => $url):
$id_attr = ($url === "#") ? 'id="cv-toggle"' : '';
?>
<li><a href="<?= $url ?>" <?= $id_attr ?>><span><?= $label ?></span></a></li>
<?php endforeach; ?>
</ul>
<ul class="link-grid cv-only hidden">
<li><a href="#" id="cv-back"><span>GO BACK ~/</span></a></li>
</ul>
</section>
</main>
<script>
@ -197,7 +201,7 @@ $cv_skills = ["PHP", "Go", "Rust", "Docker", "Kubernetes", "PostgreSQL", "Redis"
card.dataset.isShifting = "true";
card.classList.add('shifting');
setTimeout(() => {
card.classList.remove('shifting', 'reveal', 'fade-out');
card.classList.remove('shifting');
delete card.dataset.isShifting;
if (!flashAtEnd) return;
@ -210,63 +214,52 @@ $cv_skills = ["PHP", "Go", "Rust", "Docker", "Kubernetes", "PostgreSQL", "Redis"
const toggleCV = (show, updateHash = true) => {
const grid = document.querySelector('.bento-grid');
const labelText = document.getElementById('toggle-label-text');
if (labelText) labelText.innerText = show ? 'SYSTEM ACCESS: CV_DATA' : 'Looking to hire?';
const cvPanels = [
document.querySelector('.experience'),
document.querySelector('.skills'),
document.querySelector('.education'),
...document.querySelectorAll('.cv-only')
document.querySelector('.education')
].filter(el => el);
const navDefault = document.querySelectorAll('.nav-default');
if (show) {
grid.classList.add('cv-mode');
// Hide default navigation
navDefault.forEach(el => {
el.classList.remove('reveal');
el.classList.add('fade-out');
applyShifting(el, false); // No flash when disappearing
setTimeout(() => el.classList.add('hidden'), animationDuration);
});
// Unroll CV panels sequentially (Top -> Bottom)
cvPanels.forEach((el, index) => {
setTimeout(() => {
el.classList.remove('hidden', 'fade-out');
el.classList.remove('hidden', 'fade-out', 'reveal');
void el.offsetWidth; // Force reflow
el.classList.add('reveal');
applyShifting(el, true); // Flash when appearing
applyShifting(el, true, animationDuration);
}, index * staggerDelay);
});
} else {
// Roll up CV panels sequentially (Bottom -> Top)
[...cvPanels].reverse().forEach((el, index) => {
setTimeout(() => {
el.classList.remove('reveal');
el.classList.add('fade-out');
applyShifting(el, false); // No flash when disappearing
applyShifting(el, false);
setTimeout(() => {
el.classList.add('hidden');
el.classList.remove('fade-out');
}, animationDuration);
}, index * staggerDelay);
}, (cvPanels.length - 1 - index) * staggerDelay);
});
// Restore default state after panels have started rolling up
setTimeout(() => {
grid.classList.remove('cv-mode');
navDefault.forEach(el => {
el.classList.remove('hidden', 'fade-out');
el.classList.add('reveal');
applyShifting(el, true); // Flash when navigation returns
});
}, cvPanels.length * staggerDelay);
// Remove cv-mode only after the longest-running shrink animation finishes
const totalRollupTime = (cvPanels.length * staggerDelay) + animationDuration;
setTimeout(() => grid.classList.remove('cv-mode'), totalRollupTime);
}
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
@ -275,15 +268,14 @@ $cv_skills = ["PHP", "Go", "Rust", "Docker", "Kubernetes", "PostgreSQL", "Redis"
window.addEventListener('load', checkHash);
window.addEventListener('hashchange', checkHash);
document.getElementById('cv-toggle')?.addEventListener('click', (e) => {
e.preventDefault();
toggleCV(true);
});
document.getElementById('cv-back')?.addEventListener('click', (e) => {
e.preventDefault();
toggleCV(false);
window.scrollTo({ top: 0, behavior: 'smooth' });
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('.hiring-toggle').offsetTop - 20, behavior: 'smooth' });
}
});
</script>
</body>

162
style.css
View File

@ -26,7 +26,7 @@
transform: scale(0.95);
}
to {
opacity: 0.4; /* Faded while unrolling */
opacity: 1;
max-height: 2000px; /* Sufficiently large to contain content */
transform: scale(1);
}
@ -34,7 +34,7 @@
@keyframes shrink-fade-away { /* New animation for shrinking */
from {
opacity: 0.4;
opacity: 1;
max-height: 2000px;
transform: scale(1);
}
@ -47,12 +47,11 @@
@keyframes flash-impact {
0% {
opacity: 1;
filter: brightness(3) saturate(1.5);
box-shadow: 0 0 40px var(--accent-color);
border-color: var(--accent-color);
}
100% { opacity: 1; filter: brightness(1) saturate(1); }
100% { filter: brightness(1) saturate(1); }
}
* { box-sizing: border-box; }
@ -74,9 +73,9 @@ body {
.bento-grid {
display: grid;
max-width: 900px;
max-width: 800px;
gap: 24px;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr);
}
.card {
@ -111,42 +110,39 @@ body {
linear-gradient(135deg, rgba(0, 243, 255, 0.8), rgba(255, 0, 255, 0.8)) border-box;
}
/* Desktop Grid Spans */
.profile { grid-column: span 3; }
.about { grid-column: span 2; }
.interests { grid-column: span 1; }
.contact { grid-column: span 2; }
.links { grid-column: span 1; }
.hiring-toggle { grid-column: span 3; }
.experience { grid-column: span 2; }
.skills { grid-column: span 1; }
.education { grid-column: span 3; }
/* Shuffle Logic */
.profile { transform: rotate(0.1deg); }
.about { transform: translate(-4px, 2px) rotate(-0.5deg); }
.interests { transform: translate(6px, -2px) rotate(0.4deg); }
.links { transform: translate(-2px, -4px) rotate(-0.3deg); grid-column: span 4; }
.contact { transform: translate(4px, 6px) rotate(0.5deg); grid-column: span 4; }
/* CV sections also shuffled */
.links { transform: translate(-2px, -4px) rotate(-0.3deg); }
.contact { transform: translate(4px, 6px) rotate(0.5deg); }
.hiring-toggle { transform: translate(-2px, 3px) rotate(0.2deg); }
.experience { transform: translate(3px, -2px) rotate(-0.2deg); }
.skills { transform: translate(-3px, 4px) rotate(0.6deg); }
.education { transform: translate(2px, 3px) rotate(-0.4deg); }
/* Base states to prevent "re-added to DOM" flashes */
.experience, .skills, .education, .cv-only {
/* Base states for CV elements */
.experience, .skills, .education {
opacity: 0;
max-height: 0;
overflow: hidden;
}
/* Solid states when CV is fully locked in */
.cv-mode .experience, .cv-mode .skills, .cv-mode .education, .cv-mode .cv-only {
opacity: 1;
/* Visible states when CV mode is active */
.cv-mode .experience, .cv-mode .skills, .cv-mode .education {
max-height: 2000px;
}
.cv-mode .nav-default {
opacity: 0;
max-height: 0;
pointer-events: none;
overflow: hidden;
}
/* CV mode layout overrides - ensure full width for key info */
.cv-mode .education {
grid-column: span 4;
}
}
@media (max-width: 768px) {
@ -158,10 +154,20 @@ body {
}
}
.profile { grid-column: span 4; display: flex; align-items: center; gap: 32px; }
.profile { display: flex; align-items: center; gap: 32px; }
.experience, .education { grid-column: span 3; }
.skills { grid-column: span 1; }
.hiring-toggle {
padding: 12px 24px;
cursor: pointer;
transition: transform 0.2s ease, filter 0.3s ease;
}
.hiring-toggle:hover {
filter: brightness(1.2);
transform: translate(-2px, 3px) rotate(0.2deg) scale(1.01);
background: linear-gradient(var(--card-bg), var(--card-bg)) padding-box,
linear-gradient(135deg, rgba(255, 0, 255, 0.6), rgba(0, 243, 255, 0.6)) border-box;
}
.hidden {
display: none !important;
@ -171,25 +177,28 @@ body {
display: block !important;
animation: grow-reveal 1s cubic-bezier(0.23, 1, 0.32, 1) forwards;
transition: none !important;
will-change: opacity, max-height, transform;
transform: translateZ(0);
}
.fade-out {
/* This class will be applied when content is being hidden */
animation: shrink-fade-away 1s cubic-bezier(0.23, 1, 0.32, 1) forwards;
transition: none !important;
will-change: opacity, max-height, transform;
transform: translateZ(0);
}
.shifting {
filter: blur(2px) grayscale(0.5);
filter: blur(1px) grayscale(0.8) brightness(0.7);
z-index: 10;
pointer-events: none;
transition: none !important;
pointer-events: none !important;
transition: filter 0.3s ease;
}
.flash-glow {
display: block !important;
animation: flash-impact 0.6s ease-out forwards;
transition: none !important;
pointer-events: none;
}
/* CV Specific Content Styles */
@ -242,6 +251,14 @@ body {
padding: 4px 8px;
background: rgba(0, 243, 255, 0.1);
border: 1px solid rgba(0, 243, 255, 0.3);
opacity: 0;
transform: translateX(-5px);
transition: opacity 0.4s ease, transform 0.4s ease;
}
.reveal .tag {
opacity: 1;
transform: translateX(0);
}
.profile::before {
@ -256,9 +273,6 @@ body {
pointer-events: none;
}
.about { grid-column: span 2; }
.interests { grid-column: span 2; }
.link-grid { list-style: none; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.link-grid a {
@ -267,9 +281,25 @@ body {
padding: 8px;
border-left: 3px solid var(--text-color);
color: var(--link-color);
background: rgba(255, 255, 255, 0.02);
background: rgba(0, 243, 255, 0.15); /* Increased alpha for better visibility */
text-align: center;
transition: all 0.2s ease;
border: 1px solid rgba(0, 243, 255, 0.3);
border-left: 3px solid var(--text-color);
text-shadow: 0 0 5px rgba(255, 215, 0, 0.3);
}
.link-grid a:hover {
background: rgba(0, 243, 255, 0.25);
border-left-color: var(--accent-color);
box-shadow: 0 0 15px var(--accent-color);
transform: translateY(-2px);
}
/* Specific styling for the small contact buttons */
.contact .link-grid {
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
margin-top: 15px;
}
.links::after {
@ -282,6 +312,59 @@ body {
border-radius: 50%;
animation: pulse-trace 1s infinite;
}
/* Cyberpunk Toggle Switch */
.cv-toggle-container {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 0.85rem;
color: var(--accent-color);
text-shadow: 0 0 10px var(--accent-color);
text-transform: uppercase;
letter-spacing: 4px;
font-weight: bold;
}
.cv-mode .cv-toggle-container span {
color: #fff;
}
.switch {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
}
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: rgba(255, 0, 255, 0.2);
transition: .4s;
border: 1px solid var(--accent-color);
}
.slider:before {
position: absolute;
content: "";
height: 12px; width: 12px;
left: 4px; bottom: 3px;
background-color: var(--accent-color);
transition: .4s;
box-shadow: 0 0 10px var(--accent-color);
}
input:checked + .slider { background-color: rgba(0, 243, 255, 0.2); border-color: var(--text-color); }
input:checked + .slider:before {
transform: translateX(20px);
background-color: var(--text-color);
box-shadow: 0 0 10px var(--text-color);
}
.avatar {
width: 120px;
height: 160px; /* Explicit height for aspect ratio 3:4 */
@ -294,7 +377,6 @@ h1 {
margin: 0;
font-size: clamp(1.5rem, 5vw, 2.2rem);
color: #fff;
text-shadow: 2px 2px 0 var(--accent-color), -2px -2px 0 var(--text-color);
}
h2 {