Commit 002befac authored by Valera Trubachev's avatar Valera Trubachev

getting there

parent 754cb4e3
This diff is collapsed.
......@@ -18,10 +18,11 @@
</div>
<header>
<div class="container">
<hgroup class="container">
<h1>Valeriy E Trubachev</h1>
<p>I am a full stack web developer who doesn't like to compromise quality. I use modern tools and languages and try to push the boundary of what is considered best practice. I have pride in my work and like to tackle problems that benefit society as a whole. I pay attention to detail and try to perfect everything I work on.</p>
</div>
</hgroup>
<p class="container">I am a full stack web developer who doesn't like to compromise quality. I use modern tools and languages and try to push the boundary of what is considered best practice. I have pride in my work and like to tackle problems that benefit society as a whole. I pay attention to detail and try to perfect everything I work on.</p>
<nav>
<ul>
......@@ -33,38 +34,63 @@
</ul>
</nav>
</header>
<main>
<div class="container">
<% Object.entries(site).forEach(([slug, section]) => { %>
<section id="<%= slug %>">
<h2><%= section.heading %></h2>
<% if (section.intro) { %>
<p><%= section.intro.replace(/\n/g, '</p><p>') %></p>
<% } %>
<% if (section.skills && section.skills.length) { %>
<ul class="skills">
<% section.skills.forEach((skill) => { %>
<li><%= skill %></li>
<% }); %>
</ul>
<% } %>
<main>
<% Object.entries(site).forEach(([slug, section]) => { %>
<section id="<%= slug %>">
<% if (section.sections && section.sections.length) { %>
<% section.sections.forEach((subsection) => { %>
<h3><%= subsection.heading %></h3>
<% if (subsection.intro) { %>
<p><%= subsection.intro.replace(/\n/g, '</p><p>') %></p>
<div class="wrapper">
<div class="container">
<h2><%= section.heading %></h2>
<div class="content-block">
<% if (section.intro) { %>
<article>
<p><%= section.intro.replace(/\n/g, '</p><p>') %></p>
</article>
<% } %>
<% if (subsection.skills && subsection.skills.length) { %>
<ul class="skills">
<% subsection.skills.forEach((skill) => { %>
<li><%= skill %></li>
<% }); %>
</ul>
<% if (section.skills && section.skills.length) { %>
<aside class="skills">
<% if (section.skills_heading !== false) { %>
<h4><%= section.skills_heading || 'Skills' %></h4>
<% } %>
<ul>
<% section.skills.forEach((skill) => { %>
<li><%= skill %></li>
<% }); %>
</ul>
</aside>
<% } %>
</div>
</div>
</div>
<% if (section.sections && section.sections.length) { %>
<% section.sections.forEach((subsection) => { %>
<div class="wrapper">
<div class="container">
<h3><%= subsection.heading %></h3>
<div class="content-block">
<% if (subsection.intro) { %>
<article>
<p><%= subsection.intro.replace(/\n/g, '</p><p>') %></p>
</article>
<% } %>
<% if (subsection.skills && subsection.skills.length) { %>
<aside class="skills">
<% if (subsection.skills_heading !== false) { %>
<h4><%= subsection.skills_heading || 'Skills' %></h4>
<% } %>
<ul>
<% subsection.skills.forEach((skill) => { %>
<li><%= skill %></li>
<% }); %>
</ul>
</aside>
<% } %>
</div>
</div>
<% if (subsection.images && subsection.images.length) { %>
<ul class="showcase">
......@@ -73,11 +99,11 @@
<% }); %>
</ul>
<% } %>
<% }); %>
<% } %>
</section>
<% }); %>
</div>
</div>
<% }); %>
<% } %>
</section>
<% }); %>
</main>
<footer>
......@@ -89,4 +115,4 @@
</p>
</footer>
</body>
</html>
\ No newline at end of file
</html>
......@@ -46,11 +46,79 @@ function bindNavLinks() {
});
}
const FOCUSABLE_SELECTOR = `
a[href],
area[href],
input:not([disabled]),
select:not([disabled]),
textarea:not([disabled]),
button:not([disabled]),
[tabindex]:not([tabindex="-1"])`;
function getFocusables(element) {
return [...element.querySelectorAll(FOCUSABLE_SELECTOR)].filter((el) => {
const { width, height } = el.getBoundingClientRect();
return width !== 0 && height !== 0;
});
}
function restrictFocus(element, event = null) {
const focusables = getFocusables(element);
if (!event) {
if (focusables.length) {
focusables[0].focus();
}
return;
}
if (focusables.length < 1) {
event.preventDefault();
} else if (event.shiftKey && document.activeElement === focusables[0]) {
event.preventDefault();
focusables[focusables.length - 1].focus();
} else if (!event.shiftKey && document.activeElement === focusables[focusables.length - 1]) {
event.preventDefault();
focusables[0].focus();
}
}
function bindShowcaseImages() {
const modal = document.getElementById('showcase_modal');
const modalBody = modal.querySelector('.modal__body');
const modalClose = modal.querySelector('.modal__close');
const modalPrev = modal.querySelector('.modal__prev');
const modalNext = modal.querySelector('.modal__next');
const modalImg = modal.querySelector('img');
let prevImage = null;
let nextImage = null;
const setModalImage = (item) => {
modalImg.src = item.querySelector('img').src;
prevImage = null;
nextImage = null;
let foundCurrent = false;
let lastImage = null;
[...item.parentNode.querySelectorAll('li')].forEach((li) => {
if (li === item) {
foundCurrent = true;
prevImage = lastImage;
} else if (foundCurrent) {
foundCurrent = false;
nextImage = li;
}
lastImage = li;
});
modalPrev.style.display = prevImage === null ? 'none' : '';
modalNext.style.display = nextImage === null ? 'none' : '';
};
const closeModal = () => {
modal.classList.remove('modal--open');
modal.classList.remove('modal--positioned');
......@@ -62,10 +130,26 @@ function bindShowcaseImages() {
case 27: // esc
closeModal();
break;
case 9: // tab
if (modal.classList.contains('modal--open')) {
restrictFocus(modal, e);
}
}
});
modalClose.addEventListener('click', closeModal);
modalPrev.addEventListener('click', () => {
if (prevImage !== null) {
setModalImage(prevImage);
}
});
modal.querySelector('.modal__close').addEventListener('click', closeModal);
modalNext.addEventListener('click', () => {
if (nextImage !== null) {
setModalImage(nextImage);
}
});
modal.addEventListener('click', (e) => {
if (e.target === modal) {
......@@ -73,22 +157,27 @@ function bindShowcaseImages() {
}
});
[...document.querySelectorAll('.showcase li')].forEach((img) => {
img.addEventListener('click', () => {
[...document.querySelectorAll('.showcase li')].forEach((item) => {
item.addEventListener('click', () => {
const {
top, left, width, height,
} = img.getBoundingClientRect();
top,
left,
width,
height,
} = item.getBoundingClientRect();
modalBody.style.top = `${top}px`;
modalBody.style.left = `${left}px`;
modalBody.style.width = `${width}px`;
modalBody.style.height = `${height}px`;
modalImg.src = img.querySelector('img').src;
setModalImage(item);
modal.classList.add('modal--open');
modal.setAttribute('aria-hidden', 'false');
restrictFocus(modal);
// double raf to let it animate properly
requestAnimationFrame(() => {
requestAnimationFrame(() => {
modalBody.style.top = '';
......
[data-whatintent="keyboard"] *:focus {
outline: solid 5px green;
}
\ No newline at end of file
outline: solid 4px yellow;
}
......@@ -8,17 +8,45 @@ main {
font-size: 2em;
}
ul.skills {
padding-left: 2em;
list-style: disc;
@include mqw-min(40rem) {
.content-block {
display: flex;
article {
flex: 0 1 auto;
}
.skills {
flex: 0 0 17em;
max-width: 33%;
margin-left: 1em;
margin-top: -1.75em;
border-left: solid 1px #e0e0e0;
}
}
}
.skills {
h4 {
margin-top: 0;
margin-bottom: 0.5em;
margin-left: 2em;
}
ul {
padding-left: 2em;
list-style: disc;
}
}
ul.showcase {
display: flex;
flex-wrap: wrap;
list-style: none;
justify-content: space-around;
justify-content: space-evenly;
align-items: center;
max-width: 75em;
margin: 0 auto;
li {
max-width: 15em;
......@@ -27,6 +55,7 @@ main {
position: relative;
transition: transform .125s ease-in-out;
box-shadow: 2px 2px 4px 1px rgba(black, .5);
cursor: pointer;
&:hover {
transform: scale(1.05);
......@@ -63,3 +92,22 @@ main {
}
}
}
#experience, #showcase {
.wrapper:nth-child(even) {
background: #f6f6f6;
}
}
#learning {
.content-block {
display: block;
.skills {
width: 100%;
max-width: none;
margin-top: 0;
border-left: 0;
}
}
}
......@@ -2,7 +2,7 @@ body {
font: 16px/1.3 lato, sans-serif;
}
h1, h2, h3 {
h1, h2, h3, h4 {
margin: 1em 0;
}
......@@ -11,7 +11,7 @@ p {
}
.container {
max-width: 50em;
max-width: 60em;
margin: 0 auto;
padding: 0 .5em;
......@@ -20,3 +20,12 @@ p {
z-index: 1;
}
}
.wrapper {
padding: 1em;
> h1, > h2, > h3,
> .container > h1, > .container > h2, > .container > h3 {
margin-top: 0;
}
}
......@@ -3,7 +3,7 @@ header {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
justify-content: space-between;
background: gray;
color: white;
text-align: center;
......@@ -38,15 +38,33 @@ header {
}
}
hgroup {
flex: 1 1 auto;
display: flex;
align-items: center;
}
h1 {
font-size: 7.5vw;
z-index: 1;
font-size: 3.5em;
margin: 0;
position: relative;
@include mqw-min(40rem) {
font-size: 7.5vw;
}
@include mqw-min(80rem) {
font-size: 6em;
}
}
p {
flex: 1 1 auto;
}
nav {
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
padding: 0.5em 0;
ul {
......@@ -56,7 +74,7 @@ header {
flex-wrap: wrap;
li {
padding: 1em;
padding: 1em 1.25em;
a {
text-decoration: none;
......
@mixin mqw-min($width) {
@media screen and (min-width: $width) {
@content;
}
}
......@@ -6,6 +6,7 @@
padding: 1em;
font-size: 1.25em;
border-radius: 4px;
cursor: pointer;
}
%line {
......@@ -44,7 +45,7 @@
&__body {
position: absolute;
transition: all .25s ease-out;
.modal--positioned & {
top: 10%;
left: 10%;
......@@ -60,12 +61,12 @@
&::before {
@extend %line;
transform: rotateZ(-45deg);
transform: translateX(-.5em) rotateZ(-45deg);
}
&::after {
@extend %line;
transform: rotateZ(45deg);
transform: translateX(-.5em) rotateZ(45deg);
}
}
......
// @import "../../node_modules/normalize.css/";
@import "variables";
@import "mixins";
@import "reset";
@import "accessibility";
......
......@@ -3,9 +3,13 @@ about:
intro: |-
Hello! I am a full-stack web developer based out of Chicago. I enjoy tinkering with all things tech and have a passion for writing elegant code and pushing the boundary of what is considered best practice. I always strive to write simple, maintainable, and testable code that is stable in the long term and adaptable across a wide range of problems. I believe that a person's code reflects their personality and I always hold myself to the highest standard when crafting solutions. I love to help others learn and attain their maximum potential.
skills:
heading: 'Core Competencies'
nav: false
experience:
heading: 'My experience'
nav: 'Experience'
intro: |-
As a full-stack developer, I've touched every part of the stack that involves building, deploying, and operating a website. From the bare metal that powers the whole thing to quality assurance of frontend code, I have exposure to a wide array of technologies. My focus falls more on the backend side of things like deployment of websites, toolchain development, and backend server logic for websites and applications; however, I am always willing to jump in to help with any of the other facets of website and application development
My preferred stack for a typical website involves a Linux server running Nginx with PHP-FPM application workers. For the application itself, I prefer working with a Laravel framework with a MySQL database for data storage and Redis for caching and sessions, along with Twig for any templating needs. For the frontend, I use Vue.js and its companion vue-router and vuex libraries as the framework, writing JS/CSS using ES6/SCSS syntax and building/packaging the assets using Webpack and Gulp.
skills_heading: 'Core Competencies'
skills:
- 'PHP'
- 'Laravel'
......@@ -15,13 +19,6 @@ skills:
- 'CSS (SASS)'
- 'JS (ES6)'
- 'Vue.js'
experience:
heading: 'My experience'
nav: 'Experience'
intro: |-
As a full-stack developer, I've touched every part of the stack that involves building, deploying, and operating a website. From the bare metal that powers the whole thing to quality assurance of frontend code, I have exposure to a wide array of technologies. My focus falls more on the backend side of things like deployment of websites, toolchain development, and backend server logic for websites and applications; however, I am always willing to jump in to help with any of the other facets of website and application development
My preferred stack for a typical website involves a Linux server running Nginx with PHP-FPM application workers. For the application itself, I prefer working with a Laravel framework with a MySQL database for data storage and Redis for caching and sessions, along with Twig for any templating needs. For the frontend, I use Vue.js and its companion vue-router and vuex libraries as the framework, writing JS/CSS using ES6/SCSS syntax and building/packaging the assets using Webpack and Gulp.
sections:
-
heading: 'Hardware'
......@@ -96,7 +93,7 @@ showcase:
As part of the project, I wrote and maintain the Android app that downloads and installs the updates. I also wrote the backend code and maintain the server infrastructure for both the public version and commercial clients.
images:
# - { src: './img/showcase/ota-website.png', label: 'OTA website' }
# - { src: './img/showcase/ota-infrastructure.png', label: 'Public server infrastructure' }
- { src: './img/showcase/ota-infrastructure.png', label: 'Server infrastructure' }
- { src: './img/showcase/ota-screenshot.png', label: 'OTA app screenshot' }
-
heading: 'Power Box'
......@@ -115,20 +112,21 @@ showcase:
heading: 'CMS Development'
intro: |-
I co-lead the development of an in-house content management system that improved our website build process, made our site administration interface consistent across projects, and delivered key features to our content managers like draft content, previewing, and scheduled publishing. At the same time, it was a headless CMS that gave developers autonomy over how they build the site without getting in their way, which has allowed unique user interfaces on new websites without the bulk of a typical CMS.
-
heading: 'Celestial Seasonings'
intro: |-
This was a brand website that was built for the Celestial Seasonings line of tea and beverage products. It was built on Drupal and integrated with the client's internal APIs for some functionality.
-
heading: 'Start Strong, Stay Strong'
intro: |-
This was a community-driven website for Procter and Gamble's Start Strong, Stay Strong initiative for military families. It featured user registration, user-submitted community content centered around military bases, and content moderation. It was our development team's first SPA (single-page application) site, written using Vue.js.
-
heading: 'Celestial Seasonings'
intro: |-
This was a brand website that was built for the Celestial Seasonings line of tea and beverage products. It was built on Drupal and integrated with the client's internal APIs for some functionality.
learning:
heading: 'What I''m Learning'
nav: false
intro: |-
As a developer, I can never stop learning. Here's what I'm exploring currently:
skills_heading: false
skills:
- 'Application and server security hardening'
- 'Mass server provisioning (e.g. Chef/Puppet)'
......@@ -136,4 +134,4 @@ learning:
contact:
heading: 'Contact'
intro: |-
Interested in working with me? <a href="https://docs.google.com/forms/d/e/1FAIpQLScySuNxhe62SE6v9yP7Zvvg790uSAII3kXGaCL3yAJU1igC5w/viewform?usp=sf_link" target="_blank">Drop me a line!</a>
\ No newline at end of file
Interested in working with me? <a href="https://docs.google.com/forms/d/e/1FAIpQLScySuNxhe62SE6v9yP7Zvvg790uSAII3kXGaCL3yAJU1igC5w/viewform?usp=sf_link" target="_blank">Drop me a line!</a>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment