Added contact section. Refactored image carousel.

This commit is contained in:
jme9
2024-12-07 14:23:00 -08:00
parent 23aa5dd3fa
commit 1e4f01b1d7
8 changed files with 403 additions and 130 deletions
+89
View File
@@ -14,6 +14,7 @@ Note: _Revision 1 will be developed from 11/24/2024 to 12/11/2024._
- [About Section](#About-Section) - 12/03/2024
- [Project Section](#Project-Section) - 12/06/2024
- [Previous Work Section](#Previous-Work-Section) - 12/07/2024
- [Contact Section](#Contact-Section) - 12/07/2024
---
@@ -588,4 +589,92 @@ and add the following CSS to create a nice effect when hovering above each card.
}
```
---
## Contact Section
**12/07/2024**
Here the user should be prompted to enter their email and a message if they would like to collaborate or work.
We will use a simple form that allows input for an email and a message. The form will have a submit button and reset button a well a validation. Submitting the message should validate user input and send an email to my preferred email with their contact info and message. Resetting should clear the form.
So how do we send an email from the form? We also need to think about deployment to github pages. Here is a good stack about email options when deploying to GH pages.
- [Stack Overflow - Email from GitHub Pages](https://stackoverflow.com/questions/24348223/send-email-from-static-page-hosted-on-github-pages)
In this various people discuss options and honestly I thinkthe best option would be to use a third party service.
Here are some options:
- [Formspree](https://formspree.io/) - Free for < 50 emails per month - a little more concise
- [Postmark](https://postmarkapp.com/) - Free for < 100 emails per month - pretty robust functionality
I am going to try Formspree. Creating an account and setting up a form server is super easy!
Once set up it gives the server URL and method:
`action="https://formspree.io/f/xwpkjpal"`
`method="POST"`
We can now build our `html` form section and include our email server configuration.
```
<section id="contact" class="contact-section">
<div class="contact-box">
<h2 class="section-title">Contact</h2>
<form id="contact-form" method="POST" action="https://formspree.io/f/xwpkjpal" class="contact-form">
<label for="email">Your Email:</label>
<input type="email" id="email" name="email" placeholder="Enter your email" required>
<label for="message">Your Message:</label>
<textarea id="message" name="message" rows="5" placeholder="Enter your message" required></textarea>
<div class="form-buttons">
<button type="submit">Submit</button>
<button type="reset">Clear</button>
</div>
</form>
</div>
</section>
```
This works really well just as html however there are some things that I think need to be fixed.
- Successful submission redirects to a formspree confirmation screen.
- When returning to our site, the form is not cleared
Formspree includes an option to disable the redirect if you upgade to a paid account.. we don't want to do that. Instead we can fix these issues by taking control of what happens on submit action via JavaScript. We can submit the form using the fetch API and catch the response. We will want to handle potential errors as well as communciate to the user success or fail. We will use a simple alert message for both. On success, we will reset the form.
```
// On contact submit
document.getElementById("contact-form").addEventListener("submit", async function (event) {
// Take control of form submission
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
try {
// Submit the form data using the Fetch API and catch response
const response = await fetch(form.action, {
method: form.method,
body: formData,
headers: {
Accept: "application/json",
},
});
if (response.ok) {
// Success! give user a message
alert("Thank you! Your message has been sent.");
form.reset(); // Clear the form
} else {
// Fail :( give user a message
alert("Oops! There was a problem submitting your form.");
}
} catch (error) {
// Log any errors
console.error("Error:", error);
alert("There was a problem submitting your form. Please try again later.");
}
});
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

+69 -10
View File
@@ -45,32 +45,47 @@
<section id="about" class="about-section">
<div class="about-box">
<!-- First Column: About Text -->
<div class="grid-container">
<h1 class="about-box-title">About</h1>
<section class="grid-item">
<div class="about-text">
<h1 class="about-title">About Jesse,</h1>
<h1 class="about-title">About me,</h1>
<p class="about-paragraph">Loading...</p>
</div>
<!-- Second Column: Images -->
<div class="about-image">
<div class="image-container">
<img
src="images/Figma.jpg"
src="images/LunaLightConcept.png"
alt="About Me Images"
class="current-image"
/>
<p class="image-title">LunaLight Concept Work by @toastydoodles</p>
</div>
<div class="image-container hidden-image">
<img
src="images/Clouds.png"
alt="About me images"
class="hidden-image"
alt="About Me Images"
/>
<p class="image-title">Cloud Photography</p>
</div>
<div class="image-container hidden-image">
<img
src="images/SiteUpdate1.jpg"
alt="About me images"
class="hidden-image"
alt="About Me Images"
/>
<p class="image-title">Website Update Mockup</p>
</div>
<div class="about-buttons">
<button class="prev-btn">&larr;</button>
<button class="next-btn">&rarr;</button>
</div>
</div>
</section>
</div>
</div>
</section>
@@ -83,7 +98,7 @@
<section class="grid-item">
<!-- Project 1 First Column: Project Overview Text -->
<div class="project-text">
<h1 class="project-title">LunaLight - A Retro Game Project,</h1>
<h1 class="project-title">LunaLight - A Retro Game Project</h1>
<p id="project1-paragraph" class="project-paragraph">
Loading...
</p>
@@ -109,7 +124,7 @@
<!-- Project 2 First Column: Project Overview Text -->
<div class="project-text">
<h1 class="project-title">
EightBiterator - A Retro Game Melody Generator,
EightBiterator - A Retro Game Melody Generator
</h1>
<p id="project2-paragraph" class="project-paragraph">
Loading...
@@ -120,6 +135,23 @@
<h1 class="project-title">EightBiterator</h1>
</div>
</section>
<!-- Project 3 -->
<section class="grid-item">
<!-- Project 2 First Column: Project Overview Text -->
<div class="project-text">
<h1 class="project-title">
Capstone - A Mobile App for the CRN
</h1>
<p id="project3-paragraph" class="project-paragraph">
Loading...
</p>
</div>
<!-- Project 2 Second Column: Form -->
<div class="project3-video-container">
<h1 class="project-title">Capstone Demo</h1>
</div>
</section>
</div>
</div>
</section>
@@ -182,11 +214,38 @@
<section id="contact" class="contact-section">
<div class="contact-box">
<h2 class="section-title">Contact</h2>
<h1 class="contact-section-title">ᛜ We'd love to hear from you! ᛜ</h1>
<form
id="contact-form"
method="POST"
action="https://formspree.io/f/xwpkjpal"
class="contact-form"
>
<label for="email">Your Email:</label>
<input
type="email"
id="email"
name="email"
placeholder="Enter your email"
required
/>
<label for="message">Your Message:</label>
<textarea
id="message"
name="message"
rows="5"
placeholder="Enter your message"
required
></textarea>
<div class="form-buttons">
<button type="submit">Submit</button>
<button type="reset">Clear</button>
</div>
</form>
</div>
</section>
<section id="title" class="title-section"></section>
<section class="page-bottom-section"></section>
</div>
</body>
</html>
+50 -21
View File
@@ -1,15 +1,16 @@
// Scroll page
document.querySelectorAll(".nav-link").forEach((anchor) => {
document.addEventListener("DOMContentLoaded", () => {
// Scroll page
document.querySelectorAll(".nav-link").forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
document.querySelector(this.getAttribute("href")).scrollIntoView({
behavior: "smooth",
});
});
});
});
// dynamic scrolling
document.addEventListener("scroll", () => {
// dynamic scrolling
document.addEventListener("scroll", () => {
const navBar = document.querySelector(".navbar");
const titleBox = document.querySelector(".title-box");
const aboutSection = document.querySelector("#about");
@@ -29,10 +30,10 @@ document.addEventListener("scroll", () => {
titleBox.style.transform = "translateY(0)";
titleBox.style.opacity = 0;
}
});
});
// Function to load text from a file into an html component
function loadTextFromFile(filePath, targetSelector) {
// Function to load text from a file into an html component
function loadTextFromFile(filePath, targetSelector) {
fetch(filePath)
.then((response) => {
if (!response.ok) {
@@ -51,44 +52,72 @@ function loadTextFromFile(filePath, targetSelector) {
.catch((error) => {
console.error("Error loading text from file:", error);
});
}
}
// Load text files when page loads
window.addEventListener("load", () => {
// Load text files when page loads
window.addEventListener("load", () => {
loadTextFromFile("text/about.txt", ".about-paragraph");
loadTextFromFile("text/project1.txt", "#project1-paragraph");
loadTextFromFile("text/project2.txt", "#project2-paragraph");
loadTextFromFile("text/project3.txt", "#project3-paragraph");
loadTextFromFile("text/airship.txt", "#airship-text");
loadTextFromFile("text/jama.txt", "#jama-text");
loadTextFromFile("text/intel.txt", "#intel-text");
});
});
// Image carousel
document.addEventListener("DOMContentLoaded", () => {
const images = document.querySelectorAll(".about-image img");
// Image carousel
const images = document.querySelectorAll(".image-container");
const prevBtn = document.querySelector(".prev-btn");
const nextBtn = document.querySelector(".next-btn");
let currentIndex = 0;
// Function to update image visibility
function updateImages() {
images.forEach((img, index) => {
img.classList.toggle("current-image", index === currentIndex);
img.classList.toggle("hidden-image", index !== currentIndex);
});
}
// Event listeners for navigation buttons
prevBtn.addEventListener("click", () => {
currentIndex = (currentIndex - 1 + images.length) % images.length;
updateImages();
});
nextBtn.addEventListener("click", () => {
currentIndex = (currentIndex + 1) % images.length;
updateImages();
});
updateImages();
// On contact submit
document
.getElementById("contact-form")
.addEventListener("submit", async function (event) {
// Take control of form submission
event.preventDefault();
const form = event.target;
const formData = new FormData(form);
try {
// Submit the form data using the Fetch API and catch response
const response = await fetch(form.action, {
method: form.method,
body: formData,
headers: {
Accept: "application/json",
},
});
if (response.ok) {
// Success! give user a message
alert("Thank you! Your message has been sent.");
form.reset(); // Clear the form
} else {
// Fail :( give user a message
alert("Oops! There was a problem submitting your form.");
}
} catch (error) {
// Log any errors
console.error("Error:", error);
alert(
"There was a problem submitting your form. Please try again later."
);
}
});
});
+119 -27
View File
@@ -66,7 +66,8 @@ body {
margin-top: 2rem;
}
.title-section {
.title-section,
.page-bottom-section {
position: relative;
height: 70vh;
display: flex;
@@ -124,6 +125,24 @@ body {
max-width: 1440px;
}
.grid-container {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
width: 100%;
}
.grid-item {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
background: rgba(11, 35, 48, 0.8);
padding: 2rem;
border-radius: 20px;
color: #ffe8e8;
align-items: center;
}
/* About box */
.about-box {
@@ -140,6 +159,11 @@ body {
height: auto;
}
.about-box-title {
text-align: center;
color: #ffe8e8;
}
.about-text {
flex: 1;
}
@@ -156,29 +180,42 @@ body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 520px;
justify-content: flex-start;
height: 500px;
width: 100%;
overflow: hidden;
border-radius: 10px;
position: relative;
background-color: #001316;
}
.about-image img {
.image-container {
text-align: center;
display: none;
height: 400px;
width: 95%;
margin: 10PX;
justify-content: center;
align-items: center;
}
.image-container img {
max-width: 100%;
max-height: 100%;
max-height: calc(100% - 40px);
object-fit: contain;
border-radius: 10px;
transition: opacity 0.5s ease;
}
.hidden-image {
display: none;
.image-container.current-image {
display: block;
}
.current-image {
display: block;
height: calc(100% - 60px);
.image-title {
margin-top: 10px;
font-size: 1rem;
color: #ffe8e8;
font-weight: bold;
}
.about-buttons {
@@ -211,23 +248,7 @@ button:hover {
/* Projects Box */
.grid-container {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
width: 100%;
}
.grid-item {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
background: rgba(11, 35, 48, 0.8);
padding: 2rem;
border-radius: 20px;
color: #ffe8e8;
align-items: center;
}
.project-box {
display: flex;
@@ -293,6 +314,27 @@ button:hover {
position: relative;
}
/* Project 3 */
.project3-video-container {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 100%;
width: 100%;
border-radius: 10px;
position: relative;
}
.project3-video {
width: 100%;
height: 100%;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
/* Previous Work */
.work-box {
@@ -369,12 +411,62 @@ button:hover {
border-radius: 20px;
padding: 1rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
width: 95%;
width: 500px;
color: #ffe8e8;
box-sizing: border-box;
text-align: left;
}
.contact-section-title {
text-align: center;
font-size: large;
color: #ffe8e8;
padding: 3rem;
}
.contact-form {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 400px;
margin: 0 auto;
}
.contact-form label {
font-weight: bold;
}
.contact-form input, .contact-form textarea {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-family: 'Courier New', Courier, monospace;
}
.form-buttons {
display: flex;
justify-content: center;
gap: 10px;
}
.form-buttons button {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
}
button[type="submit"] {
background-color: #336854;
color: white;
}
button[type="reset"] {
background-color: #06425a;
color: white;
}
/* Media Queries */
@media (min-width: 1440px) {
+2 -4
View File
@@ -1,5 +1,3 @@
Hey! This section is about me!
Im a creative and resourceful problem-solver with a deep passion for blending technology and art. With a foundation as an Engineering Technician and years of freelance experience in illustration, graphic design and music, I bring a unique perspective that unites technical expertise with creative vision. My love for game development, music, and illustration has driven me to craft meaningful projects, including a retro game development initiative that showcases my dedication to combining art and technology.
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC.
As a recent Computer Science graduate from Portland State University, Ive honed my skills in web and mobile app development, backend architecture, and project leadership. Through hands-on internships and academic projects, Ive built dynamic web applications, cross-platform mobile apps, and integrated APIs, working with diverse programming languages like Python, Java, C#, C++, JavaScript, HTML, and CSS. Leading a collaborative capstone project further solidified my ability to guide teams and deliver impactful software solutions. I thrive at the intersection of creativity and technology, continuously pushing boundaries to create innovative and engaging experiences.
+5 -2
View File
@@ -1,3 +1,6 @@
Hey! This section is about my retro game project!
This game project is what got me into software development!
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
LunaLight is a sci-fi fantasy rogue-ish platformer built in the retro style of our favoite games from the late 80's and early 90's.
As someone with a background in art, music and engineering I needed a way to put all of my interests together. Game development is exactly what I was looking for!
In this project I am the writer, designer, pixel artist, music composer and software developer. The game is built using the C# framework MonoGame and will hopefully be available to demo very soon!
+3
View File
@@ -0,0 +1,3 @@
Hey! This section is about my retro game melody generator!
Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.