1/** 2 * Board Recommendation Widget 3 * Displays surf board recommendations with quality rating 4 */ 5 6class BoardWidget { 7 constructor(containerId, options = {}) { 8 this.container = document.getElementById(containerId); 9 if (!this.container) { 10 console.error(`Container ${containerId} not found`); 11 return; 12 } 13 14 this.options = { 15 recommendation: options.recommendation || null, 16 ...options 17 }; 18 19 this.render(); 20 } 21 22 /** 23 * Render the board recommendation widget 24 */ 25 render() { 26 const { recommendation } = this.options; 27 28 if (!recommendation) { 29 this.container.innerHTML = '<p class="no-recommendation">No recommendation available</p>'; 30 return; 31 } 32 33 const { 34 board_type, 35 board_length_display, 36 skill_level, 37 wave_quality, 38 quality_stars 39 } = recommendation; 40 41 const icon = this.getBoardIcon(board_type); 42 const qualityClass = `quality-${wave_quality.toLowerCase()}`; 43 const stars = this.renderStars(quality_stars); 44 45 this.container.innerHTML = ` 46 <div class="ccnai-board-widget ${qualityClass}"> 47 <div class="board-header"> 48 <h3>Recommended Board</h3> 49 </div> 50 51 <div class="board-content"> 52 <div class="board-icon"> 53 ${icon} 54 </div> 55 56 <div class="board-details"> 57 <div class="board-type">${this.formatBoardType(board_type)}</div> 58 <div class="board-length">${board_length_display}</div> 59 <div class="skill-level"> 60 <span class="label">Skill:</span> 61 <span class="value">${this.formatSkillLevel(skill_level)}</span> 62 </div> 63 </div> 64 </div> 65 66 <div class="board-footer"> 67 <div class="quality-rating"> 68 <span class="quality-label">${this.formatQuality(wave_quality)}</span> 69 <div class="stars">${stars}</div> 70 </div> 71 </div> 72 </div> 73 `; 74 } 75 76 /** 77 * Get SVG icon for board type 78 */ 79 getBoardIcon(boardType) { 80 const icons = { 81 'shortboard': ` 82 <svg viewBox="0 0 60 200" class="board-svg shortboard"> 83 <path d="M 30 10 Q 20 20, 25 50 L 25 150 Q 25 180, 30 190 Q 35 180, 35 150 L 35 50 Q 40 20, 30 10 Z" 84 fill="currentColor" stroke="#fff" stroke-width="2"/> 85 <line x1="30" y1="100" x2="30" y2="120" stroke="#fff" stroke-width="1.5"/> 86 </svg> 87 `, 88 'longboard': ` 89 <svg viewBox="0 0 80 200" class="board-svg longboard"> 90 <path d="M 40 5 Q 25 10, 30 40 L 30 160 Q 30 190, 40 195 Q 50 190, 50 160 L 50 40 Q 55 10, 40 5 Z" 91 fill="currentColor" stroke="#fff" stroke-width="2"/> 92 <line x1="40" y1="80" x2="40" y2="100" stroke="#fff" stroke-width="1.5"/> 93 <line x1="40" y1="110" x2="40" y2="130" stroke="#fff" stroke-width="1.5"/> 94 </svg> 95 `, 96 'fish': ` 97 <svg viewBox="0 0 70 180" class="board-svg fish"> 98 <path d="M 35 10 Q 20 15, 25 45 L 25 140 Q 20 165, 30 175 Q 35 170, 35 165 Q 35 170, 40 175 Q 50 165, 45 140 L 45 45 Q 50 15, 35 10 Z" 99 fill="currentColor" stroke="#fff" stroke-width="2"/> 100 </svg> 101 `, 102 'funboard': ` 103 <svg viewBox="0 0 75 190" class="board-svg funboard"> 104 <path d="M 37.5 8 Q 23 12, 28 42 L 28 150 Q 28 180, 37.5 188 Q 47 180, 47 150 L 47 42 Q 52 12, 37.5 8 Z" 105 fill="currentColor" stroke="#fff" stroke-width="2"/>
106 <line x1="37.5" y1="95" x2="37.5" y2="115" stroke="#fff" stroke-width="1.5"/> 107 </svg> 108 `, 109 'gun': ` 110 <svg viewBox="0 0 50 220" class="board-svg gun"> 111 <path d="M 25 5 Q 15 8, 20 35 L 20 180 Q 20 210, 25 215 Q 30 210, 30 180 L 30 35 Q 35 8, 25 5 Z" 112 fill="currentColor" stroke="#fff" stroke-width="2"/> 113 <line x1="25" y1="110" x2="25" y2="130" stroke="#fff" stroke-width="1.5"/> 114 </svg> 115 `, 116 'bodyboard': ` 117 <svg viewBox="0 0 90 120" class="board-svg bodyboard"> 118 <rect x="15" y="10" width="60" height="100" rx="8" 119 fill="currentColor" stroke="#fff" stroke-width="2"/> 120 <line x1="45" y1="50" x2="45" y2="70" stroke="#fff" stroke-width="1.5"/> 121 </svg> 122 ` 123 }; 124 125 return icons[boardType] || icons['shortboard']; 126 } 127 128 /** 129 * Render star rating 130 */ 131 renderStars(count) { 132 const stars = []; 133 for (let i = 0; i < 5; i++) { 134 const filled = i < count; 135 stars.push(` 136 <svg class="star ${filled ? 'filled' : ''}" width="20" height="20" viewBox="0 0 20 20"> 137 <path d="M10 1 L12.5 7.5 L19 8.5 L14.5 13 L16 19.5 L10 16 L4 19.5 L5.5 13 L1 8.5 L7.5 7.5 Z" 138 fill="${filled ? '#fbbf24' : '#e5e7eb'}" stroke="#d1d5db" stroke-width="0.5"/> 139 </svg> 140 `); 141 } 142 return stars.join(''); 143 } 144 145 /** 146 * Format board type for display 147 */ 148 formatBoardType(type) { 149 const names = { 150 'shortboard': 'Shortboard', 151 'longboard': 'Longboard', 152 'fish': 'Fish', 153 'funboard': 'Funboard', 154 'gun': 'Gun', 155 'bodyboard': 'Bodyboard' 156 }; 157 return names[type] || type; 158 } 159 160 /** 161 * Format skill level for display 162 */ 163 formatSkillLevel(level) { 164 const names = { 165 'beginner': 'Beginner', 166 'beginner-intermediate': 'Beginner-Intermediate', 167 'intermediate': 'Intermediate', 168 'intermediate-advanced': 'Intermediate-Advanced', 169 'advanced': 'Advanced', 170 'expert': 'Expert' 171 }; 172 return names[level] || level; 173 } 174 175 /** 176 * Format quality for display 177 */ 178 formatQuality(quality) { 179 const names = { 180 'flat': 'Flat', 181 'poor': 'Poor', 182 'fair': 'Fair', 183 'good': 'Good', 184 'epic': 'Epic' 185 }; 186 return names[quality] || quality; 187 } 188 189 /** 190 * Update widget with new recommendation 191 */ 192 update(newRecommendation) { 193 this.options.recommendation = newRecommendation; 194 this.render(); 195 } 196} 197 198// Export for WordPress 199if (typeof window !== 'undefined') { 200 window.BoardWidget = BoardWidget; 201}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.