-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
147 lines (122 loc) · 4.26 KB
/
Copy pathscript.js
File metadata and controls
147 lines (122 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SECTION: Helpers
const select = (selector, scope = document) => scope.querySelector(selector);
const selectAll = (selector, scope = document) => Array.from(scope.querySelectorAll(selector));
// SECTION: Smooth scrolling for nav links
selectAll('a[href^="#"]').forEach((link) => {
link.addEventListener('click', (event) => {
const targetId = link.getAttribute('href');
if (!targetId || targetId === '#') return;
const targetEl = document.querySelector(targetId);
if (!targetEl) return;
event.preventDefault();
const headerOffset = 70;
const elementPosition = targetEl.getBoundingClientRect().top + window.scrollY;
const offsetPosition = elementPosition - headerOffset;
window.scrollTo({
top: offsetPosition,
behavior: 'smooth',
});
});
});
// SECTION: Nav highlight on scroll
const sections = selectAll('main section[id]');
const navLinks = selectAll('.nav-list a');
const highlightNav = () => {
const scrollPosition = window.scrollY;
const offset = 120;
let currentId = '';
sections.forEach((section) => {
const sectionTop = section.offsetTop - offset;
if (scrollPosition >= sectionTop) {
currentId = section.id;
}
});
navLinks.forEach((link) => {
const href = link.getAttribute('href') || '';
const id = href.startsWith('#') ? href.substring(1) : '';
if (id === currentId) {
link.classList.add('is-active');
} else {
link.classList.remove('is-active');
}
});
};
window.addEventListener('scroll', highlightNav);
window.addEventListener('load', highlightNav);
// SECTION: Mobile nav toggle
const navToggle = select('.nav-toggle');
const navList = select('.nav-list');
if (navToggle && navList) {
navToggle.addEventListener('click', () => {
const expanded = navToggle.getAttribute('aria-expanded') === 'true';
navToggle.setAttribute('aria-expanded', String(!expanded));
navList.classList.toggle('nav-open');
});
}
// Close mobile nav when clicking a link
navLinks.forEach((link) => {
link.addEventListener('click', () => {
if (navList.classList.contains('nav-open')) {
navList.classList.remove('nav-open');
navToggle.setAttribute('aria-expanded', 'false');
}
});
});
// Close mobile nav when clicking outside
document.addEventListener('click', (e) => {
if (
navList &&
navToggle &&
navList.classList.contains('nav-open') &&
!navList.contains(e.target) &&
!navToggle.contains(e.target)
) {
navList.classList.remove('nav-open');
navToggle.setAttribute('aria-expanded', 'false');
}
});
// SECTION: Contact form handling (Formspree)
// Sign up at https://formspree.io, create a form, and replace YOUR_FORM_ID below.
const FORMSPREE_ENDPOINT = 'https://formspree.io/f/xpqoqqdk';
const contactForm = select('#contact-form');
const formHelper = select('#form-helper');
if (contactForm && formHelper) {
contactForm.addEventListener('submit', async (event) => {
event.preventDefault();
const formData = new FormData(contactForm);
const name = String(formData.get('name') || '').trim();
const email = String(formData.get('email') || '').trim();
const message = String(formData.get('message') || '').trim();
if (!name || !email || !message) {
formHelper.textContent = 'Please fill in all fields before submitting.';
formHelper.style.color = '#f97373';
return;
}
formHelper.textContent = 'Sending…';
formHelper.style.color = 'var(--color-text-muted)';
try {
const response = await fetch(FORMSPREE_ENDPOINT, {
method: 'POST',
body: formData,
headers: { Accept: 'application/json' },
});
if (response.ok) {
formHelper.textContent = 'Message sent! I\'ll get back to you soon.';
formHelper.style.color = '#34d399';
contactForm.reset();
} else {
const data = await response.json();
formHelper.textContent = (data?.errors?.[0]?.message) || 'Something went wrong. Please try again.';
formHelper.style.color = '#f97373';
}
} catch {
formHelper.textContent = 'Network error. Please try again later.';
formHelper.style.color = '#f97373';
}
});
}
// SECTION: Footer year
const yearSpan = select('#year');
if (yearSpan) {
yearSpan.textContent = String(new Date().getFullYear());
}