fix(profile): visual frame previews in shop + sidebar avatar sync
Shop items of type 'frame' now render a real avatar-sized preview with
the frame's CSS applied (instead of a generic lucide icon) so buyers
see exactly what they're paying for. Title items get a tag-shaped
preview in their color. The avatar-frames section above the shop also
shows the user's actual avatar inside the frame circles, not 'LS' text.
Sidebar nav-avatar now:
• renders the uploaded avatar_url instead of always showing initials
(LS.initPage + new LS.refreshNavAvatar helper)
• picks up frame CSS on every page via applyCosmetics — previously
only dashboard.html applied it
• repaints immediately after picking/deleting an avatar preset
(avPickPreset / avDelete now call LS.setUser + LS.refreshNavAvatar)
Backend getMyActive resolves avatar_frame to {id, css} for both
gamification frames ('fire', 'crown', ...) and shop-purchased frames
('shop_<id>'), so the client doesn't need a second round-trip to
look up the CSS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,24 @@
|
||||
const db = require('../db/db');
|
||||
const { AVATAR_FRAMES } = require('./gamification/_shared');
|
||||
|
||||
/* Resolve a frame slug to { id, css }. Slug can be either a gamification
|
||||
frame id (e.g. 'fire', 'crown') or a shop-purchased frame stored as
|
||||
'shop_<itemId>'. Returns null for 'default' / unknown. */
|
||||
function resolveFrame(slug) {
|
||||
if (!slug || slug === 'default') return null;
|
||||
if (slug.startsWith('shop_')) {
|
||||
const itemId = Number(slug.slice(5));
|
||||
if (!Number.isFinite(itemId)) return null;
|
||||
const item = db.prepare('SELECT data FROM shop_items WHERE id = ? AND type = ?').get(itemId, 'frame');
|
||||
if (!item) return null;
|
||||
try {
|
||||
const data = JSON.parse(item.data || '{}');
|
||||
return { id: slug, css: data.css || '' };
|
||||
} catch { return { id: slug, css: '' }; }
|
||||
}
|
||||
const f = AVATAR_FRAMES.find(fr => fr.id === slug);
|
||||
return f ? { id: f.id, css: f.css || '' } : null;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════
|
||||
Shop — Items, Purchases, Coins
|
||||
@@ -71,11 +91,10 @@ function getMyActive(req, res) {
|
||||
// Resolve full data for each active item
|
||||
const result = { frame: null, title: null, effect: null };
|
||||
|
||||
// Frame from avatar_frame (gamification frames) — handled separately
|
||||
// Shop frame override
|
||||
if (u.avatar_frame && u.avatar_frame !== 'default') {
|
||||
result.frame = { id: u.avatar_frame };
|
||||
}
|
||||
// Frame: resolve either a gamification frame ('fire', 'crown', ...) or
|
||||
// a shop-purchased one ('shop_<id>') to { id, css } so applyCosmetics
|
||||
// on the client can render it without an extra round-trip.
|
||||
result.frame = resolveFrame(u.avatar_frame);
|
||||
|
||||
if (u.active_title) {
|
||||
const item = db.prepare('SELECT data FROM shop_items WHERE id = ?').get(u.active_title);
|
||||
|
||||
Reference in New Issue
Block a user