diff --git a/.eslintrc.js b/.eslintrc.js index 5850dc4..f4d8682 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,6 +7,8 @@ module.exports = { 'globals': { // We use jQuery, so $ is fine '$': 'readonly', + // Vue 3 is loaded via CDN as a global in some articles + 'Vue': 'readonly', 'Atomics': 'readonly', 'SharedArrayBuffer': 'readonly' }, @@ -14,6 +16,7 @@ module.exports = { 'ecmaVersion': 2018 }, 'rules': { + // TODO: Change this to indent of 2 'indent': [ 'error', 4 diff --git a/_posts/2026-07-15-ev-road-tripping.md b/_posts/2026-07-15-ev-road-tripping.md new file mode 100644 index 0000000..d5d6063 --- /dev/null +++ b/_posts/2026-07-15-ev-road-tripping.md @@ -0,0 +1,79 @@ +--- +layout: post +title: "Chicago to Mammoth Caves: A Case Study in Seamless EV Road Tripping" +metadata: + image: + description: + If you haven't driven electric before, a road trip might sound impossible - but I want to tell + you about driving from Chicago to Kentucky to Mammoth Caves. +stylesheets: + - articles/ev-road-trip.css +scripts: + - articles/ev-road-trip.js +--- + +When people talk about going electric, they are often concerned about road tripping - an EV might be +fine for my daily commute, but can I still make a long road trip for a summer vacation? I'm here +to tell you that modern EVs can tackle a road trip incredibly well. + +## What I'll Cover + +Ionna Lafayette Indiana + +## The Vehicle - The Hyundai IONIQ 5! + +For this road trip, I was driving a 2023 Hyundai IONIQ 5 SEL AWD. The IONIQ 5 is a very capable but +budget-friendly EV. Here's some quick facts on the Ioniq 5 it: + +- Starts at around $37,000 ($45,000 for the long-range version) +- Charges 10% to 80% in about 20 minutes +- EPA rated range of up to 318 miles + + +Since I was driving a 2023 all wheel drive model, it has an 266 mile EPA range rating - the newer +model years have a larger battery for the long-range model, adn rear wheel drive models have the +318 mile range! + +## The Trip + +Let's get started! I've made a little progress bar to follow along on our journey + + + +
+ +## Day 1: Departing Chicago {#chicago-start} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +## Rockford Stop {#rockford-day1} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +## Arriving in Galena {#galena-arrival} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +## Day 2: Leaving Galena {#galena-day2} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +## Rockford Again {#rockford-day2} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +## Back in Chicago {#chicago-return} + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. diff --git a/css/articles/ev-road-trip.scss b/css/articles/ev-road-trip.scss new file mode 100644 index 0000000..242fb89 --- /dev/null +++ b/css/articles/ev-road-trip.scss @@ -0,0 +1,229 @@ +--- +--- + +@use 'variables/colors' as *; +@use 'variables/sizing' as *; + +h2 { + border-left: 5px solid transparent; + padding-left: 0.5rem; + margin-left: -0.5rem; + transition: border-color 0.3s ease; + scroll-margin-top: 300px; +} + +h2.-active-section { + border-color: $brand-red; +} + +.trip-progress { + background: var(--light-bg-color); + border-bottom: 2px solid $mid-grey; + padding: 0.5rem 1rem 0.75rem; + margin-bottom: 2rem; + box-shadow: 0px 2px 3px #0000006b; + border-radius: 10rem; +} + +.trip-row { + display: flex; + align-items: center; + gap: 2.5rem; + margin: 0 auto; + padding: 0 1rem; +} + +.trip-days__label { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--text-color-faded); + white-space: nowrap; +} + +.trip-track { + position: relative; + height: 3.75rem; + flex: 1; + + // Line sits at the center of the dot row: 20px icon + 4px gap + 10px (half dot) = 34px + &__line { + position: absolute; + top: 33px; + left: 0; + right: 0; + height: 2px; + background: var(--dark-bg-color); + } + + &__progress { + position: absolute; + top: 33px; + left: 0; + height: 2px; + background: $brand-red; + transition: width 0.5s ease; + } + + &__divider { + position: absolute; + top: 0.25rem; + bottom: 0; + left: 50%; + width: 1px; + background: var(--dark-bg-color); + transform: translateX(-50%); + } + + &__car { + position: absolute; + top: 1.45rem; + width: 3rem; + transform: translateX(-50%); + transition: left 0.5s ease; + pointer-events: none; + z-index: 3; + } +} + +.trip-stop { + position: absolute; + top: 0; + display: flex; + flex-direction: column; + align-items: center; + background: none; + border: none; + cursor: pointer; + padding: 0; + transform: translateX(-50%); + text-decoration: none; + color: inherit; + + &:focus-visible { + outline: 2px solid $brand-red; + outline-offset: 2px; + border-radius: 2px; + } + + &__icon { + width: 20px; + height: 20px; + position: relative; + z-index: 2; + transition: filter 0.3s; + } + + &:hover .trip-stop__icon { + filter: invert(14%) sepia(89%) saturate(4680%) hue-rotate(3deg) brightness(85%) contrast(115%); + } + + &__dot { + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--light-bg-color); + border: 2px solid var(--dark-bg-color); + transition: border-color 0.3s; + margin-top: 4px; + position: relative; + z-index: 1; + } + + &__label { + font-size: 0.65rem; + color: var(--text-color-faded); + white-space: nowrap; + margin-top: 0.3rem; + transition: color 0.3s; + } + + &.-visited { + .trip-stop__dot { + background: $brand-red; + border-color: $brand-red; + + &::after { + content: '✓'; + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + color: $white; + font-size: 11px; + font-weight: 700; + line-height: 1; + } + } + + .trip-stop__icon { + filter: invert(14%) sepia(89%) saturate(4680%) hue-rotate(3deg) brightness(85%) contrast(115%); + } + + .trip-stop__label { + color: var(--text-color); + } + } + + &.-active { + .trip-stop__dot { + background: $brand-red; + border-color: $brand-red; + box-shadow: 0 0 0 3px rgba($brand-red, 0.2); + } + + .trip-stop__icon { + filter: drop-shadow(0 0 3px rgba($brand-red, 0.4)) invert(14%) sepia(89%) saturate(4680%) hue-rotate(3deg) brightness(85%) contrast(115%); + } + + .trip-stop__label { + font-weight: 700; + color: var(--brand-text-color); + } + } +} + +@media (prefers-color-scheme: dark) { + .trip-stop__icon { + filter: invert(1); + } + + // In dark mode --dark-bg-color == --light-bg-color, so use a lighter color + // for lines and borders to create contrast against the dark bar background + .trip-track__line, + .trip-track__divider { + background: var(--text-color-faded); + } + + .trip-stop__dot { + border-color: var(--text-color-faded); + } +} + +@media (max-width: $mobile-max-width) { + .trip-progress { + border-radius: 0.5rem; + margin: 0 -0.7rem; + } + + .trip-row { + padding: 0; + gap: 1rem; + flex-wrap: wrap; + } + + .trip-days__label { + width: 45%; + text-align: center; + } + + // Move track below Day 1 & Day 2 labels + .trip-track { + order: 3; + flex-basis: 100%; + max-width: 90%; + margin: auto; + } +} diff --git a/js/articles/ev-road-trip.js b/js/articles/ev-road-trip.js new file mode 100644 index 0000000..ee1de9d --- /dev/null +++ b/js/articles/ev-road-trip.js @@ -0,0 +1,119 @@ +/** + * Trip progress component for the Chicago → Galena EV road trip article. + * Requires Vue 3 to be loaded globally before this script runs. + * + * Each stop's `id` should match a heading ID in the article markup, e.g.: + * ## Departing Chicago {#chicago-start} + */ + +const { createApp, ref, computed, onMounted, onUnmounted } = Vue; + +const STOPS = [ + { label: 'Chicago', id: 'chicago-start', day: 1 }, + { label: 'Rockford', id: 'rockford-day1', day: 1, charging: true }, + { label: 'Mammoth Caves', id: 'galena-arrival', day: 1 }, + { label: 'Mammoth Caves', id: 'galena-day2', day: 2 }, + { label: 'Rockford', id: 'rockford-day2', day: 2, charging: true }, + { label: 'Chicago', id: 'chicago-return', day: 2 }, +]; + +createApp({ + setup() { + const currentStop = ref(0); + + function updateCurrentStop() { + // At the bottom of the page, always activate the last stop + const atBottom = window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 10; + if (atBottom) { + const lastIdx = STOPS.length - 1; + if (lastIdx !== currentStop.value) { + const prev = document.getElementById(STOPS[currentStop.value].id); + if (prev) prev.classList.remove('-active-section'); + const next = document.getElementById(STOPS[lastIdx].id); + if (next) next.classList.add('-active-section'); + currentStop.value = lastIdx; + } + return; + } + + // Trigger when a section heading reaches 40% down the viewport + const threshold = window.scrollY + window.innerHeight * 0.4; + let active = 0; + for (let i = 0; i < STOPS.length; i++) { + const el = document.getElementById(STOPS[i].id); + if (el && el.getBoundingClientRect().top + window.scrollY <= threshold) { + active = i; + } + } + + if (active !== currentStop.value) { + const prev = document.getElementById(STOPS[currentStop.value].id); + if (prev) prev.classList.remove('-active-section'); + const next = document.getElementById(STOPS[active].id); + if (next) next.classList.add('-active-section'); + } + + currentStop.value = active; + } + + onMounted(() => { + window.addEventListener('scroll', updateCurrentStop, { passive: true }); + updateCurrentStop(); + }); + + onUnmounted(() => { + window.removeEventListener('scroll', updateCurrentStop); + }); + + const carLeftPercent = computed(() => + (currentStop.value / (STOPS.length - 1)) * 100 + ); + + function stopLeftPercent(i) { + return (i / (STOPS.length - 1)) * 100; + } + + return { STOPS, currentStop, carLeftPercent, stopLeftPercent }; + }, + + template: ` + + ` +}).mount('#trip-progress'); diff --git a/post-assets/ev-roadtrip/ev_station.svg b/post-assets/ev-roadtrip/ev_station.svg new file mode 100644 index 0000000..34e21a2 --- /dev/null +++ b/post-assets/ev-roadtrip/ev_station.svg @@ -0,0 +1,6 @@ + + diff --git a/post-assets/ev-roadtrip/ioniq5-icon.png b/post-assets/ev-roadtrip/ioniq5-icon.png new file mode 100644 index 0000000..338086e Binary files /dev/null and b/post-assets/ev-roadtrip/ioniq5-icon.png differ diff --git a/post-assets/ev-roadtrip/location_on.svg b/post-assets/ev-roadtrip/location_on.svg new file mode 100644 index 0000000..2e0e6e7 --- /dev/null +++ b/post-assets/ev-roadtrip/location_on.svg @@ -0,0 +1,6 @@ + +