Files
WeChatDataAnalysis/frontend/components/wrapped/visualizations/MessageCharsChart.vue
2977094657 bdd89c681a improvement(wrapped-ui): 优化 GameBoy/DOS 主题布局并增强键盘可视化
- slide 模式在 GameBoy/DOS 下采用更紧凑的标题字号与间距\n- MessageChars 键盘增加 GameBoy 风格外观与功能键样式
2026-02-03 16:32:12 +08:00

1720 lines
48 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="w-full">
<!-- 聊天气泡区域 -->
<div class="rounded-2xl border border-[#00000010] bg-[#F5F5F5] p-3 sm:p-4">
<div class="flex flex-col gap-3">
<!-- Received (left) -->
<div class="flex items-end gap-2">
<div class="avatar-box bg-white">
<svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="#07C160" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 3h10a2 2 0 0 1 2 2v14H6V5a2 2 0 0 1 2-2z" />
<path d="M6 7H4a2 2 0 0 0-2 2v10h4" />
</svg>
</div>
<div class="bubble-left">
<div class="wrapped-label text-xs text-[#00000066]">你收到的字</div>
<div class="mt-0.5 wrapped-number text-xl sm:text-2xl text-[#000000e6]">
{{ formatInt(receivedChars) }}
</div>
<div class="mt-1 wrapped-body text-xs text-[#7F7F7F]">
<template v-if="receivedA4Text">{{ receivedA4Text }}</template>
<template v-else-if="receivedChars > 0">这么多字都是别人认真对你的回应</template>
<template v-else>今年还没有收到文字消息</template>
</div>
<div v-if="receivedA4 && receivedA4.a4 && receivedA4.a4.sheets > 0" class="mt-1 text-[10px] text-[#00000055] wrapped-label">
{{ formatInt(receivedA4.a4.sheets) }} A4 · 堆叠高度约 {{ receivedA4.a4.heightText }}
</div>
</div>
</div>
<!-- Sent (right) -->
<div class="flex items-end gap-2 justify-end">
<div class="bubble-right">
<div class="wrapped-label text-xs text-[#00000080] text-right">你发送的字</div>
<div class="mt-0.5 wrapped-number text-xl sm:text-2xl text-[#000000e6] text-right">
{{ formatInt(sentChars) }}
</div>
<div class="mt-1 wrapped-body text-xs text-[#00000099] text-right">
<template v-if="sentBookText">{{ sentBookText }}</template>
<template v-else-if="sentChars > 0">这么多字是你打出的每一次认真</template>
<template v-else>今年还没有文字消息</template>
</div>
</div>
<div class="avatar-box bg-[#95EC69]">
<svg viewBox="0 0 24 24" class="w-4 h-4" fill="none" stroke="#1f2d1f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 20h9" />
<path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z" />
</svg>
</div>
</div>
</div>
</div>
<!-- 键盘磨损可视化 -->
<div class="keyboard-outer">
<div class="keyboard-inner">
<!-- 顶部信息 -->
<div class="keyboard-header">
<div class="keyboard-dots">
<span class="dot dot-red"></span>
<span class="dot dot-yellow"></span>
<span class="dot dot-green"></span>
</div>
<div class="keyboard-hint">键帽磨损程度反映你的打字频率</div>
<div class="keyboard-stats">{{ formatInt(totalKeyHits) }} 次敲击</div>
</div>
<!-- 键盘主体 -->
<div class="keyboard-body">
<div v-for="(row, ri) in keyboardRows" :key="ri" class="kb-row">
<div
v-for="key in row"
:key="key.code + key.label"
class="kb-key"
:class="[`kb-w-${key.w || 1}`, { 'kb-space': key.isSpace, 'kb-func': key.isFunc }, getKeyClasses(key.code)]"
:style="getKeyStyle(key.code)"
>
<div class="kb-key-top" :style="getKeyTopStyle(key.code)">
<span v-if="key.sub" class="kb-sub" :style="getLabelStyle(key.code)">{{ key.sub }}</span>
<span v-if="key.label" class="kb-label" :class="{ 'kb-label-sm': key.isFunc }" :style="getLabelStyle(key.code)">{{ key.label }}</span>
<div v-if="key.isSpace" class="kb-space-bar"></div>
</div>
</div>
</div>
</div>
<!-- 底部品牌 -->
<div class="keyboard-brand">微信机械键盘</div>
</div>
</div>
</div>
</template>
<script setup>
const props = defineProps({
data: { type: Object, default: () => ({}) }
})
const nfInt = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 0 })
const formatInt = (n) => nfInt.format(Math.round(Number(n) || 0))
const sentChars = computed(() => Number(props.data?.sentChars || 0))
const receivedChars = computed(() => Number(props.data?.receivedChars || 0))
const sentBookText = computed(() => props.data?.sentBook?.text || '')
const receivedA4 = computed(() => props.data?.receivedA4 || null)
const receivedA4Text = computed(() => receivedA4.value?.text || '')
// 从后端获取键盘统计数据
const keyboardData = computed(() => props.data?.keyboard || null)
// 总敲击次数(优先使用后端数据)
const totalKeyHits = computed(() => {
// 注意totalKeyHits 可能为 0比如今年没发出文字消息不能用 truthy 判断。
const backend = Number(keyboardData.value?.totalKeyHits)
if (Number.isFinite(backend)) return backend
// 回退:粗略估算(仅基于"你发送的字",假设拼音输入 + 一定比例空格)
const letterHits = Math.round(sentChars.value * 2.8)
return letterHits + Math.round(letterHits * 0.15)
})
// 获取各键的敲击次数(优先使用后端精确数据)
const keyHitsMap = computed(() => {
const backendHits = keyboardData.value?.keyHits
const backendSpace = Number(keyboardData.value?.spaceHits || 0)
if (backendHits && typeof backendHits === 'object') {
// 后端把空格次数单独放在 spaceHits这里合并进 keyHitsMap 以便空格键也能显示磨损。
return backendSpace > 0 ? { ...backendHits, space: backendSpace } : backendHits
}
// 回退:使用默认频率估算(仅基于"你发送的字"
const defaultFreq = {
a: 0.121, i: 0.118, n: 0.098, e: 0.089, u: 0.082, g: 0.072, h: 0.065,
o: 0.052, z: 0.048, s: 0.042, x: 0.038, y: 0.036, d: 0.032, l: 0.028,
j: 0.026, b: 0.022, c: 0.020, w: 0.018, m: 0.016, f: 0.014, t: 0.012,
r: 0.010, p: 0.009, k: 0.007, q: 0.005, v: 0.001,
}
const letterHits = Math.round(sentChars.value * 2.8)
const spaceHits = Math.round(letterHits * 0.15)
const result = {}
for (const [k, freq] of Object.entries(defaultFreq)) {
result[k] = Math.round(freq * letterHits)
}
if (spaceHits > 0) result.space = spaceHits
return result
})
// 计算磨损程度0-1基于实际敲击次数
const getWear = (code) => {
const k = code.toLowerCase()
const hits = Number(keyHitsMap.value[k] || 0)
if (!Number.isFinite(hits) || hits <= 0) return 0
const values = Object.values(keyHitsMap.value).map((v) => Number(v) || 0)
const maxHits = Math.max(...values, 1)
// 小数量级键(如数字/标点)容易"看起来没变化",用对数缩放增强可视化差异。
const ratio = Math.log1p(hits) / Math.log1p(maxHits)
return Math.min(1, Math.pow(ratio, 1.6))
}
// ========== 10级磨损系统 ==========
// 磨损等级阈值
const LEVEL_THRESHOLDS = [0, 0.10, 0.20, 0.35, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00]
// 获取磨损等级 (0-10)
const getWearLevel = (wear) => {
if (wear === 0) return 0
if (wear >= 1) return 10
for (let i = 1; i < LEVEL_THRESHOLDS.length; i++) {
if (wear <= LEVEL_THRESHOLDS[i]) return i
}
return 10
}
// 获取当前等级内的进度 (0-1),用于等级内平滑过渡
const getWearProgress = (wear) => {
const level = getWearLevel(wear)
if (level === 0 || level === 10) return 0
const start = LEVEL_THRESHOLDS[level - 1]
const end = LEVEL_THRESHOLDS[level]
return (wear - start) / (end - start)
}
// 根据键码确定缺角/破碎方向 (用于 level 8-9)
const getBrokenCorner = (code) => {
const hash = code.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0)
return ['tl', 'tr', 'bl', 'br'][hash % 4]
}
// 获取键的CSS类名
const getKeyClasses = (code) => {
const level = getWearLevel(getWear(code))
const classes = [`kb-level-${level}`]
if (level === 8) classes.push(`kb-broken-${getBrokenCorner(code)}`)
if (level === 9) classes.push(`kb-shattered-${getBrokenCorner(code)}`)
return classes.join(' ')
}
// 键盘布局
const keyboardRows = [
[
{ code: '`', label: '`', sub: '~' }, { code: '1', label: '1', sub: '!' },
{ code: '2', label: '2', sub: '@' }, { code: '3', label: '3', sub: '#' },
{ code: '4', label: '4', sub: '$' }, { code: '5', label: '5', sub: '%' },
{ code: '6', label: '6', sub: '^' }, { code: '7', label: '7', sub: '&' },
{ code: '8', label: '8', sub: '*' }, { code: '9', label: '9', sub: '(' },
{ code: '0', label: '0', sub: ')' }, { code: '-', label: '-', sub: '_' },
{ code: '=', label: '=', sub: '+' }, { code: 'backspace', label: '⌫', w: 2, isFunc: true },
],
[
{ code: 'tab', label: 'Tab', w: 1.5, isFunc: true },
{ code: 'q', label: 'Q' }, { code: 'w', label: 'W' }, { code: 'e', label: 'E' },
{ code: 'r', label: 'R' }, { code: 't', label: 'T' }, { code: 'y', label: 'Y' },
{ code: 'u', label: 'U' }, { code: 'i', label: 'I' }, { code: 'o', label: 'O' },
{ code: 'p', label: 'P' }, { code: '[', label: '[', sub: '{' },
{ code: ']', label: ']', sub: '}' }, { code: '\\', label: '\\', sub: '|', w: 1.5 },
],
[
{ code: 'caps', label: 'Caps', w: 1.75, isFunc: true },
{ code: 'a', label: 'A' }, { code: 's', label: 'S' }, { code: 'd', label: 'D' },
{ code: 'f', label: 'F' }, { code: 'g', label: 'G' }, { code: 'h', label: 'H' },
{ code: 'j', label: 'J' }, { code: 'k', label: 'K' }, { code: 'l', label: 'L' },
{ code: ';', label: ';', sub: ':' }, { code: "'", label: "'", sub: '"' },
{ code: 'enter', label: 'Enter', w: 2.25, isFunc: true },
],
[
{ code: 'shift', label: 'Shift', w: 2.25, isFunc: true },
{ code: 'z', label: 'Z' }, { code: 'x', label: 'X' }, { code: 'c', label: 'C' },
{ code: 'v', label: 'V' }, { code: 'b', label: 'B' }, { code: 'n', label: 'N' },
{ code: 'm', label: 'M' }, { code: ',', label: ',', sub: '<' },
{ code: '.', label: '.', sub: '>' }, { code: '/', label: '/', sub: '?' },
{ code: 'shift', label: 'Shift', w: 2.75, isFunc: true },
],
[
{ code: 'ctrl', label: 'Ctrl', w: 1.25, isFunc: true },
{ code: 'alt', label: 'Alt', w: 1.25, isFunc: true },
{ code: 'space', label: '', w: 6.25, isSpace: true },
{ code: 'alt', label: 'Alt', w: 1.25, isFunc: true },
{ code: 'ctrl', label: 'Ctrl', w: 1.25, isFunc: true },
],
]
// 键帽样式 - 基于10级系统
const getKeyStyle = (code) => {
const w = getWear(code)
const level = getWearLevel(w)
const progress = getWearProgress(w)
// 等级对应的基础亮度和饱和度
const levelParams = [
{ l: 94, s: 8 }, // 0: 全新
{ l: 92, s: 12 }, // 1: 指纹油渍
{ l: 89, s: 16 }, // 2: 涂层初磨
{ l: 85, s: 20 }, // 3: 涂层磨损
{ l: 80, s: 24 }, // 4: 涂层剥落
{ l: 76, s: 26 }, // 5: 表面凹陷
{ l: 72, s: 28 }, // 6: 细微裂纹
{ l: 68, s: 30 }, // 7: 网状龟裂
{ l: 64, s: 32 }, // 8: 缺角碎裂
{ l: 60, s: 34 }, // 9: 严重破损
{ l: 45, s: 10 }, // 10: 完全报废(轴体底座)
]
const current = levelParams[level]
const next = levelParams[Math.min(level + 1, 10)]
// 等级内平滑插值
const baseL = current.l + (next.l - current.l) * progress
const sat = current.s + (next.s - current.s) * progress
return {
'--key-bg': `hsl(40, ${sat}%, ${baseL}%)`,
'--key-bg-dark': `hsl(40, ${sat}%, ${baseL - 6}%)`,
'--key-border': `hsl(40, ${Math.max(0, sat - 2)}%, ${baseL - 18}%)`,
'--wear-level': level,
'--wear-progress': progress,
}
}
const getKeyTopStyle = (code) => {
const w = getWear(code)
const level = getWearLevel(w)
const progress = getWearProgress(w)
// 高光和深度随等级变化
const highlightLevels = [0.55, 0.48, 0.40, 0.32, 0.24, 0.18, 0.12, 0.08, 0.05, 0.02, 0]
const depthLevels = [0.12, 0.14, 0.16, 0.18, 0.20, 0.24, 0.28, 0.32, 0.36, 0.40, 0.45]
const highlight = highlightLevels[level] + (highlightLevels[Math.min(level + 1, 10)] - highlightLevels[level]) * progress
const depth = depthLevels[level] + (depthLevels[Math.min(level + 1, 10)] - depthLevels[level]) * progress
return {
background: `linear-gradient(180deg, var(--key-bg) 0%, var(--key-bg-dark) 100%)`,
boxShadow: `inset 0 1px 0 rgba(255,255,255,${highlight}), inset 0 -1px 2px rgba(0,0,0,${depth})`,
}
}
const getLabelStyle = (code) => {
const w = getWear(code)
const level = getWearLevel(w)
const progress = getWearProgress(w)
// 标签透明度和模糊度随等级变化
const opacityLevels = [1, 0.95, 0.88, 0.75, 0.55, 0.35, 0.18, 0.08, 0.03, 0.01, 0]
const blurLevels = [0, 0.2, 0.4, 0.7, 1.0, 1.4, 1.8, 2.2, 2.6, 3.0, 3.5]
const opacity = opacityLevels[level] + (opacityLevels[Math.min(level + 1, 10)] - opacityLevels[level]) * progress
const blur = blurLevels[level] + (blurLevels[Math.min(level + 1, 10)] - blurLevels[level]) * progress
return {
opacity: opacity,
filter: `blur(${blur}px)`,
}
}
</script>
<style>
/* 头像 */
.avatar-box {
@apply w-8 h-8 rounded-lg border border-[#00000010] flex items-center justify-center flex-shrink-0;
}
/* 气泡 - 左侧 */
.bubble-left {
@apply relative max-w-[85%] bg-white shadow-sm rounded-xl px-3 py-2;
}
.bubble-left::before {
content: '';
position: absolute;
left: -6px;
bottom: 8px;
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-right: 6px solid #fff;
filter: drop-shadow(-1px 0 0 rgba(0,0,0,0.05));
}
/* 气泡 - 右侧 */
.bubble-right {
@apply relative max-w-[85%] bg-[#95EC69] shadow-sm rounded-xl px-3 py-2;
}
.bubble-right::after {
content: '';
position: absolute;
right: -6px;
bottom: 8px;
width: 0;
height: 0;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 6px solid #95EC69;
filter: drop-shadow(1px 0 0 rgba(0,0,0,0.05));
}
/* 键盘外框 */
.keyboard-outer {
@apply mt-3 rounded-2xl p-1;
/* Needed for ::before/::after overlays (scanlines, speaker grill, etc.) */
position: relative;
isolation: isolate;
background: linear-gradient(145deg, #ffffff, #e8e8e8);
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 8px 24px rgba(0,0,0,0.12);
}
.keyboard-inner {
@apply rounded-xl p-3;
background: linear-gradient(180deg, #fbfbfb, #f0f0f0);
border: 1px solid rgba(0,0,0,0.06);
}
.keyboard-header {
@apply relative flex items-center justify-between mb-2 px-1;
}
.keyboard-dots {
@apply flex items-center gap-1.5;
}
.dot {
@apply w-2 h-2 rounded-full;
}
.dot-red { background: #ff5f57; }
.dot-yellow { background: #febc2e; }
.dot-green { background: #28c840; }
.keyboard-hint {
@apply absolute left-1/2 -translate-x-1/2 text-[9px] text-[#00000055];
}
.keyboard-stats {
@apply text-[10px] text-[#00000066] tracking-wider;
font-family: ui-monospace, monospace;
}
.keyboard-body {
@apply rounded-lg p-2;
background: #f4f4f5;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.12);
}
.kb-row {
@apply flex justify-center gap-[3px] mb-[3px];
}
.kb-row:last-child {
@apply mb-0;
}
/* 键帽 */
.kb-key {
--unit: 22px;
height: 26px;
width: var(--unit);
position: relative;
}
@media (min-width: 640px) {
.kb-key {
--unit: 28px;
height: 32px;
}
}
/* 宽度变体 */
.kb-w-1 { width: var(--unit); }
.kb-w-1\.25 { width: calc(var(--unit) * 1.25 + 3px * 0.25); }
.kb-w-1\.5 { width: calc(var(--unit) * 1.5 + 3px * 0.5); }
.kb-w-1\.75 { width: calc(var(--unit) * 1.75 + 3px * 0.75); }
.kb-w-2 { width: calc(var(--unit) * 2 + 3px); }
.kb-w-2\.25 { width: calc(var(--unit) * 2.25 + 3px * 1.25); }
.kb-w-2\.75 { width: calc(var(--unit) * 2.75 + 3px * 1.75); }
.kb-w-6\.25 { width: calc(var(--unit) * 6.25 + 3px * 5.25); }
.kb-key::before {
content: '';
position: absolute;
inset: 0;
top: 2px;
background: #d4d4d8;
border-radius: 4px;
}
.kb-key-top {
position: absolute;
inset: 0;
bottom: 2px;
border-radius: 4px;
border: 1px solid var(--key-border);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
}
.kb-sub {
font-size: 7px;
line-height: 1;
color: #666;
margin-bottom: 1px;
}
@media (min-width: 640px) {
.kb-sub {
font-size: 8px;
}
}
.kb-label {
font-size: 10px;
font-weight: 500;
color: #262626;
line-height: 1;
text-shadow: 0 1px 0 rgba(255,255,255,0.6);
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', sans-serif;
}
@media (min-width: 640px) {
.kb-label {
font-size: 11px;
}
}
.kb-label-sm {
font-size: 7px !important;
font-weight: 400;
}
@media (min-width: 640px) {
.kb-label-sm {
font-size: 8px !important;
}
}
.kb-space-bar {
width: 50%;
height: 3px;
background: rgba(0,0,0,0.12);
border-radius: 2px;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.18);
}
/* ========== 10级磨损视觉效果 ========== */
/* Level 1: 指纹油渍 - 中心淡淡油光 */
.kb-level-1 .kb-key-top::after {
content: '';
position: absolute;
inset: 20%;
background: radial-gradient(ellipse at center, rgba(255,255,255,0.15) 0%, transparent 70%);
pointer-events: none;
border-radius: 50%;
}
/* Level 2: 涂层初磨 - 边缘变薄 */
.kb-level-2 .kb-key-top::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 50%, rgba(180,160,140,0.12) 100%);
pointer-events: none;
border-radius: 4px;
}
/* Level 3: 涂层磨损 - 浅色磨痕纹理 */
.kb-level-3 .kb-key-top::after {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 30% 40%, rgba(160,140,120,0.15) 0%, transparent 50%),
radial-gradient(ellipse at 70% 60%, rgba(160,140,120,0.12) 0%, transparent 45%);
pointer-events: none;
border-radius: 4px;
}
/* Level 4: 涂层剥落 - 斑驳露底色 */
.kb-level-4 .kb-key-top::after {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 25% 35%, rgba(140,120,100,0.25) 0%, transparent 40%),
radial-gradient(ellipse at 65% 55%, rgba(140,120,100,0.20) 0%, transparent 35%),
radial-gradient(ellipse at 50% 70%, rgba(140,120,100,0.18) 0%, transparent 30%);
pointer-events: none;
border-radius: 4px;
}
/* Level 5: 表面凹陷 - 中心凹陷阴影 */
.kb-level-5 .kb-key-top {
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.18),
inset 0 -1px 2px rgba(0,0,0,0.24),
inset 0 3px 6px rgba(0,0,0,0.15) !important;
}
.kb-level-5 .kb-key-top::after {
content: '';
position: absolute;
inset: 15%;
background: radial-gradient(ellipse at center, rgba(0,0,0,0.12) 0%, transparent 70%);
pointer-events: none;
border-radius: 50%;
}
/* Level 6: 细微裂纹 - 边缘1-2条细裂纹 */
.kb-level-6 .kb-key-top {
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.12),
inset 0 -1px 2px rgba(0,0,0,0.28),
inset 0 3px 8px rgba(0,0,0,0.18) !important;
}
.kb-level-6 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
background:
/* 主裂纹 - 从右上角延伸 */
linear-gradient(135deg,
transparent 0%, transparent 72%,
rgba(80,60,40,0.35) 72%, rgba(80,60,40,0.35) 73%,
transparent 73%, transparent 100%
),
/* 细小分支 */
linear-gradient(160deg,
transparent 0%, transparent 78%,
rgba(80,60,40,0.25) 78%, rgba(80,60,40,0.25) 79%,
transparent 79%, transparent 100%
);
pointer-events: none;
border-radius: 4px;
z-index: 2;
}
.kb-level-6 .kb-key-top::after {
content: '';
position: absolute;
inset: 10%;
background: radial-gradient(ellipse at center, rgba(0,0,0,0.15) 0%, transparent 70%);
pointer-events: none;
border-radius: 50%;
}
/* Level 7: 网状龟裂 - 多条裂纹交叉 */
.kb-level-7 .kb-key-top {
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.08),
inset 0 -1px 2px rgba(0,0,0,0.32),
inset 0 4px 10px rgba(0,0,0,0.22) !important;
}
.kb-level-7 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
background:
/* 主裂纹 - 对角线 */
linear-gradient(135deg,
transparent 0%, transparent 25%,
rgba(70,50,30,0.4) 25%, rgba(70,50,30,0.4) 26%,
transparent 26%, transparent 65%,
rgba(70,50,30,0.35) 65%, rgba(70,50,30,0.35) 66%,
transparent 66%, transparent 100%
),
/* 交叉裂纹 */
linear-gradient(45deg,
transparent 0%, transparent 35%,
rgba(70,50,30,0.3) 35%, rgba(70,50,30,0.3) 36%,
transparent 36%, transparent 70%,
rgba(70,50,30,0.25) 70%, rgba(70,50,30,0.25) 71%,
transparent 71%, transparent 100%
),
/* 横向裂纹 */
linear-gradient(95deg,
transparent 0%, transparent 40%,
rgba(70,50,30,0.28) 40%, rgba(70,50,30,0.28) 41%,
transparent 41%, transparent 100%
);
pointer-events: none;
border-radius: 4px;
z-index: 2;
}
.kb-level-7 .kb-key-top::after {
content: '';
position: absolute;
inset: 5%;
background: radial-gradient(ellipse at center, rgba(0,0,0,0.18) 0%, transparent 65%);
pointer-events: none;
border-radius: 50%;
}
/* Level 8: 缺角碎裂 - clip-path切割缺角 */
.kb-level-8 .kb-key-top {
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.05),
inset 0 -1px 2px rgba(0,0,0,0.36),
inset 0 4px 12px rgba(0,0,0,0.25) !important;
}
.kb-level-8 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
background:
linear-gradient(135deg,
transparent 0%, transparent 20%,
rgba(60,40,20,0.45) 20%, rgba(60,40,20,0.45) 21%,
transparent 21%, transparent 55%,
rgba(60,40,20,0.4) 55%, rgba(60,40,20,0.4) 56%,
transparent 56%, transparent 100%
),
linear-gradient(45deg,
transparent 0%, transparent 30%,
rgba(60,40,20,0.35) 30%, rgba(60,40,20,0.35) 31%,
transparent 31%, transparent 65%,
rgba(60,40,20,0.3) 65%, rgba(60,40,20,0.3) 66%,
transparent 66%, transparent 100%
);
pointer-events: none;
border-radius: 4px;
z-index: 2;
}
/* 缺角方向变体 */
.kb-broken-tl .kb-key-top {
clip-path: polygon(18% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 22%);
}
.kb-broken-tr .kb-key-top {
clip-path: polygon(0% 0%, 82% 0%, 100% 20%, 100% 100%, 0% 100%);
}
.kb-broken-bl .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 20% 100%, 0% 78%);
}
.kb-broken-br .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% 80%, 82% 100%, 0% 100%);
}
/* Level 9: 严重破损 - 大面积不规则破碎 */
.kb-level-9 .kb-key-top {
box-shadow:
inset 0 1px 0 rgba(255,255,255,0.02),
inset 0 -1px 2px rgba(0,0,0,0.40),
inset 0 5px 14px rgba(0,0,0,0.30) !important;
}
.kb-level-9 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
background:
linear-gradient(125deg,
transparent 0%, transparent 15%,
rgba(50,30,10,0.5) 15%, rgba(50,30,10,0.5) 16%,
transparent 16%, transparent 45%,
rgba(50,30,10,0.45) 45%, rgba(50,30,10,0.45) 46%,
transparent 46%, transparent 100%
),
linear-gradient(55deg,
transparent 0%, transparent 25%,
rgba(50,30,10,0.4) 25%, rgba(50,30,10,0.4) 26%,
transparent 26%, transparent 60%,
rgba(50,30,10,0.35) 60%, rgba(50,30,10,0.35) 61%,
transparent 61%, transparent 100%
),
linear-gradient(170deg,
transparent 0%, transparent 50%,
rgba(50,30,10,0.38) 50%, rgba(50,30,10,0.38) 51%,
transparent 51%, transparent 100%
);
pointer-events: none;
border-radius: 4px;
z-index: 2;
}
/* 严重破碎方向变体 */
.kb-shattered-tl .kb-key-top {
clip-path: polygon(28% 0%, 100% 0%, 100% 100%, 0% 100%, 0% 35%, 12% 18%);
}
.kb-shattered-tr .kb-key-top {
clip-path: polygon(0% 0%, 72% 0%, 88% 15%, 100% 32%, 100% 100%, 0% 100%);
}
.kb-shattered-bl .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 30% 100%, 10% 82%, 0% 65%);
}
.kb-shattered-br .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% 68%, 90% 85%, 70% 100%, 0% 100%);
}
/* Level 10: 完全报废 - 键帽消失,显示轴体 */
.kb-level-10 .kb-key-top {
opacity: 0 !important;
}
.kb-level-10::before {
/* 轴座底座 - 深灰色凹槽 */
background: linear-gradient(180deg, #3a3a3c 0%, #2c2c2e 100%) !important;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.5);
}
.kb-level-10::after {
content: '';
position: absolute;
/* 十字轴心居中 */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 40%;
height: 40%;
/* Cherry MX 风格十字轴 */
background:
/* 横向 */
linear-gradient(90deg,
transparent 0%, transparent 30%,
#606065 30%, #707075 35%, #606065 40%,
#555558 45%, #555558 55%,
#606065 60%, #707075 65%, #606065 70%,
transparent 70%, transparent 100%
),
/* 纵向 */
linear-gradient(0deg,
transparent 0%, transparent 30%,
#606065 30%, #707075 35%, #606065 40%,
#555558 45%, #555558 55%,
#606065 60%, #707075 65%, #606065 70%,
transparent 70%, transparent 100%
);
border-radius: 1px;
box-shadow:
0 1px 2px rgba(0,0,0,0.4),
inset 0 0 1px rgba(255,255,255,0.1);
z-index: 1;
}
/* 高磨损等级性能优化 */
.kb-level-8 .kb-key-top,
.kb-level-9 .kb-key-top {
will-change: clip-path;
}
.keyboard-brand {
@apply mt-2 text-center text-[8px] text-[#00000025] tracking-[0.15em] uppercase;
}
/* ========== 复古模式 - 8-bit 像素风格键盘 ========== */
/* 全局像素化渲染 */
.wrapped-retro .keyboard-outer,
.wrapped-retro .keyboard-outer * {
image-rendering: pixelated;
-webkit-font-smoothing: none;
-moz-osx-font-smoothing: unset;
}
/* 键盘外框 - 粗像素边框Game Boy 风格 */
.wrapped-retro .keyboard-outer {
border-radius: 0;
background: #8b956d;
border: none;
padding: 4px;
/* 多层像素边框效果 */
box-shadow:
0 0 0 4px #2d3320,
0 0 0 8px #5a6448,
0 0 0 10px #2d3320,
inset 0 0 0 2px #a5b38a;
}
.wrapped-retro .keyboard-inner {
border-radius: 0;
background: #9aa582;
border: none;
padding: 6px;
/* 内凹像素边框 */
box-shadow:
inset 4px 4px 0 #6b7a54,
inset -4px -4px 0 #c5d4a8;
}
/* 顶部装饰点 - 大像素方块 + 闪烁动画 */
.wrapped-retro .dot {
border-radius: 0;
width: 10px;
height: 10px;
box-shadow:
2px 2px 0 rgba(0,0,0,0.5),
inset -2px -2px 0 rgba(0,0,0,0.3);
}
.wrapped-retro .dot-red {
background: #e43b44;
animation: pixel-blink 1s steps(2) infinite;
}
.wrapped-retro .dot-yellow {
background: #f7d51d;
animation: pixel-blink 1.5s steps(2) infinite 0.3s;
}
.wrapped-retro .dot-green {
background: #63c64d;
animation: pixel-blink 2s steps(2) infinite 0.6s;
}
@keyframes pixel-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
/* 统计文字 - 像素字体 + 阴影 */
.wrapped-retro .keyboard-stats {
font-family: var(--font-pixel-10), 'Courier New', monospace;
font-size: 10px;
color: #2d3320;
letter-spacing: 1px;
text-shadow: 1px 1px 0 #c5d4a8;
}
.wrapped-retro .keyboard-hint {
font-family: var(--font-pixel-10), 'Courier New', monospace;
font-size: 9px;
color: #4a5a38;
text-shadow: 1px 1px 0 #c5d4a8;
}
/* 键盘主体 - 像素网格背景 */
.wrapped-retro .keyboard-body {
border-radius: 0;
background:
repeating-linear-gradient(
0deg,
#7a8a62 0px, #7a8a62 2px,
#8b9b72 2px, #8b9b72 4px
);
padding: 4px;
box-shadow:
inset 3px 3px 0 #5a6a48,
inset -3px -3px 0 #a5b592;
}
/* 键帽基础 - 粗像素边框 */
.wrapped-retro .kb-key::before {
border-radius: 0;
background: #3d4a2d;
box-shadow: none;
}
.wrapped-retro .kb-key-top {
border-radius: 0;
border: none;
background: #c5d4a8 !important;
/* 3D 像素凸起效果 - 多层 box-shadow */
box-shadow:
inset -3px -3px 0 #7a8a62,
inset 3px 3px 0 #e8f4d8,
inset -1px -1px 0 #5a6a48,
inset 1px 1px 0 #f0fce0 !important;
}
/* 键帽标签 - 粗像素字体 */
.wrapped-retro .kb-label {
font-family: var(--font-pixel-10), 'Courier New', monospace;
font-size: 8px;
font-weight: bold;
color: #2d3320;
text-shadow: 1px 1px 0 #e8f4d8;
filter: none !important;
letter-spacing: 0;
}
@media (min-width: 640px) {
.wrapped-retro .kb-label {
font-size: 10px;
}
}
.wrapped-retro .kb-label-sm {
font-size: 6px !important;
}
@media (min-width: 640px) {
.wrapped-retro .kb-label-sm {
font-size: 7px !important;
}
}
.wrapped-retro .kb-sub {
font-family: var(--font-pixel-10), 'Courier New', monospace;
font-size: 5px;
color: #5a6a48;
text-shadow: none;
filter: none !important;
}
@media (min-width: 640px) {
.wrapped-retro .kb-sub {
font-size: 6px;
}
}
/* 空格键凹槽 - 像素凹陷 */
.wrapped-retro .kb-space-bar {
border-radius: 0;
background: #5a6a48;
box-shadow:
inset 2px 2px 0 #3d4a2d,
inset -1px -1px 0 #7a8a62;
height: 3px;
}
/* 品牌文字 */
.wrapped-retro .keyboard-brand {
font-family: var(--font-pixel-10), 'Courier New', monospace;
color: #5a6a48;
letter-spacing: 2px;
text-shadow: 1px 1px 0 #c5d4a8;
}
/* ========== 复古模式 - 像素化磨损等级 ========== */
/* Level 1-2: 轻微变色 + 中心模糊污渍 */
.wrapped-retro .kb-level-1 .kb-key-top,
.wrapped-retro .kb-level-2 .kb-key-top {
background: #b5c498 !important;
}
.wrapped-retro .kb-level-1 .kb-key-top::after,
.wrapped-retro .kb-level-2 .kb-key-top::after {
content: '';
position: absolute;
inset: 25%;
background: radial-gradient(ellipse at center, #8b9b72 0%, transparent 70%);
opacity: 0.4;
pointer-events: none;
}
.wrapped-retro .kb-level-2 .kb-key-top::after {
inset: 20%;
opacity: 0.5;
}
/* Level 3-4: 更深的模糊污渍 */
.wrapped-retro .kb-level-3 .kb-key-top,
.wrapped-retro .kb-level-4 .kb-key-top {
background: #a5b488 !important;
}
.wrapped-retro .kb-level-3 .kb-key-top::after,
.wrapped-retro .kb-level-4 .kb-key-top::after {
content: '';
position: absolute;
inset: 15%;
background: radial-gradient(ellipse at center, #6b7a54 0%, #7a8a62 40%, transparent 70%);
opacity: 0.5;
pointer-events: none;
}
.wrapped-retro .kb-level-4 .kb-key-top::after {
inset: 10%;
background: radial-gradient(ellipse at center, #5a6a48 0%, #6b7a54 30%, #7a8a62 50%, transparent 75%);
opacity: 0.6;
}
/* Level 5-6: 凹陷效果 + 磨损渐变 + 裂纹线 */
.wrapped-retro .kb-level-5 .kb-key-top,
.wrapped-retro .kb-level-6 .kb-key-top {
background: #95a478 !important;
/* 反转阴影 = 凹陷效果 */
box-shadow:
inset 3px 3px 0 #6b7a54,
inset -3px -3px 0 #b5c498,
inset 1px 1px 0 #5a6a48 !important;
}
.wrapped-retro .kb-level-5 .kb-key-top::before,
.wrapped-retro .kb-level-6 .kb-key-top::before {
content: '';
position: absolute;
top: 0;
right: 2px;
width: 2px;
height: 40%;
background: linear-gradient(to bottom, #3d4a2d, transparent);
z-index: 2;
}
.wrapped-retro .kb-level-5 .kb-key-top::after,
.wrapped-retro .kb-level-6 .kb-key-top::after {
content: '';
position: absolute;
inset: 10%;
background: radial-gradient(ellipse at center, #5a6a48 0%, #6b7a54 30%, transparent 65%);
opacity: 0.6;
pointer-events: none;
}
.wrapped-retro .kb-level-6 .kb-key-top::before {
height: 55%;
}
.wrapped-retro .kb-level-6 .kb-key-top::after {
opacity: 0.7;
}
/* Level 7-8: 严重磨损 + 裂纹 */
.wrapped-retro .kb-level-7 .kb-key-top,
.wrapped-retro .kb-level-8 .kb-key-top {
background: #859468 !important;
box-shadow:
inset 3px 3px 0 #5a6a48,
inset -3px -3px 0 #a5b488,
inset 2px 2px 0 #4a5a38 !important;
}
.wrapped-retro .kb-level-7 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
/* 对角裂纹 */
background:
linear-gradient(135deg,
transparent 0%, transparent 45%,
#3d4a2d 45%, #3d4a2d 48%,
transparent 48%, transparent 100%
);
pointer-events: none;
z-index: 2;
}
.wrapped-retro .kb-level-7 .kb-key-top::after {
content: '';
position: absolute;
inset: 5%;
background: radial-gradient(ellipse at center, #4a5a38 0%, #5a6a48 25%, transparent 60%);
opacity: 0.7;
pointer-events: none;
}
/* Level 8: 缺角 + 交叉裂纹 + 深度磨损 */
.wrapped-retro .kb-broken-tl .kb-key-top {
clip-path: polygon(6px 0%, 100% 0%, 100% 100%, 0% 100%, 0% 6px);
}
.wrapped-retro .kb-broken-tr .kb-key-top {
clip-path: polygon(0% 0%, calc(100% - 6px) 0%, 100% 6px, 100% 100%, 0% 100%);
}
.wrapped-retro .kb-broken-bl .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 6px 100%, 0% calc(100% - 6px));
}
.wrapped-retro .kb-broken-br .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% calc(100% - 6px), calc(100% - 6px) 100%, 0% 100%);
}
.wrapped-retro .kb-level-8 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
/* 交叉裂纹 */
background:
linear-gradient(135deg, transparent 46%, #3d4a2d 46%, #3d4a2d 50%, transparent 50%),
linear-gradient(45deg, transparent 46%, #3d4a2d 46%, #3d4a2d 50%, transparent 50%);
pointer-events: none;
z-index: 2;
}
.wrapped-retro .kb-level-8 .kb-key-top::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, #3d4a2d 0%, #4a5a38 20%, transparent 55%);
opacity: 0.75;
pointer-events: none;
}
/* Level 9: 严重损坏 - 大块缺失 + 深色磨损 */
.wrapped-retro .kb-level-9 .kb-key-top {
background: #758458 !important;
box-shadow:
inset 3px 3px 0 #4a5a38,
inset -3px -3px 0 #95a478 !important;
}
.wrapped-retro .kb-shattered-tl .kb-key-top {
clip-path: polygon(10px 0%, 100% 0%, 100% 100%, 0% 100%, 0% 10px);
}
.wrapped-retro .kb-shattered-tr .kb-key-top {
clip-path: polygon(0% 0%, calc(100% - 10px) 0%, 100% 10px, 100% 100%, 0% 100%);
}
.wrapped-retro .kb-shattered-bl .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 10px 100%, 0% calc(100% - 10px));
}
.wrapped-retro .kb-shattered-br .kb-key-top {
clip-path: polygon(0% 0%, 100% 0%, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0% 100%);
}
.wrapped-retro .kb-level-9 .kb-key-top::before {
content: '';
position: absolute;
inset: 0;
/* 多条裂纹 */
background:
linear-gradient(135deg, transparent 30%, #2d3320 30%, #2d3320 33%, transparent 33%),
linear-gradient(135deg, transparent 60%, #2d3320 60%, #2d3320 63%, transparent 63%),
linear-gradient(45deg, transparent 45%, #2d3320 45%, #2d3320 48%, transparent 48%);
pointer-events: none;
z-index: 2;
}
.wrapped-retro .kb-level-9 .kb-key-top::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, #2d3320 0%, #3d4a2d 15%, #4a5a38 30%, transparent 55%);
opacity: 0.8;
pointer-events: none;
}
/* Level 10: 完全报废 - 键帽脱落露出轴体 */
.wrapped-retro .kb-level-10 .kb-key-top {
opacity: 0 !important;
}
.wrapped-retro .kb-level-10::before {
background: #2d3320 !important;
border-radius: 0;
box-shadow:
inset 2px 2px 0 #1a1f14,
inset -2px -2px 0 #4a5a38;
}
.wrapped-retro .kb-level-10::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
/* 简化轴体 - 纯色方块 + 凹陷效果 */
background: #6b7a54;
border-radius: 0;
box-shadow:
inset 2px 2px 0 #8b956d,
inset -2px -2px 0 #4a5a38;
z-index: 1;
}
@media (min-width: 640px) {
.wrapped-retro .kb-level-10::after {
width: 10px;
height: 10px;
}
}
/* 复古模式下移除性能优化的 will-change */
.wrapped-retro .kb-level-8 .kb-key-top,
.wrapped-retro .kb-level-9 .kb-key-top {
will-change: auto;
}
/* 复古模式 - 扫描线效果(可选,增强 CRT 感) */
.wrapped-retro .keyboard-outer::after {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent 0px,
transparent 2px,
rgba(0, 0, 0, 0.03) 2px,
rgba(0, 0, 0, 0.03) 4px
);
pointer-events: none;
z-index: 100;
}
/* ========== Game Boy 主题 - DMG / WorkBoy 风格外观 ========== */
/* 设计参考:原版 Game Boy 的“灰机身 + 蓝/洋红点缀”配色,以及社区常见的 DMG 键盘配色(例如 GMK DMG 系列)。 */
.wrapped-theme-gameboy .keyboard-outer {
background: #c4c1bd;
box-shadow:
0 0 0 4px #2b2b2b,
0 0 0 8px #e8e4e2,
0 0 0 10px #2b2b2b,
inset 0 0 0 2px rgba(255,255,255,0.35);
}
.wrapped-theme-gameboy .keyboard-inner {
background: #d6d2ce;
box-shadow:
inset 4px 4px 0 #a9a39f,
inset -4px -4px 0 #f5f2ee;
}
/* 顶部信息条做成“屏幕窗” */
.wrapped-theme-gameboy .keyboard-header {
margin-bottom: 8px;
padding: 8px 10px;
border: 2px solid #081820;
background: #e0f8d0;
box-shadow:
inset -2px -2px 0 #88c070,
inset 2px 2px 0 #f8f8f8;
}
/* 左侧指示灯:保留一个“电量灯”,其余隐藏 */
.wrapped-theme-gameboy .dot {
width: 8px;
height: 8px;
border-radius: 0;
box-shadow:
0 0 0 2px #081820,
inset 1px 1px 0 rgba(255,255,255,0.25);
}
.wrapped-theme-gameboy .dot-red {
background: #9a2257;
animation: none !important;
}
.wrapped-theme-gameboy .dot-yellow,
.wrapped-theme-gameboy .dot-green {
display: none;
}
.wrapped-theme-gameboy .keyboard-dots::after {
content: 'BATTERY';
margin-left: 6px;
font-family: var(--font-pixel-10), 'Courier New', monospace;
font-size: 8px;
letter-spacing: 1px;
color: #081820;
}
.wrapped-theme-gameboy .keyboard-hint,
.wrapped-theme-gameboy .keyboard-stats {
color: #081820;
text-shadow: none;
}
/* 键盘底板:偏灰,避免整块“全绿”导致像素感过强 */
.wrapped-theme-gameboy .keyboard-body {
background:
repeating-linear-gradient(
0deg,
#bdb8b4 0px, #bdb8b4 2px,
#c9c4c0 2px, #c9c4c0 4px
);
box-shadow:
inset 3px 3px 0 #a9a39f,
inset -3px -3px 0 #f5f2ee;
}
/* 功能键给一点“蓝色键帽”点缀(更像 DMG 配色键盘) */
.wrapped-theme-gameboy .kb-func .kb-key-top {
background: #494786 !important;
box-shadow:
inset -3px -3px 0 #2f2d3a,
inset 3px 3px 0 #6a66a2,
inset -1px -1px 0 #1b1a22,
inset 1px 1px 0 #8a86d0 !important;
}
.wrapped-theme-gameboy .kb-func .kb-label,
.wrapped-theme-gameboy .kb-func .kb-sub {
color: #e0f8d0 !important;
text-shadow: 1px 1px 0 rgba(0,0,0,0.35);
}
/* “音响孔”点阵 */
.wrapped-theme-gameboy .keyboard-outer::before {
content: '';
position: absolute;
right: 10px;
bottom: 12px;
width: 52px;
height: 18px;
background:
radial-gradient(circle, rgba(8, 24, 32, 0.35) 35%, transparent 36%) 0 0 / 6px 6px;
opacity: 0.85;
pointer-events: none;
z-index: 2;
}
/* 品牌文字换成更贴近主题的“WorkBoy”梗 */
.wrapped-theme-gameboy .keyboard-brand {
position: relative;
color: transparent;
text-shadow: none;
}
.wrapped-theme-gameboy .keyboard-brand::before {
content: 'WECHAT WORKBOY';
color: rgba(8, 24, 32, 0.7);
font-family: var(--font-pixel-10), 'Courier New', monospace;
letter-spacing: 2px;
}
/* ========== DOS 终端主题 - 黑底绿字键盘 ========== */
.wrapped-theme-dos .keyboard-outer {
border-radius: 0;
background: #000000;
border: 2px solid #33ff33;
padding: 4px;
box-shadow:
0 0 10px rgba(51, 255, 51, 0.3),
inset 0 0 20px rgba(51, 255, 51, 0.05);
}
.wrapped-theme-dos .keyboard-inner {
border-radius: 0;
background: #0a0a0a;
border: 1px solid #22aa22;
padding: 6px;
}
.wrapped-theme-dos .keyboard-header {
border-bottom: 1px solid #22aa22;
padding-bottom: 6px;
margin-bottom: 6px;
}
.wrapped-theme-dos .dot {
border-radius: 0;
width: 8px;
height: 8px;
}
.wrapped-theme-dos .dot-red { background: #ff3333; box-shadow: 0 0 5px #ff3333; }
.wrapped-theme-dos .dot-yellow { background: #ffaa00; box-shadow: 0 0 5px #ffaa00; }
.wrapped-theme-dos .dot-green { background: #33ff33; box-shadow: 0 0 5px #33ff33; }
.wrapped-theme-dos .keyboard-stats,
.wrapped-theme-dos .keyboard-hint {
font-family: 'Courier New', 'Consolas', monospace;
color: #33ff33;
text-shadow: 0 0 5px #33ff33;
}
.wrapped-theme-dos .keyboard-body {
border-radius: 0;
background: #050505;
box-shadow: inset 0 0 10px rgba(51, 255, 51, 0.1);
}
.wrapped-theme-dos .kb-key::before {
border-radius: 0;
background: #111111;
}
.wrapped-theme-dos .kb-key-top {
border-radius: 0;
border: 1px solid #33ff33 !important;
background: #0a0a0a !important;
box-shadow:
0 0 3px rgba(51, 255, 51, 0.3),
inset 0 0 5px rgba(51, 255, 51, 0.1) !important;
}
.wrapped-theme-dos .kb-label,
.wrapped-theme-dos .kb-sub {
font-family: 'Courier New', 'Consolas', monospace;
color: #33ff33 !important;
text-shadow: 0 0 3px #33ff33;
filter: none !important;
opacity: 1 !important;
}
.wrapped-theme-dos .kb-space-bar {
border-radius: 0;
background: #33ff33;
box-shadow: 0 0 5px #33ff33;
height: 2px;
}
.wrapped-theme-dos .keyboard-brand {
font-family: 'Courier New', 'Consolas', monospace;
color: #22aa22;
text-shadow: 0 0 3px #22aa22;
}
/* DOS 磨损效果 - 发光强度变化 */
.wrapped-theme-dos .kb-level-1 .kb-key-top,
.wrapped-theme-dos .kb-level-2 .kb-key-top {
box-shadow: 0 0 5px rgba(51, 255, 51, 0.4) !important;
}
.wrapped-theme-dos .kb-level-3 .kb-key-top,
.wrapped-theme-dos .kb-level-4 .kb-key-top {
box-shadow: 0 0 8px rgba(51, 255, 51, 0.5) !important;
}
.wrapped-theme-dos .kb-level-5 .kb-key-top,
.wrapped-theme-dos .kb-level-6 .kb-key-top {
box-shadow: 0 0 10px rgba(51, 255, 51, 0.6) !important;
}
.wrapped-theme-dos .kb-level-7 .kb-key-top,
.wrapped-theme-dos .kb-level-8 .kb-key-top {
box-shadow: 0 0 12px rgba(51, 255, 51, 0.7) !important;
border-color: #44ff44 !important;
}
.wrapped-theme-dos .kb-level-9 .kb-key-top {
box-shadow: 0 0 15px rgba(51, 255, 51, 0.8) !important;
border-color: #55ff55 !important;
}
.wrapped-theme-dos .kb-level-10 .kb-key-top {
opacity: 0 !important;
}
.wrapped-theme-dos .kb-level-10::before {
background: #000000 !important;
border: 1px dashed #22aa22;
}
.wrapped-theme-dos .kb-level-10::after {
content: 'X';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: auto;
height: auto;
background: none;
color: #ff3333;
font-family: 'Courier New', monospace;
font-size: 10px;
text-shadow: 0 0 5px #ff3333;
}
/* ========== Win98 主题 - 键盘外观 ========== */
.wrapped-theme-win98 .keyboard-outer {
border-radius: 0;
background: #c0c0c0;
border: 1px solid #808080;
padding: 4px;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
.wrapped-theme-win98 .keyboard-inner {
border-radius: 0;
background: #dfdfdf;
border: 1px solid #808080;
padding: 6px;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
.wrapped-theme-win98 .keyboard-header {
border-bottom: 1px solid rgba(0, 0, 0, 0.18);
padding-bottom: 6px;
margin-bottom: 6px;
}
.wrapped-theme-win98 .dot {
border-radius: 0;
width: 8px;
height: 8px;
box-shadow: none;
}
.wrapped-theme-win98 .dot-red { background: #800000; }
.wrapped-theme-win98 .dot-yellow { background: #808000; }
.wrapped-theme-win98 .dot-green { background: #008000; }
.wrapped-theme-win98 .keyboard-stats,
.wrapped-theme-win98 .keyboard-hint {
font-family: inherit;
color: rgba(0, 0, 0, 0.72);
text-shadow: none;
}
.wrapped-theme-win98 .keyboard-body {
border-radius: 0;
background: #ffffff;
border: 1px solid #808080;
box-shadow:
inset 1px 1px 0 #000000,
inset -1px -1px 0 #ffffff;
}
.wrapped-theme-win98 .kb-key::before {
border-radius: 0;
background: #808080;
}
.wrapped-theme-win98 .kb-key-top {
border-radius: 0;
border: 1px solid #808080 !important;
background: #c0c0c0 !important;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000 !important;
}
.wrapped-theme-win98 .kb-label,
.wrapped-theme-win98 .kb-sub {
font-family: inherit;
color: #000000 !important;
text-shadow: none !important;
filter: none !important;
opacity: 1 !important;
}
.wrapped-theme-win98 .kb-space-bar {
border-radius: 0;
background: #000080;
box-shadow: none;
height: 2px;
}
.wrapped-theme-win98 .keyboard-brand {
font-family: inherit;
color: rgba(0, 0, 0, 0.35);
text-shadow: none;
}
/* DOS 聊天气泡主题适配 */
.wrapped-theme-dos .bubble-left,
.wrapped-theme-dos .bubble-right {
background: #0a0a0a;
border: 1px solid #33ff33;
box-shadow: 0 0 5px rgba(51, 255, 51, 0.2);
}
.wrapped-theme-dos .bubble-left::before {
border-right-color: #0a0a0a;
filter: drop-shadow(-1px 0 0 #33ff33);
}
.wrapped-theme-dos .bubble-right {
background: #0a0a0a;
border-color: #33ff33;
}
.wrapped-theme-dos .bubble-right::after {
border-left-color: #0a0a0a;
filter: drop-shadow(1px 0 0 #33ff33);
}
.wrapped-theme-dos .avatar-box {
background: #0a0a0a;
border-color: #33ff33;
}
.wrapped-theme-dos .avatar-box svg {
stroke: #33ff33;
}
/* ========== Game Boy 主题 - 聊天气泡适配 ========== */
/* 聊天区域背景 */
.wrapped-theme-gameboy .rounded-2xl.border.bg-\[\#F5F5F5\] {
background: #9bbc0f !important;
border: 4px solid #306230 !important;
border-radius: 0 !important;
box-shadow:
inset -2px -2px 0 0 #306230,
inset 2px 2px 0 0 #c5d870;
}
/* 气泡 - 左侧 */
.wrapped-theme-gameboy .bubble-left {
background: #8bac0f;
border: 3px solid #306230;
border-radius: 0;
box-shadow:
inset -2px -2px 0 0 #306230,
inset 2px 2px 0 0 #9bbc0f;
}
.wrapped-theme-gameboy .bubble-left::before {
border-right-color: #8bac0f;
filter: none;
}
/* 气泡 - 右侧 */
.wrapped-theme-gameboy .bubble-right {
background: #9bbc0f;
border: 3px solid #306230;
border-radius: 0;
box-shadow:
inset -2px -2px 0 0 #306230,
inset 2px 2px 0 0 #c5d870;
}
.wrapped-theme-gameboy .bubble-right::after {
border-left-color: #9bbc0f;
filter: none;
}
/* 头像 */
.wrapped-theme-gameboy .avatar-box {
background: #9bbc0f;
border: 2px solid #306230;
border-radius: 0;
}
.wrapped-theme-gameboy .avatar-box svg {
stroke: #0f380f;
}
/* 文字样式 */
.wrapped-theme-gameboy .bubble-left .wrapped-label,
.wrapped-theme-gameboy .bubble-right .wrapped-label {
color: #306230 !important;
}
.wrapped-theme-gameboy .bubble-left .wrapped-number,
.wrapped-theme-gameboy .bubble-right .wrapped-number {
color: #0f380f !important;
font-family: var(--font-pixel-10), 'Courier New', monospace;
}
.wrapped-theme-gameboy .bubble-left .wrapped-body,
.wrapped-theme-gameboy .bubble-right .wrapped-body {
color: #306230 !important;
}
/* ========== Win98 主题 - 聊天气泡适配 ========== */
/* 聊天区域背景 */
.wrapped-theme-win98 .rounded-2xl.border.bg-\[\#F5F5F5\] {
background: #c0c0c0 !important;
border: 1px solid #808080 !important;
border-radius: 0 !important;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
/* 气泡 - 左侧 */
.wrapped-theme-win98 .bubble-left {
background: #ffffff;
border: 1px solid #808080;
border-radius: 0;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
.wrapped-theme-win98 .bubble-left::before {
border-right-color: #ffffff;
filter: none;
}
/* 气泡 - 右侧 */
.wrapped-theme-win98 .bubble-right {
background: #dfdfdf;
border: 1px solid #808080;
border-radius: 0;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
.wrapped-theme-win98 .bubble-right::after {
border-left-color: #dfdfdf;
filter: none;
}
/* 头像 */
.wrapped-theme-win98 .avatar-box {
background: #c0c0c0;
border-color: #808080;
border-radius: 0;
box-shadow:
inset 1px 1px 0 #ffffff,
inset -1px -1px 0 #000000;
}
.wrapped-theme-win98 .avatar-box svg {
stroke: #000080;
}
/* 文字样式(气泡内需要更“黑白”) */
.wrapped-theme-win98 .bubble-left .wrapped-label,
.wrapped-theme-win98 .bubble-right .wrapped-label {
color: rgba(0, 0, 0, 0.65) !important;
}
.wrapped-theme-win98 .bubble-left .wrapped-number,
.wrapped-theme-win98 .bubble-right .wrapped-number {
color: #000080 !important;
}
.wrapped-theme-win98 .bubble-left .wrapped-body,
.wrapped-theme-win98 .bubble-right .wrapped-body {
color: rgba(0, 0, 0, 0.85) !important;
}
/* ========== DOS 主题 - 聊天气泡文字适配 ========== */
.wrapped-theme-dos .bubble-left .wrapped-label,
.wrapped-theme-dos .bubble-right .wrapped-label {
color: #22aa22 !important;
text-shadow: 0 0 3px #22aa22;
font-family: 'Courier New', monospace;
}
.wrapped-theme-dos .bubble-left .wrapped-number,
.wrapped-theme-dos .bubble-right .wrapped-number {
color: #33ff33 !important;
text-shadow: 0 0 5px #33ff33;
font-family: 'Courier New', monospace;
}
.wrapped-theme-dos .bubble-left .wrapped-body,
.wrapped-theme-dos .bubble-right .wrapped-body {
color: #33ff33 !important;
text-shadow: 0 0 3px #33ff33;
font-family: 'Courier New', monospace;
}
</style>