-
@@ -35,7 +35,7 @@
v-for="(v, hi) in row"
:key="`${wi}-${hi}`"
class="aspect-square min-h-[10px] rounded-[2px] transition-transform duration-150 hover:scale-125 hover:z-10 relative"
- :style="{ backgroundColor: colorFor(v) }"
+ :style="{ backgroundColor: colorFor(v), transformOrigin: originFor(wi, hi) }"
:title="tooltipFor(wi, hi, v)"
/>
@@ -46,13 +46,13 @@
-
最大 {{ maxValue }}
+
最大 {{ maxValue }}
@@ -102,4 +102,12 @@ const legendColor = (i) => {
const t = i / 6
return heatColor(Math.max(1, t * (maxValue.value || 1)), maxValue.value || 1)
}
+
+const originFor = (weekdayIndex, hour) => {
+ // Avoid hover scaling pushing scrollWidth/scrollHeight and showing scrollbars:
+ // keep the "outer" edges anchored on the first/last row/col.
+ const x = hour === 0 ? 'left' : (hour === 23 ? 'right' : 'center')
+ const y = weekdayIndex === 0 ? 'top' : (weekdayIndex === 6 ? 'bottom' : 'center')
+ return `${x} ${y}`
+}
diff --git a/frontend/composables/useApi.js b/frontend/composables/useApi.js
index 5977948..590379f 100644
--- a/frontend/composables/useApi.js
+++ b/frontend/composables/useApi.js
@@ -321,6 +321,28 @@ export const useApi = () => {
const url = '/wrapped/annual' + (query.toString() ? `?${query.toString()}` : '')
return await request(url)
}
+
+ // WeChat Wrapped(年度总结)- 目录/元信息(轻量,用于按页懒加载)
+ const getWrappedAnnualMeta = async (params = {}) => {
+ const query = new URLSearchParams()
+ if (params && params.year != null) query.set('year', String(params.year))
+ if (params && params.account) query.set('account', String(params.account))
+ if (params && params.refresh != null) query.set('refresh', String(!!params.refresh))
+ const url = '/wrapped/annual/meta' + (query.toString() ? `?${query.toString()}` : '')
+ return await request(url)
+ }
+
+ // WeChat Wrapped(年度总结)- 单张卡片(按页加载)
+ const getWrappedAnnualCard = async (cardId, params = {}) => {
+ if (cardId == null) throw new Error('Missing cardId')
+ const query = new URLSearchParams()
+ if (params && params.year != null) query.set('year', String(params.year))
+ if (params && params.account) query.set('account', String(params.account))
+ if (params && params.refresh != null) query.set('refresh', String(!!params.refresh))
+ const safeId = encodeURIComponent(String(cardId))
+ const url = `/wrapped/annual/cards/${safeId}` + (query.toString() ? `?${query.toString()}` : '')
+ return await request(url)
+ }
return {
detectWechat,
@@ -350,6 +372,8 @@ export const useApi = () => {
getChatExport,
listChatExports,
cancelChatExport,
- getWrappedAnnual
+ getWrappedAnnual,
+ getWrappedAnnualMeta,
+ getWrappedAnnualCard
}
}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 2139597..059c82e 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,6 +9,7 @@
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.2",
+ "@vueuse/motion": "^3.0.3",
"axios": "^1.11.0",
"nuxt": "^4.0.1",
"vue": "^3.5.17",
@@ -1036,6 +1037,16 @@
"@jridgewell/trace-mapping": "^0.3.24"
}
},
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -1056,9 +1067,9 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.4",
- "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
- "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "version": "1.5.5",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
@@ -3898,6 +3909,12 @@
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "license": "MIT"
+ },
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz",
@@ -4328,6 +4345,114 @@
"integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
"license": "MIT"
},
+ "node_modules/@vueuse/core": {
+ "version": "13.9.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-13.9.0.tgz",
+ "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "13.9.0",
+ "@vueuse/shared": "13.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "13.9.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-13.9.0.tgz",
+ "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/motion": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/@vueuse/motion/-/motion-3.0.3.tgz",
+ "integrity": "sha512-4B+ITsxCI9cojikvrpaJcLXyq0spj3sdlzXjzesWdMRd99hhtFI6OJ/1JsqwtF73YooLe0hUn/xDR6qCtmn5GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vueuse/core": "^13.0.0",
+ "@vueuse/shared": "^13.0.0",
+ "defu": "^6.1.4",
+ "framesync": "^6.1.2",
+ "popmotion": "^11.0.5",
+ "style-value-types": "^5.1.2"
+ },
+ "optionalDependencies": {
+ "@nuxt/kit": "^3.13.0"
+ },
+ "peerDependencies": {
+ "vue": ">=3.0.0"
+ }
+ },
+ "node_modules/@vueuse/motion/node_modules/@nuxt/kit": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmmirror.com/@nuxt/kit/-/kit-3.21.0.tgz",
+ "integrity": "sha512-KMTLK/dsGaQioZzkYUvgfN9le4grNW54aNcA1jqzgVZLcFVy4jJfrJr5WZio9NT2EMfajdoZ+V28aD7BRr4Zfw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "c12": "^3.3.3",
+ "consola": "^3.4.2",
+ "defu": "^6.1.4",
+ "destr": "^2.0.5",
+ "errx": "^0.1.0",
+ "exsolve": "^1.0.8",
+ "ignore": "^7.0.5",
+ "jiti": "^2.6.1",
+ "klona": "^2.0.6",
+ "knitwork": "^1.3.0",
+ "mlly": "^1.8.0",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "pkg-types": "^2.3.0",
+ "rc9": "^2.1.2",
+ "scule": "^1.3.0",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ufo": "^1.6.3",
+ "unctx": "^2.5.0",
+ "untyped": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/@vueuse/motion/node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "13.9.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-13.9.0.tgz",
+ "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
"node_modules/@whatwg-node/disposablestack": {
"version": "0.0.6",
"resolved": "https://registry.npmmirror.com/@whatwg-node/disposablestack/-/disposablestack-0.0.6.tgz",
@@ -4916,26 +5041,26 @@
}
},
"node_modules/c12": {
- "version": "3.1.0",
- "resolved": "https://registry.npmmirror.com/c12/-/c12-3.1.0.tgz",
- "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
+ "version": "3.3.3",
+ "resolved": "https://registry.npmmirror.com/c12/-/c12-3.3.3.tgz",
+ "integrity": "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==",
"license": "MIT",
"dependencies": {
- "chokidar": "^4.0.3",
+ "chokidar": "^5.0.0",
"confbox": "^0.2.2",
"defu": "^6.1.4",
- "dotenv": "^16.6.1",
- "exsolve": "^1.0.7",
+ "dotenv": "^17.2.3",
+ "exsolve": "^1.0.8",
"giget": "^2.0.0",
- "jiti": "^2.4.2",
+ "jiti": "^2.6.1",
"ohash": "^2.0.11",
"pathe": "^2.0.3",
- "perfect-debounce": "^1.0.0",
- "pkg-types": "^2.2.0",
+ "perfect-debounce": "^2.0.0",
+ "pkg-types": "^2.3.0",
"rc9": "^2.1.2"
},
"peerDependencies": {
- "magicast": "^0.3.5"
+ "magicast": "*"
},
"peerDependenciesMeta": {
"magicast": {
@@ -4943,6 +5068,52 @@
}
}
},
+ "node_modules/c12/node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/c12/node_modules/dotenv": {
+ "version": "17.2.3",
+ "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-17.2.3.tgz",
+ "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/c12/node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "license": "MIT"
+ },
+ "node_modules/c12/node_modules/readdirp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz",
+ "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz",
@@ -6583,9 +6754,9 @@
}
},
"node_modules/exsolve": {
- "version": "1.0.7",
- "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.7.tgz",
- "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
"license": "MIT"
},
"node_modules/extract-zip": {
@@ -6673,10 +6844,13 @@
}
},
"node_modules/fdir": {
- "version": "6.4.6",
- "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.6.tgz",
- "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
+ "version": "6.5.0",
+ "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
"peerDependencies": {
"picomatch": "^3 || ^4"
},
@@ -6878,6 +7052,21 @@
"url": "https://github.com/sponsors/rawify"
}
},
+ "node_modules/framesync": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmmirror.com/framesync/-/framesync-6.1.2.tgz",
+ "integrity": "sha512-jBTqhX6KaQVDyus8muwZbBeGGP0XgujBRbQ7gM7BRdS3CadCZIHiawyzYLnafYcvZIh5j8WE7cxZKFn7dXhu9g==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "2.4.0"
+ }
+ },
+ "node_modules/framesync/node_modules/tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+ "license": "0BSD"
+ },
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz",
@@ -7260,6 +7449,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/hey-listen": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/hey-listen/-/hey-listen-1.0.8.tgz",
+ "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==",
+ "license": "MIT"
+ },
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
@@ -7842,9 +8037,9 @@
}
},
"node_modules/jiti": {
- "version": "2.5.1",
- "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.5.1.tgz",
- "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
+ "version": "2.6.1",
+ "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -7944,9 +8139,9 @@
}
},
"node_modules/knitwork": {
- "version": "1.2.0",
- "resolved": "https://registry.npmmirror.com/knitwork/-/knitwork-1.2.0.tgz",
- "integrity": "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==",
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/knitwork/-/knitwork-1.3.0.tgz",
+ "integrity": "sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw==",
"license": "MIT"
},
"node_modules/koa": {
@@ -8378,12 +8573,12 @@
}
},
"node_modules/magic-string": {
- "version": "0.30.17",
- "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
- "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "version": "0.30.21",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
+ "@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/magic-string-ast": {
@@ -8618,15 +8813,15 @@
}
},
"node_modules/mlly": {
- "version": "1.7.4",
- "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz",
- "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "version": "1.8.0",
+ "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz",
+ "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.14.0",
- "pathe": "^2.0.1",
- "pkg-types": "^1.3.0",
- "ufo": "^1.5.4"
+ "acorn": "^8.15.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.1"
}
},
"node_modules/mlly/node_modules/confbox": {
@@ -9739,9 +9934,9 @@
}
},
"node_modules/pkg-types": {
- "version": "2.2.0",
- "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.2.0.tgz",
- "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz",
+ "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
"license": "MIT",
"dependencies": {
"confbox": "^0.2.2",
@@ -9749,6 +9944,24 @@
"pathe": "^2.0.3"
}
},
+ "node_modules/popmotion": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmmirror.com/popmotion/-/popmotion-11.0.5.tgz",
+ "integrity": "sha512-la8gPM1WYeFznb/JqF4GiTkRRPZsfaj2+kCxqQgr2MJylMmIKUwBfWW8Wa5fml/8gmtlD5yI01MP1QCZPWmppA==",
+ "license": "MIT",
+ "dependencies": {
+ "framesync": "6.1.2",
+ "hey-listen": "^1.0.8",
+ "style-value-types": "5.1.2",
+ "tslib": "2.4.0"
+ }
+ },
+ "node_modules/popmotion/node_modules/tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+ "license": "0BSD"
+ },
"node_modules/portfinder": {
"version": "1.0.37",
"resolved": "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.37.tgz",
@@ -11071,9 +11284,9 @@
"license": "MIT"
},
"node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "version": "7.7.3",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -11584,6 +11797,22 @@
"integrity": "sha512-FL8EeKFFyNQv5cMnXI31CIMCsFarSVI2bF0U0ImeNE3g/F1IvJQyqzOXxPBRXiwQfyBTlbNe88jh1jFW0O/jiQ==",
"license": "ISC"
},
+ "node_modules/style-value-types": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/style-value-types/-/style-value-types-5.1.2.tgz",
+ "integrity": "sha512-Vs9fNreYF9j6W2VvuDTP7kepALi7sk0xtk2Tu8Yxi9UoajJdEVpNpCov0HsLTqXvNGKX+Uv09pkozVITi1jf3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "hey-listen": "^1.0.8",
+ "tslib": "2.4.0"
+ }
+ },
+ "node_modules/style-value-types/node_modules/tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+ "license": "0BSD"
+ },
"node_modules/stylehacks": {
"version": "7.0.6",
"resolved": "https://registry.npmmirror.com/stylehacks/-/stylehacks-7.0.6.tgz",
@@ -12235,9 +12464,9 @@
}
},
"node_modules/ufo": {
- "version": "1.6.1",
- "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
- "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "version": "1.6.3",
+ "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.3.tgz",
+ "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
"license": "MIT"
},
"node_modules/ultrahtml": {
@@ -12253,15 +12482,15 @@
"license": "MIT"
},
"node_modules/unctx": {
- "version": "2.4.1",
- "resolved": "https://registry.npmmirror.com/unctx/-/unctx-2.4.1.tgz",
- "integrity": "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==",
+ "version": "2.5.0",
+ "resolved": "https://registry.npmmirror.com/unctx/-/unctx-2.5.0.tgz",
+ "integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.14.0",
+ "acorn": "^8.15.0",
"estree-walker": "^3.0.3",
- "magic-string": "^0.30.17",
- "unplugin": "^2.1.0"
+ "magic-string": "^0.30.21",
+ "unplugin": "^2.3.11"
}
},
"node_modules/undici-types": {
@@ -12367,13 +12596,14 @@
}
},
"node_modules/unplugin": {
- "version": "2.3.5",
- "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.5.tgz",
- "integrity": "sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==",
+ "version": "2.3.11",
+ "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.11.tgz",
+ "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
"license": "MIT",
"dependencies": {
- "acorn": "^8.14.1",
- "picomatch": "^4.0.2",
+ "@jridgewell/remapping": "^2.3.5",
+ "acorn": "^8.15.0",
+ "picomatch": "^4.0.3",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
diff --git a/frontend/package.json b/frontend/package.json
index 685e338..ead0715 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.2",
+ "@vueuse/motion": "^3.0.3",
"axios": "^1.11.0",
"nuxt": "^4.0.1",
"vue": "^3.5.17",
diff --git a/frontend/pages/wrapped/index.vue b/frontend/pages/wrapped/index.vue
index 03ef0fe..ef8b6ae 100644
--- a/frontend/pages/wrapped/index.vue
+++ b/frontend/pages/wrapped/index.vue
@@ -1,18 +1,96 @@
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -26,33 +104,6 @@
-
-
-
-
-
-
生成失败
-
{{ error }}
-
- 提示:请确认已完成解密,并且后端服务正在运行(默认 http://127.0.0.1:8000)。
-
-
-
-
{ year.value = v }"
- @update:account="(v) => { account.value = v }"
- @update:refresh="(v) => { refresh.value = v }"
- @reload="reload"
- />
-
-
@@ -63,8 +114,55 @@
class="w-full"
:style="slideStyle"
>
+
+
+
{{ c?.error || '未知错误' }}
+
+
+
+
+
+
+ 翻到此页后开始生成…
+ 正在生成本页数据…
+
+
+
+
+
+
{
+ const ys = Array.isArray(availableYears.value) ? availableYears.value : []
+ const out = ys
+ .map((x) => Number(x))
+ .filter((x) => Number.isFinite(x))
+ .sort((a, b) => b - a)
+ // Fallback to current year if backend couldn't provide a list yet.
+ return out.length > 0 ? out : [year.value]
+})
const deckEl = ref(null)
const viewportHeight = ref(0)
@@ -278,25 +391,161 @@ const loadAccounts = async () => {
}
}
-const reload = async () => {
+const ensureCardLoaded = async (cardId) => {
+ const id = Number(cardId)
+ if (!Number.isFinite(id)) return
+ const token = reportToken
+
+ const cards = report.value?.cards
+ if (!Array.isArray(cards)) return
+
+ const idx = cards.findIndex((x) => Number(x?.id) === id)
+ if (idx < 0) return
+
+ const cur = cards[idx]
+ if (cur?.status === 'ok' || cur?.status === 'loading') return
+
+ // Mark as loading immediately so the UI can show a spinner on this slide.
+ cards[idx] = {
+ ...(cur || {}),
+ id,
+ title: cur?.title || `Card ${id}`,
+ scope: cur?.scope || 'global',
+ category: cur?.category || 'A',
+ kind: cur?.kind || '',
+ status: 'loading',
+ error: ''
+ }
+
+ try {
+ const resp = await api.getWrappedAnnualCard(id, {
+ year: year.value,
+ account: account.value || null,
+ refresh: !!refreshCards.value
+ })
+
+ // Ignore stale responses after year/account reload.
+ if (token !== reportToken) return
+
+ if (resp && Number(resp?.id) === id) {
+ cards[idx] = resp
+ } else {
+ // Best-effort fallback (shouldn't happen unless backend shape changes).
+ cards[idx] = resp || cards[idx]
+ }
+ } catch (e) {
+ if (token !== reportToken) return
+ const msg = e?.message || String(e)
+ cards[idx] = {
+ ...(cur || {}),
+ id,
+ title: cur?.title || `Card ${id}`,
+ scope: cur?.scope || 'global',
+ category: cur?.category || 'A',
+ kind: cur?.kind || '',
+ status: 'error',
+ narrative: '',
+ data: null,
+ error: msg
+ }
+ }
+}
+
+const retryCard = async (cardId) => {
+ await ensureCardLoaded(cardId)
+}
+
+const reload = async (forceRefresh = false) => {
+ const token = ++reportToken
activeIndex.value = 0
error.value = ''
loading.value = true
+ refreshCards.value = !!forceRefresh
try {
- const resp = await api.getWrappedAnnual({
+ const resp = await api.getWrappedAnnualMeta({
year: year.value,
account: account.value || null,
- refresh: !!refresh.value
+ refresh: !!forceRefresh
})
- report.value = resp || null
+
+ if (token !== reportToken) return
+
+ const manifest = Array.isArray(resp?.cards) ? resp.cards : []
+ report.value = {
+ ...(resp || {}),
+ cards: manifest.map((m, i) => ({
+ id: Number(m?.id ?? i),
+ title: String(m?.title || `Card ${m?.id ?? i}`),
+ scope: m?.scope || 'global',
+ category: m?.category || 'A',
+ kind: String(m?.kind || ''),
+ status: 'idle',
+ narrative: '',
+ data: null,
+ error: ''
+ }))
+ }
+
+ // Backend may snap the year to the latest available year (only years with data are selectable).
+ const respYear = Number(resp?.year)
+ if (Number.isFinite(respYear)) {
+ year.value = respYear
+ try {
+ await router.replace({ query: { ...route.query, year: String(respYear) } })
+ } catch {
+ // ignore
+ }
+ }
+
+ availableYears.value = Array.isArray(resp?.availableYears) ? resp.availableYears : []
} catch (e) {
+ if (token !== reportToken) return
report.value = null
error.value = e?.message || String(e)
} finally {
+ if (token !== reportToken) return
loading.value = false
}
}
+// Lazy-load the active slide's card data.
+watch(activeIndex, (i) => {
+ const cardIdx = Number(i) - 1
+ if (!Number.isFinite(cardIdx) || cardIdx < 0) return
+ const c = report.value?.cards?.[cardIdx]
+ const id = Number(c?.id)
+ if (!Number.isFinite(id)) return
+ void ensureCardLoaded(id)
+})
+
+const setYear = async (y) => {
+ const ny = Number(y)
+ if (!Number.isFinite(ny)) return
+ if (ny === year.value) return
+ // Only allow switching to years that the backend reported as having data.
+ if (Array.isArray(availableYears.value) && availableYears.value.length > 0 && !availableYears.value.includes(ny)) return
+ year.value = ny
+ await reload()
+}
+
+onMounted(() => {
+ try {
+ const saved = localStorage.getItem('wrapped_retro')
+ if (saved === '0') retro.value = false
+ if (saved === '1') retro.value = true
+ } catch {
+ // ignore
+ }
+})
+
+watch(retro, (v) => {
+ try {
+ localStorage.setItem('wrapped_retro', v ? '1' : '0')
+ } catch {
+ // ignore
+ }
+})
+
onMounted(async () => {
updateViewport()
window.addEventListener('resize', updateViewport)
@@ -306,14 +555,10 @@ onMounted(async () => {
deckEl.value?.addEventListener('touchstart', onTouchStart, { passive: true })
deckEl.value?.addEventListener('touchend', onTouchEnd, { passive: true })
- try {
- await loadAccounts()
- // Auto-generate once if we already have decrypted accounts, to match "one click" expectations.
- if (accounts.value.length > 0) {
- await reload()
- }
- } finally {
- bootstrapped.value = true
+ await loadAccounts()
+ // Auto-generate once if we already have decrypted accounts, to match "one click" expectations.
+ if (accounts.value.length > 0) {
+ await reload()
}
})
diff --git a/frontend/public/assets/images/wechat-audio-dark.png b/frontend/public/assets/images/wechat-audio-dark.png
new file mode 100644
index 0000000..0bf8aa3
Binary files /dev/null and b/frontend/public/assets/images/wechat-audio-dark.png differ
diff --git a/frontend/public/fonts/.gitkeep b/frontend/public/fonts/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/public/fonts/LICENSE/ark-pixel.txt b/frontend/public/fonts/LICENSE/ark-pixel.txt
new file mode 100644
index 0000000..0145130
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/ark-pixel.txt
@@ -0,0 +1,94 @@
+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.
diff --git a/frontend/public/fonts/LICENSE/boutique-bitmap-7x7.txt b/frontend/public/fonts/LICENSE/boutique-bitmap-7x7.txt
new file mode 100644
index 0000000..16d8630
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/boutique-bitmap-7x7.txt
@@ -0,0 +1,110 @@
+[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.
\ No newline at end of file
diff --git a/frontend/public/fonts/LICENSE/boutique-bitmap-9x9.txt b/frontend/public/fonts/LICENSE/boutique-bitmap-9x9.txt
new file mode 100644
index 0000000..5e52852
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/boutique-bitmap-9x9.txt
@@ -0,0 +1,122 @@
+[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.
diff --git a/frontend/public/fonts/LICENSE/cubic-11.txt b/frontend/public/fonts/LICENSE/cubic-11.txt
new file mode 100644
index 0000000..4e86331
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/cubic-11.txt
@@ -0,0 +1,108 @@
+[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.
diff --git a/frontend/public/fonts/LICENSE/galmuri.txt b/frontend/public/fonts/LICENSE/galmuri.txt
new file mode 100644
index 0000000..ae34182
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/galmuri.txt
@@ -0,0 +1,93 @@
+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.
diff --git a/frontend/public/fonts/LICENSE/misaki.txt b/frontend/public/fonts/LICENSE/misaki.txt
new file mode 100644
index 0000000..3fefec4
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/misaki.txt
@@ -0,0 +1,258 @@
+===============================================================================
+
+ 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/
diff --git a/frontend/public/fonts/LICENSE/miseki-bitmap.txt b/frontend/public/fonts/LICENSE/miseki-bitmap.txt
new file mode 100644
index 0000000..a43be19
--- /dev/null
+++ b/frontend/public/fonts/LICENSE/miseki-bitmap.txt
@@ -0,0 +1,92 @@
+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.
diff --git a/frontend/public/fonts/OFL.txt b/frontend/public/fonts/OFL.txt
new file mode 100644
index 0000000..88feeb1
--- /dev/null
+++ b/frontend/public/fonts/OFL.txt
@@ -0,0 +1,94 @@
+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.
diff --git a/frontend/public/fonts/fusion-pixel-10px-monospaced-ja.otf.woff2 b/frontend/public/fonts/fusion-pixel-10px-monospaced-ja.otf.woff2
new file mode 100644
index 0000000..0a8e634
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-10px-monospaced-ja.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-10px-monospaced-ko.otf.woff2 b/frontend/public/fonts/fusion-pixel-10px-monospaced-ko.otf.woff2
new file mode 100644
index 0000000..4db85e7
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-10px-monospaced-ko.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-10px-monospaced-latin.otf.woff2 b/frontend/public/fonts/fusion-pixel-10px-monospaced-latin.otf.woff2
new file mode 100644
index 0000000..34e81ba
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-10px-monospaced-latin.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-10px-monospaced-zh_hans.otf.woff2 b/frontend/public/fonts/fusion-pixel-10px-monospaced-zh_hans.otf.woff2
new file mode 100644
index 0000000..ffefa74
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-10px-monospaced-zh_hans.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-10px-monospaced-zh_hant.otf.woff2 b/frontend/public/fonts/fusion-pixel-10px-monospaced-zh_hant.otf.woff2
new file mode 100644
index 0000000..bd7233d
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-10px-monospaced-zh_hant.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-12px-monospaced-ja.otf.woff2 b/frontend/public/fonts/fusion-pixel-12px-monospaced-ja.otf.woff2
new file mode 100644
index 0000000..f03dbdf
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-12px-monospaced-ja.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-12px-monospaced-ko.otf.woff2 b/frontend/public/fonts/fusion-pixel-12px-monospaced-ko.otf.woff2
new file mode 100644
index 0000000..e99b824
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-12px-monospaced-ko.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-12px-monospaced-latin.otf.woff2 b/frontend/public/fonts/fusion-pixel-12px-monospaced-latin.otf.woff2
new file mode 100644
index 0000000..37df691
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-12px-monospaced-latin.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-12px-monospaced-zh_hans.otf.woff2 b/frontend/public/fonts/fusion-pixel-12px-monospaced-zh_hans.otf.woff2
new file mode 100644
index 0000000..979ab24
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-12px-monospaced-zh_hans.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-12px-monospaced-zh_hant.otf.woff2 b/frontend/public/fonts/fusion-pixel-12px-monospaced-zh_hant.otf.woff2
new file mode 100644
index 0000000..9b815b6
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-12px-monospaced-zh_hant.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-8px-monospaced-ja.otf.woff2 b/frontend/public/fonts/fusion-pixel-8px-monospaced-ja.otf.woff2
new file mode 100644
index 0000000..c38d65f
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-8px-monospaced-ja.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-8px-monospaced-ko.otf.woff2 b/frontend/public/fonts/fusion-pixel-8px-monospaced-ko.otf.woff2
new file mode 100644
index 0000000..4d111e0
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-8px-monospaced-ko.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-8px-monospaced-latin.otf.woff2 b/frontend/public/fonts/fusion-pixel-8px-monospaced-latin.otf.woff2
new file mode 100644
index 0000000..f8acacc
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-8px-monospaced-latin.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-8px-monospaced-zh_hans.otf.woff2 b/frontend/public/fonts/fusion-pixel-8px-monospaced-zh_hans.otf.woff2
new file mode 100644
index 0000000..ea104ba
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-8px-monospaced-zh_hans.otf.woff2 differ
diff --git a/frontend/public/fonts/fusion-pixel-8px-monospaced-zh_hant.otf.woff2 b/frontend/public/fonts/fusion-pixel-8px-monospaced-zh_hant.otf.woff2
new file mode 100644
index 0000000..0b85d06
Binary files /dev/null and b/frontend/public/fonts/fusion-pixel-8px-monospaced-zh_hant.otf.woff2 differ
diff --git a/frontend/utils/wrapped/types.js b/frontend/utils/wrapped/types.js
index 04c8581..57083dd 100644
--- a/frontend/utils/wrapped/types.js
+++ b/frontend/utils/wrapped/types.js
@@ -6,12 +6,30 @@
* @property {string} title
* @property {'global'} scope
* @property {'A'|'B'|'C'|'D'|'E'} category
- * @property {'ok'|'error'} status
+ * @property {'ok'|'error'|'idle'|'loading'} status
* @property {string} kind
* @property {string} narrative
* @property {Record} data
*/
+/**
+ * @typedef {Object} WrappedCardManifest
+ * @property {number} id
+ * @property {string} title
+ * @property {'global'} scope
+ * @property {'A'|'B'|'C'|'D'|'E'} category
+ * @property {string} kind
+ */
+
+/**
+ * @typedef {Object} WrappedAnnualMetaResponse
+ * @property {string} account
+ * @property {number} year
+ * @property {'global'} scope
+ * @property {number[]|undefined} availableYears
+ * @property {WrappedCardManifest[]} cards
+ */
+
/**
* @typedef {Object} WrappedAnnualResponse
* @property {string} account
@@ -20,8 +38,8 @@
* @property {string|null} username
* @property {number} generated_at
* @property {boolean} cached
+ * @property {number[]|undefined} availableYears
* @property {WrappedCardBase[]} cards
*/
export {}
-