/** * Book Card Component * Reusable card displaying book thumbnail, title, author, and price */ class BookCard extends HTMLElement { static get observedAttributes() { return ['title', 'author', 'price', 'image', 'href', 'rating']; } constructor() { super(); this.attachShadow({ mode: 'open' }); } connectedCallback() { this.render(); } attributeChangedCallback() { if (this.shadowRoot) { this.render(); } } get title() { return this.getAttribute('title') || 'Book Title'; } get author() { return this.getAttribute('author') || 'Author Name'; } get price() { return this.getAttribute('price') || '$0.00'; } get image() { return this.getAttribute('image') || ''; } get href() { return this.getAttribute('href') || 'book.html'; } get rating() { return parseFloat(this.getAttribute('rating')) || 0; } renderStars(rating) { const fullStars = Math.floor(rating); const hasHalf = rating % 1 >= 0.5; const emptyStars = 5 - fullStars - (hasHalf ? 1 : 0); let stars = ''; // Full stars for (let i = 0; i < fullStars; i++) { stars += ` `; } // Half star if (hasHalf) { stars += ` `; } // Empty stars for (let i = 0; i < emptyStars; i++) { stars += ` `; } return stars; } render() { // Generate placeholder image if none provided const imageHtml = this.image ? `${this.title}` : `
`; this.shadowRoot.innerHTML = `
${imageHtml}

${this.title}

${this.author}

${this.rating > 0 ? `
${this.renderStars(this.rating)}
` : ''}

${this.price}

`; } } customElements.define('book-card', BookCard);