@antoneeo/mkt-agentic-sdlc-skill 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1127 @@
1
+ /* Docs site — the page. A classic script: a page opened from disk cannot fetch,
2
+ so the generator writes its data as scripts this page loads. Every word comes
3
+ from strings.js; text from the project is inserted as text, and document
4
+ bodies arrive already formatted and sanitized by the generator. The only
5
+ action anywhere is opening a document: filters, search and view switches
6
+ change nothing. */
7
+ (function () {
8
+ "use strict";
9
+
10
+ var S = window.DOCS_SITE_STRINGS || {};
11
+ var C = window.DOCS_SITE_CORE;
12
+ var D = null; // documents, loaded on first use
13
+ var K = null; // knowledge base, loaded on first use
14
+ var app = document.getElementById("app");
15
+ var ORIGINS = ["given", "elicited", "derived", "ruling", "imported"];
16
+
17
+ // ------------------------------------------------------------- helpers
18
+
19
+ function t(key, params) {
20
+ var s = Object.prototype.hasOwnProperty.call(S, key) ? S[key] : key;
21
+ if (params) {
22
+ Object.keys(params).forEach(function (k) {
23
+ s = s.split("{" + k + "}").join(String(params[k]));
24
+ });
25
+ }
26
+ return s;
27
+ }
28
+
29
+ function el(tag, attrs) {
30
+ var node = document.createElement(tag);
31
+ if (attrs) {
32
+ Object.keys(attrs).forEach(function (k) {
33
+ var v = attrs[k];
34
+ if (v === null || v === undefined || v === false) return;
35
+ if (k === "class") node.className = v;
36
+ else if (k === "text") node.textContent = v;
37
+ else if (k.slice(0, 2) === "on") node.addEventListener(k.slice(2), v);
38
+ else node.setAttribute(k, v === true ? "" : v);
39
+ });
40
+ }
41
+ for (var i = 2; i < arguments.length; i++) add(node, arguments[i]);
42
+ return node;
43
+ }
44
+
45
+ function add(node, child) {
46
+ if (child === null || child === undefined || child === false) return;
47
+ if (Array.isArray(child)) { child.forEach(function (c) { add(node, c); }); return; }
48
+ node.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
49
+ }
50
+
51
+ function docHref(path) { return "#/doc/" + encodeURI(path); }
52
+ function link(href, text, cls) { return el("a", { href: href, "class": cls }, text); }
53
+ function docLink(path, text) { return link(docHref(path), text || path); }
54
+
55
+ function normalize(s) {
56
+ s = String(s || "").normalize("NFKD").replace(/\p{Mn}/gu, "");
57
+ s = s.toLowerCase();
58
+ s = s.replace(/[‐-―−﹘﹣-]/g, "-");
59
+ s = s.replace(/[­​-‏⁠-⁤]/g, "");
60
+ return s.replace(/\s+/g, " ").trim();
61
+ }
62
+
63
+ function written(v) { return v !== undefined && v !== null && String(v).trim() !== "" && String(v).trim() !== "-"; }
64
+
65
+ var pending = {};
66
+
67
+ function loadScript(src, ready) {
68
+ if (pending[src]) { pending[src].push(ready); return; }
69
+ pending[src] = [ready];
70
+ var s = document.createElement("script");
71
+ s.src = src;
72
+ s.onload = function () { var q = pending[src]; pending[src] = null; q.forEach(function (f) { f(); }); };
73
+ s.onerror = function () { fail(); };
74
+ document.body.appendChild(s);
75
+ }
76
+
77
+ function needDocs(then) {
78
+ if (D) return then();
79
+ main(el("p", { "class": "muted", text: t("loading_documents") }));
80
+ loadScript("data/docs.js", function () {
81
+ D = window.DOCS_SITE_DOCS;
82
+ if (!D) return fail();
83
+ then();
84
+ });
85
+ }
86
+
87
+ function needKb(then) {
88
+ if (K) return then();
89
+ if (!C.meta.has_kb) return notFound();
90
+ main(el("p", { "class": "muted", text: t("loading_knowledge") }));
91
+ loadScript("data/kb.js", function () {
92
+ K = window.DOCS_SITE_KB;
93
+ if (!K) return fail();
94
+ then();
95
+ });
96
+ }
97
+
98
+ function html(target, markup) {
99
+ target.innerHTML = markup; // produced by the generator's sanitizer only
100
+ Array.prototype.forEach.call(target.querySelectorAll("code.notfact"), function (c) {
101
+ c.title = t("not_fact_id");
102
+ });
103
+ Array.prototype.forEach.call(target.querySelectorAll('a[href^="#"]:not([href^="#/"])'), function (a) {
104
+ a.addEventListener("click", function (e) {
105
+ e.preventDefault();
106
+ var id = decodeURIComponent(a.getAttribute("href").slice(1));
107
+ var dest = document.getElementById(id);
108
+ if (dest) dest.scrollIntoView({ behavior: "smooth" });
109
+ });
110
+ });
111
+ return target;
112
+ }
113
+
114
+ function levelLabel(v) {
115
+ return Object.prototype.hasOwnProperty.call(S, "level_" + v) ? t("level_" + v) : v;
116
+ }
117
+
118
+ function statusPill(status, group) {
119
+ return el("span", { "class": "pill g" + group, title: t("group_" + group) }, status || t("group_" + group));
120
+ }
121
+
122
+ function originClass(o) { return ORIGINS.indexOf(o) >= 0 ? o : "other"; }
123
+
124
+ function originLabel(o) {
125
+ return ORIGINS.indexOf(o) >= 0 ? t("origin_" + o) : t("origin_other", { v: o });
126
+ }
127
+
128
+ function originWhy(o) { return ORIGINS.indexOf(o) >= 0 ? t("origin_" + o + "_why") : t("origin_other_why"); }
129
+
130
+ // ------------------------------------------------------------ the frame
131
+
132
+ var mainEl, panelEl = null, navLinks = {};
133
+
134
+ function frame() {
135
+ var nav = el("nav");
136
+ var items = [["home", "#/", "nav_home"], ["chronology", "#/chronology", "nav_chronology"],
137
+ ["work", "#/work", "nav_work"]];
138
+ if (C.meta.units_page) items.push(["units", "#/units", "nav_" + C.meta.units_page]);
139
+ items.push(["rules", "#/rules", "nav_rules"], ["guides", "#/guides", "nav_guides"], ["documents", "#/documents", "nav_documents"]);
140
+ if (C.meta.has_kb) items.push(["map", "#/map", "nav_map"]);
141
+ if (C.meta.has_sources) items.push(["sources", "#/sources", "nav_sources"]);
142
+ items.forEach(function (it) {
143
+ navLinks[it[0]] = link(it[1], t(it[2]));
144
+ if (it[0] === "map") {
145
+ // the side bar's Map always opens the topics view
146
+ navLinks.map.addEventListener("click", function () {
147
+ mapView = "topics";
148
+ if (location.hash === "#/map") pageMap();
149
+ });
150
+ }
151
+ nav.appendChild(navLinks[it[0]]);
152
+ });
153
+ var stamp = C.meta.git
154
+ ? t("stamp_git", { date: C.meta.generated, commit: C.meta.commit }) +
155
+ (C.meta.changed === 1 ? t("stamp_changed_one")
156
+ : C.meta.changed > 1 ? t("stamp_changed_many", { n: C.meta.changed }) : "")
157
+ : t("stamp_no_git", { date: C.meta.generated });
158
+ var rail = el("aside", { "class": "rail" },
159
+ el("div", { "class": "brand" }, el("div", { "class": "kicker", text: t("site_name") }),
160
+ el("div", { "class": "name", text: C.home.title || t("site_name") })),
161
+ nav,
162
+ el("button", { "class": "searchbtn", type: "button", onclick: openSearch },
163
+ el("span", { text: t("nav_search") }), el("kbd", { text: t("search_hint") })),
164
+ el("div", { "class": "stamp", text: stamp }));
165
+ mainEl = el("main");
166
+ app.textContent = "";
167
+ add(app, [rail, mainEl]);
168
+ document.title = (C.home.title ? C.home.title + " — " : "") + t("site_name");
169
+ }
170
+
171
+ function main() {
172
+ mainEl.className = "";
173
+ mainEl.textContent = "";
174
+ for (var i = 0; i < arguments.length; i++) add(mainEl, arguments[i]);
175
+ window.scrollTo(0, 0);
176
+ }
177
+
178
+ function setNav(key) {
179
+ Object.keys(navLinks).forEach(function (k) { navLinks[k].className = k === key ? "on" : ""; });
180
+ }
181
+
182
+ function fail() {
183
+ app.textContent = "";
184
+ add(app, el("main", null, el("p", { "class": "empty", text: t("incomplete") })));
185
+ }
186
+
187
+ function notFound() {
188
+ main(el("p", { "class": "empty" }, t("not_found"), " ", link("#/", t("back_home"))));
189
+ }
190
+
191
+ function heading(title, why) {
192
+ return [el("h1", { text: title }), why ? el("p", { "class": "why", text: why }) : null];
193
+ }
194
+
195
+ // ------------------------------------------------------------- the pages
196
+
197
+ function eventList(events) {
198
+ return el("ul", { "class": "events" }, events.map(function (e) {
199
+ return el("li", { "class": "event" },
200
+ el("span", { "class": "date", text: e.date }),
201
+ el("span", null, el("div", { "class": "kind", text: t("event_" + e.kind) }),
202
+ e.source && C.meta.has_kb ? link("#/source/" + encodeURIComponent(e.source), e.label)
203
+ : e.path ? docLink(e.path, e.label) : e.label));
204
+ }));
205
+ }
206
+
207
+ function pageHome() {
208
+ var h = C.home;
209
+ setNav("home");
210
+ var northStar = h.vision && h.vision.north_star
211
+ ? el("section", { "class": "north-star" },
212
+ el("h2", { text: h.vision.section_title }),
213
+ el("p", { "class": "why", text: t("home_north_star_why") }),
214
+ html(el("div", { "class": "prose" }), h.vision.section_html),
215
+ el("div", { "class": "links" },
216
+ el("span", { "class": "status", text: t("home_vision_status", { status: h.vision.status || "—" }) }),
217
+ docLink(h.vision.path, t("home_vision"))))
218
+ : null;
219
+ var project = el("section", { "class": "block" },
220
+ el("h2", { text: t("home_project") }),
221
+ el("p", { "class": "why", text: t("home_project_why") }),
222
+ h.vision
223
+ ? el("div", { "class": "vision-box" },
224
+ el("div", { "class": "status", text: t("home_vision_status", { status: h.vision.status || "—" }) }),
225
+ !h.vision.north_star && h.vision.section_title ? el("h3", { text: h.vision.section_title }) : null,
226
+ !h.vision.north_star ? html(el("div", { "class": "prose" }), h.vision.section_html) : null,
227
+ docLink(h.vision.path, t("home_vision")))
228
+ : el("p", { "class": "muted", text: t("home_vision_none") }),
229
+ el("div", { "class": "links" },
230
+ h.links.readme ? docLink(h.links.readme, t("home_readme")) : null,
231
+ link("#/rules", t("home_rules")),
232
+ h.links.manifest ? docLink(h.links.manifest, t("home_manifest")) : null,
233
+ h.links.router ? docLink(h.links.router, t("home_router")) : null));
234
+ var lately = el("section", { "class": "block" },
235
+ el("h2", null, link("#/chronology", t("home_lately"))),
236
+ el("p", { "class": "why", text: t("home_lately_why") }),
237
+ h.lately.length ? eventList(h.lately) : el("p", { "class": "muted", text: t("chronology_empty") }),
238
+ link("#/chronology", t("home_all_chronology"), "more"));
239
+ var open = el("section", { "class": "block" },
240
+ el("h2", null, link("#/work", t("home_open"))),
241
+ el("p", { "class": "why", text: t("home_open_why") }),
242
+ h.open.length
243
+ ? el("ul", null, h.open.map(function (c) {
244
+ return el("li", null, docLink(c.path, c.name), " ", c.status ? el("span", { "class": "pill", text: c.status }) : null,
245
+ c.next ? el("div", { "class": "muted", text: c.next }) : null);
246
+ }))
247
+ : el("p", { "class": "muted", text: t("work_empty") }),
248
+ el("div", { "class": "links" },
249
+ link("#/work", t("home_all_work")),
250
+ h.links.notes ? docLink(h.links.notes, t("home_notes")) : null,
251
+ h.links.register ? docLink(h.links.register, t("home_register")) : null));
252
+ var known = el("section", { "class": "block" },
253
+ el("h2", null, C.meta.has_kb ? link("#/map", t("home_known")) : t("home_known")),
254
+ el("p", { "class": "why", text: t("home_known_why") }),
255
+ h.known.length
256
+ ? h.known.map(function (a) {
257
+ return el("div", { "class": "area-row" }, link("#/topic/" + encodeURIComponent(a.slug), a.name, "area"),
258
+ el("div", { "class": "chips" }, a.topics.map(function (s) {
259
+ return link("#/topic/" + encodeURIComponent(s), s, "chip");
260
+ })));
261
+ })
262
+ : el("p", { "class": "muted", text: t("home_known_none") }),
263
+ C.meta.has_kb ? link("#/map", t("home_map"), "more") : null);
264
+ var head = el("div", { "class": "home-head" },
265
+ el("h1", { text: h.title || t("home_no_title") }),
266
+ html(el("div", { "class": "intro" }), h.intro_html));
267
+ main(head, northStar, el("div", { "class": "quad" }, project, lately, open, known));
268
+ }
269
+
270
+ function pageChronology() {
271
+ setNav("chronology");
272
+ main(heading(t("chronology_title"), t("chronology_why")),
273
+ C.chronology.length ? eventList(C.chronology) : el("p", { "class": "empty", text: t("chronology_empty") }));
274
+ }
275
+
276
+ function pageWork() {
277
+ setNav("work");
278
+ var w = C.work;
279
+ var cards = w.cards.length
280
+ ? el("div", { "class": "cards" }, w.cards.map(function (c) {
281
+ var rows = [];
282
+ function row(label, value) { if (written(value)) rows.push(el("dt", { text: t(label) }), el("dd", null, value)); }
283
+ row("work_status", c.status ? statusPill(c.status, c.group) : null);
284
+ row("work_level", c.level ? levelLabel(c.level) : null);
285
+ row("work_since", c.since);
286
+ row("work_updated", c.updated);
287
+ row("work_branch", c.branch ? el("code", { text: c.branch }) : null);
288
+ return el("article", { "class": "card" },
289
+ el("h3", { text: c.name }),
290
+ el("dl", null, rows),
291
+ c.next ? el("div", { "class": "next" }, el("b", { text: t("work_next") }), c.next) : null,
292
+ el("div", { "class": "links" }, docLink(c.path, t("work_resume"))));
293
+ }))
294
+ : el("p", { "class": "empty", text: t("work_empty") });
295
+ main(heading(t("work_title"), t("work_why")), cards,
296
+ w.notes ? [el("h2", { text: t("work_notes") }), el("p", { "class": "why", text: t("work_notes_why") }),
297
+ html(el("div", { "class": "prose prose-box" }), w.notes.html)] : null,
298
+ w.register ? el("p", null, docLink(w.register, t("work_register"))) : null);
299
+ }
300
+
301
+ var unitsView = "columns", unitsLens = "";
302
+
303
+ function pageUnits() {
304
+ if (!C.meta.units_page) return notFound();
305
+ setNav("units");
306
+ var units = C.units || [];
307
+ var title = t("units_" + C.meta.units_page);
308
+ if (!units.length) return main(heading(title, t("units_why")), el("p", { "class": "empty", text: t("units_empty") }));
309
+ var lenses = units.map(function (u) { return u.lens; }).filter(function (v, i, a) { return a.indexOf(v) === i; }).sort();
310
+ var shown = units.filter(function (u) { return !unitsLens || u.lens === unitsLens; });
311
+ function lensName(l) { return Object.prototype.hasOwnProperty.call(S, "lens_" + l) ? t("lens_" + l) : l; }
312
+ function dates(u) { return [u.start, u.end].filter(written).join(" → "); }
313
+ var bar = el("div", { "class": "toolbar" },
314
+ el("span", { "class": "seg" },
315
+ el("button", { type: "button", "class": unitsView === "columns" ? "on" : "", onclick: function () { unitsView = "columns"; pageUnits(); } }, t("units_columns")),
316
+ el("button", { type: "button", "class": unitsView === "list" ? "on" : "", onclick: function () { unitsView = "list"; pageUnits(); } }, t("units_list"))),
317
+ lenses.length > 1
318
+ ? el("label", null, t("units_lens"), " ",
319
+ el("select", { onchange: function (e) { unitsLens = e.target.value; pageUnits(); } },
320
+ el("option", { value: "", text: t("units_lens_all") }),
321
+ lenses.map(function (l) { return el("option", { value: l, text: lensName(l), selected: l === unitsLens }); })))
322
+ : null);
323
+ var body;
324
+ if (unitsView === "columns") {
325
+ var groups = [];
326
+ shown.forEach(function (u) { if (groups.indexOf(u.group) < 0) groups.push(u.group); });
327
+ groups.sort(function (a, b) { return a - b; });
328
+ body = el("div", { "class": "columns" }, groups.map(function (g) {
329
+ return el("section", { "class": "column" }, el("h3", { text: t("group_" + g) }),
330
+ shown.filter(function (u) { return u.group === g; }).map(function (u) {
331
+ return el("div", { "class": "unit" },
332
+ el("span", { "class": "id", text: u.id || "" }),
333
+ docLink(u.path, u.name),
334
+ el("div", { "class": "muted" }, [u.status, u.level ? levelLabel(u.level) : null, lensName(u.lens), dates(u)].filter(written).join(" · ")));
335
+ }));
336
+ }));
337
+ } else {
338
+ body = el("table", { "class": "list" },
339
+ el("thead", null, el("tr", null, [t("units_id"), t("units_name"), t("work_status"), t("work_level"), t("units_lens"), t("units_dates")].map(function (h) { return el("th", { text: h }); }))),
340
+ el("tbody", null, shown.map(function (u) {
341
+ return el("tr", null, el("td", null, el("code", { text: u.id || "" })), el("td", null, docLink(u.path, u.name)),
342
+ el("td", null, u.status ? statusPill(u.status, u.group) : ""), el("td", { text: u.level ? levelLabel(u.level) : "" }),
343
+ el("td", { text: lensName(u.lens) }), el("td", { text: dates(u) }));
344
+ })));
345
+ }
346
+ main(heading(title, t("units_why")), bar, body);
347
+ }
348
+
349
+ function ruleList(items, withStatus) {
350
+ return el("ul", null, items.map(function (r) {
351
+ return el("li", null, docLink(r.path, r.title),
352
+ withStatus && r.status ? [" ", el("span", { "class": "pill", text: r.status })] : null,
353
+ r.when ? el("div", { "class": "muted", text: withStatus ? r.when : t("rules_when", { when: r.when }) }) : null);
354
+ }));
355
+ }
356
+
357
+ function pageRules() {
358
+ setNav("rules");
359
+ var r = C.rules;
360
+ var any = r.vision.length || r.decisions.length;
361
+ main(heading(t("rules_title"), t("rules_why")),
362
+ any ? null : el("p", { "class": "empty", text: t("rules_empty") }),
363
+ r.vision.length ? [el("h2", { text: t("rules_vision") }), ruleList(r.vision, true)] : null,
364
+ r.decisions.length ? [el("h2", { text: t("rules_decisions") }), el("p", { "class": "why", text: t("rules_decisions_why") }), ruleList(r.decisions)] : null,
365
+ el("div", { "class": "links" }, r.manifest ? docLink(r.manifest, t("rules_manifest")) : null,
366
+ link("#/guides", t("rules_see_guides"))));
367
+ }
368
+
369
+ // The guides (R26): which guide to read before which task, the operative guides with
370
+ // when to read each and their status as declared, then the maps of complex parts.
371
+ function guideList(items) {
372
+ return el("ul", { "class": "guides" }, items.map(function (g) {
373
+ return el("li", null, docLink(g.path, g.title),
374
+ g.status ? [" ", el("span", { "class": "pill", text: g.status })] : null,
375
+ g.when ? el("div", { "class": "muted", text: t("rules_when", { when: g.when }) }) : null);
376
+ }));
377
+ }
378
+
379
+ function pageGuides() {
380
+ setNav("guides");
381
+ var g = C.guides;
382
+ var any = g.operative.length || g.comprehension.length;
383
+ main(heading(t("guides_title"), t("guides_why")),
384
+ g.router ? el("p", { "class": "router" }, docLink(g.router, t("guides_router")),
385
+ el("span", { "class": "why-line", text: t("guides_router_why") })) : null,
386
+ any ? null : el("p", { "class": "empty", text: t("guides_empty") }),
387
+ g.operative.length ? [el("h2", { text: t("guides_operative") }), el("p", { "class": "why", text: t("guides_operative_why") }), guideList(g.operative)] : null,
388
+ g.comprehension.length ? [el("h2", { text: t("guides_comprehension") }), el("p", { "class": "why", text: t("guides_comprehension_why") }), guideList(g.comprehension)] : null);
389
+ }
390
+
391
+ function folderName(f) {
392
+ var key = "folder_" + f.replace(/[\/\\]/g, "_");
393
+ return Object.prototype.hasOwnProperty.call(S, key) ? t(key) : f;
394
+ }
395
+
396
+ function pageDocuments() {
397
+ setNav("documents");
398
+ main(heading(t("documents_title"), t("documents_why")),
399
+ C.tree.length
400
+ ? el("div", { "class": "tree" }, C.tree.map(function (f) {
401
+ return el("details", { open: C.tree.length < 8 },
402
+ el("summary", null, folderName(f.folder), f.folder ? el("span", { "class": "muted", text: f.folder + "/" }) : null),
403
+ el("ul", null, f.docs.map(function (d) { return el("li", null, docLink(d.path, d.title)); })));
404
+ }))
405
+ : el("p", { "class": "empty", text: t("documents_empty") }));
406
+ }
407
+
408
+ function docAt(path, id) { return docHref(path) + "?at=" + encodeURIComponent(id); }
409
+
410
+ function pageDoc(address) {
411
+ setNav("documents");
412
+ var at = address.split("?at=");
413
+ var path = at[0];
414
+ needDocs(function () {
415
+ var d = D[path];
416
+ if (!d) return notFound();
417
+ // the document's own first heading is its title; the body follows the chain
418
+ var body = d.html.replace(/^<h1[^>]*>[\s\S]*?<\/h1>/, "");
419
+ main(el("h1", { text: d.title }),
420
+ el("p", { "class": "muted" }, el("code", { text: path })),
421
+ d.meta.length ? el("div", { "class": "doc-meta", title: t("doc_about") }, d.meta.map(function (kv) {
422
+ return el("span", { "class": "label" }, el("b", { text: kv[0] }), kv[1]);
423
+ })) : null,
424
+ d.chain ? chainStrip(d.chain) : null,
425
+ html(el("article", { "class": "prose" }), body));
426
+ if (at[1]) {
427
+ var dest = document.getElementById(at[1]);
428
+ if (dest) dest.scrollIntoView();
429
+ }
430
+ });
431
+ }
432
+
433
+ // An analysis read as the chain the agent works through (R24): one row per step,
434
+ // each linking to its sections as written; a step with none says so. Nothing here
435
+ // counts, scores or marks completeness.
436
+ function chainStrip(chain) {
437
+ function jump(id, label) {
438
+ return el("a", { href: "#" + id, onclick: function (e) {
439
+ e.preventDefault();
440
+ var dest = document.getElementById(id);
441
+ if (dest) dest.scrollIntoView({ behavior: "smooth" });
442
+ } }, label);
443
+ }
444
+ var c = chain.companions || {};
445
+ return el("nav", { "class": "chain", "aria-label": t("chain_title") },
446
+ el("h2", { text: t("chain_title") }),
447
+ el("ol", null, chain.steps.map(function (s) {
448
+ // what this analysis has for the step: its sections (for "merge and tests", the
449
+ // unit's own merge brief and E2E script). Only that decides "Not in this analysis";
450
+ // the project's North Star and the request record sit beside it, never in place of it.
451
+ var own = s.sections.map(function (sec) { return jump(sec.id, sec.title); });
452
+ if (s.step === "merge_tests") {
453
+ if (c.merge) own.push(docLink(c.merge, t("chain_merge")));
454
+ if (c.e2e) own.push(docLink(c.e2e, t("chain_e2e")));
455
+ }
456
+ var beside = [];
457
+ if (s.step === "vision" && chain.north_star) beside.push(link(docAt(chain.north_star.path, chain.north_star.id), t("chain_north_star")));
458
+ if (s.step === "vision" && c.request) beside.push(docLink(c.request, t("chain_request")));
459
+ return el("li", { "class": own.length ? "" : "absent" },
460
+ el("span", { "class": "step" }, el("b", { text: t("chain_step_" + s.step) }),
461
+ el("span", { "class": "why-line", text: t("chain_step_" + s.step + "_why") })),
462
+ el("span", { "class": "links" }, own.length ? own : el("span", { "class": "muted", text: t("chain_absent") }), beside));
463
+ })));
464
+ }
465
+
466
+ // ----------------------------------------------------- what is known
467
+
468
+ // The one way a fact's text becomes a label or a tooltip: a fact resting on a
469
+ // person's statement says so wherever its text appears (criterion 6).
470
+ function factTitle(f) { return f.statement ? t("statement_prefix", { text: f.text }) : f.text; }
471
+
472
+ function factLine(id, opts) {
473
+ var f = K.facts[id];
474
+ if (!f) return el("li", { "class": "fact" }, el("span"), el("span", { "class": "muted", text: id + " — " + t("fact_unknown") }));
475
+ var oc = originClass(f.origin);
476
+ var marks = [el("span", { "class": "tag o-" + oc, title: originWhy(f.origin), text: originLabel(f.origin) })];
477
+ if (f.statement) marks.push(el("span", { "class": "tag statement", title: t("statement_notice"), text: t("statement_mark") }));
478
+ if (f.state === "superseded") marks.push(el("span", { "class": "tag replaced", title: t("state_superseded_why"), text: t("state_superseded") }));
479
+ if (f.state === "contested") marks.push(el("span", { "class": "tag conflict", title: t("state_contested_why"), text: t("state_contested") }));
480
+ if (opts && opts.topic) marks.push(link("#/topic/" + encodeURIComponent(f.topic), f.topic, "chip"));
481
+ return el("li", { "class": "fact" + (f.state === "superseded" ? " replaced" : ""), onclick: function () { location.hash = "#/fact/" + encodeURIComponent(id); } },
482
+ el("span", { "class": "dot bg-" + oc }),
483
+ el("span", null, el("span", { "class": "text", text: f.text }), el("div", { "class": "marks" }, marks)));
484
+ }
485
+
486
+ function topicPage(slug) {
487
+ needKb(function () {
488
+ var tp = K.topics[slug];
489
+ if (!tp) return notFound();
490
+ if (tp.merged_into) { location.replace("#/topic/" + encodeURIComponent(tp.merged_into)); return; }
491
+ setNav("map");
492
+ var filterText = "", filterOrigin = "";
493
+ var list = el("ul", { "class": "facts" });
494
+ function refill() {
495
+ list.textContent = "";
496
+ var q = normalize(filterText).split(" ").filter(Boolean);
497
+ var ids = tp.facts.filter(function (id) {
498
+ var f = K.facts[id];
499
+ if (!f) return true;
500
+ if (filterOrigin && f.origin !== filterOrigin) return false;
501
+ var n = normalize(f.text);
502
+ return q.every(function (w) { return n.indexOf(w) >= 0; });
503
+ });
504
+ add(list, ids.length ? ids.map(function (id) { return factLine(id); }) : el("li", { "class": "muted", text: t("topic_no_facts") }));
505
+ }
506
+ function chipsOf(slugs) {
507
+ return el("div", { "class": "chips" }, slugs.map(function (s) { return link("#/topic/" + encodeURIComponent(s), K.topics[s] ? K.topics[s].name : s, "chip"); }));
508
+ }
509
+ var dl = [];
510
+ function row(label, v) { if (v) dl.push(el("dt", { text: t(label) }), el("dd", null, v)); }
511
+ row("topic_area", tp.area ? link("#/topic/" + encodeURIComponent(tp.area), K.topics[tp.area].name) : el("span", { "class": "muted", text: t("topic_no_area") }));
512
+ row("topic_parents", tp.parents.length ? chipsOf(tp.parents) : null);
513
+ row("topic_aka", tp.aka.length ? tp.aka.join(" · ") : null);
514
+ row("topic_gaps", tp.gaps.length ? tp.gaps.join(" · ") : null);
515
+ row("topic_related", tp.related.length ? chipsOf(tp.related) : null);
516
+ row("topic_children", tp.children.length ? chipsOf(tp.children) : null);
517
+ var used = tp.facts.map(function (id) { return K.facts[id] && K.facts[id].origin; })
518
+ .filter(function (v, i, a) { return v && a.indexOf(v) === i; });
519
+ main(el("h1", { text: tp.name }),
520
+ el("p", { "class": "muted" }, el("code", { text: slug }), " · ",
521
+ tp.count === 1 ? t("topic_facts_one") : t("topic_facts_count", { n: tp.count }), " · ",
522
+ link("#/map/" + encodeURIComponent(slug), t("topic_on_map"))),
523
+ tp.description ? el("p", { "class": "intro", text: tp.description }) : null,
524
+ el("div", { "class": "panel-inline" }, el("dl", { "class": "card", style: null }, dl)),
525
+ el("h2", { text: t("topic_facts") }),
526
+ el("div", { "class": "toolbar" },
527
+ el("input", { type: "search", placeholder: t("topic_filter"), oninput: function (e) { filterText = e.target.value; refill(); } }),
528
+ used.length > 1 ? el("select", { onchange: function (e) { filterOrigin = e.target.value; refill(); } },
529
+ el("option", { value: "", text: t("topic_origin_all") }),
530
+ used.map(function (o) { return el("option", { value: o, text: originLabel(o) }); })) : null),
531
+ list);
532
+ refill();
533
+ });
534
+ }
535
+
536
+ function whereText(w) {
537
+ if (!w) return "";
538
+ if (w.page) return t("fact_page", { n: w.page });
539
+ if (w.lines) return w.lines[0] === w.lines[1] ? t("fact_line", { a: w.lines[0] }) : t("fact_lines", { a: w.lines[0], b: w.lines[1] });
540
+ return "";
541
+ }
542
+
543
+ function sourceBlock(s) {
544
+ var target = s.source
545
+ ? link("#/source/" + encodeURIComponent(s.source), s.source)
546
+ : s.path.indexOf("corpus/notes/") === 0 ? docLink(s.path, s.path.split("/").pop()) : docLink(s.path);
547
+ var body = [];
548
+ if (s.passage !== undefined) {
549
+ body.push(el("div", { "class": "passage" }, s.before || "", el("mark", { text: s.passage }), s.after || ""));
550
+ } else if (s.reason) {
551
+ body.push(el("div", { "class": "reason", text: t("reason_" + s.reason, s.where || {}) }));
552
+ }
553
+ return el("div", { "class": "src" },
554
+ el("div", { "class": "head" }, target, el("span", { "class": "muted", text: whereText(s.where) })),
555
+ body);
556
+ }
557
+
558
+ function factPanel(id) {
559
+ needKb(function () {
560
+ closePanel();
561
+ var f0 = K.facts[id];
562
+ if (!lastPage) {
563
+ lastPage = f0 && K.topics[f0.topic] ? "#/topic/" + encodeURIComponent(f0.topic) : "#/";
564
+ if (f0 && K.topics[f0.topic]) topicPage(f0.topic); else pageHome();
565
+ }
566
+ var f = K.facts[id];
567
+ var box = el("aside", { "class": "panel" });
568
+ var close = el("button", { "class": "close", type: "button", onclick: leavePanel }, t("fact_close"));
569
+ if (!f) { add(box, [close, el("h2", { text: t("fact_title") }), el("p", { "class": "empty", text: t("fact_unknown") })]); return openPanel(box); }
570
+ var dl = [];
571
+ function row(label, v) { if (v) dl.push(el("dt", { text: t(label) }), el("dd", null, v)); }
572
+ row("fact_origin", el("span", null, el("span", { "class": "o-" + originClass(f.origin), text: originLabel(f.origin) }),
573
+ el("span", { "class": "why-line", text: originWhy(f.origin) })));
574
+ if (written(f.valid)) row("fact_valid", f.valid);
575
+ if (written(f.qty)) row("fact_qty", f.qty);
576
+ if (written(f.about)) row("fact_about", f.about);
577
+ row("fact_topic", link("#/topic/" + encodeURIComponent(f.topic), K.topics[f.topic] ? K.topics[f.topic].name : f.topic));
578
+ var parts = [];
579
+ if (f.state === "superseded") {
580
+ parts.push(el("h3", null, el("span", { "class": "tag replaced", text: t("state_superseded") }), " ", t("fact_replaced_by")),
581
+ el("p", { "class": "why-line", text: t("state_superseded_why") }),
582
+ el("ul", { "class": "facts" }, (f.successors || []).map(function (s) { return factLine(s, { topic: true }); })),
583
+ el("h3", { text: t("fact_current_at_end") }),
584
+ el("ul", { "class": "facts" }, (f.chain_ends || []).map(function (e) {
585
+ if (e.stop) return el("li", { "class": "reason", text: t("chain_" + e.stop, { id: e.id }) });
586
+ return factLine(e.id, { topic: true });
587
+ })));
588
+ }
589
+ if (f.state === "contested") {
590
+ parts.push(el("h3", null, el("span", { "class": "tag conflict", text: t("state_contested") }), " ", t("fact_in_conflict_with")),
591
+ el("p", { "class": "why-line", text: t("state_contested_why") }),
592
+ el("ul", { "class": "facts" }, (f.with || []).map(function (s) { return factLine(s, { topic: true }); })));
593
+ }
594
+ if (f.grounds) {
595
+ parts.push(el("h3", { text: t("fact_rests_on") }),
596
+ el("p", { "class": "why-line", text: t("ground_class_" + f.ground_class) }),
597
+ el("ul", null, f.grounds.map(function (g) {
598
+ var what = t("ground_" + g.kind);
599
+ var where = g.path ? (g.path.indexOf("corpus/given/") === 0
600
+ ? link("#/source/" + encodeURIComponent(g.path.slice(13)), g.path.slice(13))
601
+ : docLink(g.path, g.path.split("/").pop())) : null;
602
+ return el("li", null, el("b", { text: what }), where ? [" — ", where] : null,
603
+ g.kind === "untraceable" ? el("div", { "class": "reason" }, t("fact_untraced"), g.entry ? [" ", el("code", { text: g.entry })] : null) : null);
604
+ })));
605
+ }
606
+ parts.push(el("h3", { text: t("fact_sources") }), f.sources.map(sourceBlock));
607
+ add(box, [close, el("div", { "class": "muted" }, el("code", { text: id })),
608
+ el("div", { "class": "ftext", text: f.text }),
609
+ f.statement ? el("div", { "class": "statement-notice" }, el("b", { text: t("statement_mark") + ": " }), t("statement_notice")) : null,
610
+ el("dl", null, dl), parts]);
611
+ openPanel(box);
612
+ });
613
+ }
614
+
615
+ function leavePanel() { location.hash = lastPage || "#/"; }
616
+
617
+ function openPanel(box) { panelEl = box; document.body.appendChild(box); }
618
+ function closePanel() {
619
+ Array.prototype.forEach.call(document.querySelectorAll(".panel"), function (p) { p.remove(); });
620
+ panelEl = null;
621
+ }
622
+
623
+ function readText(r) {
624
+ if (!r) return "";
625
+ if (r.kind === "complete") return t("read_complete");
626
+ if (r.kind === "page") return r.of ? t("read_page_of", { n: r.n, m: r.of }) : t("read_page", { n: r.n });
627
+ if (r.kind === "line") return r.of ? t("read_line_of", { n: r.n, m: r.of }) : t("read_line", { n: r.n });
628
+ if (r.kind === "unreadable") return t("read_unreadable");
629
+ return t("read_unrecorded");
630
+ }
631
+
632
+ function pageSources() {
633
+ needKb(function () {
634
+ setNav("sources");
635
+ var names = Object.keys(K.sources).sort();
636
+ if (!names.length) return main(heading(t("sources_title"), t("sources_why")), el("p", { "class": "empty", text: t("sources_empty") }));
637
+ main(heading(t("sources_title"), t("sources_why")),
638
+ el("table", { "class": "list" },
639
+ el("thead", null, el("tr", null, ["source_name", "source_kind", "source_date", "source_facts", "source_topics"].map(function (k) { return el("th", { text: t(k) }); }))),
640
+ el("tbody", null, names.map(function (n) {
641
+ var s = K.sources[n];
642
+ return el("tr", null, el("td", null, link("#/source/" + encodeURIComponent(n), n)), el("td", { text: s.kind }),
643
+ el("td", { text: s.date }), el("td", { text: String(s.facts) }),
644
+ el("td", null, el("div", { "class": "chips" }, s.topics.map(function (tp) { return link("#/topic/" + encodeURIComponent(tp), tp, "chip"); }))));
645
+ }))));
646
+ });
647
+ }
648
+
649
+ function pageSource(name) {
650
+ needKb(function () {
651
+ var s = K.sources[name];
652
+ if (!s) return notFound();
653
+ setNav("sources");
654
+ var dl = [];
655
+ function row(label, v) { if (written(v) || (v && v.nodeType)) dl.push(el("dt", { text: t(label) }), el("dd", null, v)); }
656
+ row("source_kind", s.kind);
657
+ row("source_date", s.date);
658
+ row("source_facts", String(s.facts));
659
+ row("source_read", readText(s.read));
660
+ row("source_newer", s.older_of ? link("#/source/" + encodeURIComponent(s.older_of), s.older_of) : null);
661
+ row("source_older", s.newer_of ? (K.sources[s.newer_of] ? link("#/source/" + encodeURIComponent(s.newer_of), s.newer_of) : s.newer_of) : null);
662
+ row("source_topics", s.topics.length ? el("div", { "class": "chips" }, s.topics.map(function (tp) { return link("#/topic/" + encodeURIComponent(tp), tp, "chip"); })) : null);
663
+ row("source_record", docLink(s.sidecar, s.sidecar.split("/").pop()));
664
+ var ids = Object.keys(K.facts).filter(function (id) {
665
+ return K.facts[id].sources.some(function (x) { return x.source === name; });
666
+ }).sort();
667
+ main(el("h1", { text: name }), el("dl", { "class": "card" }, dl),
668
+ el("h2", { text: t("source_facts_list") }),
669
+ el("ul", { "class": "facts" }, ids.map(function (id) { return factLine(id, { topic: true }); })));
670
+ });
671
+ }
672
+
673
+ // ----------------------------------------------------------- the map
674
+
675
+ var mapView = "topics";
676
+ var hidden = {};
677
+
678
+ function areaColors() {
679
+ var areas = K.order.filter(function (s) { return K.topics[s].area === s; });
680
+ var colors = {};
681
+ areas.forEach(function (a, i) {
682
+ var hue = Math.round((i * 137.508 + 28) % 360);
683
+ colors[a] = "hsl(" + hue + ", 46%, 62%)";
684
+ });
685
+ return colors;
686
+ }
687
+
688
+ function originColor(o) {
689
+ return getComputedStyle(document.documentElement).getPropertyValue("--o-" + originClass(o)).trim() || "#888";
690
+ }
691
+
692
+ function pageMap(slug) {
693
+ needKb(function () {
694
+ setNav("map");
695
+ if (slug !== undefined && !K.topics[slug]) return notFound();
696
+ if (slug && K.topics[slug].merged_into) { location.replace("#/map/" + encodeURIComponent(K.topics[slug].merged_into)); return; }
697
+ // the address decides the view: #/map/<slug> is one topic up close; #/map is
698
+ // the topics view, or everything when its button was pressed (the side bar's
699
+ // Map resets to topics)
700
+ if (slug) { mapView = "close"; lastFocus = slug; }
701
+ else if (mapView === "close") mapView = "topics";
702
+ var focus = slug || lastFocus || K.order.filter(function (s) { return K.topics[s].area === s; })[0] || K.order[0];
703
+ var ctl = null;
704
+ var views = el("span", { "class": "seg" },
705
+ ["topics", "close", "every"].map(function (v) {
706
+ return el("button", { type: "button", "class": mapView === v ? "on" : "", disabled: v === "close" && !focus, onclick: function () {
707
+ if (v === "close") { location.hash = "#/map/" + encodeURIComponent(focus); return; }
708
+ mapView = v;
709
+ if (slug) location.hash = "#/map"; else pageMap();
710
+ } }, t("map_view_" + v));
711
+ }));
712
+ var zoom = el("span", { "class": "seg" },
713
+ el("button", { type: "button", title: t("map_zoom_out_why"), onclick: function () { if (ctl) ctl.zoom(1 / 1.4); } }, t("map_zoom_out")),
714
+ el("button", { type: "button", title: t("map_zoom_in_why"), onclick: function () { if (ctl) ctl.zoom(1.4); } }, t("map_zoom_in")),
715
+ el("button", { type: "button", onclick: function () { if (ctl) ctl.fit(); } }, t("map_fit")));
716
+ var wrap = el("div", { "class": "mapwrap" });
717
+ var canvas = el("canvas");
718
+ var tip = el("div", { "class": "tip" });
719
+ wrap.appendChild(canvas);
720
+ wrap.appendChild(tip);
721
+ wrap.appendChild(el("div", { "class": "maphint", text: t(mapView === "close" ? "map_hint_close" : "map_hint") }));
722
+ main(heading(t("map_title"), t(mapView === "close" ? "map_why_close" : mapView === "every" ? "map_why_every" : "map_why")),
723
+ el("div", { "class": "toolbar" }, views, zoom,
724
+ mapView === "close" ? link("#/topic/" + encodeURIComponent(focus), t("map_open_topic", { name: K.topics[focus].name })) : null), wrap);
725
+ mainEl.className = "wide";
726
+ if (!K.order.length) { wrap.textContent = ""; add(wrap, el("p", { "class": "empty", text: t("map_empty") })); return; }
727
+ var g = mapView === "close" ? closeGraph(focus) : mapView === "every" ? everyGraph() : topicGraph();
728
+ wrap.appendChild(legend(g, function () { if (ctl) ctl.draw(); }));
729
+ ctl = canvasGraph(canvas, tip, g);
730
+ });
731
+ }
732
+
733
+ var lastFocus = null;
734
+
735
+ function topicGraph() {
736
+ var colors = areaColors();
737
+ var nodes = K.map.nodes.map(function (n) {
738
+ return { id: n.id, x: n.x, y: n.y, r: 5 + 2.2 * Math.sqrt(n.count), kind: "topic",
739
+ color: n.area ? colors[n.area] : "#6c747d", label: K.topics[n.id].name,
740
+ href: "#/map/" + encodeURIComponent(n.id), tip: K.topics[n.id].name + (n.area ? "" : " · " + t("map_no_area")) };
741
+ });
742
+ var edges = K.map.edges.map(function (e) {
743
+ return { a: e.a, b: e.b, style: e.kind === "related" ? "dash" : e.kind === "shared" ? "thin" : "solid",
744
+ w: e.kind === "shared" ? Math.min(4, 0.6 + Math.log(1 + e.n)) : 1.4 };
745
+ });
746
+ var keys = [["solid", "map_edge_part"], ["dash", "map_edge_related"], ["thin", "map_edge_shared"]];
747
+ return { nodes: nodes, edges: edges, labels: true, edgeKeys: keys, kinds: ["topic"], origins: false };
748
+ }
749
+
750
+ function closeGraph(slug) {
751
+ var tp = K.topics[slug];
752
+ var nodes = [], edges = [], byId = {};
753
+ function node(n) { if (!byId[n.id]) { byId[n.id] = n; nodes.push(n); } return byId[n.id]; }
754
+ node({ id: "t:" + slug, x: 0, y: 0, r: 14, kind: "topic", color: "#d9a441", label: tp.name, href: "#/topic/" + encodeURIComponent(slug), tip: tp.name });
755
+ var ids = tp.facts.slice();
756
+ tp.facts.forEach(function (id) {
757
+ var f = K.facts[id];
758
+ if (!f) return;
759
+ (f.successors || []).concat(f.with || []).forEach(function (o) { if (ids.indexOf(o) < 0 && K.facts[o]) ids.push(o); });
760
+ });
761
+ var ring = Math.max(170, ids.length * 9);
762
+ ids.forEach(function (id, i) {
763
+ var f = K.facts[id];
764
+ if (!f) return;
765
+ var a = 2 * Math.PI * i / Math.max(1, ids.length);
766
+ node({ id: "f:" + id, x: ring * Math.cos(a), y: ring * Math.sin(a), r: 6, kind: "fact", origin: f.origin,
767
+ color: originColor(f.origin), dim: f.state === "superseded", conflict: f.state === "contested",
768
+ statement: f.statement, label: factTitle(f), href: "#/fact/" + encodeURIComponent(id), tip: factTitle(f) });
769
+ if (f.topic === slug) edges.push({ a: "f:" + id, b: "t:" + slug, style: "thin", w: 1 });
770
+ });
771
+ var outer = [], outerSet = {};
772
+ ids.forEach(function (id) {
773
+ var f = K.facts[id];
774
+ if (!f) return;
775
+ f.sources.forEach(function (s) { var k = s.source ? "s:" + s.source : "n:" + s.path; if (!outerSet[k]) { outerSet[k] = 1; outer.push([k, s]); } });
776
+ (f.grounds || []).forEach(function (gr) { if (gr.path) { var k = gr.path.indexOf("corpus/given/") === 0 ? "s:" + gr.path.slice(13) : "n:" + gr.path; if (!outerSet[k]) { outerSet[k] = 1; outer.push([k, { path: gr.path, source: k.slice(0, 2) === "s:" ? gr.path.slice(13) : null }]); } } });
777
+ });
778
+ var ring2 = ring + 170;
779
+ outer.forEach(function (o, i) {
780
+ var a = 2 * Math.PI * (i + 0.5) / Math.max(1, outer.length);
781
+ var s = o[1], isSrc = o[0].slice(0, 2) === "s:";
782
+ node({ id: o[0], x: ring2 * Math.cos(a), y: ring2 * Math.sin(a), r: 7, kind: isSrc ? "source" : "note",
783
+ color: isSrc ? "#c9c2b2" : "#8b93a0", square: true, label: (isSrc ? s.source : s.path.split("/").pop()),
784
+ href: isSrc ? "#/source/" + encodeURIComponent(s.source) : docHref(s.path), tip: isSrc ? s.source : s.path });
785
+ });
786
+ ids.forEach(function (id) {
787
+ var f = K.facts[id];
788
+ if (!f) return;
789
+ f.sources.forEach(function (s) { var k = s.source ? "s:" + s.source : "n:" + s.path; if (byId[k]) edges.push({ a: "f:" + id, b: k, style: "solid", w: 1 }); });
790
+ (f.grounds || []).forEach(function (gr) { if (!gr.path) return; var k = gr.path.indexOf("corpus/given/") === 0 ? "s:" + gr.path.slice(13) : "n:" + gr.path; if (byId[k]) edges.push({ a: "f:" + id, b: k, style: "based", w: 1.2 }); });
791
+ (f.successors || []).forEach(function (o) { if (byId["f:" + o]) edges.push({ a: "f:" + id, b: "f:" + o, style: "dash", w: 1.2 }); });
792
+ (f.with || []).forEach(function (o) { if (byId["f:" + o]) edges.push({ a: "f:" + id, b: "f:" + o, style: "conflict", w: 1.4 }); });
793
+ });
794
+ Object.keys(K.sources).forEach(function (n) {
795
+ var s = K.sources[n];
796
+ if (s.newer_of && byId["s:" + n] && byId["s:" + s.newer_of]) edges.push({ a: "s:" + n, b: "s:" + s.newer_of, style: "newer", w: 1.2 });
797
+ });
798
+ var keys = [["thin", "topic_facts"], ["solid", "fact_sources"], ["based", "map_edge_based"], ["newer", "map_edge_newer"], ["dash", "map_edge_replaced"], ["conflict", "map_edge_conflict"]];
799
+ return { nodes: nodes, edges: edges, labels: true, edgeKeys: keys, kinds: ["topic", "fact", "source", "note"], origins: true };
800
+ }
801
+
802
+ function everyGraph() {
803
+ var g = topicGraph();
804
+ g.edges = g.edges.filter(function (e) { return e.style !== "thin"; });
805
+ var topics = g.nodes;
806
+ g.nodes = [];
807
+ K.map.every.forEach(function (n) {
808
+ if (n.kind === "fact") {
809
+ var f = K.facts[n.id];
810
+ if (!f) return;
811
+ g.nodes.push({ id: "f:" + n.id, x: n.x, y: n.y, r: 2.2, kind: "fact", origin: f.origin, color: originColor(f.origin),
812
+ dim: f.state === "superseded", conflict: f.state === "contested", statement: f.statement, label: factTitle(f), href: "#/fact/" + encodeURIComponent(n.id), tip: factTitle(f) });
813
+ } else {
814
+ g.nodes.push({ id: "s:" + n.id, x: n.x, y: n.y, r: 3.5, kind: "source", color: "#c9c2b2", square: true,
815
+ label: n.id, href: "#/source/" + encodeURIComponent(n.id), tip: n.id });
816
+ }
817
+ });
818
+ g.nodes = g.nodes.concat(topics); // topics drawn last, on top of their facts
819
+ g.labels = true;
820
+ g.kinds = ["topic", "fact", "source"];
821
+ g.origins = true;
822
+ return g;
823
+ }
824
+
825
+ var KIND_SWATCH = { topic: "#d9a441", fact: "#9aa3ad", source: "#c9c2b2", note: "#8b93a0" };
826
+
827
+ function legend(g, redraw) {
828
+ var box = el("details", { "class": "legend", open: window.innerWidth > 1000 });
829
+ add(box, el("summary", { text: t("map_legend") }));
830
+ function toggle(key, label, swatch) {
831
+ var cb = el("input", { type: "checkbox", checked: !hidden[key], onchange: function (e) { hidden[key] = !e.target.checked; redraw(); } });
832
+ return el("label", null, cb, swatch, label);
833
+ }
834
+ if (g.origins) {
835
+ add(box, el("h4", { text: t("fact_origin") }));
836
+ ORIGINS.forEach(function (o) {
837
+ add(box, toggle("o:" + o, originLabel(o), el("span", { "class": "sw bg-" + o })));
838
+ });
839
+ }
840
+ if (g.kinds.length > 1) {
841
+ add(box, el("h4", { text: t("map_kinds") }));
842
+ g.kinds.forEach(function (k) {
843
+ var sw = el("span", { "class": "sw" + (k === "source" || k === "note" ? " sq" : "") });
844
+ sw.style.background = KIND_SWATCH[k];
845
+ add(box, toggle("k:" + k, t("map_kind_" + k), sw));
846
+ });
847
+ }
848
+ add(box, el("h4", { text: t("map_lines") }));
849
+ g.edgeKeys.forEach(function (k) { add(box, el("label", null, el("span", { "class": "ln " + k[0] }), t(k[1]))); });
850
+ if (g.origins) add(box, el("label", { title: t("statement_notice") }, el("span", { "class": "sw ring" }), t("statement_mark")));
851
+ add(box, el("label", null, el("span", { "class": "tag replaced", text: t("state_superseded") })),
852
+ el("label", null, el("span", { "class": "tag conflict", text: t("state_contested") })));
853
+ return box;
854
+ }
855
+
856
+ function canvasGraph(canvas, tip, g) {
857
+ var ctx = canvas.getContext("2d");
858
+ var byId = {};
859
+ g.nodes.forEach(function (n) { byId[n.id] = n; });
860
+ var view = { s: 1, x: 0, y: 0 };
861
+ var W = 0, H = 0, dpr = window.devicePixelRatio || 1;
862
+
863
+ function visible(n) {
864
+ if (hidden["k:" + n.kind]) return false;
865
+ if (n.origin && hidden["o:" + originClass(n.origin)]) return false;
866
+ return true;
867
+ }
868
+ function fit() {
869
+ var r = canvas.getBoundingClientRect();
870
+ W = r.width; H = r.height;
871
+ canvas.width = Math.round(W * dpr); canvas.height = Math.round(H * dpr);
872
+ var xs = g.nodes.map(function (n) { return n.x; }), ys = g.nodes.map(function (n) { return n.y; });
873
+ var x0 = Math.min.apply(null, xs), x1 = Math.max.apply(null, xs), y0 = Math.min.apply(null, ys), y1 = Math.max.apply(null, ys);
874
+ var pad = 60;
875
+ view.s = Math.min((W - 2 * pad) / Math.max(1, x1 - x0), (H - 2 * pad) / Math.max(1, y1 - y0), 2.2);
876
+ view.x = W / 2 - view.s * (x0 + x1) / 2;
877
+ view.y = H / 2 - view.s * (y0 + y1) / 2;
878
+ }
879
+ function sx(x) { return view.x + view.s * x; }
880
+ function sy(y) { return view.y + view.s * y; }
881
+ function draw() {
882
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
883
+ ctx.clearRect(0, 0, W, H);
884
+ g.edges.forEach(function (e) {
885
+ var a = byId[e.a], b = byId[e.b];
886
+ if (!a || !b || !visible(a) || !visible(b)) return;
887
+ ctx.beginPath();
888
+ ctx.setLineDash(e.style === "dash" ? [6, 5] : e.style === "newer" ? [2, 4] : []);
889
+ ctx.strokeStyle = e.style === "thin" ? "rgba(120,132,145,0.35)" : e.style === "based" ? "rgba(169,155,224,0.7)"
890
+ : e.style === "newer" ? "rgba(111,183,168,0.8)" : e.style === "conflict" ? "rgba(224,96,90,0.85)" : "rgba(160,168,178,0.55)";
891
+ ctx.lineWidth = e.w;
892
+ ctx.moveTo(sx(a.x), sy(a.y));
893
+ ctx.lineTo(sx(b.x), sy(b.y));
894
+ ctx.stroke();
895
+ });
896
+ ctx.setLineDash([]);
897
+ var rs = Math.max(0.6, Math.min(1.6, Math.sqrt(view.s)));
898
+ g.nodes.forEach(function (n) {
899
+ if (!visible(n)) return;
900
+ var r = n.r * rs;
901
+ ctx.globalAlpha = n.dim ? 0.35 : 1;
902
+ ctx.fillStyle = n.color;
903
+ ctx.beginPath();
904
+ if (n.square) ctx.rect(sx(n.x) - r, sy(n.y) - r, 2 * r, 2 * r);
905
+ else ctx.arc(sx(n.x), sy(n.y), r, 0, 2 * Math.PI);
906
+ ctx.fill();
907
+ if (n.conflict) { ctx.strokeStyle = "#e0605a"; ctx.lineWidth = 2; ctx.stroke(); }
908
+ if (n.statement) {
909
+ ctx.beginPath();
910
+ ctx.arc(sx(n.x), sy(n.y), r + 2.5, 0, 2 * Math.PI);
911
+ ctx.strokeStyle = "#e0a24a"; ctx.lineWidth = 1.5; ctx.setLineDash([2, 2]); ctx.stroke(); ctx.setLineDash([]);
912
+ }
913
+ ctx.globalAlpha = 1;
914
+ });
915
+ if (g.labels) labels(rs);
916
+ }
917
+
918
+ // Every node carries its label; a label is drawn only where it can be read
919
+ // whole without touching another one. Topics first, then documents and notes,
920
+ // then facts -- whose text is wrapped, never cut. Zooming in makes room, so
921
+ // more labels appear.
922
+ var PRIORITY = { topic: 0, source: 1, note: 1, fact: 2 };
923
+ var labelled = g.nodes.filter(function (n) { return n.label; }).sort(function (a, b) {
924
+ return (PRIORITY[a.kind] - PRIORITY[b.kind]) || (b.r - a.r) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
925
+ });
926
+ var family = null;
927
+ function fontOf(n) { return (n.kind === "topic" ? "600 12.5px " : n.kind === "fact" ? "11.5px " : "11px ") + family; }
928
+ function linesOf(n) {
929
+ if (n._lines) return n._lines;
930
+ ctx.font = fontOf(n);
931
+ var maxW = n.kind === "fact" ? 220 : Infinity, lines = [], cur = "";
932
+ String(n.label).split(/\s+/).forEach(function (w) {
933
+ var next = cur ? cur + " " + w : w;
934
+ if (cur && ctx.measureText(next).width > maxW) { lines.push(cur); cur = w; } else cur = next;
935
+ });
936
+ if (cur) lines.push(cur);
937
+ n._lines = lines;
938
+ n._w = Math.max.apply(null, lines.map(function (l) { return ctx.measureText(l).width; }));
939
+ n._lh = n.kind === "topic" ? 15 : 14;
940
+ return lines;
941
+ }
942
+ function labels(rs) {
943
+ family = family || getComputedStyle(document.body).fontFamily;
944
+ var CELL = 96, grid = {};
945
+ function keys(b) {
946
+ var out = [];
947
+ for (var gx = Math.floor(b.x0 / CELL); gx <= Math.floor(b.x1 / CELL); gx++)
948
+ for (var gy = Math.floor(b.y0 / CELL); gy <= Math.floor(b.y1 / CELL); gy++) out.push(gx + ":" + gy);
949
+ return out;
950
+ }
951
+ function free(b, ks) {
952
+ return ks.every(function (k) {
953
+ return !(grid[k] || []).some(function (o) { return b.x0 < o.x1 && o.x0 < b.x1 && b.y0 < o.y1 && o.y0 < b.y1; });
954
+ });
955
+ }
956
+ ctx.textAlign = "center";
957
+ ctx.textBaseline = "bottom";
958
+ ctx.lineJoin = "round";
959
+ labelled.forEach(function (n) {
960
+ if (!visible(n)) return;
961
+ var x = sx(n.x), y = sy(n.y);
962
+ if (x < -40 || x > W + 40 || y < -40 || y > H + 40) return;
963
+ var lines = linesOf(n), h = lines.length * n._lh, top = y - n.r * rs - 4 - h;
964
+ var b = { x0: x - n._w / 2 - 3, x1: x + n._w / 2 + 3, y0: top - 2, y1: top + h + 2 };
965
+ if (b.x0 < 0 || b.x1 > W || b.y0 < 0 || b.y1 > H) return;
966
+ var ks = keys(b);
967
+ if (!free(b, ks)) return;
968
+ ks.forEach(function (k) { (grid[k] = grid[k] || []).push(b); });
969
+ ctx.font = fontOf(n);
970
+ lines.forEach(function (ln, i) {
971
+ var yy = top + (i + 1) * n._lh;
972
+ ctx.strokeStyle = "rgba(12,15,19,0.92)";
973
+ ctx.lineWidth = 4;
974
+ ctx.strokeText(ln, x, yy);
975
+ ctx.fillStyle = n.kind === "topic" ? "#e8e2d4" : n.kind === "fact" ? "#d2ccbe" : "#b9b2a3";
976
+ ctx.fillText(ln, x, yy);
977
+ });
978
+ });
979
+ }
980
+ function at(px, py) {
981
+ var best = null, bd = 1e9;
982
+ g.nodes.forEach(function (n) {
983
+ if (!visible(n)) return;
984
+ var d = Math.hypot(sx(n.x) - px, sy(n.y) - py);
985
+ if (d < Math.max(8, n.r * 1.6) && d < bd) { bd = d; best = n; }
986
+ });
987
+ return best;
988
+ }
989
+ var drag = null, moved = false;
990
+ canvas.addEventListener("mousedown", function (e) { drag = { x: e.clientX, y: e.clientY, vx: view.x, vy: view.y }; moved = false; canvas.style.cursor = ""; canvas.className = "drag"; });
991
+ window.addEventListener("mouseup", function () { drag = null; canvas.className = ""; });
992
+ canvas.addEventListener("mousemove", function (e) {
993
+ var r = canvas.getBoundingClientRect();
994
+ if (drag) {
995
+ var dx = e.clientX - drag.x, dy = e.clientY - drag.y;
996
+ if (Math.abs(dx) + Math.abs(dy) > 3) moved = true;
997
+ view.x = drag.vx + dx; view.y = drag.vy + dy; draw(); return;
998
+ }
999
+ var n = at(e.clientX - r.left, e.clientY - r.top);
1000
+ if (n && n.tip) { tip.textContent = n.tip; tip.style.display = "block"; tip.style.left = (e.clientX - r.left + 14) + "px"; tip.style.top = (e.clientY - r.top + 10) + "px"; canvas.style.cursor = "pointer"; }
1001
+ else { tip.style.display = "none"; canvas.style.cursor = ""; }
1002
+ });
1003
+ canvas.addEventListener("click", function (e) {
1004
+ if (moved) return;
1005
+ var r = canvas.getBoundingClientRect();
1006
+ var n = at(e.clientX - r.left, e.clientY - r.top);
1007
+ if (n && n.href) location.hash = n.href;
1008
+ });
1009
+ canvas.addEventListener("wheel", function (e) {
1010
+ e.preventDefault();
1011
+ var r = canvas.getBoundingClientRect();
1012
+ var px = e.clientX - r.left, py = e.clientY - r.top;
1013
+ var k = Math.exp(-e.deltaY * 0.0015);
1014
+ k = Math.max(0.02, Math.min(60, view.s * k)) / view.s;
1015
+ view.x = px - (px - view.x) * k; view.y = py - (py - view.y) * k; view.s *= k;
1016
+ draw();
1017
+ }, { passive: false });
1018
+ window.addEventListener("resize", function () { if (canvas.isConnected) { fit(); draw(); } });
1019
+ fit();
1020
+ draw();
1021
+ return {
1022
+ draw: draw,
1023
+ fit: function () { fit(); draw(); },
1024
+ zoom: function (k) {
1025
+ var px = W / 2, py = H / 2;
1026
+ view.x = px - (px - view.x) * k; view.y = py - (py - view.y) * k; view.s *= k;
1027
+ draw();
1028
+ }
1029
+ };
1030
+ }
1031
+
1032
+ // ---------------------------------------------------------- search
1033
+
1034
+ var overlay = null;
1035
+
1036
+ function openSearch() {
1037
+ if (overlay) return;
1038
+ var input = el("input", { type: "search", placeholder: t(C.meta.has_kb ? "search_placeholder" : "search_placeholder_docs") });
1039
+ var results = el("div", { "class": "results" });
1040
+ overlay = el("div", { "class": "overlay", onclick: function (e) { if (e.target === overlay) closeSearch(); } },
1041
+ el("div", { "class": "searchbox" }, input, results, el("div", { "class": "keys", text: t("search_keys") })));
1042
+ document.body.appendChild(overlay);
1043
+ input.focus();
1044
+ var hits = [], sel = 0;
1045
+ function render() {
1046
+ results.textContent = "";
1047
+ var q = normalize(input.value).split(" ").filter(Boolean);
1048
+ if (!q.length) return;
1049
+ function match(e) { return q.every(function (w) { return e.n.indexOf(w) >= 0; }); }
1050
+ var groups = [];
1051
+ if (K) {
1052
+ groups.push(["search_group_facts", K.search.filter(function (e) { return e.k === "fact"; }).filter(match), function (e) { return "#/fact/" + encodeURIComponent(e.id); }]);
1053
+ groups.push(["search_group_topics", K.search.filter(function (e) { return e.k === "topic"; }).filter(match), function (e) { return "#/topic/" + encodeURIComponent(e.id); }]);
1054
+ }
1055
+ groups.push(["search_group_documents", C.search.filter(match), function (e) { return docHref(e.path); }]);
1056
+ hits = [];
1057
+ groups.forEach(function (gr) {
1058
+ if (!gr[1].length) return;
1059
+ add(results, el("div", { "class": "grp", text: t(gr[0]) }));
1060
+ gr[1].slice(0, 40).forEach(function (e) {
1061
+ var a = el("a", { "class": "res", href: gr[2](e), onclick: closeSearch }, e.title || e.path,
1062
+ e.statement ? [" ", el("span", { "class": "tag statement", title: t("statement_notice"), text: t("statement_mark") })] : null,
1063
+ el("small", { text: e.path || e.id }));
1064
+ hits.push(a);
1065
+ results.appendChild(a);
1066
+ });
1067
+ if (gr[1].length > 40) add(results, el("div", { "class": "none", text: t("search_more", { n: gr[1].length - 40 }) }));
1068
+ });
1069
+ if (!hits.length) add(results, el("div", { "class": "none", text: t(C.meta.has_kb ? "search_none_kb" : "search_none_docs") }));
1070
+ sel = 0;
1071
+ mark();
1072
+ }
1073
+ function mark() { hits.forEach(function (a, i) { a.className = "res" + (i === sel ? " sel" : ""); }); if (hits[sel]) hits[sel].scrollIntoView({ block: "nearest" }); }
1074
+ input.addEventListener("input", render);
1075
+ input.addEventListener("keydown", function (e) {
1076
+ if (e.key === "ArrowDown") { sel = Math.min(hits.length - 1, sel + 1); mark(); e.preventDefault(); }
1077
+ else if (e.key === "ArrowUp") { sel = Math.max(0, sel - 1); mark(); e.preventDefault(); }
1078
+ else if (e.key === "Enter" && hits[sel]) { location.hash = hits[sel].getAttribute("href"); closeSearch(); }
1079
+ else if (e.key === "Escape") closeSearch();
1080
+ });
1081
+ if (C.meta.has_kb && !K) {
1082
+ loadScript("data/kb.js", function () { K = window.DOCS_SITE_KB; render(); });
1083
+ }
1084
+ }
1085
+
1086
+ function closeSearch() { if (overlay) { overlay.remove(); overlay = null; } }
1087
+
1088
+ document.addEventListener("keydown", function (e) {
1089
+ if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); openSearch(); }
1090
+ else if (e.key === "Escape" && panelEl && !overlay) leavePanel();
1091
+ });
1092
+
1093
+ // ---------------------------------------------------------- routing
1094
+
1095
+ var lastPage = "";
1096
+
1097
+ function route() {
1098
+ var h = location.hash || "#/";
1099
+ var parts = h.replace(/^#\/?/, "").split("/");
1100
+ var head = parts[0];
1101
+ var rest;
1102
+ try { rest = decodeURIComponent(parts.slice(1).join("/")); }
1103
+ catch (e) { closePanel(); notFound(); return; }
1104
+ closeSearch();
1105
+ if (head === "fact") { factPanel(rest); return; }
1106
+ closePanel();
1107
+ lastPage = h;
1108
+ if (head === "") pageHome();
1109
+ else if (head === "chronology") pageChronology();
1110
+ else if (head === "work") pageWork();
1111
+ else if (head === "units") pageUnits();
1112
+ else if (head === "rules") pageRules();
1113
+ else if (head === "guides") pageGuides();
1114
+ else if (head === "documents") pageDocuments();
1115
+ else if (head === "doc") pageDoc(rest);
1116
+ else if (head === "map") pageMap(parts.length > 1 && rest ? rest : undefined);
1117
+ else if (head === "topic") topicPage(rest);
1118
+ else if (head === "sources") pageSources();
1119
+ else if (head === "source") pageSource(rest);
1120
+ else notFound();
1121
+ }
1122
+
1123
+ if (!C || !C.meta || !C.home) { fail(); return; }
1124
+ frame();
1125
+ window.addEventListener("hashchange", route);
1126
+ route();
1127
+ })();