Tell us what’s happening:
Hi to everyone!
I’m having trouble understanding why my code does not pass the tests 17 to 24, even though it does everything is should. Any help would be appreciated!
Your code so far
<!-- file: index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MotoShop - Find Your Perfect Ride</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div id="app">
<header class="bg-gradient-to-r from-gray-900 via-gray-800 to-gray-900 text-white">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="flex flex-col md:flex-row items-start md:items-center gap-4 md:gap-6">
<div class="flex items-center gap-3">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bike-icon lucide-bike w-8 h-8 text-orange-500"><circle cx="18.5" cy="17.5" r="3.5"/><circle cx="5.5" cy="17.5" r="3.5"/><circle cx="15" cy="5" r="1"/><path d="M12 17.5V14l-3-3 4-3 2 3h2"/></svg>
<h1 class="text-3xl font-bold">MotoShop</h1>
</div>
<div class="flex-1 w-full md:w-auto">
<div class="relative">
<input
type="text"
id="name-filter-input"
placeholder="Search motorcycles by name..."
class="w-full md:w-96 px-4 py-2 pl-10 rounded-lg bg-gray-700 text-white placeholder-gray-400 border border-gray-600 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
/>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400">
<circle cx="11" cy="11" r="8"/>
<path d="m21 21-4.35-4.35"/>
</svg>
</div>
</div>
</div>
</div>
</header>
<section class="bg-gradient-to-r from-orange-600 via-orange-500 to-red-500 text-white py-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 class="text-5xl font-bold mb-4">Find Your Perfect Ride</h2>
<p class="text-xl opacity-90 max-w-2xl mx-auto">
Explore our curated collection of premium motorcycles from the world's leading manufacturers
</p>
</div>
</section>
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div id="results-count" class="results-count">
<p class="results-count-text">
Showing <span id="results-number" class="results-count-number">0</span> motorcycles
</p>
</div>
<div id="loading-container" class="loading-container" style="display: none;">
<div class="loading-spinner">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-loader-circle-icon lucide-loader-circle w-24 h-24 text-orange-500 animate-spin"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>
</div>
</div>
<div id="no-results" class="no-results" style="display: none;">
<p class="no-results-text">
No motorcycles found matching your filters.
</p>
</div>
<div id="motorcycle-grid" class="motorcycle-grid">
</div>
</main>
<footer class="bg-gray-900 text-white py-8 mt-20">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<p class="text-gray-400">
© 2024 MotoGallery. Ride with passion, choose with confidence.
</p>
</div>
</footer>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lucide/0.344.0/lucide.min.js"></script>
<script src="./index.ts"></script>
</body>
</html>
/* file: styles.css */
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Layout utilities */
.max-w-7xl {
max-width: 80rem;
}
.mx-auto {
margin-left: auto;
margin-right: auto;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.py-6 {
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
.py-8 {
padding-top: 2rem;
padding-bottom: 2rem;
}
.py-20 {
padding-top: 5rem;
padding-bottom: 5rem;
}
.mt-20 {
margin-top: 5rem;
}
.mb-4 {
margin-bottom: 1rem;
}
/* Responsive padding */
@media (min-width: 640px) {
.sm\:px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
@media (min-width: 1024px) {
.lg\:px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
}
/* Background gradients */
.bg-gradient-to-r {
background: linear-gradient(to right, var(--tw-gradient-stops));
}
.from-gray-900 {
--tw-gradient-from: #111827;
--tw-gradient-to: rgb(17 24 39 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-gray-800 {
--tw-gradient-to: rgb(31 41 55 / 0);
--tw-gradient-stops: var(--tw-gradient-from), #1f2937, var(--tw-gradient-to);
}
.to-gray-900 {
--tw-gradient-to: #111827;
}
.from-orange-600 {
--tw-gradient-from: #ea580c;
--tw-gradient-to: rgb(234 88 12 / 0);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.via-orange-500 {
--tw-gradient-to: rgb(249 115 22 / 0);
--tw-gradient-stops: var(--tw-gradient-from), #f97316, var(--tw-gradient-to);
}
.to-red-500 {
--tw-gradient-to: #ef4444;
}
/* Colors */
.bg-gray-900 {
background-color: #111827;
}
.text-white {
color: #ffffff;
}
.text-gray-400 {
color: #9ca3af;
}
.text-orange-500 {
color: #f97316;
stroke: #f97316;
}
/* Flexbox utilities */
.flex {
display: flex;
}
.items-center {
align-items: center;
}
.gap-3 {
gap: 0.75rem;
}
/* Grid utilities */
.grid {
display: grid;
}
.gap-6 {
gap: 1.5rem;
}
/* Typography */
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-3xl {
font-size: 1.875rem;
line-height: 2.25rem;
}
.text-5xl {
font-size: 3rem;
line-height: 1;
}
.font-bold {
font-weight: 700;
}
.text-center {
text-align: center;
}
.opacity-90 {
opacity: 0.9;
}
.max-w-2xl {
max-width: 42rem;
}
/* Spacing utilities */
.w-8 {
width: 2rem;
}
.h-8 {
height: 2rem;
}
.h-2 {
height: 0.5rem;
}
.w-full {
width: 100%;
}
/* Border utilities */
.border {
border-width: 1px;
}
.border-t {
border-top-width: 1px;
}
.rounded-lg {
border-radius: 0.5rem;
}
/* Position utilities */
.relative {
position: relative;
}
.absolute {
position: absolute;
}
/* Padding utilities */
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.py-2 {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
}
/* Margin utilities */
.mb-4 {
margin-bottom: 1rem;
}
/* Focus utilities */
.focus\:ring-2:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-orange-500:focus {
--tw-ring-color: #f97316;
}
.focus\:border-transparent:focus {
border-color: transparent;
}
/* Hover utilities */
.hover\:-translate-y-1:hover {
transform: translateY(-0.25rem);
}
/* Animation utilities */
.animate-spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Custom component styles */
.loading-container {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
.loading-spinner {
display: flex;
align-items: center;
justify-content: center;
}
.motorcycle-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 2rem;
}
@media (min-width: 768px) {
.motorcycle-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.motorcycle-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
.motorcycle-card {
background-color: #ffffff;
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(0);
}
.motorcycle-card:hover {
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
transform: translateY(-0.25rem);
}
.motorcycle-card-image-container {
position: relative;
height: 16rem;
overflow: hidden;
background-color: #111827;
}
.motorcycle-card-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 500ms cubic-bezier(0.4, 0, 0.2, 1);
}
.motorcycle-card:hover .motorcycle-card-image {
transform: scale(1.1);
}
.motorcycle-card-year-badge {
position: absolute;
top: 1rem;
right: 1rem;
background-color: #f97316;
color: #ffffff;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 700;
}
.motorcycle-card-content {
padding: 1.5rem;
}
.motorcycle-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.motorcycle-card-title {
font-size: 1.25rem;
font-weight: 700;
color: #111827;
margin-bottom: 0.25rem;
}
.motorcycle-card-manufacturer {
color: #4b5563;
font-weight: 500;
}
.motorcycle-card-category {
padding: 0.25rem 0.75rem;
background-color: #f3f4f6;
color: #374151;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.025em;
}
.motorcycle-card-description {
color: #4b5563;
font-size: 0.875rem;
margin-bottom: 1rem;
overflow: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
.motorcycle-card-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
}
.motorcycle-card-price {
font-size: 1.5rem;
font-weight: 700;
color: #ea580c;
}
.motorcycle-card-engine {
font-size: 0.75rem;
color: #6b7280;
}
.motorcycle-card-button {
background-color: #f97316;
color: #ffffff;
padding: 0.5rem 1.5rem;
border-radius: 0.5rem;
font-weight: 600;
transition: background-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
border: none;
cursor: pointer;
}
.motorcycle-card-button:hover {
background-color: #ea580c;
}
.no-results {
text-align: center;
padding: 3rem 0;
}
.no-results-text {
font-size: 1.25rem;
color: #4b5563;
}
.results-count {
margin-bottom: 1.5rem;
}
.results-count-text {
color: #4b5563;
}
.results-count-number {
font-weight: 700;
color: #111827;
}
/* Additional utility classes for search input */
.bg-gray-700 {
background-color: #374151;
}
.border-gray-600 {
border-color: #4b5563;
}
.placeholder-gray-400::placeholder {
color: #9ca3af;
}
.focus\:outline-none:focus {
outline: none;
}
.focus\:ring-2:focus {
box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.5);
}
.focus\:ring-orange-500:focus {
box-shadow: 0 0 0 2px rgba(249, 115, 22, 0.5);
}
.focus\:border-transparent:focus {
border-color: transparent;
}
.pl-10 {
padding-left: 2.5rem;
}
.w-96 {
width: 24rem;
}
.left-3 {
left: 0.75rem;
}
.top-1\/2 {
top: 50%;
}
.transform {
transform: translateY(-50%);
}
.flex-1 {
flex: 1 1 0%;
}
.gap-4 {
gap: 1rem;
}
.gap-6 {
gap: 1.5rem;
}
.flex-col {
flex-direction: column;
}
.items-start {
align-items: flex-start;
}
.items-center {
align-items: center;
}
@media (min-width: 768px) {
.md\:flex-row {
flex-direction: row;
}
.md\:items-center {
align-items: center;
}
.md\:w-auto {
width: auto;
}
}
/* file: index.ts */
// Type definition for motorcycle categories - restricts possible values
type Category =
| "Sport"
| "Cruiser"
| "Touring"
| "Dirt"
| "Adventure"
| "Naked"
| "Electric";
// Interface defining the shape of a Motorcycle object
interface Motorcycle {
id: string;
name: string;
manufacturer: string;
category: Category;
price: number;
image_url: string;
created_at: Date;
description: string;
year: number;
engine?: string; // Optional property - may not exist on all motorcycles
}
// Fetches motorcycle data from external API, returns array or empty array on error
const fetchMotorcycles = async (): Promise<Motorcycle[]> => {
try {
const res = await fetch(
"https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json",
);
const data = await res.json();
return data;
} catch (err) {
console.error("Error fetching data: ", err);
return []; // Return empty array as fallback to prevent crashes
}
};
// Converts a Motorcycle object into an HTML string for display in the gallery
const renderMotorcycleCard = (motorcycle: Motorcycle): string => {
return `<div class="motorcycle-card">
<div class="motorcycle-card-image-container">
<img class="motorcycle-card-image" src='${motorcycle.image_url}' alt='${motorcycle.name}' />
<p class='motorcycle-card-year-badge'>${motorcycle.year}</p>
</div>
<div class='motorcycle-card-content'>
<div class='motorcycle-card-header'>
<div>
<p class='motorcycle-card-title'>${motorcycle.name}</p>
<p class='motorcycle-card-manufacturer'>${motorcycle.manufacturer}</p>
</div>
<p class='motorcycle-card-category'>${motorcycle.category}</p>
</div>
<p class='motorcycle-card-description'>${motorcycle.description}</p>
<div class='motorcycle-card-footer'>
<div>
<p class='motorcycle-card-price'>$${motorcycle.price.toLocaleString()}</p>
<p class='motorcycle-card-engine'>${motorcycle?.engine || "undefinedcc"}</p>
</div>
<button class='motorcycle-card-button'>View Details</button>
</div>
</div>
</div>`;
};
// Main application class that manages the motorcycle gallery
class MotorcycleGalleryApp {
private allMotorcycles: Motorcycle[] = []; // Complete unfiltered dataset
private filteredMoto: Motorcycle[] = []; // Currently displayed dataset (after filtering)
constructor() {
this.loadMotorcycles(); // Fetch data when app starts
this.setupEventListener(); // Set up search input listener
}
// Fetches motorcycle data from API, shows/hides loading spinner, then renders
private async loadMotorcycles(): Promise<void> {
const loadingContainer = document.getElementById("loading-container");
if (loadingContainer) loadingContainer.style.display = "flex"; // Show spinner
try {
this.allMotorcycles = await fetchMotorcycles();
this.filteredMoto = this.allMotorcycles;
if (loadingContainer) loadingContainer.style.display = "none"; // Hide spinner
this.renderMotorcycles(); // Display the motorcycles
} catch (err) {
console.error("Error fetching data: ", err);
}
}
// Renders current filtered motorcycles to the DOM, updates count, handles empty state
renderMotorcycles(): void {
const moto = this.filteredMoto;
const gridElement = document.getElementById("motorcycle-grid");
const noResults = document.getElementById("no-results");
// Clear previous content
if (gridElement) {
gridElement.textContent = "";
}
// Show/hide "no results" message based on filtered results
if (moto.length === 0) {
if (noResults) noResults.style.display = "block";
if (gridElement) gridElement.style.display = "none";
} else {
if (noResults) noResults.style.display = "none";
if (gridElement) gridElement.style.display = "grid";
}
// Update results count display
const number = document.getElementById("results-number");
if (number) {
number.innerText = moto.length.toString();
}
// Generate and insert HTML for each motorcycle
moto.forEach((moto: Motorcycle) => {
gridElement?.insertAdjacentHTML("beforeend", renderMotorcycleCard(moto));
});
}
// Filters motorcycles by name (case-insensitive) and re-renders
handleSearch(query: string): void {
this.filteredMoto = this.allMotorcycles.filter((moto: Motorcycle) =>
moto.name.toLowerCase().includes(query.toLowerCase()),
);
console.log("filtered moto", this.filteredMoto);
this.renderMotorcycles();
}
// Attaches input event listener to search box for real-time filtering
setupEventListener(): void {
const input = document.getElementById(
"name-filter-input",
) as HTMLInputElement | null;
input?.addEventListener("input", e => {
const target = e.target as HTMLInputElement;
this.handleSearch(target.value);
console.log("target value", target.value);
});
}
}
// Create and start the application instance
const shop = new MotorcycleGalleryApp();
Your browser information:
User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36
Challenge Information:
Build a Motorcycle Shop - Lab Motorcycle Shop