personal-site/resume.html
2026-09-17 14:40:09 +03:00

604 lines
23 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Artem Titov — DevOps, Rust and Python. Explore my projects on my self-hosted Forgejo server as artobyte." />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%227%22%20fill%3D%22%2317171a%22%2F%3E%3Ctext%20x%3D%2216%22%20y%3D%2223%22%20text-anchor%3D%22middle%22%20font-family%3D%22monospace%22%20font-size%3D%2223%22%20fill%3D%22%23fbfbf9%22%3Ea%3C%2Ftext%3E%3C%2Fsvg%3E" />
<title>Artem Titov — DevOps · Rust · Python</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,500;12..96,600;12..96,700&family=Archivo:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #fbfbf9;
--bg-soft: #f3f3f0;
--ink: #17171a;
--muted: #6c6c71;
--faint: #9a9a9e;
--line: rgba(20, 20, 25, 0.10);
--line-soft: rgba(20, 20, 25, 0.06);
--accent: #2f6df6;
--accent-soft: rgba(47, 109, 246, 0.10);
--sans: "Archivo", system-ui, sans-serif;
--display: "Bricolage Grotesque", Georgia, serif;
--mono: "JetBrains Mono", ui-monospace, monospace;
--maxw: 72rem;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
font-size: 17px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a { color: inherit; text-decoration: none; }
.wrap {
width: 100%;
max-width: var(--maxw);
margin: 0 auto;
padding-inline: clamp(1rem, 4vw, 3rem);
}
/* ---------- top bar ---------- */
header.bar {
position: sticky;
top: 0;
z-index: 50;
backdrop-filter: blur(10px);
background: color-mix(in srgb, var(--bg) 82%, transparent);
border-bottom: 1px solid var(--line-soft);
}
.bar-inner {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 4.5rem;
padding-block: .75rem;
gap: .5rem 2rem;
flex-wrap: wrap;
}
.brand {
font-family: var(--mono);
font-size: 13px;
letter-spacing: 0.02em;
display: flex;
align-items: center;
gap: 9px;
}
.dot {
width: 8px; height: 8px; border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 4px var(--accent-soft);
}
nav.links {
display: flex;
gap: .25rem clamp(.75rem, 2vw, 1.75rem);
flex-wrap: wrap;
align-items: center;
font-family: var(--mono);
font-size: 12.5px;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--muted);
}
nav.links a { display: inline-flex; align-items: center; min-height: 2.75rem; transition: color 0.18s; }
nav.links a:hover { color: var(--ink); }
nav.links a.cv {
color: var(--accent);
border: 1px solid var(--line);
padding: 7px 13px;
border-radius: 999px;
transition: background 0.18s, border-color 0.18s;
}
nav.links a.cv:hover { background: var(--accent-soft); border-color: transparent; }
/* ---------- section frame ---------- */
section { padding: clamp(3rem, 7vw, 5.5rem) 0; border-bottom: 1px solid var(--line-soft); }
.label {
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--faint);
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 30px;
}
.label .num { color: var(--accent); }
.label::after {
content: "";
flex: 1;
height: 1px;
background: var(--line-soft);
}
/* ---------- hero ---------- */
.hero { padding: clamp(3rem, 8vw, 6.5rem) 0 clamp(3rem, 7vw, 5.5rem); border-bottom: 1px solid var(--line-soft); }
.avail {
font-family: var(--mono);
font-size: 12.5px;
letter-spacing: 0.04em;
color: var(--muted);
display: inline-flex;
align-items: center;
gap: 9px;
margin-bottom: 28px;
}
.hero h1 {
font-family: var(--display);
font-weight: 600;
font-size: clamp(48px, 9vw, 104px);
line-height: 0.98;
letter-spacing: -0.03em;
margin: 0 0 24px;
}
.hero .role {
font-family: var(--display);
font-weight: 500;
font-size: clamp(20px, 3.2vw, 30px);
letter-spacing: -0.01em;
color: var(--ink);
margin: 0 0 22px;
display: flex;
flex-wrap: wrap;
gap: 0 14px;
align-items: center;
}
.hero .role .sep { color: var(--accent); font-weight: 400; }
.hero p.lede {
max-width: 560px;
font-size: 18px;
color: var(--muted);
margin: 0 0 38px;
}
.cta { display: flex; flex-wrap: wrap; gap: 13px; }
.btn {
font-family: var(--mono);
font-size: 13px;
letter-spacing: 0.02em;
padding: 12px 20px;
border-radius: 999px;
border: 1px solid var(--line);
display: inline-flex;
align-items: center;
gap: 9px;
transition: transform 0.16s, background 0.18s, border-color 0.18s, color 0.18s;
}
.btn:hover { transform: translateY(-2px); }
.btn.primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.btn.primary:hover { background: var(--accent); border-color: var(--accent); }
.btn:not(.primary):hover { border-color: var(--ink); }
.btn svg { width: 15px; height: 15px; }
/* ---------- stack ---------- */
.stack-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2px;
background: var(--line-soft);
border: 1px solid var(--line-soft);
border-radius: 14px;
overflow: hidden;
}
@media (min-width: 56rem) { .stack-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.stack-col { min-width: 0; background: var(--bg); padding: clamp(1.25rem, 3vw, 2rem); }
.stack-col h3 {
font-family: var(--display);
font-weight: 600;
font-size: 22px;
margin: 0 0 4px;
letter-spacing: -0.01em;
}
.stack-col .tag {
font-family: var(--mono);
font-size: 11.5px;
color: var(--accent);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 20px;
display: block;
}
.stack-col ul { list-style: none; margin: 0; padding: 0; }
.stack-col li {
font-size: 15.5px;
color: var(--muted);
padding: 7px 0;
border-top: 1px solid var(--line-soft);
display: flex;
align-items: center;
gap: 10px;
}
.stack-col li:first-child { border-top: none; }
.stack-col li::before {
content: "";
width: 5px; height: 5px;
border-radius: 50%;
background: var(--accent);
flex: none;
opacity: 0.6;
}
/* ---------- projects ---------- */
.proj {
display: block;
padding: 30px 0;
border-top: 1px solid var(--line);
transition: padding-left 0.22s;
}
.proj:hover { padding-left: 14px; }
.proj-top { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.proj h3 {
font-family: var(--display);
font-weight: 600;
font-size: clamp(26px, 4vw, 38px);
margin: 0;
letter-spacing: -0.02em;
}
.proj .arrow {
font-family: var(--mono);
color: var(--accent);
font-size: 20px;
transition: transform 0.22s;
flex: none;
}
.proj:hover .arrow { transform: translate(4px, -4px); }
.proj p { color: var(--muted); margin: 14px 0 16px; max-width: 620px; font-size: 16.5px; }
.proj .meta { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
font-family: var(--mono);
font-size: 11.5px;
letter-spacing: 0.02em;
color: var(--muted);
background: var(--bg-soft);
padding: 5px 11px;
border-radius: 999px;
}
/* ---------- timeline ---------- */
.tl { position: relative; padding-left: 26px; }
.tl::before {
content: "";
position: absolute;
left: 4px; top: 6px; bottom: 6px;
width: 1px;
background: var(--line);
}
.tl-item { position: relative; padding: 0 0 34px; }
.tl-item:last-child { padding-bottom: 0; }
.tl-item::before {
content: "";
position: absolute;
left: -26px; top: 5px;
width: 9px; height: 9px;
border-radius: 50%;
background: var(--bg);
border: 2px solid var(--accent);
}
.tl-item .when {
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0.04em;
color: var(--faint);
margin-bottom: 5px;
}
.tl-item h4 {
font-family: var(--display);
font-weight: 600;
font-size: 20px;
margin: 0 0 6px;
letter-spacing: -0.01em;
}
.tl-item p { margin: 0; color: var(--muted); font-size: 15.5px; max-width: 600px; }
/* ---------- contact ---------- */
#contact { border-bottom: none; padding-bottom: clamp(3rem, 6vw, 4.5rem); }
.contact-head {
font-family: var(--display);
font-weight: 600;
font-size: clamp(34px, 6vw, 64px);
letter-spacing: -0.025em;
line-height: 1.02;
margin: 0 0 36px;
}
.contact-head a { border-bottom: 2px solid var(--accent-soft); transition: border-color 0.2s; }
.contact-head a:hover { border-color: var(--accent); }
.contact-rows { display: grid; gap: 1px; background: var(--line-soft); border-radius: 12px; overflow: hidden; border: 1px solid var(--line-soft); }
.crow {
background: var(--bg);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 18px 22px;
transition: background 0.18s;
}
.crow:hover { background: var(--bg-soft); }
.crow .k { font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--faint); }
.crow .v { font-family: var(--mono); font-size: 14px; color: var(--ink); display: flex; align-items: center; gap: 9px; }
.crow .v svg { width: 14px; height: 14px; color: var(--accent); }
footer {
padding: 30px 0 50px;
font-family: var(--mono);
font-size: 12px;
color: var(--faint);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 10px;
}
/* ---------- entrance animation ---------- */
.reveal { opacity: 0; transform: translateY(20px); }
@media (prefers-reduced-motion: no-preference) {
.reveal { transition: opacity 0.7s cubic-bezier(.2,.6,.2,1), transform 0.7s cubic-bezier(.2,.6,.2,1); }
}
.reveal.in { opacity: 1; transform: none; }
/* Source links and compact screens */
.all-projects { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
.all-projects { display: inline-block; margin-top: 24px; font-size: 16px; }
.crow .v { min-width: 0; overflow-wrap: anywhere; }
.crow .v svg { flex-shrink: 0; }
a:focus-visible { outline: 3px solid var(--accent); outline-offset: 5px; }
section { scroll-margin-top: 8rem; }
.brand { min-height: 2.75rem; }
.dot, .btn svg { flex-shrink: 0; }
.btn { min-height: 2.75rem; max-width: 100%; }
.proj h3, .proj p, .lede, .tl-item p, .stack-col li { overflow-wrap: anywhere; }
.crow { flex-wrap: wrap; }
.crow .v { max-width: 100%; }
.hero .role { display: block; }
@media (max-width: 55.999rem) {
header.bar { position: relative; }
.bar-inner { align-items: flex-start; }
nav.links { width: 100%; }
section { scroll-margin-top: 1rem; }
}
@media (hover: none) {
.proj:hover { padding-left: 0; }
.btn:hover, .proj:hover .arrow { transform: none; }
}
@media (max-width: 35rem) {
.cta { display: grid; grid-template-columns: minmax(0, 1fr); }
.btn { justify-content: center; }
.crow { align-items: flex-start; flex-direction: column; gap: 5px; padding: 16px; }
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.reveal { opacity: 1; transform: none; }
.btn, .proj, .proj .arrow { transition: none; }
}
/* ---------- print / resume ---------- */
@media print {
header.bar, .cta, footer, .label::after, nav.links { display: none !important; }
body { font-size: 11pt; background: #fff; }
section, .hero { padding: 18px 0; border-color: #ddd; break-inside: avoid; }
.reveal { opacity: 1 !important; transform: none !important; }
.hero h1 { font-size: 34pt; }
a { color: #000; }
.proj:hover { padding-left: 0; }
}
</style>
<noscript><style>.reveal { opacity: 1; transform: none; }</style></noscript>
</head>
<body>
<header class="bar">
<div class="wrap bar-inner">
<a class="brand" href="#top"><span class="dot"></span>Artem&nbsp;Titov</a>
<nav class="links" aria-label="Main navigation">
<a href="#stack">Stack</a>
<a href="#projects">Projects</a>
<a href="#timeline">Timeline</a>
<a href="#contact">Contact</a>
<a class="cv" href="#" id="cv-link">Resume ↓</a>
</nav>
</div>
</header>
<main class="wrap" id="top">
<!-- HERO -->
<section class="hero">
<span class="avail reveal"><span class="dot"></span>Open to internships &amp; collaboration</span>
<h1 class="reveal">Artem<br />Titov</h1>
<div class="role reveal">Linux DevOps
/
Rust
/
Python</div>
<p class="lede reveal">Backend &amp; systems developer focused on reliable infrastructure, fast tooling, and clean automation. 2nd-year student at VSU, building real projects in spare cycles.</p>
<div class="cta reveal">
<a class="btn primary" href="https://git.vpnpickup.com/artobyte" target="_blank" rel="noopener"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="5" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="19" r="3"/><path d="M6 8v8m12-7a9 9 0 0 1-9 9H6"/></svg>Forgejo · artobyte</a>
<a class="btn" href="#" id="cv-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m7 10 5 5 5-5"/><path d="M5 21h14"/></svg>
Download resume
</a>
<a class="btn" href="https://github.com/T1tukll" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.2.8-.6v-2c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.4-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11 11 0 0 1 6 0C18 4.7 19 5 19 5c.6 1.6.2 2.8.1 3.1.7.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .4.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5Z"/></svg>
GitHub
</a>
<a class="btn" href="#" data-eml>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg>
Email
</a>
</div>
</section>
<!-- STACK -->
<section id="stack">
<div class="label reveal"><span class="num">01</span> Skills &amp; Stack</div>
<div class="stack-grid">
<div class="stack-col reveal">
<h3>DevOps</h3>
<span class="tag">Infra &amp; automation</span>
<ul>
<li>Docker &amp; Compose</li>
<li>Linux administration</li>
<li>CI/CD pipelines</li>
<li>Nginx &amp; TLS</li>
<li>Bash scripting</li>
<li>Git &amp; GitHub Actions</li>
</ul>
</div>
<div class="stack-col reveal">
<h3>Rust</h3>
<span class="tag">Systems &amp; performance</span>
<ul>
<li>Tokio &amp; async</li>
<li>Networking / sockets</li>
<li>CLI tooling</li>
<li>Memory-safe design</li>
<li>Cargo &amp; crates</li>
<li>Error handling</li>
<li>Self-hosted web services</li>
</ul>
</div>
<div class="stack-col reveal">
<h3>Python</h3>
<span class="tag">Apps &amp; scripting</span>
<ul>
<li>Async &amp; sockets</li>
<li>Automation scripts</li>
<li>NumPy basics</li>
<li>REST APIs</li>
<li>Testing &amp; tooling</li>
</ul>
</div>
</div>
</section>
<!-- PROJECTS -->
<section id="projects">
<div class="label reveal"><span class="num">02</span> Selected Projects</div>
<a class="proj reveal" href="https://git.vpnpickup.com/artobyte/ipban" target="_blank" rel="noopener">
<div class="proj-top"><h3>ipban</h3><span class="arrow" aria-hidden="true"></span></div>
<p>A Python service for managing banned IP addresses, created to conveniently block bots. Includes SQLite support and systemd service. Currently in development, with a rewrite in Rust planned.</p>
<div class="meta"><span class="chip">Python</span><span class="chip">Rust (planned)</span><span class="chip">SQLite</span><span class="chip">systemd</span></div>
</a>
<a class="proj reveal" href="https://git.vpnpickup.com/artobyte/nas-py" target="_blank" rel="noopener">
<div class="proj-top"><h3>nas-py</h3><span class="arrow" aria-hidden="true"></span></div>
<p>A simple program for hosting network storage on your local network, designed to be easy to use: just run server_core.py and access your files through the web interface. Built with AI assistance, with some parts vibe-coded.</p>
<div class="meta"><span class="chip">Python</span><span class="chip">TLS</span><span class="chip">Web client</span><span class="chip">LAN</span></div>
</a>
<a class="proj reveal" href="https://git.vpnpickup.com/artobyte/nstart" target="_blank" rel="noopener">
<div class="proj-top"><h3>nstart</h3><span class="arrow" aria-hidden="true"></span></div>
<p>An interactive Zsh tool for connecting to a NetworkManager profile and launching Xray. Choose a preferred connection or browse available profiles from the terminal.</p>
<div class="meta"><span class="chip">Zsh</span><span class="chip">NetworkManager</span><span class="chip">Xray</span><span class="chip">CLI</span></div>
</a>
<a class="all-projects reveal" href="https://git.vpnpickup.com/artobyte" target="_blank" rel="noopener">All repositories on Forgejo ↗</a>
</section>
<!-- TIMELINE -->
<section id="timeline">
<div class="label reveal"><span class="num">03</span> Timeline</div>
<div class="tl">
<div class="tl-item reveal">
<div class="when">2025 — Present</div>
<h4>2nd-year student · VSU</h4>
<p>Studying computer science &amp; IT. Building backend, systems, and infrastructure
skills across DevOps, Rust, and Python alongside coursework.</p>
</div>
<div class="tl-item reveal">
<div class="when">2026</div>
<h4>Building in the open</h4>
<p>Hosting my projects on my own Forgejo server as artobyte — networked file storage, IP management, server tooling, and automation experiments.</p>
</div>
<div class="tl-item reveal">
<div class="when">Now</div>
<h4>Looking ahead</h4>
<p>Going deeper on distributed systems, container orchestration, and writing production-grade services.</p>
</div>
</div>
</section>
<!-- CONTACT -->
<section id="contact">
<div class="label reveal"><span class="num">04</span> Contact</div>
<h2 class="contact-head reveal">Let's build<br />something. <a href="#" data-eml>Say hi&nbsp;</a></h2>
<div class="contact-rows reveal">
<a class="crow" href="https://git.vpnpickup.com/artobyte" target="_blank" rel="noopener">
<span class="k">Forgejo · Primary</span>
<span class="v"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="5" r="3"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="19" r="3"/><path d="M6 8v8m12-7a9 9 0 0 1-9 9H6"/></svg>git.vpnpickup.com/artobyte</span>
</a>
<a class="crow" href="#" data-eml>
<span class="k">Email</span>
<span class="v"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 6L2 7"/></svg><span data-eml-text>[protected]</span></span>
</a>
<a class="crow" href="https://github.com/T1tukll" target="_blank" rel="noopener">
<span class="k">GitHub</span>
<span class="v"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.2.8-.6v-2c-3.2.7-3.9-1.4-3.9-1.4-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.7-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.4-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.3 1.2a11 11 0 0 1 6 0C18 4.7 19 5 19 5c.6 1.6.2 2.8.1 3.1.7.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .4.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5Z"/></svg>github.com/T1tukll</span>
</a>
</div>
</section>
<footer>
<span>© 2026 Artem Titov</span>
<span>DevOps · Rust · Python</span>
</footer>
</main>
<script>
// Staggered scroll reveal
(function () {
var els = Array.prototype.slice.call(document.querySelectorAll('.reveal'));
if (!('IntersectionObserver' in window)) {
els.forEach(function (el) { el.classList.add('in'); });
return;
}
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
var el = e.target;
var sibs = Array.prototype.slice.call(el.parentNode.querySelectorAll(':scope > .reveal'));
var idx = sibs.indexOf(el);
el.style.transitionDelay = (Math.max(0, idx) * 70) + 'ms';
el.classList.add('in');
io.unobserve(el);
}
});
}, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' });
els.forEach(function (el) { io.observe(el); });
})();
// Resume download → print to PDF
function openResume(e) { if (e) e.preventDefault(); window.print(); }
document.getElementById('cv-btn').addEventListener('click', openResume);
document.getElementById('cv-link').addEventListener('click', openResume);
// Email obfuscation: address is never present in the HTML source.
// It is assembled from parts in the browser, for humans only.
(function () {
var user = ['ti', 'tart', '1337'].join(''); // local part
var host = ['gmail', 'com'].join('.'); // domain
var addr = user + String.fromCharCode(64) + host; // 64 = "@"
document.querySelectorAll('[data-eml]').forEach(function (el) {
el.setAttribute('href', 'mailto:' + addr);
var label = el.querySelector('[data-eml-text]');
if (label) { label.textContent = addr; }
});
})();
</script>
</body>
</html>