feat(services-cards): add accordion feature to section

This commit is contained in:
Derek L. Seitz 2025-08-16 21:44:13 -05:00
parent d742364f46
commit b109387471
9 changed files with 231 additions and 102 deletions

View File

@ -4,6 +4,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ metaDesc }}">
<title>{{title}}</title>
@ -11,6 +12,11 @@
<link rel="stylesheet" href="/styles/base.css">
<link rel="stylesheet" href="/styles/header-footer.css">
<link rel="stylesheet" href="{{ stylesheet }}">
{% if hCAPTCHA %}
<script src="{{ hCAPTCHA }}" async defer></script>
{% endif %}
</head>
<body>

View File

@ -1,15 +1,17 @@
---
layout: base.njk
metaDesc: "Learn about Derek L. Seitz's values as a freelance developer, his commitment to accessible design, and his collaborative approach to web development."
title: "About Derek"
stylesheet: /styles/about.css
isLandingPage: false
hCAPTCHA: "https://hcaptcha.com/1/api.js"
pageScripts:
- "/scripts/contact-form.js"
- "/scripts/accordion.js"
---
<section class="about-section module" id="bio" role="region" aria-labelledby="about-heading">
<h1 id="about-heading">Who <span class="i-am">I</span> Am</h1>
<h2 id="about-heading">Who <span class="i-am">I</span> Am</h2>
<p>Im a freelance developer dedicated to building clean, functional, and accessible web experiences. But beyond the code, Im someone driven by a deep sense of purpose and a commitment to creating digital solutions that <strong>genuinely</strong> serve people.</p>
<p>My approach is simple: solve real problems and build with integrity. I believe great websites dont just look good—they respect users time, adapt to diverse needs, and are built on a foundation of honesty and transparency. Thats why I communicate openly with clients, build trust through consistency, and deliver work that is both technically sound and thoughtfully designed.</p>
<p class="click">Click on the cards below to learn more about what makes me who I am.</p>
@ -106,10 +108,15 @@ pageScripts:
<textarea id="message" name="message" rows="15" placeholder="What questions or feedback do you have for me?" required aria-describedby="message-error"></textarea>
<span class="error-message" id="message-error" aria-live="polite"></span>
</fieldset>
<div class="h-captcha" data-sitekey="b63e5b64-c6f2-4154-b5a6-77169a924022"></div>
<div class="submit-reset">
<button type="submit">Send Message</button>
<button type="reset">Reset Form</button>
</div>
<div class="honeypot-field" aria-hidden="true">
<label for="url">Website URL</label>
<input type="text" name="url" id="url" autocomplete="off" tabindex="-1">
</div>
</div>
</div>
<p class="form-note">* Required fields</p>

View File

@ -1,11 +1,12 @@
---
layout: base.njk
metaDesc: "Elevate your business with a custom, high-quality website. Derek L. Seitz provides professional web design and development to help you grow your online presence."
title: "Derek L. Seitz - Portfolio"
stylesheet: styles/index.css
isLandingPage: true
pageScripts:
- "/scripts/benefits.js"
- "/scripts/carousel.js"
- "/scripts/services.js"
---
@ -76,50 +77,52 @@ pageScripts:
<!--? This section will feature a carousel of services on cards. The carousel will be scrollable, and when the card is clicked or touched, it will expand to provide a clearer description of the service. This will be accomplished with JavaScript-->
<div class="services-section module">
<div class="carousel-container">
<h2>The Ways I Can Help</h2>
<div class="accordion-container">
<h2>How I Can Help</h2>
<p>Click each button below to see ways I can help!</p>
<button class="carousel-button prev-button">&#10094;</button>
<div class="service-cards">
<!-- Website Design -->
<div class="service-card">
<h3>Website Design</h3>
<ul class="design-service">
<li>Custom web design & user experience (UX) services</li>
<li>Discovery, planning, & wireframing</li>
<li>Website redesign & optimization</li>
</ul>
<div class="accordion">
<div class="accordion-item">
<button class="accordion-header">Website Design</button>
<div class="accordion-content">
<ul class="design-service">
<li>Custom web design & user experience (UX) services</li>
<li>Discovery, planning, & wireframing</li>
<li>Website redesign & optimization</li>
</ul>
</div>
</div>
<!-- Website Development -->
<div class="service-card">
<h3>Website Development</h3>
<ul class="development-service">
<li>Full-stack web development (Front-End & Back-End)</li>
<li>Custom solutions (e.g., CMS, eCommerce, booking systems)</li>
<li>Ongoing maintenance & support</li>
</ul>
<div class="accordion-item">
<button class="accordion-header">Website Development</button>
<div class="accordion-content">
<ul class="development-service">
<li>Full-stack web development (Front-End & Back-End)</li>
<li>Custom solutions (e.g., CMS, eCommerce, booking systems)</li>
<li>Ongoing maintenance & support</li>
</ul>
</div>
</div>
<!-- Other Services -->
<div class="service-card">
<h3>Other Related Services</h3>
<ul class="other-service">
<li>Basic SEO setup & Google Analytics integration</li>
<li>Hosting services</li>
<li>Deployment</li>
<li>E-commerce</li>
</ul>
<div class="accordion-item">
<button class="accordion-header">Other Related Services</button>
<div class="accordion-content">
<ul class="other-service">
<li>Basic SEO setup & Google Analytics integration</li>
<li>Hosting services</li>
<li>Deployment</li>
<li>E-commerce</li>
</ul>
</div>
</div>
</div>
<button class="carousel-button next-button">&#10095;</button>
</div>
</div>
<!-- ↓ Start Services Module ↓ -->
<!--? This section will feature a carousel of services on cards. The carousel will be scrollable, and when the card is clicked or touched, it will expand to provide a clearer description of the service. This will be accomplished with JavaScript -->
<!-- ↓ Start CTA Module ↓ -->
<div class="cta-section module">

View File

@ -23,6 +23,28 @@ form.addEventListener("submit", function(event) {
clearErrors(); // Clear previous errors
const honeypotField = document.getElementById("url").value.trim();
// Add the honeypot check at the top
if (honeypotField.length > 0) {
console.warn("Honeypot field was filled. Blocking submission.");
// You might want to display a message to the user,
// but it's often better to fail silently to not alert the bot.
// For debugging, you can add an alert:
// alert("Submission failed due to security check.");
return; // This is the most important part: stop the function here.
}
// Get the hCaptcha response token
const hCaptchaResponse = document.querySelector('[name="h-captcha-response"]').value;
// Check if the hCaptcha token is missing
if (!hCaptchaResponse) {
// You might want to display a user-facing error here
console.warn("hCaptcha token is missing. Submission blocked.");
alert("Please complete the hCaptcha verification.");
hasErrors = true;
}
// Get values
const firstName = document.getElementById("first-name").value.trim();
const lastName = document.getElementById("last-name").value.trim();
@ -79,9 +101,45 @@ form.addEventListener("submit", function(event) {
}
if (!hasErrors) {
alert("Form submitted successfully!");
// form.submit(); // Uncomment when ready to send to server
}
// Package the form data into an object
const formData = {
firstName: firstName,
lastName: lastName,
organization: organization,
email: email,
phone: phone,
contactMethod: contactMethod,
message: message,
url: honeypotField,
hCaptchaResponse: hCaptchaResponse
};
// Send the data to the backend using fetch()
fetch('/api/submit-form', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData), // Convert the data object to a JSON string
})
.then(response => {
if (response.ok) {
return response.json(); // Parse the JSON response
}
throw new Error('Network response was not ok.');
})
.then(data => {
// Success: handle the server's response
console.log('Success:', data);
alert('Form submitted successfully!');
form.reset(); // Optionally, reset the form after successful submission
})
.catch((error) => {
// Error: handle any network or server errors
console.error('Error:', error);
alert('An error occurred during submission. Please try again.');
});
}
});
form.addEventListener("reset", function (event) {

17
src/scripts/services.js Normal file
View File

@ -0,0 +1,17 @@
document.addEventListener('DOMContentLoaded', function() {
const accordionHeaders = document.querySelectorAll('.accordion-header');
accordionHeaders.forEach(header => {
header.addEventListener('click', function() {
const content = this.nextElementSibling;
// Check if maxHeight has a value. If it does, the accordion is open.
if (content.style.maxHeight) {
content.style.maxHeight = null; // Close the accordion
} else {
// The accordion is closed, open it by setting max-height to its scroll height
content.style.maxHeight = content.scrollHeight + 'px';
}
});
});
});

View File

@ -20,7 +20,7 @@ body {
}
.i-am {
color: #ea7e0b;
color: #ca6e0b;
font-weight: bold;
}
@ -42,7 +42,7 @@ body {
.click {
font-weight: bold;
color:#ea7e0b;
color:#ca6e0b;
margin-top: 20px;
text-align: center;
}
@ -81,7 +81,7 @@ body {
}
.w3-acag21 {
color: #ea7e0b;
color: #ca6e0b;
}
/*? ↓ Contact Section Styles ↓ */
@ -208,7 +208,7 @@ textarea:focus {
button[type="submit"], button[type="reset"] {
margin:20px 0px 20px 0px;
padding: 8px 25px;
background-color: #ea7e0b;
background-color: #ca6e0b;
color: white;
border: none;
border-radius: 50px;
@ -221,6 +221,10 @@ button[type="submit"]:hover, button[type="reset"]:hover {
background-color: #2e97be;
}
.honeypot-field {
display: none;
}
/* Responsive tweaks */
@media (max-width: 768px) {
.form-sections {

View File

@ -3,7 +3,7 @@ body {
background-image: url("/images/pattern-randomized.svg");
background-attachment: fixed;
background-size: cover;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.intro.module {
@ -25,10 +25,11 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
.intro h1 {
color: #495057;
margin-bottom: 20px;
line-height: 1.2em;
}
.intro h1 .accent-name {
color: #ea7e0b;
color: var(--primary-color);
}
.intro p {
@ -54,8 +55,12 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin-bottom: 20px;
}
.benefits-container h2, .accordion-container h2, .project-steps h2 {
font-color: #2e97be;
}
.benefits-container p, .transition p {
font-size: 1.2em;
font-size: 1.1em;
font-weight: 600;
}
@ -68,6 +73,13 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
min-height: 150px;
}
/* Correct media query to force a single column layout below 950px */
@media (max-width: 950px) {
.benefits-visual {
grid-template-columns: 1fr;
}
}
.benefits-card {
display: flex;
flex-direction: column;
@ -78,7 +90,7 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
border-radius: 10px;
padding: 15px 10px 15px 10px;
text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease-in-out;
overflow: hidden;
}
@ -86,7 +98,7 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
.benefits-card:hover {
transform: translateY(-5px);
background: linear-gradient(to right, #DDF0FF 0%, #fff 40%, #fff 60%, #DDF0FF 100%);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
}
.benefits-card h3 {
@ -130,92 +142,114 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 1.4rem;
}
/*? ↓  Start Services Container  ↓ */
.carousel-container {
.services-section.module {
max-width: 850px;
}
.accordion-container {
position: relative;
width: 100%;
max-width: 800px;
margin: 10px auto;
overflow: hidden;
min-height: 400px;
min-height: 150px;
background-color: transparent; /* Ensure no background color is affecting the text */
}
.carousel {
.accordion-container h2, .accordion-container p {
margin-bottom: 20px;
}
.accordion-container p {
font-weight: 600;
color: #333333;
}
.accordion {
display: flex;
transition: transform 0.5s ease-in-out;
height: 100%;
}
.service-cards {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
.accordion-item {
position: relative;
width: 100%;
max-width: 1200px;
margin: 50px auto;
}
.service-card {
width: 300px;
background-color: #ffffff;
border: 1px solid #dee2e6;
background-color: #ffffff; /* Ensure a solid white background */
border-radius: 10px;
padding: 20px;
text-align: center;
box-sizing: border-box;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
opacity: 0.8;
transition: opacity 0.3s ease, transform 0.3s ease;
}
.service-card h3 {
font-size: 1.3em;
color: #495057;
background-color: #f8f9fa;
.accordion-item.active {
opacity: 1; /* Fully opaque for the focused card */
transform: scale(1.1);
z-index: 1;
min-height: 318px; /* Ensure the focused card is above others */
}
/* Styles for an active (open) accordion item */
.accordion-item.active .accordion-content {
max-height: 500px; /* This value should be large enough to contain all content */
padding: 15px; /* Adds padding back when open */
border-top: 1px solid #dee2e6; /* Adds a border to separate header and content */
}
.accordion-header {
width: 100%;
background-color: #ddf0ff;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 10px;
box-shadow: none;
margin-bottom: 15px;
}
.service-card li {
padding: 10px;
color: #495057;
}
.service-card ul {
list-style: none;
padding: 0;
}
.carousel-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(46, 151, 190, 0.8);
color: #fff;
border: none;
padding: 10px 15px;
border-radius: 5px;
font-size: 1.3em;
color: #333333;
cursor: pointer;
font-size: 20px;
z-index: 1;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: background-color 0.3s ease;
}
.carousel-button:hover {
background-color: #ea7e0b;
.accordion-header:hover {
background-color: #ca6e0b;
color: #fff;
}
.prev-button {
left: 10px;
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
background-color: #ffffff; /* Ensure a solid white background */
padding: 0 15px;
color: #333333; /* Explicitly set text color to dark gray */
}
.next-button {
right: 10px;
.accordion-content ul {
list-style: none;
padding: 0;
min-height: 192px;
}
.accordion-content li {
padding: 10px;
color: #333333; /* Explicitly set text color to dark gray */
}
.hover-list {
transition: max-height 0.4s ease-in-out, opacity 0.4s ease-in-out;
max-height: 0;
opacity: 0;
overflow: hidden;
}
.hover-list.active {
max-height: 300px; /* Adjust this value if your lists get longer */
opacity: 1;
}
/*? ↓  Start CTA Section  ↓ */
@ -241,7 +275,7 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
display: inline-block;
padding: 8px 25px;
margin: 20px 0px 20px 0px;
background-color: #ea7e0b;
background-color: #ca6e0b;
color: #fff;
text-decoration: none;
font-weight: 600;
@ -256,7 +290,7 @@ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.demos-link:active, .cta-button:active {
background-color: #ea7e0b;
background-color: #ca6e0b;
box-shadow: 0 4px 8px rgba(46, 151, 190, 0.2);
}

View File

@ -1,7 +1,7 @@
:root {
/* Color Palette */
--primary-color: #2e97be;
--secondary-color: #ea7e0b;
--secondary-color: #ca6e0b;
--text-color: #495057;
--light-bg-color: #ffffff;
--light-gray-color: #f8f9fa;