feat(定时任务): 新增第八个 HTML 模板 no.8(只展示)

- no.8.html(77KB)复制进 templates/no8/;数据存于 <script id="DATA"
  type="application/json"> 元素(同 no.4/no.7 元素注入),当前引擎只支持 const VAR= 行注入,
  故先注册为 convertible=False 只展示
- 前端下拉/参考列表自动出现 no.8
- 测试覆盖 no.8 存在/只展示/文件可读

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-24 18:16:34 +08:00
1 parent 60029ae3dc
commit 31cd19aabb
2 files changed
+312

No files matched your search

@@ -184,6 +184,15 @@ TEMPLATES: tuple[TemplateSpec, ...] = (
# 当前引擎只支持 const VAR= 行注入,故暂注册为只展示;接转换需扩展元素注入。
convertible=False,
),
TemplateSpec(
id="no8",
name="no.8",
description="",
file="no8/no8.html",
# 数据存于 <script id="DATA" type="application/json"> 元素(同 no.4/no.7 元素注入),
# 当前引擎只支持 const VAR= 行注入,故暂注册为只展示;接转换需扩展元素注入。
convertible=False,
),
)
_TEMPLATES_BY_ID: dict[str, TemplateSpec] = {t.id: t for t in TEMPLATES}
@@ -0,0 +1,303 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>美国左翼退伍军人与关联组织专题展示平台</title>
<style>
:root{
--bg:#f6f1eb;
--panel:#fffaf4;
--panel2:#fffdf9;
--text:#251b17;
--muted:#776963;
--line:#d9c9bb;
--heavy:#4b241b;
--accent:#a33a2b;
--accent2:#cf7a45;
--soft:#f1dfd2;
--soft2:#ead1c0;
--green:#627b58;
--blue:#4f6f86;
--purple:#6f5d83;
--shadow:0 18px 50px rgba(66,35,20,.13);
--radius:22px;
}
*{box-sizing:border-box}
body{margin:0;font-family:"Microsoft YaHei","Noto Sans SC",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:linear-gradient(180deg,#f8f1e8 0%,#f3e9dc 55%,#efe0d2 100%);color:var(--text);}
.app{max-width:1480px;margin:0 auto;padding:26px 24px 56px;}
.hero{position:relative;overflow:hidden;border:1px solid var(--line);border-radius:30px;background:radial-gradient(circle at 10% 10%,rgba(207,122,69,.24),transparent 28%),radial-gradient(circle at 90% 0%,rgba(163,58,43,.15),transparent 34%),linear-gradient(135deg,#fffaf4,#f3dfcd);box-shadow:var(--shadow);padding:34px 36px;margin-bottom:22px;}
.hero:before{content:"";position:absolute;right:-90px;top:-130px;width:310px;height:310px;border:32px solid rgba(163,58,43,.08);border-radius:50%;}
.hero:after{content:"";position:absolute;right:120px;bottom:-130px;width:260px;height:260px;border:1px solid rgba(75,36,27,.14);border-radius:50%;}
.hero-inner{position:relative;z-index:2;display:grid;grid-template-columns:minmax(0,1.25fr) minmax(360px,.75fr);gap:24px;align-items:end;}
.eyebrow{display:inline-flex;gap:8px;align-items:center;font-size:13px;letter-spacing:.12em;color:var(--accent);font-weight:800;text-transform:uppercase;margin-bottom:10px;}
.eyebrow:before{content:"";width:34px;height:2px;background:var(--accent);display:block;}
h1{font-size:38px;line-height:1.16;margin:0 0 12px;font-weight:900;letter-spacing:-.02em;color:#3e2018;}
.lead{font-size:16px;line-height:1.85;color:#5f4f48;max-width:980px;margin:0;}
.hero-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px;}
.btn{border:1px solid var(--heavy);border-radius:999px;background:#fffdf9;color:var(--heavy);padding:10px 16px;font-weight:800;cursor:pointer;transition:.18s;display:inline-flex;align-items:center;gap:8px;text-decoration:none;font-size:14px;}
.btn:hover{transform:translateY(-1px);box-shadow:0 8px 18px rgba(66,35,20,.12);background:var(--heavy);color:#fffaf4;}
.btn.accent{background:var(--accent);color:#fffaf4;border-color:var(--accent);}
.quick{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;}
.quick-card{border:1px solid rgba(75,36,27,.16);background:rgba(255,255,255,.58);border-radius:18px;padding:16px;backdrop-filter:blur(6px);}
.quick-card b{display:block;font-size:24px;color:var(--accent);margin-bottom:2px;}
.quick-card span{font-size:12px;color:var(--muted);line-height:1.45;}
.toolbar{position:sticky;top:0;z-index:10;margin:0 0 20px;padding:12px;border:1px solid var(--line);border-radius:24px;background:rgba(255,250,244,.86);backdrop-filter:blur(18px);box-shadow:0 12px 30px rgba(66,35,20,.08);display:flex;gap:12px;align-items:center;}
.tabs{display:flex;gap:8px;overflow-x:auto;padding:2px;flex:1;}
.tab{white-space:nowrap;border:1px solid transparent;background:transparent;border-radius:999px;padding:10px 14px;font-weight:800;color:#5a453c;cursor:pointer;transition:.16s;font-size:14px;}
.tab:hover{background:#f2dfd0;}
.tab.active{background:#4b241b;color:#fffaf4;box-shadow:0 7px 18px rgba(75,36,27,.18);}
.search{min-width:270px;display:flex;align-items:center;gap:8px;border:1px solid var(--line);background:#fffdf9;border-radius:999px;padding:9px 12px;}
.search input{border:0;outline:0;background:transparent;width:100%;font-size:14px;color:var(--text);}
.panel{display:none;animation:fade .22s ease;}
.panel.active{display:block;}
@keyframes fade{from{opacity:.5;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
.section-title{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin:24px 0 14px;}
.section-title h2{margin:0;font-size:24px;color:#3e2018;}
.section-title p{margin:6px 0 0;color:var(--muted);line-height:1.7;max-width:920px;}
.grid{display:grid;gap:16px;}
.cols-2{grid-template-columns:repeat(2,minmax(0,1fr));}.cols-3{grid-template-columns:repeat(3,minmax(0,1fr));}.cols-4{grid-template-columns:repeat(4,minmax(0,1fr));}
.card{border:1px solid var(--line);background:var(--panel2);border-radius:var(--radius);padding:20px;box-shadow:0 8px 28px rgba(66,35,20,.06);}
.card h3{margin:0 0 10px;font-size:18px;color:#44231a;line-height:1.35;}
.card p{margin:0;color:#66564f;line-height:1.75;font-size:14px;}
.mini{font-size:12px;color:var(--muted);line-height:1.6;}
.metric{position:relative;overflow:hidden;padding:18px;border-radius:20px;border:1px solid rgba(75,36,27,.16);background:linear-gradient(180deg,#fffdf9,#f5e7dc);}
.metric .value{font-size:27px;font-weight:900;color:var(--accent);margin-bottom:5px;}
.metric .label{font-size:13px;font-weight:800;color:#4b241b;margin-bottom:4px;}
.metric .desc{font-size:12px;line-height:1.45;color:var(--muted);}
.pill-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;}
.pill{display:inline-flex;align-items:center;border:1px solid rgba(75,36,27,.18);border-radius:999px;padding:6px 10px;background:#fff8f2;color:#5a433b;font-size:12px;font-weight:700;}
.pill.red{background:#f5dfd9;color:#7d251a;border-color:#e7bdb4}.pill.green{background:#e8eadb;color:#536b49;border-color:#c6cdb4}.pill.blue{background:#e4ecf0;color:#405f75;border-color:#bfd0d9}.pill.purple{background:#e8e0ed;color:#5f4f72;border-color:#d0c3da}
.org-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;}
.org-card{border:1px solid var(--line);background:#fffdf9;border-radius:24px;padding:18px;position:relative;overflow:hidden;cursor:pointer;transition:.18s;min-height:270px;display:flex;flex-direction:column;}
.org-card:hover{transform:translateY(-3px);box-shadow:var(--shadow);border-color:#bd927c;}
.org-card:before{content:"";position:absolute;right:-40px;top:-48px;width:120px;height:120px;background:rgba(163,58,43,.08);border-radius:50%;}
.org-card .top{position:relative;display:flex;gap:12px;align-items:flex-start;margin-bottom:12px;}
.avatar{width:48px;height:48px;border-radius:50%;border:1px solid var(--heavy);display:grid;place-items:center;font-weight:900;color:#4b241b;background:#f4e0d1;flex:0 0 auto;}
.org-name{font-size:17px;font-weight:900;color:#3d2118;line-height:1.3;}.org-zh{font-size:12px;color:var(--muted);margin-top:3px;}
.org-card .summary{font-size:13px;line-height:1.7;color:#66564f;display:-webkit-box;-webkit-line-clamp:4;-webkit-box-orient:vertical;overflow:hidden;margin-bottom:12px;}
.org-meta{display:flex;flex-wrap:wrap;gap:7px;margin-top:auto;}
.badge{font-size:11px;font-weight:800;border-radius:999px;padding:5px 8px;border:1px solid rgba(75,36,27,.18);background:#f8ecdf;color:#5a433b;}
.badge.hot{background:#7d2e22;color:#fff;border-color:#7d2e22}.badge.ok{background:#607a55;color:#fff;border-color:#607a55}.badge.warn{background:#cf7a45;color:#fff;border-color:#cf7a45}
.timeline{position:relative;margin-top:14px;padding-left:24px;}
.timeline:before{content:"";position:absolute;left:8px;top:0;bottom:0;width:2px;background:linear-gradient(var(--accent),rgba(163,58,43,.1));}
.t-item{position:relative;border:1px solid var(--line);background:#fffdf9;border-radius:18px;margin:0 0 12px;padding:14px 15px;cursor:pointer;transition:.16s;}
.t-item:hover{transform:translateX(4px);box-shadow:0 8px 20px rgba(66,35,20,.08);}
.t-item:before{content:"";position:absolute;left:-22px;top:20px;width:11px;height:11px;border-radius:50%;background:var(--accent);border:3px solid #f8f1e8;}
.t-year{font-size:12px;font-weight:900;color:var(--accent);letter-spacing:.05em}.t-title{font-size:16px;font-weight:900;margin-top:2px;color:#422319}.t-meaning{font-size:13px;color:#66564f;margin-top:4px;line-height:1.55;}
table{width:100%;border-collapse:separate;border-spacing:0;overflow:hidden;border:1px solid var(--line);border-radius:18px;background:#fffdf9;}
th,td{padding:13px 14px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top;font-size:13px;line-height:1.6;}
th{background:#4b241b;color:#fffaf4;font-size:12px;letter-spacing:.04em;}
tr:last-child td{border-bottom:0} tr:nth-child(even) td{background:#fff7ef;}
.matrix-card{border:1px solid var(--line);border-radius:20px;background:#fffdf9;padding:16px;cursor:pointer;transition:.16s;}
.matrix-card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(66,35,20,.08);}
.matrix-card h3{font-size:16px;margin:0 0 6px;}.matrix-card .orgs{font-size:12px;color:var(--accent);font-weight:900;margin-bottom:8px;}.matrix-card p{font-size:13px;line-height:1.7;color:#66564f;}
.route{border-left:5px solid var(--accent);padding:18px;border-radius:18px;background:#fffdf9;border-top:1px solid var(--line);border-right:1px solid var(--line);border-bottom:1px solid var(--line);}
.route h3{margin:0 0 8px;font-size:18px;}.route .orgs{font-size:12px;font-weight:900;color:var(--accent);margin-bottom:8px;}.route p{font-size:13px;line-height:1.75;color:#66564f;}
.notice{border:1px dashed rgba(75,36,27,.35);background:#fff6ed;border-radius:22px;padding:18px;color:#5f4f48;line-height:1.75;}
.source-box{font-size:13px;line-height:1.8;color:#5f4f48;}
.modal-backdrop{position:fixed;inset:0;background:rgba(39,26,20,.48);display:none;z-index:90;align-items:center;justify-content:center;padding:24px;}
.modal-backdrop.open{display:flex;}
.modal{width:min(1060px,96vw);max-height:88vh;overflow:auto;border-radius:28px;background:#fffdf9;border:1px solid #c7a995;box-shadow:0 30px 100px rgba(0,0,0,.28);}
.modal-head{position:sticky;top:0;background:linear-gradient(135deg,#4b241b,#7c2f24);color:#fffaf4;padding:22px 26px;z-index:3;display:flex;gap:16px;align-items:flex-start;justify-content:space-between;}
.modal-title h2{font-size:24px;line-height:1.25;margin:0 0 4px;}.modal-title p{margin:0;color:#f5d5c7;line-height:1.5;}
.close{background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.3);width:40px;height:40px;border-radius:50%;font-size:22px;cursor:pointer;}
.modal-body{padding:24px 26px 30px;}
.detail-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:16px 0;}
.detail-box{border:1px solid var(--line);border-radius:18px;padding:14px;background:#fff8f1;}
.detail-box .k{font-size:12px;font-weight:900;color:var(--accent);margin-bottom:4px;}.detail-box .v{font-size:14px;color:#4e3f38;line-height:1.6;}
.modal-section{margin:22px 0 0;}.modal-section h3{font-size:18px;margin:0 0 10px;color:#44231a;border-bottom:1px solid var(--line);padding-bottom:8px;}
.list{margin:0;padding-left:18px;color:#5f4f48;line-height:1.8;font-size:14px;}.list li{margin:4px 0;}
.empty{padding:28px;border:1px dashed var(--line);border-radius:20px;text-align:center;color:var(--muted);background:#fffaf4;}
@media (max-width:1100px){.hero-inner{grid-template-columns:1fr}.quick{grid-template-columns:repeat(3,1fr)}.cols-4,.cols-3{grid-template-columns:repeat(2,1fr)}.org-grid{grid-template-columns:repeat(2,1fr)}.search{min-width:190px}}
@media (max-width:760px){.app{padding:14px}.hero{padding:24px}.toolbar{align-items:stretch;flex-direction:column}.search{min-width:0}.quick,.cols-4,.cols-3,.cols-2,.org-grid,.detail-grid{grid-template-columns:1fr}h1{font-size:28px}.modal-backdrop{padding:10px}.modal-head{padding:18px}.modal-body{padding:18px}}
</style>
</head>
<body>
<div class="app">
<header class="hero">
<div class="hero-inner">
<div>
<div class="eyebrow">Research Dashboard</div>
<h1 id="pageTitle"></h1>
<p class="lead" id="pageSubtitle"></p>
<div class="hero-actions">
<button class="btn accent" onclick="switchTab('orgs')">查看组织资料库</button>
<button class="btn" onclick="switchTab('china')">查看涉中涉台分化</button>
<button class="btn" onclick="switchTab('narrative')">查看叙事框架</button>
</div>
</div>
<div class="quick" id="quickMetrics"></div>
</div>
</header>
<nav class="toolbar">
<div class="tabs" id="tabs"></div>
<label class="search"><span>⌕</span><input id="globalSearch" placeholder="搜索组织、议题、路线、关键词..." /></label>
</nav>
<main>
<section id="panel-overview" class="panel active"></section>
<section id="panel-ecology" class="panel"></section>
<section id="panel-orgs" class="panel"></section>
<section id="panel-history" class="panel"></section>
<section id="panel-identity" class="panel"></section>
<section id="panel-issues" class="panel"></section>
<section id="panel-china" class="panel"></section>
<section id="panel-narrative" class="panel"></section>
</main>
</div>
<div class="modal-backdrop" id="modalBackdrop" onclick="backdropClose(event)">
<article class="modal" id="modal"></article>
</div>
<script id="DATA" type="application/json">{"meta": {"title": "美国左翼退伍军人与关联组织专题展示平台", "subtitle": "组织生态 · 历史谱系 · 人员身份 · 议题矩阵 · 涉中涉台分化 · 爱国主义重构", "version": "enhanced_v1.0", "source_basis": ["us_left_wing_veterans_skill/SKILL.md", "docs/full_reference_current_version.md", "docs/answer_bank_three_priority_questions.md", "examples/veterans_for_peace_china_taiwan_profile.md", "examples/votevets_common_defense_compare.md", "本轮对话中补充的8个栏目研究数据"], "safety_note": "本专题仅用于公开资料研究、组织生态分析、政策与舆论研究,不包含定向影响、骚扰、欺骗、操纵或针对个人的行动建议。"}, "overview": {"definition": "美国左翼退伍军人群体不是一个统一组织,而是一个以退伍军人身份为共同象征资源,围绕反战、进步派政治、民主保护、劳工、福利、身份平权、人权与难民、反新冷战等议题形成的松散议题生态。", "judgments": ["该群体更像“议题联盟”,不是统一组织群体。", "其影响力不主要来自人数,而来自退伍军人身份在美国政治文化中的象征权威。", "它们共同挑战“军人天然保守、爱国等于支持战争”的传统叙事。", "它们在VA福利、反永久战争、反威权、民主保护、军属支持、心理健康等议题上有交集。", "它们在涉中涉台、人权、对华竞争、台湾民主、美国军事介入问题上存在明显分歧。"], "metrics": [{"label": "2023年美国退伍军人", "value": "约1580万", "desc": "占18岁及以上平民人口约6.1%"}, {"label": "2023年女性退伍军人", "value": "约170万", "desc": "约占退伍军人总数10.9%"}, {"label": "VA预测FY2024", "value": "约1790万", "desc": "VA VetPop2023预测口径"}, {"label": "VA预测FY2053", "value": "约1120万", "desc": "总体人数下降,结构更女性化、多元化"}, {"label": "2024退伍军人选民", "value": "63%偏共和党", "desc": "左翼退伍军人属于少数派反叙事力量"}, {"label": "VFP分会", "value": "120+", "desc": "美国各州和多个国家拥有成员与分会"}], "layers": [{"name": "反战和平主义圈", "orgs": "Veterans For Peace、VVAW、About Face", "logic": "反永久战争、反军事主义、反军工复合体"}, {"name": "民主党进步派圈", "orgs": "VoteVets、Common Defense", "logic": "选举动员、反威权、保护民主、保护VA"}, {"name": "服务福利圈", "orgs": "Swords to Plowshares", "logic": "住房、医疗、就业、法律和退伍军人服务"}, {"name": "身份平权圈", "orgs": "Minority Veterans of America", "logic": "少数族裔、女性、LGBTQ+、残障退伍军人权益"}, {"name": "劳工/人权/军属外围圈", "orgs": "UVC、VFAI、MFSO、Gold Star Families", "logic": "工会、人权、难民、军属和阵亡家庭反战"}], "comparison": [{"dimension": "组织属性", "traditional": "全国会员组织、福利服务、纪念传统", "left": "PAC、501(c)(3)/(c)(4)、工会项目、服务组织、军属网络并存"}, {"dimension": "核心话语", "traditional": "服务国家、荣誉、纪念、福利", "left": "支持军人不等于支持战争;誓言没有过期;VA不是商品"}, {"dimension": "政治取向", "traditional": "整体更接近主流退伍军人政治,偏保守", "left": "内部差异大,但更偏反战、进步派、劳工与平权"}, {"dimension": "研究重点", "traditional": "主流退伍军人政治与福利", "left": "少数派如何争夺爱国主义、国家安全和军人形象解释权"}]}, "ecology": {"core_judgment": "美国左翼退伍军人生态由反战、选举、服务、身份平权、劳工、人权、军属和社会主义外围网络构成。共同点是使用退伍军人身份参与公共议题,差异在于法律形态、行动方式、对外政策和政治路线。", "layers": [{"name": "老牌反战和平主义", "representative": "Veterans For Peace、VVAW", "nature": "会员制、分会型、和平主义退伍军人组织", "issues": ["反战", "反军事化", "反核", "反军工", "反新冷战"], "value": "研究涉中涉台反介入路线的核心入口。"}, {"name": "后9·11反永久战争", "representative": "About Face", "nature": "后9·11军人与退伍军人运动组织", "issues": ["伊拉克", "阿富汗", "永久战争", "国内军事化"], "value": "代表年轻一代左翼退伍军人运动。"}, {"name": "民主党/进步派选举", "representative": "VoteVets、Common Defense", "nature": "PAC、501(c)(4)、基层运动平台", "issues": ["候选人背书", "国家安全叙事", "保护民主", "保护VA"], "value": "解释退伍军人身份如何成为民主党和进步派政治资源。"}, {"name": "服务福利型", "representative": "Swords to Plowshares", "nature": "退伍军人服务型非营利组织", "issues": ["住房", "就业", "医疗", "法律援助"Line truncated
<script>
const DATA = JSON.parse(document.getElementById('DATA').textContent);
const tabs = [
['overview','群体总览'],['ecology','组织生态'],['orgs','组织资料库'],['history','历史演化'],['identity','人员画像'],['issues','议题主张'],['china','涉中涉台'],['narrative','叙事重构']
];
function esc(s){return String(s ?? '').replace(/[&<>"']/g, m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function list(items, cls='list'){return `<ul class="${cls}">${(items||[]).map(x=>`<li>${esc(x)}</li>`).join('')}</ul>`}
function pills(items, color=''){return `<div class="pill-row">${(items||[]).map(x=>`<span class="pill ${color}">${esc(x)}</span>`).join('')}</div>`}
function kv(k,v){return `<div class="detail-box"><div class="k">${esc(k)}</div><div class="v">${esc(v)}</div></div>`}
function section(title, html){return `<div class="modal-section"><h3>${esc(title)}</h3>${html}</div>`}
function table(headers, rows){return `<table><thead><tr>${headers.map(h=>`<th>${esc(h)}</th>`).join('')}</tr></thead><tbody>${rows.map(r=>`<tr>${r.map(c=>`<td>${c}</td>`).join('')}</tr>`).join('')}</tbody></table>`}
function norm(s){return String(s||'').toLowerCase();}
function includesDeep(obj,q){return norm(JSON.stringify(obj)).includes(norm(q));}
function init(){
document.getElementById('pageTitle').textContent = DATA.meta.title;
document.getElementById('pageSubtitle').textContent = DATA.meta.subtitle + '。' + DATA.meta.safety_note;
document.getElementById('quickMetrics').innerHTML = DATA.overview.metrics.slice(0,4).map(m=>`<div class="quick-card"><b>${esc(m.value)}</b><span>${esc(m.label)}|${esc(m.desc)}</span></div>`).join('');
document.getElementById('tabs').innerHTML = tabs.map(([id,name])=>`<button class="tab ${id==='overview'?'active':''}" data-tab="${id}" onclick="switchTab('${id}')">${name}</button>`).join('');
renderAll();
document.getElementById('globalSearch').addEventListener('input', renderAll);
}
function switchTab(id){
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
document.getElementById('panel-'+id).classList.add('active');
document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('active',t.dataset.tab===id));
window.scrollTo({top:0,behavior:'smooth'});
}
function q(){return document.getElementById('globalSearch')?.value.trim() || ''}
function renderAll(){renderOverview();renderEcology();renderOrgs();renderHistory();renderIdentity();renderIssues();renderChina();renderNarrative();}
function renderOverview(){
const o = DATA.overview;
const rows = o.comparison.map(r=>[esc(r.dimension),esc(r.traditional),esc(r.left)]);
document.getElementById('panel-overview').innerHTML = `
<div class="section-title"><div><h2>群体总览与概念界定</h2><p>${esc(o.definition)}</p></div></div>
<div class="grid cols-3">${o.judgments.map(j=>`<div class="card"><h3>核心判断</h3><p>${esc(j)}</p></div>`).join('')}</div>
<div class="section-title"><div><h2>关键人口与政治背景</h2><p>这一部分用于说明:左翼退伍军人并非美国退伍军人主流,但其身份符号具有高传播权威。</p></div></div>
<div class="grid cols-3">${o.metrics.map(m=>`<div class="metric"><div class="value">${esc(m.value)}</div><div class="label">${esc(m.label)}</div><div class="desc">${esc(m.desc)}</div></div>`).join('')}</div>
<div class="section-title"><div><h2>议题联盟分层</h2><p>专题不把它们当成单一阵营,而是按议题、组织形态和行动方式分层展示。</p></div></div>
<div class="grid cols-3">${o.layers.map(l=>`<div class="card"><h3>${esc(l.name)}</h3><p><b>代表组织:</b>${esc(l.orgs)}</p><p><b>核心逻辑:</b>${esc(l.logic)}</p></div>`).join('')}</div>
<div class="section-title"><div><h2>与传统退伍军人组织的区别</h2></div></div>${table(['对比维度','传统退伍军人组织','左翼退伍军人生态'], rows)}
`;
}
function renderEcology(){
const e = DATA.ecology;
document.getElementById('panel-ecology').innerHTML = `
<div class="section-title"><div><h2>组织生态与类型分层</h2><p>${esc(e.core_judgment)}</p></div></div>
<div class="grid cols-3">${e.layers.map((l,i)=>`<div class="card"><h3>${esc(l.name)}</h3><p><b>代表组织:</b>${esc(l.representative)}</p><p><b>组织性质:</b>${esc(l.nature)}</p>${pills(l.issues,['red','blue','green'][i%3])}<p style="margin-top:12px"><b>研究价值:</b>${esc(l.value)}</p></div>`).join('')}</div>
<div class="section-title"><div><h2>组织关系类型</h2><p>这些关系可用于后续生成关系图谱,而不是简单的组织列表。</p></div></div>
<div class="grid cols-3">${e.relationship_types.map(r=>`<div class="card"><h3>${esc(r.type)}</h3><p>${esc(r.explain)}</p><div class="pill-row"><span class="pill red">${esc(r.example)}</span></div></div>`).join('')}</div>
<div class="section-title"><div><h2>关系网络文字版</h2></div></div>
<div class="notice"><b>核心反战谱系:</b>VVAW → Veterans For Peace → About Face,并与MFSO、Gold Star Families形成战争代价网络。<br><b>选举与进步派谱系:</b>VoteVets ↔ Common Defense,连接民主党候选人、国会、州级选举和基层动员。<br><b>服务与福利谱系:</b>Swords to Plowshares连接VA、地方政府、住房、法律和医疗网络。<br><b>人权与劳工谱系:</b>VFAI连接难民与人权,UVC连接工会、公共部门、VA就业与服务。</div>
`;
}
function renderOrgs(){
const query = q();
const orgs = DATA.organizations.filter(o=>!query || includesDeep(o,query));
document.getElementById('panel-orgs').innerHTML = `
<div class="section-title"><div><h2>核心组织资料库</h2><p>点击组织卡片进入结构化详情页。弹框不再展示JSON,而是按“基础资料—核心特征—议题—涉中涉台—叙事—研究重点—注意事项”展示。</p></div><span class="pill red">${orgs.length} 个结果</span></div>
<div class="org-grid">${orgs.map(o=>orgCard(o)).join('')}</div>
${orgs.length? '' : '<div class="empty">没有匹配的组织,请换一个关键词。</div>'}
`;
}
function orgCard(o){
const initials = o.abbr.split(/\s|\//)[0].slice(0,4);
const b = o.priority.includes('第一')?'hot':(o.priority.includes('第二')?'warn':'');
return `<div class="org-card" onclick="showOrg('${o.id}')"><div class="top"><div class="avatar">${esc(initials)}</div><div><div class="org-name">${esc(o.name)}</div><div class="org-zh">${esc(o.zh)}</div></div></div><div class="summary">${esc(o.positioning)}</div><div class="pill-row">${o.issues.slice(0,5).map(x=>`<span class="pill">${esc(x)}</span>`).join('')}</div><div class="org-meta"><span class="badge ${b}">${esc(o.priority)}</span><span class="badge ok">数据${esc(o.completeness)}</span><span class="badge">${esc(o.category)}</span></div></div>`
}
function showOrg(id){
const o = DATA.organizations.find(x=>x.id===id); if(!o) return;
openModal(`<div class="modal-head"><div class="modal-title"><h2>${esc(o.name)}</h2><p>${esc(o.zh)}|${esc(o.category)}|${esc(o.priority)}|数据完备度 ${esc(o.completeness)}</p></div><button class="close" onclick="closeModal()">×</button></div><div class="modal-body">
<div class="detail-grid">${kv('法律形态',o.legal)+kv('成立时间',o.founded)+kv('活动地区',o.area)+kv('规模口径',o.scale)}</div>
${section('基本定位', `<p>${esc(o.positioning)}</p>`)}
${section('核心特点', list(o.features))}
${section('核心议题', pills(o.issues,'red'))}
${section('涉中涉台与对外政策价值', `<p>${esc(o.china_taiwan)}</p>`)}
${section('政治叙事', `<p>${esc(o.narrative)}</p>`)}
${section('后续研究重点', list(o.research_focus))}
${section('研究注意事项', list(o.cautions))}
</div>`)
}
function renderHistory(){
const h = DATA.history;
document.getElementById('panel-history').innerHTML = `
<div class="section-title"><div><h2>历史演化与运动谱系</h2><p>${esc(h.core_judgment)}</p></div></div>
<div class="grid cols-3">${h.stages.map(s=>`<div class="card"><h3>${esc(s.name)}</h3><div class="pill-row"><span class="pill red">${esc(s.period)}</span>${s.orgs.map(o=>`<span class="pill">${esc(o)}</span>`).join('')}</div><p style="margin-top:12px">${esc(s.summary)}</p><p style="margin-top:8px"><b>核心叙事:</b>${esc(s.narrative)}</p></div>`).join('')}</div>
<div class="section-title"><div><h2>关键事件时间线</h2><p>点击事件查看历史意义和可入库字段。</p></div></div>
<div class="timeline">${h.events.map((ev,i)=>`<div class="t-item" onclick="showEvent(${i})"><div class="t-year">${esc(ev.year)}|${esc(ev.org)}</div><div class="t-title">${esc(ev.title)}</div><div class="t-meaning">${esc(ev.meaning)}</div></div>`).join('')}</div>
`;
}
function showEvent(i){const ev=DATA.history.events[i]; openModal(`<div class="modal-head"><div class="modal-title"><h2>${esc(ev.title)}</h2><p>${esc(ev.year)}|${esc(ev.org)}</p></div><button class="close" onclick="closeModal()">×</button></div><div class="modal-body">${section('阶段意义',`<p>${esc(ev.meaning)}</p>`)}${section('事件说明',`<p>${esc(ev.details)}</p>`)}${section('可入库字段', table(['字段','内容'], [['时间',esc(ev.year)],['组织',esc(ev.org)],['事件',esc(ev.title)],['意义',esc(ev.meaning)]]))}</div>`)}
function renderIdentity(){
const d = DATA.identity;
document.getElementById('panel-identity').innerHTML = `
<div class="section-title"><div><h2>人员结构与身份画像</h2><p>${esc(d.core_judgment)}</p></div></div>
<div class="grid cols-3">${d.metrics.map(m=>`<div class="metric"><div class="value">${esc(m.value)}</div><div class="label">${esc(m.label)}</div></div>`).join('')}</div>
<div class="section-title"><div><h2>重点人群分层</h2></div></div>
<div class="grid cols-3">${d.groups.map(g=>`<div class="card"><h3>${esc(g.name)}</h3><p><b>代表组织:</b>${esc(g.orgs)}</p><p><b>画像特征:</b>${esc(g.traits)}</p><p><b>议题倾向:</b>${esc(g.issues)}</p></div>`).join('')}</div>
<div class="section-title"><div><h2>心理结构</h2><p>这一部分用于解释为什么退伍军人身份能够转化为反战、福利、民主保护或身份平权叙事。</p></div></div>
<div class="grid cols-3">${d.psychology.map(p=>`<div class="card"><h3>${esc(p.name)}</h3><p>${esc(p.explain)}</p></div>`).join('')}</div>
`;
}
function renderIssues(){
const d = DATA.issues;
document.getElementById('panel-issues').innerHTML = `
<div class="section-title"><div><h2>核心议题与政策主张</h2><p>${esc(d.core_judgment)}</p></div></div>
<div class="notice"><b>最强共识议题:</b>${d.consensus.map(esc).join('、')}。<br><b>最易分歧议题:</b>${d.divisive.map(esc).join('、')}。</div>
<div class="section-title"><div><h2>组织—议题矩阵</h2><p>点击卡片查看该议题的组织分布、主张方向和分歧点。</p></div></div>
<div class="grid cols-3">${d.matrix.map((m,i)=>`<div class="matrix-card" onclick="showIssue(${i})"><h3>${esc(m.issue)}</h3><div class="orgs">${esc(m.orgs)}</div><p>${esc(m.position)}</p><div class="pill-row"><span class="pill ${m.consensus==='最高'?'green':m.consensus==='低'?'red':'blue'}">共识度:${esc(m.consensus)}</span></div></div>`).join('')}</div>
`;
}
function showIssue(i){const m=DATA.issues.matrix[i]; openModal(`<div class="modal-head"><div class="modal-title"><h2>${esc(m.issue)}</h2><p>${esc(m.orgs)}|共识度:${esc(m.consensus)}</p></div><button class="close" onclick="closeModal()">×</button></div><div class="modal-body">${section('主张方向',`<p>${esc(m.position)}</p>`)}${section('主要组织',`<p>${esc(m.orgs)}</p>`)}${section('潜在分歧',`<p>${esc(m.friction)}</p>`)}${section('页面展示建议',`<p>可在此议题下继续挂接组织声明、国会倡导、社媒发声、候选人广告、公开信和政策文件。</p>`)}</div>`)}
function renderChina(){
const d = DATA.china_taiwan;
document.getElementById('panel-china').innerHTML = `
<div class="section-title"><div><h2>涉中涉台与对外政策立场</h2><p>${esc(d.core_judgment)}</p></div></div>
<div class="grid cols-2">${d.routes.map(r=>`<div class="route"><h3>${esc(r.route)}</h3><div class="orgs">${esc(r.orgs)}</div><p><b>主要立场:</b>${esc(r.stance)}</p><p><b>重点事件:</b>${esc(r.events)}</p><p><b>研究风险:</b>${esc(r.risk)}</p></div>`).join('')}</div>
<div class="section-title"><div><h2>事件级数据库字段</h2><p>涉中涉台不能只按组织印象判断,后续应逐事件收集原文证据。</p></div></div>
${pills(d.event_fields,'blue')}
<div class="section-title"><div><h2>需要重点补充的事件类别</h2></div></div>
<div class="grid cols-3">${d.event_types.map(t=>`<div class="card"><h3>${esc(t.type)}</h3><p>${esc(t.need)}</p></div>`).join('')}</div>
`;
}
function renderNarrative(){
const d = DATA.narratives;
document.getElementById('panel-narrative').innerHTML = `
<div class="section-title"><div><h2>爱国主义重构与政治叙事</h2><p>${esc(d.core_judgment)}</p></div></div>
<div class="grid cols-3">${d.frames.map((f,i)=>`<div class="matrix-card" onclick="showNarrative(${i})"><h3>${esc(f.frame)}</h3><div class="orgs">${esc(f.orgs)}</div><p>${esc(f.meaning)}</p>${pills(f.keywords,'purple')}</div>`).join('')}</div>
<div class="section-title"><div><h2>传统爱国主义 vs 左翼退伍军人叙事</h2></div></div>
${table(['对比项','传统爱国主义叙事','左翼退伍军人叙事'], d.comparison.map(x=>[esc(x.dimension),esc(x.traditional),esc(x.left)]))}
<div class="section-title"><div><h2>社交媒体、认知安全、地方社会与趋势</h2><p>这些内容来自技能文件的扩展模块,用于增强页面信息密度。</p></div></div>
<div class="grid cols-3">
<div class="card"><h3>社交媒体生态</h3>${DATA.media_security_future.media_ecology.map(x=>`<p><b>${esc(x.type)}:</b>${esc(x.platforms)};${esc(x.style)}</p>`).join('')}</div>
<div class="card"><h3>认知安全风险</h3>${list(DATA.media_security_future.cognitive_security)}</div>
<div class="card"><h3>未来十年趋势</h3>${list(DATA.media_security_future.future_trends)}</div>
</div>
<div class="section-title"><div><h2>来源与核验提示</h2></div></div>
<div class="notice source-box">${DATA.sources.map(s=>`<b>${esc(s.name)}:</b>${s.items.map(esc).join('、')}`).join('<br>')}</div>
`;
}
function showNarrative(i){const f=DATA.narratives.frames[i]; openModal(`<div class="modal-head"><div class="modal-title"><h2>${esc(f.frame)}</h2><p>${esc(f.orgs)}</p></div><button class="close" onclick="closeModal()">×</button></div><div class="modal-body">${section('叙事含义',`<p>${esc(f.meaning)}</p>`)}${section('关键词',pills(f.keywords,'purple'))}${section('适合挂接的数据',`<p>可继续挂接组织口号、广告文本、官网使命表述、社媒高频词、演讲、公开信、活动海报和退伍军人证言。</p>`)}</div>`)}
function openModal(html){document.getElementById('modal').innerHTML = html; document.getElementById('modalBackdrop').classList.add('open'); document.body.style.overflow='hidden';}
function closeModal(){document.getElementById('modalBackdrop').classList.remove('open'); document.body.style.overflow='';}
function backdropClose(e){if(e.target.id==='modalBackdrop') closeModal();}
document.addEventListener('keydown',e=>{if(e.key==='Escape') closeModal();});
init();
</script>
</body>
</html>