Update WBBR frontend template collection

This commit is contained in:
Codex
2026-08-27 20:15:24 +08:00
parent bd4d54a110
commit e444605e5d
85 changed files with 6322 additions and 115 deletions

File diff suppressed because one or more lines are too long

107
app/character/page.tsx Normal file
View File

@@ -0,0 +1,107 @@
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import data from "../data/content-templates.json";
import styles from "./CharacterDetail.module.css";
const character = data.character;
export default function CharacterDetailPage() {
const [favorite, setFavorite] = useState(false);
const [tags, setTags] = useState<string[]>(character.tags);
const [newTag, setNewTag] = useState("");
const [note, setNote] = useState("第 09 话需要加强她主动提出关闭穹顶照明的动机铺垫。\n与遥的冲突不能只停留在信息差需要落到选择差异。 ");
const [editingNote, setEditingNote] = useState(false);
const [activeTab, setActiveTab] = useState("overview");
const [toast, setToast] = useState("");
useEffect(() => {
try {
const saved = window.localStorage.getItem("star-character-detail");
if (saved) {
const parsed = JSON.parse(saved);
setFavorite(Boolean(parsed.favorite));
if (Array.isArray(parsed.tags)) setTags(parsed.tags);
if (typeof parsed.note === "string") setNote(parsed.note);
}
} catch { /* use JSON defaults */ }
}, []);
useEffect(() => {
window.localStorage.setItem("star-character-detail", JSON.stringify({ favorite, tags, note }));
}, [favorite, tags, note]);
const notify = (message: string) => {
setToast(message);
window.setTimeout(() => setToast(""), 2100);
};
const addTag = () => {
const value = newTag.trim();
if (!value || tags.includes(value)) return;
setTags((current) => [...current, value]);
setNewTag("");
notify(`已添加标签「${value}`);
};
return (
<div className={styles.page}>
<header className={styles.topbar}>
<Link className={styles.brand} href="/"><span>S</span><strong>STELLAR<br /><small>CHARACTER DATABASE</small></strong></Link>
<nav><Link href="/simple-list"></Link><Link className={styles.active} href="/character"></Link><Link href="/markdown">Markdown </Link></nav>
<div className={styles.headerActions}><button onClick={() => notify("搜索为模板交互占位")}></button><button onClick={() => notify("分享链接已复制(演示)")}></button><span></span></div>
</header>
<section className={styles.cover}>
<div className={styles.coverImage} />
<div className={styles.coverShade} />
<div className={styles.coverInner}>
<div className={styles.breadcrumb}><Link href="/"></Link><span>/</span><Link href="/simple-list"></Link><span>/</span><strong>{character.name}</strong></div>
<div className={styles.identity}>
<p>{character.code}</p><h1>{character.name}</h1><span>{character.nameEn}</span><blockquote>{character.quote}</blockquote>
</div>
<button className={favorite ? styles.following : styles.follow} onClick={() => { setFavorite((value) => !value); notify(favorite ? "已取消收藏" : "已收藏角色"); }}>{favorite ? "✓ 已收藏" : " 收藏角色"}</button>
</div>
</section>
<main className={styles.content}>
<aside className={styles.profileCard}>
<div className={styles.portrait} role="img" aria-label="天野澪角色形象"><span></span></div>
<div className={styles.profileBody}>
<p>{character.role}</p>
<dl>
<div><dt></dt><dd>{character.age} </dd></div><div><dt></dt><dd>{character.birthday}</dd></div><div><dt></dt><dd>{character.height}</dd></div><div><dt></dt><dd>{character.voice}</dd></div><div><dt></dt><dd>{character.affiliation}</dd></div>
</dl>
<button onClick={() => notify("角色编辑表单为模板交互占位")}></button>
</div>
</aside>
<div className={styles.mainColumn}>
<nav className={styles.tabs}>{[["overview","角色概览"],["setting","能力设定"],["timeline","剧情轨迹"]].map(([id,label]) => <button key={id} className={activeTab === id ? styles.tabActive : ""} onClick={() => setActiveTab(id)}>{label}</button>)}</nav>
{activeTab === "overview" && <>
<section className={styles.panel}><header><div><span>PROFILE</span><h2></h2></div><button onClick={() => notify("编辑器为模板交互占位")}></button></header><p className={styles.bio}>{character.bio}</p><div className={styles.keyFacts}><div><small></small><strong></strong></div><div><small></small><strong></strong></div><div><small></small><strong></strong></div></div></section>
<section className={styles.panel}><header><div><span>RELATIONSHIPS</span><h2></h2></div><button onClick={() => notify("关系图为模板交互占位")}></button></header><div className={styles.relations}>{character.relations.map((item) => <article key={item.name}><span className={styles[item.tone]}>{item.initial}</span><p><strong>{item.name}</strong><small>{item.relation}</small></p><button></button></article>)}</div></section>
</>}
{activeTab === "setting" && <>
<section className={styles.panel}><header><div><span>PERSONAL ATTRIBUTES</span><h2></h2></div><small> v2.4</small></header><div className={styles.traits}>{character.traits.map((trait) => <div key={trait.label}><p><span>{trait.label}</span><strong>{trait.value}</strong></p><i><b style={{width:`${trait.value}%`}} /></i></div>)}</div></section>
<section className={styles.panel}><header><div><span>SPECIAL SKILLS</span><h2></h2></div></header><div className={styles.skills}>{character.skills.map((skill) => <article key={skill.name}><span>{skill.level}</span><p><strong>{skill.name}</strong><small>{skill.description}</small></p></article>)}</div></section>
</>}
{activeTab === "timeline" && <section className={styles.panel}><header><div><span>STORY TIMELINE</span><h2></h2></div><button onClick={() => notify("已切换为按剧集排序")}></button></header><div className={styles.timeline}>{character.timeline.map((item,index) => <article key={item.episode}><i>{index+1}</i><div><span>{item.episode} · {item.type}</span><strong>{item.title}</strong></div><button> </button></article>)}</div></section>}
</div>
<aside className={styles.sideColumn}>
<section className={styles.tagPanel}><header><div><span>TAGS</span><h2></h2></div><small>{tags.length} </small></header><div className={styles.tags}>{tags.map((tag,index) => <span className={index < 2 ? styles.importantTag : ""} key={tag}>#{tag}<button onClick={() => setTags((current) => current.filter((item) => item !== tag))}>×</button></span>)}</div><form onSubmit={(event) => {event.preventDefault();addTag();}}><input value={newTag} onChange={(event) => setNewTag(event.target.value)} placeholder="输入新标签" /><button type="submit"></button></form><p></p></section>
<section className={styles.notePanel}><header><div><span>PRIVATE NOTE</span><h2></h2></div><button onClick={() => { setEditingNote((value) => !value); if (editingNote) notify("备注已保存"); }}>{editingNote ? "保存" : "编辑"}</button></header>{editingNote ? <textarea value={note} onChange={(event) => setNote(event.target.value)} autoFocus /> : <p>{note}</p>}<small> · </small></section>
<section className={styles.versionPanel}><span>v2.4</span><div><strong></strong><small> 14:32 · </small></div><button onClick={() => notify("版本记录为模板交互占位")}></button></section>
</aside>
</main>
{toast && <div className={styles.toast}> {toast}</div>}
</div>
);
}