+
+
Let's talk
+
+ I am currently employed and open to the right software engineering opportunity, particularly
+ backend or full-stack roles with teams that value thoughtful delivery and technical quality.
+
+
+ For professional enquiries, please connect with me on LinkedIn.
+
-
-
- See more
-
-
-
-
-
-
-
-
-
- umbra.mom — John Gatward
-
- Source code repository is also self-hosted: repository
+
+
+ umbra.mom — John Gatward
+
+ Source code (self‑hosted): repository
-
+
-
+
diff --git a/script.js b/script.js
index a5f9972..77b4b27 100644
--- a/script.js
+++ b/script.js
@@ -1,26 +1,26 @@
// ─── Scroll reveal ─────────────────────────────────────────
const reveals = document.querySelectorAll('.reveal');
const observer = new IntersectionObserver((entries) => {
- entries.forEach(e => {
- if (e.isIntersecting) {
- e.target.classList.add('visible');
- observer.unobserve(e.target);
- }
- });
-}, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' });
+ entries.forEach(e => {
+ if (e.isIntersecting) {
+ e.target.classList.add('visible');
+ observer.unobserve(e.target);
+ }
+ });
+}, {threshold: 0.1, rootMargin: '0px 0px -40px 0px'});
reveals.forEach(el => observer.observe(el));
// ─── Active nav link on scroll ──────────────────────────────
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('.nav-links a');
window.addEventListener('scroll', () => {
- let current = '';
- sections.forEach(s => {
- if (window.scrollY >= s.offsetTop - 120) current = s.id;
- });
- navLinks.forEach(a => {
- a.style.color = a.getAttribute('href') === `#${current}` ? 'var(--mauve)' : '';
- });
+ let current = '';
+ sections.forEach(s => {
+ if (window.scrollY >= s.offsetTop - 120) current = s.id;
+ });
+ navLinks.forEach(a => {
+ a.style.color = a.getAttribute('href') === `#${current}` ? 'var(--mauve)' : '';
+ });
});
// ─── Project filters ──────────────────────────────────────────────
@@ -30,57 +30,57 @@ const seeMoreButton = document.querySelector('#projects-see-more');
const DEFAULT_VISIBLE_PROJECTS = 8;
if (filterChips.length > 0 && projectCards.length > 0) {
- let selectedFilter = 'all';
- let isExpanded = false;
+ let selectedFilter = 'all';
+ let isExpanded = false;
- const applyFilter = () => {
- const visibleCards = [];
+ const applyFilter = () => {
+ const visibleCards = [];
- projectCards.forEach(card => {
- const tech = (card.dataset.tech || '').split(/\s+/).filter(Boolean);
- const shouldShow = selectedFilter === 'all' || tech.includes(selectedFilter);
- card.classList.toggle('is-hidden', !shouldShow);
+ projectCards.forEach(card => {
+ const tech = (card.dataset.tech || '').split(/\s+/).filter(Boolean);
+ const shouldShow = selectedFilter === 'all' || tech.includes(selectedFilter);
+ card.classList.toggle('is-hidden', !shouldShow);
- if (shouldShow) {
- card.classList.remove('is-collapsed');
- visibleCards.push(card);
- }
+ 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 => {
+ chip.addEventListener('click', () => {
+ selectedFilter = chip.dataset.filter || 'all';
+ isExpanded = false;
+
+ filterChips.forEach(other => {
+ other.classList.toggle('active', other === chip);
+ });
+
+ applyFilter();
+ });
});
- 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';
+ seeMoreButton.addEventListener('click', () => {
+ isExpanded = !isExpanded;
+ applyFilter();
+ });
}
- };
- filterChips.forEach(chip => {
- chip.addEventListener('click', () => {
- selectedFilter = chip.dataset.filter || 'all';
- isExpanded = false;
-
- filterChips.forEach(other => {
- other.classList.toggle('active', other === chip);
- });
-
- applyFilter();
- });
- });
-
- if (seeMoreButton) {
- seeMoreButton.addEventListener('click', () => {
- isExpanded = !isExpanded;
- applyFilter();
- });
- }
-
- applyFilter();
+ applyFilter();
}
diff --git a/style.css b/style.css
index 601f2e3..f821d55 100644
--- a/style.css
+++ b/style.css
@@ -1,864 +1,1015 @@
/* ─── Catppuccin Mocha ─────────────────────────────────── */
:root {
- --base: #1e1e2e;
- --mantle: #181825;
- --crust: #11111b;
- --surface0:#313244;
- --surface1:#45475a;
- --surface2:#585b70;
- --overlay0:#6c7086;
- --overlay1:#7f849c;
- --overlay2:#9399b2;
- --text: #cdd6f4;
- --subtext0:#a6adc8;
- --subtext1:#bac2de;
- --lavender:#b4befe;
- --blue: #89b4fa;
- --sapphire:#74c7ec;
- --sky: #89dceb;
- --teal: #94e2d5;
- --green: #a6e3a1;
- --yellow: #f9e2af;
- --peach: #fab387;
- --maroon: #eba0ac;
- --red: #f38ba8;
- --mauve: #cba6f7;
- --pink: #f5c2e7;
- --flamingo:#f2cdcd;
- --rosewater:#f5e0dc;
+ --base: #1e1e2e;
+ --mantle: #181825;
+ --crust: #11111b;
+ --surface0: #313244;
+ --surface1: #45475a;
+ --surface2: #585b70;
+ --overlay0: #6c7086;
+ --overlay1: #7f849c;
+ --overlay2: #9399b2;
+ --text: #cdd6f4;
+ --subtext0: #a6adc8;
+ --subtext1: #bac2de;
+ --lavender: #b4befe;
+ --blue: #89b4fa;
+ --sapphire: #74c7ec;
+ --sky: #89dceb;
+ --teal: #94e2d5;
+ --green: #a6e3a1;
+ --yellow: #f9e2af;
+ --peach: #fab387;
+ --maroon: #eba0ac;
+ --red: #f38ba8;
+ --mauve: #cba6f7;
+ --pink: #f5c2e7;
+ --flamingo: #f2cdcd;
+ --rosewater: #f5e0dc;
}
-*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+*, *::before, *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
-html { scroll-behavior: smooth; }
+html {
+ scroll-behavior: smooth;
+}
body {
- background: var(--base);
- color: var(--text);
- font-family: 'JetBrains Mono', 'Apple Color Emoji', monospace;
- font-size: 15px;
- line-height: 1.7;
- overflow-x: hidden;
- position: relative;
+ background: var(--base);
+ color: var(--text);
+ font-family: 'JetBrains Mono', 'Apple Color Emoji', monospace;
+ font-size: 15px;
+ line-height: 1.7;
+ 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;
+ 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%);
+ 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%);
+ 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);
+}
/* ─── Scrollbar ────────────────────────────────────────── */
-::-webkit-scrollbar { width: 6px; }
-::-webkit-scrollbar-track { background: var(--mantle); }
-::-webkit-scrollbar-thumb { background: var(--surface1); border-radius: 3px; }
-::-webkit-scrollbar-thumb:hover { background: var(--mauve); }
+::-webkit-scrollbar {
+ width: 6px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--mantle);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--surface1);
+ border-radius: 3px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--mauve);
+}
/* ─── Nav ──────────────────────────────────────────────── */
nav {
- position: fixed;
- top: 0; left: 0; right: 0;
- z-index: 100;
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 0 3rem;
- height: 56px;
- background: rgba(17, 17, 27, 0.85);
- backdrop-filter: blur(12px);
- border-bottom: 1px solid var(--surface0);
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 3rem;
+ height: 56px;
+ background: rgba(17, 17, 27, 0.85);
+ backdrop-filter: blur(12px);
+ border-bottom: 1px solid var(--surface0);
}
.nav-logo {
- font-family: 'Fraunces', serif;
- font-size: 1.3rem;
- color: var(--mauve);
- text-decoration: none;
- letter-spacing: 0.02em;
+ font-family: 'Fraunces', serif;
+ font-size: 1.3rem;
+ color: var(--mauve);
+ text-decoration: none;
+ letter-spacing: 0.02em;
}
.nav-links {
- display: flex;
- gap: 2.5rem;
- list-style: none;
+ display: flex;
+ gap: 2.5rem;
+ list-style: none;
}
.nav-links a {
- color: var(--subtext0);
- text-decoration: none;
- font-size: 0.78rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- transition: color 0.2s;
- position: relative;
+ color: var(--subtext0);
+ text-decoration: none;
+ font-size: 0.78rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ transition: color 0.2s;
+ position: relative;
}
.nav-links a::after {
- content: '';
- position: absolute;
- bottom: -3px; left: 0; right: 0;
- height: 1px;
- background: var(--mauve);
- transform: scaleX(0);
- transform-origin: left;
- transition: transform 0.2s;
+ content: '';
+ position: absolute;
+ bottom: -3px;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: var(--mauve);
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 0.2s;
}
-.nav-links a:hover { color: var(--text); }
-.nav-links a:hover::after { transform: scaleX(1); }
+.nav-links a:hover {
+ color: var(--text);
+}
+
+.nav-links a:hover::after {
+ transform: scaleX(1);
+}
/* ─── Sections ─────────────────────────────────────────── */
section {
- min-height: 100vh;
- padding: 7rem 3rem 5rem;
- max-width: 1100px;
- margin: 0 auto;
+ min-height: 100vh;
+ padding: 7rem 3rem 5rem;
+ max-width: 1100px;
+ margin: 0 auto;
}
section.full-width {
- max-width: none;
- padding-left: 0;
- padding-right: 0;
+ max-width: none;
+ padding-left: 0;
+ padding-right: 0;
}
/* ─── Section labels ───────────────────────────────────── */
.section-label {
- font-size: 0.7rem;
- letter-spacing: 0.2em;
- text-transform: uppercase;
- color: var(--mauve);
- margin-bottom: 0.5rem;
- display: flex;
- align-items: center;
- gap: 0.75rem;
+ font-size: 0.7rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--mauve);
+ margin-bottom: 0.5rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
}
.section-label::after {
- content: '';
- display: block;
- height: 1px;
- width: 3rem;
- background: var(--mauve);
- opacity: 0.5;
+ content: '';
+ display: block;
+ height: 1px;
+ width: 3rem;
+ background: var(--mauve);
+ opacity: 0.5;
}
/* ─── Hero ─────────────────────────────────────────────── */
#hero {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- justify-content: center;
- padding-top: 5rem;
- position: relative;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding-top: 5rem;
+ position: relative;
}
.hero-eyebrow {
- font-size: 0.75rem;
- letter-spacing: 0.25em;
- text-transform: uppercase;
- color: var(--green);
- margin-bottom: 1.5rem;
- opacity: 0;
- animation: fadeUp 0.6s 0.2s forwards;
+ font-size: 0.75rem;
+ letter-spacing: 0.25em;
+ text-transform: uppercase;
+ color: var(--green);
+ margin-bottom: 1.5rem;
+ opacity: 0;
+ animation: fadeUp 0.6s 0.2s forwards;
}
.hero-name {
- font-family: 'Fraunces', serif;
- font-size: clamp(3.5rem, 9vw, 7.5rem);
- font-weight: 600;
- line-height: 1;
- color: var(--text);
- margin-bottom: 0.5rem;
- opacity: 0;
- animation: fadeUp 0.7s 0.35s forwards;
+ font-family: 'Fraunces', serif;
+ font-size: clamp(3.5rem, 9vw, 7.5rem);
+ font-weight: 600;
+ line-height: 1;
+ color: var(--text);
+ margin-bottom: 0.5rem;
+ opacity: 0;
+ animation: fadeUp 0.7s 0.35s forwards;
}
.hero-name em {
- font-style: italic;
- color: var(--mauve);
+ font-style: italic;
+ color: var(--mauve);
}
.hero-subtitle {
- font-family: 'Fraunces', serif;
- font-size: clamp(1.2rem, 3vw, 2.2rem);
- font-weight: 300;
- color: var(--subtext1);
- margin-bottom: 2rem;
- opacity: 0;
- animation: fadeUp 0.7s 0.5s forwards;
+ font-family: 'Fraunces', serif;
+ font-size: clamp(1.2rem, 3vw, 2.2rem);
+ font-weight: 300;
+ color: var(--subtext1);
+ margin-bottom: 2rem;
+ opacity: 0;
+ animation: fadeUp 0.7s 0.5s forwards;
}
.hero-desc {
- max-width: 560px;
- color: var(--subtext0);
- font-size: 0.9rem;
- line-height: 1.8;
- margin-bottom: 2.5rem;
- opacity: 0;
- animation: fadeUp 0.7s 0.65s forwards;
+ max-width: 560px;
+ color: var(--subtext0);
+ font-size: 0.9rem;
+ line-height: 1.8;
+ margin-bottom: 2.5rem;
+ opacity: 0;
+ animation: fadeUp 0.7s 0.65s forwards;
}
.hero-by-line {
- display: flex;
- align-items: baseline;
- gap: 0.6rem;
- margin-bottom: 0.3rem;
+ display: flex;
+ align-items: baseline;
+ gap: 0.6rem;
+ margin-bottom: 0.3rem;
}
.hero-by {
- color: var(--text);
- font-size: 0.9rem;
+ color: var(--text);
+ font-size: 0.9rem;
}
.hero-by-role {
- font-family: 'Fraunces', serif;
- font-style: italic;
- font-size: 0.85rem;
- color: var(--mauve);
- opacity: 0.8;
+ font-family: 'Fraunces', serif;
+ font-style: italic;
+ font-size: 0.85rem;
+ color: var(--mauve);
+ opacity: 0.8;
}
.hero-by-role::before {
- content: '— ';
- opacity: 0.4;
+ content: '— ';
+ opacity: 0.4;
}
.hero-by-footer {
- display: block;
- margin-top: 1rem;
- color: var(--subtext0);
- font-size: 0.82rem;
- opacity: 0.75;
+ display: block;
+ margin-top: 1rem;
+ color: var(--subtext0);
+ font-size: 0.82rem;
+ opacity: 0.75;
}
.hero-links {
- display: flex;
- gap: 1.25rem;
- flex-wrap: wrap;
- opacity: 0;
- animation: fadeUp 0.7s 0.8s forwards;
+ display: flex;
+ gap: 1.25rem;
+ flex-wrap: wrap;
+ opacity: 0;
+ animation: fadeUp 0.7s 0.8s forwards;
}
.btn {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.6rem 1.4rem;
- border-radius: 4px;
- font-family: 'JetBrains Mono', monospace;
- font-size: 0.78rem;
- letter-spacing: 0.05em;
- text-decoration: none;
- transition: all 0.2s;
- cursor: pointer;
- border: none;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.6rem 1.4rem;
+ border-radius: 4px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.78rem;
+ letter-spacing: 0.05em;
+ text-decoration: none;
+ transition: all 0.2s;
+ cursor: pointer;
+ border: none;
}
.btn-primary {
- background: var(--mauve);
- color: var(--crust);
- box-shadow: 0 0 0 1px rgba(203,166,247,0.25), 0 10px 26px rgba(17,17,27,0.4);
+ background: var(--mauve);
+ 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); }
.btn-ghost {
- background: transparent;
- color: var(--text);
- border: 1px solid var(--surface1);
+ background: transparent;
+ color: var(--text);
+ border: 1px solid var(--surface1);
+}
+
+.btn-ghost:hover {
+ border-color: var(--mauve);
+ color: var(--mauve);
+ transform: translateY(-2px);
}
-.btn-ghost:hover { border-color: var(--mauve); color: var(--mauve); transform: translateY(-2px); }
/* Decorative grid bg */
#hero::before {
- content: '';
- position: absolute;
- inset: 0;
- background-image:
- linear-gradient(var(--surface0) 1px, transparent 1px),
+ content: '';
+ position: absolute;
+ inset: 0;
+ background-image: linear-gradient(var(--surface0) 1px, transparent 1px),
linear-gradient(90deg, var(--surface0) 1px, transparent 1px);
- background-size: 60px 60px;
- opacity: 0.18;
- mask-image: radial-gradient(ellipse 80% 60% at 50% 50%, black, transparent);
- pointer-events: none;
+ background-size: 60px 60px;
+ opacity: 0.18;
+ mask-image: radial-gradient(ellipse 80% 60% at 50% 50%, black, transparent);
+ pointer-events: none;
}
/* ─── About ────────────────────────────────────────────── */
.about-section {
- min-height: auto;
- padding-bottom: 5rem;
+ min-height: auto;
+ padding-bottom: 5rem;
}
.about-columns {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 2rem;
- margin-top: 2.5rem;
- align-items: start;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+ margin-top: 2.5rem;
+ align-items: start;
}
.about-grid {
- display: flex;
- flex-direction: column;
- border: 1px solid var(--surface0);
- border-radius: 8px;
- overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--surface0);
+ border-radius: 8px;
+ overflow: hidden;
}
.about-block {
- background: var(--mantle);
- padding: 1.25rem 1.5rem;
- border-bottom: 1px solid var(--surface0);
- transition: background 0.2s;
- display: flex;
- align-items: baseline;
- gap: 1.25rem;
+ background: var(--mantle);
+ padding: 1.25rem 1.5rem;
+ border-bottom: 1px solid var(--surface0);
+ transition: background 0.2s;
+ display: flex;
+ align-items: baseline;
+ gap: 1.25rem;
}
-.about-block:last-child { border-bottom: none; }
+.about-block:last-child {
+ border-bottom: none;
+}
.about-block-tag {
- flex-shrink: 0;
- font-size: 0.65rem;
- letter-spacing: 0.1em;
- text-transform: uppercase;
- width: 5rem;
- text-align: center;
+ flex-shrink: 0;
+ font-size: 0.65rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ width: 5rem;
+ text-align: center;
}
.about-block p {
- color: var(--subtext0);
- font-size: 0.85rem;
- line-height: 1.75;
+ color: var(--subtext0);
+ font-size: 0.85rem;
+ line-height: 1.75;
}
-.about-block p strong { color: var(--text); font-weight: 700; }
-.about-block p em { color: var(--mauve); font-style: italic; }
-.about-block p s { color: var(--overlay1); }
+.about-block p strong {
+ color: var(--text);
+ font-weight: 700;
+}
+
+.about-block p em {
+ color: var(--mauve);
+ font-style: italic;
+}
+
+.about-block p s {
+ color: var(--overlay1);
+}
.version-timeline {
- display: flex;
- flex-direction: column;
- gap: 0;
- position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+ position: relative;
}
.version-timeline::before {
- content: '';
- position: absolute;
- left: 0.65rem;
- top: 0.5rem;
- bottom: 0.5rem;
- width: 1px;
- background: var(--surface1);
+ content: '';
+ position: absolute;
+ left: 0.65rem;
+ top: 0.5rem;
+ bottom: 0.5rem;
+ width: 1px;
+ background: var(--surface1);
}
.version-item {
- display: flex;
- gap: 1.25rem;
- padding: 1rem 0;
- position: relative;
+ display: flex;
+ gap: 1.25rem;
+ padding: 1rem 0;
+ position: relative;
}
.v-dot {
- width: 1.3rem;
- height: 1.3rem;
- border-radius: 50%;
- background: var(--surface0);
- border: 2px solid var(--surface1);
- flex-shrink: 0;
- margin-top: 0.15rem;
- transition: border-color 0.2s, background 0.2s;
- position: relative;
- z-index: 1;
+ width: 1.3rem;
+ height: 1.3rem;
+ border-radius: 50%;
+ background: var(--surface0);
+ border: 2px solid var(--surface1);
+ flex-shrink: 0;
+ margin-top: 0.15rem;
+ transition: border-color 0.2s, background 0.2s;
+ position: relative;
+ z-index: 1;
}
.version-item:hover .v-dot {
- border-color: var(--mauve);
- background: var(--mauve);
+ border-color: var(--mauve);
+ background: var(--mauve);
}
.v-title {
- font-size: 0.85rem;
- font-weight: 700;
- color: var(--text);
- margin-bottom: 0.15rem;
+ font-size: 0.85rem;
+ font-weight: 700;
+ color: var(--text);
+ margin-bottom: 0.15rem;
}
.v-year {
- font-size: 0.7rem;
- color: var(--mauve);
- margin-bottom: 0.3rem;
- letter-spacing: 0.05em;
+ font-size: 0.7rem;
+ color: var(--mauve);
+ margin-bottom: 0.3rem;
+ letter-spacing: 0.05em;
}
.v-desc {
- font-size: 0.8rem;
- color: var(--subtext0);
- line-height: 1.6;
+ font-size: 0.8rem;
+ color: var(--subtext0);
+ line-height: 1.6;
}
.version-item--link {
- text-decoration: none;
- color: inherit;
- cursor: pointer;
+ text-decoration: none;
+ color: inherit;
+ cursor: pointer;
}
.version-item--link:hover .v-dot {
- border-color: var(--mauve);
- background: var(--mauve);
+ border-color: var(--mauve);
+ background: var(--mauve);
}
.version-item--link:hover .v-title {
- color: var(--mauve);
+ color: var(--mauve);
}
.v-link-arrow {
- font-size: 0.7rem;
- color: var(--overlay1);
- margin-left: 0.3rem;
- transition: color 0.2s, transform 0.2s;
- display: inline-block;
+ font-size: 0.7rem;
+ color: var(--overlay1);
+ margin-left: 0.3rem;
+ transition: color 0.2s, transform 0.2s;
+ display: inline-block;
}
.version-item--link:hover .v-link-arrow {
- color: var(--mauve);
- transform: translate(2px, -2px);
+ color: var(--mauve);
+ transform: translate(2px, -2px);
}
.v-dot--current {
- border-color: var(--mauve);
- background: var(--mauve);
+ border-color: var(--mauve);
+ background: var(--mauve);
}
.v-current-badge {
- font-size: 0.6rem;
- letter-spacing: 0.08em;
- text-transform: uppercase;
- background: rgba(203,166,247,0.15);
- color: var(--mauve);
- padding: 0.1rem 0.45rem;
- border-radius: 3px;
- margin-left: 0.4rem;
- vertical-align: middle;
- font-weight: 700;
+ font-size: 0.6rem;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ background: rgba(203, 166, 247, 0.15);
+ color: var(--mauve);
+ padding: 0.1rem 0.45rem;
+ border-radius: 3px;
+ margin-left: 0.4rem;
+ vertical-align: middle;
+ font-weight: 700;
}
/* ─── Skills ───────────────────────────────────────────── */
#skills h2 {
- font-family: 'Fraunces', serif;
- font-size: 2.8rem;
- font-weight: 600;
- color: var(--text);
- margin-bottom: 3rem;
+ font-family: 'Fraunces', serif;
+ font-size: 2.8rem;
+ font-weight: 600;
+ color: var(--text);
+ margin-bottom: 3rem;
}
-#skills h2 em { font-style: italic; color: var(--mauve); }
+#skills h2 em {
+ font-style: italic;
+ color: var(--mauve);
+}
.skills-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 1px;
- background: var(--surface0);
- border: 1px solid var(--surface0);
- border-radius: 8px;
- overflow: hidden;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1px;
+ background: var(--surface0);
+ border: 1px solid var(--surface0);
+ border-radius: 8px;
+ overflow: hidden;
}
.skill-card--wide {
- grid-column: 1 / -1;
+ grid-column: 1 / -1;
}
.skill-card {
- background: var(--mantle);
- padding: 2rem;
- transition: background 0.2s;
+ background: var(--mantle);
+ padding: 2rem;
+ transition: background 0.2s;
}
.skill-card-icon {
- font-size: 1.5rem;
- margin-bottom: 1rem;
- display: block;
+ font-size: 1.5rem;
+ margin-bottom: 1rem;
+ display: block;
}
.skill-card h3 {
- font-size: 0.85rem;
- font-weight: 700;
- color: var(--text);
- margin-bottom: 0.5rem;
- letter-spacing: 0.05em;
- text-transform: uppercase;
+ font-size: 0.85rem;
+ font-weight: 700;
+ color: var(--text);
+ margin-bottom: 0.5rem;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
}
.skill-card p {
- font-size: 0.82rem;
- color: var(--subtext0);
- line-height: 1.7;
+ font-size: 0.82rem;
+ color: var(--subtext0);
+ line-height: 1.7;
}
.tag-row {
- display: flex;
- flex-wrap: wrap;
- gap: 0.4rem;
- margin-top: 1rem;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ margin-top: 1rem;
}
.tag {
- display: inline-block;
- padding: 0.15rem 0.6rem;
- border-radius: 3px;
- font-size: 0.7rem;
- letter-spacing: 0.04em;
- font-weight: 700;
+ display: inline-block;
+ padding: 0.15rem 0.6rem;
+ border-radius: 3px;
+ font-size: 0.7rem;
+ letter-spacing: 0.04em;
+ font-weight: 700;
}
-.tag-blue { background: rgba(137,180,250,0.12); color: var(--blue); }
-.tag-green { background: rgba(166,227,161,0.12); color: var(--green); }
-.tag-peach { background: rgba(250,179,135,0.12); color: var(--peach); }
-.tag-teal { background: rgba(148,226,213,0.12); color: var(--teal); }
-.tag-mauve { background: rgba(203,166,247,0.12); color: var(--mauve); }
-.tag-yellow { background: rgba(249,226,175,0.12); color: var(--yellow); }
-.tag-sky { background: rgba(137,220,235,0.12); color: var(--sky); }
-.tag-red { background: rgba(243,139,168,0.12); color: var(--red); }
-.tag-pink { background: rgba(245,194,231,0.12); color: var(--pink); }
+.tag-blue {
+ background: rgba(137, 180, 250, 0.12);
+ color: var(--blue);
+}
+
+.tag-green {
+ background: rgba(166, 227, 161, 0.12);
+ color: var(--green);
+}
+
+.tag-peach {
+ background: rgba(250, 179, 135, 0.12);
+ color: var(--peach);
+}
+
+.tag-teal {
+ background: rgba(148, 226, 213, 0.12);
+ color: var(--teal);
+}
+
+.tag-mauve {
+ background: rgba(203, 166, 247, 0.12);
+ color: var(--mauve);
+}
+
+.tag-yellow {
+ background: rgba(249, 226, 175, 0.12);
+ color: var(--yellow);
+}
+
+.tag-sky {
+ background: rgba(137, 220, 235, 0.12);
+ color: var(--sky);
+}
+
+.tag-red {
+ background: rgba(243, 139, 168, 0.12);
+ color: var(--red);
+}
+
+.tag-pink {
+ background: rgba(245, 194, 231, 0.12);
+ color: var(--pink);
+}
/* ─── Tutorials / Projects shared ─────────────────────── */
.section-heading {
- font-family: 'Fraunces', serif;
- font-size: 2.8rem;
- font-weight: 600;
- color: var(--text);
- margin-bottom: 0.75rem;
+ font-family: 'Fraunces', serif;
+ font-size: 2.8rem;
+ font-weight: 600;
+ color: var(--text);
+ margin-bottom: 0.75rem;
}
.section-heading em {
- font-style: italic;
- color: var(--mauve);
- text-shadow: 0 0 16px rgba(203,166,247,0.25);
+ font-style: italic;
+ color: var(--mauve);
+ text-shadow: 0 0 16px rgba(203, 166, 247, 0.25);
}
.section-intro {
- color: var(--subtext0);
- font-size: 0.88rem;
- max-width: 560px;
- line-height: 1.8;
- margin-bottom: 3rem;
+ color: var(--subtext0);
+ font-size: 0.88rem;
+ max-width: 560px;
+ line-height: 1.8;
+ margin-bottom: 3rem;
}
.projects-subheading {
- margin: 0 0 0.9rem;
- font-size: 0.7rem;
- letter-spacing: 0.14em;
- text-transform: uppercase;
- color: var(--mauve);
+ margin: 0 0 0.9rem;
+ font-size: 0.7rem;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--mauve);
}
.card-grid.card-grid--featured {
- margin-bottom: 2rem;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ margin-bottom: 2rem;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
}
#projects .section-intro {
- margin-bottom: 1.8rem;
- max-width: 640px;
+ margin-bottom: 1.8rem;
+ max-width: 640px;
}
#project-grid {
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ grid-template-columns: repeat(3, minmax(0, 1fr));
}
#project-grid .card {
- padding: 1.2rem;
+ padding: 1.2rem;
}
.project-filters {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- border: none;
- padding: 0;
- min-width: 0;
- margin-bottom: 1.25rem;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ border: none;
+ padding: 0;
+ min-width: 0;
+ margin-bottom: 1.25rem;
}
.filter-chip {
- border: 1px solid var(--surface1);
- background: var(--mantle);
- color: var(--subtext0);
- border-radius: 999px;
- padding: 0.32rem 0.8rem;
- font-size: 0.68rem;
- letter-spacing: 0.04em;
- text-transform: uppercase;
- font-family: 'JetBrains Mono', monospace;
- cursor: pointer;
- transition: all 0.2s;
+ border: 1px solid var(--surface1);
+ background: var(--mantle);
+ color: var(--subtext0);
+ border-radius: 999px;
+ padding: 0.32rem 0.8rem;
+ font-size: 0.68rem;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ font-family: 'JetBrains Mono', monospace;
+ cursor: pointer;
+ transition: all 0.2s;
}
.filter-chip:hover {
- border-color: var(--mauve);
- color: var(--text);
+ border-color: var(--mauve);
+ color: var(--text);
}
.filter-chip.active {
- background: rgba(203,166,247,0.18);
- border-color: rgba(203,166,247,0.7);
- color: var(--mauve);
+ background: rgba(203, 166, 247, 0.18);
+ border-color: rgba(203, 166, 247, 0.7);
+ color: var(--mauve);
}
.card.is-hidden {
- display: none;
+ display: none;
}
.card.is-collapsed {
- display: none;
+ display: none;
}
.projects-more-row {
- margin-top: 1rem;
- display: flex;
- justify-content: center;
+ 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;
+ 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);
+ border-color: var(--mauve);
+ color: var(--text);
}
/* ─── Card grid ────────────────────────────────────────── */
.card-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
- gap: 1px;
- background: var(--surface0);
- border: 1px solid var(--surface0);
- border-radius: 8px;
- overflow: hidden;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1px;
+ background: var(--surface0);
+ border: 1px solid var(--surface0);
+ border-radius: 8px;
+ overflow: hidden;
}
.card {
- background: var(--mantle);
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 0.5rem;
- text-decoration: none;
- color: inherit;
- transition: background 0.2s;
- position: relative;
- overflow: hidden;
+ background: var(--mantle);
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ text-decoration: none;
+ color: inherit;
+ transition: background 0.2s;
+ position: relative;
+ overflow: hidden;
}
.card::before {
- content: '';
- position: absolute;
- top: 0; left: 0;
- width: 3px;
- height: 100%;
- background: var(--mauve);
- transform: scaleY(0);
- transform-origin: bottom;
- transition: transform 0.25s;
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 3px;
+ height: 100%;
+ background: var(--mauve);
+ transform: scaleY(0);
+ transform-origin: bottom;
+ transition: transform 0.25s;
}
.card:hover {
- background: var(--surface0);
- transform: translateY(-1px);
+ background: var(--surface0);
+ transform: translateY(-1px);
+}
+
+.card:hover::before {
+ transform: scaleY(1);
}
-.card:hover::before { transform: scaleY(1); }
.card-date {
- font-size: 0.68rem;
- color: var(--overlay1);
- letter-spacing: 0.06em;
+ font-size: 0.68rem;
+ color: var(--overlay1);
+ letter-spacing: 0.06em;
}
.card-title {
- font-size: 0.92rem;
- font-weight: 700;
- color: var(--text);
- transition: color 0.2s;
+ font-size: 0.92rem;
+ font-weight: 700;
+ color: var(--text);
+ transition: color 0.2s;
}
-.card:hover .card-title { color: var(--mauve); }
+.card:hover .card-title {
+ color: var(--mauve);
+}
.card-desc {
- font-size: 0.8rem;
- color: var(--subtext0);
- line-height: 1.6;
- flex: 1;
+ font-size: 0.8rem;
+ color: var(--subtext0);
+ line-height: 1.6;
+ flex: 1;
}
.card-arrow {
- font-size: 0.8rem;
- color: var(--overlay0);
- transition: color 0.2s, transform 0.2s;
- align-self: flex-end;
- margin-top: 0.5rem;
+ font-size: 0.8rem;
+ color: var(--overlay0);
+ transition: color 0.2s, transform 0.2s;
+ align-self: flex-end;
+ margin-top: 0.5rem;
}
-.card:hover .card-arrow { color: var(--mauve); transform: translateX(3px); }
+.card:hover .card-arrow {
+ color: var(--mauve);
+ transform: translateX(3px);
+}
/* ─── Contact ──────────────────────────────────────────── */
#contact {
- min-height: auto;
- padding-bottom: 8rem;
+ min-height: auto;
+ padding-bottom: 8rem;
}
.contact-inner {
- border: 1px solid var(--surface0);
- border-radius: 8px;
- padding: 3.5rem;
- background: var(--mantle);
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 4rem;
- align-items: start;
+ border: 1px solid var(--surface0);
+ border-radius: 8px;
+ padding: 3.5rem;
+ background: var(--mantle);
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 4rem;
+ align-items: start;
}
.contact-inner h2 {
- font-family: 'Fraunces', serif;
- font-size: 2.4rem;
- font-weight: 600;
- color: var(--text);
- margin-bottom: 1rem;
+ font-family: 'Fraunces', serif;
+ font-size: 2.4rem;
+ font-weight: 600;
+ color: var(--text);
+ margin-bottom: 1rem;
}
-.contact-inner h2 em { font-style: italic; color: var(--mauve); }
+.contact-inner h2 em {
+ font-style: italic;
+ color: var(--mauve);
+}
.contact-inner p {
- color: var(--subtext0);
- font-size: 0.88rem;
- line-height: 1.8;
- margin-bottom: 1.5rem;
+ color: var(--subtext0);
+ font-size: 0.88rem;
+ line-height: 1.8;
+ margin-bottom: 1.5rem;
}
.contact-links {
- display: flex;
- flex-direction: column;
- gap: 0.75rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
}
.contact-link {
- display: flex;
- align-items: center;
- gap: 1rem;
- padding: 1rem 1.25rem;
- border: 1px solid var(--surface0);
- border-radius: 6px;
- text-decoration: none;
- color: var(--text);
- background: var(--base);
- transition: all 0.2s;
- font-size: 0.85rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem 1.25rem;
+ border: 1px solid var(--surface0);
+ border-radius: 6px;
+ text-decoration: none;
+ color: var(--text);
+ background: var(--base);
+ transition: all 0.2s;
+ font-size: 0.85rem;
}
.contact-link:hover {
- border-color: var(--mauve);
- color: var(--mauve);
- transform: translateX(4px);
+ border-color: var(--mauve);
+ color: var(--mauve);
+ transform: translateX(4px);
}
.contact-link-icon {
- font-size: 1.1rem;
- width: 1.5rem;
- text-align: center;
+ font-size: 1.1rem;
+ width: 1.5rem;
+ text-align: center;
}
.contact-link-label {
- flex: 1;
+ flex: 1;
}
.contact-link-handle {
- font-size: 0.72rem;
- color: var(--overlay1);
+ font-size: 0.72rem;
+ color: var(--overlay1);
}
/* ─── Footer ───────────────────────────────────────────── */
footer {
- border-top: 1px solid var(--surface0);
- padding: 1.5rem 3rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 0.72rem;
- color: var(--overlay0);
+ border-top: 1px solid var(--surface0);
+ padding: 1.5rem 3rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.72rem;
+ color: var(--overlay0);
}
-footer a { color: var(--overlay1); text-decoration: none; }
-footer a:hover { color: var(--mauve); }
+footer a {
+ color: var(--overlay1);
+ text-decoration: none;
+}
+
+footer a:hover {
+ color: var(--mauve);
+}
/* ─── Animations ───────────────────────────────────────── */
@keyframes fadeUp {
- from { opacity: 0; transform: translateY(20px); }
- to { opacity: 1; transform: translateY(0); }
+ from {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
.reveal {
- opacity: 0;
- transform: translateY(24px);
- transition: opacity 0.6s ease, transform 0.6s ease;
+ opacity: 0;
+ transform: translateY(24px);
+ transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.visible {
- opacity: 1;
- transform: none;
+ opacity: 1;
+ transform: none;
}
/* ─── Divider ──────────────────────────────────────────── */
.divider {
- height: 1px;
- background: var(--surface0);
- max-width: 1100px;
- margin: 0 auto;
+ height: 1px;
+ background: var(--surface0);
+ max-width: 1100px;
+ margin: 0 auto;
}
/* ─── Responsive ───────────────────────────────────────── */
@media (max-width: 768px) {
- nav { padding: 0 1.5rem; }
- .nav-links { gap: 1.2rem; }
- section { padding: 6rem 1.5rem 4rem; }
- .card-grid.card-grid--featured { grid-template-columns: 1fr; }
- #project-grid { grid-template-columns: 1fr; }
- .about-columns { grid-template-columns: 1fr; }
- .about-block-tag { width: 4rem; }
- .skills-grid { grid-template-columns: 1fr; }
- .skill-card--wide { grid-column: 1; }
- .contact-inner { grid-template-columns: 1fr; gap: 2rem; padding: 2rem; }
- .hero-name { font-size: clamp(2.8rem, 12vw, 5rem); }
- footer { flex-direction: column; gap: 0.5rem; text-align: center; }
+ nav {
+ padding: 0 1.5rem;
+ }
+
+ .nav-links {
+ gap: 1.2rem;
+ }
+
+ section {
+ padding: 6rem 1.5rem 4rem;
+ }
+
+ .card-grid.card-grid--featured {
+ grid-template-columns: 1fr;
+ }
+
+ #project-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .about-columns {
+ grid-template-columns: 1fr;
+ }
+
+ .about-block-tag {
+ width: 4rem;
+ }
+
+ .skills-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .skill-card--wide {
+ grid-column: 1;
+ }
+
+ .contact-inner {
+ grid-template-columns: 1fr;
+ gap: 2rem;
+ padding: 2rem;
+ }
+
+ .hero-name {
+ font-size: clamp(2.8rem, 12vw, 5rem);
+ }
+
+ footer {
+ flex-direction: column;
+ gap: 0.5rem;
+ text-align: center;
+ }
}