Tell us what’s happening:
I cannot get tests 17-24 to pass. Everything seems to work in my preview but I cannot get the failed testes to pass with anything I try. I’m not sure what I am missing and would appreciate any help. Thanks in advance!
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 Category =
| 'Sport'
| 'Cruiser'
| 'Touring'
| 'Dirt'
| 'Adventure'
| 'Naked'
| 'Electric';
interface Motorcycle {
id: string;
name: string;
manufacturer: string;
category: Category;
price: number;
image_url: string;
created_at: Date;
description: string;
year: number;
horsepower?: number;
}
async function fetchMotorcycles(): Promise<Motorcycle[]> {
const response = await fetch(
'https://cdn.freecodecamp.org/curriculum/labs/data/motorcycles.json'
);
const data = await response.json();
if (!Array.isArray(data)) {
console.error('Unexpected motorcycles response shape:', data);
return [];
}
return data as Motorcycle[];
}
function renderMotorcycleCard(motorcycle: Motorcycle): string {
const hp = (motorcycle as any).horsepower ?? 'N/A';
const priceFormatted = Number(motorcycle.price) ? Number(motorcycle.price).toLocaleString() : motorcycle.price;
return `
<div class="motorcycle-card">
<div class="motorcycle-card-image-container">
<img class="motorcycle-card-image" src="${motorcycle.image_url}" alt="${motorcycle.name}" />
<span class="motorcycle-card-year-badge">${motorcycle.year}</span>
</div>
<div class="motorcycle-card-content">
<div class="motorcycle-card-header">
<h3 class="motorcycle-card-title">${motorcycle.name}</h3>
</div>
<p class="motorcycle-card-manufacturer">${motorcycle.manufacturer}</p>
<span class="motorcycle-card-category">${motorcycle.category}</span>
<p class="motorcycle-card-description">${motorcycle.description}</p>
<div class="motorcycle-card-footer">
<p class="motorcycle-card-price">$${priceFormatted}</p>
<p class="motorcycle-card-engine">${hp} hp</p>
</div>
</div>
</div>
`;
}
class MotorcycleGalleryApp {
private allMotorcycles: Motorcycle[] = [];
public readonly ready: Promise<void>;
constructor(initialData: Motorcycle[] = []) {
this.allMotorcycles = initialData;
this.ready = this.load();
}
public async load(): Promise<void> {
const loadingContainer = document.getElementById('loading-container');
if (loadingContainer) loadingContainer.style.display = 'flex';
try {
const fetched = await fetchMotorcycles();
if (fetched.length > 0) {
this.allMotorcycles = fetched;
}
} catch (err) {
console.error('Failed to fetch motorcycles:', err);
}
if (loadingContainer) loadingContainer.style.display = 'none';
this.renderMotorcycles();
this.attachFilterListener();
}
public renderMotorcycles(motorcycles?: Motorcycle[]): void {
const listToRender = motorcycles ?? this.allMotorcycles;
const grid = document.getElementById('motorcycle-grid');
const resultsNumber = document.getElementById('results-number');
const noResults = document.getElementById('no-results');
if (!grid) return;
grid.innerHTML = listToRender
.map((motorcycle) => renderMotorcycleCard(motorcycle))
.join('');
if (resultsNumber) {
resultsNumber.textContent = listToRender.length.toString();
}
if (noResults) {
noResults.style.display = listToRender.length === 0 ? 'block' : 'none';
}
}
private attachFilterListener(): void {
const filterInput = document.getElementById('name-filter-input') as HTMLInputElement | null;
if (!filterInput) return;
filterInput.addEventListener('input', () => {
const query = filterInput.value.toLowerCase();
const filtered = this.allMotorcycles.filter((motorcycle) =>
motorcycle.name.toLowerCase().includes(query)
);
this.renderMotorcycles(filtered);
});
}
}
function startApp(): void {
const app = new MotorcycleGalleryApp();
(window as any).motorcycleGalleryApp = app;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startApp);
} else {
startApp();
}
Your browser information:
User Agent is: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36
Challenge Information:
Build a Motorcycle Shop - Build a Motorcycle Shop