homepage/index.php
2026-07-27 19:58:29 +02:00

405 lines
14 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 data.json)
*/
$interests_showcase = [];
if (!empty($cvData['interests'])) {
foreach ($cvData['interests'] as $item) {
$img = $item['image'] ?? null;
$has_image = $img && file_exists(__DIR__ . '/' . $img);
$interests_showcase[] = [
'name' => $item['name'] ?? '',
'image' => $has_image ? $img : null,
'caption' => $item['caption'] ?? ''
];
}
}
$site_url = "https://www.dejvino.cz/";
$site_title = $name;
$site_desc = "$name - $headline";
$extra_contact_links = $cvData['contact_links'] ?? [];
/**
* Emoji icons for link pills
*/
$pill_icons = [
'Website' => '🌐',
'Email' => '✉️',
'Resume' => '📄',
'CV' => '📄',
'PDF' => '📑',
'Blog' => '✍️',
'Projects' => '📁',
'Code' => '💻',
'GitLab' => '🔶',
'GitHub' => '🐙',
];
/** MACHINE READABLE ENDPOINTS */
$parsedUrl = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if (in_array($parsedUrl, ['/api/data.json', '/api/data.json/'], true)) {
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
readfile(__DIR__ . '/data.json');
exit;
}
if (in_array($parsedUrl, ['/llms.txt', '/llms.txt/'], true)) {
header('Content-Type: text/plain; charset=utf-8');
echo '# ' . $site_url . PHP_EOL;
echo '> ' . $name . ' — ' . $headline . PHP_EOL;
echo PHP_EOL;
echo $summary . PHP_EOL;
echo PHP_EOL;
echo '## Machine-readable data' . PHP_EOL;
echo '- JSON: ' . $site_url . 'api/data.json' . PHP_EOL;
echo PHP_EOL;
echo '## Links' . PHP_EOL;
foreach ($nav_links as $label => $href) {
echo '- ' . $label . ': ' . $href . PHP_EOL;
}
if (!empty($cvData['contact_links'])) {
echo PHP_EOL;
echo '## Contact' . PHP_EOL;
foreach ($cvData['contact_links'] as $label => $href) {
echo '- ' . $label . ': ' . $href . PHP_EOL;
}
}
exit;
}
/** END OF MACHINE READABLE ENDPOINTS */
/**
* 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'] ?? [];
// JSON-LD structured data for machine readability (AI crawlers, schema.org)
$jsonLd = [
'@context' => 'https://schema.org',
'@type' => 'Person',
'name' => $name,
'jobTitle' => $headline,
'description' => $summary,
'url' => $site_url,
'email' => $cvData['email'] ?? '',
'telephone' => $cvData['phone'] ?? '',
'image' => $site_url . $photo_file,
'address' => [
'@type' => 'PostalAddress',
'addressLocality' => $location,
],
'sameAs' => array_values($nav_links),
'knowsAbout' => [],
'alumniOf' => [],
'worksFor' => [],
];
foreach ($cvData['cv']['skills'] ?? [] as $category => $skills) {
$jsonLd['knowsAbout'] = array_merge($jsonLd['knowsAbout'], $skills);
}
foreach ($cvData['interests'] ?? [] as $interest) {
$jsonLd['knowsAbout'][] = $interest['name'] ?? '';
}
$firstExp = $cvData['cv']['experience'][0] ?? null;
if ($firstExp) {
$jsonLd['worksFor'] = [
'@type' => 'Organization',
'name' => str_replace('|', ' / ', $firstExp['company'] ?? ''),
'description' => $firstExp['title'] ?? '',
];
} else {
unset($jsonLd['worksFor']);
}
foreach ($cvData['cv']['education'] ?? [] as $edu) {
$jsonLd['alumniOf'][] = [
'@type' => 'CollegeOrUniversity',
'name' => $edu['institution'] ?? '',
'description' => ($edu['degree'] ?? '') . ($edu['field'] ? ' in ' . $edu['field'] : ''),
];
}
$jsonLdEncoded = json_encode($jsonLd, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
/**
* 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 ?>">
<script type="application/ld+json"><?= $jsonLdEncoded ?></script>
<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>
<button class="print-button" onclick="window.print()">
🖨️ Print CV
</button>
<?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' . ($label === 'CV' || $label === 'Resume' ? ' pill-cv' : '')) ?>
<?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): ?>
<?php if ($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']): ?>
<div class="highlight-descriptions">
<?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; ?>
</div>
<?php endif; ?>
</article>
<?php endforeach; ?>
<?php if ($cvData['cv']['philosophy'] ?? ''): ?>
<blockquote class="philosophy"><?= $cvData['cv']['philosophy'] ?></blockquote>
<?php endif; ?>
</section>
<?php endif; ?>
<section class="card skills">
<h2>Skills</h2>
<div class="skill-categories">
<?php foreach ($cv_skills as $category => $skills): ?>
<div class="skill-group">
<h3><?= $category ?></h3>
<div class="skill-tags">
<?php foreach ($skills as $skill): ?>
<span class="tag"><?= $skill ?></span>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<?php
$firstJob = $cv_experience[0] ?? null;
if ($firstJob):
?>
<section class="card 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="card 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="card 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>
<?php if ($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 endif; ?>
</main>
<!-- 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>