Skip to content

Commit 537c0bd

Browse files
committed
feat(portfolio): add Phalanx Duel and implement dynamic evolution slideshow
1 parent 4ea46c1 commit 537c0bd

2 files changed

Lines changed: 216 additions & 8 deletions

File tree

_data/portfolio.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1+
- id: phalanx-duel
2+
title: Phalanx Duel
3+
years: "2024 - Present"
4+
role: Creator & Architect
5+
description: "A deterministic, tactical card combat game built for the web. Designed to provide a high-bandwidth, strategic experience without the need for downloads or accounts. Focuses on perfect information and pure tactical decision-making."
6+
url: "https://phalanxduel.com"
7+
image: "/assets/images/portfolio/phalanx-duel.png"
8+
tags: ["Game Design", "Product Architecture", "Software Engineering"]
9+
110
- id: chicago-code-camp
211
title: Chicago Code Camp
312
years: "2010 - 2012"
413
role: Co-Organizer & Web Lead
514
description: "A community-focused code camp in the Chicago area. I served as a co-organizer and designed and developed the official event websites and speaker management systems for the 2010-2012 seasons."
615
wayback_url: "https://web.archive.org/web/20121228074319/https://chicagocodecamp.com:80/"
16+
images:
17+
- "/assets/images/portfolio/ccc-2010.png"
18+
- "/assets/images/portfolio/ccc-2011.png"
19+
- "/assets/images/portfolio/ccc-2012.png"
720
tags: ["Community", "Event Planning", "Web Development"]
821

922
- id: ugtastic
@@ -12,6 +25,9 @@
1225
role: Founder & Host
1326
description: "A technical media platform dedicated to capturing high-bandwidth technical conversations. Produced over 200 interviews with industry leaders at the start of the DevOps and Craftsmanship movements."
1427
wayback_url: "https://web.archive.org/web/20120414040704/https://www.ugtastic.com/"
28+
images:
29+
- "/assets/images/portfolio/ugtastic-v1.png"
30+
- "/assets/images/portfolio/ugtastic-v2.png"
1531
tags: ["Technical Media", "Interviewing", "Content Strategy"]
1632

1733
- id: ugl-st
@@ -20,4 +36,5 @@
2036
role: Creator
2137
description: "The User-Group List. I built this project to help User-Group organizers promote their communities and bridge the gap between physical conference interactions and digital discovery."
2238
wayback_url: "https://web.archive.org/web/20140111160057/http://ugl.st/"
39+
image: "/assets/images/portfolio/ugl-st.png"
2340
tags: ["Product Design", "Developer Tools", "Community"]

portfolio.html

Lines changed: 199 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,39 @@
11
---
22
layout: minimal
33
title: Mike Hall - Portfolio
4-
description: A collection of historical projects, community platforms, and engineering work preserved via the Wayback Machine.
4+
description: A collection of historical projects, community platforms, and engineering work. Features a dynamic visual timeline of site evolution.
55
breadcrumb: Portfolio
66
permalink: /portfolio/
77
---
88

99
<article class="page">
1010
<header class="home-header">
11-
<h1>Project Portfolio <span class="subtitle">Digital Archeology</span></h1>
11+
<h1>Project Portfolio <span class="subtitle">Digital Archeology & Modern Games</span></h1>
1212
<h2 class="title">A career built on community, platform architecture, and technical media.</h2>
13-
<p class="home-intro">This portfolio showcases key projects and platforms I've designed, built, and led over the years. Many of these are preserved here via the Wayback Machine, documenting the evolution of my work in the technical community.</p>
13+
<p class="home-intro">This portfolio showcases key projects and platforms I've designed, built, and led. From deterministic card combat to historical community archives, this is a look at the evolution of my work.</p>
1414
</header>
1515

1616
<div class="editorial-index-grid">
1717
{% for project in site.data.portfolio %}
18-
<article class="editorial-card portfolio-card">
19-
{% if project.image %}
20-
<a class="editorial-card-thumb" href="{{ project.wayback_url }}" target="_blank" rel="noopener">
18+
<article class="editorial-card portfolio-card" id="project-{{ project.id }}">
19+
{% if project.images %}
20+
<div class="portfolio-slideshow" data-project="{{ project.id }}">
21+
<div class="slideshow-track">
22+
{% for img in project.images %}
23+
<img src="{{ img }}" alt="{{ project.title }} Evolution" class="slide {% if forloop.first %}is-active{% endif %}" data-index="{{ forloop.index0 }}">
24+
{% endfor %}
25+
</div>
26+
<div class="slideshow-controls">
27+
<button class="slide-prev" aria-label="Previous Slide"></button>
28+
<button class="slide-next" aria-label="Next Slide"></button>
29+
</div>
30+
</div>
31+
{% elsif project.image %}
32+
<div class="editorial-card-thumb">
2133
<img src="{{ project.image }}" alt="Screenshot for {{ project.title }}" loading="lazy">
22-
</a>
34+
</div>
2335
{% endif %}
36+
2437
<div class="editorial-card-body">
2538
<div class="editorial-card-meta">
2639
<span>{{ project.years }}</span>
@@ -39,26 +52,204 @@ <h3 class="editorial-card-title">
3952
{% endfor %}
4053
</div>
4154
<div class="editorial-card-actions">
42-
<a class="editorial-card-button" href="{{ project.wayback_url }}" target="_blank" rel="noopener">Explore on Wayback</a>
55+
{% if project.url %}
56+
<a class="editorial-card-button" href="{{ project.url }}" target="_blank" rel="noopener">Visit Site</a>
57+
{% endif %}
58+
{% if project.wayback_url %}
59+
<a class="editorial-card-button" href="{{ project.wayback_url }}" target="_blank" rel="noopener">Explore Archive</a>
60+
{% endif %}
4361
</div>
4462
</div>
4563
</article>
4664
{% endfor %}
4765
</div>
4866
</article>
4967

68+
<div id="gallery-modal" class="gallery-modal" aria-hidden="true">
69+
<span class="modal-close">×</span>
70+
<div class="modal-content">
71+
<img id="modal-img" src="" alt="Expanded Preview">
72+
</div>
73+
</div>
74+
5075
<style>
5176
.portfolio-card {
5277
border-top: 4px solid var(--color-primary, #007bff);
78+
overflow: hidden;
5379
}
80+
5481
.project-tags-row {
5582
display: flex;
5683
gap: 0.5rem;
5784
flex-wrap: wrap;
5885
margin-bottom: 1.5rem;
5986
}
87+
6088
.project-tags-row .related-tag {
6189
margin-bottom: 0;
6290
font-size: 0.6rem;
6391
}
92+
93+
/* Slideshow CSS */
94+
.portfolio-slideshow {
95+
position: relative;
96+
width: 100%;
97+
padding-top: 56.25%; /* 16:9 */
98+
background: #000;
99+
cursor: pointer;
100+
}
101+
102+
.slideshow-track {
103+
position: absolute;
104+
top: 0;
105+
left: 0;
106+
width: 100%;
107+
height: 100%;
108+
}
109+
110+
.slide {
111+
position: absolute;
112+
top: 0;
113+
left: 0;
114+
width: 100%;
115+
height: 100%;
116+
object-fit: cover;
117+
opacity: 0;
118+
transition: opacity 0.5s ease;
119+
}
120+
121+
.slide.is-active {
122+
opacity: 1;
123+
}
124+
125+
.slideshow-controls {
126+
position: absolute;
127+
bottom: 1rem;
128+
right: 1rem;
129+
display: flex;
130+
gap: 0.5rem;
131+
z-index: 10;
132+
}
133+
134+
.slideshow-controls button {
135+
background: rgba(0, 0, 0, 0.6);
136+
color: white;
137+
border: none;
138+
width: 30px;
139+
height: 30px;
140+
border-radius: 50%;
141+
cursor: pointer;
142+
font-size: 0.8rem;
143+
display: flex;
144+
align-items: center;
145+
justify-content: center;
146+
backdrop-filter: blur(4px);
147+
transition: background 0.2s;
148+
}
149+
150+
.slideshow-controls button:hover {
151+
background: var(--color-primary, #007bff);
152+
}
153+
154+
/* Modal/Gallery CSS */
155+
.gallery-modal {
156+
display: none;
157+
position: fixed;
158+
z-index: 1000;
159+
top: 0;
160+
left: 0;
161+
width: 100%;
162+
height: 100%;
163+
background: rgba(0,0,0,0.9);
164+
align-items: center;
165+
justify-content: center;
166+
}
167+
168+
.gallery-modal[aria-hidden="false"] {
169+
display: flex;
170+
}
171+
172+
.modal-close {
173+
position: absolute;
174+
top: 2rem;
175+
right: 2rem;
176+
color: white;
177+
font-size: 3rem;
178+
cursor: pointer;
179+
}
180+
181+
.modal-content img {
182+
max-width: 90vw;
183+
max-height: 90vh;
184+
border-radius: 8px;
185+
box-shadow: 0 0 50px rgba(0,0,0,0.5);
186+
}
64187
</style>
188+
189+
<script>
190+
document.addEventListener('DOMContentLoaded', () => {
191+
const slideshows = document.querySelectorAll('.portfolio-slideshow');
192+
const modal = document.getElementById('gallery-modal');
193+
const modalImg = document.getElementById('modal-img');
194+
const closeBtn = document.querySelector('.modal-close');
195+
196+
slideshows.forEach(slideshow => {
197+
const slides = slideshow.querySelectorAll('.slide');
198+
let currentIndex = 0;
199+
let interval;
200+
201+
const showSlide = (index) => {
202+
slides.forEach(s => s.classList.remove('is-active'));
203+
slides[index].classList.add('is-active');
204+
currentIndex = index;
205+
};
206+
207+
const nextSlide = (e) => {
208+
if (e) e.stopPropagation();
209+
let next = (currentIndex + 1) % slides.length;
210+
showSlide(next);
211+
};
212+
213+
const prevSlide = (e) => {
214+
if (e) e.stopPropagation();
215+
let prev = (currentIndex - 1 + slides.length) % slides.length;
216+
showSlide(prev);
217+
};
218+
219+
// Auto-play
220+
const startAutoPlay = () => {
221+
interval = setInterval(nextSlide, 4000);
222+
};
223+
224+
const stopAutoPlay = () => {
225+
clearInterval(interval);
226+
};
227+
228+
startAutoPlay();
229+
230+
// Controls
231+
slideshow.querySelector('.slide-next').addEventListener('click', nextSlide);
232+
slideshow.querySelector('.slide-prev').addEventListener('click', prevSlide);
233+
234+
// Modal expansion
235+
slideshow.addEventListener('click', (e) => {
236+
if (e.target.tagName === 'BUTTON') return;
237+
const activeImg = slides[currentIndex];
238+
modalImg.src = activeImg.src;
239+
modal.setAttribute('aria-hidden', 'false');
240+
stopAutoPlay();
241+
});
242+
243+
slideshow.addEventListener('mouseenter', stopAutoPlay);
244+
slideshow.addEventListener('mouseleave', startAutoPlay);
245+
});
246+
247+
closeBtn.addEventListener('click', () => {
248+
modal.setAttribute('aria-hidden', 'true');
249+
});
250+
251+
modal.addEventListener('click', (e) => {
252+
if (e.target === modal) modal.setAttribute('aria-hidden', 'true');
253+
});
254+
});
255+
</script>

0 commit comments

Comments
 (0)