diff --git a/index.html b/index.html index d6762ae..90236bb 100644 --- a/index.html +++ b/index.html @@ -1,527 +1,391 @@ - + - + + Havox — John Gatward - + + 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" + rel="stylesheet"> - - + + + +
-

// havox — v4

-

John Gatward

-

Software Engineer

-

- - - - - - I like building useful things and figuring how they work. - -

- +

// havox.org — v4

+

John Gatward

+

Software Developer

+

+ + + + I like building useful things and understanding how they work. +

+
-
- -

Havox & me

-
- - -
-
- day job -

- CS graduate from the University of Nottingham (2022), now working as - a backend engineer at Sainsbury's Supply Chain - & Logistics — building microservices with Spring Boot on AWS, - designing REST APIs, connecting services with Kafka, and storing - data in MongoDB. -

+
+ +

Havox & me

-
- home infra -

- I run a home server that hosts media and projects for friends and - family. I enjoy building things that feel solid and useful — from - small games like - Crack the Quote to websites and services running on my own - hardware. -

-
+
-
- linux -

- Long-time Linux user. Used to distro-hop obsessively and be deep in - the ricing community — these days it's Hyprland on Void Linux and a - growing collection of scripts that make the day job easier. -

-
+ +
-
- teaching -

- I tutored during school and university — CS, maths, English. - Teaching stuck with me. Explaining things clearly is a skill, and - it's why I also write tutorials here. -

-
+
+ day job +

Backend engineer at Sainsbury's Supply Chain & Logistics. Microservices, + Spring Boot, Kafka, MongoDB, AWS. It's good fun.

+
-
- havox -

- Havox started as a place to document whatever I found cool — - tutorials, visualisations, experiments. It's been running in some - form since 2017, gone through four major redesigns, and outlasted - several domains. It represents my entire journey through technology, - and it's still going. -

-
-
+
+ linux +

Long-time Linux user and recovering distro-hopper. Currently: Hyprland on Void. Home server + running 24/7 for friends and family who didn't ask for it but appreciate it.

+
+ +
+ teaching +

Tutored CS, maths and English through school and uni. Still think the best way to understand + something is to explain it — which is why this site has tutorials.

+
+ +
+ havox +

Started in 2017 as a place to dump whatever I found interesting. Four redesigns and several dead + domains later, it's still going. Less a portfolio, more a paper trail.

+
- -
- -
-
-
-
-
Havox V4 — this one
-
2025 → present
-
- Catppuccin Mocha. Single page. Trying to get a new job this - time. -
-
-
-
-
-
Havox V3
-
2023 → 2025
-
- Borrowed a template. Nord theme. Took SCSS seriously. Considered - mobile widths. -
+ + + -
-
-
-
-
Havox V2
-
2019 → 2022
-
- First SCSS project. First site on own hardware — a Debian box - running nginx. Nord theme. -
-
-
-
-
-
-
Havox.org
-
2017 → 2019
-
- Entire site in one PHP file 🤣. Had a Python quote-of-the-day - via cronjob though. Counts. -
-
-
+
-
+
-

skills

-

- What I know -

+

skills

+

What I know

-
-
- -

Backend

-

- Professional Java experience building RESTful APIs with Spring Boot. - Testing with JUnit & Mockito. Data persistence with MySQL, H2, JDBC. -

-
- Java - Spring Boot - REST - JUnit - MySQL -
+
+
+ +

Backend

+

Professional Java experience building microservices & RESTful APIs with Spring Boot. TDD with + JUnit & Mockito.

+
+ Java + Spring Boot + AWS + Kafka + MongoDB +
+
+
+ 🌐 +

Frontend & Creative

+

Comfortable with react and CSS. I enjoy visualising algorithms interactively — it's more fun than + a + console output.

+
+ React.js + TypeScript + HTML/CSS +
+
+
+ 🖥 +

Systems & Infra

+

Long-term Linux user. Self-hosted web & media servers. Comfortable with networking, security + practices, and the command line.

+
+ Linux + Docker + Self-Hosting + Wireguard +
+
+
+ 🎓 +

Academic Breadth

+

C, C++, x86_64 assembly, Haskell, R & MatLab from uni. Modules in Advanced networking, + algorithms, compilers, + graphics, cryptography, malware analysis & many more.

+
+ C / C++ + Haskell + Algorithms + Security + Low Level OS +
+
-
- 🌐 -

Frontend & Creative

-

- JavaScript, p5.js, HTML/CSS/Scss. I enjoy visualising algorithms - interactively — it's more fun than a console output. -

-
- JavaScript - p5.js - Scss - HTML/CSS -
-
-
- 🖥 -

Systems & Infra

-

- Long-term Linux user. Self-hosted web & media servers. Comfortable - with networking, security practices, and the command line. -

-
- Linux - nginx - Networking - Self-hosting -
-
-
- 🎓 -

Academic Breadth

-

- C, C++, Haskell from uni. Coursework across Android dev, distributed - systems, graphics, cryptography, malware analysis. -

-
- C / C++ - Haskell - Algorithms - Security -
-
-
-

tutorials

-

- Things I wrote down -

-

- Best way to learn is to teach. When I find something interesting I write - a short tutorial — mainly to consolidate my own understanding, but if it - helps someone else that's a win. -

+

tutorials

+

Things I wrote down

+

+ Best way to learn is to teach. When I find something interesting I write a short tutorial — mainly to + consolidate my own understanding, but if it helps someone else that's a win. +

-
- - 3 Jan 2021 - Summed-area tables - Integral images — what they are, how they work, and why they're - surprisingly useful for image processing. - - - - 3 Feb 2019 - Midpoint displacement terrain generation - Generating random, natural-looking 2D terrains using the midpoint - displacement algorithm. - - - - 22 Sep 2018 - Travelling salesperson - Different implementations of the TSP problem — a classic that never - gets old. - - - - 14 Feb 2018 - Convex hull generator - Wrapping algorithms — visualising how to find the convex hull of a - point set. - - -
+
+ + 3 Jan 2021 + Summed-area tables + Integral images — what they are, how they work, and why they're surprisingly + useful for image processing. + + + + 3 Feb 2019 + Midpoint displacement terrain generation + Generating random, natural-looking 2D terrains using the midpoint displacement + algorithm. + + + + 22 Sep 2018 + Travelling salesperson + Different implementations of the TSP problem — a classic that never gets + old. + + + + 14 Feb 2018 + Convex hull generator + Wrapping algorithms — visualising how to find the convex hull of a point + set. + + +
-

projects

-

- Things I built -

-

- Mostly p5.js — visualising algorithms in ways that look more interesting - than they have any right to. Most use - mouseClicked() - so desktop only, sorry mobile. -

+

projects

+

Things I built

+

+ Mostly p5.js — visualising algorithms in ways that look more interesting than they have any right to. Most + use mouseClicked() + so desktop only, sorry mobile. +

-
- - 1 Oct 2021 - Drawing Bézier curves - Interactive animated diagram for quadratic and cubic Bézier curves. - Click to place points. - - - - 23 Sep 2021 - 2D Marching squares - Marching squares in action — click a tile to edit the - terrain. - - - - 11 Apr 2020 - Bubble sort visualiser - Bars sorted by height, one frame per loop. Classic. - - - - 2 Apr 2020 - Müller-Lyer illusion - A visualisation of the Müller-Lyer optical illusion rendered in - JS. - - - - 27 Feb 2019 - Fourier series - Building a square wave from nothing but sine waves. - Mesmerising. - - - - 23 Feb 2019 - Constructing an ellipse - How an ellipse can be constructed with a circle and radial lines — - inspired by 3Blue1Brown. - - - - 2 May 2018 - Game of Life - A JS implementation of Conway's classic. Still fascinating. - - - - 18 Mar 2018 - Calculating PI - Monte Carlo method — ratio of randomly placed dots in a square vs - circle. - - - - 17 Dec 2017 - Oscillations in 3D - JS implementation of a Bees and Bombs gif. One of my oldest. - - - - 13 Nov 2017 - Maze generator - A maze generator in JS. The first project that made me think "oh, I - like this". - - -
+
+ + 1 Oct 2021 + Drawing Bézier curves + Interactive animated diagram for quadratic and cubic Bézier curves. Click to + place points. + + + + 23 Sep 2021 + 2D Marching squares + Marching squares in action — click a tile to edit the terrain. + + + + 11 Apr 2020 + Bubble sort visualiser + Bars sorted by height, one frame per loop. Classic. + + + + 2 Apr 2020 + Müller-Lyer illusion + A visualisation of the Müller-Lyer optical illusion rendered in JS. + + + + 27 Feb 2019 + Fourier series + Building a square wave from nothing but sine waves. Mesmerising. + + + + 23 Feb 2019 + Constructing an ellipse + How an ellipse can be constructed with a circle and radial lines — inspired by + 3Blue1Brown. + + + + 2 May 2018 + Game of Life + A JS implementation of Conway's classic. Still fascinating. + + + + 18 Mar 2018 + Calculating PI + Monte Carlo method — ratio of randomly placed dots in a square vs circle. + + + + 17 Dec 2017 + Oscillations in 3D + JS implementation of a Bees and Bombs gif. One of my oldest. + + + + 13 Nov 2017 + Maze generator + A maze generator in JS. The first project that made me think "oh, I like + this". + + +
-

contact

-
-
-

Let's talk

-

- I'm currently employed but open to the right opportunity — backend - or full-stack, somewhere that ships interesting things with people - who care about their craft. -

-

- GitHub has the most complete picture of how I think. LinkedIn if - you're formal. Either works. -

+ +
+
+

Let's talk

+

+ I'm currently employed but open to the right opportunity — backend or full-stack, somewhere that + ships interesting things with people who care about their craft. +

+

+ GitHub has the most complete picture of how I think. LinkedIn if you're formal. Either works. +

+
+
- -
- + + diff --git a/style.css b/style.css index 9103902..5ef73f7 100644 --- a/style.css +++ b/style.css @@ -1,80 +1,58 @@ /* ─── 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", monospace; + font-family: 'JetBrains Mono', monospace; font-size: 15px; line-height: 1.7; overflow-x: hidden; } -::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; + top: 0; left: 0; right: 0; z-index: 100; display: flex; align-items: center; @@ -87,7 +65,7 @@ nav { } .nav-logo { - font-family: "Fraunces", serif; + font-family: 'Fraunces', serif; font-size: 1.3rem; color: var(--mauve); text-decoration: none; @@ -111,11 +89,9 @@ nav { } .nav-links a::after { - content: ""; + content: ''; position: absolute; - bottom: -3px; - left: 0; - right: 0; + bottom: -3px; left: 0; right: 0; height: 1px; background: var(--mauve); transform: scaleX(0); @@ -123,12 +99,8 @@ nav { 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 { @@ -157,7 +129,7 @@ section.full-width { } .section-label::after { - content: ""; + content: ''; display: block; height: 1px; width: 3rem; @@ -186,7 +158,7 @@ section.full-width { } .hero-name { - font-family: "Fraunces", serif; + font-family: 'Fraunces', serif; font-size: clamp(3.5rem, 9vw, 7.5rem); font-weight: 600; line-height: 1; @@ -202,7 +174,7 @@ section.full-width { } .hero-subtitle { - font-family: "Fraunces", serif; + font-family: 'Fraunces', serif; font-size: clamp(1.2rem, 3vw, 2.2rem); font-weight: 300; color: var(--subtext1); @@ -234,7 +206,7 @@ section.full-width { } .hero-by-role { - font-family: "Fraunces", serif; + font-family: 'Fraunces', serif; font-style: italic; font-size: 0.85rem; color: var(--mauve); @@ -242,7 +214,7 @@ section.full-width { } .hero-by-role::before { - content: "— "; + content: '— '; opacity: 0.4; } @@ -268,7 +240,7 @@ section.full-width { gap: 0.5rem; padding: 0.6rem 1.4rem; border-radius: 4px; - font-family: "JetBrains Mono", monospace; + font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; letter-spacing: 0.05em; text-decoration: none; @@ -281,25 +253,18 @@ section.full-width { background: var(--mauve); color: var(--crust); } -.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); } -.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: ""; + content: ''; position: absolute; inset: 0; background-image: @@ -317,58 +282,53 @@ section.full-width { padding-bottom: 5rem; } -.about-grid { +.about-columns { display: grid; grid-template-columns: 1fr 1fr; - gap: 1px; - background: var(--surface0); + 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; - margin-top: 2.5rem; } .about-block { background: var(--mantle); - padding: 1.75rem 2rem; - position: relative; + 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:hover { - background: var(--surface0); -} - -.about-block--wide { - grid-column: 1 / -1; - border-top: 1px solid var(--surface0); -} +.about-block:last-child { border-bottom: none; } +.about-block:hover { background: var(--surface0); } .about-block-tag { - display: inline-block; - margin-bottom: 0.85rem; + 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.85; + 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; @@ -378,7 +338,7 @@ section.full-width { } .version-timeline::before { - content: ""; + content: ''; position: absolute; left: 0.65rem; top: 0.5rem; @@ -402,9 +362,7 @@ section.full-width { border: 2px solid var(--surface1); flex-shrink: 0; margin-top: 0.15rem; - transition: - border-color 0.2s, - background 0.2s; + transition: border-color 0.2s, background 0.2s; position: relative; z-index: 1; } @@ -434,23 +392,66 @@ section.full-width { line-height: 1.6; } +.version-item--link { + text-decoration: none; + color: inherit; + cursor: pointer; +} + +.version-item--link:hover .v-dot { + border-color: var(--mauve); + background: var(--mauve); +} + +.version-item--link:hover .v-title { + 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; +} + +.version-item--link:hover .v-link-arrow { + color: var(--mauve); + transform: translate(2px, -2px); +} + +.v-dot--current { + 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; +} + /* ─── Skills ───────────────────────────────────────────── */ #skills h2 { - font-family: "Fraunces", serif; + 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(auto-fill, minmax(300px, 1fr)); + grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--surface0); border: 1px solid var(--surface0); @@ -458,15 +459,17 @@ section.full-width { overflow: hidden; } +.skill-card--wide { + grid-column: 1 / -1; +} + .skill-card { background: var(--mantle); padding: 2rem; transition: background 0.2s; } -.skill-card:hover { - background: var(--surface0); -} +.skill-card:hover { background: var(--surface0); } .skill-card-icon { font-size: 1.5rem; @@ -505,48 +508,24 @@ section.full-width { 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-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); } /* ─── Tutorials / Projects shared ─────────────────────── */ .section-heading { - font-family: "Fraunces", serif; + 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); -} +.section-heading em { font-style: italic; color: var(--mauve); } .section-intro { color: var(--subtext0); @@ -581,10 +560,9 @@ section.full-width { } .card::before { - content: ""; + content: ''; position: absolute; - top: 0; - left: 0; + top: 0; left: 0; width: 3px; height: 100%; background: var(--mauve); @@ -593,12 +571,8 @@ section.full-width { transition: transform 0.25s; } -.card:hover { - background: var(--surface0); -} -.card:hover::before { - transform: scaleY(1); -} +.card:hover { background: var(--surface0); } +.card:hover::before { transform: scaleY(1); } .card-date { font-size: 0.68rem; @@ -613,9 +587,7 @@ section.full-width { transition: color 0.2s; } -.card:hover .card-title { - color: var(--mauve); -} +.card:hover .card-title { color: var(--mauve); } .card-desc { font-size: 0.8rem; @@ -627,17 +599,12 @@ section.full-width { .card-arrow { font-size: 0.8rem; color: var(--overlay0); - transition: - color 0.2s, - transform 0.2s; + 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 { @@ -657,17 +624,14 @@ section.full-width { } .contact-inner h2 { - font-family: "Fraunces", serif; + 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); @@ -728,32 +692,19 @@ footer { 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; + transition: opacity 0.6s ease, transform 0.6s ease; } .reveal.visible { @@ -771,32 +722,14 @@ footer a:hover { /* ─── Responsive ───────────────────────────────────────── */ @media (max-width: 768px) { - nav { - padding: 0 1.5rem; - } - .nav-links { - gap: 1.2rem; - } - section { - padding: 6rem 1.5rem 4rem; - } - .about-grid { - grid-template-columns: 1fr; - } - .about-block--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; } + .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; } }