homepage/index.php
2026-05-24 18:38:15 +02:00

418 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Content config:
*/
$cvData = json_decode(file_get_contents(__DIR__ . '/data.json'), true);
$isCvPath = strpos($_SERVER['REQUEST_URI'], '/cv') !== false;
$name = $cvData['name'] ?? '';
$headline = $cvData['headline'] ?? '';
$summary = $cvData['summary'] ?? '';
$location = $cvData['location'] ?? '';
$photo_file = $cvData['photo'] ?? '';
$nav_links = [
'Blog' => 'https://wp.dejvino.com/',
'Projects' => 'https://projects.dejvino.com/',
'Code' => 'https://git.dejvino.cz/',
];
$highlights = $cvData['highlights'] ?? [];
/**
* Sourced Interests Showcase (from filesystem)
*/
$interests_showcase = [];
$interests_base = __DIR__ . '/interests';
if (is_dir($interests_base)) {
$folders = glob($interests_base . '/*', GLOB_ONLYDIR);
if ($folders) {
foreach ($folders as $folder) {
$config_path = $folder . '/interest.json';
$config = [];
if (file_exists($config_path)) {
$config = json_decode(file_get_contents($config_path), true);
}
$img_file = $config['image'] ?? 'image.png';
$has_image = file_exists($folder . '/' . $img_file);
$interests_showcase[] = [
'name' => $config['name'] ?? basename($folder),
'image' => $has_image ? 'interests/' . basename($folder) . '/' . $img_file : null,
'caption' => $config['caption'] ?? ''
];
}
}
}
$site_url = "https://www.dejvino.cz/";
$site_title = $name;
$site_desc = "$name - $headline";
$mailto_link = $cvData['contact_links']['Email'] ?? '';
if (preg_match('/mailto:([^@]+)/', $mailto_link, $m)) {
$email_user = $m[1];
} else {
$email_user = $cvData['email'] ?? '';
}
$extra_contact_links = $cvData['contact_links'] ?? [];
/**
* Emoji icons for link pills
*/
$pill_icons = [
'Website' => '🌐',
'Email' => '✉️',
'Resume' => '📄',
'CV' => '📄',
'PDF' => '📑',
'Blog' => '✍️',
'Projects' => '📁',
'Code' => '💻',
'GitLab' => '🔶',
'GitHub' => '🐙',
];
/**
* Extract a readable URL from a href (strip protocol, trailing slash)
*/
function pillUrl(string $href): string {
$url = $href;
if (preg_match('/^[a-z]+:\/\//i', $url)) {
$url = preg_replace('/^[a-z]+:\/\//i', '', $url);
}
$url = rtrim($url, '/');
return strlen($url) > 30 ? mb_substr($url, 0, 27) . '…' : $url;
}
/**
* Render a standardized pill link
*/
function render_pill(string $href, string $label, string $icon_emoji, string $class_suffix = ''): string {
$url_display = pillUrl($href);
$classes = $class_suffix ? "pill {$class_suffix}" : 'pill';
return sprintf(
'<a href="%s" target="_blank" class="%s">' .
'<span class="pill-icon">%s</span>' .
'<span class="pill-label">%s</span>' .
'<span class="pill-url">%s</span>' .
'</a>',
htmlspecialchars($href, ENT_QUOTES, 'UTF-8'),
$classes,
$icon_emoji,
htmlspecialchars($label, ENT_QUOTES, 'UTF-8'),
htmlspecialchars($url_display, ENT_QUOTES, 'UTF-8')
);
}
/**
* CV Data
*/
// Map experience to expected structure
$cv_experience = array_map(function ($e) {
return [
'title' => $e['title'] ?? '',
'org' => $e['company'] ?? '',
'date' => $e['date'] ?? '',
'desc' => $e['description'] ?? '',
];
}, $cvData['cv']['experience'] ?? []);
// Helper to calculate years between start and end dates
function calculateYears($dateStr) {
// Extract start and end years, support 'present' or 'current'
$dateStr = str_replace(['', '—', '', '-'], '-', $dateStr);
$parts = array_map('trim', explode('-', $dateStr, 2));
if (count($parts) < 2) {
return 0;
}
$start = (int)$parts[0];
$endRaw = strtolower($parts[1]);
$end = ($endRaw === 'present' || $endRaw === 'current') ? (int)date('Y') : (int)$endRaw;
$years = $end - $start;
if ($years <= 1) {
return "1 year";
}
return "$years years";
}
// Map education to expected structure
$cv_education = array_map(function ($e) {
return [
'title' => $e['degree'] ?? '',
'org' => $e['institution'] ?? '',
'date' => $e['date'] ?? '',
'desc' => $e['thesis'] ?? '',
];
}, $cvData['cv']['education'] ?? []);
// Keep skills categories as provided
$cv_skills = $cvData['cv']['skills'] ?? [];
/**
* Page design:
* - clean and simple, human-maintainable
* - no dependencies
* - single-file website
*/
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title><?= $site_title ?></title>
<link rel="canonical" href="<?= $site_url ?>">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="<?= $site_desc ?>">
<meta name="author" content="<?= $name ?>">
<!-- Open Graph / Social Media -->
<meta property="og:type" content="website">
<meta property="og:url" content="<?= $site_url ?>">
<meta property="og:title" content="<?= $name ?>">
<meta property="og:description" content="<?= $headline ?>">
<style>
<?php
include 'style.css';
?>
</style>
<link rel="shortcut icon" href="favicon.ico">
</head>
<body class="<?= $isCvPath ? 'cv-mode' : '' ?>">
<main class="bento-grid">
<section class="card hero">
<div class="hero-row hero-identity">
<?php
if (file_exists($photo_file)) {
$avatarBase64 = base64_encode(file_get_contents($photo_file));
echo '<img src="data:image/png;base64,' . $avatarBase64 . '" alt="' . $name . '" class="avatar" width="80" height="106" />';
} else {
echo '<div class="avatar-placeholder"></div>';
}
?>
<div class="hero-name">
<h1><?= $name ?></h1>
<?php if ($headline): ?>
<p class="headline"><?= $headline ?></p>
<?php endif; ?>
<?php if ($location): ?>
<p class="location"><?= $location ?></p>
<?php endif; ?>
</div>
</div>
<?php if ($summary): ?>
<div class="hero-row hero-description">
<p><?= $summary ?></p>
</div>
<?php endif; ?>
<div class="hero-row hero-contacts">
<?php foreach ($extra_contact_links as $label => $href): ?>
<?= render_pill($href, $label, $pill_icons[$label] ?? '🔗', 'contact-pill') ?>
<?php endforeach; ?>
</div>
<div class="hero-row hero-nav">
<?php foreach ($nav_links as $label => $href): ?>
<?= render_pill(trim($href), $label, $pill_icons[$label] ?? '🔗', 'nav-pill') ?>
<?php endforeach; ?>
</div>
</section>
<?php if (!$isCvPath && $interests_showcase): ?>
<?php // Hardcoded palette of dark, varied tints
$bg_palette = [
'#1e2a33', // Dark Blue
'#1e332a', // Dark Green
'#2a1e33', // Dark Purple
'#331e2a', // Dark Red
'#1a3333', // Dark Teal
'#33331a', // Dark Olive
];
$palette_size = count($bg_palette);
?>
<section class="card interests-showcase">
<nav class="interests-nav">
<h2>Things I like to do</h2>
<ul>
<?php foreach ($interests_showcase as $i => $item): ?>
<li class="<?= $i === 0 ? 'active' : '' ?>" data-index="<?= $i ?>">
<?= htmlspecialchars($item['name']) ?>
</li>
<?php endforeach; ?>
</ul>
</nav>
<div class="interests-display">
<?php foreach ($interests_showcase as $i => $item): ?>
<div class="interest-slide <?= $i === 0 ? 'active' : '' ?>" data-index="<?= $i ?>" style="background-color: <?= $bg_palette[$i % $palette_size] ?>;">
<?php if ($item['image']): ?>
<img src="<?= htmlspecialchars($item['image']) ?>" alt="<?= htmlspecialchars($item['name']) ?>">
<?php endif; ?>
<p class="caption"><?= htmlspecialchars($item['caption']) ?></p>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php if ($isCvPath && $highlights): ?>
<section class="card selected-work">
<h2>Selected Work</h2>
<?php foreach ($highlights as $h): ?>
<article class="highlight-item">
<h3><?= $h['title'] ?? '' ?><?php if ($h['date'] ?? ''): ?> <time class="highlight-date"><?= $h['date'] ?></time><?php endif; ?></h3>
<?php if ($h['description']): ?>
<?php if (is_array($h['description'])): ?>
<?php foreach ($h['description'] as $desc): ?>
<p class="highlight-description"><?= $desc ?></p>
<?php endforeach; ?>
<?php else: ?>
<p class="highlight-description"><?= $h['description'] ?></p>
<?php endif; ?>
<?php endif; ?>
</article>
<?php endforeach; ?>
<?php if ($cvData['cv']['philosophy'] ?? []): ?>
<p class="philosophy"><?= $cvData['cv']['philosophy'] ?></p>
<?php endif; ?>
</section>
<?php endif; ?>
<?php if ($isCvPath && $interests_showcase): ?>
<section class="card personal">
<h2>Interests</h2>
<div class="personal-tags">
<?php foreach ($interests_showcase as $item): ?>
<span class="personal-tag"><?= htmlspecialchars($item['name']) ?></span>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<?php if ($isCvPath): ?>
<section class="card cv-container">
<div class="cv-content">
<?php
$firstJob = $cv_experience[0] ?? null;
if ($firstJob):
?>
<section class="cv-section first-experience">
<h2>Latest Experience</h2>
<article class="cv-item">
<header class="cv-item-header">
<h3 class="cv-title"><?= $firstJob['title'] ?></h3>
<span class="cv-date"><?= $firstJob['date'] ?> (<?= calculateYears($firstJob['date']) ?>)</span>
</header>
<p class="cv-org"><?= $firstJob['org'] ?></p>
<p class="cv-desc"><?= $firstJob['desc'] ?></p>
</article>
</section>
<?php endif; ?>
<section class="cv-section skills">
<h2>Skills</h2>
<div class="skill-categories">
<?php
$global_i = 0;
foreach ($cv_skills as $category => $skills): ?>
<div class="skill-group">
<h3><?= $category ?></h3>
<div class="skill-tags">
<?php foreach ($skills as $skill):
$fontSize = 0.85 + (($global_i * 3) % 5) * 0.2;
$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>
<?php $global_i++;
endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<section class="cv-section experience">
<h2>Past Experience</h2>
<?php foreach (array_slice($cv_experience, 1) as $job): ?>
<article class="cv-item">
<header class="cv-item-header">
<h3 class="cv-title"><?= $job['title'] ?></h3>
<span class="cv-date"><?= $job['date'] ?> (<?= calculateYears($job['date']) ?>)</span>
</header>
<p class="cv-org"><?= $job['org'] ?></p>
<p class="cv-desc"><?= $job['desc'] ?></p>
</article>
<?php endforeach; ?>
</section>
<section class="cv-section education">
<h2>Education</h2>
<?php foreach ($cv_education as $edu): ?>
<article class="cv-item">
<header class="cv-item-header">
<h3 class="cv-title"><?= $edu['title'] ?></h3>
<time class="cv-date"><?= $edu['date'] ?></time>
</header>
<p class="cv-org"><?= $edu['org'] ?></p>
<?php if ($edu['desc']): ?>
<p class="cv-desc"><?= $edu['desc'] ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</section>
</div>
</section>
<?php endif; ?>
</main>
<script>
((u, d, t) => {
const addr = (atob(u) + '@' + d + '.' + t).trim();
const el = document.getElementById("contact-mail");
if (el) el.innerHTML = `<a href="mailto:${addr}">${addr}</a>`;
})('<?= base64_encode($email_user) ?>', 'dejvino', 'cz');
// Interests Showcase Logic
(() => {
const slides = document.querySelectorAll('.interest-slide');
const labels = document.querySelectorAll('.interests-nav li');
if (!slides.length) return;
let currentIndex = 0;
let interval = setInterval(next, 5000);
function show(index) {
slides.forEach(s => s.classList.remove('active'));
labels.forEach(l => l.classList.remove('active'));
slides[index].classList.add('active');
labels[index].classList.add('active');
currentIndex = index;
}
function next() { show((currentIndex + 1) % slides.length); }
labels.forEach((label, i) => {
label.addEventListener('click', () => {
show(i);
clearInterval(interval);
interval = setInterval(next, 8000); // Slower auto-switch after manual interaction
});
});
})();
</script>
<!-- Cloudflare Web Analytics -->
<script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "90827cb740cd4b1db611be059f64daa9"}'></script><!-- End Cloudflare Web Analytics -->
<?php $file_mod_time = filemtime(__FILE__); ?>
<p class="page-footer">Modified: <?= date("Y-m-d", $file_mod_time) ?></p>
</body>
</html>