Compare commits
61 Commits
834e3194ca
...
c435ceacda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c435ceacda | ||
|
|
d590ba1b21 | ||
|
|
5407e5133e | ||
|
|
9d6f0e21a2 | ||
|
|
e365a24ad5 | ||
|
|
53f1cd59c5 | ||
|
|
d79f454b8d | ||
|
|
4e41649002 | ||
|
|
5ea3bf21b2 | ||
|
|
f2ca5e5171 | ||
|
|
0a3d373cc7 | ||
|
|
6478dc37df | ||
|
|
2f8de3cd7d | ||
|
|
595b9ca233 | ||
|
|
64efd8ebce | ||
|
|
3c0cc155dc | ||
|
|
b574e2531c | ||
|
|
9af492998f | ||
|
|
af23da9b5f | ||
|
|
fcefd3c957 | ||
|
|
a89cd9c09f | ||
|
|
0c8bcabaef | ||
|
|
9c75405d11 | ||
|
|
65cdf845bb | ||
|
|
bb2cfd342e | ||
|
|
e45b6625e3 | ||
|
|
c751b91bbb | ||
|
|
9628205375 | ||
|
|
b7707bb8ed | ||
|
|
7c009ee851 | ||
|
|
597b673958 | ||
|
|
9abb6efd73 | ||
|
|
dc42199ba4 | ||
|
|
7f61ca4965 | ||
|
|
b079cc1b57 | ||
|
|
81cd3ba093 | ||
|
|
bd849d6e34 | ||
|
|
6ffb41b23e | ||
|
|
e81fb842d0 | ||
|
|
44d08f8583 | ||
|
|
2181e912d2 | ||
|
|
bd2faca3de | ||
|
|
0d97902b40 | ||
|
|
86b5743f7a | ||
|
|
c204fc0ab8 | ||
|
|
35c5106521 | ||
|
|
c1df821c5f | ||
|
|
ec4780b20b | ||
|
|
319c01c59c | ||
|
|
e7cb2ec37a | ||
|
|
6d776b74c0 | ||
|
|
697afe6667 | ||
|
|
ff65b74864 | ||
|
|
26fa67a233 | ||
|
|
ebc81f5d8e | ||
|
|
adee14fabc | ||
|
|
229b5dee94 | ||
|
|
0365b26e67 | ||
|
|
d3abf042ad | ||
|
|
0ad194041c | ||
|
|
29581ad66e |
@ -30,3 +30,8 @@ RewriteRule ^domains/[^/]+/(.+[^/])$ /$1/ [R]
|
|||||||
# subdomeny - spravne presmerovani pri chybejicim /
|
# subdomeny - spravne presmerovani pri chybejicim /
|
||||||
RewriteCond %{REQUEST_FILENAME} -d
|
RewriteCond %{REQUEST_FILENAME} -d
|
||||||
RewriteRule ^subdom/[^/]+/(.+[^/])$ /$1/ [R]
|
RewriteRule ^subdom/[^/]+/(.+[^/])$ /$1/ [R]
|
||||||
|
|
||||||
|
# Route /cv to index.php for CV view
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-f
|
||||||
|
RewriteCond %{REQUEST_FILENAME} !-d
|
||||||
|
RewriteRule ^cv/?$ index.php [L,QSA]
|
||||||
|
|||||||
185
data.json
Normal file
185
data.json
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
{
|
||||||
|
"name": "David Hrdina Němeček",
|
||||||
|
"photo": "photo.png",
|
||||||
|
"headline": "Principal Software Engineer at Oracle NetSuite",
|
||||||
|
"summary": "UI platform architect specializing in AI-assisted engineering enablement, telemetry and performance.",
|
||||||
|
"email": "explosive@dejvino.cz",
|
||||||
|
"location": "Brno, Czechia",
|
||||||
|
"phone": "+420 775 26 26 32",
|
||||||
|
"contact_links": {
|
||||||
|
"Website": "https://www.dejvino.cz",
|
||||||
|
"Email": "mailto:explosive@dejvino.cz",
|
||||||
|
"Resume": "https://dejvino.cz/cv"
|
||||||
|
},
|
||||||
|
"about": [
|
||||||
|
"Principal Software Engineer at Oracle NetSuite, focusing on AI-assisted developer enablement, UI platform architecture, performance, and observability for 40k+ enterprise customers."
|
||||||
|
],
|
||||||
|
"highlights": [
|
||||||
|
{
|
||||||
|
"title": "AI-first Design System Guidance Tooling",
|
||||||
|
"description": [
|
||||||
|
"Researched and prototyped an AI-first UI Toolkit guidance system leveraging Model Context Protocol (MCP). The tool covered over 260 component guideline documents, code samples and development best practices. It was authored in collaboration with 3 frontrunner UX designers, scaling their expertise across multiple teams.",
|
||||||
|
"Created an automated code review bot leveraging this knowledge corpus, successfully trialed across 4 platform and app delivery teams to validate UI changes early in development."
|
||||||
|
],
|
||||||
|
"date": "2025 – 2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Telemetry Stack Architecture",
|
||||||
|
"description": [
|
||||||
|
"Shaped product telemetry architecture spanning frontend UI, backend integration, and AI interaction streams. Championed migration from legacy telemetry solutions to OpenTelemetry. Integrated Oracle APM for enterprise-grade performance monitoring.",
|
||||||
|
"Designed action-root hybrid sampling strategy to achieve a 92% telemetry volume reduction while preserving critical user-behavior signals.",
|
||||||
|
"Delivered in-app trace visualizer, driving up telemetry adoption among teams."
|
||||||
|
],
|
||||||
|
"date": "2024 – 2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "UI Performance & Bootstrap Optimization",
|
||||||
|
"description": [
|
||||||
|
"Analyzed UI app bootstrap performance, driving improvements that gained 28% speed improvement.",
|
||||||
|
"Eliminated redundant network requests by introducing TanStack Query network caching. Defined tiered JavaScript dependency loading mechanism for on-demand toolkit loading."
|
||||||
|
],
|
||||||
|
"date": "2025 – 2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Consumer-driven Contract Testing",
|
||||||
|
"description": [
|
||||||
|
"Prototyped UI <=> backend API contract testing using Pact to prevent cross-system breaking changes early in the development lifecycle.",
|
||||||
|
"Defined consumer test suites, contract generation, and provider verification workflows between frontend UI applications and backend services."
|
||||||
|
],
|
||||||
|
"date": "2025 – 2026"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "UI Testing Strategy & Architecture",
|
||||||
|
"description": [
|
||||||
|
"Led senior QA engineers in defining and executing the long-term UI testing architecture strategy across platform teams.",
|
||||||
|
"Established automated test suites and quality guardrails to elevate testing standards."
|
||||||
|
],
|
||||||
|
"date": "2021 – 2023"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Pluggable Tax Calculation Engine",
|
||||||
|
"description": [
|
||||||
|
"Architected and led the development of a pluggable tax calculation engine for NetSuite cloud ERP.",
|
||||||
|
"Coordinated technical strategy across 20+ software engineers in 4 squads, standardizing global tax calculation pipelines."
|
||||||
|
],
|
||||||
|
"date": "2017 – 2020"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cv": {
|
||||||
|
"experience": [
|
||||||
|
{
|
||||||
|
"title": "Principal Software Engineer (UI Platform Architecture)",
|
||||||
|
"company": "Oracle|NetSuite",
|
||||||
|
"date": "2023 – present",
|
||||||
|
"description": "Driving technical vision and solution architecture for the NetSuite UI Platform (NetSuite Next). Leading initiatives in AI-assisted developer enablement, telemetry architecture, performance optimization."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Senior Software Development Manager (UI Platform)",
|
||||||
|
"company": "Oracle|NetSuite",
|
||||||
|
"date": "2022 – 2023",
|
||||||
|
"description": "Engineering lead for multiple UI Platform teams building core framework components and developer tooling."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Software Development Manager (UI Platform)",
|
||||||
|
"company": "Oracle|NetSuite",
|
||||||
|
"date": "2020 – 2022",
|
||||||
|
"description": "Engineering lead for a UI Platform team building shared UI components. Acted as technical product owner while bootstrapping the team and establishing component delivery standards."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Software Development Manager (ERP, Tax)",
|
||||||
|
"company": "Oracle|NetSuite",
|
||||||
|
"date": "2017 – 2020",
|
||||||
|
"description": "Engineering lead for ERP Tax domain. Designed and delivered a pluggable tax calculation engine, coordinating technical direction for 20+ engineers across 4 squads."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Senior Software Engineer (ERP, Tax)",
|
||||||
|
"company": "Oracle|NetSuite",
|
||||||
|
"date": "2013 – 2017",
|
||||||
|
"description": "Development of cloud ERP tax calculation and reporting modules. Led large-scale refactoring of legacy codebases and improved calculation reliability for enterprise clients."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Software Engineer",
|
||||||
|
"company": "Q2 Interactive",
|
||||||
|
"date": "2009 – 2013",
|
||||||
|
"description": "Development of e-commerce, ERP, and CRM web applications and custom services."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"skills": {
|
||||||
|
"Architecture & Domains": [
|
||||||
|
"UI Platform Architecture",
|
||||||
|
"AI-Assisted Engineering",
|
||||||
|
"Developer Enablement & Tooling",
|
||||||
|
"Telemetry & Observability",
|
||||||
|
"Performance Optimization",
|
||||||
|
"Consumer-Driven Contract Testing"
|
||||||
|
],
|
||||||
|
"Languages & Core Ecosystems": [
|
||||||
|
"TypeScript",
|
||||||
|
"JavaScript",
|
||||||
|
"Node.js",
|
||||||
|
"Java",
|
||||||
|
"Python",
|
||||||
|
"OpenTelemetry",
|
||||||
|
"Model Context Protocol (MCP)"
|
||||||
|
],
|
||||||
|
"Spoken Languages": [
|
||||||
|
"Czech (native)",
|
||||||
|
"English (proficient)",
|
||||||
|
"German (elementary)"
|
||||||
|
],
|
||||||
|
"Certifications": [
|
||||||
|
"OCI 2025 Certified Observability Professional",
|
||||||
|
"OCI 2025 Certified AI Foundations Associate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"education": [
|
||||||
|
{
|
||||||
|
"degree": "Master's degree",
|
||||||
|
"field": "Parallel and Distributed Systems",
|
||||||
|
"institution": "Faculty of Informatics, Masaryk University Brno",
|
||||||
|
"date": "2011 – 2013",
|
||||||
|
"thesis": "Efficient computation and visualization of correlations in medical signals. High-performance computation of correlations on large volumes of analog data in C++ using supercomputers. Visualization of the computed results through interactive exploration and processing of the data by a researcher in Python."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"degree": "Bachelor's degree",
|
||||||
|
"field": "Parallel and Distributed Systems",
|
||||||
|
"institution": "Faculty of Informatics, Masaryk University Brno",
|
||||||
|
"date": "2008 – 2011",
|
||||||
|
"thesis": "Parallel implementation of force-field decompression algorithm. Executed on a supercomputer cluster to perform real-time simulation of molecular forces, providing haptic feedback to a 3D pointing device."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"philosophy": "I design platform layers that stay out of the way while enabling everything else.</br>Quiet infrastructure. Measurable value."
|
||||||
|
},
|
||||||
|
"interests": [
|
||||||
|
{
|
||||||
|
"name": "Mandolin",
|
||||||
|
"caption": "Octave mandolin is my instrument of choice. Wide range of tones, pleasant sound. Great way to relax your mind, explore melodies and harmonies.",
|
||||||
|
"image": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Electronics tinkering",
|
||||||
|
"caption": "Building a useful device from scratch. Retrofitting modern brains into a broken vintage shell. Electronics is the magic that makes things alive.",
|
||||||
|
"image": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Synthwave",
|
||||||
|
"caption": "Neon-filled nostalgia. Fun to create, relaxing to listen to it.",
|
||||||
|
"image": "interests/synthwave.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3D Printing & Design",
|
||||||
|
"caption": "Designing and printing custom parts that serve a purpose. Unique designs, useful accessories, toys or just repairs.",
|
||||||
|
"image": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mountain Biking",
|
||||||
|
"caption": "Going places under your own power. Exploring forests, trails and nature where cars aren't allowed.",
|
||||||
|
"image": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RC Cars",
|
||||||
|
"caption": "Rock crawlers, construction trucks, military vehicles. Building a custom offroad course in the garden to trial them all.",
|
||||||
|
"image": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
387
index.php
387
index.php
@ -1,38 +1,158 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content config:
|
* Content config:
|
||||||
*/
|
*/
|
||||||
$name = "David Hrdina Němeček";
|
$cvData = json_decode(file_get_contents(__DIR__ . '/data.json'), true);
|
||||||
$site_title = "www.dejvino.cz";
|
|
||||||
$site_url = "https://www.dejvino.cz/";
|
|
||||||
$site_desc = "$name - Software Developer";
|
|
||||||
$subtitle = "Software Developer, Architect, Problem Solver";
|
|
||||||
$photo_file = "photo.png";
|
|
||||||
$email_user = "ciao";
|
|
||||||
|
|
||||||
$about_text = [
|
$isCvPath = strpos($_SERVER['REQUEST_URI'], '/cv') !== false;
|
||||||
"Full-stack developer focused on pragmatic architecture, observability and performance.",
|
|
||||||
//""
|
$name = $cvData['name'] ?? '';
|
||||||
];
|
$headline = $cvData['headline'] ?? '';
|
||||||
|
$summary = $cvData['summary'] ?? '';
|
||||||
|
$location = $cvData['location'] ?? '';
|
||||||
|
$photo_file = $cvData['photo'] ?? '';
|
||||||
|
|
||||||
$nav_links = [
|
$nav_links = [
|
||||||
"~/blog" => "https://wp.dejvino.com/",
|
'Blog' => 'https://wp.dejvino.com/',
|
||||||
"~/projects" => "https://projects.dejvino.com/",
|
'Projects' => 'https://projects.dejvino.com/',
|
||||||
"~/code" => "https://git.dejvino.cz/",
|
'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";
|
||||||
|
|
||||||
|
$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' => '🐙',
|
||||||
];
|
];
|
||||||
|
|
||||||
$hardware_info = "Playing the mandolin, mountain biking, 3D printing and tinkering.";
|
/**
|
||||||
|
* 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:
|
* Page design:
|
||||||
* - clean and simple, human-maintaninable
|
* - clean and simple, human-maintainable
|
||||||
* - no dependencies
|
* - no dependencies
|
||||||
* - single-file website
|
* - single-file website
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
<!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 ?>">
|
||||||
@ -44,7 +164,7 @@ $hardware_info = "Playing the mandolin, mountain biking, 3D printing and tinker
|
|||||||
<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 ?>">
|
||||||
<meta property="og:title" content="<?= $name ?>">
|
<meta property="og:title" content="<?= $name ?>">
|
||||||
<meta property="og:description" content="<?= $subtitle ?>">
|
<meta property="og:description" content="<?= $headline ?>">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
<?php
|
<?php
|
||||||
@ -52,60 +172,231 @@ $hardware_info = "Playing the mandolin, mountain biking, 3D printing and tinker
|
|||||||
?>
|
?>
|
||||||
</style>
|
</style>
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
|
<body class="<?= $isCvPath ? 'cv-mode' : '' ?>">
|
||||||
|
|
||||||
<main class="bento-grid">
|
<main class="bento-grid">
|
||||||
<section class="card profile">
|
|
||||||
|
<section class="card hero">
|
||||||
|
<div class="hero-row hero-identity">
|
||||||
<?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="80" height="106" />';
|
||||||
} else {
|
} else {
|
||||||
echo '<div class="avatar-placeholder"></div>';
|
echo '<div class="avatar-placeholder"></div>';
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<div class="header-text">
|
<div class="hero-name">
|
||||||
<h1><?= $name ?></h1>
|
<h1><?= $name ?></h1>
|
||||||
<p class="subtitle"><?= $subtitle ?></p>
|
<?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-only-print' : '')) ?>
|
||||||
|
<?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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card about">
|
<?php if (!$isCvPath && $interests_showcase): ?>
|
||||||
<h2>Bio</h2>
|
<?php // Hardcoded palette of dark, varied tints
|
||||||
<?php foreach ($about_text as $para): ?>
|
$bg_palette = [
|
||||||
<p><?= $para ?></p>
|
'#1e2a33', // Dark Blue
|
||||||
<?php endforeach; ?>
|
'#1e332a', // Dark Green
|
||||||
</section>
|
'#2a1e33', // Dark Purple
|
||||||
|
'#331e2a', // Dark Red
|
||||||
<section class="card links">
|
'#1a3333', // Dark Teal
|
||||||
<h2>Navigation</h2>
|
'#33331a', // Dark Olive
|
||||||
<ul class="link-grid">
|
];
|
||||||
<?php foreach ($nav_links as $label => $url): ?>
|
$palette_size = count($bg_palette);
|
||||||
<li><a href="<?= $url ?>"><span><?= $label ?></span></a></li>
|
?>
|
||||||
|
<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; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</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): ?>
|
||||||
|
<?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>
|
</section>
|
||||||
|
|
||||||
<section class="card interests">
|
<?php
|
||||||
<h2>Likes</h2>
|
$firstJob = $cv_experience[0] ?? null;
|
||||||
<p><?= $hardware_info ?></p>
|
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>
|
</section>
|
||||||
|
|
||||||
<section class="card contact">
|
<?php if ($interests_showcase): ?>
|
||||||
<h2>Remote_Access</h2>
|
<section class="card personal">
|
||||||
<p class="mono-addr">
|
<h2>Interests</h2>
|
||||||
Contact email: <span id="contact-mail">uncovering...</span>
|
<div class="personal-tags">
|
||||||
</p>
|
<?php foreach ($interests_showcase as $item): ?>
|
||||||
|
<span class="personal-tag"><?= htmlspecialchars($item['name']) ?></span>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
((u, d, t) => {
|
((u, d, t) => {
|
||||||
const addr = u + '@' + d + '.' + t;
|
const addr = (atob(u) + '@' + d + '.' + t).trim();
|
||||||
const el = document.getElementById("contact-mail");
|
const el = document.getElementById("contact-mail");
|
||||||
el.innerHTML = `<a href="mailto:${addr}">${addr}</a>`;
|
if (el) el.innerHTML = `<a href="mailto:${addr}">${addr}</a>`;
|
||||||
})('<?= $email_user ?>', 'dejvino', 'cz');
|
})('<?= 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>
|
</script>
|
||||||
</body>
|
<!-- 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>
|
</html>
|
||||||
|
|||||||
BIN
interests/synthwave.png
Normal file
BIN
interests/synthwave.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 751 KiB |
Loading…
Reference in New Issue
Block a user