feat: 新增消息文字颜色特效装扮(七彩/流光/霓虹/火焰/冰蓝)

- 新增 msg_text_color 商品类型,扩展 shop_items.type ENUM
- DecorationService 支持 text_color 槽位,自动注入消息广播
- CSS 动画:rainbow(彩虹流动)、shimmer(金属流光)、neon(霓虹脉动)、flame(火焰跃动)、ice(冰蓝流转)
- ShopItemSeeder 新增 5 款文字颜色特效商品
- 商店前端新增「🌈 文字颜色」装扮分组
- 消息渲染 appendMessage/buildChatMessageContent 支持文字特效 class
This commit is contained in:
pllx
2026-04-27 06:17:22 +00:00
parent dd9ae46c04
commit 277cb617da
5 changed files with 172 additions and 23 deletions
@@ -170,6 +170,82 @@
50% { text-shadow: 0 0 10px #fbbf24, 0 0 16px #ef4444; }
}
/* ========== 消息文字颜色特效 ========== */
.msg-text--rainbow {
background: linear-gradient(90deg,
#ef4444, #f97316, #eab308, #22c55e, #06b6d4, #3b82f6, #a855f7, #ef4444);
background-size: 300% 100%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 600;
animation: text-rainbow 3.5s linear infinite;
}
@keyframes text-rainbow {
0% { background-position: 0% 50%; }
100% { background-position: 300% 50%; }
}
.msg-text--shimmer {
background: linear-gradient(110deg,
#6b7280 0%, #9ca3af 18%, #f3f4f6 28%, #d1d5db 36%,
#6b7280 52%, #9ca3af 66%, #f9fafb 74%, #6b7280 100%);
background-size: 300% 100%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 600;
animation: text-shimmer 4s ease-in-out infinite;
}
@keyframes text-shimmer {
0% { background-position: 100% 50%; }
100% { background-position: -200% 50%; }
}
.msg-text--neon {
color: #a855f7 !important;
font-weight: 600;
text-shadow:
0 0 7px #a855f7,
0 0 14px #7c3aed,
0 0 28px #6366f1,
0 0 42px #4f46e5;
animation: text-neon 2s ease-in-out infinite alternate;
}
@keyframes text-neon {
0% { text-shadow: 0 0 7px #a855f7, 0 0 14px #7c3aed, 0 0 28px #6366f1; }
100% { text-shadow: 0 0 14px #c084fc, 0 0 28px #a855f7, 0 0 48px #7c3aed, 0 0 64px #6366f1; }
}
.msg-text--flame {
background: linear-gradient(180deg, #fef3c7 0%, #f59e0b 28%, #ea580c 60%, #dc2626 100%);
background-size: 100% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 700;
animation: text-flame 1.2s ease-in-out infinite alternate;
}
@keyframes text-flame {
0% { background-position: 0% 0%; }
100% { background-position: 0% 100%; }
}
.msg-text--ice {
background: linear-gradient(135deg, #e0f2fe 0%, #7dd3fc 25%, #38bdf8 50%, #bae6fd 75%, #e0f2fe 100%);
background-size: 200% 200%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 600;
animation: text-ice 3s ease-in-out infinite;
filter: drop-shadow(0 0 4px rgba(56, 189, 248, 0.5));
}
@keyframes text-ice {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
/* ========== 头像框 ========== */
.avatar-frame-wrapper {
position: relative;
@@ -1630,15 +1706,16 @@
/**
* 构建普通聊天消息的正文区域,支持缩略图与过期占位渲染。
*/
function buildChatMessageContent(msg, fontColor) {
function buildChatMessageContent(msg, fontColor, textColorClass) {
const rawContent = msg.content || '';
if (msg.message_type === 'image' && !isExpiredChatImageMessage(msg)) {
const fullUrl = escapeHtml(msg.image_url || '');
const thumbUrl = escapeHtml(msg.image_thumb_url || '');
const imageName = escapeHtml(msg.image_original_name || '聊天图片');
const captionColorStyle = textColorClass ? '' : `color:${fontColor};`;
const captionHtml = rawContent ?
`<span style="display:inline-block; max-width:220px; color:${fontColor}; line-height:1.55;">${rawContent}</span>` :
`<span class="msg-content${textColorClass || ''}" style="display:inline-block; max-width:220px; ${captionColorStyle} line-height:1.55;">${rawContent}</span>` :
'';
return `
@@ -1654,8 +1731,9 @@
}
if (msg.message_type === 'expired_image' || isExpiredChatImageMessage(msg)) {
const captionColorStyle = textColorClass ? '' : `color:${fontColor};`;
const captionHtml = rawContent ?
`<span style="display:inline-block; color:${fontColor}; line-height:1.55;">${rawContent}</span>` :
`<span class="msg-content${textColorClass || ''}" style="display:inline-block; ${captionColorStyle} line-height:1.55;">${rawContent}</span>` :
'';
return `
@@ -2368,6 +2446,11 @@
nameClass = ' msg-name--' + msg.msg_name_color.replace(/^msg_name_/, '');
}
var textColorClass = '';
if (msg.msg_text_color) {
textColorClass = ' msg-text--' + msg.msg_text_color.replace(/^msg_text_/, '');
}
// 系统用户名列表(不可被选为聊天对象)
const systemUsers = ['钓鱼播报', '星海小博士', '系统传音', '系统公告', '送花播报', '系统', '欢迎', '系统播报', '神秘箱子'];
@@ -2464,7 +2547,7 @@
}
const headImg =
`<img src="${headImgSrc}" style="display:inline;width:16px;height:16px;vertical-align:middle;margin-right:2px;mix-blend-mode: multiply;" onerror="this.src='/images/headface/1.gif'">`;
const messageBodyHtml = buildChatMessageContent(msg, fontColor);
const messageBodyHtml = buildChatMessageContent(msg, fontColor, textColorClass);
let html = '';
@@ -2559,7 +2642,7 @@
return '【' + clickableUser(uName, '#000099') + '】';
});
html =
`${headImg}<span style="font-weight: bold;">${clickableUser(msg.from_user, fontColor, nameClass)}</span><span class="msg-content" style="color: ${fontColor}; font-weight: bold;">${parsedContent}</span>`;
`${headImg}<span style="font-weight: bold;">${clickableUser(msg.from_user, fontColor, nameClass)}</span><span class="msg-content${textColorClass}" style="color: ${fontColor}; font-weight: bold;">${parsedContent}</span>`;
} else {
// 自动升级播报 / 赠礼通知 / 婚恋广播 等非游戏系统传音:金色左边框,轻量提示样式,不喧宾夺主
div.style.cssText =
@@ -2593,7 +2676,7 @@
});
html =
`${headImg}<span style="font-weight: bold;">${clickableUser(msg.from_user, fontColor, nameClass)}</span><span class="msg-content" style="color: ${fontColor}">${parsedContent}</span>${giftHtml}`;
`${headImg}<span style="font-weight: bold;">${clickableUser(msg.from_user, fontColor, nameClass)}</span><span class="msg-content${textColorClass}" style="color: ${fontColor}">${parsedContent}</span>${giftHtml}`;
}
} else if (msg.is_secret) {
if (msg.from_user === '系统') {
@@ -2610,7 +2693,7 @@
buildActionStr(msg.action, fromHtml, toHtml, '悄悄说') :
`${fromHtml}对${toHtml}悄悄说:`;
html =
`${headImg}<span class="msg-secret">${verbStr}</span><span class="msg-content" style="color: ${fontColor}; font-style: italic;">${messageBodyHtml}</span>`;
`${headImg}<span class="msg-secret">${verbStr}</span><span class="msg-content${textColorClass}" style="color: ${fontColor}; font-style: italic;">${messageBodyHtml}</span>`;
}
} else if (msg.to_user && msg.to_user !== '大家') {
// 对特定对象说话
@@ -2619,14 +2702,14 @@
const verbStr = msg.action ?
buildActionStr(msg.action, fromHtml, toHtml) :
`${fromHtml}对${toHtml}说:`;
html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${messageBodyHtml}</span>`;
html = `${headImg}${verbStr}<span class="msg-content${textColorClass}" style="color: ${fontColor}">${messageBodyHtml}</span>`;
} else {
// 对大家说话
const fromHtml = clickableUser(msg.from_user, '#000099', nameClass);
const verbStr = msg.action ?
buildActionStr(msg.action, fromHtml, '大家') :
`${fromHtml}对大家说:`;
html = `${headImg}${verbStr}<span class="msg-content" style="color: ${fontColor}">${messageBodyHtml}</span>`;
html = `${headImg}${verbStr}<span class="msg-content${textColorClass}" style="color: ${fontColor}">${messageBodyHtml}</span>`;
}
if (!timeStrOverride) {