Files
archify/scripts/guide-template.html

349 lines
32 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="archify guide [[ARCHIFY_VERSION]]">
<title>Archify Scenario Guide — Choose the Right Diagram</title>
<meta name="description" content="Describe the technical question you need to answer. Archify recommends a bounded diagram recipe, evidence checklist, and copy-ready prompt.">
<meta name="theme-color" content="#eff2f1">
<link rel="icon" type="image/svg+xml" href="assets/archify-mark.svg">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300;0,9..144,400;0,9..144,500;1,9..144,400&family=Inter:wght@400;450;500;600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/site-navigation.css">
<style>
:root {
/* Drafted-sheet palette — shared with the project page */
--paper:#eff2f1; --paper-raised:#f8faf9;
--ink:#15181b; --ink-soft:#363c41; --muted:#60686d; --dim:#929a9e;
--line:rgba(20,23,26,0.14); --line-hi:rgba(20,23,26,0.3);
--accent:#e14a0e; --accent-soft:rgba(225,74,14,0.08);
--plate:#0f1216;
--emerald:#047857; --rose:#be123c; --violet:#6d28d9;
--font-display:'Fraunces','Songti SC','STSong',Georgia,serif;
--font-body:'Inter',-apple-system,BlinkMacSystemFont,'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;
--font-mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
--ease-out:cubic-bezier(.22,1,.36,1);
}
*,*::before,*::after { margin:0; padding:0; box-sizing:border-box; }
html { scroll-behavior:smooth; }
body {
font-family:var(--font-body);
-webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
background:var(--paper); color:var(--ink); min-height:100vh; overflow-x:hidden;
}
body::after {
content:''; position:fixed; inset:0; z-index:1000; pointer-events:none;
opacity:.05; mix-blend-mode:multiply;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
}
::selection { background:rgba(225,74,14,.18); color:var(--ink); }
button,textarea { font:inherit; }
button { color:inherit; }
a { color:inherit; }
:focus-visible { outline:2px solid var(--accent); outline-offset:3px; border-radius:4px; }
.grid-bg {
position:fixed; inset:0; z-index:0; pointer-events:none;
background-image:linear-gradient(rgba(20,23,26,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(20,23,26,.045) 1px,transparent 1px);
background-size:56px 56px;
-webkit-mask-image:radial-gradient(ellipse 95% 62% at 50% -6%, black 12%, transparent 75%);
mask-image:radial-gradient(ellipse 95% 62% at 50% -6%, black 12%, transparent 75%);
}
.btn { display:inline-flex; align-items:center; gap:.5rem; padding:.55rem 1.15rem; border-radius:6px; font-family:var(--font-body); font-size:.8125rem; font-weight:500; letter-spacing:-.005em; cursor:pointer; text-decoration:none; transition:all .2s var(--ease-out); border:1px solid transparent; }
.btn-primary { background:var(--ink); color:var(--paper); }
.btn-primary:hover { background:#272c31; transform:translateY(-1px); }
.shell { position:relative; z-index:1; width:min(1180px,calc(100% - 48px)); margin:0 auto; }
/* HERO */
.hero { padding:10rem 0 4rem; display:grid; grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr); gap:4.5rem; align-items:end; }
.lead-label { display:flex; align-items:center; gap:.875rem; margin-bottom:1.5rem; font-family:var(--font-mono); font-size:.625rem; font-weight:500; letter-spacing:.2em; color:var(--muted); text-transform:uppercase; }
.lead-label::before { content:''; width:2.75rem; height:1px; background:var(--line-hi); }
h1 { max-width:770px; font-family:var(--font-display); font-optical-sizing:auto; font-size:clamp(2.5rem,5vw,4.25rem); font-weight:400; line-height:1.06; letter-spacing:-.028em; color:var(--ink); text-wrap:balance; }
h1 em { font-style:italic; color:var(--accent); }
.lede { max-width:680px; margin-top:1.5rem; color:var(--muted); font-size:.9375rem; line-height:1.72; text-wrap:pretty; }
.metrics { display:grid; gap:1px; background:var(--line); border:1px solid var(--line-hi); border-radius:12px; overflow:hidden; }
.metric { display:flex; justify-content:space-between; align-items:center; gap:1rem; padding:1rem 1.25rem; background:var(--paper-raised); }
.metric strong { font-family:var(--font-display); font-style:italic; font-optical-sizing:auto; font-weight:400; font-size:1.625rem; line-height:1; color:var(--ink); }
.metric span { color:var(--dim); font-family:var(--font-mono); font-size:.5625rem; letter-spacing:.14em; text-transform:uppercase; line-height:1.5; text-align:right; }
/* CHOOSER */
.chooser { padding:1.75rem 2rem 2rem; border:1px solid var(--line-hi); border-radius:14px; background:var(--paper-raised); position:relative; }
.chooser-head { display:flex; align-items:flex-start; justify-content:space-between; gap:1.25rem; margin-bottom:1.125rem; }
.chooser-head h2 { margin-bottom:.375rem; font-family:var(--font-display); font-optical-sizing:auto; font-size:1.5rem; font-weight:500; letter-spacing:-.02em; }
.chooser-head p { color:var(--muted); font-size:.8125rem; line-height:1.6; }
.kbd { flex:none; border:1px solid var(--line-hi); border-radius:5px; padding:.3rem .5rem; color:var(--dim); font-family:var(--font-mono); font-size:.5625rem; background:var(--paper); }
textarea { width:100%; min-height:104px; resize:vertical; border:1px solid var(--line-hi); border-radius:10px; padding:.9rem 1rem; background:var(--paper); color:var(--ink); outline:none; font-family:var(--font-mono); font-size:.75rem; line-height:1.7; transition:border-color .18s var(--ease-out),box-shadow .18s var(--ease-out); }
textarea::placeholder { color:var(--dim); }
textarea:focus { border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-soft); }
.sample-row { margin:.875rem 0 1.125rem; display:flex; gap:.45rem; flex-wrap:wrap; }
.chip { border:1px solid var(--line); border-radius:999px; background:var(--paper); color:var(--muted); padding:.45rem .75rem; font-family:var(--font-mono); font-size:.5625rem; letter-spacing:.06em; cursor:pointer; transition:all .18s var(--ease-out); }
.chip:hover { color:var(--accent); border-color:var(--accent); }
.actions { display:flex; align-items:center; gap:.625rem; }
.primary,.secondary { min-height:44px; border-radius:8px; padding:.65rem 1.15rem; border:1px solid transparent; font-size:.8125rem; font-weight:500; cursor:pointer; transition:all .2s var(--ease-out); }
.primary { background:var(--ink); color:var(--paper); border-color:var(--ink); }
.primary:hover { background:var(--accent); border-color:var(--accent); }
.secondary { background:transparent; border-color:var(--line-hi); color:var(--ink-soft); }
.secondary:hover { color:var(--ink); border-color:var(--ink); }
/* RESULT */
.result { margin-top:1.25rem; border:1px solid var(--line-hi); border-radius:14px; background:var(--paper-raised); overflow:hidden; display:none; }
.result.visible { display:block; animation:reveal .3s var(--ease-out); }
@keyframes reveal { from { opacity:0; transform:translateY(6px); } }
.result-main { padding:1.75rem 2rem; display:grid; grid-template-columns:minmax(0,1.1fr) minmax(260px,.9fr); gap:2rem; }
.result-kicker { color:var(--accent); font-family:var(--font-mono); font-size:.5625rem; font-weight:600; letter-spacing:.16em; text-transform:uppercase; }
.result h3 { margin:.5rem 0 .375rem; font-family:var(--font-display); font-optical-sizing:auto; font-size:1.625rem; font-weight:500; letter-spacing:-.02em; }
.result-question { color:var(--ink-soft); font-size:.875rem; font-weight:500; line-height:1.6; }
.result-summary { margin-top:.875rem; color:var(--muted); font-size:.8125rem; line-height:1.7; }
.proof-link { display:inline-flex; align-items:center; margin-top:1.125rem; padding:.5rem .75rem; border:1px solid rgba(4,120,87,.35); border-radius:999px; background:rgba(4,120,87,.06); color:var(--emerald); text-decoration:none; font-family:var(--font-mono); font-size:.5625rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase; transition:all .18s var(--ease-out); }
.proof-link:hover { border-color:var(--emerald); background:rgba(4,120,87,.1); }
.boundary { display:grid; gap:1rem; align-content:start; }
.boundary-item small { display:block; margin-bottom:.375rem; color:var(--emerald); font-family:var(--font-mono); font-size:.5625rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase; }
.boundary-item.avoid small { color:var(--rose); }
.boundary-item p { color:var(--muted); font-size:.8125rem; line-height:1.65; }
.result-grid { display:grid; grid-template-columns:minmax(230px,.7fr) minmax(0,1.3fr); border-top:1px solid var(--line); }
.checklist,.prompt-box { padding:1.5rem 2rem; }
.checklist { border-right:1px solid var(--line); }
.mini-heading { margin-bottom:.875rem; color:var(--dim); font-family:var(--font-mono); font-size:.5625rem; font-weight:500; letter-spacing:.16em; text-transform:uppercase; }
.checklist ul { list-style:none; display:grid; gap:.55rem; }
.checklist li { color:var(--muted); font-size:.75rem; line-height:1.55; display:flex; gap:.5rem; }
.checklist li::before { content:'✓'; color:var(--emerald); flex:0 0 auto; }
.presentation { display:flex; flex-wrap:wrap; gap:.4rem; margin-top:1.125rem; }
.tag { padding:.35rem .6rem; border-radius:999px; border:1px solid var(--line); background:var(--paper); color:var(--muted); font-family:var(--font-mono); font-size:.5625rem; letter-spacing:.06em; }
.prompt-bar { display:flex; align-items:center; justify-content:space-between; gap:.75rem; margin-bottom:.75rem; }
.copy { border:0; background:none; color:var(--accent); padding:0; font-family:var(--font-mono); font-size:.5625rem; font-weight:600; letter-spacing:.12em; text-transform:uppercase; cursor:pointer; }
.copy:hover { color:var(--ink); }
.prompt-text { color:var(--ink-soft); font-family:var(--font-mono); font-size:.6875rem; line-height:1.75; white-space:pre-wrap; }
.alternatives { padding:.9rem 2rem; border-top:1px solid var(--line); color:var(--dim); font-size:.75rem; }
.alternatives button { margin-left:.5rem; padding:0; border:0; background:none; color:var(--muted); cursor:pointer; font-size:.75rem; text-decoration:underline; text-underline-offset:3px; text-decoration-color:var(--line-hi); }
.alternatives button:hover { color:var(--accent); text-decoration-color:var(--accent); }
/* LIBRARY */
.library { padding:5rem 0 6rem; }
.section-head { display:flex; align-items:end; justify-content:space-between; gap:1.75rem; margin-bottom:1.75rem; }
.section-head h2 { margin-top:.5rem; font-family:var(--font-display); font-optical-sizing:auto; font-size:clamp(1.75rem,3vw,2.5rem); font-weight:400; letter-spacing:-.026em; }
.section-head p { max-width:440px; color:var(--muted); font-size:.8125rem; line-height:1.7; }
.filters { display:flex; flex-wrap:wrap; gap:.45rem; margin-bottom:1.25rem; }
.filter { border:1px solid var(--line-hi); border-radius:999px; background:transparent; color:var(--muted); padding:.5rem .85rem; font-family:var(--font-mono); font-size:.5625rem; font-weight:500; letter-spacing:.1em; text-transform:uppercase; cursor:pointer; transition:all .18s var(--ease-out); }
.filter:hover { color:var(--ink); border-color:var(--ink); }
.filter.active { color:var(--paper); background:var(--ink); border-color:var(--ink); }
.cards { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:14px; }
.card { min-width:0; min-height:206px; padding:1.375rem 1.5rem; text-align:left; border:1px solid var(--line-hi); border-radius:12px; background:var(--paper-raised); cursor:pointer; transition:transform .22s var(--ease-out),border-color .22s var(--ease-out),box-shadow .22s var(--ease-out); position:relative; font-family:inherit; }
.card:hover,.card:focus-visible { transform:translateY(-3px); border-color:var(--ink); box-shadow:0 14px 36px rgba(20,23,26,.1); outline:none; }
.card-type { color:var(--type-color,var(--accent)); font-family:var(--font-mono); font-size:.5625rem; font-weight:600; letter-spacing:.14em; text-transform:uppercase; }
.card h3 { margin:.875rem 0 .5rem; font-family:var(--font-display); font-optical-sizing:auto; font-size:1.125rem; font-weight:500; letter-spacing:-.015em; color:var(--ink); }
.card-question { color:var(--ink-soft); font-size:.75rem; line-height:1.6; }
.card-summary { margin:.75rem 0 0; color:var(--dim); font-size:.6875rem; line-height:1.6; }
.card-foot { position:absolute; left:1.5rem; right:1.5rem; bottom:1.125rem; display:flex; justify-content:space-between; gap:.75rem; color:var(--dim); font-family:var(--font-mono); font-size:.5625rem; letter-spacing:.06em; }
.card { padding-bottom:2.75rem; }
footer { border-top:1px solid var(--line); padding:1.75rem 0 2.5rem; color:var(--dim); font-family:var(--font-mono); font-size:.625rem; letter-spacing:.04em; }
footer .shell { display:flex; justify-content:space-between; gap:1rem; }
/* ENTRANCE */
@keyframes rise { from { opacity:0; transform:translateY(14px); } to { opacity:1; transform:none; } }
.hero, .chooser, .library { animation:rise .7s var(--ease-out) both; }
.chooser { animation-delay:.08s; }
.library { animation-delay:.16s; }
@media(max-width:900px) {
.hero { grid-template-columns:1fr; gap:2.25rem; padding-top:8.5rem; }
.metrics { grid-template-columns:repeat(3,1fr); }
.metric { display:grid; gap:.4rem; }
.metric span { text-align:left; }
.cards { grid-template-columns:repeat(2,minmax(0,1fr)); }
.result-main,.result-grid { grid-template-columns:1fr; }
.checklist { border-right:0; border-bottom:1px solid var(--line); }
}
@media(max-width:620px) {
.shell { width:min(100% - 28px,1180px); }
.hero { padding:7.5rem 0 2.5rem; }
h1 { font-size:2.2rem; }
.lede { font-size:.875rem; }
.metrics { grid-template-columns:1fr; }
.metric { display:flex; }
.metric span { text-align:right; }
.chooser { padding:1.25rem; }
.chooser-head .kbd { display:none; }
.sample-row { flex-wrap:nowrap; overflow:auto; padding-bottom:4px; }
.chip { white-space:nowrap; }
.result-main { padding:1.25rem; }
.checklist,.prompt-box { padding:1.125rem 1.25rem; }
.cards { grid-template-columns:1fr; }
.section-head { align-items:flex-start; flex-direction:column; }
.library { padding:3.5rem 0 4.5rem; }
footer .shell { flex-direction:column; }
}
@media (prefers-reduced-motion:reduce) {
html { scroll-behavior:auto; }
.hero, .chooser, .library, .result.visible { animation:none; }
}
</style>
</head>
<body>
<div class="grid-bg" aria-hidden="true"></div>
<nav class="site-nav" aria-label="Primary navigation">
<a class="nav-logo" href="./" aria-label="Archify home">
<img class="nav-logo-text brand-lockup" src="assets/archify-lockup-light.svg" alt="Archify" width="117" height="32">
<span class="nav-logo-path" data-en="/ guide" data-zh="/ 场景指南">/ guide</span>
</a>
<div class="nav-right">
<a class="nav-link" href="guide.html" aria-current="page" data-i18n="navGuide">Guide</a>
<a class="nav-link" href="gallery.html" data-i18n="navProof">Proof Lab</a>
<a class="nav-link" href="start.html" data-i18n="navStart">Start</a>
<a class="nav-link" href="https://github.com/tt-a1i/archify" target="_blank" rel="noopener">GitHub</a>
<button class="btn-lang" id="language" type="button" aria-label="Switch language">中文</button>
<a class="btn btn-primary nav-cta" href="start.html" data-i18n="navInstall">Install Skill</a>
</div>
</nav>
<main class="shell">
<section class="hero">
<div>
<div class="lead-label" data-i18n="eyebrow">Question-first diagramming</div>
<h1 data-i18n-html="headline">Choose the question.<br>Get the <em>right diagram.</em></h1>
<p class="lede" data-i18n="lede">Describe what your audience needs to understand. Archify recommends one bounded visual recipe—plus the evidence it must contain, when not to use it, and a prompt you can copy.</p>
</div>
<div class="metrics" aria-label="Guide metrics">
<div class="metric"><strong>[[RECIPE_COUNT]]</strong><span data-i18n-html="metricRecipes">real-world<br>recipes</span></div>
<div class="metric"><strong>5</strong><span data-i18n-html="metricModes">typed diagram<br>modes</span></div>
<div class="metric"><strong>0</strong><span data-i18n-html="metricRuntime">runtime<br>dependencies</span></div>
</div>
</section>
<section class="chooser" aria-labelledby="chooser-title">
<div class="chooser-head">
<div><h2 id="chooser-title" data-i18n="chooserTitle">What must the diagram explain?</h2><p data-i18n="chooserBody">Write a situation, not a diagram type. Specific system facts produce a stronger recommendation.</p></div>
<span class="kbd">⌘ / Ctrl + Enter</span>
</div>
<textarea id="scenario" data-i18n-placeholder="placeholder" placeholder="Example: Show an API request with JWT auth, a Redis cache miss, database fallback, and async tracing."></textarea>
<div class="sample-row" id="samples"></div>
<div class="actions">
<button class="primary" id="recommend" type="button" data-i18n="recommend">Recommend a recipe →</button>
<button class="secondary" id="clear" type="button" data-i18n="clear">Clear</button>
</div>
<div class="result" id="result" aria-live="polite"></div>
</section>
<section class="library" id="library">
<div class="section-head">
<div><div class="lead-label" data-i18n="libraryEyebrow">Recipe library</div><h2 data-i18n="libraryTitle">[[RECIPE_COUNT]] small, opinionated starting points.</h2></div>
<p data-i18n="libraryBody">Each recipe answers one technical question. That boundary keeps the result legible, reviewable, and honest about missing evidence.</p>
</div>
<div class="filters" id="filters" aria-label="Filter recipes"></div>
<div class="cards" id="cards"></div>
</section>
</main>
<footer><div class="shell"><span data-i18n="footerLeft">Generated from the same recipe source as the Archify CLI.</span><span>archify guide "your scenario"</span><span data-i18n="versionLabel">Scenario guide / stable / v[[ARCHIFY_VERSION]]</span></div></footer>
<script id="guide-data" type="application/json">[[GUIDE_JSON]]</script>
<script src="assets/site-language.js"></script>
<script>
(function () {
'use strict';
var recipes = JSON.parse(document.getElementById('guide-data').textContent);
var types = [[DIAGRAM_TYPES_JSON]];
var colors = { architecture:'#0891b2', workflow:'#047857', sequence:'#6d28d9', dataflow:'#b45309', lifecycle:'#be123c' };
var language = ArchifySiteLanguage.read();
var activeType = 'all';
var lastRecipe = null;
var copy = {
en: {
navGuide:'Guide',navProof:'Proof Lab',navStart:'Start',navInstall:'Install Skill',versionLabel:'Scenario guide / stable / v[[ARCHIFY_VERSION]]',eyebrow:'Question-first diagramming', headline:'Choose the question.<br>Get the <em>right diagram.</em>', lede:'Describe what your audience needs to understand. Archify recommends one bounded visual recipe—plus the evidence it must contain, when not to use it, and a prompt you can copy.',
metricRecipes:'real-world<br>recipes',metricModes:'typed diagram<br>modes',metricRuntime:'runtime<br>dependencies',chooserTitle:'What must the diagram explain?',chooserBody:'Write a situation, not a diagram type. Specific system facts produce a stronger recommendation.',placeholder:'Example: Show an API request with JWT auth, a Redis cache miss, database fallback, and async tracing.',recommend:'Recommend a recipe →',clear:'Clear',libraryEyebrow:'Recipe library',libraryTitle:'[[RECIPE_COUNT]] small, opinionated starting points.',libraryBody:'Each recipe answers one technical question. That boundary keeps the result legible, reviewable, and honest about missing evidence.',footerLeft:'Generated from the same recipe source as the Archify CLI.',all:'All recipes',recommended:'Recommended recipe',use:'Use when',avoid:'Avoid when',must:'Evidence to include',presentation:'Presentation',prompt:'Copy-ready prompt',copyPrompt:'Copy prompt',copied:'Copied',alternatives:'Other possible fits:',confidence:'confidence',open:'Open recipe',proofReady:'Verified proof',proofLink:'Open verified example ↗',
samples:[['API + cache miss','Show an API request with JWT auth, a Redis cache miss, database fallback, and async tracing.'],['Kafka + DLQ','Map Kafka topics, ordered processors, consumer groups, replay, state stores, and the dead-letter queue.'],['Incident response','Show how responders detect, triage, mitigate, escalate, communicate, and verify recovery.']]
},
zh: {
navGuide:'场景指南',navProof:'验证作品集',navStart:'快速上手',navInstall:'安装技能',versionLabel:'场景指南 / 稳定版 / v[[ARCHIFY_VERSION]]',eyebrow:'先问题,后图表',headline:'先选对问题,<br>再得到<em>对的图。</em>',lede:'描述受众真正需要理解的内容。Archify 会推荐一个有边界的视觉配方,同时给出证据清单、禁用条件和可复制提示词。',
metricRecipes:'个真实场景<br>配方',metricModes:'种类型化<br>图表模式',metricRuntime:'个运行时<br>依赖',chooserTitle:'这张图必须解释什么?',chooserBody:'写清场景,不要只写图表类型。系统事实越具体,推荐越可靠。',placeholder:'例如:展示带 JWT 鉴权、Redis 缓存未命中、数据库回退和异步追踪的 API 请求。',recommend:'推荐配方 →',clear:'清空',libraryEyebrow:'配方库',libraryTitle:'[[RECIPE_COUNT]] 个小而专的起点。',libraryBody:'每个配方只回答一个技术问题。清晰的边界让图更易读、可评审,也不会掩盖证据缺口。',footerLeft:'网页与 Archify CLI 使用同一份配方数据生成。',all:'全部配方',recommended:'推荐配方',use:'适合',avoid:'不要这样用',must:'必须包含的证据',presentation:'表现建议',prompt:'可直接复制的提示词',copyPrompt:'复制提示词',copied:'已复制',alternatives:'其他可能:',confidence:'置信度',open:'打开配方',proofReady:'已验证成品',proofLink:'打开验证成品 ↗',
samples:[['API + 缓存未命中','展示带 JWT 鉴权、Redis 缓存未命中、数据库回退和异步追踪的 API 请求。'],['Kafka + 死信','梳理 Kafka Topic、有序处理器、消费者组、重放、状态存储和死信队列。'],['事故处置','展示响应者如何发现、分诊、缓解、升级、沟通并验证恢复。']]
}
};
function t(key) { return copy[language][key]; }
function local(recipe) { return Object.assign({}, recipe, recipe[language]); }
function normalize(value) { return String(value || '').normalize('NFKC').toLowerCase().replace(/[\s_]+/g,' ').trim(); }
function rank(query) {
var text = normalize(query);
return recipes.map(function (recipe,index) {
var score = 0, matched = [];
if (text === recipe.id || text === recipe.id.replace(/-/g,' ')) { score = 100; matched = [recipe.id]; }
else recipe.signals.forEach(function (signal) { if (text.includes(normalize(signal[0]))) { score += signal[1]; matched.push(signal[0]); } });
return { recipe:recipe, score:score, matched:matched, index:index };
}).sort(function (a,b) { return b.score - a.score || a.index - b.index; });
}
function recommendation(query) {
var ranked = rank(query), winner = ranked[0].score > 0 ? ranked[0] : { recipe:recipes[0], score:0, matched:[] };
return { recipe:winner.recipe, confidence:winner.score >= 14 ? 'high' : winner.score >= 7 ? 'medium' : 'low', alternatives:ranked.filter(function (entry) { return entry.recipe.id !== winner.recipe.id && entry.score > 0; }).slice(0,2) };
}
function escapeHtml(value) { var node = document.createElement('span'); node.textContent = String(value); return node.innerHTML; }
function renderSamples() {
document.getElementById('samples').innerHTML = t('samples').map(function (sample) { return '<button class="chip" type="button" data-query="'+escapeHtml(sample[1])+'">'+escapeHtml(sample[0])+'</button>'; }).join('');
}
function renderFilters() {
var labels = [[DIAGRAM_TYPE_LABELS_JSON]];
document.getElementById('filters').innerHTML = ['all'].concat(types).map(function (type) { return '<button class="filter '+(activeType === type ? 'active':'')+'" type="button" data-filter="'+type+'">'+escapeHtml(type === 'all' ? t('all') : labels[language][type])+'</button>'; }).join('');
}
function renderCards() {
var visible = recipes.filter(function (recipe) { return activeType === 'all' || recipe.type === activeType; });
document.getElementById('cards').innerHTML = visible.map(function (raw) {
var recipe = local(raw);
return '<button class="card" type="button" data-recipe="'+recipe.id+'" style="--type-color:'+colors[recipe.type]+'"><span class="card-type">'+escapeHtml(recipe.type)+'</span><h3>'+escapeHtml(recipe.title)+'</h3><p class="card-question">'+escapeHtml(recipe.question)+'</p><p class="card-summary">'+escapeHtml(recipe.summary)+'</p><span class="card-foot"><span>'+escapeHtml(recipe.presentation.preset)+' · '+escapeHtml(recipe.presentation.motion)+'</span><span>'+escapeHtml(recipe.proof ? t('proofReady') : t('open'))+' ↗</span></span></button>';
}).join('');
}
function renderResult(rawRecipe, confidence, alternatives) {
var recipe = local(rawRecipe);
lastRecipe = rawRecipe;
var alt = alternatives || [];
var html = '<div class="result-main"><div><span class="result-kicker">'+escapeHtml(t('recommended'))+' · '+escapeHtml(confidence)+' '+escapeHtml(t('confidence'))+'</span><h3>'+escapeHtml(recipe.title)+'</h3><p class="result-question">'+escapeHtml(recipe.question)+'</p><p class="result-summary">'+escapeHtml(recipe.summary)+'</p>'+(recipe.proof ? '<a class="proof-link" href="gallery.html#proof-'+encodeURIComponent(recipe.proof)+'">'+escapeHtml(t('proofLink'))+'</a>' : '')+'</div><div class="boundary"><div class="boundary-item"><small>'+escapeHtml(t('use'))+'</small><p>'+escapeHtml(recipe.useWhen)+'</p></div><div class="boundary-item avoid"><small>'+escapeHtml(t('avoid'))+'</small><p>'+escapeHtml(recipe.avoidWhen)+'</p></div></div></div>';
html += '<div class="result-grid"><div class="checklist"><div class="mini-heading">'+escapeHtml(t('must'))+'</div><ul>'+recipe.include.map(function (item) { return '<li>'+escapeHtml(item)+'</li>'; }).join('')+'</ul><div class="presentation"><span class="tag">'+escapeHtml(recipe.type)+'</span><span class="tag">'+escapeHtml(recipe.presentation.preset)+'</span><span class="tag">'+escapeHtml(recipe.presentation.motion)+'</span></div></div>';
html += '<div class="prompt-box"><div class="prompt-bar"><div class="mini-heading" style="margin:0">'+escapeHtml(t('prompt'))+'</div><button class="copy" id="copy-prompt" type="button">'+escapeHtml(t('copyPrompt'))+'</button></div><p class="prompt-text">'+escapeHtml(recipe.prompt)+'</p></div></div>';
if (alt.length) html += '<div class="alternatives">'+escapeHtml(t('alternatives'))+alt.map(function (entry) { var item=local(entry.recipe); return '<button type="button" data-alt="'+item.id+'">'+escapeHtml(item.title)+' ['+item.type+']</button>'; }).join('')+'</div>';
var result = document.getElementById('result');
result.innerHTML = html;
result.classList.add('visible');
}
function runRecommendation() {
var query = document.getElementById('scenario').value.trim();
if (!query) { document.getElementById('scenario').focus(); return; }
var picked = recommendation(query);
renderResult(picked.recipe,picked.confidence,picked.alternatives);
}
function applyLanguage(next) {
language = ArchifySiteLanguage.write(next);
document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
document.getElementById('language').textContent = language === 'zh' ? 'EN' : '中文';
document.getElementById('language').setAttribute('aria-label', language === 'zh' ? 'Switch to English' : '切换到中文');
document.querySelectorAll('[data-en][data-zh]').forEach(function (node) { node.textContent=node.getAttribute(language === 'zh' ? 'data-zh' : 'data-en'); });
document.querySelectorAll('[data-i18n]').forEach(function (node) { node.textContent=t(node.dataset.i18n); });
document.querySelectorAll('[data-i18n-html]').forEach(function (node) { node.innerHTML=t(node.dataset.i18nHtml); });
document.querySelectorAll('[data-i18n-placeholder]').forEach(function (node) { node.placeholder=t(node.dataset.i18nPlaceholder); });
renderSamples(); renderFilters(); renderCards();
if (lastRecipe) renderResult(lastRecipe,'selected',[]);
}
async function copyPrompt() {
if (!lastRecipe) return;
var text = local(lastRecipe).prompt, button = document.getElementById('copy-prompt');
try { await navigator.clipboard.writeText(text); } catch (_) {
var area=document.createElement('textarea'); area.value=text; document.body.appendChild(area); area.select(); document.execCommand('copy'); area.remove();
}
button.textContent=t('copied'); setTimeout(function () { if (button.isConnected) button.textContent=t('copyPrompt'); },1200);
}
document.getElementById('recommend').addEventListener('click',runRecommendation);
document.getElementById('clear').addEventListener('click',function () { document.getElementById('scenario').value=''; document.getElementById('result').classList.remove('visible'); lastRecipe=null; });
document.getElementById('language').addEventListener('click',function () { applyLanguage(language === 'en' ? 'zh':'en'); });
document.getElementById('scenario').addEventListener('keydown',function (event) { if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') runRecommendation(); });
document.getElementById('samples').addEventListener('click',function (event) { var chip=event.target.closest('[data-query]'); if (!chip) return; document.getElementById('scenario').value=chip.dataset.query; runRecommendation(); });
document.getElementById('filters').addEventListener('click',function (event) { var filter=event.target.closest('[data-filter]'); if (!filter) return; activeType=filter.dataset.filter; renderFilters(); renderCards(); });
document.getElementById('cards').addEventListener('click',function (event) { var card=event.target.closest('[data-recipe]'); if (!card) return; var recipe=recipes.find(function (item) { return item.id === card.dataset.recipe; }); renderResult(recipe,'selected',[]); document.getElementById('result').scrollIntoView({behavior:'smooth',block:'center'}); });
document.getElementById('result').addEventListener('click',function (event) { if (event.target.id === 'copy-prompt') copyPrompt(); var alt=event.target.closest('[data-alt]'); if (alt) renderResult(recipes.find(function (item) { return item.id === alt.dataset.alt; }),'selected',[]); });
applyLanguage(language);
}());
</script>
</body>
</html>