Skip to content

Commit ccdb006

Browse files
committed
added responsive hamburger menu
1 parent 885f045 commit ccdb006

File tree

1 file changed

+160
-17
lines changed

1 file changed

+160
-17
lines changed

src/components/Header.astro

Lines changed: 160 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,77 @@
22
const base = import.meta.env.BASE_URL.replace(/\/$/, '');
33
const currentPath = Astro.url.pathname;
44
---
5-
<header>
5+
6+
<header id="site-header">
67
<a href={base} class="site-logo">
78
<span class="logo-fish">🐟</span>
89
GuppyFisher
910
</a>
10-
<nav>
11-
<a href={base} class={currentPath === base || currentPath === base.slice(0,-1) ? 'active' : ''}>Posts</a>
12-
<a href={`${base}/series`} class={currentPath.startsWith(`${base}/series`) ? 'active' : ''}>Deep Dives</a>
11+
12+
<nav id="main-nav" aria-label="Main navigation">
13+
<a href={base} class={currentPath === base || currentPath === `${base}/` ? 'active' : ''}>Posts</a>
1314
<a href={`${base}/series`} class={currentPath.startsWith(`${base}/series`) ? 'active' : ''}>Series</a>
1415
<a href={`${base}/about`} class={currentPath === `${base}/about` ? 'active' : ''}>About</a>
1516
</nav>
17+
1618
<div class="header-right">
1719
<span class="tag-badge">Learning in Public</span>
20+
<button
21+
id="menu-toggle"
22+
class="menu-toggle"
23+
aria-label="Open menu"
24+
aria-expanded="false"
25+
aria-controls="main-nav"
26+
>
27+
<svg class="icon-menu" width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
28+
<line x1="3" y1="6" x2="19" y2="6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
29+
<line x1="3" y1="11" x2="19" y2="11" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
30+
<line x1="3" y1="16" x2="19" y2="16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
31+
</svg>
32+
<svg class="icon-close" width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true">
33+
<line x1="4" y1="4" x2="18" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
34+
<line x1="18" y1="4" x2="4" y2="18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
35+
</svg>
36+
</button>
1837
</div>
1938
</header>
2039

2140
<style>
22-
header {
41+
/* ── BASE ── */
42+
#site-header {
43+
position: sticky;
44+
top: 0;
45+
z-index: 200;
46+
background: var(--paper);
2347
border-bottom: 2px solid var(--ink);
2448
padding: 0 2rem;
2549
display: grid;
2650
grid-template-columns: 1fr auto 1fr;
2751
align-items: center;
28-
min-height: 80px;
52+
min-height: 72px;
53+
transition: box-shadow 0.25s ease;
2954
}
3055

56+
#site-header.scrolled {
57+
box-shadow: 0 4px 24px rgba(13, 15, 20, 0.07);
58+
}
59+
60+
/* ── LOGO ── */
3161
.site-logo {
3262
font-family: 'Syne', sans-serif;
3363
font-weight: 800;
34-
font-size: 1.5rem;
64+
font-size: 1.4rem;
3565
letter-spacing: -0.03em;
3666
text-decoration: none;
3767
color: var(--ink);
3868
display: flex;
3969
align-items: center;
40-
gap: 0.5rem;
70+
gap: 0.45rem;
4171
}
4272

4373
.logo-fish {
4474
display: inline-block;
45-
font-size: 1.8rem;
75+
font-size: 1.6rem;
4676
animation: swim 3s ease-in-out infinite;
4777
}
4878

@@ -51,11 +81,12 @@ const currentPath = Astro.url.pathname;
5181
50% { transform: translateY(-4px) rotate(5deg); }
5282
}
5383

84+
/* ── DESKTOP NAV ── */
5485
nav {
5586
display: flex;
5687
gap: 2rem;
5788
font-family: 'Syne', sans-serif;
58-
font-size: 0.85rem;
89+
font-size: 0.82rem;
5990
font-weight: 700;
6091
letter-spacing: 0.08em;
6192
text-transform: uppercase;
@@ -65,13 +96,15 @@ const currentPath = Astro.url.pathname;
6596
color: var(--ink);
6697
text-decoration: none;
6798
position: relative;
68-
padding-bottom: 2px;
99+
padding-bottom: 3px;
69100
}
70101

71102
nav a::after {
72103
content: '';
73104
position: absolute;
74-
bottom: 0; left: 0; right: 100%;
105+
bottom: 0;
106+
left: 0;
107+
right: 100%;
75108
height: 2px;
76109
background: var(--accent);
77110
transition: right 0.25s ease;
@@ -80,26 +113,136 @@ const currentPath = Astro.url.pathname;
80113
nav a:hover::after,
81114
nav a.active::after { right: 0; }
82115

116+
nav a.active { color: var(--ink); }
117+
118+
/* ── RIGHT SLOT ── */
83119
.header-right {
84120
display: flex;
85121
justify-content: flex-end;
122+
align-items: center;
123+
gap: 1rem;
86124
}
87125

88126
.tag-badge {
89127
background: var(--ink);
90128
color: var(--paper);
91129
font-family: 'Syne', sans-serif;
92-
font-size: 0.7rem;
130+
font-size: 0.68rem;
93131
font-weight: 700;
94132
letter-spacing: 0.1em;
95133
text-transform: uppercase;
96134
padding: 0.3rem 0.7rem;
97135
border-radius: 2px;
98136
}
99137

100-
@media (max-width: 700px) {
101-
header { grid-template-columns: 1fr auto; }
102-
.header-right { display: none; }
103-
nav { gap: 1rem; }
138+
/* ── HAMBURGER ── */
139+
.menu-toggle {
140+
display: none;
141+
align-items: center;
142+
justify-content: center;
143+
width: 40px;
144+
height: 40px;
145+
background: none;
146+
border: 1.5px solid var(--border);
147+
border-radius: 4px;
148+
cursor: pointer;
149+
color: var(--ink);
150+
transition: border-color 0.2s, background 0.2s;
151+
flex-shrink: 0;
152+
}
153+
154+
.menu-toggle:hover {
155+
border-color: var(--ink);
156+
background: rgba(13, 15, 20, 0.04);
157+
}
158+
159+
.icon-close { display: none; }
160+
161+
.menu-toggle[aria-expanded="true"] .icon-menu { display: none; }
162+
.menu-toggle[aria-expanded="true"] .icon-close { display: block; }
163+
164+
/* ── MOBILE ── */
165+
@media (max-width: 680px) {
166+
#site-header {
167+
grid-template-columns: 1fr auto;
168+
padding: 0 1.25rem;
169+
min-height: 64px;
170+
}
171+
172+
nav {
173+
display: none;
174+
position: absolute;
175+
top: calc(100% + 2px); /* sits just below the border */
176+
left: 0;
177+
right: 0;
178+
flex-direction: column;
179+
gap: 0;
180+
background: var(--paper);
181+
border-bottom: 2px solid var(--ink);
182+
box-shadow: 0 8px 24px rgba(13, 15, 20, 0.08);
183+
}
184+
185+
nav.open {
186+
display: flex;
187+
animation: slideDown 0.2s ease both;
188+
}
189+
190+
@keyframes slideDown {
191+
from { opacity: 0; transform: translateY(-8px); }
192+
to { opacity: 1; transform: translateY(0); }
193+
}
194+
195+
nav a {
196+
padding: 1rem 1.5rem;
197+
border-top: 1px solid var(--border);
198+
font-size: 0.95rem;
199+
letter-spacing: 0.06em;
200+
}
201+
202+
nav a::after { display: none; }
203+
204+
nav a.active {
205+
color: var(--accent);
206+
background: rgba(0, 200, 150, 0.05);
207+
}
208+
209+
.tag-badge { display: none; }
210+
.menu-toggle { display: flex; }
104211
}
105212
</style>
213+
214+
<script>
215+
const header = document.getElementById('site-header');
216+
const toggle = document.getElementById('menu-toggle');
217+
const nav = document.getElementById('main-nav');
218+
219+
// Sticky shadow on scroll
220+
window.addEventListener('scroll', () => {
221+
header?.classList.toggle('scrolled', window.scrollY > 8);
222+
}, { passive: true });
223+
224+
// Toggle menu
225+
toggle?.addEventListener('click', () => {
226+
const isOpen = nav?.classList.toggle('open') ?? false;
227+
toggle.setAttribute('aria-expanded', String(isOpen));
228+
toggle.setAttribute('aria-label', isOpen ? 'Close menu' : 'Open menu');
229+
});
230+
231+
// Close on link click
232+
nav?.querySelectorAll('a').forEach(link => {
233+
link.addEventListener('click', () => {
234+
nav.classList.remove('open');
235+
toggle?.setAttribute('aria-expanded', 'false');
236+
toggle?.setAttribute('aria-label', 'Open menu');
237+
});
238+
});
239+
240+
// Close on outside click
241+
document.addEventListener('click', (e) => {
242+
if (!header?.contains(e.target as Node)) {
243+
nav?.classList.remove('open');
244+
toggle?.setAttribute('aria-expanded', 'false');
245+
toggle?.setAttribute('aria-label', 'Open menu');
246+
}
247+
});
248+
</script>

0 commit comments

Comments
 (0)