Hello
+I'm Logan, a front-end software engineer.
++ I build delightful software experiences, and love making genuine + connections with people along the way. +
+diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..a73033f --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +codinglogan.dev \ No newline at end of file diff --git a/docs/fonts/SnesItalic-1G9Be.ttf b/docs/fonts/SnesItalic-1G9Be.ttf new file mode 100644 index 0000000..5352ac3 Binary files /dev/null and b/docs/fonts/SnesItalic-1G9Be.ttf differ diff --git a/docs/graph/graph.js b/docs/graph/graph.js new file mode 100644 index 0000000..bf9b090 --- /dev/null +++ b/docs/graph/graph.js @@ -0,0 +1,254 @@ +import { Point } from './point.js' +import { Rectangle } from './rectangle.js' + +let INTEREST_HIGH = 'High' +let INTEREST_MEDIUM = 'Medium' +let INTEREST_LOW = 'Low' + +let humanReadableData = [ + { + label: 'JS, HTML, CSS', + range: `2013-2023`, + interest: INTEREST_HIGH, + color: 'yellow', + }, + { + label: 'MySQL', + range: '2013-2020', + interest: INTEREST_MEDIUM, + color: '#5d4c36', + }, + { + label: 'PHP', + range: '2013-2020', + interest: INTEREST_LOW, + color: 'white', + }, + { + label: 'NodeJS', + range: `2015-2023`, + interest: INTEREST_HIGH, + color: 'lightgreen', + }, + { + label: 'Angular', + range: `2017-2020`, + interest: INTEREST_LOW, + color: 'pink', + }, + { + label: 'React', + range: `2020-2023`, + interest: INTEREST_HIGH, + color: 'cyan', + }, + + { + label: 'Ruby on Rails', + range: '2022-2023', + interest: INTEREST_MEDIUM, + color: 'red', + }, +] + +function convertToGrid(experienceData) { + // Determine lowest X value (grid will treat it as 0) + let minYear = 3000 + let maxYear = 0 + for (const lineElement of experienceData) { + const range = lineElement.range.split('-') + minYear = Math.min(minYear, Number.parseInt(range[0])) + maxYear = Math.max(maxYear, Number.parseInt(range[1])) + } + + let index = 0 + const gridMapping = {} + for (let i = minYear; i <= maxYear; i++) { + gridMapping[i] = index + index++ + } + + const lines = [] + for (const lineElement of experienceData) { + const range = lineElement.range.split('-') + const lowYear = Number.parseInt(range[0]) + const highYear = Number.parseInt(range[1]) + const experienceIncrease = + Number.parseInt(highYear) - Number.parseInt(lowYear) + + lines.push({ + color: lineElement.color, + label: lineElement.label, + points: [ + new Point(gridMapping[lowYear], 0), + new Point(gridMapping[highYear], experienceIncrease), + ], + interest: lineElement.interest, + }) + } + + return { lines, minYear, maxYear } +} + +let { lines: data, minYear, maxYear } = convertToGrid(humanReadableData) + +function resizeCanvas(canvas) { + const parentStyles = getComputedStyle(canvas.parentElement) + + canvas.width = + canvas.parentElement.clientWidth - + parseFloat(parentStyles.paddingLeft) - + parseFloat(parentStyles.paddingRight) +} + +/** + * Assumes canvas size is properly set + */ +function drawGraph(canvas) { + const axisColor = 'rgb(207, 205, 180)' + /* Determine sections of the graph, helpful for drawing calculations + - Grid Area + - Label gutters + - Key/Legend area + */ + const gutterSize = 24 + const gridRectangle = new Rectangle( + gutterSize, + canvas.width, + 0, + canvas.height - gutterSize + ) + + let maxX = 0 + let maxY = 0 + for (const lineElement of data) { + for (const linePoint of lineElement.points) { + maxX = Math.max(maxX, linePoint.x) + maxY = Math.max(maxY, linePoint.y) + } + } + // Get canvas scale factor + const xScale = Math.floor(gridRectangle.width / maxX) + const yScale = Math.floor(gridRectangle.height / maxY) + + // BEGIN DRAWING ===================================================== + const ctx = canvas.getContext('2d') + + // Draw The X and Y axis lines + ctx.lineWidth = 4 + ctx.strokeStyle = axisColor + ctx.beginPath() + ctx.moveTo(gridRectangle.left, 0) + ctx.lineTo(gridRectangle.left, gridRectangle.bottom) + ctx.lineTo(gridRectangle.right, gridRectangle.bottom) + ctx.stroke() // Actually draws axis lines + + // Draw the tick marks for the x axis + const tickSize = 8 + + // Draw X axis ticks (left to right) + for (let i = 0; i <= maxYear - minYear; i++) { + ctx.strokeStyle = axisColor + ctx.beginPath() + ctx.moveTo(gridRectangle.left + xScale * i, gridRectangle.bottom) + ctx.lineTo(gridRectangle.left + xScale * i, gridRectangle.bottom + tickSize) + ctx.stroke() + + // Text labels + ctx.font = '12px serif' + ctx.fillStyle = axisColor + ctx.fillText( + minYear + i, + gridRectangle.left + xScale * i - 24, // 24 bumps text left + gridRectangle.bottom + tickSize * 3 + ) + } + + // Draw Y axis ticks (bottom to top) + for (let i = 0; i <= maxY; i++) { + // Draw a vertical reference line + ctx.lineWidth = 1 + ctx.strokeStyle = 'darkgray' + ctx.beginPath() + ctx.moveTo(gridRectangle.left, gridRectangle.bottom - yScale * i) + ctx.lineTo(gridRectangle.right, gridRectangle.bottom - yScale * i) + ctx.stroke() + + ctx.lineWidth = 4 + ctx.strokeStyle = axisColor + ctx.beginPath() + ctx.moveTo(gridRectangle.left, gridRectangle.bottom - yScale * i) + ctx.lineTo(gridRectangle.left - tickSize, gridRectangle.bottom - yScale * i) + ctx.stroke() + + // Text labels + ctx.font = '12px serif' + ctx.fillStyle = axisColor + ctx.fillText( + i, + gridRectangle.left - tickSize - 16, // 16 adjust the text position slightly + gridRectangle.bottom - yScale * i + 4 // 4 helps align text vertically with tick mark + ) + } + + // Draw lines + const lineWidths = { + [INTEREST_HIGH]: 12, + [INTEREST_MEDIUM]: 8, + [INTEREST_LOW]: 4, + } + + for (const lineElement of data) { + ctx.beginPath() + ctx.strokeStyle = lineElement.color + ctx.lineWidth = lineWidths[lineElement.interest] + + // Use the given points to draw each line + let startingPoint = true + for (const point of lineElement.points) { + if (startingPoint) { + ctx.moveTo( + gridRectangle.left + point.x * xScale, + gridRectangle.bottom - point.y * yScale + ) + startingPoint = false + } else { + ctx.lineTo( + gridRectangle.left + point.x * xScale, + gridRectangle.bottom - point.y * yScale + ) + } + } + ctx.lineCap = 'round' + ctx.stroke() + } + + // Draw the Key for each line + const legendTop = 14 + const colorKeyLeft = gridRectangle.left + 8 + const legendLeft = gridRectangle.left + 28 + for (let i = 0; i < data.length; i++) { + ctx.fillStyle = data[i].color + ctx.fillRect(colorKeyLeft, legendTop - 14 + i * 20, 16, 16) + ctx.font = '20px serif' + ctx.fillStyle = axisColor + ctx.fillText(data[i].label, legendLeft, legendTop + i * 20) + } +} + +function drawCanvas() { + const canvas = document.getElementById('experience-chart') + resizeCanvas(canvas) + drawGraph(canvas) +} + +function init() { + window.addEventListener('resize', (event) => { + drawCanvas() + }) +} + +window.graphJS = { + init, + drawCanvas, +} diff --git a/docs/graph/point.js b/docs/graph/point.js new file mode 100644 index 0000000..65171d2 --- /dev/null +++ b/docs/graph/point.js @@ -0,0 +1,6 @@ +export class Point { + constructor(x, y) { + this.x = x + this.y = y + } +} diff --git a/docs/graph/rectangle.js b/docs/graph/rectangle.js new file mode 100644 index 0000000..53a3949 --- /dev/null +++ b/docs/graph/rectangle.js @@ -0,0 +1,12 @@ +export class Rectangle { + constructor(left, right, top, bottom) { + this.left = left + this.right = right + this.width = right - left + + // Using standard display y axis where 0,0 is top left + this.top = top + this.bottom = bottom + this.height = bottom - top + } +} diff --git a/docs/images/Keys720.png b/docs/images/Keys720.png new file mode 100644 index 0000000..5798ec2 Binary files /dev/null and b/docs/images/Keys720.png differ diff --git a/docs/images/diablotools.jpg b/docs/images/diablotools.jpg new file mode 100644 index 0000000..024d3de Binary files /dev/null and b/docs/images/diablotools.jpg differ diff --git a/docs/images/familysearch.png b/docs/images/familysearch.png new file mode 100644 index 0000000..d02f600 Binary files /dev/null and b/docs/images/familysearch.png differ diff --git a/docs/images/good-company.jpg b/docs/images/good-company.jpg new file mode 100644 index 0000000..f0f7d6f Binary files /dev/null and b/docs/images/good-company.jpg differ diff --git a/docs/images/learning.jpg b/docs/images/learning.jpg new file mode 100644 index 0000000..ae2b31d Binary files /dev/null and b/docs/images/learning.jpg differ diff --git a/docs/images/profile-cropped.jpg b/docs/images/profile-cropped.jpg new file mode 100644 index 0000000..4217501 Binary files /dev/null and b/docs/images/profile-cropped.jpg differ diff --git a/docs/images/profile.jpg b/docs/images/profile.jpg new file mode 100644 index 0000000..7bae19a Binary files /dev/null and b/docs/images/profile.jpg differ diff --git a/docs/images/trust.jpg b/docs/images/trust.jpg new file mode 100644 index 0000000..acb0976 Binary files /dev/null and b/docs/images/trust.jpg differ diff --git a/docs/index.css b/docs/index.css new file mode 100644 index 0000000..182b071 --- /dev/null +++ b/docs/index.css @@ -0,0 +1,236 @@ +:root { + --nav-background: rgb(20, 21, 22); + --background-color: rgb(20, 21, 22); + --text-color: rgb(207, 205, 180); + --background-alternate-color: rgb(1, 36, 54); + + --space-sm: 8px; + --space-md: 16px; + --space-lg: 32px; + --space-xl: 64px; +} + +@font-face { + font-family: 'SNES'; + src: url('fonts/SnesItalic-1G9Be.ttf') format('truetype'); +} + +body { + margin: 0; + padding: 0; + background-color: var(--background-color); + color: var(--text-color); + font-family: sans-serif; + line-height: 1.5em; +} + +nav { + background-color: var(--nav-background); +} + +nav ul { + display: flex; + align-items: center; + list-style: none; + margin: 0; + padding: 0; +} + +/* The first child is an img with 60px height */ +nav ul li:first-child { + height: 60px; + margin-right: 0; +} + +.nav-item, +nav ul li { + margin-right: var(--space-md); + font-family: SNES; + font-size: 1.75em; +} + +/* Nav image is expected to be square */ +.nav-left-img { + height: 60px; + clip-path: polygon(0 0, 100% 0, 71% 100%, 0 100%); +} + +.nav-item .active, +nav ul li .active { + border-bottom: 2px solid var(--text-color); +} + +.nav-link, +.nav-item a { + color: var(--text-color); + text-decoration: none; +} + +.nav-link:hover, +.nav-item a:hover { + text-decoration: underline; +} + +section:nth-child(odd) { + background-color: var(--background-alternate-color); + background: linear-gradient( + var(--background-alternate-color), + var(--background-alternate-color) 90%, + var(--background-color) 100% + ); +} + +section:nth-child(2n + 4) { + background: linear-gradient( + var(--background-color), + var(--background-color) 90%, + var(--background-alternate-color) 100% + ); +} + +section { + position: relative; +} + +video { + width: 100%; +} + +/* General classes */ +.content { + margin: auto; + padding: var(--space-md); + padding-top: var(--space-lg); + padding-bottom: var(--space-xl); + max-width: 750px; +} + +.heading { + margin-bottom: var(--space-xl); +} + +/* Hero Declaration Section */ +.hero { + background-color: black; +} + +.hero-image { + width: 100%; + object-fit: cover; +} + +.hero-helloText { + position: absolute; + left: 16px; + top: 100px; + max-width: 75px; + line-height: 1.25em; +} + +/* This makes the paragraph appear in correct layer order */ +#hero-paragraph p { + position: relative; +} + +/* Creates the gradient to blend into the other section */ +.hero-mask { + position: relative; +} +.hero-mask::before { + content: ''; + width: 100%; + height: 100%; + position: absolute; + background: linear-gradient( + to bottom, + rgba(0, 0, 0, 0) 80%, + rgba(0, 0, 0, 0.3) 93%, + rgba(0, 0, 0, 1) 100% + ); +} + +@media screen and (max-width: 749px) and (orientation: landscape) { + .hero-image { + width: unset; + } +} + +@media (min-width: 750px) or (orientation: landscape) { + .hero { + display: flex; + } + + .hero-image { + max-height: 100vh; + object-fit: contain; + object-position: top; + } + + /* Changes direction and definition of the gradient */ + .hero-mask::before { + background: linear-gradient( + to right, + rgba(0, 0, 0, 0) 80%, + rgba(0, 0, 0, 0.3) 93%, + rgba(0, 0, 0, 1) 100% + ); + } + + .hero-helloText { + position: relative; + left: 0; + top: 0; + max-width: 100%; + font-size: 1.5em; + } +} +/* End Hero Declaration */ + +/* Describe how to make content-image flip flop */ +.flipFlop { + display: flex; + flex-direction: column; + gap: 16px; + margin-top: var(--space-xl); +} + +.flipFlop-image { + width: 100%; + object-fit: contain; +} + +@media (min-width: 750px) or (orientation: landscape) { + .flipFlop { + flex-direction: row; + } + + .flipFlop:nth-child(odd) { + flex-direction: row-reverse; + } + + .flipFlop-image { + width: 40%; + } +} +/* End flipFlop */ + +/* Footer styles */ +footer ul, +footer ol { + display: flex; + gap: 32px; + list-style: none; + justify-content: center; + font-size: 0.8em; + background-color: var(--nav-background); + padding-left: 0px; +} + +.squareList { + padding-left: 0.8em; + list-style: square; +} + +#experience-chart { + margin-top: var(--space-lg); +} diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..0ac9140 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,321 @@ + + +
+ + + + + +
+ I'm Logan, a front-end software engineer.
++ I build delightful software experiences, and love making genuine + connections with people along the way. +
++ Currently I focus on building front-end web experiences for + end-users and I've also built back-office software and database + integrations. If you're reading this, you may be wondering how I might + fit in with your interests and technology needs, right? +
++ To make that answer easier for you, here's a visual timeline graph + showing the years of professional experience I have in relevant + technologies. +
++ Thicker lines in the graph are what I use regulary and I have a + high interest in. +
+ + ++ Simply put, I look for ways to turn every moment into a learning + opportunity and make it enjoyable! Here are some other ways to + describe what I mean... +
+ +
+ + I strive to stay current, learn new skills, and elevate my + effectiveness. I find great value in stepping away from the screen + and diving into a technology book or attending a conference. +
++ They introduce me to new people, ideas, age-old solutions, and a + fresh view on how I can solve a problem. Some of my favorite books + are The Pragmatic Programmer, Eloquent JavaScript, and Code + Complete. +
+
+ + I firmly beleive that trust is a key component to successful + professional relationships and cultures. +
++ When employers trust their employees to get the job done, and + employees genuinely feel trusted and properly empowered, happiness + and productivity can find its peak. +
+
+ + While employed at FamilySearch, I had the privilege of turning a + beautiful UX design into the page you can currently see at. + https://www.familysearch.org/gettingstarted/. +
++ It was carefully designed to be responsive to screen-sizes, + translated to multiple languages, and simple for users to consume. + These pages were built using highly reusable React components. +
+
+ + On a more personal note, I'm a long-time fan of the Diablo series + of games made by Blizzard Entertainment. +
++ So much of a fan... I created a small website/tool that helps + players discover what runes should be placed into socketed items + to create powerful runeword-enhanced weapons and armor. +
++ You can see it at + https://codinglogan.github.io/diablo-2-runewords/ +
+
+ + This is a browser game that I created many years ago. Games are + what brought me into this amazing programming career, so it is + fitting that I have at least one simple game of my own on display! +
+ ++ A playable version of it is hosted on GitHub at + https://codinglogan.github.io/dungeon-keys/DungeonKeys/grid.html. Feel free to check it out! (Also, you can win, it isn't rigged + ;) ) +
+
+ + Games inspired my curiosity and a 5th grade research report set + my interest in stone. +
++ My first experience playing a PC game was on a Windows 95 PC around + the age of 6 (where I hilariously thought the large white CRT monitor + was a new TV on Christmas morning). Those very early experiences of + controlling pixels on the screen with a ball-mouse planted a seed of + curiosity in tech. +
+ ++ In the 5th grade that seed sprouted from curiosity to real interest + when I was assigned to write a career research paper. Somehow, even + being that young, I chose to research "Computer Network Engineers". + After turning in the paper I was entranced that a person could make a + decent living working with computers, and I NEVER looked back! +
+ ++ Through my teen years I found myself solving network issues and + frequently setting up local-area networks for friends and family to + play games like Diablo II and Starcraft. Combine that with Starcraft's + Map Editor which gave me a taste of "if-then" constructs to change the + gameplay, I felt like a techno-wizard! +
+ ++ This led me to branch out and make some basic space shooter games + using Game Maker, which only further deepened my interest in exploring + software programming. +
+ ++ After being the "computer guy" of the family for many years I started + college, and then I landed my first official programming job building + back-office websites for a famous pizza company. I worked full-time + and tackled a full-time load of courses each semester, and graduated + with a Computer Science degree at UVU in 2016. +
+ ++ Programming has become a part of who I am, and I can't imagine + choosing any other career knowing the fun and the challenge that it + brings. +
+