final polish

This commit is contained in:
John Gatward
2026-03-20 18:40:30 +00:00
parent 2646321ad9
commit ed713a931d
3 changed files with 141 additions and 47 deletions

View File

@@ -5,6 +5,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Havox — John Gatward</title> <title>Havox — John Gatward</title>
<meta name="description" content="John Gatward's portfolio: software engineering projects spanning backend systems, infrastructure, and creative developer experiments." />
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link <link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,700;1,400&family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;1,9..144,300&display=swap" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,700;1,400&family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,600;1,9..144,300&display=swap"
@@ -29,32 +30,19 @@
<section id="hero"> <section id="hero">
<p class="hero-eyebrow">// havox.org — v4</p> <p class="hero-eyebrow">// havox.org — v4</p>
<h1 class="hero-name">John <em>Gatward</em></h1> <h1 class="hero-name">John <em>Gatward</em></h1>
<p class="hero-subtitle">Software Developer</p> <p class="hero-subtitle">Software Engineer</p>
<p class="hero-desc"> <p class="hero-desc">
<span class="hero-by-line"><span class="hero-by">Backend engineer</span><span class="hero-by-role">by <span class="hero-by-line"><span class="hero-by">Backend engineer</span><span class="hero-by-role">by
trade</span></span> trade</span></span>
<span class="hero-by-line"><span class="hero-by">Full-stack developer</span><span class="hero-by-role">by <span class="hero-by-line"><span class="hero-by">Developer enthusiast</span><span class="hero-by-role">by
curiosity</span></span> curiosity</span></span>
<span class="hero-by-line"><span class="hero-by">Unofficial family cloud engineer</span><span <span class="hero-by-line"><span class="hero-by">Unofficial family cloud engineer</span><span
class="hero-by-role">by necessity</span></span> class="hero-by-role">by necessity</span></span>
<span class="hero-by-footer">I like building useful things and understanding how they work.</span> <span class="hero-by-footer">I like building useful things and understanding how they work.</span>
</p> </p>
<div class="hero-links"> <div class="hero-links">
<a href="#projects" class="btn btn-primary">see my work</a> <a href="#projects" class="btn btn-primary">View projects</a>
<a href="https://github.com/jayo60013" class="btn btn-ghost" target="_blank" rel="noopener"> <a href="#contact" class="btn btn-ghost">Contact me</a>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0 0 24 12c0-6.63-5.37-12-12-12z" />
</svg>
GitHub
</a>
<a href="https://www.linkedin.com/in/jaygatward" class="btn btn-ghost" target="_blank" rel="noopener">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
<path
d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
LinkedIn
</a>
</div> </div>
</section> </section>
@@ -108,7 +96,7 @@
<div class="v-content"> <div class="v-content">
<div class="v-title">Havox V4 <span class="v-current-badge">current</span></div> <div class="v-title">Havox V4 <span class="v-current-badge">current</span></div>
<div class="v-year">2026 → present</div> <div class="v-year">2026 → present</div>
<div class="v-desc">Catppuccin Mocha. Single page. Trying to get a new job this time.</div> <div class="v-desc">Catppuccin Mocha. Single page. Built to highlight projects and craft.</div>
</div> </div>
</div> </div>
@@ -117,7 +105,7 @@
<div class="v-content"> <div class="v-content">
<div class="v-title">Havox V3 <span class="v-link-arrow"></span></div> <div class="v-title">Havox V3 <span class="v-link-arrow"></span></div>
<div class="v-year">2022 → 2025</div> <div class="v-year">2022 → 2025</div>
<div class="v-desc">Borrowed a template. Wasn't very 'me'.</div> <div class="v-desc">Borrowed a professional looking template. Wasn't very 'me'.</div>
</div> </div>
</a> </a>
@@ -139,7 +127,6 @@
quote-of-the-day achieved via webscraping a site daily.</div> quote-of-the-day achieved via webscraping a site daily.</div>
</div> </div>
</a> </a>
</div> </div>
</div> </div>
@@ -172,8 +159,7 @@
<span class="skill-card-icon">🎨</span> <span class="skill-card-icon">🎨</span>
<h3>Frontend & Creative</h3> <h3>Frontend & Creative</h3>
<p>Comfortable with react and CSS. I enjoy visualising algorithms interactively — it's more fun than <p>Comfortable with react and CSS. I enjoy visualising algorithms interactively — it's more fun than
a a console output.</p>
console output.</p>
<div class="tag-row"> <div class="tag-row">
<span class="tag tag-teal">React</span> <span class="tag tag-teal">React</span>
<span class="tag tag-green">TypeScript</span> <span class="tag tag-green">TypeScript</span>
@@ -216,7 +202,7 @@
<p class="section-label reveal">projects</p> <p class="section-label reveal">projects</p>
<h2 class="section-heading reveal" style="transition-delay:0.05s">Things I <em>built</em></h2> <h2 class="section-heading reveal" style="transition-delay:0.05s">Things I <em>built</em></h2>
<p class="section-intro reveal" style="transition-delay:0.1s"> <p class="section-intro reveal" style="transition-delay:0.1s">
A timeline of all my projects and experiments. Started with p5.js, then the tech-stack mirrored whatever I was interested in at that time. A timeline of projects focused on technical challenge and what each build taught me.
</p> </p>
<div class="projects-subheading reveal" style="transition-delay:0.12s"> <div class="projects-subheading reveal" style="transition-delay:0.12s">
@@ -225,10 +211,10 @@
<div class="card-grid card-grid--featured reveal" style="transition-delay:0.14s"> <div class="card-grid card-grid--featured reveal" style="transition-delay:0.14s">
<a class="card" href="https://wordlesolver.umbra.mom"> <a class="card" href="https://wordlesolver.umbra.mom">
<span class="card-date">9 Feb 2025</span> <span class="card-date">9 Jan 2025</span>
<span class="card-title">Wordle Solver</span> <span class="card-title">Wordle Solver</span>
<span class="card-desc">Built after getting frustrated after one too many missed 3-guess games. Now it <span class="card-desc">Built after getting frustrated after one too many missed 3-guess games. Now it
plays marginally better than me.</span> plays marginally better than me. Uses information theory to recommend the next best guess.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-yellow">Rust</span> <span class="tag tag-yellow">Rust</span>
<span class="tag tag-teal">React</span> <span class="tag tag-teal">React</span>
@@ -239,8 +225,7 @@
<a class="card" href="#projects"> <a class="card" href="#projects">
<span class="card-date">13 Apr 2024</span> <span class="card-date">13 Apr 2024</span>
<span class="card-title">Crack the Quote</span> <span class="card-title">Crack the Quote</span>
<span class="card-desc">A fun word puzzle game focused on breaking a simple substitution cipher from a <span class="card-desc">A substitution-cipher puzzle game with a daily challenge.</span>
daily quote.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-yellow">Rust</span> <span class="tag tag-yellow">Rust</span>
<span class="tag tag-teal">React</span> <span class="tag tag-teal">React</span>
@@ -257,6 +242,7 @@
<fieldset class="project-filters reveal" style="transition-delay:0.18s" aria-label="Filter projects by tech stack"> <fieldset class="project-filters reveal" style="transition-delay:0.18s" aria-label="Filter projects by tech stack">
<button type="button" class="filter-chip active" data-filter="all">All</button> <button type="button" class="filter-chip active" data-filter="all">All</button>
<button type="button" class="filter-chip" data-filter="javascript">JavaScript</button> <button type="button" class="filter-chip" data-filter="javascript">JavaScript</button>
<button type="button" class="filter-chip" data-filter="python">Python</button>
<button type="button" class="filter-chip" data-filter="rust">Rust</button> <button type="button" class="filter-chip" data-filter="rust">Rust</button>
<button type="button" class="filter-chip" data-filter="react">React</button> <button type="button" class="filter-chip" data-filter="react">React</button>
<button type="button" class="filter-chip" data-filter="webassembly">WebAssembly</button> <button type="button" class="filter-chip" data-filter="webassembly">WebAssembly</button>
@@ -266,6 +252,15 @@
</fieldset> </fieldset>
<div id="project-grid" class="card-grid reveal" style="transition-delay:0.2s"> <div id="project-grid" class="card-grid reveal" style="transition-delay:0.2s">
<a class="card project-card" data-tech="python" href="https://pubquiz.umbra.mom">
<span class="card-date">03 Apr 2025</span>
<span class="card-title">Pub Quiz Dashboard</span>
<span class="card-desc">A Python dashboard to track quiz performance.</span>
<span class="tag-row">
<span class="tag tag-red">Python</span>
</span>
<span class="card-arrow"></span>
</a>
<a class="card project-card" data-tech="cpp webassembly" href="projects/thin_ice/thin_ice.html"> <a class="card project-card" data-tech="cpp webassembly" href="projects/thin_ice/thin_ice.html">
<span class="card-date">14 Nov 2024</span> <span class="card-date">14 Nov 2024</span>
<span class="card-title">Thin Ice</span> <span class="card-title">Thin Ice</span>
@@ -280,7 +275,7 @@
<a class="card project-card" data-tech="zig webassembly" href="projects/tsp/index.html"> <a class="card project-card" data-tech="zig webassembly" href="projects/tsp/index.html">
<span class="card-date">20 Mar 2024</span> <span class="card-date">20 Mar 2024</span>
<span class="card-title">Travelling Salesman Problem</span> <span class="card-title">Travelling Salesman Problem</span>
<span class="card-desc">A nearest-neighbor + 2-opt pathfinding visualiser.</span> <span class="card-desc">A nearest-neighbor + 2-opt visualiser.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-yellow">Zig</span> <span class="tag tag-yellow">Zig</span>
<span class="tag tag-sky">WebGL</span> <span class="tag tag-sky">WebGL</span>
@@ -291,7 +286,7 @@
<a class="card project-card" data-tech="rust" href="projects/flocking/index.html"> <a class="card project-card" data-tech="rust" href="projects/flocking/index.html">
<span class="card-date">31 Jan 2024</span> <span class="card-date">31 Jan 2024</span>
<span class="card-title">Flocking</span> <span class="card-title">Flocking</span>
<span class="card-desc">A simulation to show how complex behaviour emerges from 3 simple rules.</span> <span class="card-desc">A boids simulation showing emergent behaviour from three simple rules.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-yellow">Rust</span> <span class="tag tag-yellow">Rust</span>
<span class="tag tag-peach">Raylib</span> <span class="tag tag-peach">Raylib</span>
@@ -321,7 +316,7 @@
<a class="card project-card" data-tech="cpp webassembly" href="projects/percolation/index.html"> <a class="card project-card" data-tech="cpp webassembly" href="projects/percolation/index.html">
<span class="card-date">5 Feb 2022</span> <span class="card-date">5 Feb 2022</span>
<span class="card-title">Percolation</span> <span class="card-title">Percolation</span>
<span class="card-desc">A visual demo of percolation through a medium, showing how behavior changes as p increases.</span> <span class="card-desc">A visual demo of 'water' percolating through a medium, as it disappears.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-blue">C</span> <span class="tag tag-blue">C</span>
<span class="tag tag-peach">RayLib</span> <span class="tag tag-peach">RayLib</span>
@@ -372,7 +367,7 @@
<a class="card project-card" data-tech="javascript p5" href="projects/ellipse_construction.html"> <a class="card project-card" data-tech="javascript p5" href="projects/ellipse_construction.html">
<span class="card-date">23 Feb 2019</span> <span class="card-date">23 Feb 2019</span>
<span class="card-title">Constructing an ellipse</span> <span class="card-title">Constructing an ellipse</span>
<span class="card-desc">Shows how an ellipse can be constructed from a circle and radial lines, inspired by 3Blue1Brown.</span> <span class="card-desc">A geometric construction demo inspired by 3Blue1Brown.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-green">JavaScript</span> <span class="tag tag-green">JavaScript</span>
<span class="tag tag-red">p5.js</span> <span class="tag tag-red">p5.js</span>
@@ -382,7 +377,7 @@
<a class="card project-card" data-tech="javascript p5" href="projects/pi_approximation.html"> <a class="card project-card" data-tech="javascript p5" href="projects/pi_approximation.html">
<span class="card-date">18 Mar 2018</span> <span class="card-date">18 Mar 2018</span>
<span class="card-title">Calculating PI</span> <span class="card-title">Calculating PI</span>
<span class="card-desc">Approximates pi using the Monte Carlo method with random points in a square and circle.</span> <span class="card-desc">A Monte Carlo approximation of pi using random sampling.</span>
<span class="tag-row"> <span class="tag-row">
<span class="tag tag-green">JavaScript</span> <span class="tag tag-green">JavaScript</span>
<span class="tag tag-red">p5.js</span> <span class="tag tag-red">p5.js</span>
@@ -410,6 +405,10 @@
<span class="card-arrow"></span> <span class="card-arrow"></span>
</a> </a>
</div> </div>
<div class="projects-more-row reveal" style="transition-delay:0.22s">
<button id="projects-see-more" type="button" class="projects-more-btn" aria-expanded="false">See more</button>
</div>
</section> </section>
<div class="divider"></div> <div class="divider"></div>
@@ -421,11 +420,11 @@
<div> <div>
<h2>Let's <em>talk</em></h2> <h2>Let's <em>talk</em></h2>
<p> <p>
I'm currently employed but open to the right opportunity — backend or full-stack, somewhere that I am currently employed and open to the right software engineering opportunity, particularly
ships interesting things with people who care about their craft. backend or full-stack roles with teams that value thoughtful delivery and technical quality.
</p> </p>
<p> <p>
GitHub has the most complete picture of how I think. LinkedIn if you're formal. Either works. For professional enquiries, please connect with me on LinkedIn.
</p> </p>
</div> </div>
<div class="contact-links"> <div class="contact-links">
@@ -457,8 +456,7 @@
<footer> <footer>
<span>umbra.mom — John Gatward</span> <span>umbra.mom — John Gatward</span>
<span> <span>
Shoutout to <a href="https://www.youtube.com/user/shiffman" target="_blank" rel="noopener">Daniel Source code repository is also self-hosted: <a href="https://gitea.umbra.mom/jay/havox">repository</a>
Shiffman</a> & the <a href="https://p5js.org" target="_blank" rel="noopener">p5.js team</a> for starting this obsession.
</span> </span>
</footer> </footer>

View File

@@ -26,26 +26,61 @@ window.addEventListener('scroll', () => {
// ─── Project filters ────────────────────────────────────────────── // ─── Project filters ──────────────────────────────────────────────
const filterChips = document.querySelectorAll('.filter-chip'); const filterChips = document.querySelectorAll('.filter-chip');
const projectCards = document.querySelectorAll('#project-grid .project-card'); const projectCards = document.querySelectorAll('#project-grid .project-card');
const seeMoreButton = document.querySelector('#projects-see-more');
const DEFAULT_VISIBLE_PROJECTS = 8;
if (filterChips.length > 0 && projectCards.length > 0) { if (filterChips.length > 0 && projectCards.length > 0) {
const applyFilter = (filter) => { let selectedFilter = 'all';
let isExpanded = false;
const applyFilter = () => {
const visibleCards = [];
projectCards.forEach(card => { projectCards.forEach(card => {
const tech = (card.dataset.tech || '').split(/\s+/).filter(Boolean); const tech = (card.dataset.tech || '').split(/\s+/).filter(Boolean);
const shouldShow = filter === 'all' || tech.includes(filter); const shouldShow = selectedFilter === 'all' || tech.includes(selectedFilter);
card.classList.toggle('is-hidden', !shouldShow); card.classList.toggle('is-hidden', !shouldShow);
if (shouldShow) {
card.classList.remove('is-collapsed');
visibleCards.push(card);
}
}); });
if (!isExpanded) {
visibleCards.slice(DEFAULT_VISIBLE_PROJECTS).forEach(card => {
card.classList.add('is-collapsed');
});
}
if (seeMoreButton) {
const canExpand = visibleCards.length > DEFAULT_VISIBLE_PROJECTS;
seeMoreButton.hidden = !canExpand;
seeMoreButton.setAttribute('aria-expanded', canExpand && isExpanded ? 'true' : 'false');
seeMoreButton.textContent = isExpanded ? 'See less' : 'See more';
}
}; };
filterChips.forEach(chip => { filterChips.forEach(chip => {
chip.addEventListener('click', () => { chip.addEventListener('click', () => {
const selected = chip.dataset.filter || 'all'; selectedFilter = chip.dataset.filter || 'all';
isExpanded = false;
filterChips.forEach(other => { filterChips.forEach(other => {
other.classList.toggle('active', other === chip); other.classList.toggle('active', other === chip);
}); });
applyFilter(selected); applyFilter();
}); });
}); });
if (seeMoreButton) {
seeMoreButton.addEventListener('click', () => {
isExpanded = !isExpanded;
applyFilter();
});
}
applyFilter();
} }

View File

@@ -39,6 +39,32 @@ body {
font-size: 15px; font-size: 15px;
line-height: 1.7; line-height: 1.7;
overflow-x: hidden; overflow-x: hidden;
position: relative;
}
body::before,
body::after {
content: '';
position: fixed;
width: 36rem;
height: 36rem;
border-radius: 50%;
pointer-events: none;
z-index: -1;
filter: blur(70px);
opacity: 0.12;
}
body::before {
top: -10rem;
right: -10rem;
background: radial-gradient(circle, var(--mauve), transparent 65%);
}
body::after {
bottom: -14rem;
left: -12rem;
background: radial-gradient(circle, var(--blue), transparent 65%);
} }
::selection { background: var(--mauve); color: var(--crust); } ::selection { background: var(--mauve); color: var(--crust); }
@@ -252,6 +278,7 @@ section.full-width {
.btn-primary { .btn-primary {
background: var(--mauve); background: var(--mauve);
color: var(--crust); color: var(--crust);
box-shadow: 0 0 0 1px rgba(203,166,247,0.25), 0 10px 26px rgba(17,17,27,0.4);
} }
.btn-primary:hover { background: var(--lavender); transform: translateY(-2px); } .btn-primary:hover { background: var(--lavender); transform: translateY(-2px); }
@@ -309,7 +336,6 @@ section.full-width {
} }
.about-block:last-child { border-bottom: none; } .about-block:last-child { border-bottom: none; }
.about-block:hover { background: var(--surface0); }
.about-block-tag { .about-block-tag {
flex-shrink: 0; flex-shrink: 0;
@@ -469,8 +495,6 @@ section.full-width {
transition: background 0.2s; transition: background 0.2s;
} }
.skill-card:hover { background: var(--surface0); }
.skill-card-icon { .skill-card-icon {
font-size: 1.5rem; font-size: 1.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -527,7 +551,11 @@ section.full-width {
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.section-heading em { font-style: italic; color: var(--mauve); } .section-heading em {
font-style: italic;
color: var(--mauve);
text-shadow: 0 0 16px rgba(203,166,247,0.25);
}
.section-intro { .section-intro {
color: var(--subtext0); color: var(--subtext0);
@@ -556,7 +584,7 @@ section.full-width {
} }
#project-grid { #project-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
} }
#project-grid .card { #project-grid .card {
@@ -602,6 +630,35 @@ section.full-width {
display: none; display: none;
} }
.card.is-collapsed {
display: none;
}
.projects-more-row {
margin-top: 1rem;
display: flex;
justify-content: center;
}
.projects-more-btn {
border: 1px solid var(--surface1);
background: var(--mantle);
color: var(--subtext0);
border-radius: 999px;
padding: 0.42rem 1rem;
font-size: 0.72rem;
letter-spacing: 0.06em;
text-transform: uppercase;
font-family: 'JetBrains Mono', monospace;
cursor: pointer;
transition: all 0.2s;
}
.projects-more-btn:hover {
border-color: var(--mauve);
color: var(--text);
}
/* ─── Card grid ────────────────────────────────────────── */ /* ─── Card grid ────────────────────────────────────────── */
.card-grid { .card-grid {
display: grid; display: grid;
@@ -638,7 +695,10 @@ section.full-width {
transition: transform 0.25s; transition: transform 0.25s;
} }
.card:hover { background: var(--surface0); } .card:hover {
background: var(--surface0);
transform: translateY(-1px);
}
.card:hover::before { transform: scaleY(1); } .card:hover::before { transform: scaleY(1); }
.card-date { .card-date {
@@ -793,6 +853,7 @@ footer a:hover { color: var(--mauve); }
.nav-links { gap: 1.2rem; } .nav-links { gap: 1.2rem; }
section { padding: 6rem 1.5rem 4rem; } section { padding: 6rem 1.5rem 4rem; }
.card-grid.card-grid--featured { grid-template-columns: 1fr; } .card-grid.card-grid--featured { grid-template-columns: 1fr; }
#project-grid { grid-template-columns: 1fr; }
.about-columns { grid-template-columns: 1fr; } .about-columns { grid-template-columns: 1fr; }
.about-block-tag { width: 4rem; } .about-block-tag { width: 4rem; }
.skills-grid { grid-template-columns: 1fr; } .skills-grid { grid-template-columns: 1fr; }