Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable public product changes are recorded here. Release notes contain only product scope and reproducible validation; platform identifiers and private operational details are excluded.

## v1.45.0 — 署名搜尋連結

- 歌曲卡片上的歌手、作詞、作曲及編曲署名可直接開啟創作者文字搜尋,沿用現有篩選及 12 套作品分頁。
- 入口名稱只放在瀏覽器網址片段;手動輸入及重設會清除片段,不傳送搜尋字詞或寫入應用程式儲存空間。
- 保留 80 字元輸入限制、完整超長合唱名單、鍵盤返回、無 JavaScript 目錄入口及離線搜尋;公開資料與 API 不變。

## v1.44.1 — 作品頁間距

- 縮短作品資料、外部來源連結、歌曲與資料來源區塊之間的過大留白,讓歌曲更早進入畫面。
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ Anisonary 是以 Astro + strict TypeScript 建立的動畫歌曲目錄,按季

This repository contains the completed static product: season directory, anime detail pages, traceable OP/ED credits and links, per-song source ledgers, source-attributed media, local-only cross-season search, privacy-bounded offline reading, a GitHub correction flow, and deployment through Cloudflare Workers Static Assets. The default catalogue covers twenty-nine reviewed snapshots across 2019–2026, with 1,947 unique titles and 4,307 known OP/ED records. The 2019 spring snapshot contains thirty TV titles, including television shorts and a TV recut; summer contains 38 TV titles and four web series. Both quarters are still being expanded, and 2025 fall is not yet included. Fictional Mock Data remains test-only.

目前 source 版本:**v1.44.1**。作品資料、外部來源連結及主題曲之間的留白縮短,讓歌曲更早進入畫面;海報、文字層級與完整資料保留。目錄共有 29 個季度、1,947 套作品與 4,307 筆 OP/ED;春季為 30 套作品、78 筆歌曲,春、夏季仍在補充,2025 秋季尚未收錄。
目前 source 版本:**v1.45.0**。歌曲卡片的歌手與創作署名可直接開啟本機搜尋,方便查找相關歌曲;超過搜尋字數上限的合唱名單仍完整顯示。目錄共有 29 個季度、1,947 套作品與 4,307 筆 OP/ED;春季為 30 套作品、78 筆歌曲,春、夏季仍在補充,2025 秋季尚未收錄。

導覽延續固定五個入口與年代、年份、季度目錄;搜尋可按動畫、歌曲、創作者、年份、季度及 OP/ED 篩選,每頁顯示 12 套作品。搜尋完全在瀏覽器內進行,YouTube 只於明確操作後連線。網站與同源靜態 JSON API 共用已審閱資料,無 application backend 或 database binding。依賴安全 gate 繼續鎖定 `fast-uri` 3.1.6 並稽核開發依賴。

Production build 會從最終 HTML 自動產生 hash-based Content Security Policy。政策不使用 `unsafe-inline` 或 `unsafe-eval`,禁止 inline event/style attributes,只開放同源資產、已核對的海報來源及使用者啟動後的 YouTube privacy-enhanced iframe。任何未批准的 media origin 會令 build fail closed。

| 可用性 / Availability | 成熟度 / Maturity | 證據 / Evidence |
| -------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 公開靜態目錄 / Public static catalogue | Source 版本為 `v1.44.1`;正式發布狀態以對應 Release 驗收摘要為準 | [入口網站 / Live](https://anisonary.k-y.cc) · [版本與驗收 / Releases](https://github.com/kyeunga25/anisonary/releases) · [資料來源 / Sources](docs/DATA_SOURCES.md) · [安全政策 / Security](SECURITY.md) · [授權 / Licence](LICENSING.md) · [版權 / Copyright](COPYRIGHT.md) |
| 公開靜態目錄 / Public static catalogue | Source 版本為 `v1.45.0`;正式發布狀態以對應 Release 驗收摘要為準 | [入口網站 / Live](https://anisonary.k-y.cc) · [版本與驗收 / Releases](https://github.com/kyeunga25/anisonary/releases) · [資料來源 / Sources](docs/DATA_SOURCES.md) · [安全政策 / Security](SECURITY.md) · [授權 / Licence](LICENSING.md) · [版權 / Copyright](COPYRIGHT.md) |

## 技術棧|Technology stack

Expand Down Expand Up @@ -67,9 +67,11 @@ Season coverage uses a repository-owned source registry: Annict is the Japanese
- year and quarter navigation is generated from the reviewed registry; cross-season works keep one detail page and every published season membership;
- `/search/` embeds a bounded, text-only JSON index in static HTML and renders up to 12 anime results per page;
- combine anime, song, or creator scope with year, quarter, and OP/ED filters; song links open the corresponding theme and its credits;
- click a song's performer or production credit to search related songs. These are name-based text searches, not canonical person profiles; names above the existing 80 UTF-16-unit input limit remain complete, unlinked text, as does the existing instrumental rendition marker;
- the index is limited to 10,000 unique works, 20,000 seasonal references, and 8 MiB of serialized UTF-8 data. Larger catalogues require a reviewed indexing change; these are safety ceilings, not promised device capacity;
- native year/quarter links remain usable without JavaScript;
- filtering runs locally without query parameters, analytics, cookies, or a search API;
- creator entry links carry the credited name in a browser URL fragment, which is excluded from HTTP requests and may appear in normal browser history. Manual edits clear that fragment and are never copied into the URL or application storage;
- the text-only result list does not load remote poster media;
- YouTube connects only after the user activates a clearly labelled consent button;
- Japanese and Traditional Chinese remain the primary visible languages, with English/Romaji support where available.
Expand Down
5 changes: 3 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Astro output ──► generated CSP ──► bounded service worker ──►
| `src/utils/catalog-directory.ts` | 從既有快照派生年代/年份/季度分類,不建立另一份資料來源 |
| `src/utils/catalog-search-index.ts` | 有 UTF-8 byte budget 的公開文字索引、跨季 membership、歌曲與 creator 篩選 |
| `src/pages/catalog/` | 固定入口下的年代目錄與年份靜態頁 |
| `src/scripts/catalog-search.ts` | 本機搜尋、每頁最多 12 套作品的 DOM,以及歌曲 anchor 導航 |
| `src/scripts/catalog-search.ts` | 本機搜尋、每頁最多 12 套作品的 DOM、歌曲 anchor 導航與署名入口條件 |
| `src/utils/creator-search.ts` | 只使用同源網址片段的署名搜尋連結,與輸入共用 80 UTF-16 單位上限 |
| `src/pages/api/v1/` | build-time 靜態 JSON routes |
| `scripts/generate-security-headers.mjs` | 由 build output 產生 hash-based CSP |
| `scripts/generate-service-worker.mjs` | 由 build output 產生同源、無 runtime write 的離線清單 |
Expand All @@ -61,7 +62,7 @@ Astro output ──► generated CSP ──► bounded service worker ──►

## Browser 與媒體邊界

- 搜尋只操作 build-time HTML,不把輸入放入 URL、request、analytics 或 storage。
- 搜尋只操作 build-time HTML,手動輸入不進入 URL、request、analytics 或 storage。歌曲署名入口使用 `/search/#creator=...` 預選創作者文字搜尋;片段不隨 HTTP request 送出,但可留在瀏覽器的一般歷史紀錄。手動輸入、篩選及重設會清除入口片段。
- Service Worker 只 precache 已公開的同源 app shell;不建立 runtime cache entry,不保存 `/api/` JSON、query string 或第三方媒體。
- 作品圖像使用核准 HTTPS origin 並加上頁面來源連結及 `no-referrer`;不下載後重新託管。
- YouTube iframe 只在使用者明確啟動後建立,使用 privacy-enhanced domain。
Expand Down
1 change: 1 addition & 0 deletions docs/PROJECT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
- **v1.43.0 source**:registry 支援具來源證據的本站識別碼,季度、歌曲來源及影片共用穩定關聯;未核對的外部 ID 與別名保持空缺,既有作品、搜尋與 API 不變;發布證據見對應 GitHub Release。
- **v1.44.0 source**:2019 春季增加《毛球權次郎》TV 版、2 筆 OP/ED 與 1 筆正式授權片尾影片 metadata;使用具來源證據的本站識別碼,保留動畫短版、CD 完整版日期與共同作曲,春季共 30 套作品與 78 筆歌曲;發布證據見對應 GitHub Release。
- **v1.44.1 source**:縮短作品頁資料、來源連結與歌曲分區間距,保留海報尺寸、文字層級及全部連結;目錄、搜尋與 API 不變,發布證據見對應 GitHub Release。
- **v1.45.0 source**:歌曲署名直接開啟本機創作者文字搜尋,保留完整名稱及既有字數上限;入口條件使用不會送出的網址片段,手動編輯時清除,資料、API 與全域導覽數量不變;發布證據見對應 GitHub Release。
- 正式網站使用 custom domain;非正式 Cloudflare hostname 不在公開文檔記錄;
- 私有 backend、資料庫、crawler、帳戶設定、憑證與內部規則不屬於本公開 repository。

Expand Down
26 changes: 26 additions & 0 deletions docs/QA_V1_45_CREATOR_LINKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# v1.45.0|署名搜尋連結 QA

核對日期:2026-09-07。本文件記錄 source 與本機驗證;正式部署須於固定版本合併後獨立驗收。

## 功能與資料邊界

- 歌曲卡片的演出名義及個別創作署名可開啟既有創作者文字搜尋,保留年份、季度、OP/ED 篩選及 12 套作品分頁。
- 連結使用同源 `/search/#creator=...`;名稱只由瀏覽器讀取,HTTP request 不包含片段。片段可留在一般瀏覽器歷史紀錄,手動輸入、篩選及重設會清除它,不另寫入 application storage。
- 這是名稱文字搜尋,沒有建立人物身份、同名消歧、人物頁或新的資料庫關聯。
- 共用既有 80 UTF-16 單位輸入上限;完整超長合唱署名與既有純音樂版本標記維持文字,不截斷、推測個別人名或產生誤導連結。
- 拒絕不合法編碼、控制字元、空白變形、超長或多組入口條件;其他頁面錨點不會重設搜尋。來源、影片同意及完整 credits 保留。

## 本機驗收

- `npm run catalog:check`:223 項通過;最終 `npm run check`:公開邊界、Lint、326 項單元測試、build、Cloudflare dry-run 及 39 項 Playwright 測試通過,最終 E2E 沒有重試。
- 完整及正式依賴稽核均為 0 項漏洞;`fast-uri` 3.1.6 的既有安全回歸通過。
- 1,977 份 API 與 v1.44.1 source 逐位元相同,沒有新增或移除;1,947 份作品 HTML 的 5,550 個署名連結均能完整還原顯示名稱,且不含 URL query。
- 建置共 1,991 份 HTML、3,981 份公開產物;Cloudflare dry-run 沒有 runtime bindings。
- Chromium 1280、960、390、320px 及 390px 無 JavaScript,共 15 個畫面沒有水平溢出、頁面錯誤、提前建立的 iframe 或 YouTube 媒體請求;人工檢視 1280px 歌曲卡片及 390px 預填搜尋畫面。
- E2E 驗證鍵盤開啟署名、歌曲返回、重設、手動輸入與篩選清除片段、跳到主要內容、不合法入口、超長完整合唱、純音樂標記,以及無 JavaScript 的年份目錄替代入口。
- 離線狀態可從已快取的歌曲卡片進入署名搜尋並重新整理;搜尋字詞不進入 localStorage、sessionStorage 或 Cache Storage URL。
- 初次新 E2E 對無 JavaScript 提示的定位過於寬泛,收窄後仍受測試工具忽略 `noscript` 容器文字影響;直接確認可見內文後改為定位其段落,單項及最終完整回歸通過。

## 發布界線

本機與分支建置不代表正式網站已更新。發布時須核對合併 SHA、CI、Cloudflare 實際版本與流量,以及 live routes、headers、API 與產物內容。本切片未驗證實體裝置、Safari、Firefox、Lighthouse/Core Web Vitals 或實際 YouTube 播放。
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "anisonary",
"version": "1.44.1",
"version": "1.45.0",
"private": true,
"license": "SEE LICENSE IN LICENSING.md",
"type": "module",
Expand Down
13 changes: 13 additions & 0 deletions src/components/links/CreatorSearchLink.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
import { buildCreatorSearchHref } from "@/utils/creator-search";

interface Props { name: string; }
const { name } = Astro.props;
const href = buildCreatorSearchHref(name);
---
{href ? <a class="creator-search-link" href={href} aria-label={`搜尋「${name}」的歌曲`}>{name}</a> : name}

<style>
.creator-search-link { color: inherit; text-decoration-color: color-mix(in srgb, currentColor 45%, transparent); text-decoration-line: underline; text-underline-offset: 0.2em; }
.creator-search-link:hover { color: var(--color-accent); text-decoration-color: currentColor; }
</style>
5 changes: 3 additions & 2 deletions src/components/theme/ThemeCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { PublicTheme } from "@/types/public-api";
import { creditRoleLabel } from "@/utils/theme";
import ExternalLinkList from "@/components/links/ExternalLinkList.astro";
import CreatorSearchLink from "@/components/links/CreatorSearchLink.astro";
import LazyYouTube from "@/components/youtube/LazyYouTube.astro";

interface Props { theme: PublicTheme; }
Expand Down Expand Up @@ -52,14 +53,14 @@ const sourceRoleLabels = {
<div class="theme-card__identity">
<h3 lang="ja">{theme.titleJa}</h3>
{theme.titleZhHant && <p class="theme-card__translation">{theme.titleZhHant}</p>}
<p class="theme-card__artist">{theme.artistDisplayName}</p>
<p class="theme-card__artist"><CreatorSearchLink name={theme.artistDisplayName} /></p>
{theme.versionLabel && <p class="theme-card__version">{theme.versionLabel}</p>}
{theme.releaseDate && <p class="theme-card__release-date">發行日期 <time datetime={theme.releaseDate}>{theme.releaseDate}</time></p>}
</div>

<dl class="theme-card__credits">
{creditGroups.size > 0 ? [...creditGroups].map(([role, names]) => (
<div><dt>{creditRoleLabel(role)}</dt><dd>{names.join("、")}</dd></div>
<div><dt>{creditRoleLabel(role)}</dt><dd>{names.map((name, index) => <>{index > 0 && "、"}<CreatorSearchLink name={name} /></>)}</dd></div>
)) : (
<div><dt>{repeatsVocalList ? "製作" : "Credits"}</dt><dd>待確認</dd></div>
)}
Expand Down
5 changes: 3 additions & 2 deletions src/pages/search.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BaseLayout from "@/layouts/BaseLayout.astro";
import { getDataProvider, shouldRequireApiData } from "@/data/provider";
import { loadCatalogSearchData } from "@/data/page-data";
import { buildCatalogSearchIndex, serializeCatalogSearchIndex } from "@/utils/catalog-search-index";
import { MAX_CATALOG_QUERY_LENGTH } from "@/utils/creator-search";

const data = await loadCatalogSearchData(getDataProvider(), shouldRequireApiData());
const index = buildCatalogSearchIndex(data.entries);
Expand All @@ -23,7 +24,7 @@ const themeCount = index.reduce((total, anime) => total + anime.themes.length, 0
<label id="catalog-search-label" for="catalog-query">搜尋動畫、歌曲或創作者</label>
<div class="catalog-search__control">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5" /><path d="m15.5 15.5 5 5" /></svg>
<input id="catalog-query" type="search" placeholder="例如:幼女 myth、片頭曲 ReoNa" maxlength="80" autocomplete="off" autocapitalize="off" spellcheck="false" aria-describedby="catalog-search-privacy catalog-search-summary" data-catalog-search-input />
<input id="catalog-query" type="search" placeholder="例如:幼女 myth、片頭曲 ReoNa" maxlength={MAX_CATALOG_QUERY_LENGTH} autocomplete="off" autocapitalize="off" spellcheck="false" aria-describedby="catalog-search-privacy catalog-search-summary" data-catalog-search-input />
<button type="button" data-catalog-search-clear disabled>重設</button>
</div>
<div class="catalog-search__filters">
Expand All @@ -32,7 +33,7 @@ const themeCount = index.reduce((total, anime) => total + anime.themes.length, 0
<div><label for="catalog-quarter">季度</label><select id="catalog-quarter"><option value="">全部季度</option><option value="winter">冬季</option><option value="spring">春季</option><option value="summer">夏季</option><option value="fall">秋季</option></select></div>
<div><label for="catalog-type">歌曲用途</label><select id="catalog-type"><option value="all">全部 OP/ED</option><option value="OP">片頭曲 OP</option><option value="ED">片尾曲 ED</option></select></div>
</div>
<p id="catalog-search-privacy" class="catalog-search__privacy">Local-only search · 搜尋字詞不會傳送或儲存,不載入第三方圖片。</p>
<p id="catalog-search-privacy" class="catalog-search__privacy">Local-only search · 本站不傳送或儲存搜尋字詞,不載入第三方圖片。</p>
</form>
<noscript><p>搜尋需要 JavaScript。你仍可<a href="/catalog/">按年份與季度瀏覽全部動畫</a>。</p></noscript>
<p id="catalog-search-summary" class="catalog-search__summary" aria-live="polite">找到 <strong data-catalog-anime-count>{index.length}</strong> 套動畫、<strong data-catalog-theme-count>{themeCount}</strong> 筆主題曲</p>
Expand Down
Loading