Compare commits
5 Commits
@@ -14,6 +14,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Derive version from tag
|
||||
shell: pwsh
|
||||
@@ -26,6 +28,67 @@ jobs:
|
||||
"TAG_NAME=$tag" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
"VERSION=$version" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
|
||||
- name: Generate release notes from commits
|
||||
shell: pwsh
|
||||
run: |
|
||||
git fetch --force --tags
|
||||
|
||||
$tag = $env:TAG_NAME
|
||||
if ([string]::IsNullOrWhiteSpace($tag)) {
|
||||
throw "TAG_NAME is empty"
|
||||
}
|
||||
|
||||
$repo = "${{ github.repository }}"
|
||||
|
||||
$prev = ""
|
||||
try {
|
||||
$commit = (git rev-list -n 1 $tag).Trim()
|
||||
if (-not [string]::IsNullOrWhiteSpace($commit)) {
|
||||
$prev = (git describe --tags --abbrev=0 "$commit^" 2>$null).Trim()
|
||||
}
|
||||
} catch {}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($prev)) {
|
||||
# Fallback: best-effort previous version tag by semver-ish sorting.
|
||||
$prev = (git tag --list "v*" --sort=-v:refname | Where-Object { $_ -ne $tag } | Select-Object -First 1)
|
||||
}
|
||||
|
||||
$range = ""
|
||||
if (-not [string]::IsNullOrWhiteSpace($prev)) {
|
||||
$range = "$prev..$tag"
|
||||
}
|
||||
|
||||
$lines = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($range)) {
|
||||
$lines = @(git log --no-merges --pretty=format:"- %s (%h)" --reverse $range)
|
||||
} else {
|
||||
# First release tag / missing history: include a small recent window.
|
||||
$lines = @(git log --no-merges --pretty=format:"- %s (%h)" --reverse -n 50)
|
||||
}
|
||||
|
||||
if (-not $lines -or $lines.Count -eq 0) {
|
||||
$lines = @("- 修复了一些已知问题,提升了稳定性。")
|
||||
}
|
||||
|
||||
$max = 60
|
||||
if ($lines.Count -gt $max) {
|
||||
$total = $lines.Count
|
||||
$lines = @($lines | Select-Object -First $max)
|
||||
$lines += "- ...(共 $total 条提交,更多请查看完整变更链接)"
|
||||
}
|
||||
|
||||
$body = @()
|
||||
$body += "## 更新内容 ($tag)"
|
||||
$body += ""
|
||||
$body += $lines
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($prev)) {
|
||||
$body += ""
|
||||
$body += "完整变更: https://github.com/$repo/compare/$prev...$tag"
|
||||
}
|
||||
|
||||
($body -join "`n") | Out-File -FilePath release-notes.md -Encoding utf8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -71,7 +134,7 @@ jobs:
|
||||
with:
|
||||
tag_name: ${{ env.TAG_NAME }}
|
||||
name: ${{ env.TAG_NAME }}
|
||||
generate_release_notes: true
|
||||
body_path: release-notes.md
|
||||
files: |
|
||||
desktop/dist/*Setup*.exe
|
||||
desktop/dist/*Setup*.exe.blockmap
|
||||
|
||||
@@ -65,26 +65,13 @@
|
||||
|
||||
## 年度总结
|
||||
|
||||
年度总结现在支持 3 种不同风格(style1、style2、style3)。如果你对某个风格有更好的修改建议,或有新风格的点子,欢迎到 Issue 区反馈:https://github.com/LifeArchiveProject/WeChatDataAnalysis/issues
|
||||
|
||||
> ⚠️ **提醒**:年度总结目前还不是最终版本,后续还会增加新总结或新风格。
|
||||
> ⚠️ **提醒**:年度总结目前还不是最终版本,后续还会增加新总结或新内容。
|
||||
|
||||
也欢迎加入下方 QQ 群一起讨论。
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"><b>Style 1</b></td>
|
||||
<td align="center"><b>Style 2</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="frontend/public/style1.png" alt="年度总结 Style 1" width="400"/></td>
|
||||
<td><img src="frontend/public/style2.png" alt="年度总结 Style 2" width="400"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="2"><b>Style 3</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" colspan="2"><img src="frontend/public/style3.png" alt="年度总结 Style 3" width="400"/></td>
|
||||
<td align="center"><img src="frontend/public/style1.png" alt="年度总结 Modern"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -265,9 +265,108 @@ function setWindowProgressBar(value) {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function looksLikeHtml(input) {
|
||||
if (!input) return false;
|
||||
const s = String(input);
|
||||
if (!s.includes("<") || !s.includes(">")) return false;
|
||||
// Be conservative: only treat the note as HTML if it contains common tags we expect from GitHub-rendered bodies.
|
||||
return /<(p|div|br|ul|ol|li|a|strong|em|tt|code|pre|h[1-6])\b/i.test(s);
|
||||
}
|
||||
|
||||
function htmlToPlainText(html) {
|
||||
if (!html) return "";
|
||||
|
||||
let text = String(html);
|
||||
|
||||
// Drop script/style blocks entirely.
|
||||
text = text.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
||||
text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
||||
|
||||
// Keep links readable after stripping tags.
|
||||
text = text.replace(
|
||||
/<a\s+[^>]*href=(["'])([^"']+)\1[^>]*>([\s\S]*?)<\/a>/gi,
|
||||
(_m, _q, href, inner) => {
|
||||
const innerText = String(inner).replace(/<[^>]*>/g, "").trim();
|
||||
const url = String(href || "").trim();
|
||||
if (!url) return innerText;
|
||||
if (!innerText) return url;
|
||||
return `${innerText} (${url})`;
|
||||
}
|
||||
);
|
||||
|
||||
// Preserve line breaks / list structure before stripping remaining tags.
|
||||
text = text.replace(/<\s*br\s*\/?>/gi, "\n");
|
||||
text = text.replace(/<\/\s*(p|div|h1|h2|h3|h4|h5|h6)\s*>/gi, "\n");
|
||||
text = text.replace(/<\s*li[^>]*>/gi, "- ");
|
||||
text = text.replace(/<\/\s*li\s*>/gi, "\n");
|
||||
text = text.replace(/<\/\s*(ul|ol)\s*>/gi, "\n");
|
||||
|
||||
// Strip remaining tags.
|
||||
text = text.replace(/<[^>]*>/g, "");
|
||||
|
||||
// Decode the handful of entities we commonly see from GitHub-rendered HTML.
|
||||
const named = {
|
||||
nbsp: " ",
|
||||
amp: "&",
|
||||
lt: "<",
|
||||
gt: ">",
|
||||
quot: '"',
|
||||
apos: "'",
|
||||
"#39": "'",
|
||||
};
|
||||
text = text.replace(/&([a-z0-9#]+);/gi, (m, name) => {
|
||||
const key = String(name || "").toLowerCase();
|
||||
if (named[key] != null) return named[key];
|
||||
|
||||
// Numeric entities (decimal / hex).
|
||||
const decMatch = key.match(/^#(\d+)$/);
|
||||
if (decMatch) {
|
||||
const n = Number(decMatch[1]);
|
||||
if (Number.isFinite(n) && n >= 0 && n <= 0x10ffff) {
|
||||
try {
|
||||
return String.fromCodePoint(n);
|
||||
} catch {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
const hexMatch = key.match(/^#x([0-9a-f]+)$/i);
|
||||
if (hexMatch) {
|
||||
const n = Number.parseInt(hexMatch[1], 16);
|
||||
if (Number.isFinite(n) && n >= 0 && n <= 0x10ffff) {
|
||||
try {
|
||||
return String.fromCodePoint(n);
|
||||
} catch {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
return m;
|
||||
});
|
||||
|
||||
// Normalize whitespace/newlines.
|
||||
text = text.replace(/\r\n/g, "\n");
|
||||
text = text.replace(/\n{3,}/g, "\n\n");
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
function normalizeReleaseNotes(releaseNotes) {
|
||||
if (!releaseNotes) return "";
|
||||
if (typeof releaseNotes === "string") return releaseNotes;
|
||||
|
||||
const normalizeText = (value) => {
|
||||
if (value == null) return "";
|
||||
const raw = typeof value === "string" ? value : String(value);
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return "";
|
||||
if (looksLikeHtml(trimmed)) return htmlToPlainText(trimmed);
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
if (typeof releaseNotes === "string") return normalizeText(releaseNotes);
|
||||
if (Array.isArray(releaseNotes)) {
|
||||
const parts = [];
|
||||
for (const item of releaseNotes) {
|
||||
@@ -275,15 +374,17 @@ function normalizeReleaseNotes(releaseNotes) {
|
||||
const note = item?.note;
|
||||
const noteText =
|
||||
typeof note === "string" ? note : note != null ? JSON.stringify(note, null, 2) : "";
|
||||
const block = [version ? `v${version}` : "", noteText].filter(Boolean).join("\n");
|
||||
const block = [version ? `v${version}` : "", normalizeText(noteText)]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
if (block) parts.push(block);
|
||||
}
|
||||
return parts.join("\n\n");
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(releaseNotes, null, 2);
|
||||
return normalizeText(JSON.stringify(releaseNotes, null, 2));
|
||||
} catch {
|
||||
return String(releaseNotes);
|
||||
return normalizeText(releaseNotes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,33 +2,6 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Fusion Pixel Font - 像素字体 @font-face 声明 */
|
||||
/* 下载地址: https://github.com/TakWolf/fusion-pixel-font/releases */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fusion Pixel 12';
|
||||
src: url('/fonts/fusion-pixel-12px-monospaced-zh_hans.otf.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fusion Pixel 10';
|
||||
src: url('/fonts/fusion-pixel-10px-monospaced-zh_hans.otf.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Fusion Pixel 8';
|
||||
src: url('/fonts/fusion-pixel-8px-monospaced-zh_hans.otf.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* 自定义全局样式 - 微信配色主题 */
|
||||
@layer base {
|
||||
:root {
|
||||
@@ -69,11 +42,6 @@
|
||||
|
||||
/* 统一消息圆角(聊天所有消息共用) */
|
||||
--message-radius: 4px;
|
||||
|
||||
/* Wrapped 年度总结 - 像素字体 */
|
||||
--font-pixel-12: 'Fusion Pixel 12', 'Microsoft YaHei', sans-serif;
|
||||
--font-pixel-10: 'Fusion Pixel 10', 'Microsoft YaHei', sans-serif;
|
||||
--font-pixel-8: 'Fusion Pixel 8', 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -169,8 +137,7 @@
|
||||
animation: noise-jitter 0.5s steps(3) infinite;
|
||||
}
|
||||
|
||||
/* Wrapped 像素字体类 */
|
||||
/* Wrapped typography: default is modern; `.wrapped-retro` enables pixel font + CRT vibe. */
|
||||
/* Wrapped typography */
|
||||
.wrapped-title {
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
@@ -195,94 +162,6 @@
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.wrapped-retro .wrapped-title {
|
||||
font-family: var(--font-pixel-12);
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.05em;
|
||||
image-rendering: pixelated;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
.wrapped-retro .wrapped-title-en {
|
||||
font-family: var(--font-pixel-12);
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.08em;
|
||||
image-rendering: pixelated;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
.wrapped-retro .wrapped-body {
|
||||
font-family: var(--font-pixel-10);
|
||||
font-weight: normal;
|
||||
line-height: 1.8;
|
||||
image-rendering: pixelated;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
.wrapped-retro .wrapped-label {
|
||||
font-family: var(--font-pixel-8);
|
||||
font-weight: normal;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
image-rendering: pixelated;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
.wrapped-retro .wrapped-number {
|
||||
font-family: var(--font-pixel-12);
|
||||
font-weight: normal;
|
||||
font-variant-numeric: tabular-nums;
|
||||
image-rendering: pixelated;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
/* CRT 扫描线 - 水平线条(明显可见) */
|
||||
.crt-scanlines {
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
transparent 0px,
|
||||
transparent 3px,
|
||||
rgba(0, 0, 0, 0.15) 3px,
|
||||
rgba(0, 0, 0, 0.15) 4px
|
||||
);
|
||||
background-size: 100% 4px;
|
||||
animation: scanline-scroll 12s linear infinite;
|
||||
}
|
||||
|
||||
/* CRT RGB 子像素 - 垂直彩色条纹 */
|
||||
.crt-rgb-pixels {
|
||||
background-image: repeating-linear-gradient(
|
||||
to right,
|
||||
rgba(255, 0, 0, 0.06) 0px,
|
||||
rgba(255, 0, 0, 0.06) 1px,
|
||||
rgba(0, 255, 0, 0.06) 1px,
|
||||
rgba(0, 255, 0, 0.06) 2px,
|
||||
rgba(0, 0, 255, 0.06) 2px,
|
||||
rgba(0, 0, 255, 0.06) 3px
|
||||
);
|
||||
}
|
||||
|
||||
/* CRT 闪烁 - 亮度波动 */
|
||||
.crt-flicker {
|
||||
background-color: rgba(255, 255, 255, 0.01);
|
||||
animation: crt-flicker 0.15s infinite alternate;
|
||||
}
|
||||
|
||||
/* CRT 暗角 - 边缘渐暗(更强) */
|
||||
.crt-vignette {
|
||||
box-shadow: inset 0 0 250px 80px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* CRT 屏幕曲率效果 */
|
||||
.crt-curvature {
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
transparent 0%,
|
||||
transparent 40%,
|
||||
rgba(0, 0, 0, 0.1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.input {
|
||||
@apply w-full px-4 py-3 bg-[#f7f8fa] border border-transparent rounded-xl focus:outline-none focus:ring-2 focus:ring-[#07c160] focus:bg-white focus:border-[#07c160] transition-all duration-200;
|
||||
@@ -1224,24 +1103,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* CRT 和 Wrapped 动画关键帧 */
|
||||
@keyframes scanline-scroll {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 0 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes crt-flicker {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.985;
|
||||
}
|
||||
}
|
||||
/* Wrapped 动画关键帧 */
|
||||
|
||||
@keyframes noise-jitter {
|
||||
0% {
|
||||
@@ -1273,348 +1135,3 @@
|
||||
.wrapped-animate-in {
|
||||
animation: wrapped-fade-in 0.6s ease-out forwards;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Wrapped 主题系统 - Game Boy / DOS
|
||||
============================================ */
|
||||
|
||||
/* 复古模式共享基础样式 */
|
||||
.wrapped-retro {
|
||||
/* 共享 CSS 变量(各主题覆盖) */
|
||||
--wrapped-bg: #9bbc0f;
|
||||
--wrapped-card-bg: #8bac0f;
|
||||
--wrapped-text: #0f380f;
|
||||
--wrapped-text-secondary: #306230;
|
||||
--wrapped-accent: #0f380f;
|
||||
--wrapped-border: #306230;
|
||||
--wrapped-warning: #0f380f;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Theme 1: Game Boy - 复古绿色系
|
||||
============================================ */
|
||||
.wrapped-theme-gameboy {
|
||||
/* Game Boy 4色调色板 */
|
||||
--wrapped-bg: #9bbc0f; /* 最亮绿 */
|
||||
--wrapped-card-bg: #8bac0f; /* 次亮绿 */
|
||||
--wrapped-text: #0f380f; /* 最深绿 */
|
||||
--wrapped-text-secondary: #306230; /* 中深绿 */
|
||||
--wrapped-accent: #0f380f;
|
||||
--wrapped-border: #306230;
|
||||
--wrapped-warning: #306230;
|
||||
|
||||
/* 像素化渲染 */
|
||||
image-rendering: pixelated;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
/* Game Boy 像素化边框 */
|
||||
.wrapped-theme-gameboy .wrapped-card-shell,
|
||||
.wrapped-theme-gameboy [class*="rounded"] {
|
||||
border-radius: 0 !important;
|
||||
box-shadow:
|
||||
inset -4px -4px 0 0 #306230,
|
||||
inset 4px 4px 0 0 #9bbc0f,
|
||||
0 0 0 4px #0f380f;
|
||||
}
|
||||
|
||||
/* Game Boy 步进动画 */
|
||||
.wrapped-theme-gameboy * {
|
||||
animation-timing-function: steps(8) !important;
|
||||
}
|
||||
|
||||
/* Game Boy 按钮样式 */
|
||||
.wrapped-theme-gameboy button {
|
||||
border-radius: 0 !important;
|
||||
box-shadow:
|
||||
inset -2px -2px 0 0 #306230,
|
||||
inset 2px 2px 0 0 #9bbc0f;
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy button:active {
|
||||
box-shadow:
|
||||
inset 2px 2px 0 0 #306230,
|
||||
inset -2px -2px 0 0 #9bbc0f;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Theme 2: DOS Terminal - 黑底绿字/琥珀字
|
||||
============================================ */
|
||||
.wrapped-theme-dos {
|
||||
--wrapped-bg: #000000;
|
||||
--wrapped-card-bg: #0a0a0a;
|
||||
--wrapped-text: #33ff33; /* 磷光绿 */
|
||||
--wrapped-text-secondary: #22aa22;
|
||||
--wrapped-accent: #33ff33;
|
||||
--wrapped-border: #33ff33;
|
||||
--wrapped-warning: #ffaa00; /* 琥珀警告色 */
|
||||
|
||||
background-color: #000000 !important;
|
||||
/* 使用现有 Fusion Pixel 10px 字体 */
|
||||
font-family: var(--font-pixel-10), 'Courier New', monospace !important;
|
||||
-webkit-font-smoothing: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
/* DOS 文字发光效果 */
|
||||
.wrapped-theme-dos .wrapped-title,
|
||||
.wrapped-theme-dos .wrapped-body,
|
||||
.wrapped-theme-dos .wrapped-label,
|
||||
.wrapped-theme-dos .wrapped-number {
|
||||
color: #33ff33 !important;
|
||||
/* 从 4 层减少到 2 层,降低发光强度 */
|
||||
text-shadow:
|
||||
0 0 4px rgba(51, 255, 51, 0.8),
|
||||
0 0 8px rgba(34, 170, 34, 0.4);
|
||||
font-family: var(--font-pixel-10), 'Courier New', monospace !important;
|
||||
-webkit-font-smoothing: none;
|
||||
}
|
||||
|
||||
/* DOS ASCII 边框 */
|
||||
.wrapped-theme-dos .wrapped-card-shell,
|
||||
.wrapped-theme-dos [class*="border"] {
|
||||
border: 2px solid #33ff33 !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow:
|
||||
0 0 5px #33ff33,
|
||||
inset 0 0 5px rgba(51, 255, 51, 0.1);
|
||||
}
|
||||
|
||||
/* DOS 磷光残影效果 */
|
||||
.wrapped-theme-dos * {
|
||||
transition: text-shadow 0.1s ease-out !important;
|
||||
}
|
||||
|
||||
/* DOS 扫描线(细化) */
|
||||
.wrapped-theme-dos .crt-scanlines {
|
||||
background: repeating-linear-gradient(
|
||||
to bottom,
|
||||
transparent 0px,
|
||||
transparent 1px,
|
||||
rgba(0, 0, 0, 0.15) 1px,
|
||||
rgba(0, 0, 0, 0.15) 2px
|
||||
) !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* DOS 按钮样式 */
|
||||
.wrapped-theme-dos button {
|
||||
background-color: transparent !important;
|
||||
border: 1px solid #33ff33 !important;
|
||||
color: #33ff33 !important;
|
||||
border-radius: 0 !important;
|
||||
text-shadow: 0 0 5px #33ff33;
|
||||
box-shadow: 0 0 5px rgba(51, 255, 51, 0.3);
|
||||
}
|
||||
|
||||
.wrapped-theme-dos button:hover {
|
||||
background-color: #33ff33 !important;
|
||||
color: #000000 !important;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Theme 3: Windows 95/98 - Win98
|
||||
============================================ */
|
||||
.wrapped-theme-win98 {
|
||||
/* System-like colors (approx.) */
|
||||
--win98-face: #c0c0c0; /* ButtonFace */
|
||||
--win98-hi: #ffffff; /* ButtonHighlight */
|
||||
--win98-shadow: #808080; /* ButtonShadow */
|
||||
--win98-dkshadow: #000000; /* Black */
|
||||
--win98-outline: #dedede; /* Extra light line (common in Win95 clones) */
|
||||
--win98-dither: repeating-conic-gradient(#bdbebd 0% 25%, #ffffff 0% 50%) 50% / 2px 2px;
|
||||
|
||||
--win98-title: #000080; /* ActiveCaption */
|
||||
--win98-title2: #1084d0; /* Caption gradient (approx.) */
|
||||
--win98-title-text: #ffffff;
|
||||
--win98-title-inactive: #7b7d7b;
|
||||
--win98-title-inactive-text: #e6e6e6;
|
||||
|
||||
/* Map to Wrapped theme variables */
|
||||
--wrapped-bg: #ffffff; /* fields/content */
|
||||
--wrapped-card-bg: var(--win98-face);
|
||||
--wrapped-text: #000000;
|
||||
--wrapped-text-secondary: #404040;
|
||||
--wrapped-accent: var(--win98-title);
|
||||
--wrapped-border: var(--win98-shadow);
|
||||
--wrapped-warning: #800000;
|
||||
|
||||
font-family: "MS Sans Serif", Tahoma, "Microsoft Sans Serif", "Segoe UI", sans-serif !important;
|
||||
}
|
||||
|
||||
/* Win98: hard edges */
|
||||
.wrapped-theme-win98 [class*="rounded"] {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Win98: baseline typography (override Tailwind inline colors via !important) */
|
||||
.wrapped-theme-win98 .wrapped-title {
|
||||
color: var(--wrapped-text) !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .wrapped-body,
|
||||
.wrapped-theme-win98 .wrapped-label {
|
||||
color: var(--wrapped-text-secondary) !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .wrapped-number {
|
||||
color: var(--wrapped-accent) !important;
|
||||
}
|
||||
|
||||
/* Win98: generic raised button */
|
||||
.wrapped-theme-win98 button {
|
||||
background: var(--win98-face) !important;
|
||||
color: #000000 !important;
|
||||
border-radius: 0 !important;
|
||||
text-shadow: none !important;
|
||||
|
||||
/* Win95-ish bevel: light (top/left) + shadow (bottom/right) + hard drop */
|
||||
border-top: 1px solid var(--win98-hi) !important;
|
||||
border-left: 1px solid var(--win98-hi) !important;
|
||||
border-right: 1px solid var(--win98-shadow) !important;
|
||||
border-bottom: 1px solid var(--win98-shadow) !important;
|
||||
box-shadow: 1px 1px 0 var(--win98-dkshadow) !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 button:active:not(:disabled) {
|
||||
background: var(--win98-face) !important;
|
||||
box-shadow: none !important;
|
||||
border-top: 1px solid var(--win98-dkshadow) !important;
|
||||
border-left: 1px solid var(--win98-dkshadow) !important;
|
||||
border-right: 1px solid var(--win98-hi) !important;
|
||||
border-bottom: 1px solid var(--win98-hi) !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 button:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Win98: dotted focus rectangle (classic) */
|
||||
.wrapped-theme-win98 button:focus-visible {
|
||||
outline: 1px dotted var(--win98-dkshadow);
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
/* Win98 helper: checkered/dither fill for "pressed/toggled" UI (taskbar active, start menu pressed, etc.) */
|
||||
.wrapped-theme-win98 .win98-dither {
|
||||
background: var(--win98-dither) !important;
|
||||
}
|
||||
|
||||
/* Win98: text-ish inputs / selects get a sunken look (avoid checkbox/radio etc.) */
|
||||
.wrapped-theme-win98 textarea,
|
||||
.wrapped-theme-win98 select,
|
||||
.wrapped-theme-win98 input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]) {
|
||||
background: var(--wrapped-bg) !important;
|
||||
color: var(--wrapped-text) !important;
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid var(--win98-shadow) !important;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 var(--win98-dkshadow),
|
||||
inset -1px -1px 0 var(--win98-hi);
|
||||
}
|
||||
|
||||
/* Win98: window primitives (98.css-like semantics) */
|
||||
.wrapped-theme-win98 .window {
|
||||
background: var(--win98-face);
|
||||
|
||||
/* Stronger Win95-ish window frame (inspired by /win95/assets/css/windows/window.css) */
|
||||
border-top: 2px solid var(--win98-hi);
|
||||
border-left: 2px solid var(--win98-hi);
|
||||
border-right: 1.5px solid var(--win98-shadow);
|
||||
border-bottom: 1.5px solid var(--win98-shadow);
|
||||
box-shadow: 1.5px 1.5px 0 var(--win98-dkshadow);
|
||||
outline: 1px solid var(--win98-outline);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
padding: 3px 4px;
|
||||
|
||||
/* Win95 is typically solid; Win98 sometimes gradients. Keep solid by default. */
|
||||
background: var(--win98-title);
|
||||
color: var(--win98-title-text);
|
||||
font-weight: 700;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex: none;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-text span {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-controls {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-controls button {
|
||||
width: 18px;
|
||||
height: 16px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-controls button::before {
|
||||
content: "";
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Win95-ish glyphs (avoid relying on fonts for the line/square). */
|
||||
.wrapped-theme-win98 .title-bar-controls button[aria-label="Minimize"]::before {
|
||||
width: 6px;
|
||||
height: 2px;
|
||||
background: #000000;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-controls button[aria-label="Maximize"]::before {
|
||||
width: 9px;
|
||||
height: 8px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #000000;
|
||||
border-top-width: 2px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .title-bar-controls button[aria-label="Close"]::before {
|
||||
content: "×";
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .window-body {
|
||||
padding: 8px;
|
||||
background: var(--win98-face);
|
||||
border-top: 1px solid var(--win98-shadow);
|
||||
box-shadow: inset 0 1px 0 var(--win98-hi);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<span v-if="remainingText">剩余 {{ remainingText }}</span>
|
||||
</div>
|
||||
<div class="mt-2 h-2 w-full rounded bg-gray-200 overflow-hidden">
|
||||
<div class="h-2 bg-emerald-500" :style="{ width: `${percent}%` }" />
|
||||
<div class="h-2 bg-wechat-green" :style="{ width: `${percent}%` }" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
<button
|
||||
v-if="readyToInstall"
|
||||
class="px-3 py-1.5 rounded-md bg-emerald-600 text-white text-sm hover:bg-emerald-700"
|
||||
class="px-3 py-1.5 rounded-md bg-wechat-green text-white text-sm hover:bg-wechat-green-hover"
|
||||
type="button"
|
||||
@click="emitInstall"
|
||||
>
|
||||
@@ -86,7 +86,7 @@
|
||||
忽略此版本
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1.5 rounded-md bg-emerald-600 text-white text-sm hover:bg-emerald-700"
|
||||
class="px-3 py-1.5 rounded-md bg-wechat-green text-white text-sm hover:bg-wechat-green-hover"
|
||||
type="button"
|
||||
@click="emitUpdate"
|
||||
>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
|
||||
<!-- 主内容:抽奖揭晓 + 右侧年度 Top10 总消息 bar race -->
|
||||
<div v-else class="w-full">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center" :class="isRetro ? 'lg:items-start' : ''">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-center">
|
||||
<!-- Left: 抽奖区 -->
|
||||
<div
|
||||
class="reply-buddy-rail flex flex-col items-center justify-center transition-transform duration-500 will-change-transform"
|
||||
@@ -104,8 +104,7 @@
|
||||
<div class="wrapped-label text-xs text-[#00000066]">最佳聊天搭子</div>
|
||||
|
||||
<div
|
||||
class="mt-4 w-28 h-28 sm:w-32 sm:h-32 rounded-2xl border border-[#EDEDED] overflow-hidden flex items-center justify-center"
|
||||
:class="isRetro ? 'bg-transparent' : 'bg-white/60'"
|
||||
class="mt-4 w-28 h-28 sm:w-32 sm:h-32 rounded-2xl border border-[#EDEDED] bg-white/60 overflow-hidden flex items-center justify-center"
|
||||
>
|
||||
<img
|
||||
v-if="shownAvatarUrl && shownAvatarOk"
|
||||
@@ -115,7 +114,7 @@
|
||||
@error="onShownAvatarError"
|
||||
/>
|
||||
<img
|
||||
v-else-if="isGameboy && phase === 'idle'"
|
||||
v-else-if="phase === 'idle'"
|
||||
src="/assets/images/LuckyBlock.png"
|
||||
class="w-full h-full object-contain"
|
||||
alt="Lucky Block"
|
||||
@@ -167,15 +166,14 @@
|
||||
|
||||
<!-- Right: bar race(揭晓后出现) -->
|
||||
<Transition name="chart-fade">
|
||||
<div v-if="showChart" class="w-full" :class="isRetro ? 'lg:self-start' : ''">
|
||||
<div v-if="showChart" class="w-full">
|
||||
<div
|
||||
class="rounded-2xl border border-[#EDEDED] bg-white/60"
|
||||
:class="isRetro ? 'p-3 sm:p-4' : 'p-4 sm:p-5'"
|
||||
class="rounded-2xl border border-[#EDEDED] bg-white/60 p-4 sm:p-5"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div class="wrapped-label text-xs text-[#00000066]">年度聊天排行(总消息数)</div>
|
||||
<div class="wrapped-body text-sm text-[#000000e6]" :class="isRetro ? 'mt-0.5' : 'mt-1'">
|
||||
<div class="wrapped-body text-sm text-[#000000e6] mt-1">
|
||||
<span class="wrapped-number text-[#07C160] font-semibold">{{ raceDate }}</span>
|
||||
<span class="text-[#00000055]"> · 0.1秒/天</span>
|
||||
</div>
|
||||
@@ -190,21 +188,19 @@
|
||||
<TransitionGroup
|
||||
name="race"
|
||||
tag="div"
|
||||
:class="isRetro ? 'space-y-1.5' : 'space-y-2'"
|
||||
class="space-y-2"
|
||||
>
|
||||
<div
|
||||
v-for="item in raceItems"
|
||||
:key="item.username"
|
||||
class="race-row flex items-center"
|
||||
:class="isRetro ? 'gap-3' : 'gap-3'"
|
||||
class="race-row flex items-center gap-3"
|
||||
>
|
||||
<div class="w-6 text-right wrapped-label text-[11px] text-[#00000055]">
|
||||
{{ item.rank }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-md overflow-hidden bg-[#0000000d] flex items-center justify-center flex-shrink-0"
|
||||
:class="isRetro ? 'w-6 h-6' : 'w-7 h-7'"
|
||||
class="w-7 h-7 rounded-md overflow-hidden bg-[#0000000d] flex items-center justify-center flex-shrink-0"
|
||||
>
|
||||
<img
|
||||
v-if="item.avatarUrl && avatarOk[item.username] !== false"
|
||||
@@ -221,7 +217,7 @@
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<div class="wrapped-body text-[#000000e6] truncate" :class="isRetro ? 'text-xs' : 'text-sm'" :title="item.displayName">
|
||||
<div class="wrapped-body text-[#000000e6] text-sm truncate" :title="item.displayName">
|
||||
{{ item.displayName }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,7 +225,7 @@
|
||||
{{ formatInt(item.value) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 rounded-full bg-[#00000008] overflow-hidden" :class="isRetro ? 'h-1.5' : 'h-2'">
|
||||
<div class="mt-1 h-2 rounded-full bg-[#00000008] overflow-hidden">
|
||||
<div
|
||||
class="race-bar h-full rounded-full bg-[#07C160]"
|
||||
:style="{ width: `${item.pct}%` }"
|
||||
@@ -249,17 +245,12 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue'
|
||||
import { useWrappedTheme } from '~/composables/useWrappedTheme'
|
||||
|
||||
const props = defineProps({
|
||||
card: { type: Object, required: true },
|
||||
variant: { type: String, default: 'panel' } // 'panel' | 'slide'
|
||||
})
|
||||
|
||||
const { theme } = useWrappedTheme()
|
||||
const isGameboy = computed(() => theme.value === 'gameboy')
|
||||
const isRetro = computed(() => isGameboy.value)
|
||||
|
||||
const nfInt = new Intl.NumberFormat('zh-CN', { maximumFractionDigits: 0 })
|
||||
const formatInt = (n) => nfInt.format(Math.round(Number(n) || 0))
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<template>
|
||||
<!-- CRT 滤镜叠加层 - 复古主题使用 -->
|
||||
<div class="absolute inset-0 pointer-events-none select-none z-30" aria-hidden="true">
|
||||
<!-- Game Boy: noise 作为最前景层统一覆盖整个画面 -->
|
||||
<WrappedGameboyDither
|
||||
v-if="theme === 'gameboy'"
|
||||
class="opacity-[0.3]"
|
||||
style="filter: contrast(1.16)"
|
||||
:pattern-refresh-interval="1"
|
||||
:pattern-alpha="56"
|
||||
mix-blend-mode="overlay"
|
||||
:pattern-size="256"
|
||||
/>
|
||||
|
||||
<!-- 扫描线 / RGB 子像素 / 闪烁 / 暗角 / 曲率 -->
|
||||
<div class="absolute inset-0 crt-scanlines"></div>
|
||||
<div class="absolute inset-0 crt-rgb-pixels"></div>
|
||||
<div class="absolute inset-0 crt-flicker"></div>
|
||||
<div class="absolute inset-0 crt-vignette"></div>
|
||||
<div class="absolute inset-0 crt-curvature"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { theme } = useWrappedTheme()
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="variant === 'panel'" class="window bg-white rounded-2xl border border-[#EDEDED] overflow-hidden">
|
||||
<div v-if="variant === 'panel'" class="bg-white rounded-2xl border border-[#EDEDED] overflow-hidden">
|
||||
<div class="px-6 py-5 border-b border-[#F3F3F3]">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
@@ -20,58 +20,24 @@
|
||||
|
||||
<!-- Slide 模式:单张卡片占据全页面,背景由外层(年度总结)统一控制 -->
|
||||
<section v-else class="relative h-full w-full overflow-hidden">
|
||||
<div :class="slideContainerClass">
|
||||
<!-- Win98:把整页内容包进一个“窗口” -->
|
||||
<div v-if="isWin98" class="window w-full flex-1 flex flex-col overflow-hidden">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">
|
||||
<img class="title-bar-icon" src="/assets/images/windows-0.png" alt="" aria-hidden="true" />
|
||||
<span>{{ title }}</span>
|
||||
</div>
|
||||
<div class="title-bar-controls" aria-hidden="true">
|
||||
<button type="button" aria-label="Minimize" tabindex="-1"></button>
|
||||
<button type="button" aria-label="Maximize" tabindex="-1"></button>
|
||||
<button type="button" aria-label="Close" tabindex="-1"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="window-body flex-1 flex flex-col min-h-0">
|
||||
<slot name="narrative">
|
||||
<p v-if="narrative" class="wrapped-body text-sm sm:text-base whitespace-pre-wrap">
|
||||
{{ narrative }}
|
||||
</p>
|
||||
</slot>
|
||||
|
||||
<div class="mt-4 flex-1 min-h-0 overflow-auto">
|
||||
<div class="w-full">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他主题:保持原样 -->
|
||||
<template v-else>
|
||||
<div class="relative h-full max-w-5xl mx-auto px-6 py-10 sm:px-8 sm:py-12 flex flex-col">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="wrapped-title text-[#000000e6]" :class="slideTitleClass">{{ title }}</h2>
|
||||
<div :class="slideNarrativeWrapClass">
|
||||
<slot name="narrative">
|
||||
<p v-if="narrative" class="mt-3 wrapped-body text-sm sm:text-base text-[#7F7F7F] max-w-2xl whitespace-pre-wrap">
|
||||
{{ narrative }}
|
||||
</p>
|
||||
</slot>
|
||||
</div>
|
||||
<h2 class="wrapped-title text-2xl sm:text-3xl text-[#000000e6]">{{ title }}</h2>
|
||||
<slot name="narrative">
|
||||
<p v-if="narrative" class="mt-3 wrapped-body text-sm sm:text-base text-[#7F7F7F] max-w-2xl whitespace-pre-wrap">
|
||||
{{ narrative }}
|
||||
</p>
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="badge" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex items-center" :class="slideContentWrapClass">
|
||||
<div class="flex-1 flex items-center mt-6 sm:mt-8">
|
||||
<div class="w-full">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -83,62 +49,4 @@ defineProps({
|
||||
narrative: { type: String, default: '' },
|
||||
variant: { type: String, default: 'panel' } // 'panel' | 'slide'
|
||||
})
|
||||
|
||||
const { theme } = useWrappedTheme()
|
||||
const isWin98 = computed(() => theme.value === 'win98')
|
||||
const isGameboy = computed(() => theme.value === 'gameboy')
|
||||
const isCompactSlide = computed(() => isGameboy.value)
|
||||
|
||||
const slideTitleClass = computed(() => (
|
||||
isCompactSlide.value ? 'text-xl sm:text-2xl' : 'text-2xl sm:text-3xl'
|
||||
))
|
||||
|
||||
// Keep as a computed so we can tune per-theme spacing later without touching template.
|
||||
const slideNarrativeWrapClass = computed(() => '')
|
||||
|
||||
const slideContentWrapClass = computed(() => (
|
||||
isCompactSlide.value ? 'mt-4 sm:mt-5' : 'mt-6 sm:mt-8'
|
||||
))
|
||||
|
||||
const slideContainerClass = computed(() => (
|
||||
isWin98.value
|
||||
? 'relative h-full max-w-5xl mx-auto px-6 pt-2 pb-4 sm:px-8 sm:pt-3 sm:pb-6 flex flex-col'
|
||||
: (isCompactSlide.value
|
||||
? 'relative h-full max-w-5xl mx-auto px-6 pt-5 pb-6 sm:px-8 sm:pt-6 sm:pb-7 flex flex-col'
|
||||
: 'relative h-full max-w-5xl mx-auto px-6 py-10 sm:px-8 sm:py-12 flex flex-col')
|
||||
))
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ========== Game Boy 主题 ========== */
|
||||
|
||||
/* 卡片背景 */
|
||||
.wrapped-theme-gameboy .bg-white {
|
||||
background: #9bbc0f !important;
|
||||
border-color: #306230 !important;
|
||||
}
|
||||
|
||||
/* 标题 */
|
||||
.wrapped-theme-gameboy .wrapped-title {
|
||||
color: #0f380f !important;
|
||||
font-family: var(--font-pixel-10), 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 描述文字 */
|
||||
.wrapped-theme-gameboy .wrapped-body {
|
||||
color: #306230 !important;
|
||||
}
|
||||
|
||||
/* 数字高亮 */
|
||||
.wrapped-theme-gameboy .wrapped-number {
|
||||
color: #0f380f !important;
|
||||
font-family: var(--font-pixel-10), 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 边框 */
|
||||
.wrapped-theme-gameboy .border-\[\#EDEDED\],
|
||||
.wrapped-theme-gameboy .border-\[\#F3F3F3\] {
|
||||
border-color: #306230 !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 items-end">
|
||||
<WrappedThemeSwitcher />
|
||||
<button
|
||||
class="inline-flex items-center justify-center px-4 py-2 rounded-lg bg-[#07C160] text-white text-sm wrapped-label hover:bg-[#06AD56] disabled:opacity-60 disabled:cursor-not-allowed transition controls-btn"
|
||||
:disabled="loading"
|
||||
@@ -83,73 +82,3 @@ const yearOptions = computed(() => {
|
||||
return years
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 复古模式 - 控制面板样式 */
|
||||
.wrapped-retro .controls-panel {
|
||||
background-color: var(--wrapped-card-bg);
|
||||
border-color: var(--wrapped-border);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-label {
|
||||
color: var(--wrapped-text-secondary);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-select {
|
||||
background-color: var(--wrapped-bg);
|
||||
border-color: var(--wrapped-border);
|
||||
color: var(--wrapped-text);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-select:focus {
|
||||
--tw-ring-color: var(--wrapped-accent);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-checkbox {
|
||||
border-color: var(--wrapped-border);
|
||||
color: var(--wrapped-accent);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-checkbox:focus {
|
||||
--tw-ring-color: var(--wrapped-accent);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-hint {
|
||||
color: var(--wrapped-text-secondary);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-warning {
|
||||
color: var(--wrapped-warning);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-btn {
|
||||
background-color: var(--wrapped-accent);
|
||||
color: var(--wrapped-bg);
|
||||
}
|
||||
|
||||
.wrapped-retro .controls-btn:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* Win98 特殊样式 */
|
||||
.wrapped-theme-win98 .controls-panel {
|
||||
border-radius: 0;
|
||||
border: 1px solid #808080;
|
||||
background: #c0c0c0;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .controls-select {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .controls-btn {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .controls-warning {
|
||||
color: #800000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- Shared backdrop for modern/gameboy "Wrapped" slides (keeps cover + cards visually consistent). -->
|
||||
<div v-if="theme !== 'win98'" class="absolute inset-0 pointer-events-none select-none z-0" aria-hidden="true">
|
||||
<!-- Shared backdrop for modern "Wrapped" slides (keeps cover + cards visually consistent). -->
|
||||
<div class="absolute inset-0 pointer-events-none select-none z-0" aria-hidden="true">
|
||||
<!-- Soft color blobs (brand + warm highlights) -->
|
||||
<div class="absolute -top-24 -left-24 w-80 h-80 bg-[#07C160] opacity-[0.08] rounded-full blur-3xl"></div>
|
||||
<div class="absolute -top-24 -right-24 w-96 h-96 bg-[#F2AA00] opacity-[0.06] rounded-full blur-3xl"></div>
|
||||
@@ -11,77 +11,11 @@
|
||||
class="absolute inset-0 bg-[linear-gradient(rgba(7,193,96,0.05)_1px,transparent_1px),linear-gradient(90deg,rgba(7,193,96,0.05)_1px,transparent_1px)] bg-[size:52px_52px] opacity-[0.28]"
|
||||
></div>
|
||||
|
||||
<!-- Grain/noise: gameboy 使用动态 canvas 噪点,其它主题沿用现有纹理 -->
|
||||
<WrappedGameboyDither
|
||||
v-if="theme === 'gameboy'"
|
||||
class="opacity-[0.3]"
|
||||
style="filter: contrast(1.16)"
|
||||
:pattern-refresh-interval="1"
|
||||
:pattern-alpha="56"
|
||||
mix-blend-mode="overlay"
|
||||
:pattern-size="256"
|
||||
/>
|
||||
<div v-else class="absolute inset-0 wrapped-noise-enhanced opacity-[0.08]"></div>
|
||||
<!-- Grain/noise -->
|
||||
<div class="absolute inset-0 wrapped-noise-enhanced opacity-[0.08]"></div>
|
||||
|
||||
<!-- Gentle vignette so typography stays readable on textured bg -->
|
||||
<div class="absolute inset-x-0 top-0 h-40 bg-gradient-to-b from-white/50 to-transparent"></div>
|
||||
<div class="absolute inset-x-0 bottom-0 h-44 bg-gradient-to-t from-white/40 to-transparent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Win98: classic desktop icons (purely decorative) -->
|
||||
<div v-else class="absolute inset-0 pointer-events-none select-none z-0" aria-hidden="true">
|
||||
<div class="win98-desktop-icons">
|
||||
<div v-for="it in desktopIcons" :key="it.label" class="win98-desktop-icon">
|
||||
<img class="win98-desktop-icon__img" :src="it.src" :alt="it.label" />
|
||||
<div class="win98-desktop-icon__label">{{ it.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { theme } = useWrappedTheme()
|
||||
|
||||
const desktopIcons = [
|
||||
{ label: '我的文档', src: '/assets/images/win98-icons/folder.png' },
|
||||
{ label: '图片', src: '/assets/images/win98-icons/photos.png' },
|
||||
{ label: '收件箱', src: '/assets/images/win98-icons/mail.png' },
|
||||
{ label: '回收站', src: '/assets/images/win98-icons/recycle.png' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.win98-desktop-icons {
|
||||
position: absolute;
|
||||
top: 84px; /* leave space for top-left controls */
|
||||
left: 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.win98-desktop-icon {
|
||||
width: 74px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.win98-desktop-icon__img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.win98-desktop-icon__label {
|
||||
max-width: 74px;
|
||||
padding: 0 2px;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px 0 #000000;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
<template>
|
||||
<canvas
|
||||
ref="grainRef"
|
||||
class="pointer-events-none absolute inset-0 h-full w-full"
|
||||
:style="canvasStyle"
|
||||
aria-hidden="true"
|
||||
></canvas>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
patternRefreshInterval: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
patternAlpha: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
mixBlendMode: {
|
||||
type: String,
|
||||
default: 'multiply'
|
||||
},
|
||||
patternSize: {
|
||||
type: Number,
|
||||
default: 512
|
||||
}
|
||||
})
|
||||
|
||||
const grainRef = ref(null)
|
||||
|
||||
const canvasStyle = computed(() => `image-rendering: pixelated; mix-blend-mode: ${props.mixBlendMode};`)
|
||||
|
||||
let animationId = 0
|
||||
let frame = 0
|
||||
let noiseData
|
||||
let noise32
|
||||
|
||||
const clamp = (value, min, max) => Math.min(max, Math.max(min, value))
|
||||
|
||||
const resize = () => {
|
||||
const canvas = grainRef.value
|
||||
if (!canvas) return
|
||||
const size = Math.max(64, Math.round(props.patternSize))
|
||||
canvas.width = size
|
||||
canvas.height = size
|
||||
}
|
||||
|
||||
const initImageData = (ctx) => {
|
||||
const canvas = grainRef.value
|
||||
if (!canvas) return
|
||||
noiseData = ctx.createImageData(canvas.width, canvas.height)
|
||||
noise32 = new Uint32Array(noiseData.data.buffer)
|
||||
}
|
||||
|
||||
const drawGrain = () => {
|
||||
if (!noise32) return
|
||||
const alpha = clamp(Math.round(props.patternAlpha), 0, 255) << 24
|
||||
for (let i = 0; i < noise32.length; i++) {
|
||||
const value = (Math.random() * 255) | 0
|
||||
noise32[i] = alpha | (value << 16) | (value << 8) | value
|
||||
}
|
||||
}
|
||||
|
||||
const loop = (ctx) => {
|
||||
const refreshEvery = Math.max(1, Math.round(props.patternRefreshInterval))
|
||||
if (frame % refreshEvery === 0) {
|
||||
drawGrain()
|
||||
ctx.putImageData(noiseData, 0, 0)
|
||||
}
|
||||
|
||||
frame++
|
||||
animationId = window.requestAnimationFrame(() => loop(ctx))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const canvas = grainRef.value
|
||||
if (!canvas) return
|
||||
|
||||
const ctx = canvas.getContext('2d', { alpha: true })
|
||||
if (!ctx) return
|
||||
|
||||
resize()
|
||||
initImageData(ctx)
|
||||
drawGrain()
|
||||
ctx.putImageData(noiseData, 0, 0)
|
||||
loop(ctx)
|
||||
|
||||
window.addEventListener('resize', resize)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
window.cancelAnimationFrame(animationId)
|
||||
})
|
||||
</script>
|
||||
@@ -9,57 +9,7 @@
|
||||
|
||||
<div :class="innerClass">
|
||||
<template v-if="variant === 'slide'">
|
||||
<!-- Win98:封面也做成一个“窗口” -->
|
||||
<div v-if="isWin98" class="window h-full w-full flex flex-col overflow-hidden">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">
|
||||
<img class="title-bar-icon" src="/assets/images/windows-0.png" alt="" aria-hidden="true" />
|
||||
<span>WECHAT WRAPPED</span>
|
||||
</div>
|
||||
<div class="title-bar-controls" aria-hidden="true">
|
||||
<button type="button" aria-label="Minimize" tabindex="-1"></button>
|
||||
<button type="button" aria-label="Maximize" tabindex="-1"></button>
|
||||
<button type="button" aria-label="Close" tabindex="-1"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="window-body flex-1 overflow-hidden">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="wrapped-label text-xs text-[#00000080]">
|
||||
WECHAT WRAPPED
|
||||
</div>
|
||||
<div class="wrapped-body text-xs text-[#00000055]">
|
||||
年度回望
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 sm:mt-14">
|
||||
<h1 class="wrapped-title text-3xl sm:text-5xl text-[#000000e6] leading-[1.05]">
|
||||
{{ randomTitle.main }}
|
||||
<span class="block mt-3 win98-hero-highlight">
|
||||
{{ randomTitle.highlight }}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div class="mt-7 sm:mt-9 max-w-2xl">
|
||||
<p class="wrapped-body text-base sm:text-lg text-[#00000080]">
|
||||
{{ randomSubtitle }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pb-1">
|
||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-[#00000066]">
|
||||
<!-- Intentionally left blank (avoid "feature bullet list" tone on the cover). -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他主题:保持原样 -->
|
||||
<div v-else class="h-full flex flex-col justify-between">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="wrapped-label text-xs text-[#00000080]">
|
||||
WECHAT WRAPPED
|
||||
@@ -92,79 +42,41 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="previewQuestions.length > 0 && (isGameboy || isModern)"
|
||||
v-if="previewQuestions.length > 0"
|
||||
class="pointer-events-none absolute bottom-0 right-0 hidden xl:flex items-end"
|
||||
>
|
||||
<div class="pointer-events-auto relative" :class="previewStageClass">
|
||||
<div class="relative" :class="previewViewportClass">
|
||||
<template v-if="isGameboy">
|
||||
<BitsCardSwap
|
||||
:width="previewCardWidth"
|
||||
:height="previewCardHeight"
|
||||
:delay="previewSwapDelay"
|
||||
:card-count="previewQuestions.length"
|
||||
:card-distance="previewCardDistance"
|
||||
:vertical-distance="previewVerticalDistance"
|
||||
:skew-amount="4"
|
||||
easing="elastic"
|
||||
:pause-on-hover="true"
|
||||
<BitsGridMotion
|
||||
:items="modernPreviewItems"
|
||||
gradient-color="rgba(7, 193, 96, 0.24)"
|
||||
:row-count="7"
|
||||
:column-count="8"
|
||||
:scroll-speed="42"
|
||||
:base-offset-x="46"
|
||||
>
|
||||
<template
|
||||
v-for="(previewItem, previewIndex) in previewQuestions"
|
||||
:key="`preview-${previewItem.order}-${previewIndex}`"
|
||||
v-slot:[`card-${previewIndex}`]
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<WrappedCardShell
|
||||
:card-id="previewItem.order"
|
||||
:title="previewItem.title"
|
||||
:card-id="Number(item?.order || 0)"
|
||||
:title="String(item?.title || '年度卡片')"
|
||||
variant="panel"
|
||||
class="h-full w-full"
|
||||
class="h-full w-full preview-grid-shell"
|
||||
>
|
||||
<div
|
||||
class="flex h-[168px] items-center justify-center rounded-xl border border-dashed px-5"
|
||||
:class="previewQuestionPanelClass"
|
||||
>
|
||||
<p class="wrapped-body text-lg leading-relaxed text-center" :class="previewQuestionClass">
|
||||
{{ previewItem.question }}
|
||||
<div class="preview-grid-body">
|
||||
<div class="preview-grid-summary">
|
||||
{{ String(item?.summary || '年度线索') }}
|
||||
</div>
|
||||
<p class="preview-grid-question">
|
||||
{{ String(item?.question || '这一页会揭晓你的聊天答案。') }}
|
||||
</p>
|
||||
<div class="preview-grid-lines" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</WrappedCardShell>
|
||||
</template>
|
||||
</BitsCardSwap>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<BitsGridMotion
|
||||
:items="modernPreviewItems"
|
||||
gradient-color="rgba(7, 193, 96, 0.24)"
|
||||
:row-count="7"
|
||||
:column-count="8"
|
||||
:scroll-speed="42"
|
||||
:base-offset-x="46"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<WrappedCardShell
|
||||
:card-id="Number(item?.order || 0)"
|
||||
:title="String(item?.title || '年度卡片')"
|
||||
variant="panel"
|
||||
class="h-full w-full preview-grid-shell"
|
||||
>
|
||||
<div class="preview-grid-body">
|
||||
<div class="preview-grid-summary">
|
||||
{{ String(item?.summary || '年度线索') }}
|
||||
</div>
|
||||
<p class="preview-grid-question">
|
||||
{{ String(item?.question || '这一页会揭晓你的聊天答案。') }}
|
||||
</p>
|
||||
<div class="preview-grid-lines" aria-hidden="true">
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</WrappedCardShell>
|
||||
</template>
|
||||
</BitsGridMotion>
|
||||
</template>
|
||||
</BitsGridMotion>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -362,11 +274,6 @@ const props = defineProps({
|
||||
cardManifests: { type: Array, default: () => [] }
|
||||
})
|
||||
|
||||
const { theme } = useWrappedTheme()
|
||||
const isWin98 = computed(() => theme.value === 'win98')
|
||||
const isGameboy = computed(() => theme.value === 'gameboy')
|
||||
const isModern = computed(() => theme.value === 'off')
|
||||
|
||||
const previewQuestions = computed(() => {
|
||||
const manifests = Array.isArray(props.cardManifests) ? props.cardManifests : []
|
||||
if (!manifests.length) {
|
||||
@@ -392,10 +299,6 @@ const previewQuestions = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
const previewSwapDelay = 4200
|
||||
const previewCardWidth = 420
|
||||
const previewCardHeight = 280
|
||||
|
||||
const modernPreviewItems = computed(() => {
|
||||
if (!previewQuestions.value.length) return []
|
||||
return previewQuestions.value.map((item) => ({
|
||||
@@ -407,15 +310,11 @@ const modernPreviewItems = computed(() => {
|
||||
})
|
||||
|
||||
const previewStageClass = computed(() => (
|
||||
isGameboy.value
|
||||
? 'w-[500px] h-[360px] translate-x-24 -translate-y-8'
|
||||
: 'w-[620px] h-[420px] translate-x-32 -translate-y-10'
|
||||
'w-[620px] h-[420px] translate-x-32 -translate-y-10'
|
||||
))
|
||||
|
||||
const previewViewportClass = computed(() => (
|
||||
isGameboy.value
|
||||
? 'h-[340px] w-[460px]'
|
||||
: 'h-[390px] w-[580px]'
|
||||
'h-[390px] w-[580px]'
|
||||
))
|
||||
|
||||
const previewCardDistance = computed(() => {
|
||||
@@ -428,16 +327,6 @@ const previewVerticalDistance = computed(() => {
|
||||
return total >= 9 ? 10 : total >= 7 ? 11 : total >= 5 ? 14 : 18
|
||||
})
|
||||
|
||||
const previewQuestionClass = computed(() => {
|
||||
if (isWin98.value) return 'text-[#111111]'
|
||||
return 'text-[#1F2937]'
|
||||
})
|
||||
|
||||
const previewQuestionPanelClass = computed(() => {
|
||||
if (isWin98.value) return 'border-[#B7B7B7] bg-[#FFFFFF]'
|
||||
return 'border-[#07C160]/30 bg-[#F7FFFB]'
|
||||
})
|
||||
|
||||
const yearText = computed(() => `${props.year}年`)
|
||||
|
||||
const rootClass = computed(() => {
|
||||
@@ -449,20 +338,11 @@ const rootClass = computed(() => {
|
||||
|
||||
const innerClass = computed(() => {
|
||||
if (props.variant !== 'slide') return 'relative px-6 py-7 sm:px-8 sm:py-9'
|
||||
if (isWin98.value) return 'relative h-full max-w-5xl mx-auto px-6 pt-2 pb-4 sm:px-8 sm:pt-3 sm:pb-6'
|
||||
return 'relative h-full max-w-5xl mx-auto px-6 py-10 sm:px-8 sm:py-12'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Win98:封面标题的高亮句做成“选中/标题栏”感觉 */
|
||||
.win98-hero-highlight {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
background: #000080;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.preview-grid-shell {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 24px rgba(7, 193, 96, 0.14);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<template>
|
||||
<component :is="themeSwitcherComponent" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { theme } = useWrappedTheme()
|
||||
|
||||
// 根据当前主题动态选择对应的切换器组件
|
||||
const themeSwitcherComponent = computed(() => {
|
||||
const map = {
|
||||
off: resolveComponent('WrappedThemeSwitcherModern'),
|
||||
gameboy: resolveComponent('WrappedThemeSwitcherGameboy'),
|
||||
win98: resolveComponent('WrappedThemeSwitcherWin98')
|
||||
}
|
||||
return map[theme.value] || map.off
|
||||
})
|
||||
</script>
|
||||
@@ -1,97 +0,0 @@
|
||||
<template>
|
||||
<div class="gameboy-menu select-none">
|
||||
<!-- 像素风格菜单框 -->
|
||||
<div class="gameboy-menu-box">
|
||||
<div class="gameboy-menu-title">SELECT THEME</div>
|
||||
<div class="gameboy-menu-items">
|
||||
<button
|
||||
v-for="t in themes"
|
||||
:key="t.value"
|
||||
class="gameboy-menu-item"
|
||||
:class="{ 'is-active': theme === t.value }"
|
||||
@click="selectTheme(t.value)"
|
||||
>
|
||||
<span class="gameboy-cursor">{{ theme === t.value ? '▶' : ' ' }}</span>
|
||||
<span class="gameboy-label">{{ t.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { theme, setTheme } = useWrappedTheme()
|
||||
|
||||
const themes = [
|
||||
{ value: 'off', label: 'MODERN' },
|
||||
{ value: 'gameboy', label: 'GAME BOY' },
|
||||
{ value: 'win98', label: 'WIN98' }
|
||||
]
|
||||
|
||||
const selectTheme = (value) => {
|
||||
setTheme(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gameboy-menu {
|
||||
font-family: 'Press Start 2P', 'Courier New', monospace;
|
||||
font-size: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.gameboy-menu-box {
|
||||
background: #0f380f;
|
||||
border: 3px solid #306230;
|
||||
padding: 8px;
|
||||
box-shadow:
|
||||
inset 2px 2px 0 #9bbc0f,
|
||||
inset -2px -2px 0 #0f380f;
|
||||
}
|
||||
|
||||
.gameboy-menu-title {
|
||||
color: #9bbc0f;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 2px dashed #306230;
|
||||
}
|
||||
|
||||
.gameboy-menu-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.gameboy-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 6px;
|
||||
color: #9bbc0f;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: background-color 0.1s;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.gameboy-menu-item:hover {
|
||||
background: #306230;
|
||||
}
|
||||
|
||||
.gameboy-menu-item.is-active {
|
||||
color: #8bac0f;
|
||||
}
|
||||
|
||||
.gameboy-cursor {
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.gameboy-label {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-[#00000099]">Theme</span>
|
||||
<div class="inline-flex rounded-lg border border-[#EDEDED] overflow-hidden">
|
||||
<button
|
||||
v-for="t in themes"
|
||||
:key="t.value"
|
||||
class="px-3 py-1.5 text-xs wrapped-label transition-colors"
|
||||
:class="[
|
||||
theme === t.value
|
||||
? 'bg-[#07C160] text-white'
|
||||
: 'bg-white text-[#333] hover:bg-[#F5F5F5]'
|
||||
]"
|
||||
@click="setTheme(t.value)"
|
||||
>
|
||||
{{ t.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { theme, setTheme } = useWrappedTheme()
|
||||
|
||||
const themes = [
|
||||
{ value: 'off', label: 'Modern' },
|
||||
{ value: 'gameboy', label: 'Game Boy' },
|
||||
{ value: 'win98', label: 'Win98' }
|
||||
]
|
||||
</script>
|
||||
@@ -1,73 +0,0 @@
|
||||
<template>
|
||||
<div class="win98-switcher select-none">
|
||||
<span class="win98-switcher__label">Theme</span>
|
||||
|
||||
<div class="win98-switcher__group" role="group" aria-label="Theme switcher">
|
||||
<button
|
||||
v-for="t in themes"
|
||||
:key="t.value"
|
||||
type="button"
|
||||
class="win98-switcher__btn"
|
||||
:class="{ 'is-active': theme === t.value }"
|
||||
@click="setTheme(t.value)"
|
||||
>
|
||||
{{ t.label }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { theme, setTheme } = useWrappedTheme()
|
||||
|
||||
const themes = [
|
||||
{ value: 'off', label: 'Modern' },
|
||||
{ value: 'gameboy', label: 'Game Boy' },
|
||||
{ value: 'win98', label: 'Win98' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.win98-switcher {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.win98-switcher__label {
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
/* Bevel group container */
|
||||
.win98-switcher__group {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
padding: 2px;
|
||||
background: #c0c0c0;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
}
|
||||
|
||||
.win98-switcher__btn {
|
||||
padding: 4px 10px;
|
||||
background: #c0c0c0;
|
||||
color: #000000;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.win98-switcher__btn:hover {
|
||||
filter: brightness(1.03);
|
||||
}
|
||||
|
||||
.win98-switcher__btn.is-active {
|
||||
background: #000080 !important;
|
||||
color: #ffffff !important;
|
||||
border-color: #000080 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -1,169 +0,0 @@
|
||||
<template>
|
||||
<div class="win98-taskbar" @wheel.stop.prevent>
|
||||
<button
|
||||
type="button"
|
||||
class="win98-start"
|
||||
aria-label="Start"
|
||||
:aria-pressed="startPressed ? 'true' : 'false'"
|
||||
@mousedown="startPressed = true"
|
||||
@mouseup="startPressed = false"
|
||||
@mouseleave="startPressed = false"
|
||||
>
|
||||
<img class="win98-start__icon" src="/assets/images/windows-0.png" alt="" aria-hidden="true" />
|
||||
<span class="win98-start__text">Start</span>
|
||||
</button>
|
||||
|
||||
<div class="win98-taskbar__divider" aria-hidden="true"></div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="win98-task"
|
||||
:title="title"
|
||||
tabindex="-1"
|
||||
aria-label="Active window"
|
||||
>
|
||||
{{ title }}
|
||||
</button>
|
||||
|
||||
<div class="win98-taskbar__spacer" aria-hidden="true"></div>
|
||||
|
||||
<div class="win98-tray" aria-label="System tray">
|
||||
<div class="win98-tray__clock" :title="timeText">
|
||||
{{ timeText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
title: { type: String, default: 'WeChat Wrapped' }
|
||||
})
|
||||
|
||||
const startPressed = ref(false)
|
||||
const timeText = ref('--:--')
|
||||
let timer = null
|
||||
|
||||
const formatWin98Time = (d) => {
|
||||
try {
|
||||
// Win98 screenshot style: 12-hour + AM/PM
|
||||
return new Intl.DateTimeFormat('en-US', { hour: 'numeric', minute: '2-digit' }).format(d)
|
||||
} catch {
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
return `${hh}:${mm}`
|
||||
}
|
||||
}
|
||||
|
||||
const updateClock = () => { timeText.value = formatWin98Time(new Date()) }
|
||||
|
||||
onMounted(() => {
|
||||
updateClock()
|
||||
timer = setInterval(updateClock, 30_000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
timer = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.win98-taskbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px;
|
||||
background: #c0c0c0;
|
||||
border-top: 2px solid #ffffff;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.win98-start {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 30px;
|
||||
padding: 0 10px 0 8px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.win98-start__icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.win98-start__text {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.win98-taskbar__divider {
|
||||
width: 2px;
|
||||
height: 28px;
|
||||
background: #808080;
|
||||
box-shadow: 1px 0 0 #ffffff;
|
||||
}
|
||||
|
||||
.win98-task {
|
||||
height: 30px;
|
||||
min-width: 160px;
|
||||
max-width: 56vw;
|
||||
padding: 0 10px;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.win98-task {
|
||||
/* Active window task button: depressed + dither fill (Win95-ish) */
|
||||
background: var(--win98-dither) !important;
|
||||
box-shadow: none !important;
|
||||
border-top: 1px solid var(--win98-dkshadow) !important;
|
||||
border-left: 1px solid var(--win98-dkshadow) !important;
|
||||
border-right: 1px solid var(--win98-hi) !important;
|
||||
border-bottom: 1px solid var(--win98-hi) !important;
|
||||
}
|
||||
|
||||
.win98-start[aria-pressed="true"] {
|
||||
/* Start button pressed: depressed + dither */
|
||||
background: var(--win98-dither) !important;
|
||||
box-shadow: none !important;
|
||||
border-top: 1px solid var(--win98-dkshadow) !important;
|
||||
border-left: 1px solid var(--win98-dkshadow) !important;
|
||||
border-right: 1px solid var(--win98-hi) !important;
|
||||
border-bottom: 1px solid var(--win98-hi) !important;
|
||||
}
|
||||
|
||||
.win98-taskbar__spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.win98-tray {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 0 8px;
|
||||
background: #c0c0c0;
|
||||
border-top: 1px solid var(--win98-shadow);
|
||||
border-left: 1px solid var(--win98-shadow);
|
||||
border-right: 1px solid var(--win98-hi);
|
||||
border-bottom: 1px solid var(--win98-hi);
|
||||
}
|
||||
|
||||
.win98-tray__clock {
|
||||
font-size: 11px;
|
||||
color: #000000;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
@@ -1,45 +1,6 @@
|
||||
<template>
|
||||
<div class="year-selector" :class="selectorClass">
|
||||
<!-- Game Boy 风格 -->
|
||||
<div v-if="theme === 'gameboy'" class="year-gameboy">
|
||||
<div class="gameboy-year-box">
|
||||
<button
|
||||
class="gameboy-arrow"
|
||||
:disabled="!canGoPrev"
|
||||
@click="prevYear"
|
||||
aria-label="Previous year"
|
||||
>◀</button>
|
||||
<span class="gameboy-year-value">{{ modelValue }}</span>
|
||||
<button
|
||||
class="gameboy-arrow"
|
||||
:disabled="!canGoNext"
|
||||
@click="nextYear"
|
||||
aria-label="Next year"
|
||||
>▶</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Win98 风格 -->
|
||||
<div v-else-if="theme === 'win98'" class="year-win98">
|
||||
<div class="win98-year-box">
|
||||
<button
|
||||
class="win98-arrow"
|
||||
:disabled="!canGoPrev"
|
||||
@click="prevYear"
|
||||
aria-label="Previous year"
|
||||
>◄</button>
|
||||
<span class="win98-year-value">{{ modelValue }}年</span>
|
||||
<button
|
||||
class="win98-arrow"
|
||||
:disabled="!canGoNext"
|
||||
@click="nextYear"
|
||||
aria-label="Next year"
|
||||
>►</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modern 风格:下拉菜单(默认) -->
|
||||
<div v-else class="year-modern">
|
||||
<div class="year-selector">
|
||||
<div class="year-modern">
|
||||
<div class="relative inline-flex items-center">
|
||||
<select
|
||||
class="appearance-none bg-transparent pr-5 pl-0 py-0.5 rounded-md wrapped-label text-xs text-[#00000066] text-right focus:outline-none focus-visible:ring-2 focus-visible:ring-[#07C160]/30 hover:bg-[#000000]/5 transition disabled:opacity-70 disabled:cursor-default"
|
||||
@@ -81,8 +42,6 @@ const props = defineProps({
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const { theme } = useWrappedTheme()
|
||||
|
||||
const currentIndex = computed(() => props.years.indexOf(props.modelValue))
|
||||
const canGoPrev = computed(() => currentIndex.value > 0)
|
||||
const canGoNext = computed(() => currentIndex.value < props.years.length - 1)
|
||||
@@ -106,10 +65,6 @@ const onSelectChange = (e) => {
|
||||
}
|
||||
}
|
||||
|
||||
const selectorClass = computed(() => {
|
||||
return `year-selector-${theme.value}`
|
||||
})
|
||||
|
||||
// 全局左右键切换年份(所有主题)
|
||||
const handleKeydown = (e) => {
|
||||
if (props.years.length <= 1) return
|
||||
@@ -144,102 +99,4 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ========== Game Boy 风格 ========== */
|
||||
.year-gameboy {
|
||||
font-family: 'Press Start 2P', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.gameboy-year-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: #0f380f;
|
||||
border: 3px solid #306230;
|
||||
padding: 6px 8px;
|
||||
box-shadow:
|
||||
inset 2px 2px 0 #9bbc0f,
|
||||
inset -2px -2px 0 #0f380f;
|
||||
}
|
||||
|
||||
.gameboy-arrow {
|
||||
background: #306230;
|
||||
border: none;
|
||||
color: #9bbc0f;
|
||||
font-size: 8px;
|
||||
padding: 4px 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.gameboy-arrow:hover:not(:disabled) {
|
||||
background: #8bac0f;
|
||||
color: #0f380f;
|
||||
}
|
||||
|
||||
.gameboy-arrow:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.gameboy-year-value {
|
||||
color: #9bbc0f;
|
||||
font-size: 10px;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* ========== Win98 风格 ========== */
|
||||
.year-win98 {
|
||||
font-family: "MS Sans Serif", Tahoma, "Microsoft Sans Serif", "Segoe UI", sans-serif;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.win98-year-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: #c0c0c0;
|
||||
padding: 2px;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
}
|
||||
|
||||
.win98-year-value {
|
||||
min-width: 62px;
|
||||
text-align: center;
|
||||
color: #000000;
|
||||
padding: 2px 8px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #000000,
|
||||
inset -1px -1px 0 #ffffff;
|
||||
}
|
||||
|
||||
.win98-arrow {
|
||||
width: 24px;
|
||||
height: 22px;
|
||||
background: #c0c0c0;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.win98-arrow:active:not(:disabled) {
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #000000,
|
||||
inset -1px -1px 0 #ffffff;
|
||||
}
|
||||
|
||||
.win98-arrow:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -309,126 +309,4 @@ watch(
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* ========== Game Boy 主题 ========== */
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay {
|
||||
--wr-chat-frame-bg: #9bbc0f;
|
||||
--wr-chat-top-bg: #8bac0f;
|
||||
--wr-chat-chat-bg: #9bbc0f;
|
||||
--wr-chat-border: #306230;
|
||||
|
||||
--wr-chat-bubble-bg: #8bac0f;
|
||||
--wr-chat-bubble-tail: #8bac0f;
|
||||
--wr-chat-bubble-text: #0f380f;
|
||||
|
||||
--wr-chat-typing-bg: #c5d870;
|
||||
--wr-chat-typing-dot: #0f380f;
|
||||
|
||||
border-width: 4px;
|
||||
border-radius: 0;
|
||||
box-shadow:
|
||||
inset -2px -2px 0 0 #306230,
|
||||
inset 2px 2px 0 0 #c5d870;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay__top {
|
||||
border-bottom-width: 3px;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay__typing,
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay__bubble {
|
||||
border-radius: 0;
|
||||
border: 3px solid #306230;
|
||||
box-shadow:
|
||||
inset -2px -2px 0 0 #306230,
|
||||
inset 2px 2px 0 0 #9bbc0f;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay__top .wrapped-label,
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay__top .wrapped-body {
|
||||
color: #0f380f !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-chat-replay__bubble-text {
|
||||
font-family: var(--font-pixel-10), 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* ========== DOS 主题 ========== */
|
||||
.wrapped-theme-dos .wrapped-chat-replay {
|
||||
--wr-chat-frame-bg: #0a0a0a;
|
||||
--wr-chat-top-bg: #0a0a0a;
|
||||
--wr-chat-chat-bg: #0a0a0a;
|
||||
--wr-chat-border: #33ff33;
|
||||
|
||||
--wr-chat-bubble-bg: #0a0a0a;
|
||||
--wr-chat-bubble-tail: #0a0a0a;
|
||||
--wr-chat-bubble-text: #33ff33;
|
||||
|
||||
--wr-chat-typing-bg: #0a0a0a;
|
||||
--wr-chat-typing-dot: #33ff33;
|
||||
|
||||
box-shadow: 0 0 10px rgba(51, 255, 51, 0.2);
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-chat-replay__typing,
|
||||
.wrapped-theme-dos .wrapped-chat-replay__bubble {
|
||||
border: 1px solid #33ff33;
|
||||
box-shadow: 0 0 6px rgba(51, 255, 51, 0.18);
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-chat-replay__top .wrapped-label,
|
||||
.wrapped-theme-dos .wrapped-chat-replay__top .wrapped-body {
|
||||
color: #33ff33 !important;
|
||||
text-shadow: 0 0 3px rgba(51, 255, 51, 0.6);
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-chat-replay__bubble-text {
|
||||
font-family: 'Courier New', monospace;
|
||||
text-shadow: 0 0 3px rgba(51, 255, 51, 0.6);
|
||||
}
|
||||
|
||||
/* ========== Win98 主题 ========== */
|
||||
.wrapped-theme-win98 .wrapped-chat-replay {
|
||||
--wr-chat-frame-bg: #c0c0c0;
|
||||
--wr-chat-top-bg: linear-gradient(90deg, #000080, #1084d0);
|
||||
--wr-chat-chat-bg: #ffffff;
|
||||
--wr-chat-border: #808080;
|
||||
|
||||
--wr-chat-bubble-bg: #ffffff;
|
||||
--wr-chat-bubble-tail: #ffffff;
|
||||
--wr-chat-bubble-text: #000000;
|
||||
|
||||
--wr-chat-typing-bg: #ffffff;
|
||||
--wr-chat-typing-dot: #000000;
|
||||
|
||||
border-radius: 0;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .wrapped-chat-replay__top {
|
||||
border-bottom: 1px solid #808080;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .wrapped-chat-replay__top .wrapped-label,
|
||||
.wrapped-theme-win98 .wrapped-chat-replay__top .wrapped-body {
|
||||
/* Title-bar text should be white on the Win98 blue gradient. */
|
||||
color: #ffffff !important;
|
||||
text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .wrapped-chat-replay__avatar-fallback {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .wrapped-chat-replay__typing,
|
||||
.wrapped-theme-win98 .wrapped-chat-replay__bubble {
|
||||
border-radius: 0;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -268,160 +268,4 @@ const labels = computed(() => {
|
||||
.overview-progress-fill {
|
||||
@apply h-full rounded-full bg-[#07C160];
|
||||
}
|
||||
|
||||
/* ========== Game Boy 主题 ========== */
|
||||
|
||||
.wrapped-theme-gameboy .overview-card {
|
||||
background: #8bac0f !important;
|
||||
border: 4px solid #306230 !important;
|
||||
border-radius: 0 !important;
|
||||
backdrop-filter: none;
|
||||
box-shadow:
|
||||
inset -2px -2px 0 0 #306230,
|
||||
inset 2px 2px 0 0 #c5d870;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-progress-bg {
|
||||
background: #306230 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-progress-fill {
|
||||
background: #0f380f !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-grid-line {
|
||||
stroke: #306230;
|
||||
stroke-opacity: 0.4;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-axis-line {
|
||||
stroke: #306230;
|
||||
stroke-opacity: 0.5;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-data-polygon {
|
||||
fill: rgba(15, 56, 15, 0.3);
|
||||
stroke: #0f380f;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-data-node {
|
||||
fill: #0f380f;
|
||||
stroke: #9bbc0f;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .overview-label {
|
||||
fill: #0f380f;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-label,
|
||||
.wrapped-theme-gameboy .wrapped-body {
|
||||
color: #306230 !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-number {
|
||||
color: #0f380f !important;
|
||||
}
|
||||
|
||||
/* ========== DOS 主题 ========== */
|
||||
|
||||
.wrapped-theme-dos .overview-card {
|
||||
background: #0a0a0a !important;
|
||||
border: 1px solid #33ff33 !important;
|
||||
box-shadow: 0 0 10px rgba(51, 255, 51, 0.2);
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-grid-line {
|
||||
stroke: #33ff33;
|
||||
stroke-opacity: 0.2;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-axis-line {
|
||||
stroke: #33ff33;
|
||||
stroke-opacity: 0.3;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-data-polygon {
|
||||
fill: rgba(51, 255, 51, 0.15);
|
||||
stroke: #33ff33;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-data-node {
|
||||
fill: #33ff33;
|
||||
stroke: #0a0a0a;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-label {
|
||||
fill: #33ff33;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-progress-bg {
|
||||
background: #1a1a1a !important;
|
||||
border: 1px solid #33ff33;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .overview-progress-fill {
|
||||
background: #33ff33 !important;
|
||||
box-shadow: 0 0 5px #33ff33;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-label,
|
||||
.wrapped-theme-dos .wrapped-body {
|
||||
color: #22aa22 !important;
|
||||
text-shadow: 0 0 3px #22aa22;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-number {
|
||||
color: #33ff33 !important;
|
||||
text-shadow: 0 0 5px #33ff33;
|
||||
}
|
||||
|
||||
/* ========== Win98 主题 ========== */
|
||||
.wrapped-theme-win98 .overview-card {
|
||||
background: #c0c0c0 !important;
|
||||
border: 1px solid #808080 !important;
|
||||
border-radius: 0 !important;
|
||||
backdrop-filter: none;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #ffffff,
|
||||
inset -1px -1px 0 #000000;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-progress-bg {
|
||||
background: #ffffff !important;
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid #808080;
|
||||
box-shadow:
|
||||
inset 1px 1px 0 #000000,
|
||||
inset -1px -1px 0 #ffffff;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-progress-fill {
|
||||
background: #000080 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-grid-line {
|
||||
stroke: rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-axis-line {
|
||||
stroke: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-data-polygon {
|
||||
fill: rgba(0, 0, 128, 0.16);
|
||||
stroke: #000080;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-data-node {
|
||||
fill: #000080;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .overview-label {
|
||||
fill: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -791,929 +791,4 @@ const getLabelStyle = (code) => {
|
||||
@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>
|
||||
|
||||
@@ -59,8 +59,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { themedHeatColor, maxInMatrix, formatHourRange } from '~/utils/wrapped/heatmap'
|
||||
import { useWrappedTheme } from '~/composables/useWrappedTheme'
|
||||
import { heatColor, maxInMatrix, formatHourRange } from '~/utils/wrapped/heatmap'
|
||||
|
||||
const props = defineProps({
|
||||
weekdayLabels: { type: Array, default: () => ['周一', '周二', '周三', '周四', '周五', '周六', '周日'] },
|
||||
@@ -69,8 +68,6 @@ const props = defineProps({
|
||||
totalMessages: { type: Number, default: 0 }
|
||||
})
|
||||
|
||||
const { theme } = useWrappedTheme()
|
||||
|
||||
const matrixSafe = computed(() => {
|
||||
// Expect 7x24, but keep defensive to avoid UI crashes.
|
||||
const m = Array.isArray(props.matrix) ? props.matrix : []
|
||||
@@ -93,7 +90,7 @@ const timeLabels = computed(() => {
|
||||
return labels
|
||||
})
|
||||
|
||||
const colorFor = (v) => themedHeatColor(v, maxValue.value, theme.value)
|
||||
const colorFor = (v) => heatColor(v, maxValue.value)
|
||||
|
||||
const tooltipFor = (weekdayIndex, hour, v) => {
|
||||
const w = props.weekdayLabels?.[weekdayIndex] ?? `周${weekdayIndex + 1}`
|
||||
@@ -104,7 +101,7 @@ const tooltipFor = (weekdayIndex, hour, v) => {
|
||||
|
||||
const legendColor = (i) => {
|
||||
const t = i / 6
|
||||
return themedHeatColor(Math.max(1, t * (maxValue.value || 1)), maxValue.value || 1, theme.value)
|
||||
return heatColor(Math.max(1, t * (maxValue.value || 1)), maxValue.value || 1)
|
||||
}
|
||||
|
||||
const originFor = (weekdayIndex, hour) => {
|
||||
@@ -115,57 +112,3 @@ const originFor = (weekdayIndex, hour) => {
|
||||
return `${x} ${y}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* ========== Game Boy 主题 ========== */
|
||||
|
||||
.wrapped-theme-gameboy .heatmap-cell {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-label,
|
||||
.wrapped-theme-gameboy .wrapped-body {
|
||||
color: #306230 !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .wrapped-number {
|
||||
color: #0f380f !important;
|
||||
}
|
||||
|
||||
.wrapped-theme-gameboy .heatmap-legend-cell {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* ========== DOS 主题 ========== */
|
||||
|
||||
.wrapped-theme-dos .heatmap-cell {
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 0 2px rgba(51, 255, 51, 0.3);
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-label,
|
||||
.wrapped-theme-dos .wrapped-body {
|
||||
color: #22aa22 !important;
|
||||
text-shadow: 0 0 3px #22aa22;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .wrapped-number {
|
||||
color: #33ff33 !important;
|
||||
text-shadow: 0 0 5px #33ff33;
|
||||
}
|
||||
|
||||
.wrapped-theme-dos .heatmap-legend-cell {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* ========== Win98 主题 ========== */
|
||||
.wrapped-theme-win98 .heatmap-cell {
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.wrapped-theme-win98 .heatmap-legend-cell {
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/**
|
||||
* 年度总结页面主题管理 composable
|
||||
* 支持三种主题:modern(现代)、gameboy(Game Boy)、win98(Windows 98)
|
||||
*/
|
||||
|
||||
const STORAGE_KEY = 'wrapped-theme'
|
||||
const VALID_THEMES = ['off', 'gameboy', 'win98']
|
||||
const RETRO_THEMES = new Set(['gameboy'])
|
||||
|
||||
// 全局响应式状态(跨组件共享)
|
||||
const theme = ref('off')
|
||||
let initialized = false
|
||||
let keyboardInitialized = false
|
||||
|
||||
export function useWrappedTheme() {
|
||||
// 初始化:从 localStorage 读取(仅执行一次)
|
||||
const initTheme = () => {
|
||||
if (initialized || !import.meta.client) return
|
||||
const saved = localStorage.getItem(STORAGE_KEY)
|
||||
if (saved && VALID_THEMES.includes(saved)) {
|
||||
theme.value = saved
|
||||
}
|
||||
initialized = true
|
||||
}
|
||||
|
||||
// 设置主题
|
||||
const setTheme = (newTheme) => {
|
||||
if (!VALID_THEMES.includes(newTheme)) {
|
||||
console.warn(`Invalid theme: ${newTheme}`)
|
||||
return
|
||||
}
|
||||
theme.value = newTheme
|
||||
if (import.meta.client) {
|
||||
localStorage.setItem(STORAGE_KEY, newTheme)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到下一个主题(循环)
|
||||
const cycleTheme = () => {
|
||||
const currentIndex = VALID_THEMES.indexOf(theme.value)
|
||||
const nextIndex = (currentIndex + 1) % VALID_THEMES.length
|
||||
setTheme(VALID_THEMES[nextIndex])
|
||||
}
|
||||
|
||||
// 计算属性:是否为复古模式(非 off)
|
||||
const isRetro = computed(() => theme.value !== 'off')
|
||||
|
||||
// 计算属性:当前主题的 CSS 类名
|
||||
const themeClass = computed(() => {
|
||||
if (theme.value === 'off') return ''
|
||||
// Note: not every non-modern theme is "retro pixel/CRT".
|
||||
// Keep wrapped-retro for themes that rely on pixel/CRT shared styles.
|
||||
const base = RETRO_THEMES.has(theme.value) ? 'wrapped-retro ' : ''
|
||||
return `${base}wrapped-theme-${theme.value}`
|
||||
})
|
||||
|
||||
// 计算属性:主题显示名称
|
||||
const themeName = computed(() => {
|
||||
const names = {
|
||||
off: 'Modern',
|
||||
gameboy: 'Game Boy',
|
||||
win98: 'Windows 98'
|
||||
}
|
||||
return names[theme.value] || 'Modern'
|
||||
})
|
||||
|
||||
// 全局 F1-F3 快捷键切换主题(仅初始化一次)
|
||||
const initKeyboardShortcuts = () => {
|
||||
if (keyboardInitialized || !import.meta.client) return
|
||||
keyboardInitialized = true
|
||||
|
||||
const handleKeydown = (e) => {
|
||||
// 检查是否在可编辑元素中
|
||||
const el = e.target
|
||||
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || el.isContentEditable)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.key === 'F1') {
|
||||
e.preventDefault()
|
||||
setTheme('off')
|
||||
} else if (e.key === 'F2') {
|
||||
e.preventDefault()
|
||||
setTheme('gameboy')
|
||||
} else if (e.key === 'F3') {
|
||||
e.preventDefault()
|
||||
setTheme('win98')
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeydown)
|
||||
}
|
||||
|
||||
// 客户端挂载后再初始化:避免 SSR 与首帧 hydration 不一致
|
||||
onMounted(() => {
|
||||
initTheme()
|
||||
initKeyboardShortcuts()
|
||||
})
|
||||
|
||||
return {
|
||||
theme: readonly(theme),
|
||||
setTheme,
|
||||
cycleTheme,
|
||||
isRetro,
|
||||
themeClass,
|
||||
themeName,
|
||||
VALID_THEMES
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,12 @@
|
||||
<div
|
||||
ref="deckEl"
|
||||
class="wrapped-deck-root relative h-screen w-full overflow-hidden transition-colors duration-500"
|
||||
:class="themeClass"
|
||||
:style="{ backgroundColor: currentBg }"
|
||||
>
|
||||
<!-- PPT 风格:单张卡片占据全页面,鼠标滚轮切换 -->
|
||||
<WrappedDeckBackground />
|
||||
<!-- CRT 叠加层仅用于“像素屏”类主题,Win98 等桌面 GUI 主题不应开启 -->
|
||||
<WrappedCRTOverlay v-if="theme === 'gameboy'" />
|
||||
|
||||
<!-- 左上角:刷新 + 复古模式开关 -->
|
||||
<!-- 左上角:返回 + 刷新 -->
|
||||
<div class="absolute top-6 left-6 z-20 select-none">
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
@@ -59,24 +56,6 @@
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="pointer-events-auto inline-flex items-center justify-center w-9 h-9 rounded-full bg-transparent transition disabled:opacity-60 disabled:cursor-not-allowed focus:outline-none focus-visible:ring-2 focus-visible:ring-[#07C160]/30"
|
||||
:class="isRetro ? 'text-[#07C160] hover:bg-[#07C160]/10' : 'text-[#00000055] hover:bg-[#000000]/5'"
|
||||
:aria-pressed="isRetro ? 'true' : 'false'"
|
||||
:aria-label="`复古模式(当前:${theme === 'off' ? 'Modern' : theme.toUpperCase()})`"
|
||||
:title="`复古模式:${theme === 'off' ? 'Modern' : theme.toUpperCase()}(点击切换)`"
|
||||
@click="cycleTheme"
|
||||
>
|
||||
<img
|
||||
src="/assets/images/wechat-audio-dark.png"
|
||||
class="w-4 h-4 transition"
|
||||
:style="{ filter: isRetro ? 'none' : 'grayscale(1)', opacity: isRetro ? '1' : '0.55' }"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
draggable="false"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="mt-2 pointer-events-auto bg-white/90 backdrop-blur rounded-xl border border-red-200 px-3 py-2">
|
||||
@@ -88,7 +67,7 @@
|
||||
<!-- 右上角:年份选择器(主题化) -->
|
||||
<div class="absolute top-6 right-6 z-20 pointer-events-auto select-none">
|
||||
<div class="relative">
|
||||
<div v-if="!isRetro" class="absolute -inset-6 rounded-full bg-[#07C160]/10 blur-2xl"></div>
|
||||
<div class="absolute -inset-6 rounded-full bg-[#07C160]/10 blur-2xl"></div>
|
||||
<div class="relative flex justify-end">
|
||||
<WrappedYearSelector
|
||||
v-if="yearOptions.length > 1"
|
||||
@@ -97,7 +76,7 @@
|
||||
/>
|
||||
<div v-else class="wrapped-label text-xs text-[#00000066]">{{ year }}年</div>
|
||||
</div>
|
||||
<div v-if="!isRetro" class="relative mt-1 h-[1px] w-16 ml-auto bg-gradient-to-l from-[#07C160]/40 to-transparent"></div>
|
||||
<div class="relative mt-1 h-[1px] w-16 ml-auto bg-gradient-to-l from-[#07C160]/40 to-transparent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -205,11 +184,6 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Win98:底部任务栏 -->
|
||||
<WrappedWin98Taskbar
|
||||
v-if="theme === 'win98'"
|
||||
:title="taskbarTitle"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -229,9 +203,6 @@ const year = ref(Number(route.query?.year) || new Date().getFullYear())
|
||||
// 分享视图不展示账号信息:默认让后端自动选择;需要指定时可用 query ?account=wxid_xxx
|
||||
const account = ref(typeof route.query?.account === 'string' ? route.query.account : '')
|
||||
|
||||
// 主题管理:modern / gameboy / win98
|
||||
const { theme, cycleTheme, isRetro, themeClass } = useWrappedTheme()
|
||||
|
||||
const accounts = ref([])
|
||||
const accountsLoading = ref(true)
|
||||
|
||||
@@ -262,13 +233,6 @@ const wheelAcc = ref(0)
|
||||
let navUnlockTimer = null
|
||||
let deckResizeObserver = null
|
||||
|
||||
// 各主题的背景颜色
|
||||
const THEME_BG = {
|
||||
off: '#F3FFF8', // Modern: 浅绿
|
||||
gameboy: '#9bbc0f', // Game Boy: 亮绿
|
||||
win98: '#008080' // Win98: 经典桌面青色
|
||||
}
|
||||
|
||||
const slides = computed(() => {
|
||||
const cards = Array.isArray(report.value?.cards) ? report.value.cards : []
|
||||
const out = [{ key: 'cover' }]
|
||||
@@ -276,15 +240,7 @@ const slides = computed(() => {
|
||||
return out
|
||||
})
|
||||
|
||||
const taskbarTitle = computed(() => {
|
||||
if (theme.value !== 'win98') return ''
|
||||
if (activeIndex.value === 0) return `${year.value} WeChat Wrapped`
|
||||
const idx = activeIndex.value - 1
|
||||
const c = report.value?.cards?.[idx]
|
||||
return String(c?.title || 'WeChat Wrapped')
|
||||
})
|
||||
|
||||
const currentBg = computed(() => THEME_BG[theme.value] || THEME_BG.off)
|
||||
const currentBg = computed(() => '#F3FFF8')
|
||||
const deckTrackClass = computed(() => 'z-10')
|
||||
|
||||
const applyViewportBg = () => {
|
||||
@@ -423,11 +379,8 @@ const onTouchEnd = (e) => {
|
||||
const updateViewport = () => {
|
||||
const h = Math.round(deckEl.value?.getBoundingClientRect?.().height || deckEl.value?.clientHeight || window.innerHeight || 0)
|
||||
if (!h) return
|
||||
// Reserve space for the Win98 taskbar at the bottom.
|
||||
const offset = theme.value === 'win98' ? 40 : 0
|
||||
const effective = Math.max(0, h - offset)
|
||||
// Avoid endless reflows from 1px rounding errors (especially in Electron).
|
||||
if (Math.abs(viewportHeight.value - effective) > 1) viewportHeight.value = effective
|
||||
if (Math.abs(viewportHeight.value - h) > 1) viewportHeight.value = h
|
||||
}
|
||||
|
||||
const loadAccounts = async () => {
|
||||
@@ -592,12 +545,6 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Theme switch may change reserved UI space (e.g., Win98 taskbar)
|
||||
watch(theme, () => {
|
||||
applyViewportBg()
|
||||
updateViewport()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (import.meta.client) {
|
||||
document.documentElement.style.backgroundColor = ''
|
||||
|
||||
|
Before Width: | Height: | Size: 484 B |
|
Before Width: | Height: | Size: 571 B |
|
Before Width: | Height: | Size: 696 B |
|
Before Width: | Height: | Size: 773 B |
|
Before Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 458 B |
@@ -1,94 +0,0 @@
|
||||
Copyright (c) 2021, TakWolf (https://takwolf.com),
|
||||
with Reserved Font Name "Ark Pixel".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,110 +0,0 @@
|
||||
[BoutiqueBitmap7x7]
|
||||
2020-2022《字言字語》Cen-cyun, Liu. Luke Liu.
|
||||
https://fontspeech.blogspot.com/
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
此字型是免費的。
|
||||
無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
|
||||
本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
|
||||
[MisakiGothic]
|
||||
Copyright (C) 2002-2019 Num Kadoma
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
これらのフォントはフリー(自由な)ソフトウエアです。
|
||||
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
|
||||
[观致]
|
||||
这是日文字体Misaki基础上补充的
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,122 +0,0 @@
|
||||
[BoutiqueBitmap9x9]
|
||||
Copyright © 2025 字言字型 版權所有。
|
||||
Copyright © 2025 fancy type foundry. All rights reserved.
|
||||
2020-2025《字言字語》Cen-cyun, Liu. Luke Liu.
|
||||
https://fontspeech.blogspot.com/
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
此字型是免費的。
|
||||
無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
|
||||
本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
|
||||
此字体是免费的。
|
||||
无论您是否进行对本字型进行商业或非商业性修改,均可无限制地使用,复制和分发它们。
|
||||
本字型的衍生品之授权必须与此字型相同,且不作任何担保。
|
||||
[M+ BITMAP FONTS]
|
||||
Copyright (C) 2002-2004 COZ
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
これらのフォントはフリー(自由な)ソフトウエアです。
|
||||
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
|
||||
[ベストテン(BestTen-DOT)]
|
||||
フリーフォントのベストテンは無料でダウンロードできるドットフォント。商用・非商用問わず使用可能なので、安心してダウンロードしてください。
|
||||
作成した印刷物およびデジタル・コンテンツにつき、その商用・非商用にかかわらず印刷、放送、通信、各種記録メディアなどの媒体の形式も問わず、使用をすることができます。プログラムへの埋め込みが可能です。
|
||||
このフォントのライセンスは、
|
||||
M+のライセンスに準じます。
|
||||
M+ FONT LICENSEについては、配布物に含まれる
|
||||
mplus_bitmap_fonts をご覧ください。
|
||||
[Fusion Pixel]
|
||||
Copyright (c) 2022, TakWolf (https://takwolf.com), with Reserved Font Name 'Fusion Pixel'.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,108 +0,0 @@
|
||||
[Cubic 11]
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
此字型是免費的。
|
||||
無論您是否進行對本字型進行商業或非商業性修改,均可無限制地使用,複製和分發它們。
|
||||
本字型的衍生品之授權必須與此字型相同,且不作任何擔保。
|
||||
[JF Dot M+H 12]
|
||||
Copyright(c) 2005 M+ FONTS PROJECT
|
||||
[M+ BITMAP FONTS]
|
||||
Copyright (C) 2002-2004 COZ
|
||||
These fonts are free software.
|
||||
Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
これらのフォントはフリー(自由な)ソフトウエアです。
|
||||
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,93 +0,0 @@
|
||||
Copyright (c) 2019–2025 Lee Minseo (quiple@quiple.dev)
|
||||
|
||||
This font software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,258 +0,0 @@
|
||||
===============================================================================
|
||||
|
||||
8×8 ドット日本語フォント「美咲フォント」
|
||||
(2021-05-05 版)
|
||||
|
||||
Copyright(C) 2002-2021 Num Kadoma
|
||||
|
||||
===============================================================================
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
概要
|
||||
-------------------------------------------------------------------------------
|
||||
美咲フォントは 8×8 ドットの日本語ビットマップフォントです。
|
||||
JIS第一・第二水準をサポートしています。
|
||||
一部の記号を除いた全ての文字は 7×7 ドットの範囲に収まっているため、
|
||||
文字同士を隣接させても行間・字間が確保できます。
|
||||
|
||||
フォント名は制作のきっかけとなった美咲 礼威氏からいただきました。
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
バリエーション
|
||||
-------------------------------------------------------------------------------
|
||||
・「美咲明朝」は、美咲ゴシックの非漢字部を明朝風の字形に差し替えたものです。
|
||||
・「美咲ゴシック第2」は美咲ゴシックをベースに、半角文字は縦 7 px、
|
||||
全角仮名は横 7 px をいっぱいに使う字形に差し替えたものです。
|
||||
|
||||
各機種用のアーカイブに、差替え済みのフォントファイルを同梱してあります。
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
ライセンス
|
||||
-------------------------------------------------------------------------------
|
||||
These fonts are free softwares.
|
||||
Unlimited permission is granted to use, copy, and distribute it, with or without modification, either commercially and noncommercially.
|
||||
THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY.
|
||||
|
||||
これらのフォントはフリー(自由な)ソフトウエアです。
|
||||
あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、複製、再配布することができますが、全て無保証とさせていただきます。
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
制作履歴
|
||||
-------------------------------------------------------------------------------
|
||||
■2021-05-05
|
||||
・文字を追加
|
||||
共通: ␣
|
||||
・字形を修正・変更
|
||||
共通: №
|
||||
ゴシック第2: プヴ
|
||||
・(TTF) ビットマップを埋込み
|
||||
|
||||
■2019-10-19
|
||||
・ドキュメントの文字コードを UTF-8N、改行コードを LF に統一
|
||||
・(TTF, BDF) 記号を追加:
|
||||
▁▂▃▄▅▆▇█▏▎▍▌▋▊▉▔▕╭╮╰╯♠♥♦♣╱╲╳♤♡♢♧
|
||||
|
||||
■2019-08-18
|
||||
・各ファイルのパーミッションから、不要な実行ビットを除去 (フォントの変更はなし)
|
||||
|
||||
■2019-06-03
|
||||
・新フォント「美咲ゴシック第2」を追加。英語名「MisakiGothic2nd」
|
||||
・「令和」の合字を追加
|
||||
・各種字形の修正
|
||||
・(E500) ファイル名のつけ方を変更して配布を再開
|
||||
・(BDF) 一部仕様を変更して配布を再開
|
||||
エンコーディングは ISO 10646-1 のみとし、全ての文字を 1 ファイルに収録
|
||||
XLFD 変更
|
||||
BITMAP を文字の上下左右端の空白を削った形とし、BBX で位置を指定する形に修正
|
||||
|
||||
■2019-02-03a
|
||||
・制作履歴における、変更した文字の抜け (ゴシックの「と」「ね」「ゐ」) を修正
|
||||
|
||||
■2019-02-03
|
||||
・ゴシック・明朝共通の変更: &,.‘’()〔〕[]{}〈〉【】°′″№0索二
|
||||
半角 (「&」字形変更)
|
||||
記号類 (主に括弧類の位置調整)
|
||||
数字 (ゼロにスラッシュを追加)
|
||||
漢字 (「索」「二」の字形変更)
|
||||
・ゴシックのみの変更: 69CGOQΟОСЭぐぜとどねばぶゐガグゾダバヴ
|
||||
数字 (「6」「9」の先端を伸ばす形に変更)
|
||||
アルファベット (オーなどの丸み変更)
|
||||
仮名 (主に濁点/半濁点つき文字の調整)
|
||||
・明朝のみの変更: ぐでとどなぶウグケサザソゾダドワヱヴЁ
|
||||
仮名 (各種字形変更)
|
||||
キリル (「Ё」の字形変更)
|
||||
|
||||
■2015-04-10
|
||||
・誤字になっていた「喪」を修正
|
||||
・(TTF) ウェイトを「Regular」に変更
|
||||
一部の記号を追加、変更
|
||||
バージョン番号を「yyyy.mmdd」の形に変更
|
||||
その他、パラメータを見直し
|
||||
埋込みビットマップ版の同梱を取り止め
|
||||
|
||||
■2012-06-03
|
||||
・正式公開初版
|
||||
|
||||
■2012-03-31
|
||||
・各種ファイル名、ドキュメントの見直し
|
||||
・BDF 版以外のアーカイブ形式を zip に変更
|
||||
・(TTF) アウトライン版の正式採用、Mac におそらく対応
|
||||
・(FONTX) 4X8.FNT の 00h~1Fh に DOS/V 罫線を追加
|
||||
|
||||
■2011-03-08
|
||||
・(TTF-Outline) 生成手順の改善に伴い periodβ11 作成・公開
|
||||
|
||||
■2010-03-07
|
||||
・(BDF) FOUNDRY, POINT_SIZE, AVERAGE_WIDTH, SWIDTH, XLFD, 各ファイル名の変更
|
||||
|
||||
■2006-10-01
|
||||
・(TTF-Outline) 試作版配布開始
|
||||
|
||||
■2006-04-01
|
||||
・(PNG) 配布開始
|
||||
|
||||
■2006-03-26
|
||||
・(E500) 13 区追加版・軽量版 (第一水準のみ) 同梱開始
|
||||
フォントファイルの命名規則変更
|
||||
・(BDF) 美咲明朝の配布形態をゴシックとの差分から実ファイル同梱へ
|
||||
BITMAP の記述方法を修正 (16 進 4 桁から 2 桁へ)
|
||||
・(FONTX) 配布再開
|
||||
|
||||
■2005-07-20
|
||||
・アーカイブの命名規則を変更
|
||||
・(TTF, BDF) 著作権表記を変更
|
||||
・(BDF) 1 バイト文字フォントのファイル名をそれぞれ 4x8.bdf, 4x8_8859.bdf に
|
||||
|
||||
■2004-09-12
|
||||
・(TTF, BDF) 13 区の文字追加
|
||||
|
||||
■2004-06-24
|
||||
・フォントの名称変更
|
||||
・(美咲フォントを美咲ゴシック、ファミリーの総称を美咲フォントに)
|
||||
|
||||
■2004-05-09
|
||||
・美咲明朝の同梱開始
|
||||
|
||||
■2004-04-19
|
||||
・半濁点の処理を変更
|
||||
|
||||
■2004-04-11
|
||||
・3 フォント同時編集ツール「"SCRNJPN.FNT" Editor」完成
|
||||
|
||||
■2004-02-09
|
||||
・濁点の処理を変更
|
||||
|
||||
■2003-09-21
|
||||
・制作履歴の日付ミスを修正 (美咲氏 Thanx!)
|
||||
|
||||
■2003-06-04
|
||||
・ライセンスに関する記述を変更
|
||||
|
||||
■2003-05-04
|
||||
・(BDF) 3x7_8859.bdf の CHARS の値を修正
|
||||
・(BDF) 3x7_8859.bdf のファイル名が 3X7_8859.bdf になっていたのを修正
|
||||
|
||||
■2003-03-26
|
||||
・マニュアルをフォントの説明と各形式の説明に分離
|
||||
|
||||
■2003-01-02
|
||||
・各部のフォント間での統一化作業を本格的に開始
|
||||
|
||||
■2002-06-10
|
||||
・JIS X 0208-1983 での文字の変更、JIS X 0208-1990 での文字の追加に対応
|
||||
|
||||
■2002-06-06
|
||||
・未定義文字のフォントを 3×3 の ■ に
|
||||
|
||||
■2002-06-01
|
||||
・第二水準完成
|
||||
|
||||
■2002-05-22
|
||||
・制作再開
|
||||
|
||||
■2002-05-21
|
||||
・フォント作成専用ツール「'SCRNJPN.FNT' Editor」完成
|
||||
|
||||
■2002-05-11
|
||||
・E650 暴走により第二水準中の 600 文字近くが消滅→制作中断
|
||||
|
||||
■2002-04-初
|
||||
・第一水準完成
|
||||
|
||||
■2002-03-中
|
||||
・制作開始
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
バージョン情報
|
||||
-------------------------------------------------------------------------------
|
||||
■2021-05-05
|
||||
・美咲フォント 2021-05-05 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-10-19
|
||||
・美咲フォント 2019-10-19 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-08-18
|
||||
・美咲フォント 2019-06-03a 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-06-03
|
||||
・美咲フォント 2019-06-03 版 (E500/BDF/TTF/PNG)
|
||||
|
||||
■2019-02-03
|
||||
・美咲フォント 2019-02-03 版 (TTF/PNG)
|
||||
|
||||
■2015-04-10
|
||||
・美咲フォント 2015-04-10 版 (TTF/PNG)
|
||||
|
||||
■2012-06-03
|
||||
・美咲フォント 2012-06-03 版 (E500/BDF/TTF/Ruputer/FONTX/PNG)
|
||||
|
||||
■2012-03-31
|
||||
・美咲フォント periodβ12 (E500/BDF/TTF/Ruputer/FONTX/PNG)
|
||||
|
||||
■2010-03-07
|
||||
・美咲フォント periodβ11a (BDF)
|
||||
|
||||
■2008-06-03
|
||||
・美咲フォント periodβ11 (E500/BDF/TTF/Ruputer/FONTX/PNG/TTF-Outline)
|
||||
|
||||
■2006-03-26
|
||||
・美咲フォント periodβ10 (E500/BDF/TTF/Ruputer/FONTX/PNG/TTF-Outline)
|
||||
|
||||
■2005-07-20
|
||||
・美咲フォント periodβ9 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2004-05-09
|
||||
・美咲フォント periodβ8 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-09-21
|
||||
・美咲フォント periodβ7 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-06-04
|
||||
・美咲フォント periodβ6 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-05-04
|
||||
・美咲フォント periodβ5 (E500/BDF/TTF/Ruputer)
|
||||
|
||||
■2003-03-26
|
||||
・美咲フォント periodβ4 (E500/BDF/TTF/Ruputer/FONTX)
|
||||
|
||||
■2002-12-14
|
||||
・美咲フォント periodβ3 (E500)
|
||||
|
||||
■2002-06-18
|
||||
・美咲フォント periodβ2 (E500)
|
||||
|
||||
■2002-06-03
|
||||
・美咲フォント periodβ1 (E500)
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
連絡先
|
||||
-------------------------------------------------------------------------------
|
||||
門真 なむ (Num Kadoma)
|
||||
・Twitter: @num_kadoma
|
||||
・Website: http://littlelimit.net/
|
||||
@@ -1,92 +0,0 @@
|
||||
Copyright (c) 2023-2024 Mark Li (itmarkibfb@gmail.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION AND CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -1,94 +0,0 @@
|
||||
Copyright (c) 2022, TakWolf (https://takwolf.com),
|
||||
with Reserved Font Name "Fusion Pixel".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
Before Width: | Height: | Size: 818 KiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 97 KiB |
@@ -38,39 +38,6 @@ export const heatColor = (value, max) => {
|
||||
return `hsl(${hue.toFixed(1)} ${sat}% ${light.toFixed(1)}%)`
|
||||
}
|
||||
|
||||
// Theme-aware heat color function
|
||||
export const themedHeatColor = (value, max, theme) => {
|
||||
const v = Number(value) || 0
|
||||
const m = Number(max) || 0
|
||||
const t = (v > 0 && m > 0) ? clamp01(Math.sqrt(v / m)) : 0
|
||||
|
||||
switch (theme) {
|
||||
case 'gameboy': {
|
||||
// Game Boy 4-color palette: #0f380f, #306230, #8bac0f, #9bbc0f
|
||||
if (t === 0) return '#9bbc0f'
|
||||
if (t < 0.33) return '#8bac0f'
|
||||
if (t < 0.66) return '#306230'
|
||||
return '#0f380f'
|
||||
}
|
||||
case 'dos': {
|
||||
// DOS green phosphor: from dark to bright green
|
||||
if (t === 0) return 'rgba(51, 255, 51, 0.1)'
|
||||
const light = 20 + 60 * t
|
||||
return `hsl(120 100% ${light.toFixed(1)}%)`
|
||||
}
|
||||
case 'win98': {
|
||||
// Win98-ish "system colors": gray -> blue highlight
|
||||
if (t === 0) return '#dfdfdf'
|
||||
if (t < 0.33) return '#c0c0c0'
|
||||
if (t < 0.66) return '#808080'
|
||||
return '#000080'
|
||||
}
|
||||
default:
|
||||
// Modern (off) - use original heatColor
|
||||
return heatColor(value, max)
|
||||
}
|
||||
}
|
||||
|
||||
export const formatHourRange = (hour) => {
|
||||
const h = Number(hour)
|
||||
if (!Number.isFinite(h)) return ''
|
||||
|
||||