11/**
2- * BiblioDrift Core Logic
3- * Handles 3D rendering, API fetching, and LocalStorage management.
2+ * BiblioDrift Core Logic with GoodReads Mood Analysis
3+ * Handles 3D rendering, API fetching, mood analysis, and LocalStorage management.
44 */
55
66const API_BASE = 'https://www.googleapis.com/books/v1/volumes' ;
7+ const MOOD_API_BASE = 'http://localhost:5000/api/v1' ;
78
89class BookRenderer {
910 constructor ( ) {
1011 this . libraryManager = new LibraryManager ( ) ;
12+ this . moodAnalyzer = new MoodAnalyzer ( ) ;
1113 }
1214
13- createBookElement ( bookData ) {
15+ async createBookElement ( bookData ) {
1416 const { id, volumeInfo } = bookData ;
1517 const title = volumeInfo . title || "Untitled" ;
1618 const authors = volumeInfo . authors ? volumeInfo . authors . join ( ", " ) : "Unknown Author" ;
1719 const thumb = volumeInfo . imageLinks ? volumeInfo . imageLinks . thumbnail : 'https://via.placeholder.com/128x196?text=No+Cover' ;
1820 const description = volumeInfo . description ? volumeInfo . description . substring ( 0 , 100 ) + "..." : "A mysterious tome waiting to be opened." ;
1921
22+ // Try to get mood analysis for this book
23+ let vibe = this . generateVibe ( description ) ;
24+ let moodTags = [ ] ;
25+
26+ try {
27+ const moodData = await this . moodAnalyzer . getBookMood ( title , authors ) ;
28+ if ( moodData && moodData . success ) {
29+ vibe = moodData . mood_analysis . bibliodrift_vibe || vibe ;
30+ moodTags = moodData . mood_analysis . primary_moods || [ ] ;
31+ }
32+ } catch ( error ) {
33+ console . log ( 'Mood analysis not available, using fallback vibe' ) ;
34+ }
35+
2036 // Randomize spine color slightly for variety
2137 const spineColors = [ '#5D4037' , '#4E342E' , '#3E2723' , '#2C2420' , '#8D6E63' ] ;
2238 const randomSpine = spineColors [ Math . floor ( Math . random ( ) * spineColors . length ) ] ;
@@ -25,33 +41,51 @@ class BookRenderer {
2541 const scene = document . createElement ( 'div' ) ;
2642 scene . className = 'book-scene' ;
2743
44+ // Generate mood tags HTML
45+ const moodTagsHTML = moodTags . length > 0
46+ ? `<div class="mood-tags">${ moodTags . slice ( 0 , 2 ) . map ( tag => `<span class="mood-tag mood-${ tag . mood } ">${ tag . mood } </span>` ) . join ( '' ) } </div>`
47+ : '' ;
48+
2849 // Structure
2950 scene . innerHTML = `
3051 <div class="book" data-id="${ id } ">
3152 <div class="book__face book__face--front">
3253 <img src="${ thumb . replace ( 'http:' , 'https:' ) } " alt="${ title } ">
54+ ${ moodTagsHTML }
3355 </div>
3456 <div class="book__face book__face--spine" style="background: ${ randomSpine } "></div>
3557 <div class="book__face book_face--right"></div>
3658 <div class="book__face book__face--back">
3759 <div>
3860 <div style="font-weight: bold; font-size: 0.9rem; margin-bottom: 0.5rem;">${ title } </div>
3961 <div class="handwritten-note">
40- Bookseller's Note: "${ this . generateVibe ( description ) } "
62+ Bookseller's Note: "${ vibe } "
4163 </div>
64+ ${ moodTags . length > 0 ? `
65+ <div class="mood-analysis">
66+ <small>Mood Analysis:</small>
67+ <div class="mood-tags-back">
68+ ${ moodTags . slice ( 0 , 3 ) . map ( tag => `<span class="mood-tag-small">${ tag . mood } </span>` ) . join ( '' ) }
69+ </div>
70+ </div>` : '' }
4271 </div>
4372 <div class="book-actions">
4473 <button class="btn-icon add-btn" title="Add to Library"><i class="fa-regular fa-heart"></i></button>
74+ <button class="btn-icon mood-btn" title="Analyze Mood" onclick="event.stopPropagation(); this.closest('.book-scene').querySelector('.book-renderer').showMoodAnalysis('${ title } ', '${ authors } ')"><i class="fa-solid fa-brain"></i></button>
4575 <button class="btn-icon" title="Flip Back" onclick="event.stopPropagation(); this.closest('.book').classList.remove('flipped')"><i class="fa-solid fa-rotate-left"></i></button>
4676 </div>
4777 </div>
4878 </div>
4979 <div class="glass-overlay">
5080 <strong>${ title } </strong><br>
5181 <small>${ authors } </small>
82+ ${ moodTags . length > 0 ? `<div class="glass-mood-tags">${ moodTags . slice ( 0 , 2 ) . map ( tag => `<span class="glass-mood-tag">${ tag . mood } </span>` ) . join ( '' ) } </div>` : '' }
5283 </div>
5384 ` ;
5485
86+ // Store reference for mood analysis
87+ scene . querySelector ( '.book-scene' ) . bookRenderer = this ;
88+
5589 // Interaction: Flip
5690 const bookEl = scene . querySelector ( '.book' ) ;
5791 scene . addEventListener ( 'click' , ( e ) => {
@@ -72,8 +106,72 @@ class BookRenderer {
72106 return scene ;
73107 }
74108
109+ async showMoodAnalysis ( title , author ) {
110+ try {
111+ const moodData = await this . moodAnalyzer . analyzeMood ( title , author ) ;
112+ if ( moodData && moodData . success ) {
113+ this . displayMoodModal ( title , moodData . mood_analysis ) ;
114+ } else {
115+ alert ( 'Mood analysis not available for this book.' ) ;
116+ }
117+ } catch ( error ) {
118+ console . error ( 'Error showing mood analysis:' , error ) ;
119+ alert ( 'Error loading mood analysis.' ) ;
120+ }
121+ }
122+
123+ displayMoodModal ( title , moodAnalysis ) {
124+ const modal = document . createElement ( 'div' ) ;
125+ modal . className = 'mood-modal' ;
126+ modal . innerHTML = `
127+ <div class="mood-modal-content">
128+ <div class="mood-modal-header">
129+ <h3>Mood Analysis: ${ title } </h3>
130+ <button class="close-modal">×</button>
131+ </div>
132+ <div class="mood-modal-body">
133+ <div class="mood-section">
134+ <h4>Overall Sentiment</h4>
135+ <div class="sentiment-bar">
136+ <div class="sentiment-fill" style="width: ${ ( moodAnalysis . overall_sentiment . compound_score + 1 ) * 50 } %"></div>
137+ </div>
138+ <p>${ moodAnalysis . mood_description } </p>
139+ </div>
140+ <div class="mood-section">
141+ <h4>Primary Moods</h4>
142+ <div class="mood-tags-large">
143+ ${ moodAnalysis . primary_moods . map ( mood =>
144+ `<span class="mood-tag-large mood-${ mood . mood } ">${ mood . mood } (${ mood . frequency } )</span>`
145+ ) . join ( '' ) }
146+ </div>
147+ </div>
148+ <div class="mood-section">
149+ <h4>BiblioDrift Vibe</h4>
150+ <div class="vibe-quote">"${ moodAnalysis . bibliodrift_vibe } "</div>
151+ </div>
152+ <div class="mood-section">
153+ <small>Based on ${ moodAnalysis . total_reviews_analyzed } GoodReads reviews</small>
154+ </div>
155+ </div>
156+ </div>
157+ ` ;
158+
159+ document . body . appendChild ( modal ) ;
160+
161+ // Close modal functionality
162+ modal . querySelector ( '.close-modal' ) . addEventListener ( 'click' , ( ) => {
163+ document . body . removeChild ( modal ) ;
164+ } ) ;
165+
166+ modal . addEventListener ( 'click' , ( e ) => {
167+ if ( e . target === modal ) {
168+ document . body . removeChild ( modal ) ;
169+ }
170+ } ) ;
171+ }
172+
75173 generateVibe ( text ) {
76- // Simple heuristic to mock "AI" vibes
174+ // Simple heuristic to mock "AI" vibes (fallback)
77175 const vibes = [
78176 "Perfect for a rainy afternoon." ,
79177 "Smells like old paper and adventure." ,
@@ -94,9 +192,10 @@ class BookRenderer {
94192
95193 if ( data . items ) {
96194 container . innerHTML = '' ;
97- data . items . forEach ( book => {
98- container . appendChild ( this . createBookElement ( book ) ) ;
99- } ) ;
195+ for ( const book of data . items ) {
196+ const bookElement = await this . createBookElement ( book ) ;
197+ container . appendChild ( bookElement ) ;
198+ }
100199 }
101200 } catch ( err ) {
102201 console . error ( "Failed to fetch books" , err ) ;
@@ -105,6 +204,40 @@ class BookRenderer {
105204 }
106205}
107206
207+ class MoodAnalyzer {
208+ async getBookMood ( title , author ) {
209+ try {
210+ const response = await fetch ( `${ MOOD_API_BASE } /mood-tags` , {
211+ method : 'POST' ,
212+ headers : {
213+ 'Content-Type' : 'application/json' ,
214+ } ,
215+ body : JSON . stringify ( { title, author } )
216+ } ) ;
217+ return await response . json ( ) ;
218+ } catch ( error ) {
219+ console . error ( 'Error fetching mood tags:' , error ) ;
220+ return null ;
221+ }
222+ }
223+
224+ async analyzeMood ( title , author ) {
225+ try {
226+ const response = await fetch ( `${ MOOD_API_BASE } /analyze-mood` , {
227+ method : 'POST' ,
228+ headers : {
229+ 'Content-Type' : 'application/json' ,
230+ } ,
231+ body : JSON . stringify ( { title, author } )
232+ } ) ;
233+ return await response . json ( ) ;
234+ } catch ( error ) {
235+ console . error ( 'Error analyzing mood:' , error ) ;
236+ return null ;
237+ }
238+ }
239+ }
240+
108241class LibraryManager {
109242 constructor ( ) {
110243 this . storageKey = 'bibliodrift_library' ;
0 commit comments