11---
22import Base from ' ../layouts/Base.astro'
3- import HeroScene from ' ../components/HeroScene.tsx'
43import ContactForm from ' ../components/ContactForm.tsx'
54import ' ../styles/global.css'
65
76const nav = [
8- { n: ' 01' , label: ' Index' , href: ' #top' },
9- { n: ' 02' , label: ' Profile' , href: ' #profile' },
10- { n: ' 03' , label: ' Work' , href: ' #work' },
11- { n: ' 04' , label: ' Record' , href: ' #record' },
12- { n: ' 05' , label: ' Contact' , href: ' #contact' },
7+ { label: ' Profile' , href: ' #profile' },
8+ { label: ' Work' , href: ' #work' },
9+ { label: ' Record' , href: ' #record' },
10+ { label: ' Contact' , href: ' #contact' },
1311]
1412
13+ const stack = [' Java' , ' C#' , ' Python' , ' Node.js' , ' Flutter' , ' Dart' , ' Rust' , ' SQL' , ' Azure' , ' Firebase' , ' REST APIs' , ' Git' ]
14+
1515const skills = [
1616 { k: ' Languages' , v: [' Java' , ' C#' , ' Python' , ' JavaScript' , ' SQL' , ' Dart' , ' Rust' ] },
1717 { k: ' Web' , v: [' HTML' , ' CSS' , ' Node.js' , ' REST APIs' ] },
1818 { k: ' Mobile' , v: [' Flutter' , ' Dart' , ' Kivy' ] },
1919 { k: ' Cloud / DB' , v: [' Azure' , ' Firebase' ] },
20- { k: ' Tools' , v: [' Git' , ' CI/CD' , ' pytest' , ' Agile' , ' Async / network' ] },
20+ { k: ' Tools' , v: [' Git' , ' CI/CD' , ' pytest' , ' Agile' ] },
21+ { k: ' Spoken' , v: [' English' , ' Afrikaans' ] },
2122]
2223
2324const work = [
@@ -29,10 +30,10 @@ const work = [
2930]
3031
3132const record = [
32- { year: ' 2026 -' , role: ' Junior Software Engineer' , org: ' Coast IT Pty (Ltd) · Full-time' },
33- { year: ' 2025' , role: ' Junior Software Engineer' , org: ' Coast IT Pty (Ltd) · Internship' },
33+ { year: ' 2026 -' , role: ' Junior Software Engineer' , org: ' Coast IT Pty (Ltd), Full-time' },
34+ { year: ' 2025' , role: ' Junior Software Engineer' , org: ' Coast IT Pty (Ltd), Internship' },
3435 { year: ' 2025' , role: ' Intern' , org: ' Open Vantage' },
35- { year: ' 2025 - 26' , role: ' Freelance Web Developer' , org: ' Self-employed' },
36+ { year: ' 2025- 26' , role: ' Freelance Web Developer' , org: ' Self-employed' },
3637 { year: ' 2023 -' , role: ' Software Engineering (NQF 6)' , org: ' CTU Training Solutions, Boksburg' },
3738 { year: ' 2021' , role: ' National Senior Certificate' , org: ' Bergsig Academy, Rustenburg' },
3839]
@@ -50,114 +51,117 @@ const certs = [
5051<Base >
5152 <a id =" top" ></a >
5253
53- <!-- Fixed index bar -->
54- <header id =" site-header" class =" fixed top-0 inset-x-0 z-50" style =" background: rgba(var(--bg-rgb),0.85); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px); border-bottom: 1px solid var(--border)" >
54+ <header id =" site-header" class =" fixed top-0 inset-x-0 z-50" >
5555 <div class =" container-wide" >
56- <div id =" nav-inner" class =" flex items-center justify-between h-16 transition-all duration-300" >
57- <a href =" #top" class =" ed-mono font-bold tracking-tight" >RR / ROBERTS</a >
58- <nav class =" hidden md:flex items-center gap-6 " >
56+ <div id =" nav-inner" class =" flex items-center justify-between h-[72px] transition-all duration-300" >
57+ <a href =" #top" class =" ed-mono font-bold tracking-tight text-sm " >RR / ROBERTS</a >
58+ <nav class =" hidden md:flex items-center gap-7 " >
5959 { nav .map (item => (
60- <a href = { item .href } class = " ed-mono text-xs uppercase tracking-widest nav-link" >
61- <span style = " color: var(--accent)" >{ item .n } </span > { item .label }
62- </a >
60+ <a href = { item .href } class = " ed-mono text-xs uppercase tracking-widest nav-link" >{ item .label } </a >
6361 ))}
6462 </nav >
65- <span class =" ed-mono text-xs uppercase tracking-widest hidden sm:inline" style =" color: var(--muted)" >Germiston, ZA</span >
63+ <div class =" flex items-center gap-4" >
64+ <span class =" ed-avail hidden sm:inline-flex" ><i ></i >Available for work</span >
65+ <button class =" ed-menu-btn ed-mono text-xs uppercase tracking-widest" id =" ed-menu-open" aria-label =" Open menu" >Menu</button >
66+ </div >
6667 </div >
6768 </div >
6869 </header >
6970
70- <main class =" container-wide pt-24" >
71+ <div id =" ed-mobile" aria-hidden =" true" >
72+ <button id =" ed-menu-close" class =" ed-mono text-xs uppercase tracking-widest absolute top-6 right-6" style =" color: var(--muted)" aria-label =" Close menu" >Close</button >
73+ { nav .map (item => <a href = { item .href } data-ed-mclose >{ item .label } </a >)}
74+ </div >
7175
72- <!-- Masthead -->
73- <section class =" pt-6 pb-16 lg:pb-24" >
74- <div class =" flex items-center justify-between ed-kicker mb-6" >
75- <span >Full-stack Developer</span >
76- <span >Est. 2021 - Portfolio No.05</span >
77- </div >
78- <hr class =" ed-rule-accent mb-8" />
79-
80- <div class =" grid grid-cols-1 lg:grid-cols-[1fr_auto] gap-8 items-end" >
81- <h1 class =" ed-huge" style =" font-size: clamp(3.2rem, 13vw, 11rem)" data-ed-title >
82- <span class =" block" >Ronan</span >
83- <span class =" block ed-outline" >Roberts</span >
84- </h1 >
85- <div class =" hidden lg:block w-[260px] h-[260px] border" style =" border-color: var(--border)" aria-hidden =" true" >
86- <HeroScene client:only =" react" />
87- </div >
88- </div >
76+ <main class =" container-wide pt-[72px]" >
8977
90- <hr class =" ed-rule mt-8 mb-6" />
91- <div class =" grid grid-cols-1 md:grid-cols-[2fr_1fr] gap-8" >
92- <p class =" text-lg leading-relaxed" style =" max-width: 60ch" >
93- Full-stack developer building across the stack, web front-ends and
94- back-ends in Java, C#, Python and Node.js, and cross-platform mobile
95- apps in Flutter and Dart. Currently a Junior Software Engineer at Coast
96- IT, completing a Software Engineer qualification (NQF 6) with CTU.
78+ <!-- Masthead -->
79+ <section class =" min-h-[calc(100dvh-72px)] flex flex-col justify-center pt-10 pb-16" >
80+ <div class =" ed-kicker mb-6" data-reveal >Full-stack Developer, Germiston ZA</div >
81+ <hr class =" ed-rule-accent mb-8" data-reveal />
82+
83+ <h1 class =" ed-huge" style =" font-size: clamp(3.4rem, 15vw, 13rem)" data-ed-title >
84+ <span class =" ed-line" ><span >Ronan</span ></span >
85+ <span class =" ed-line" ><span class =" ed-outline" >Roberts</span ></span >
86+ </h1 >
87+
88+ <hr class =" ed-rule mt-10 mb-8" data-reveal />
89+ <div class =" grid grid-cols-1 md:grid-cols-[1.7fr_1fr] gap-8 md:gap-16 items-end" >
90+ <p class =" text-lg md:text-xl leading-relaxed" style =" max-width: 58ch" data-reveal >
91+ I build across the stack, web front-ends and back-ends in Java, C#,
92+ Python and Node.js, and cross-platform mobile apps in Flutter and Dart.
93+ Currently a Junior Software Engineer at Coast IT.
9794 </p >
98- <div class =" ed-mono text-xs" style =" color: var(--muted)" >
99- <div class =" flex justify-between py-1" ><span >STATUS</span ><span style =" color: var(--accent)" >AVAILABLE</span ></div >
100- <div class =" flex justify-between py-1" style =" border-top: 1px solid var(--border)" ><span >WORK</span ><span >FT / PT / CONTRACT</span ></div >
101- <div class =" flex justify-between py-1" style =" border-top: 1px solid var(--border)" ><span >FOCUS</span ><span >WEB / MOBILE</span ></div >
95+ <div class =" flex flex-wrap gap-3 md:justify-end" data-reveal >
96+ <a href =" #work" class =" btn-primary magnetic" data-magnetic >View work</a >
97+ <a href =" #contact" class =" btn-ghost magnetic" data-magnetic >Get in touch</a >
10298 </div >
10399 </div >
104100 </section >
101+ </main >
105102
106- <!-- Profile / skills -->
107- <section id =" profile" class =" py-16 lg:py-24" >
108- <div class =" flex items-baseline gap-4 mb-10" >
109- <span class =" ed-num" >02</span >
110- <h2 class =" ed-huge" style =" font-size: clamp(2rem, 6vw, 4rem)" >Profile</h2 >
103+ <!-- Tech marquee -->
104+ <section class =" py-6 border-y" style =" border-color: var(--border)" aria-hidden =" true" >
105+ <div class =" marquee" >
106+ <div class =" marquee__track" >
107+ { stack .map (s => <span class = " marquee__item" >{ s } </span >)}
108+ </div >
109+ <div class =" marquee__track" aria-hidden =" true" >
110+ { stack .map (s => <span class = " marquee__item" >{ s } </span >)}
111111 </div >
112- <div class =" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-10 gap-y-8" >
112+ </div >
113+ </section >
114+
115+ <main class =" container-wide" >
116+
117+ <!-- Profile / skills -->
118+ <section id =" profile" class =" py-20 lg:py-28" >
119+ <h2 class =" ed-huge mb-12" style =" font-size: clamp(2rem, 6vw, 4rem)" data-reveal >Profile</h2 >
120+ <div class =" grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-10 gap-y-10" >
113121 { skills .map (group => (
114- <div >
122+ <div data-reveal >
115123 <div class = " ed-kicker mb-3 pb-2" style = " border-bottom: 1px solid var(--border)" >{ group .k } </div >
116124 <ul class = " ed-mono text-sm leading-8" >
117125 { group .v .map (item => <li >{ item } </li >)}
118126 </ul >
119127 </div >
120128 ))}
121- <div >
122- <div class =" ed-kicker mb-3 pb-2" style =" border-bottom: 1px solid var(--border)" >Languages (spoken)</div >
123- <ul class =" ed-mono text-sm leading-8" ><li >English</li ><li >Afrikaans</li ></ul >
124- </div >
125129 </div >
126130 </section >
127131
128132 <!-- Work index -->
129- <section id =" work" class =" py-16 lg:py-24 " >
130- <div class =" flex items-baseline gap-4 mb-8 " >
131- <span class =" ed-num " >03</ span >
132- <h2 class =" ed-huge " style =" font-size: clamp(2rem, 6vw, 4rem) " >Selected Work</ h2 >
133+ <section id =" work" class =" py-20 lg:py-28 " >
134+ <div class =" flex items-end justify-between mb-10 gap-4 " >
135+ <h2 class =" ed-huge " style = " font-size: clamp(2rem, 6vw, 4rem) " data-reveal >Selected Work</ h2 >
136+ <span class =" ed-mono text-xs hidden sm:block " style =" color: var(--muted) " data-reveal >2024 to 2026</ span >
133137 </div >
134138 <div >
135139 { work .map (item => (
136- <a href = { item .href } target = " _blank" rel = " noopener noreferrer" class = " work-row group" data-ed-row >
140+ <a href = { item .href } target = " _blank" rel = " noopener noreferrer" class = " work-row group" data-reveal >
137141 <span class = " ed-num" >{ item .n } </span >
138142 <span >
139143 <span class = " work-title block" >{ item .title } </span >
140144 <span class = " ed-mono text-xs" style = " color: var(--muted)" >{ item .meta } </span >
141145 </span >
142- <span class = " flex flex-col items-end gap-2" >
143- <span class = " ed-mono text-xs" style = " color: var(--muted)" >{ item .year } ↗</span >
144- <span class = " hidden sm:flex flex-wrap gap-1.5 justify-end" style = " max-width: 240px" >
146+ <span class = " flex items-center gap-4" >
147+ <span class = " hidden md:flex flex-wrap gap-1.5 justify-end" style = " max-width: 220px" >
145148 { item .tags .map (t => <span class = " ed-tag" >{ t } </span >)}
146149 </span >
150+ <span class = " ed-mono text-xs hidden sm:inline" style = " color: var(--muted)" >{ item .year } </span >
151+ <span class = " work-row__arrow" aria-hidden = " true" >
152+ <svg width = " 15" height = " 15" viewBox = " 0 0 24 24" fill = " none" stroke = " currentColor" stroke-width = " 2.2" stroke-linecap = " round" stroke-linejoin = " round" ><path d = " M5 12h14M13 6l6 6-6 6" /></svg >
153+ </span >
147154 </span >
148155 </a >
149156 ))}
150157 </div >
151158 </section >
152159
153- <!-- Record: experience + certs -->
154- <section id =" record" class =" py-16 lg:py-24" >
155- <div class =" flex items-baseline gap-4 mb-8" >
156- <span class =" ed-num" >04</span >
157- <h2 class =" ed-huge" style =" font-size: clamp(2rem, 6vw, 4rem)" >Record</h2 >
158- </div >
160+ <!-- Record -->
161+ <section id =" record" class =" py-20 lg:py-28" >
162+ <h2 class =" ed-huge mb-12" style =" font-size: clamp(2rem, 6vw, 4rem)" data-reveal >Record</h2 >
159163 <div class =" grid grid-cols-1 lg:grid-cols-2 gap-x-16 gap-y-12" >
160- <div >
164+ <div data-reveal >
161165 <div class =" ed-kicker mb-4" >Experience & Education</div >
162166 { record .map (r => (
163167 <div class = " grid grid-cols-[5.5rem_1fr] gap-4 py-4" style = " border-top: 1px solid var(--border)" >
@@ -169,7 +173,7 @@ const certs = [
169173 </div >
170174 ))}
171175 </div >
172- <div >
176+ <div data-reveal >
173177 <div class =" ed-kicker mb-4" >Certifications</div >
174178 { certs .map (c => (
175179 <div class = " flex items-baseline justify-between gap-4 py-4" style = " border-top: 1px solid var(--border)" >
@@ -185,15 +189,12 @@ const certs = [
185189 </section >
186190
187191 <!-- Contact -->
188- <section id =" contact" class =" py-16 lg:py-28" >
189- <div class =" flex items-baseline gap-4 mb-8" >
190- <span class =" ed-num" >05</span >
191- <h2 class =" ed-huge" style =" font-size: clamp(2rem, 6vw, 4rem)" >Get in touch</h2 >
192- </div >
193- <hr class =" ed-rule-accent mb-10" />
192+ <section id =" contact" class =" py-20 lg:py-28" >
193+ <h2 class =" ed-huge mb-8" style =" font-size: clamp(2rem, 6vw, 4rem)" data-reveal >Get in touch</h2 >
194+ <hr class =" ed-rule-accent mb-12" data-reveal />
194195 <div class =" grid grid-cols-1 lg:grid-cols-[1fr_1fr] gap-12 lg:gap-20" >
195- <div >
196- <p class =" text-2xl md:text-3xl font-display font-bold leading-tight mb-8 " style =" max-width: 18ch " >
196+ <div data-reveal >
197+ <p class =" text-2xl md:text-4xl font-display font-bold leading-tight mb-10 " style =" max-width: 16ch " >
197198 Open to freelance projects and job opportunities.
198199 </p >
199200 <div class =" ed-mono text-sm flex flex-col gap-3" >
@@ -202,20 +203,29 @@ const certs = [
202203 <a href =" https://github.com/Ronan-R-R" target =" _blank" rel =" noopener noreferrer" class =" ed-link w-fit" >github.com/Ronan-R-R</a >
203204 </div >
204205 </div >
205- <div >
206+ <div data-reveal >
206207 <ContactForm client:visible />
207208 </div >
208209 </div >
209210 </section >
210211
211212 <footer class =" py-10 ed-mono text-xs flex flex-col sm:flex-row justify-between gap-3" style =" border-top: 1px solid var(--border); color: var(--muted)" >
212213 <span >© <span id =" footer-year" ></span > Ronan Roberts</span >
213- <span >Designed & built by Ronan Roberts</span >
214+ <span >Designed and built by Ronan Roberts</span >
214215 </footer >
215216 </main >
216217
217218 <script >
218219 const el = document.getElementById('footer-year')
219220 if (el) el.textContent = String(new Date().getFullYear())
221+
222+ const open = document.getElementById('ed-menu-open')
223+ const close = document.getElementById('ed-menu-close')
224+ const panel = document.getElementById('ed-mobile')
225+ open?.addEventListener('click', () => panel?.classList.add('open'))
226+ close?.addEventListener('click', () => panel?.classList.remove('open'))
227+ document.querySelectorAll('[data-ed-mclose]').forEach((a) =>
228+ a.addEventListener('click', () => panel?.classList.remove('open'))
229+ )
220230 </script >
221231</Base >
0 commit comments