@@ -3205,6 +3205,204 @@ document.addEventListener('DOMContentLoaded', async () => {
32053205
32063206 loadExtendedStats ( ) ;
32073207
3208+ // =====================================================================
3209+ // READER IDENTITY LOGIC
3210+ // Fetches reviews, determines archetype/mood/cluster, and renders states.
3211+ // =====================================================================
3212+ const renderErrorIdentityState = ( ) => {
3213+ const identityContent = document . getElementById ( 'reader-identity-content' ) ;
3214+ if ( ! identityContent ) return ;
3215+
3216+ identityContent . innerHTML = `
3217+ <div class="error-state" style="padding: 1.5rem; text-align: center; color: var(--text-muted); background: rgba(229, 57, 53, 0.05); border: 1px solid rgba(229, 57, 53, 0.2); border-radius: 10px;">
3218+ <i class="fa-solid fa-triangle-exclamation" style="font-size: 2rem; color: #e53935; margin-bottom: 1rem; display: block;"></i>
3219+ <p style="margin-bottom: 1rem;">Failed to load Reader Identity profile.</p>
3220+ <button id="retry-identity-btn" class="action-btn-secondary" style="font-size: 0.8rem; padding: 4px 10px;">Retry</button>
3221+ </div>
3222+ ` ;
3223+
3224+ const retryBtn = document . getElementById ( 'retry-identity-btn' ) ;
3225+ if ( retryBtn ) {
3226+ retryBtn . addEventListener ( 'click' , ( ) => {
3227+ loadReaderIdentity ( ) ;
3228+ } ) ;
3229+ }
3230+ } ;
3231+
3232+ const fetchAndRenderArchetype = async ( genres , reviewsList ) => {
3233+ const identityContent = document . getElementById ( 'reader-identity-content' ) ;
3234+ if ( ! identityContent ) return ;
3235+
3236+ const token = SafeStorage . get ( 'bibliodrift_token' ) ;
3237+ const response = await fetch ( `${ MOOD_API_BASE } /reader-archetype` , {
3238+ method : 'POST' ,
3239+ headers : {
3240+ 'Authorization' : `Bearer ${ token } ` ,
3241+ 'Content-Type' : 'application/json'
3242+ } ,
3243+ body : JSON . stringify ( { genres, reviews : reviewsList } )
3244+ } ) ;
3245+
3246+ if ( ! response . ok ) {
3247+ throw new Error ( `Failed to fetch archetype (Status: ${ response . status } )` ) ;
3248+ }
3249+
3250+ const data = await response . json ( ) ;
3251+ if ( ! data . success || ! data . reader_profile ) {
3252+ throw new Error ( 'API returned unsuccessful profile generation' ) ;
3253+ }
3254+
3255+ const profile = data . reader_profile ;
3256+ const archetype = profile . archetype || "Unknown Reader" ;
3257+ const mood = profile . reader_mood || "Balanced Analytical Reader" ;
3258+ const sentimentScore = typeof profile . sentiment_score === 'number' ? profile . sentiment_score . toFixed ( 2 ) : '0.00' ;
3259+ const cluster = typeof profile . reader_cluster === 'number' ? ( profile . reader_cluster === - 1 ? 'Unclassified' : `Group #${ profile . reader_cluster } ` ) : 'Unclassified' ;
3260+
3261+ const archetypeDescriptions = {
3262+ "Deep Thinker" : "Drawn to philosophy, existential questions, and reflective/psychological themes." ,
3263+ "Emotional Reader" : "Connects deeply with romance, relationships, emotional journeys, and human feelings." ,
3264+ "Dark Reader" : "Enjoys crime, violence, psychological thrillers, horror, and mystery." ,
3265+ "Adventurous Reader" : "Seeks sci-fi, fantasy, action-packed adventures, and exploration."
3266+ } ;
3267+ const desc = archetypeDescriptions [ archetype ] || "Based on your current reading habits and preferences." ;
3268+
3269+ identityContent . innerHTML = `
3270+ <div class="reader-identity-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; margin-top: 1rem; text-align: left;">
3271+ <div class="identity-card" style="background: rgba(255, 255, 255, 0.03); padding: 1.2rem; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.05);">
3272+ <div style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.5rem; letter-spacing: 0.5px;">Reader Archetype</div>
3273+ <div style="font-size: 1.3rem; font-weight: 600; color: var(--accent-gold); display: flex; align-items: center; gap: 8px;">
3274+ <i class="fa-solid fa-brain" style="font-size: 1.3rem; margin: 0; color: var(--accent-gold);"></i>
3275+ <span id="identity-archetype">${ archetype } </span>
3276+ </div>
3277+ <div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem;" id="identity-archetype-desc">
3278+ ${ desc }
3279+ </div>
3280+ </div>
3281+
3282+ <div class="identity-card" style="background: rgba(255, 255, 255, 0.03); padding: 1.2rem; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.05);">
3283+ <div style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.5rem; letter-spacing: 0.5px;">Reader Mood</div>
3284+ <div style="font-size: 1.3rem; font-weight: 600; color: var(--accent-gold); display: flex; align-items: center; gap: 8px;">
3285+ <i class="fa-solid fa-masks-theater" style="font-size: 1.3rem; margin: 0; color: var(--accent-gold);"></i>
3286+ <span id="identity-mood">${ mood } </span>
3287+ </div>
3288+ <div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem;">
3289+ Sentiment Score: <strong id="identity-sentiment">${ sentimentScore } </strong>
3290+ </div>
3291+ </div>
3292+
3293+ <div class="identity-card" style="background: rgba(255, 255, 255, 0.03); padding: 1.2rem; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.05);">
3294+ <div style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.5rem; letter-spacing: 0.5px;">Reader Group</div>
3295+ <div style="font-size: 1.3rem; font-weight: 600; color: var(--accent-gold); display: flex; align-items: center; gap: 8px;">
3296+ <i class="fa-solid fa-people-group" style="font-size: 1.3rem; margin: 0; color: var(--accent-gold);"></i>
3297+ <span id="identity-cluster">${ cluster } </span>
3298+ </div>
3299+ <div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.5rem;">
3300+ Based on review text analysis.
3301+ </div>
3302+ </div>
3303+ </div>
3304+ ` ;
3305+ } ;
3306+
3307+ const renderEmptyIdentityState = ( genres ) => {
3308+ const identityContent = document . getElementById ( 'reader-identity-content' ) ;
3309+ if ( ! identityContent ) return ;
3310+
3311+ identityContent . innerHTML = `
3312+ <div class="empty-state" style="padding: 1rem 0; text-align: center; color: var(--text-muted);">
3313+ <i class="fa-solid fa-circle-info" style="font-size: 2rem; color: var(--accent-gold); margin-bottom: 1rem; display: block;"></i>
3314+ <p style="margin-bottom: 1.5rem;">We couldn't find any reviews in your profile yet. Add reviews to your finished books to unlock your reader identity!</p>
3315+ <div style="max-width: 450px; margin: 0 auto; background: rgba(255, 255, 255, 0.02); padding: 1.5rem; border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.05); text-align: left;">
3316+ <h4 style="color: var(--text-main); margin-bottom: 0.5rem;">Or try a quick test right now:</h4>
3317+ <p style="font-size: 0.85rem; margin-bottom: 1rem;">Write a brief summary of the kinds of books you love reading (e.g. "I love deep space adventures with complex characters"):</p>
3318+ <textarea id="onboarding-review-text" placeholder="I love exploring dark mystery novels and fast-paced thrillers..." style="width: 100%; height: 80px; padding: 10px; border-radius: 8px; border: 1px solid var(--border-color); background: var(--card-bg); color: var(--text-color); margin-bottom: 1rem; font-family: inherit; font-size: 0.9rem; resize: none;"></textarea>
3319+ <button id="submit-onboarding-btn" class="action-btn-primary" style="font-size: 0.85rem; padding: 6px 15px;">Analyze Mood & Archetype</button>
3320+ </div>
3321+ </div>
3322+ ` ;
3323+
3324+ const submitBtn = document . getElementById ( 'submit-onboarding-btn' ) ;
3325+ if ( submitBtn ) {
3326+ submitBtn . addEventListener ( 'click' , async ( ) => {
3327+ const textInput = document . getElementById ( 'onboarding-review-text' ) . value . trim ( ) ;
3328+ if ( ! textInput ) return ;
3329+
3330+ identityContent . innerHTML = `
3331+ <div class="loading-state" style="padding: 2rem 0; text-align: center; color: var(--text-muted);">
3332+ <i class="fa-solid fa-spinner fa-spin" style="font-size: 2rem; color: var(--accent-gold); margin-bottom: 1rem; display: block;"></i>
3333+ <p>Analyzing your custom input...</p>
3334+ </div>
3335+ ` ;
3336+
3337+ try {
3338+ await fetchAndRenderArchetype ( genres , [ textInput ] ) ;
3339+ } catch ( error ) {
3340+ console . error ( 'Error analyzing custom onboarding input:' , error ) ;
3341+ renderErrorIdentityState ( ) ;
3342+ }
3343+ } ) ;
3344+ }
3345+ } ;
3346+
3347+ const loadReaderIdentity = async ( ) => {
3348+ const identityContent = document . getElementById ( 'reader-identity-content' ) ;
3349+ if ( ! identityContent ) return ;
3350+
3351+ identityContent . innerHTML = `
3352+ <div class="loading-state" style="padding: 2rem 0; text-align: center; color: var(--text-muted);">
3353+ <i class="fa-solid fa-spinner fa-spin" style="font-size: 2rem; color: var(--accent-gold); margin-bottom: 1rem; display: block;"></i>
3354+ <p>Analyzing your reading profile and reviews...</p>
3355+ </div>
3356+ ` ;
3357+
3358+ const token = SafeStorage . get ( 'bibliodrift_token' ) ;
3359+ if ( ! token ) {
3360+ identityContent . innerHTML = `
3361+ <div class="error-state" style="padding: 1.5rem; text-align: center; color: var(--text-muted);">
3362+ <p>Please log in to view your Reader Identity.</p>
3363+ </div>
3364+ ` ;
3365+ return ;
3366+ }
3367+
3368+ try {
3369+ // 1. Fetch user reviews
3370+ const reviewsResponse = await fetch ( `${ MOOD_API_BASE } /users/${ user . id } /reviews` , {
3371+ headers : { 'Authorization' : `Bearer ${ token } ` }
3372+ } ) ;
3373+
3374+ if ( ! reviewsResponse . ok ) {
3375+ throw new Error ( `Failed to fetch reviews (Status: ${ reviewsResponse . status } )` ) ;
3376+ }
3377+
3378+ const reviewsData = await reviewsResponse . json ( ) ;
3379+ const reviewsList = ( reviewsData . reviews || [ ] ) . map ( r => r . review_text ) . filter ( Boolean ) ;
3380+
3381+ // Derive genres
3382+ const allBooks = [
3383+ ...( libManager . library . current || [ ] ) ,
3384+ ...( libManager . library . want || [ ] ) ,
3385+ ...( libManager . library . finished || [ ] )
3386+ ] ;
3387+ const genres = Array . from ( new Set (
3388+ allBooks . flatMap ( book => book . volumeInfo ?. categories || [ ] )
3389+ ) ) ;
3390+
3391+ // 2. Render empty/onboarding or loaded state
3392+ if ( reviewsList . length === 0 ) {
3393+ renderEmptyIdentityState ( genres ) ;
3394+ } else {
3395+ await fetchAndRenderArchetype ( genres , reviewsList ) ;
3396+ }
3397+ } catch ( error ) {
3398+ console . error ( 'Error loading reader identity:' , error ) ;
3399+ renderErrorIdentityState ( ) ;
3400+ }
3401+ } ;
3402+
3403+ // Initialize reader identity loading
3404+ loadReaderIdentity ( ) ;
3405+
32083406 // Progress Bar Calculation
32093407 const barFinished = document . getElementById ( 'bar-finished' ) ;
32103408 const barCurrent = document . getElementById ( 'bar-current' ) ;
0 commit comments