
Excursion Plan
Excursion Plan Website
Developed a website to help users plan outdoor excursions such as hiking or camping. Features include a suggested packing list, a customizable field to add new items, and an inventory tracker that calculates total pack weight.
Project Type: Website Development
Year: 2025
Software & Languages Used
Visual Studio Code, HTML, CSS, JavaScript
Key Takeaways
Takeaways: This was my first major project using JavaScript, and it really showed me how much more complex things get once you start adding functionality beyond HTML and CSS. Compared to this, styling a page seems like light work now. JavaScript is an incredibly powerful tool; it adds so much interactivity to a site, but it can also get overwhelming fast. This project helped me start wrapping my head around how to break down problems and use code to build something that actually does something.
Successes: One of the features I think is nice to have is the ability to sort your pack. Being able to set a weight limit and sort your items makes the program useful for planning out a trip. I also really like the customization aspect, you can create your own items, which means each journey is completely customizable depending on what you bring. That flexibility adds a fun, personal touch.
Changes: If I had more time, the first thing I’d improve is the visual design. The current version is very bare-bones, mostly because the project deadline had a short turnaround and I had to prioritize getting the functionality to work. It does what it’s supposed to, but the interface could definitely be more user-friendly and visually engaging. I’d also love to add preset journeys that you could choose from, like a mountain hike, desert trek, or camping trip, which would auto-pack your bag with common items and give you a starting point to build from.
Using This Experience for the Future: This project gave me a solid introduction to writing JavaScript that actually solves problems and enhances the user experience. It taught me how to think logically through the different parts of an application and how to plan features based on real user needs. Moving forward, I want to keep building on this foundation, especially by improving my ability to write cleaner, more organized code and create interfaces that are both functional and fun to use. I’m excited to take on more JavaScript-heavy projects in the future and eventually dive into more advanced tools. This experience helped me see what's possible, and now I’m ready to keep leveling up.
Software Used
Twine, Affinity Designer
Project Brief
Create a page to help a visitor plan a hike, a camping trip, or some other type of excursion. The page should look professional, be easy to use, and have a responsive design.
Requirements:
-
Provide an area of the page to show their personalized list of items to bring on the excursion
-
Provide a list of common items that can be easily selected and moved to the inventory list (at least 5)
-
Each item needs a name and a weight
-
Provide a method for the visitor to type a new item to add to the list (including the weight)
-
The page should use local storage to remember the list of items with weights between page loads
Extra Credit:
-
Let the user set a weight limit, and indicate when they have exceeded the limit.
-
Allow the visitor to arrange the personalized list in order by weight.
NOTE: This was developed as part of an academic project at FLCC.
Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Excursion Plan</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: #fff;
color: #000;
font-family: Arial, Helvetica, sans-serif;
text-align: center;
padding: 0.5rem;
}
h1, h2, h3, h4 {
margin: 0.5rem 0;
}
h1 {
font-size: 3rem;
}
h2 {
font-size: 1.9rem;
}
hr {
border: 1px solid rgb(0, 0, 0);
border-radius: 1px;
margin: 10px;
}
#inputSection {
background-color: #fffbb8;
border: 2px solid #000;
border-radius: 10px;
padding: 1rem;
margin: 1rem auto;
width: 90%;
}
#inputSection h4 {
display: inline-block;
margin-right: 0.5rem;
margin-left: 1rem;
}
#inputSection input {
display: inline-block;
width: 200px;
}
#flexContainer {
display: flex;
justify-content: center;
gap: 2%;
width: 98%;
margin: 0 auto;
flex-wrap: wrap;
}
#itemsDiv, #inventoryDiv {
background-color: #fffbb8;
border: 2px solid #000;
border-radius: 10px;
padding: 1rem;
width: 45%;
}
#itemsSection, #inventoryList {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
margin-top: 1rem;
margin-bottom: 2rem;
}
.item {
background-color: #f9ee96;
border: 1.5px solid black;
border-radius: 10px;
padding: 1rem;
width: 200px;
max-width: 300px;
min-height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.item-name {
font-weight: bold;
margin-bottom: 0.5rem;
}
.item-weight {
margin-bottom: 0.5rem;
font-size: small;
}
.overLimit {
color: red;
font-weight: bold;
margin-top: 0.5rem;
}
input {
padding: 0.35rem;
margin: 0.35rem 0;
width: 200px;
max-width: 300px;
}
#weightLimit {
padding: 0.35rem;
margin: 0.35rem 0;
width: 100px;
max-width: 200px;
}
#weightLimitWrapper {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
margin: 1rem 0;
}
button {
padding: 0.35rem 1rem;
margin: 0.25rem;
border: 1px solid #000;
border-radius: 6px;
background-color: #ffda5f;
cursor: pointer;
transition: background-color 0.2s ease;
font-weight: bold;
}
.item button {
padding: 0.35rem 1rem;
margin: 0.25rem;
border: 1px solid #000;
border-radius: 6px;
background-color: #ffda5f;
cursor: pointer;
transition: background-color 0.2s ease;
font-weight: normal;
}
button:hover {
background-color: #ddb927;
}
@media screen and (max-width: 768px) {
#flexContainer {
flex-direction: column;
align-items: center;
width: 100%;
}
#itemsDiv, #inventoryDiv {
width: 90%;
margin-bottom: 1rem;
}
.item {
width: 100%;
}
input {
width: 100%;
}
#weightLimitWrapper {
display: inline-block;
}
}
@media screen and (max-width: 1024px) {
#flexContainer {
flex-direction: column;
align-items: center;
width: 100%;
}
#itemsDiv, #inventoryDiv {
width: 90%;
margin-bottom: 1rem;
}
.item {
width: 100%;
max-width: 300px;
}
input {
width: 100%;
max-width: 300px;
}
#weightLimitWrapper {
display: inline-block;
}
}
</style>
</head>
<body>
<h1>Final Project - Excursion Plan</h1>
<h4>Choose a weight limit for your pack and add all the necessary items for your journey.</h4>
<div id="inputSection">
<h2>Create New Item</h2>
<h4>Enter Item Name:</h4>
<input type="text" id="itemName" placeholder="Item Name Here">
<h4>Enter Item Weight:</h4>
<input type="number" id="itemWeight" placeholder="Item Weight Here (lbs)">
<button onclick="addCustomItem()">Add to Pack</button>
</div>
<div id="flexContainer">
<div id="itemsDiv">
<h2>Common Items</h2>
<section id="itemsSection"></section>
</div>
<div id="inventoryDiv">
<h2>Pack Inventory</h2>
<p>Weight Limit: <strong><span id="weightLimitDisplay">0</span> lbs</strong></p>
<p>Total Pack Weight: <strong><span id="totalWeight">0</span> lbs</strong></p>
<p id="weightWarning" class="overLimit" style="display:none;">⚠️ WEIGHT LIMIT EXCEEDED ⚠️</p>
<div id="weightLimitWrapper">
<label for="weightLimit"><strong>Set Weight Limit (lbs):</strong></label>
<input type="number" id="weightLimit" placeholder="Limit Here"/>
<button onclick="saveWeightLimit()">Set Limit</button>
</div>
<hr>
<button onclick="sortByName()">Alphabetize</button>
<button onclick="sortByWeight()">Sort by Weight</button>
<section id="inventoryList"></section>
</div>
</div>
<script>
"use strict";
const commonItemsSection = document.getElementById("itemsSection");
const inventorySection = document.getElementById("inventoryList");
const packWeightOutput = document.getElementById("totalWeight");
const weightWarning = document.getElementById("weightWarning");
let inventoryItems = JSON.parse(localStorage.getItem("inventoryItems")) || [];
let weightLimit = localStorage.getItem("weightLimit") || 0;
document.getElementById("weightLimit").value = weightLimit;
if (isNaN(weightLimit) || weightLimit === "") {
document.getElementById("weightLimitDisplay").textContent = "0";
} else {
document.getElementById("weightLimitDisplay").textContent = parseFloat(weightLimit).toFixed(1);
}
const commonItems = [
{ name: "Water Bottle", weight: 1 },
{ name: "Map", weight: 0.5 },
{ name: "Sunscreen", weight: 0.5 },
{ name: "Granola Bar", weight: 0.5 },
{ name: "First Aid Kit", weight: 2.5 },
{ name: "Sleeping Bag", weight: 1.5 }
];
function showCommonItems() {
commonItemsSection.innerHTML = "";
commonItems.forEach((item) => {
const div = document.createElement("div");
div.className = "item";
const name = document.createElement("span");
name.className = "item-name";
name.textContent = item.name;
const weight = document.createElement("span");
weight.className = "item-weight";
weight.textContent = `${item.weight} lbs`;
div.appendChild(name);
div.appendChild(weight);
const addButton = document.createElement("button");
addButton.textContent = "Add";
addButton.onclick = () => addToInventory(item);
div.appendChild(addButton);
commonItemsSection.appendChild(div);
});
}
function showInventoryList() {
inventorySection.innerHTML = "";
let total = 0;
inventoryItems.forEach((item, index) => {
const div = document.createElement("div");
div.className = "item";
const name = document.createElement("span");
name.className = "item-name";
name.textContent = item.name;
const weight = document.createElement("span");
weight.className = "item-weight";
weight.textContent = `${item.weight} lbs`;
div.appendChild(name);
div.appendChild(weight);
const removeButton = document.createElement("button");
removeButton.textContent = "Remove";
removeButton.onclick = () => removeFromInventory(index);
div.appendChild(removeButton);
inventorySection.appendChild(div);
total += parseFloat(item.weight);
});
packWeightOutput.textContent = total.toFixed(1);
if (weightLimit > 0 && total > weightLimit) {
weightWarning.style.display = "block";
} else {
weightWarning.style.display = "none";
}
localStorage.setItem("inventoryItems", JSON.stringify(inventoryItems));
}
function addToInventory(item) {
inventoryItems.push(item);
showInventoryList();
}
function removeFromInventory(index) {
inventoryItems.splice(index, 1);
showInventoryList();
}
function addCustomItem() {
const name = document.getElementById("itemName").value.trim();
const weight = parseFloat(document.getElementById("itemWeight").value);
if (!name || containsCode(name) || isNaN(weight)) {
alert("Please enter a valid name and weight.");
return;
}
inventoryItems.push({ name, weight });
document.getElementById("itemName").value = "";
document.getElementById("itemWeight").value = "";
showInventoryList();
}
function saveWeightLimit() {
const limitInput = parseFloat(document.getElementById("weightLimit").value);
if (isNaN(limitInput)) {
weightLimit = 0;
document.getElementById("weightLimitDisplay").textContent = 0;
} else {
weightLimit = limitInput;
document.getElementById("weightLimitDisplay").textContent = weightLimit.toFixed(1);
localStorage.setItem("weightLimit", weightLimit);
}
showInventoryList();
}
function sortByWeight() {
inventoryItems.sort((a, b) => a.weight - b.weight);
showInventoryList();
}
function sortByName() {
inventoryItems.sort((a, b) => a.name.localeCompare(b.name));
showInventoryList();
}
function containsCode(text) {
const codePattern = /<([a-zA-Z0-9]+)([^>]*)>|<\/([a-zA-Z0-9]+)>/;
const scriptPattern = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/i;
return codePattern.test(text) || scriptPattern.test(text);
}
showCommonItems();
showInventoryList();
</script>
</body>
</html>
