@appsoftwareltd/etherpk-mcp 0.9.3 → 0.9.5

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.
package/dist/main.js CHANGED
@@ -14,7 +14,7 @@ import { spawn } from "node:child_process";
14
14
  import { closeSync, createWriteStream, existsSync, openSync, statSync, watch } from "node:fs";
15
15
  import { Readable } from "node:stream";
16
16
  import { pipeline } from "node:stream/promises";
17
- import { pathToFileURL } from "node:url";
17
+ import { fileURLToPath, pathToFileURL } from "node:url";
18
18
  import { Tokenizer } from "@huggingface/tokenizers";
19
19
  import { deserialize, serialize } from "node:v8";
20
20
  import { parser } from "@lezer/markdown";
@@ -30,6 +30,8 @@ import { languages } from "@codemirror/language-data";
30
30
  import MarkdownIt from "markdown-it";
31
31
  import katex from "katex";
32
32
  import Mustache from "mustache";
33
+ import { createServer as createServer$1 } from "node:http";
34
+ import "maplibre-gl";
33
35
  import { lookup } from "node:dns";
34
36
  import { request } from "node:https";
35
37
  import { UndirectedGraph } from "graphology";
@@ -627,6 +629,73 @@ function assetChunkCount(size) {
627
629
  }
628
630
  var DAY = 864e5;
629
631
  365 * DAY, 30 * DAY, 7 * DAY;
632
+ /** Whether `text` is a short link a Sync Server opens: one of Google's share links. */
633
+ function isShortMapLink(text) {
634
+ let url;
635
+ try {
636
+ url = new URL(text.trim());
637
+ } catch {
638
+ return false;
639
+ }
640
+ if (url.protocol !== "https:" || url.username !== "" || url.password !== "" || url.port !== "") return false;
641
+ const host = url.hostname.toLowerCase();
642
+ if (host === "maps.app.goo.gl") return /^\/[A-Za-z0-9_-]+\/?$/.test(url.pathname);
643
+ if (host === "goo.gl") return /^\/maps\/[A-Za-z0-9_-]+\/?$/.test(url.pathname);
644
+ return false;
645
+ }
646
+ z.strictObject({ url: z.string().trim().max(200).refine(isShortMapLink, "not a short map link") });
647
+ var latitude = z.number().min(-90).max(90);
648
+ var longitude = z.number().min(-180).max(180);
649
+ z.strictObject({
650
+ q: z.string().trim().min(1).max(200),
651
+ /** Where the map is looking, so the nearest places come first. */
652
+ near: z.strictObject({
653
+ lat: latitude,
654
+ lon: longitude
655
+ }).optional(),
656
+ /** The reader's language, two letters, for the names of places. */
657
+ lang: z.string().regex(/^[a-z]{2}$/).optional()
658
+ });
659
+ z.strictObject({
660
+ point: z.strictObject({
661
+ lat: latitude,
662
+ lon: longitude
663
+ }),
664
+ /** The reader's language, two letters, for the name of the place. */
665
+ lang: z.string().regex(/^[a-z]{2}$/).optional()
666
+ });
667
+ /** A place's address fields, as Photon names them. Anything else a service sends is dropped. */
668
+ var placeProperties = z.object({
669
+ name: z.string().optional(),
670
+ housenumber: z.string().optional(),
671
+ street: z.string().optional(),
672
+ postcode: z.string().optional(),
673
+ district: z.string().optional(),
674
+ city: z.string().optional(),
675
+ county: z.string().optional(),
676
+ state: z.string().optional(),
677
+ country: z.string().optional(),
678
+ countrycode: z.string().optional(),
679
+ /** What sort of place it is: a house, a street, a city, a postcode. */
680
+ type: z.string().optional()
681
+ });
682
+ var placeSearchFeatureSchema = z.object({
683
+ type: z.literal("Feature"),
684
+ geometry: z.object({
685
+ type: z.literal("Point"),
686
+ coordinates: z.tuple([longitude, latitude])
687
+ }),
688
+ properties: placeProperties
689
+ });
690
+ var placeSearchCreditSchema = z.object({
691
+ text: z.string().trim().min(1).max(100),
692
+ url: z.url({ protocol: /^https$/ })
693
+ });
694
+ z.object({
695
+ type: z.literal("FeatureCollection"),
696
+ features: z.array(placeSearchFeatureSchema).max(8),
697
+ credits: z.array(placeSearchCreditSchema).max(4).optional()
698
+ });
630
699
  var AGENT_SETUP_CODE_PREFIX = "epk_setup_";
631
700
  /** Every token and setup code carries 32 random bytes, which base64url writes as 43 characters. */
632
701
  var SECRET_LENGTH = 43;
@@ -648,7 +717,7 @@ function looksLikeAgentSetupCode(value) {
648
717
  }
649
718
  var package_default = {
650
719
  name: "@appsoftwareltd/etherpk-mcp",
651
- version: "0.9.3",
720
+ version: "0.9.5",
652
721
  license: "Elastic-2.0",
653
722
  description: "EtherPK Headless Client: an MCP server over a synced knowledge graph, run beside the agent on the user's own machine.",
654
723
  type: "module",
@@ -657,7 +726,7 @@ var package_default = {
657
726
  files: ["bin", "dist"],
658
727
  scripts: {
659
728
  "client-tsconfig": "pnpm --dir ../client exec svelte-kit sync",
660
- "build": "pnpm client-tsconfig && vite build",
729
+ "build": "pnpm client-tsconfig && vite build && vite build --config vite.map-page.config.ts",
661
730
  "check": "tsc -p tsconfig.json --noEmit",
662
731
  "test": "pnpm client-tsconfig && vitest run",
663
732
  "test:watch": "pnpm client-tsconfig && vitest",
@@ -669,7 +738,7 @@ var package_default = {
669
738
  "@huggingface/tokenizers": "0.2.0",
670
739
  "@lezer/highlight": "^1.2.3",
671
740
  "@lezer/markdown": "^1.6.4",
672
- "@modelcontextprotocol/sdk": "1.30.0",
741
+ "@modelcontextprotocol/sdk": "1.31.0",
673
742
  "@noble/curves": "2.2.0",
674
743
  "@noble/hashes": "2.2.0",
675
744
  "@sqlite.org/sqlite-wasm": "3.53.0-build1",
@@ -677,8 +746,9 @@ var package_default = {
677
746
  "graphology": "^0.26.0",
678
747
  "graphology-communities-louvain": "^2.0.2",
679
748
  "graphology-types": "^0.24.8",
680
- "katex": "^0.17.0",
749
+ "katex": "^0.18.2",
681
750
  "lib0": "0.2.117",
751
+ "maplibre-gl": "6.11.2",
682
752
  "markdown-it": "15.0.2",
683
753
  "mermaid": "^11.16.1",
684
754
  "mustache": "4.2.0",
@@ -689,6 +759,8 @@ var package_default = {
689
759
  "zod": "^4.3.6"
690
760
  },
691
761
  devDependencies: {
762
+ "@appsoftwareltd/etherpk-extension-graph-view": "workspace:*",
763
+ "@appsoftwareltd/etherpk-extension-maps": "workspace:*",
692
764
  "@appsoftwareltd/etherpk-shared": "workspace:*",
693
765
  "@appsoftwareltd/etherpk-themes": "workspace:*",
694
766
  "@codemirror/view": "^6.43.0",
@@ -1392,7 +1464,8 @@ var KNOWN_FIELDS = [
1392
1464
  "signingPublicKey",
1393
1465
  "keyrings",
1394
1466
  "protection",
1395
- "pins"
1467
+ "pins",
1468
+ "settings"
1396
1469
  ];
1397
1470
  async function openContent(envelope, vaultKey, aad) {
1398
1471
  const { plaintext } = await openSymmetric({
@@ -1413,6 +1486,7 @@ async function openContent(envelope, vaultKey, aad) {
1413
1486
  keyrings: deserializeKeyrings(utf8(content.keyrings)),
1414
1487
  ...content.protection ? { protection: content.protection } : {},
1415
1488
  ...content.pins ? { pins: content.pins } : {},
1489
+ ...content.settings ? { settings: content.settings } : {},
1416
1490
  ...Object.keys(otherFields).length > 0 ? { otherFields } : {}
1417
1491
  };
1418
1492
  }
@@ -1608,6 +1682,34 @@ function createSyncApi(deps) {
1608
1682
  return {
1609
1683
  /** Resolve the authenticated service-local account before loading account-scoped data. */
1610
1684
  me: () => call("/api/v1/sync/me"),
1685
+ /**
1686
+ * Search for a place by name, address or postcode (ADR 0119). The query travels in the
1687
+ * body, so no request log holds it. The answer is unchecked JSON in Photon's form, which
1688
+ * the caller reads with `readPlaceFeatures`; a refusal carries a `PLACE_SEARCH_REFUSAL` code.
1689
+ */
1690
+ searchPlaces: (request, signal) => call("/api/v1/sync/maps/search", {
1691
+ method: "POST",
1692
+ body: JSON.stringify(request),
1693
+ signal
1694
+ }),
1695
+ /**
1696
+ * The place nearest a point, to offer a name for a place set down by hand (ADR 0119,
1697
+ * amendment of 2026-10-10). Answered and refused as `searchPlaces` is, with at most one place.
1698
+ */
1699
+ reversePlace: (request, signal) => call("/api/v1/sync/maps/reverse", {
1700
+ method: "POST",
1701
+ body: JSON.stringify(request),
1702
+ signal
1703
+ }),
1704
+ /**
1705
+ * The full link a short map link leads to, which a browser page cannot follow itself (ADR
1706
+ * 0119, amendment of 2026-10-10). Any signed-in account may ask.
1707
+ */
1708
+ openMapLink: (request, signal) => call("/api/v1/sync/maps/link", {
1709
+ method: "POST",
1710
+ body: JSON.stringify(request),
1711
+ signal
1712
+ }),
1611
1713
  createGraph: () => call("/api/v1/sync/graphs", { method: "POST" }),
1612
1714
  listGraphs: () => call("/api/v1/sync/graphs").then((r) => r.graphs),
1613
1715
  /** Graphs plus the owned-storage rollup (one request - same endpoint as listGraphs). */
@@ -3649,6 +3751,33 @@ function fencedBlocks(lines, closerTolerance = 0) {
3649
3751
  }
3650
3752
  return blocks;
3651
3753
  }
3754
+ /** {@link fencedBlockTree} by scan: a scan never changes, so neither does its tree. */
3755
+ var treeByBlocks = /* @__PURE__ */ new WeakMap();
3756
+ /**
3757
+ * The blocks of one scan as a tree. A code sample can hold a fenced block of its own, written deeper (a
3758
+ * markdown sample of a list item with code in it): the scan pairs the inner fences inside the outer
3759
+ * block, and lists blocks in the order they close, the inner pair before the block around it. Blocks
3760
+ * nest or are disjoint, never overlap partly. Read once per scan and shared, as the lookups run on
3761
+ * every caret move.
3762
+ */
3763
+ function fencedBlockTree(blocks) {
3764
+ const cached = treeByBlocks.get(blocks);
3765
+ if (cached) return cached;
3766
+ const sorted = [...blocks].sort((a, b) => a.start - b.start);
3767
+ const parent = [];
3768
+ const open = [];
3769
+ sorted.forEach((block, i) => {
3770
+ while (open.length && sorted[open[open.length - 1]].end < block.start) open.pop();
3771
+ parent.push(open.length ? open[open.length - 1] : -1);
3772
+ open.push(i);
3773
+ });
3774
+ const tree = {
3775
+ sorted,
3776
+ parent
3777
+ };
3778
+ treeByBlocks.set(blocks, tree);
3779
+ return tree;
3780
+ }
3652
3781
  /**
3653
3782
  * A line of a fenced block as code: the indentation up to the fence's column is structure (the
3654
3783
  * block's place in the outline) and goes; whatever lies past it is the code's own and stays.
@@ -4033,6 +4162,105 @@ function parseBlocks(markdown) {
4033
4162
  return roots;
4034
4163
  }
4035
4164
  //#endregion
4165
+ //#region ../client/src/lib/document/map-text.ts
4166
+ var NUMBER = String.raw`[+-]?(?:\d+(?:\.\d*)?|\.\d+)`;
4167
+ var POINT = new RegExp(String.raw`^\s*(${NUMBER})\s*,\s*(${NUMBER})\s*$`);
4168
+ function readPoint(text) {
4169
+ const match = POINT.exec(text);
4170
+ if (!match) return null;
4171
+ const lat = Number(match[1]);
4172
+ const lon = Number(match[2]);
4173
+ if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
4174
+ if (lat < -90 || lat > 90 || lon < -180 || lon > 180) return null;
4175
+ return {
4176
+ lat,
4177
+ lon
4178
+ };
4179
+ }
4180
+ /**
4181
+ * The part after the name: points joined by `>`, and for a route an optional `(recording)`.
4182
+ * The recording starts at the first `(`, which no point can hold, so a file name with brackets
4183
+ * of its own is kept whole.
4184
+ */
4185
+ function readLocation(text, name) {
4186
+ let pointsText = text.trim();
4187
+ let track = null;
4188
+ const open = pointsText.indexOf("(");
4189
+ if (open >= 0) {
4190
+ if (!pointsText.endsWith(")")) return null;
4191
+ track = pointsText.slice(open + 1, -1).trim();
4192
+ if (track === "") return null;
4193
+ pointsText = pointsText.slice(0, open);
4194
+ }
4195
+ const points = [];
4196
+ for (const part of pointsText.split(">")) {
4197
+ const point = readPoint(part);
4198
+ if (!point) return null;
4199
+ points.push(point);
4200
+ }
4201
+ if (points.length === 1) return track === null ? {
4202
+ kind: "place",
4203
+ name,
4204
+ point: points[0]
4205
+ } : null;
4206
+ return {
4207
+ kind: "route",
4208
+ name,
4209
+ points,
4210
+ track
4211
+ };
4212
+ }
4213
+ /** What one line of a Map Block's body says, or null when it is not a place or a route. */
4214
+ function readMapLine(text) {
4215
+ const trimmed = text.trim();
4216
+ if (trimmed === "") return null;
4217
+ for (let at = trimmed.lastIndexOf("@"); at >= 0; at = at === 0 ? -1 : trimmed.lastIndexOf("@", at - 1)) {
4218
+ const line = readLocation(trimmed.slice(at + 1), trimmed.slice(0, at).trim());
4219
+ if (line) return line;
4220
+ }
4221
+ return readLocation(trimmed, "");
4222
+ }
4223
+ /** Every item in a Map Block's body, numbered by line. Blank lines are skipped, not reported. */
4224
+ function readMapBody(lines) {
4225
+ const body = {
4226
+ items: [],
4227
+ unread: []
4228
+ };
4229
+ lines.forEach((text, line) => {
4230
+ if (text.trim() === "") return;
4231
+ const read = readMapLine(text);
4232
+ if (read) body.items.push({
4233
+ ...read,
4234
+ line
4235
+ });
4236
+ else body.unread.push({
4237
+ line,
4238
+ text
4239
+ });
4240
+ });
4241
+ return body;
4242
+ }
4243
+ /** How many names a picture's alt text lists before it counts the rest. */
4244
+ var NAMED_IN_ALT = 10;
4245
+ /**
4246
+ * The words for a picture of a map (a published Map Block, ADR 0118): what is on it, by name,
4247
+ * never where. Places and routes without a name are counted, and past the first ten names the
4248
+ * rest are counted too, so the text stays a sentence a screen reader can read out.
4249
+ */
4250
+ function mapAltText(body) {
4251
+ const { items } = readMapBody(body);
4252
+ if (items.length === 0) return "An empty map";
4253
+ const named = items.filter((item) => item.name !== "").map((item) => item.name);
4254
+ const parts = named.slice(0, NAMED_IN_ALT);
4255
+ const more = named.length - parts.length;
4256
+ if (more > 0) parts.push(`${more} more`);
4257
+ const unnamedPlaces = items.filter((item) => item.name === "" && item.kind === "place").length;
4258
+ const unnamedRoutes = items.filter((item) => item.name === "" && item.kind === "route").length;
4259
+ if (unnamedPlaces > 0) parts.push(`${unnamedPlaces} unnamed ${unnamedPlaces === 1 ? "place" : "places"}`);
4260
+ if (unnamedRoutes > 0) parts.push(`${unnamedRoutes} unnamed ${unnamedRoutes === 1 ? "route" : "routes"}`);
4261
+ return `Map of ${parts.length === 1 ? parts[0] : `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`}`;
4262
+ }
4263
+ //#endregion
4036
4264
  //#region ../client/src/lib/document/protection/fence-info.ts
4037
4265
  /**
4038
4266
  * The one name for the protected fence, and the one way to recognise one in raw text.
@@ -4296,7 +4524,7 @@ function blocksAndLinks(text) {
4296
4524
  }
4297
4525
  /** Derive the index rows for a single document's text. Pure. */
4298
4526
  function deriveDoc(text) {
4299
- const { blocks, links } = blocksAndLinks(text);
4527
+ const { blocks, links, lineToBlock } = blocksAndLinks(text);
4300
4528
  const candidateTasks = blocks.filter((b) => b.kind === "task");
4301
4529
  const fenced = candidateTasks.length === 0 ? () => false : fencedLines(text);
4302
4530
  const taskBlocks = candidateTasks.filter((b) => !fenced(b.startLine));
@@ -4319,7 +4547,67 @@ function deriveDoc(text) {
4319
4547
  scheduled: tags.scheduled
4320
4548
  };
4321
4549
  }),
4322
- taskConcepts: deriveTaskConcepts(blocks, links, taskBlocks)
4550
+ taskConcepts: deriveTaskConcepts(blocks, links, taskBlocks),
4551
+ ...deriveMaps(text, blocks, links, lineToBlock)
4552
+ };
4553
+ }
4554
+ /**
4555
+ * The [[Place]]s and [[Route]]s a document's [[Map Block]]s hold (ADR 0118), the [[Block
4556
+ * Concept]]s of the blocks they sit in, and what text search reads for those blocks instead of
4557
+ * their coordinates.
4558
+ *
4559
+ * A Map Block is a complete, outermost fence whose info word is `map`: one shown as an example
4560
+ * inside another fence is text. A document holding any protected content gives nothing, as the
4561
+ * Map View leaves Protected Documents out: the index is plaintext at rest, and the conservative
4562
+ * reading of a page with a cipher fence beside other text is the only safe one.
4563
+ */
4564
+ function deriveMaps(text, blocks, links, lineToBlock) {
4565
+ const searchText = /* @__PURE__ */ new Map();
4566
+ if (!text.includes("```map") || containsCipherFence(text)) return {
4567
+ mapItems: [],
4568
+ mapConcepts: [],
4569
+ searchText
4570
+ };
4571
+ const lines = text.split("\n");
4572
+ const { sorted, parent } = fencedBlockTree(fencedBlocks(lines));
4573
+ const mapItems = [];
4574
+ const mapBlocks = /* @__PURE__ */ new Set();
4575
+ const searchLines = /* @__PURE__ */ new Map();
4576
+ sorted.forEach((fence, i) => {
4577
+ if (parent[i] !== -1 || fenceLineInfo(lines[fence.start])?.info !== "map") return;
4578
+ const blockLocalId = lineToBlock.get(fence.start);
4579
+ if (blockLocalId === void 0) return;
4580
+ mapBlocks.add(blockLocalId);
4581
+ const body = lines.slice(fence.start + 1, fence.end).map((line) => codeLineText(line, fence.fenceColumn));
4582
+ body.forEach((_, n) => searchLines.set(fence.start + 1 + n, ""));
4583
+ for (const item of readMapBody(body).items) {
4584
+ const line = fence.start + 1 + item.line;
4585
+ searchLines.set(line, item.name);
4586
+ mapItems.push({
4587
+ blockLocalId,
4588
+ fenceLine: fence.start,
4589
+ line,
4590
+ kind: item.kind,
4591
+ name: item.name,
4592
+ text: body[item.line]
4593
+ });
4594
+ }
4595
+ });
4596
+ for (const id of mapBlocks) {
4597
+ const block = blocks[id];
4598
+ const read = block.text.split("\n").map((line, n) => searchLines.get(block.startLine + n) ?? line);
4599
+ searchText.set(id, read.join("\n"));
4600
+ }
4601
+ const byBlock = linksByBlock(links);
4602
+ const mapConcepts = [];
4603
+ if (byBlock.size > 0) for (const id of mapBlocks) for (const { concept } of conceptChain(blocks, byBlock, id)) mapConcepts.push({
4604
+ blockLocalId: id,
4605
+ concept
4606
+ });
4607
+ return {
4608
+ mapItems,
4609
+ mapConcepts,
4610
+ searchText
4323
4611
  };
4324
4612
  }
4325
4613
  /**
@@ -4841,6 +5129,15 @@ CREATE TABLE IF NOT EXISTS tasks (
4841
5129
  -- the canonical name in would go stale the moment a page is renamed.
4842
5130
  CREATE TABLE IF NOT EXISTS task_concepts (
4843
5131
  page_id INTEGER NOT NULL, block_local_id INTEGER NOT NULL, concept_key TEXT NOT NULL);
5132
+ -- [[Place]]s and [[Route]]s (ADR 0118): one row per line of a [[Map Block]] that reads, under the
5133
+ -- block its fence belongs to. text is the line as written; the query reads its points from it.
5134
+ CREATE TABLE IF NOT EXISTS map_items (
5135
+ page_id INTEGER NOT NULL, block_local_id INTEGER NOT NULL, fence_line INTEGER NOT NULL,
5136
+ line INTEGER NOT NULL, kind TEXT NOT NULL, name TEXT NOT NULL, text TEXT NOT NULL);
5137
+ -- The [[Block Concept]]s of each block holding a Map Block, as task_concepts holds a task's, so a
5138
+ -- [[Map View]] and a [[Kanban Board]] for one concept agree on what lies under it.
5139
+ CREATE TABLE IF NOT EXISTS map_concepts (
5140
+ page_id INTEGER NOT NULL, block_local_id INTEGER NOT NULL, concept_key TEXT NOT NULL);
4844
5141
  -- [[Passage]]s for [[Semantic Search]] (ADR 0076): the pieces of a document that get an
4845
5142
  -- [[Embedding]] each, keyed by the hash of their text (semantic/passages.ts). The vectors
4846
5143
  -- themselves live in the ATTACHed embeddings schema (semantic/embedding-db.ts), OUTSIDE this
@@ -4875,6 +5172,7 @@ CREATE INDEX IF NOT EXISTS tasks_page ON tasks(page_id);
4875
5172
  -- The Name Filter's whole selectivity: without it, filtering to a concept scans every task
4876
5173
  -- row in the graph. Mirrors links_concept_key, and like it stays put during a bulk rebuild.
4877
5174
  CREATE INDEX IF NOT EXISTS task_concepts_key ON task_concepts(concept_key);
5175
+ CREATE INDEX IF NOT EXISTS map_concepts_key ON map_concepts(concept_key);
4878
5176
  -- Search's text index. One row per block, so a document's rank can be "how many blocks
4879
5177
  -- mention this" - the ordering the results actually display. See the block_fts notes above
4880
5178
  -- createSchema for why unicode61 rather than trigram, and why the rowid encodes the block.
@@ -4907,7 +5205,7 @@ function createSchema(db) {
4907
5205
  db.exec(SCHEMA$1);
4908
5206
  db.run("INSERT OR IGNORE INTO index_metadata (key, value) VALUES ('active_generation', 1)");
4909
5207
  db.run("INSERT OR IGNORE INTO index_metadata (key, value) VALUES ('revision', 0)");
4910
- db.exec(`PRAGMA user_version = 15`);
5208
+ db.exec(`PRAGMA user_version = 16`);
4911
5209
  }
4912
5210
  function activeIndexGeneration(db) {
4913
5211
  return db.all("SELECT value FROM index_metadata WHERE key = 'active_generation'")[0]?.value ?? 1;
@@ -4928,7 +5226,7 @@ function advanceIndexRevision(db) {
4928
5226
  */
4929
5227
  function isUsableIndex(db) {
4930
5228
  try {
4931
- return db.all("PRAGMA user_version")[0]?.user_version === 15;
5229
+ return db.all("PRAGMA user_version")[0]?.user_version === 16;
4932
5230
  } catch {
4933
5231
  return false;
4934
5232
  }
@@ -5043,7 +5341,7 @@ function insertDerived(db, pageId, doc) {
5043
5341
  t.matchEnd,
5044
5342
  null
5045
5343
  ]);
5046
- const { blocks, links, tasks, taskConcepts } = deriveDoc(doc.text);
5344
+ const { blocks, links, tasks, taskConcepts, mapItems, mapConcepts, searchText } = deriveDoc(doc.text);
5047
5345
  for (const b of blocks) {
5048
5346
  db.run("INSERT INTO blocks (page_id, local_id, parent_local_id, ord, kind, depth, done, start_line, end_line, label, text) VALUES (?,?,?,?,?,?,?,?,?,?,?)", [
5049
5347
  pageId,
@@ -5059,8 +5357,9 @@ function insertDerived(db, pageId, doc) {
5059
5357
  b.text
5060
5358
  ]);
5061
5359
  if (isTextSearchable(b) && b.localId < 1048576) {
5062
- db.run("INSERT INTO block_fts (rowid, text) VALUES (?,?)", [pageId * BLOCK_FTS_STRIDE + b.localId, b.text]);
5063
- searchable.push(b.text);
5360
+ const text = searchText.get(b.localId) ?? b.text;
5361
+ db.run("INSERT INTO block_fts (rowid, text) VALUES (?,?)", [pageId * BLOCK_FTS_STRIDE + b.localId, text]);
5362
+ searchable.push(text);
5064
5363
  }
5065
5364
  }
5066
5365
  for (const l of links) db.run("INSERT INTO links (page_id, concept, concept_key, line, line_text, match_start, match_end, block_local_id) VALUES (?,?,?,?,?,?,?,?)", [
@@ -5090,7 +5389,12 @@ function insertDerived(db, pageId, doc) {
5090
5389
  t.scheduled
5091
5390
  ]);
5092
5391
  insertTaskConcepts(db, pageId, doc, tasks, taskConcepts);
5093
- for (const passage of derivePassages(doc.concept, blocks)) db.run("INSERT INTO passages (page_id, ord, start_line, end_line, first_block_local_id, content_hash, text) VALUES (?,?,?,?,?,?,?)", [
5392
+ insertMapRows(db, pageId, doc, mapItems, mapConcepts);
5393
+ const passageBlocks = searchText.size === 0 ? blocks : blocks.map((b) => ({
5394
+ ...b,
5395
+ text: searchText.get(b.localId) ?? b.text
5396
+ }));
5397
+ for (const passage of derivePassages(doc.concept, passageBlocks)) db.run("INSERT INTO passages (page_id, ord, start_line, end_line, first_block_local_id, content_hash, text) VALUES (?,?,?,?,?,?,?)", [
5094
5398
  pageId,
5095
5399
  passage.ord,
5096
5400
  passage.startLine,
@@ -5164,6 +5468,37 @@ function insertTaskConcepts(db, pageId, doc, tasks, taskConcepts) {
5164
5468
  for (const tc of taskConcepts) write(tc.blockLocalId, conceptKey$1(tc.concept));
5165
5469
  }
5166
5470
  /**
5471
+ * Write one document's [[Map Block]] rows (ADR 0118): its items, and the [[Block Concept]]s of
5472
+ * each block holding a Map Block. The document's own concept and the scopes in its name sit at
5473
+ * the root of every block's chain, added here as {@link insertTaskConcepts} adds them for tasks.
5474
+ */
5475
+ function insertMapRows(db, pageId, doc, mapItems, mapConcepts) {
5476
+ if (mapItems.length === 0) return;
5477
+ for (const item of mapItems) db.run("INSERT INTO map_items (page_id, block_local_id, fence_line, line, kind, name, text) VALUES (?,?,?,?,?,?,?)", [
5478
+ pageId,
5479
+ item.blockLocalId,
5480
+ item.fenceLine,
5481
+ item.line,
5482
+ item.kind,
5483
+ item.name,
5484
+ item.text
5485
+ ]);
5486
+ const written = /* @__PURE__ */ new Set();
5487
+ const write = (blockLocalId, key) => {
5488
+ const seen = `${blockLocalId} ${key}`;
5489
+ if (written.has(seen)) return;
5490
+ written.add(seen);
5491
+ db.run("INSERT INTO map_concepts (page_id, block_local_id, concept_key) VALUES (?,?,?)", [
5492
+ pageId,
5493
+ blockLocalId,
5494
+ key
5495
+ ]);
5496
+ };
5497
+ const rootKeys = documentTaskConcepts(doc.concept).map(conceptKey$1);
5498
+ for (const blockLocalId of new Set(mapItems.map((item) => item.blockLocalId))) for (const key of rootKeys) write(blockLocalId, key);
5499
+ for (const row of mapConcepts) write(row.blockLocalId, conceptKey$1(row.concept));
5500
+ }
5501
+ /**
5167
5502
  * Run `work` as ONE transaction. Without this every INSERT is its own implicit transaction,
5168
5503
  * which is most of what made a full rebuild cost seconds rather than milliseconds.
5169
5504
  */
@@ -5235,6 +5570,8 @@ function deleteGeneration(db, generation) {
5235
5570
  "links",
5236
5571
  "tasks",
5237
5572
  "task_concepts",
5573
+ "map_items",
5574
+ "map_concepts",
5238
5575
  "passages",
5239
5576
  "properties"
5240
5577
  ]) db.run(`DELETE FROM ${table} WHERE page_id IN (SELECT id FROM pages WHERE generation = ?)`, [generation]);
@@ -5339,6 +5676,8 @@ function deleteDerivedRows(db, pageId) {
5339
5676
  db.run("DELETE FROM links WHERE page_id = ?", [pageId]);
5340
5677
  db.run("DELETE FROM tasks WHERE page_id = ?", [pageId]);
5341
5678
  db.run("DELETE FROM task_concepts WHERE page_id = ?", [pageId]);
5679
+ db.run("DELETE FROM map_items WHERE page_id = ?", [pageId]);
5680
+ db.run("DELETE FROM map_concepts WHERE page_id = ?", [pageId]);
5342
5681
  db.run("DELETE FROM passages WHERE page_id = ?", [pageId]);
5343
5682
  db.run("DELETE FROM properties WHERE page_id = ?", [pageId]);
5344
5683
  }
@@ -6529,7 +6868,7 @@ function cacheRoot(env) {
6529
6868
  * recovered from a joined path (see there).
6530
6869
  */
6531
6870
  var CACHE_FILE_NAME = `local-cache.v${CACHE_DB_VERSION}.bin`;
6532
- var INDEX_FILE_NAME = `index.v15.sqlite`;
6871
+ var INDEX_FILE_NAME = `index.v16.sqlite`;
6533
6872
  var VECTORS_FILE_NAME = `vectors.v1.sqlite`;
6534
6873
  function cacheFile(dir) {
6535
6874
  return join(dir, CACHE_FILE_NAME);
@@ -10768,16 +11107,17 @@ async function hostRequest(endpoint, request, options = {}) {
10768
11107
  }
10769
11108
  /**
10770
11109
  * The settings a publish reads, which the command line decides even when the graph's host runs
10771
- * the publish: where the publish folders are remembered, and which browser draws diagrams. The
10772
- * host was started from another environment (an agent's), and a scheduled publish may name its
10773
- * own browser.
11110
+ * the publish: where the publish folders are remembered, which browser draws diagrams and maps,
11111
+ * and which style a map's picture is drawn over. The host was started from another environment
11112
+ * (an agent's), and a scheduled publish may name its own browser or its own map host.
10774
11113
  */
10775
11114
  var PUBLISH_SETTINGS = [
10776
11115
  "ETHERPK_MCP_PUBLISH_CONFIG",
10777
11116
  "XDG_CONFIG_HOME",
10778
11117
  "ETHERPK_CHROMIUM",
10779
11118
  "PLAYWRIGHT_BROWSERS_PATH",
10780
- "PLAYWRIGHT_CHROMIUM_EXECUTABLE"
11119
+ "PLAYWRIGHT_CHROMIUM_EXECUTABLE",
11120
+ "ETHERPK_MAP_STYLE_URL"
10781
11121
  ];
10782
11122
  /** The publish settings in `env`, for a `publish` request. */
10783
11123
  function publishSettings(env) {
@@ -11179,6 +11519,7 @@ async function writeSite(folder, bundle, options) {
11179
11519
  //#endregion
11180
11520
  //#region ../../packages/themes/src/index.ts
11181
11521
  var files = /* #__PURE__ */ Object.assign({
11522
+ "../etherpk-al-folio/assets/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 191.82 166.13\">\n <path fill=\"#0a0a0a\" fill-rule=\"evenodd\" d=\"M67.09,132.92,47.93,166.13H9.59L0,149.52,86.33,0H105.5L19.17,149.52H38.34l19.17-33.2h76.8l9.59,16.6Zm48-83.12,9.65,16.71H143.9L115.08,16.6q-24,41.57-48,83.12H143.9q14.37,24.9,28.76,49.8h-96l-9.59,16.61H182.24l9.58-16.61-38.34-66.4H96C95.85,82.9,113.39,52.73,115.08,49.8Z\"/>\n</svg>\n",
11182
11523
  "../etherpk-al-folio/assets/search.js": "/*\n * Search for a Published Site: reads search.json (built over the publication's own documents\n * and nothing else) and scores documents by title and text matches. No dependencies, no\n * network beyond the one index file, and nothing runs until the search box is used. The same\n * script runs the sidebar toggle on small screens, so a page carries one script.\n */\n(function () {\n 'use strict';\n\n // -- Navigation toggle (small screens) -------------------------------------------------\n var toggle = document.querySelector('.nav-toggle');\n var backdrop = document.querySelector('.nav-backdrop');\n var nav = document.getElementById('site-nav');\n function setNavOpen(open) {\n var was = document.body.classList.contains('nav-open');\n document.body.classList.toggle('nav-open', open);\n if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');\n if (backdrop) backdrop.hidden = !open;\n // Focus follows the drawer: into its first link on open, back to the button on close,\n // so a keyboard or screen-reader user is never left on something now hidden.\n if (open && !was) {\n var first = nav.querySelector('a');\n if (first) first.focus();\n } else if (!open && was && toggle) {\n toggle.focus();\n }\n }\n if (toggle && nav) {\n toggle.addEventListener('click', function () { setNavOpen(!document.body.classList.contains('nav-open')); });\n if (backdrop) backdrop.addEventListener('click', function () { setNavOpen(false); });\n document.addEventListener('keydown', function (e) {\n if (e.key === 'Escape' && document.body.classList.contains('nav-open')) setNavOpen(false);\n });\n }\n\n // -- Search --------------------------------------------------------------------------\n var form = document.querySelector('.site-search');\n if (!form) return;\n var input = form.querySelector('input[type=\"search\"]');\n var results = form.querySelector('.site-search-results');\n var indexUrl = form.getAttribute('data-search-index') || 'search.json';\n var scriptUrl = form.getAttribute('data-search-script') || 'search-index.js';\n var index = null;\n var loading = null;\n /* True once neither route produced the index: the results then say so rather than \"nothing matches\". */\n var unavailable = false;\n\n /* The index as a script assigning window.etherpkSearchIndex: what a site opened from disk can\n load, since file:// refuses fetch but allows <script src>. */\n function loadScript() {\n return new Promise(function (resolve, reject) {\n if (window.etherpkSearchIndex) { resolve(window.etherpkSearchIndex); return; }\n var s = document.createElement('script');\n s.src = scriptUrl;\n s.onload = function () {\n if (window.etherpkSearchIndex) resolve(window.etherpkSearchIndex);\n else reject(new Error('no index'));\n };\n s.onerror = function () { reject(new Error('no script')); };\n document.head.appendChild(s);\n });\n }\n\n function load() {\n if (index) return Promise.resolve(index);\n if (!loading) {\n loading = fetch(indexUrl).then(function (r) {\n if (!r.ok) throw new Error(String(r.status));\n return r.json();\n }).catch(loadScript).then(function (data) {\n index = (data && data.documents) || [];\n return index;\n }).catch(function () { unavailable = true; index = []; return index; });\n }\n return loading;\n }\n\n function tokens(q) {\n return q.toLowerCase().split(/\\s+/).filter(function (t) { return t.length > 0; });\n }\n\n function score(doc, terms) {\n var title = (doc.title || '').toLowerCase();\n var text = (doc.text || '').toLowerCase();\n var s = 0;\n for (var i = 0; i < terms.length; i++) {\n var t = terms[i];\n if (title === t) s += 20;\n else if (title.indexOf(t) === 0) s += 10;\n else if (title.indexOf(t) !== -1) s += 6;\n var at = text.indexOf(t);\n if (at !== -1) s += 2;\n else if (title.indexOf(t) === -1) return 0; // every term must match somewhere\n }\n return s;\n }\n\n function snippet(doc, terms) {\n var text = doc.text || '';\n var lower = text.toLowerCase();\n var at = -1;\n for (var i = 0; i < terms.length && at === -1; i++) at = lower.indexOf(terms[i]);\n if (at === -1) return doc.excerpt || '';\n var start = Math.max(0, at - 60);\n var end = Math.min(text.length, at + 100);\n return (start > 0 ? '…' : '') + text.slice(start, end).replace(/\\s+/g, ' ') + (end < text.length ? '…' : '');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n function render(list, q) {\n if (!q) { results.hidden = true; results.innerHTML = ''; return; }\n if (unavailable) {\n results.innerHTML = '<p class=\"site-search-empty\">The search index could not be loaded.</p>';\n results.hidden = false;\n return;\n }\n if (list.length === 0) {\n results.innerHTML = '<p class=\"site-search-empty\">Nothing matches \"' + escapeHtml(q) + '\".</p>';\n results.hidden = false;\n return;\n }\n results.innerHTML = list.map(function (hit) {\n return '<a class=\"site-search-hit\" role=\"option\" href=\"' + escapeHtml(hit.doc.url) + '\">' +\n '<span class=\"site-search-title\">' + escapeHtml(hit.doc.title) + '</span>' +\n '<span class=\"site-search-snippet\">' + escapeHtml(hit.snippet) + '</span></a>';\n }).join('');\n results.hidden = false;\n }\n\n var timer = null;\n function run() {\n var q = input.value.trim();\n var terms = tokens(q);\n if (terms.length === 0) { render([], ''); return; }\n load().then(function (docs) {\n var hits = [];\n for (var i = 0; i < docs.length; i++) {\n var s = score(docs[i], terms);\n if (s > 0) hits.push({ doc: docs[i], score: s, snippet: snippet(docs[i], terms) });\n }\n hits.sort(function (a, b) { return b.score - a.score || a.doc.title.localeCompare(b.doc.title); });\n render(hits.slice(0, 12), q);\n });\n }\n\n input.addEventListener('input', function () {\n clearTimeout(timer);\n timer = setTimeout(run, 120);\n });\n input.addEventListener('focus', function () { load(); });\n form.addEventListener('submit', function (e) {\n e.preventDefault();\n var first = results.querySelector('a');\n if (first) window.location.href = first.getAttribute('href');\n });\n document.addEventListener('keydown', function (e) {\n if (e.key === '/' && document.activeElement !== input && !/input|textarea/i.test(document.activeElement.tagName)) {\n e.preventDefault();\n input.focus();\n }\n if (e.key === 'Escape' && !results.hidden) { render([], ''); }\n });\n document.addEventListener('click', function (e) {\n if (!form.contains(e.target)) render([], '');\n });\n})();\n",
11183
11524
  "../etherpk-al-folio/assets/theme.css": "/* EtherPK's al-folio: after Jekyll's al-folio (alshedivat and contributors, MIT), written afresh\n for EtherPK's view - the academic personal site: a slim navbar with the name on the left and\n lower-case menu on the right, one theme colour for links and rules, an about page as the front\n page with a \"news\" list, plain post lists, and a footer that stays out of the way. System\n fonts, so the site loads nothing from anywhere. Classes the publisher emits and this file\n styles: .wikilink, .wikilink-missing, .task, .task-tag, .toc, .backlinks, .site-nav-list,\n .diagram, .math, .tok-* (code). */\n\n:root {\n --global-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n --global-font-mono: ui-monospace, \"Roboto Mono\", Menlo, Consolas, monospace;\n --global-bg-color: #ffffff;\n --global-code-bg-color: #fafafa;\n --global-text-color: #000000;\n --global-text-color-light: #828282;\n --global-theme-color: #b509ac;\n --global-hover-color: #b509ac;\n --global-hover-text-color: #ffffff;\n --global-footer-bg-color: #f2f3f3;\n --global-footer-text-color: #828282;\n --global-footer-link-color: #000000;\n --global-distill-app-color: #828282;\n --global-divider-color: rgba(0, 0, 0, 0.1);\n --global-card-bg-color: #ffffff;\n --global-highlight-color: #fff3a3;\n --global-missing-color: #b45309;\n --max-content-width: 800px;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n --global-bg-color: #1c1c1d;\n --global-code-bg-color: #2c2c2d;\n --global-text-color: #e8e8e8;\n --global-text-color-light: #e8e8e8;\n --global-theme-color: #2698ba;\n --global-hover-color: #2698ba;\n --global-hover-text-color: #ffffff;\n --global-footer-bg-color: #2c2c2d;\n --global-footer-text-color: #e8e8e8;\n --global-footer-link-color: #ffffff;\n --global-divider-color: #424246;\n --global-card-bg-color: #212529;\n --global-highlight-color: #6b5a00;\n --global-missing-color: #f59e0b;\n }\n}\n:root[data-theme=\"dark\"] {\n --global-bg-color: #1c1c1d;\n --global-code-bg-color: #2c2c2d;\n --global-text-color: #e8e8e8;\n --global-text-color-light: #e8e8e8;\n --global-theme-color: #2698ba;\n --global-hover-color: #2698ba;\n --global-hover-text-color: #ffffff;\n --global-footer-bg-color: #2c2c2d;\n --global-footer-text-color: #e8e8e8;\n --global-footer-link-color: #ffffff;\n --global-divider-color: #424246;\n --global-card-bg-color: #212529;\n --global-highlight-color: #6b5a00;\n --global-missing-color: #f59e0b;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { font-size: 16px; }\nbody {\n margin: 0;\n display: flex;\n flex-direction: column;\n min-height: 100vh;\n font-family: var(--global-font-family);\n line-height: 1.5;\n background: var(--global-bg-color);\n color: var(--global-text-color);\n -webkit-font-smoothing: antialiased;\n}\na { color: var(--global-theme-color); text-decoration: none; }\na:hover { color: var(--global-hover-color); text-decoration: underline; }\nimg { max-width: 100%; height: auto; }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--global-bg-color); padding: 8px 16px; z-index: 100; }\n.skip-link:focus { left: 16px; top: 16px; }\n.container { width: 100%; max-width: 1000px; margin: 0 auto; padding: 0 15px; }\n\n/* -- Navbar ----------------------------------------------------------------------------- */\n.navbar {\n position: sticky;\n top: 0;\n z-index: 20;\n background: var(--global-bg-color);\n border-bottom: 1px solid var(--global-divider-color);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n}\n.navbar-inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; max-width: 1000px; margin: 0 auto; padding: 10px 15px; }\n.navbar-brand { display: inline-flex; align-items: center; gap: 8px; font-size: 1.25rem; font-weight: 400; color: var(--global-text-color); }\n.navbar-brand:hover { color: var(--global-hover-color); text-decoration: none; }\n.site-logo { flex-shrink: 0; }\n.navbar-nav { display: flex; align-items: center; gap: 20px; }\n.site-nav-list { list-style: none; margin: 0; padding: 0; display: flex; align-items: center; gap: 4px; }\n.site-nav-list li { position: relative; }\n.site-nav-list li > a, .nav-group-label { display: block; padding: 8px 12px; color: var(--global-text-color); font-size: 1rem; text-transform: lowercase; }\n.site-nav-list li > a:hover { color: var(--global-hover-color); text-decoration: none; }\n.site-nav-list .nav-current > a { font-weight: 700; }\n.site-nav-list .nav-current > a::after { content: \"\"; display: block; width: 100%; height: 2px; margin-top: 2px; background: var(--global-theme-color); }\n.nav-group-label { color: var(--global-text-color-light); cursor: default; }\n.site-nav-list .nav-open > a, .site-nav-list .nav-open > .nav-group-label { color: var(--global-theme-color); }\n/* A nested outline level drops down under its parent, on hover or keyboard focus. */\n.site-nav-list .site-nav-list {\n display: none;\n position: absolute;\n top: 100%;\n left: 0;\n flex-direction: column;\n align-items: stretch;\n min-width: 12rem;\n padding: 6px 0;\n background: var(--global-bg-color);\n border: 1px solid var(--global-divider-color);\n border-radius: 4px;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);\n z-index: 25;\n}\n.site-nav-list li:hover > .site-nav-list, .site-nav-list li:focus-within > .site-nav-list { display: flex; }\n.site-nav-list .site-nav-list a { font-size: 0.95rem; white-space: nowrap; }\n.site-nav-list .site-nav-list .nav-current > a::after { display: none; }\n.theme-toggle { display: inline-flex; align-items: center; padding: 6px; border: 0; background: none; color: var(--global-text-color); cursor: pointer; }\n.theme-toggle:hover { color: var(--global-hover-color); }\n.theme-toggle .sun { display: none; }\n:root[data-theme=\"dark\"] .theme-toggle .sun { display: block; }\n:root[data-theme=\"dark\"] .theme-toggle .moon { display: none; }\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) .theme-toggle .sun { display: block; }\n :root:not([data-theme=\"light\"]) .theme-toggle .moon { display: none; }\n}\n.nav-toggle, .nav-backdrop { display: none; }\n.site-search { position: relative; }\n.site-search input {\n width: 11rem;\n padding: 6px 10px;\n border: 1px solid var(--global-divider-color);\n border-radius: 4px;\n background: var(--global-bg-color);\n color: var(--global-text-color);\n font: inherit;\n font-size: 0.95rem;\n}\n.site-search input:focus { outline: 2px solid var(--global-theme-color); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n width: min(26rem, 90vw);\n max-height: 24rem;\n overflow: auto;\n background: var(--global-bg-color);\n border: 1px solid var(--global-divider-color);\n border-radius: 4px;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 8px 12px; color: var(--global-text-color); border-bottom: 1px solid var(--global-divider-color); }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--global-code-bg-color); text-decoration: none; }\n.site-search-title { display: block; font-weight: 700; }\n.site-search-snippet { display: block; color: var(--global-text-color-light); font-size: 0.9rem; }\n.site-search-empty { margin: 0; padding: 8px 12px; color: var(--global-text-color-light); }\n\n/* -- Content ---------------------------------------------------------------------------- */\n.content { flex: 1; padding: 40px 0 60px; }\n.post-header { margin-bottom: 30px; }\n.post-title { margin: 0 0 6px; font-size: 2.5rem; font-weight: 300; line-height: 1.2; }\n.post-title .wikilink { color: inherit; }\n.post-meta { margin: 0; color: var(--global-text-color-light); font-size: 0.9rem; }\n.post-content { font-size: 1rem; }\n.post-content h1, .post-content h2, .post-content h3, .post-content h4 { margin: 2rem 0 0.75rem; font-weight: 500; line-height: 1.2; }\n.post-content h2 { font-size: 1.75rem; }\n.post-content h3 { font-size: 1.4rem; }\n.post-content h4 { font-size: 1.15rem; }\n.post-content p { margin: 0 0 1rem; }\n.post-content ul, .post-content ol { padding-left: 1.5rem; margin: 0 0 1rem; }\n.post-content li { margin: 0.2rem 0; }\n.post-content img { margin: 1rem 0; border-radius: 4px; }\n.post-content .diagram { margin: 1rem 0; text-align: center; }\n.post-content .diagram svg { max-width: 100%; height: auto; }\n.post-content blockquote { margin: 1rem 0; padding: 0.5rem 1rem; border-left: 5px solid var(--global-theme-color); color: var(--global-text-color-light); font-size: 1.1rem; }\n.post-content hr { border: 0; border-top: 1px solid var(--global-divider-color); margin: 2rem 0; }\n.post-content table { width: 100%; border-collapse: collapse; margin: 1rem 0; }\n.post-content th, .post-content td { padding: 0.5rem 0.75rem; border-top: 1px solid var(--global-divider-color); text-align: left; }\n.post-content thead th { border-bottom: 2px solid var(--global-divider-color); }\n.post-content code { padding: 2px 5px; background: var(--global-code-bg-color); border-radius: 3px; font-family: var(--global-font-mono); font-size: 0.9em; color: var(--global-theme-color); }\n.post-content pre { margin: 1rem 0; padding: 1rem; background: var(--global-code-bg-color); border: 1px solid var(--global-divider-color); border-radius: 4px; overflow-x: auto; font-family: var(--global-font-mono); font-size: 0.9rem; line-height: 1.5; }\n.post-content pre code { padding: 0; background: none; color: inherit; font-size: inherit; }\n.math-block { overflow-x: auto; margin: 1rem 0; }\nmark { background: var(--global-highlight-color); color: inherit; padding: 0 0.15em; border-radius: 2px; }\n\n/* Wikilinks and tasks. */\na.wikilink { color: var(--global-theme-color); }\na.wikilink-missing { color: var(--global-missing-color); text-decoration: underline dotted; }\nli.task { list-style: none; margin-left: -1.5rem; }\nli.task input[type=\"checkbox\"] { accent-color: var(--global-theme-color); margin-right: 6px; vertical-align: -0.1em; }\nli.task.done { color: var(--global-text-color-light); }\n.task-tag { display: inline-block; padding: 0 6px; margin-right: 4px; border: 1px solid var(--global-theme-color); border-radius: 3px; color: var(--global-theme-color); font-size: 0.8rem; font-weight: 500; }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* The table of contents, quietly boxed. */\n.toc { margin: 0 0 2rem; }\n.toc details { border: 1px solid var(--global-divider-color); border-radius: 4px; padding: 0.5rem 1rem; }\n.toc summary { cursor: pointer; font-weight: 500; }\n.toc ul { list-style: none; margin: 0.5rem 0 0; padding: 0; }\n.toc li { margin: 0.2rem 0; }\n.toc li.toc-level-3 { padding-left: 1rem; }\n.toc li.toc-level-4 { padding-left: 2rem; }\n\n/* The about page: news like al-folio's, a table with dates in the first column. */\n.about .post-title { font-size: 2.5rem; }\n.news { margin-top: 2.5rem; }\n.news h2, .posts h2 { font-size: 1.5rem; font-weight: 400; text-transform: lowercase; margin: 0 0 0.5rem; }\n.news h2 a { color: var(--global-text-color); }\n.news h2 a:hover { color: var(--global-hover-color); text-decoration: none; }\n.news-table { width: 100%; border-collapse: collapse; }\n.news-table th, .news-table td { padding: 0.35rem 0.5rem; border-top: 1px solid var(--global-divider-color); vertical-align: top; text-align: left; }\n.news-table th { width: 8rem; font-weight: 700; color: var(--global-text-color-light); white-space: nowrap; }\n.news-excerpt { color: var(--global-text-color-light); }\n.posts { margin-top: 2.5rem; }\n\n/* Post lists, the blog index. */\n.post-list { list-style: none; margin: 0; padding: 0; }\n.post-list li { padding: 1rem 0; border-bottom: 1px solid var(--global-divider-color); }\n.post-list li:last-child { border-bottom: 0; }\n.post-list h3 { margin: 0 0 0.25rem; font-size: 1.4rem; font-weight: 400; }\n/* The contents page of a docs site: the outline as a tree, then any page it leaves out. */\n.page-index-list { list-style: none; margin: 0; padding: 0; }\n.page-index-list .page-index-list { padding-left: 1.5rem; margin-top: 0.3rem; }\n.page-index-list li { margin: 0.3rem 0; }\n.page-index-group-label { color: var(--global-text-color-light); text-transform: lowercase; }\n.page-index-other { margin: 1.5rem 0 0.25rem; font-size: 1.1rem; font-weight: 400; color: var(--global-text-color-light); text-transform: lowercase; }\n/* The posts archive: a year, then one line per post. */\n.archive-year { margin: 1.5rem 0 0.25rem; font-size: 1.1rem; font-weight: 400; color: var(--global-text-color-light); }\n.archive-list li { padding: 0.35rem 0; border-bottom: 0; }\n.archive-list time { color: var(--global-text-color-light); margin-right: 0.75rem; font-variant-numeric: tabular-nums; }\n.all-posts { margin: 1rem 0 0; }\n.post-list h3 a { color: var(--global-text-color); }\n.post-list h3 a:hover { color: var(--global-hover-color); text-decoration: none; }\n.post-list p { margin: 0 0 0.25rem; }\n.post-list-empty { color: var(--global-text-color-light); }\n.post-nav { display: flex; justify-content: space-between; gap: 1rem; margin-top: 2.5rem; padding-top: 1rem; border-top: 1px solid var(--global-divider-color); }\n.post-nav .newer { margin-left: auto; }\n\n/* Linked references. */\n.backlinks { margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--global-divider-color); }\n.backlinks h2 { margin: 0 0 0.75rem; font-size: 1.25rem; font-weight: 500; }\n.backlink-list { list-style: none; margin: 0; padding: 0; }\n.backlink-source { margin: 0 0 0.75rem; }\n.backlink-source > a { font-weight: 500; }\n.backlink-source ul { list-style: none; margin: 0.25rem 0 0; padding: 0 0 0 1rem; border-left: 2px solid var(--global-divider-color); }\n.backlink-context { margin: 0.15rem 0; color: var(--global-text-color-light); font-size: 0.95rem; }\n\n/* -- Footer ----------------------------------------------------------------------------- */\n.footer { background: var(--global-footer-bg-color); color: var(--global-footer-text-color); font-size: 0.85rem; text-align: center; padding: 1rem 0; }\n.footer a { color: var(--global-footer-link-color); }\n.footer a:hover { color: var(--global-hover-color); }\n\n/* Code tokens (lezer classHighlighter names). */\n.tok-keyword { color: #0a7; }\n.tok-string, .tok-string2 { color: #690; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #999; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #905; }\n.tok-function, .tok-definition.tok-variableName { color: #c60; }\n.tok-typeName, .tok-className, .tok-namespace { color: #b58900; }\n.tok-operator, .tok-punctuation { color: #555; }\n.tok-propertyName, .tok-attributeName { color: #268bd2; }\n.tok-tagName { color: #22863a; }\n.tok-invalid { color: #d00; }\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) .tok-keyword { color: #4ec9b0; }\n :root:not([data-theme=\"light\"]) .tok-string, :root:not([data-theme=\"light\"]) .tok-string2 { color: #b5cea8; }\n :root:not([data-theme=\"light\"]) .tok-number, :root:not([data-theme=\"light\"]) .tok-atom { color: #d19a66; }\n :root:not([data-theme=\"light\"]) .tok-function { color: #dcdcaa; }\n :root:not([data-theme=\"light\"]) .tok-typeName, :root:not([data-theme=\"light\"]) .tok-className { color: #e5c07b; }\n :root:not([data-theme=\"light\"]) .tok-operator, :root:not([data-theme=\"light\"]) .tok-punctuation { color: #abb2bf; }\n :root:not([data-theme=\"light\"]) .tok-propertyName { color: #61afef; }\n :root:not([data-theme=\"light\"]) .tok-tagName { color: #98c379; }\n}\n:root[data-theme=\"dark\"] .tok-keyword { color: #4ec9b0; }\n:root[data-theme=\"dark\"] .tok-string, :root[data-theme=\"dark\"] .tok-string2 { color: #b5cea8; }\n:root[data-theme=\"dark\"] .tok-number, :root[data-theme=\"dark\"] .tok-atom { color: #d19a66; }\n:root[data-theme=\"dark\"] .tok-function { color: #dcdcaa; }\n:root[data-theme=\"dark\"] .tok-typeName, :root[data-theme=\"dark\"] .tok-className { color: #e5c07b; }\n:root[data-theme=\"dark\"] .tok-operator, :root[data-theme=\"dark\"] .tok-punctuation { color: #abb2bf; }\n:root[data-theme=\"dark\"] .tok-propertyName { color: #61afef; }\n:root[data-theme=\"dark\"] .tok-tagName { color: #98c379; }\n\n/* -- Small screens: the navbar collapses behind al-folio's three bars ------------------- */\n@media (max-width: 768px) {\n .nav-toggle {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 5px;\n width: 44px;\n height: 44px;\n padding: 10px;\n border: 1px solid var(--global-divider-color);\n border-radius: 4px;\n background: none;\n cursor: pointer;\n }\n .icon-bar { display: block; height: 2px; background: var(--global-text-color); border-radius: 1px; }\n .navbar-nav {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: min(20rem, 85vw);\n flex-direction: column;\n align-items: stretch;\n gap: 12px;\n padding: 30px 20px;\n background: var(--global-bg-color);\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);\n transform: translateX(100%);\n visibility: hidden;\n transition: transform 160ms ease-out, visibility 0s linear 160ms;\n z-index: 40;\n overflow-y: auto;\n }\n body.nav-open .navbar-nav { transform: translateX(0); visibility: visible; transition: transform 160ms ease-out; }\n body.nav-open .nav-backdrop { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35); z-index: 35; }\n .site-nav-list { flex-direction: column; align-items: stretch; }\n /* Inside the drawer every level is simply listed. */\n .site-nav-list .site-nav-list { display: flex; position: static; padding-left: 12px; border: 0; box-shadow: none; background: none; }\n .site-search input { width: 100%; }\n .site-search-results { left: 0; right: 0; width: auto; }\n .post-title, .about .post-title { font-size: 2rem; }\n}\n@media (prefers-reduced-motion: reduce) {\n .navbar-nav, body.nav-open .navbar-nav { transition: none; }\n}\n",
11184
11525
  "../etherpk-al-folio/assets/theme.js": "/*\n * The light / dark toggle: a `.theme-toggle` button flips the page between the two and the\n * choice is remembered in this browser; with no choice made the page follows the system. The\n * stylesheet keys on `data-theme` on <html>, and shell-top applies the remembered choice before\n * the first paint so a dark reader never sees a light flash.\n */\n(function () {\n 'use strict';\n var KEY = 'etherpk-theme';\n var root = document.documentElement;\n function isDark() {\n var chosen = root.getAttribute('data-theme');\n if (chosen === 'dark' || chosen === 'light') return chosen === 'dark';\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n function apply(theme) {\n if (theme === 'dark' || theme === 'light') root.setAttribute('data-theme', theme);\n else root.removeAttribute('data-theme');\n var buttons = document.querySelectorAll('.theme-toggle');\n for (var i = 0; i < buttons.length; i++) buttons[i].setAttribute('aria-pressed', isDark() ? 'true' : 'false');\n }\n try { apply(localStorage.getItem(KEY)); } catch (_e) { apply(null); }\n var toggles = document.querySelectorAll('.theme-toggle');\n for (var i = 0; i < toggles.length; i++) {\n toggles[i].addEventListener('click', function () {\n var next = isDark() ? 'light' : 'dark';\n apply(next);\n try { localStorage.setItem(KEY, next); } catch (_e) { /* private mode: the choice lasts the page */ }\n });\n }\n})();\n",
@@ -11190,6 +11531,7 @@ var files = /* #__PURE__ */ Object.assign({
11190
11531
  "../etherpk-al-folio/partials/after-content.html": "",
11191
11532
  "../etherpk-al-folio/partials/backlinks.html": " {{#page.hasBacklinks}}\n <section class=\"backlinks\" aria-labelledby=\"backlinks-heading\">\n <h2 id=\"backlinks-heading\">Linked references</h2>\n <ul class=\"backlink-list\">\n {{#page.backlinks}}\n <li class=\"backlink-source\">\n <a href=\"{{url}}\">{{title}}</a>\n <ul>\n {{#refs}}<li class=\"backlink-context\">{{{contextHtml}}}</li>{{/refs}}\n </ul>\n </li>\n {{/page.backlinks}}\n </ul>\n </section>\n {{/page.hasBacklinks}}\n",
11192
11533
  "../etherpk-al-folio/partials/before-content.html": "",
11534
+ "../etherpk-al-folio/partials/favicon.html": "<!-- The EtherPK icon in the browser tab: assets/favicon.svg, the same file the EtherPK apps use. Fill the \"favicon\" include with your own <link rel=\"icon\"> tag to replace it. -->\n<link rel=\"icon\" href=\"theme/favicon.svg\" type=\"image/svg+xml\">\n",
11193
11535
  "../etherpk-al-folio/partials/footer.html": " <footer class=\"footer\">\n <div class=\"container\">\n &copy; {{site.year}} {{site.title}}. Published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a>.{{#site.hasFeed}} <a href=\"feed.xml\">Feed</a>.{{/site.hasFeed}}\n </div>\n </footer>\n",
11194
11536
  "../etherpk-al-folio/partials/head.html": "",
11195
11537
  "../etherpk-al-folio/partials/header.html": " <header class=\"navbar\">\n <div class=\"navbar-inner\">\n <a class=\"navbar-brand site-title\" href=\"index.html\">{{> logo}}<span class=\"site-title-text\">{{site.title}}</span></a>\n <button class=\"nav-toggle navbar-toggler\" type=\"button\" aria-controls=\"site-nav\" aria-expanded=\"false\" aria-label=\"Menu\"><span class=\"icon-bar top-bar\"></span><span class=\"icon-bar middle-bar\"></span><span class=\"icon-bar bottom-bar\"></span></button>\n <div class=\"nav-backdrop\" hidden></div>\n <nav id=\"site-nav\" class=\"navbar-nav site-nav\" aria-label=\"Site\">\n <ul class=\"site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n </ul>\n <form class=\"site-search\" role=\"search\" data-search-index=\"{{site.search.url}}\" data-search-script=\"{{site.search.scriptUrl}}\">\n <label class=\"visually-hidden\" for=\"site-search-input\">Search</label>\n <input id=\"site-search-input\" type=\"search\" name=\"q\" placeholder=\"Search\" autocomplete=\"off\">\n <div class=\"site-search-results\" role=\"listbox\" hidden></div>\n </form>\n <button class=\"theme-toggle\" type=\"button\" aria-label=\"Toggle light and dark\" aria-pressed=\"false\">\n <svg class=\"moon\" viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>\n <svg class=\"sun\" viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/></svg>\n </button>\n </nav>\n </div>\n </header>\n",
@@ -11197,12 +11539,13 @@ var files = /* #__PURE__ */ Object.assign({
11197
11539
  "../etherpk-al-folio/partials/nav-item.html": " <li class=\"{{#current}}nav-current {{/current}}{{#open}}nav-open {{/open}}{{^href}}nav-group{{/href}}\">\n {{#href}}<a href=\"{{href}}\"{{#current}} aria-current=\"page\"{{/current}}{{#external}} rel=\"noopener\"{{/external}}>{{label}}</a>{{/href}}\n {{^href}}<span class=\"nav-group-label\" tabindex=\"0\">{{{labelHtml}}}</span>{{/href}}\n {{#hasChildren}}<ul class=\"site-nav-list\">{{#children}}{{> nav-item}}{{/children}}</ul>{{/hasChildren}}\n </li>\n",
11198
11540
  "../etherpk-al-folio/partials/scripts.html": "",
11199
11541
  "../etherpk-al-folio/partials/shell-bottom.html": " {{> after-content}}\n {{> backlinks}}\n </main>\n </div>\n {{> footer}}\n {{> theme-scripts}}\n {{> scripts}}\n</body>\n</html>\n",
11200
- "../etherpk-al-folio/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} | {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n <script>try{var t=localStorage.getItem('etherpk-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}<\/script>\n {{> head}}\n</head>\n<body class=\"layout-al-folio{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <div class=\"container\">\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11542
+ "../etherpk-al-folio/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} | {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n <script>try{var t=localStorage.getItem('etherpk-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}<\/script>\n {{> favicon}}\n {{> head}}\n</head>\n<body class=\"layout-al-folio{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <div class=\"container\">\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11201
11543
  "../etherpk-al-folio/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n <script src=\"theme/theme.js\" defer><\/script>\n",
11202
11544
  "../etherpk-al-folio/partials/toc.html": " {{#page.hasToc}}\n <nav class=\"toc\" aria-label=\"Table of contents\">\n <details>\n <summary>Contents</summary>\n <ul>\n {{#page.toc}}<li class=\"toc-level-{{level}}\"><a href=\"#{{id}}\">{{text}}</a></li>{{/page.toc}}\n </ul>\n </details>\n </nav>\n {{/page.hasToc}}\n",
11203
- "../etherpk-al-folio/theme.json": "{\n \"name\": \"etherpk-al-folio\",\n \"title\": \"EtherPK al-folio\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Jekyll's al-folio: an academic personal site with a slim top navbar, an about page as the front page, a news list from the journal, a plain post list and a light and dark toggle.\",\n \"kinds\": [\"docs\", \"blog\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The mark before the site title in the navbar: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The navbar. Replaces the theme's brand, menu, search box and theme toggle.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the theme toggle): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/search.js\", \"assets/theme.js\"\n ]\n}\n",
11545
+ "../etherpk-al-folio/theme.json": "{\n \"name\": \"etherpk-al-folio\",\n \"title\": \"EtherPK al-folio\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Jekyll's al-folio: an academic personal site with a slim top navbar, an about page as the front page, a news list from the journal, a plain post list and a light and dark toggle.\",\n \"kinds\": [\"docs\", \"blog\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The mark before the site title in the navbar: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"favicon\", \"description\": \"The icon in the browser tab: a <link rel=\\\"icon\\\"> tag. Raw HTML in the page body. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The navbar. Replaces the theme's brand, menu, search box and theme toggle.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the theme toggle): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/favicon.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/favicon.svg\", \"assets/search.js\", \"assets/theme.js\"\n ]\n}\n",
11546
+ "../etherpk-blog/assets/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 191.82 166.13\">\n <path fill=\"#0a0a0a\" fill-rule=\"evenodd\" d=\"M67.09,132.92,47.93,166.13H9.59L0,149.52,86.33,0H105.5L19.17,149.52H38.34l19.17-33.2h76.8l9.59,16.6Zm48-83.12,9.65,16.71H143.9L115.08,16.6q-24,41.57-48,83.12H143.9q14.37,24.9,28.76,49.8h-96l-9.59,16.61H182.24l9.58-16.61-38.34-66.4H96C95.85,82.9,113.39,52.73,115.08,49.8Z\"/>\n</svg>\n",
11204
11547
  "../etherpk-blog/assets/search.js": "/*\n * Search for a Published Site: reads search.json (built over the publication's own documents\n * and nothing else) and scores documents by title and text matches. No dependencies, no\n * network beyond the one index file, and nothing runs until the search box is used. The same\n * script runs the sidebar toggle on small screens, so a page carries one script.\n */\n(function () {\n 'use strict';\n\n // -- Navigation toggle (small screens) -------------------------------------------------\n var toggle = document.querySelector('.nav-toggle');\n var backdrop = document.querySelector('.nav-backdrop');\n var nav = document.getElementById('site-nav');\n function setNavOpen(open) {\n var was = document.body.classList.contains('nav-open');\n document.body.classList.toggle('nav-open', open);\n if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');\n if (backdrop) backdrop.hidden = !open;\n // Focus follows the drawer: into its first link on open, back to the button on close,\n // so a keyboard or screen-reader user is never left on something now hidden.\n if (open && !was) {\n var first = nav.querySelector('a');\n if (first) first.focus();\n } else if (!open && was && toggle) {\n toggle.focus();\n }\n }\n if (toggle && nav) {\n toggle.addEventListener('click', function () { setNavOpen(!document.body.classList.contains('nav-open')); });\n if (backdrop) backdrop.addEventListener('click', function () { setNavOpen(false); });\n document.addEventListener('keydown', function (e) {\n if (e.key === 'Escape' && document.body.classList.contains('nav-open')) setNavOpen(false);\n });\n }\n\n // -- Search --------------------------------------------------------------------------\n var form = document.querySelector('.site-search');\n if (!form) return;\n var input = form.querySelector('input[type=\"search\"]');\n var results = form.querySelector('.site-search-results');\n var indexUrl = form.getAttribute('data-search-index') || 'search.json';\n var scriptUrl = form.getAttribute('data-search-script') || 'search-index.js';\n var index = null;\n var loading = null;\n /* True once neither route produced the index: the results then say so rather than \"nothing matches\". */\n var unavailable = false;\n\n /* The index as a script assigning window.etherpkSearchIndex: what a site opened from disk can\n load, since file:// refuses fetch but allows <script src>. */\n function loadScript() {\n return new Promise(function (resolve, reject) {\n if (window.etherpkSearchIndex) { resolve(window.etherpkSearchIndex); return; }\n var s = document.createElement('script');\n s.src = scriptUrl;\n s.onload = function () {\n if (window.etherpkSearchIndex) resolve(window.etherpkSearchIndex);\n else reject(new Error('no index'));\n };\n s.onerror = function () { reject(new Error('no script')); };\n document.head.appendChild(s);\n });\n }\n\n function load() {\n if (index) return Promise.resolve(index);\n if (!loading) {\n loading = fetch(indexUrl).then(function (r) {\n if (!r.ok) throw new Error(String(r.status));\n return r.json();\n }).catch(loadScript).then(function (data) {\n index = (data && data.documents) || [];\n return index;\n }).catch(function () { unavailable = true; index = []; return index; });\n }\n return loading;\n }\n\n function tokens(q) {\n return q.toLowerCase().split(/\\s+/).filter(function (t) { return t.length > 0; });\n }\n\n function score(doc, terms) {\n var title = (doc.title || '').toLowerCase();\n var text = (doc.text || '').toLowerCase();\n var s = 0;\n for (var i = 0; i < terms.length; i++) {\n var t = terms[i];\n if (title === t) s += 20;\n else if (title.indexOf(t) === 0) s += 10;\n else if (title.indexOf(t) !== -1) s += 6;\n var at = text.indexOf(t);\n if (at !== -1) s += 2;\n else if (title.indexOf(t) === -1) return 0; // every term must match somewhere\n }\n return s;\n }\n\n function snippet(doc, terms) {\n var text = doc.text || '';\n var lower = text.toLowerCase();\n var at = -1;\n for (var i = 0; i < terms.length && at === -1; i++) at = lower.indexOf(terms[i]);\n if (at === -1) return doc.excerpt || '';\n var start = Math.max(0, at - 60);\n var end = Math.min(text.length, at + 100);\n return (start > 0 ? '…' : '') + text.slice(start, end).replace(/\\s+/g, ' ') + (end < text.length ? '…' : '');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n function render(list, q) {\n if (!q) { results.hidden = true; results.innerHTML = ''; return; }\n if (unavailable) {\n results.innerHTML = '<p class=\"site-search-empty\">The search index could not be loaded.</p>';\n results.hidden = false;\n return;\n }\n if (list.length === 0) {\n results.innerHTML = '<p class=\"site-search-empty\">Nothing matches \"' + escapeHtml(q) + '\".</p>';\n results.hidden = false;\n return;\n }\n results.innerHTML = list.map(function (hit) {\n return '<a class=\"site-search-hit\" role=\"option\" href=\"' + escapeHtml(hit.doc.url) + '\">' +\n '<span class=\"site-search-title\">' + escapeHtml(hit.doc.title) + '</span>' +\n '<span class=\"site-search-snippet\">' + escapeHtml(hit.snippet) + '</span></a>';\n }).join('');\n results.hidden = false;\n }\n\n var timer = null;\n function run() {\n var q = input.value.trim();\n var terms = tokens(q);\n if (terms.length === 0) { render([], ''); return; }\n load().then(function (docs) {\n var hits = [];\n for (var i = 0; i < docs.length; i++) {\n var s = score(docs[i], terms);\n if (s > 0) hits.push({ doc: docs[i], score: s, snippet: snippet(docs[i], terms) });\n }\n hits.sort(function (a, b) { return b.score - a.score || a.doc.title.localeCompare(b.doc.title); });\n render(hits.slice(0, 12), q);\n });\n }\n\n input.addEventListener('input', function () {\n clearTimeout(timer);\n timer = setTimeout(run, 120);\n });\n input.addEventListener('focus', function () { load(); });\n form.addEventListener('submit', function (e) {\n e.preventDefault();\n var first = results.querySelector('a');\n if (first) window.location.href = first.getAttribute('href');\n });\n document.addEventListener('keydown', function (e) {\n if (e.key === '/' && document.activeElement !== input && !/input|textarea/i.test(document.activeElement.tagName)) {\n e.preventDefault();\n input.focus();\n }\n if (e.key === 'Escape' && !results.hidden) { render([], ''); }\n });\n document.addEventListener('click', function (e) {\n if (!form.contains(e.target)) render([], '');\n });\n})();\n",
11205
- "../etherpk-blog/assets/theme.css": "/* EtherPK blog theme. Tokens follow the app's own (agent blue, the gk surface scale); the font\n is the system stack so the site loads nothing from anywhere. Dark mode follows the reader's\n preference. Classes the publisher emits and this file styles: .wikilink, .wikilink-missing,\n .task, .task-tag, .toc, .backlinks, .site-nav-list, .diagram, .math, .tok-* (code). */\n\n:root {\n --font-sans: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n --agent-500: #007acc;\n --agent-600: #005f9e;\n --agent-100: #c2e3ff;\n --surface-0: #ffffff;\n --surface-1: #f9fafb;\n --surface-2: #e5e7eb;\n --text-strong: #030712;\n --text-default: #374151;\n --text-muted: #6b7280;\n --border-soft: rgba(3, 7, 18, 0.08);\n --border-strong: rgba(3, 7, 18, 0.16);\n --mark: #fff3a3;\n --missing: #b45309;\n --nav-width: 17rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --agent-500: #5ab3ff;\n --agent-600: #94ccff;\n --agent-100: #002e4f;\n --surface-0: #18181b;\n --surface-1: #1c1c20;\n --surface-2: #2a2a30;\n --text-strong: #fafafa;\n --text-default: #d4d4d8;\n --text-muted: #a1a1aa;\n --border-soft: rgba(255, 255, 255, 0.08);\n --border-strong: rgba(255, 255, 255, 0.16);\n --mark: #6b5a00;\n --missing: #f59e0b;\n }\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { font-family: var(--font-sans); -webkit-font-smoothing: antialiased; }\nbody {\n margin: 0;\n color: var(--text-default);\n background: var(--surface-0);\n line-height: 1.6;\n font-size: 16px;\n}\na { color: var(--agent-500); }\na:hover { color: var(--agent-600); }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--surface-1); padding: 0.5rem 1rem; z-index: 100; }\n.skip-link:focus { left: 1rem; top: 1rem; }\n\n/* -- Header ----------------------------------------------------------------------------- */\n.site-header {\n display: flex;\n align-items: center;\n gap: 1rem;\n padding: 0.75rem 1.5rem;\n border-bottom: 1px solid var(--border-soft);\n background: var(--surface-1);\n position: sticky;\n top: 0;\n z-index: 20;\n}\n.site-title { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; font-weight: 600; color: var(--text-strong); text-decoration: none; font-size: 1.05rem; }\n.site-title:hover { color: var(--agent-500); }\n.site-logo { flex-shrink: 0; }\n.site-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.site-search { position: relative; margin-left: auto; flex: 0 1 22rem; }\n.site-search input {\n width: 100%;\n padding: 0.45rem 0.75rem;\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n background: var(--surface-0);\n color: var(--text-default);\n font: inherit;\n}\n.site-search input:focus { outline: 2px solid var(--agent-500); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n top: calc(100% + 0.35rem);\n left: 0;\n right: 0;\n max-height: 24rem;\n overflow: auto;\n background: var(--surface-0);\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 0.55rem 0.75rem; text-decoration: none; color: var(--text-default); border-bottom: 1px solid var(--border-soft); }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--surface-1); }\n.site-search-title { display: block; font-weight: 600; color: var(--text-strong); }\n.site-search-snippet { display: block; font-size: 0.9rem; color: var(--text-muted); }\n.site-search-empty { margin: 0; padding: 0.6rem 0.75rem; color: var(--text-muted); }\n\n/* -- Shell: top navigation, centred column --------------------------------------------- */\n.site-header { display: block; padding: 1rem 1.5rem 0; position: static; }\n.site-header-row { display: flex; align-items: center; gap: 1rem; max-width: 46rem; margin: 0 auto; }\n.site-title { font-size: 1.35rem; }\n.site-nav { max-width: 46rem; margin: 0.75rem auto 0; }\n.site-nav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }\n.site-nav-list .site-nav-list { display: block; padding-left: 0.75rem; }\n.site-nav a, .site-nav .nav-group-label { color: var(--text-default); text-decoration: none; font-size: 0.95rem; }\n.site-nav a:hover { color: var(--text-strong); }\n.site-nav .nav-current > a { color: var(--agent-500); font-weight: 600; }\n.site-nav .nav-group-label { font-weight: 600; color: var(--text-strong); }\n.site-nav a .wikilink { color: inherit; text-decoration: none; }\n.nav-toggle, .nav-backdrop { display: none; }\n\n.content { padding: 2rem 1.5rem 4rem; max-width: 46rem; margin: 0 auto; }\n.post-header h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 0.5rem; color: var(--text-strong); }\n.post-header h1 .wikilink { color: inherit; text-decoration: none; }\n.post-header h1 .wikilink:hover { text-decoration: underline; }\n.post-date { color: var(--text-muted); font-size: 0.95rem; }\n.post-body h2 { margin-top: 2rem; font-size: 1.4rem; color: var(--text-strong); }\n.post-body h3 { margin-top: 1.5rem; font-size: 1.15rem; color: var(--text-strong); }\n.post-body ul { padding-left: 1.4rem; }\n.post-body li { margin: 0.2rem 0; }\n.post-body img { max-width: 100%; height: auto; border-radius: 0.35rem; }\n.post-body table { border-collapse: collapse; margin: 1rem 0; width: 100%; }\n.post-body th, .post-body td { border: 1px solid var(--border-strong); padding: 0.4rem 0.6rem; text-align: left; }\n.post-body th { background: var(--surface-1); }\n.post-body blockquote { border-left: 3px solid var(--border-strong); margin: 1rem 0; padding: 0.25rem 1rem; color: var(--text-muted); }\n.post-body code { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface-1); padding: 0.1em 0.35em; border-radius: 0.25rem; }\n.post-body pre { background: var(--surface-1); border: 1px solid var(--border-soft); border-radius: 0.5rem; padding: 0.9rem 1rem; overflow-x: auto; }\n.post-body pre code { background: none; padding: 0; font-size: 0.88rem; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; border-radius: 0.2em; }\n\n/* Wikilinks, as the editor colours them. A missing target is visibly missing. */\na.wikilink { text-decoration: none; border-bottom: 1px solid var(--agent-100); }\na.wikilink:hover { border-bottom-color: var(--agent-500); }\na.wikilink-missing { color: var(--missing); text-decoration: none; border-bottom: 1px dashed var(--missing); }\n\n/* Tasks and their badges. */\nli.task { list-style: none; margin-left: -1.4rem; padding-left: 0; }\nli.task input[type=\"checkbox\"] { margin-right: 0.35rem; vertical-align: -0.1em; }\nli.task.done { color: var(--text-muted); }\n.task-tag { display: inline-block; font-size: 0.8rem; font-weight: 600; padding: 0.05em 0.5em; border-radius: 999px; background: var(--surface-2); color: var(--text-strong); margin-right: 0.25rem; vertical-align: 0.1em; }\n.task-tag.priority-1 { background: #fee2e2; color: #991b1b; }\n.task-tag.priority-2 { background: #ffedd5; color: #9a3412; }\n.task-tag.priority-3 { background: #fef9c3; color: #854d0e; }\n.task-tag.waiting, .task-tag.doing { background: var(--agent-100); color: var(--agent-600); }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Table of contents, diagrams, maths. */\n.toc { margin: 0 0 1.5rem; font-size: 0.95rem; }\n.toc summary { cursor: pointer; font-weight: 600; color: var(--text-strong); }\n.toc ul { list-style: none; padding-left: 0; margin: 0.4rem 0 0; }\n.toc li.toc-level-3 { padding-left: 1rem; }\n.toc li.toc-level-4 { padding-left: 2rem; }\n.diagram { margin: 1rem 0; text-align: center; }\n.diagram svg { max-width: 100%; height: auto; }\n.math-block { overflow-x: auto; margin: 1rem 0; }\n\n/* Linked references, beneath the content. */\n.backlinks { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border-soft); }\n.backlinks h2 { font-size: 1.1rem; color: var(--text-strong); margin: 0 0 0.75rem; }\n.backlink-list { list-style: none; padding: 0; margin: 0; }\n.backlink-source { margin: 0 0 0.9rem; }\n.backlink-source > a { font-weight: 600; }\n.backlink-source ul { list-style: none; padding-left: 1rem; margin: 0.25rem 0 0; border-left: 2px solid var(--border-soft); }\n.backlink-context { color: var(--text-muted); font-size: 0.95rem; margin: 0.15rem 0; }\n\n/* Archive and index lists. */\n.journal-archive, .page-index-list { list-style: none; padding: 0; }\n.journal-archive li { margin: 0 0 1rem; }\n.journal-archive time { color: var(--text-muted); margin-right: 0.5rem; font-variant-numeric: tabular-nums; }\n.archive-excerpt { margin: 0.2rem 0 0; color: var(--text-muted); }\n.archive-year { margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }\n.all-posts { margin: 1.5rem 0 0; }\n\n.site-footer { border-top: 1px solid var(--border-soft); padding: 1rem 1.5rem; color: var(--text-muted); font-size: 0.9rem; text-align: center; }\n\n/* Code tokens (lezer classHighlighter names). */\n.tok-keyword { color: #0a7; }\n.tok-string, .tok-string2 { color: #690; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #999; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #905; }\n.tok-function, .tok-definition.tok-variableName { color: #c60; }\n.tok-typeName, .tok-className, .tok-namespace { color: #b58900; }\n.tok-operator, .tok-punctuation { color: #555; }\n.tok-propertyName, .tok-attributeName { color: #268bd2; }\n.tok-tagName { color: #22863a; }\n.tok-invalid { color: #d00; }\n@media (prefers-color-scheme: dark) {\n .tok-keyword { color: #4ec9b0; }\n .tok-string, .tok-string2 { color: #b5cea8; }\n .tok-comment, .tok-lineComment, .tok-blockComment { color: #7f848e; }\n .tok-number, .tok-bool, .tok-null, .tok-atom { color: #d19a66; }\n .tok-function, .tok-definition.tok-variableName { color: #dcdcaa; }\n .tok-typeName, .tok-className, .tok-namespace { color: #e5c07b; }\n .tok-operator, .tok-punctuation { color: #abb2bf; }\n .tok-propertyName, .tok-attributeName { color: #61afef; }\n .tok-tagName { color: #98c379; }\n}\n\n/* -- Posts ------------------------------------------------------------------------------ */\n.post-summary { margin: 0 0 2rem; }\n.post-summary h2 { margin: 0 0 0.2rem; font-size: 1.35rem; }\n.post-summary h2 a { color: var(--text-strong); text-decoration: none; }\n.post-summary h2 a:hover { color: var(--agent-500); }\n.post-summary time { color: var(--text-muted); font-size: 0.9rem; font-variant-numeric: tabular-nums; }\n.post-summary p { margin: 0.4rem 0 0; color: var(--text-default); }\n.home-intro { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border-soft); }\n.post-list-empty { color: var(--text-muted); }\n@media (max-width: 40rem) {\n .site-header-row { flex-wrap: wrap; }\n .site-search { flex-basis: 100%; margin-left: 0; }\n .content { padding: 1.25rem 1rem 3rem; }\n}\n",
11548
+ "../etherpk-blog/assets/theme.css": "/* EtherPK blog theme. Tokens follow the app's own (agent blue, the gk surface scale); the font\n is the system stack so the site loads nothing from anywhere. Dark mode follows the reader's\n preference. Classes the publisher emits and this file styles: .wikilink, .wikilink-missing,\n .task, .task-tag, .toc, .backlinks, .site-nav-list, .diagram, .math, .tok-* (code). */\n\n:root {\n --font-sans: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n --agent-500: #007acc;\n --agent-600: #005f9e;\n --agent-100: #c2e3ff;\n --surface-0: #ffffff;\n --surface-1: #f9fafb;\n --surface-2: #e5e7eb;\n --text-strong: #030712;\n --text-default: #374151;\n --text-muted: #6b7280;\n --border-soft: rgba(3, 7, 18, 0.08);\n --border-strong: rgba(3, 7, 18, 0.16);\n --mark: #fff3a3;\n --missing: #b45309;\n --nav-width: 17rem;\n /* The width of the text column. The header, the navigation and the post share it, so the\n site title lines up with the text beneath it. Pictures are capped to it as well. */\n --content-width: 52rem;\n --gutter: 1.5rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --agent-500: #5ab3ff;\n --agent-600: #94ccff;\n --agent-100: #002e4f;\n --surface-0: #18181b;\n --surface-1: #1c1c20;\n --surface-2: #2a2a30;\n --text-strong: #fafafa;\n --text-default: #d4d4d8;\n --text-muted: #a1a1aa;\n --border-soft: rgba(255, 255, 255, 0.08);\n --border-strong: rgba(255, 255, 255, 0.16);\n --mark: #6b5a00;\n --missing: #f59e0b;\n }\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { font-family: var(--font-sans); -webkit-font-smoothing: antialiased; }\nbody {\n margin: 0;\n color: var(--text-default);\n background: var(--surface-0);\n line-height: 1.6;\n font-size: 16px;\n}\na { color: var(--agent-500); }\na:hover { color: var(--agent-600); }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--surface-1); padding: 0.5rem 1rem; z-index: 100; }\n.skip-link:focus { left: 1rem; top: 1rem; }\n\n/* -- Header ----------------------------------------------------------------------------- */\n.site-header {\n display: flex;\n align-items: center;\n gap: 1rem;\n padding: 0.75rem 1.5rem;\n border-bottom: 1px solid var(--border-soft);\n background: var(--surface-1);\n position: sticky;\n top: 0;\n z-index: 20;\n}\n.site-title { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; font-weight: 600; color: var(--text-strong); text-decoration: none; font-size: 1.05rem; }\n.site-title:hover { color: var(--agent-500); }\n.site-logo { flex-shrink: 0; }\n.site-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.site-search { position: relative; margin-left: auto; flex: 0 1 22rem; }\n.site-search input {\n width: 100%;\n padding: 0.45rem 0.75rem;\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n background: var(--surface-0);\n color: var(--text-default);\n font: inherit;\n}\n.site-search input:focus { outline: 2px solid var(--agent-500); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n top: calc(100% + 0.35rem);\n left: 0;\n right: 0;\n max-height: 24rem;\n overflow: auto;\n background: var(--surface-0);\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 0.55rem 0.75rem; text-decoration: none; color: var(--text-default); border-bottom: 1px solid var(--border-soft); }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--surface-1); }\n.site-search-title { display: block; font-weight: 600; color: var(--text-strong); }\n.site-search-snippet { display: block; font-size: 0.9rem; color: var(--text-muted); }\n.site-search-empty { margin: 0; padding: 0.6rem 0.75rem; color: var(--text-muted); }\n\n/* -- Shell: top navigation, centred column --------------------------------------------- */\n.site-header { display: block; padding: 1rem var(--gutter) 0; position: static; }\n.site-header-row { display: flex; align-items: center; gap: 1rem; max-width: var(--content-width); margin: 0 auto; }\n.site-title { font-size: 1.35rem; }\n.site-nav { max-width: var(--content-width); margin: 0.75rem auto 0; }\n.site-nav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }\n.site-nav-list .site-nav-list { display: block; padding-left: 0.75rem; }\n.site-nav a, .site-nav .nav-group-label { color: var(--text-default); text-decoration: none; font-size: 0.95rem; }\n.site-nav a:hover { color: var(--text-strong); }\n.site-nav .nav-current > a { color: var(--agent-500); font-weight: 600; }\n.site-nav .nav-group-label { font-weight: 600; color: var(--text-strong); }\n.site-nav a .wikilink { color: inherit; text-decoration: none; }\n.nav-toggle, .nav-backdrop { display: none; }\n\n/* The padding sits outside the text column (border-box would take it from inside). */\n.content { padding: 2rem var(--gutter) 4rem; max-width: calc(var(--content-width) + 2 * var(--gutter)); margin: 0 auto; }\n.post-header h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 0.5rem; color: var(--text-strong); }\n.post-header h1 .wikilink { color: inherit; text-decoration: none; }\n.post-header h1 .wikilink:hover { text-decoration: underline; }\n.post-date { color: var(--text-muted); font-size: 0.95rem; }\n.post-body h2 { margin-top: 2rem; font-size: 1.4rem; color: var(--text-strong); }\n.post-body h3 { margin-top: 1.5rem; font-size: 1.15rem; color: var(--text-strong); }\n.post-body ul { padding-left: 1.4rem; }\n.post-body li { margin: 0.2rem 0; }\n.post-body img { max-width: 100%; height: auto; border-radius: 0.35rem; }\n.post-body table { border-collapse: collapse; margin: 1rem 0; width: 100%; }\n.post-body th, .post-body td { border: 1px solid var(--border-strong); padding: 0.4rem 0.6rem; text-align: left; }\n.post-body th { background: var(--surface-1); }\n.post-body blockquote { border-left: 3px solid var(--border-strong); margin: 1rem 0; padding: 0.25rem 1rem; color: var(--text-muted); }\n.post-body code { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface-1); padding: 0.1em 0.35em; border-radius: 0.25rem; }\n.post-body pre { background: var(--surface-1); border: 1px solid var(--border-soft); border-radius: 0.5rem; padding: 0.9rem 1rem; overflow-x: auto; }\n.post-body pre code { background: none; padding: 0; font-size: 0.88rem; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; border-radius: 0.2em; }\n\n/* Wikilinks, as the editor colours them. A missing target is visibly missing. */\na.wikilink { text-decoration: none; border-bottom: 1px solid var(--agent-100); }\na.wikilink:hover { border-bottom-color: var(--agent-500); }\na.wikilink-missing { color: var(--missing); text-decoration: none; border-bottom: 1px dashed var(--missing); }\n\n/* Tasks and their badges. */\nli.task { list-style: none; margin-left: -1.4rem; padding-left: 0; }\nli.task input[type=\"checkbox\"] { margin-right: 0.35rem; vertical-align: -0.1em; }\nli.task.done { color: var(--text-muted); }\n.task-tag { display: inline-block; font-size: 0.8rem; font-weight: 600; padding: 0.05em 0.5em; border-radius: 999px; background: var(--surface-2); color: var(--text-strong); margin-right: 0.25rem; vertical-align: 0.1em; }\n.task-tag.priority-1 { background: #fee2e2; color: #991b1b; }\n.task-tag.priority-2 { background: #ffedd5; color: #9a3412; }\n.task-tag.priority-3 { background: #fef9c3; color: #854d0e; }\n.task-tag.waiting, .task-tag.doing { background: var(--agent-100); color: var(--agent-600); }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Table of contents, diagrams, maths. */\n.toc { margin: 0 0 1.5rem; font-size: 0.95rem; }\n.toc summary { cursor: pointer; font-weight: 600; color: var(--text-strong); }\n.toc ul { list-style: none; padding-left: 0; margin: 0.4rem 0 0; }\n.toc li.toc-level-3 { padding-left: 1rem; }\n.toc li.toc-level-4 { padding-left: 2rem; }\n.diagram { margin: 1rem 0; text-align: center; }\n.diagram svg { max-width: 100%; height: auto; }\n.math-block { overflow-x: auto; margin: 1rem 0; }\n\n/* Linked references, beneath the content. */\n.backlinks { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border-soft); }\n.backlinks h2 { font-size: 1.1rem; color: var(--text-strong); margin: 0 0 0.75rem; }\n.backlink-list { list-style: none; padding: 0; margin: 0; }\n.backlink-source { margin: 0 0 0.9rem; }\n.backlink-source > a { font-weight: 600; }\n.backlink-source ul { list-style: none; padding-left: 1rem; margin: 0.25rem 0 0; border-left: 2px solid var(--border-soft); }\n.backlink-context { color: var(--text-muted); font-size: 0.95rem; margin: 0.15rem 0; }\n\n/* Archive and index lists. */\n.journal-archive, .page-index-list { list-style: none; padding: 0; }\n.journal-archive li { margin: 0 0 1rem; }\n.journal-archive time { color: var(--text-muted); margin-right: 0.5rem; font-variant-numeric: tabular-nums; }\n.archive-excerpt { margin: 0.2rem 0 0; color: var(--text-muted); }\n.archive-year { margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }\n.all-posts { margin: 1.5rem 0 0; }\n\n.site-footer { border-top: 1px solid var(--border-soft); padding: 1rem 1.5rem; color: var(--text-muted); font-size: 0.9rem; text-align: center; }\n\n/* Code tokens (lezer classHighlighter names). */\n.tok-keyword { color: #0a7; }\n.tok-string, .tok-string2 { color: #690; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #999; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #905; }\n.tok-function, .tok-definition.tok-variableName { color: #c60; }\n.tok-typeName, .tok-className, .tok-namespace { color: #b58900; }\n.tok-operator, .tok-punctuation { color: #555; }\n.tok-propertyName, .tok-attributeName { color: #268bd2; }\n.tok-tagName { color: #22863a; }\n.tok-invalid { color: #d00; }\n@media (prefers-color-scheme: dark) {\n .tok-keyword { color: #4ec9b0; }\n .tok-string, .tok-string2 { color: #b5cea8; }\n .tok-comment, .tok-lineComment, .tok-blockComment { color: #7f848e; }\n .tok-number, .tok-bool, .tok-null, .tok-atom { color: #d19a66; }\n .tok-function, .tok-definition.tok-variableName { color: #dcdcaa; }\n .tok-typeName, .tok-className, .tok-namespace { color: #e5c07b; }\n .tok-operator, .tok-punctuation { color: #abb2bf; }\n .tok-propertyName, .tok-attributeName { color: #61afef; }\n .tok-tagName { color: #98c379; }\n}\n\n/* -- Posts ------------------------------------------------------------------------------ */\n.post-summary { margin: 0 0 2rem; }\n.post-summary h2 { margin: 0 0 0.2rem; font-size: 1.35rem; }\n.post-summary h2 a { color: var(--text-strong); text-decoration: none; }\n.post-summary h2 a:hover { color: var(--agent-500); }\n.post-summary time { color: var(--text-muted); font-size: 0.9rem; font-variant-numeric: tabular-nums; }\n.post-summary p { margin: 0.4rem 0 0; color: var(--text-default); }\n.home-intro { margin-bottom: 2rem; padding-bottom: 1.5rem; border-bottom: 1px solid var(--border-soft); }\n.post-list-empty { color: var(--text-muted); }\n@media (max-width: 40rem) {\n :root { --gutter: 1rem; }\n .site-header-row { flex-wrap: wrap; }\n .site-search { flex-basis: 100%; margin-left: 0; }\n .content { padding-top: 1.25rem; padding-bottom: 3rem; }\n}\n",
11206
11549
  "../etherpk-blog/layouts/404.html": "{{> shell-top}}\n <article class=\"post not-published\">\n <header class=\"post-header\">\n <h1>Not published</h1>\n </header>\n <div class=\"post-body\">\n <p>The page you followed a link to is not part of this site.</p>\n <p><a href=\"index.html\">Back to {{site.title}}</a></p>\n </div>\n </article>\n{{> shell-bottom}}\n",
11207
11550
  "../etherpk-blog/layouts/archive.html": "{{> shell-top}}\n <article class=\"post archive\">\n <header class=\"post-header\">\n <h1>{{#page.isPostsArchive}}Posts{{/page.isPostsArchive}}{{#page.isJournalArchive}}Journal{{/page.isJournalArchive}}</h1>\n </header>\n {{#page.isPostsArchive}}\n {{#site.postsByYear}}\n <h2 class=\"archive-year\" id=\"y{{year}}\">{{year}}</h2>\n <ul class=\"journal-archive\">\n {{#posts}}\n <li>\n <time datetime=\"{{date}}\">{{date}}</time>\n <a href=\"{{url}}\">{{title}}</a>\n </li>\n {{/posts}}\n </ul>\n {{/site.postsByYear}}\n {{/page.isPostsArchive}}\n {{#page.isJournalArchive}}\n <ul class=\"journal-archive\">\n {{#site.journals}}\n <li>\n <time datetime=\"{{date}}\">{{date}}</time>\n <a href=\"{{url}}\">{{title}}</a>\n {{#excerpt}}<p class=\"archive-excerpt\">{{excerpt}}</p>{{/excerpt}}\n </li>\n {{/site.journals}}\n </ul>\n {{/page.isJournalArchive}}\n </article>\n{{> shell-bottom}}\n",
11208
11551
  "../etherpk-blog/layouts/home.html": "{{> shell-top}}\n {{^page.isGeneratedIndex}}\n <section class=\"home-intro\">\n <div class=\"post-body\">\n{{{page.content}}}\n </div>\n </section>\n {{/page.isGeneratedIndex}}\n <section class=\"post-list\" aria-label=\"Posts\">\n {{#site.hasPosts}}\n {{#site.recentPosts}}\n <article class=\"post-summary\">\n <h2><a href=\"{{url}}\">{{title}}</a></h2>\n <time datetime=\"{{date}}\">{{date}}</time>\n {{#excerpt}}<p>{{excerpt}}</p>{{/excerpt}}\n </article>\n {{/site.recentPosts}}\n <p class=\"all-posts\"><a href=\"{{site.postsUrl}}\">All {{site.postCount}} posts →</a></p>\n {{/site.hasPosts}}\n {{^site.hasPosts}}\n <p class=\"post-list-empty\">No dated documents are published yet. A journal entry is dated by its day, a page by a <code>date:</code> line in its frontmatter.</p>\n {{/site.hasPosts}}\n </section>\n{{> shell-bottom}}\n",
@@ -11211,6 +11554,7 @@ var files = /* #__PURE__ */ Object.assign({
11211
11554
  "../etherpk-blog/partials/after-content.html": "",
11212
11555
  "../etherpk-blog/partials/backlinks.html": " {{#page.hasBacklinks}}\n <section class=\"backlinks\" aria-labelledby=\"backlinks-heading\">\n <h2 id=\"backlinks-heading\">Linked references</h2>\n <ul class=\"backlink-list\">\n {{#page.backlinks}}\n <li class=\"backlink-source\">\n <a href=\"{{url}}\">{{title}}</a>\n <ul>\n {{#refs}}<li class=\"backlink-context\">{{{contextHtml}}}</li>{{/refs}}\n </ul>\n </li>\n {{/page.backlinks}}\n </ul>\n </section>\n {{/page.hasBacklinks}}\n",
11213
11556
  "../etherpk-blog/partials/before-content.html": "",
11557
+ "../etherpk-blog/partials/favicon.html": "<!-- The EtherPK icon in the browser tab: assets/favicon.svg, the same file the EtherPK apps use. Fill the \"favicon\" include with your own <link rel=\"icon\"> tag to replace it. -->\n<link rel=\"icon\" href=\"theme/favicon.svg\" type=\"image/svg+xml\">\n",
11214
11558
  "../etherpk-blog/partials/footer.html": " <footer class=\"site-footer\">\n <p>{{site.title}}{{#site.hasFeed}} · <a href=\"feed.xml\">Feed</a>{{/site.hasFeed}} · published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a></p>\n </footer>\n",
11215
11559
  "../etherpk-blog/partials/head.html": "",
11216
11560
  "../etherpk-blog/partials/header.html": " <header class=\"site-header\">\n <div class=\"site-header-row\">\n <a class=\"site-title\" href=\"index.html\">{{> logo}}<span class=\"site-title-text\">{{site.title}}</span></a>\n <form class=\"site-search\" role=\"search\" data-search-index=\"{{site.search.url}}\" data-search-script=\"{{site.search.scriptUrl}}\">\n <label class=\"visually-hidden\" for=\"site-search-input\">Search</label>\n <input id=\"site-search-input\" type=\"search\" name=\"q\" placeholder=\"Search\" autocomplete=\"off\">\n <div class=\"site-search-results\" role=\"listbox\" hidden></div>\n </form>\n </div>\n <nav class=\"site-nav\" aria-label=\"Site\">\n <ul class=\"site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n </ul>\n </nav>\n </header>\n",
@@ -11218,11 +11562,12 @@ var files = /* #__PURE__ */ Object.assign({
11218
11562
  "../etherpk-blog/partials/nav-item.html": " <li class=\"{{#current}}nav-current {{/current}}{{#open}}nav-open {{/open}}{{^href}}nav-group{{/href}}\">\n {{#href}}<a href=\"{{href}}\"{{#current}} aria-current=\"page\"{{/current}}{{#external}} rel=\"noopener\"{{/external}}>{{label}}</a>{{/href}}\n {{^href}}<span class=\"nav-group-label\">{{{labelHtml}}}</span>{{/href}}\n {{#hasChildren}}<ul class=\"site-nav-list\">{{#children}}{{> nav-item}}{{/children}}</ul>{{/hasChildren}}\n </li>\n",
11219
11563
  "../etherpk-blog/partials/scripts.html": "",
11220
11564
  "../etherpk-blog/partials/shell-bottom.html": " {{> after-content}}\n {{> backlinks}}\n </main>\n {{> footer}}\n {{> theme-scripts}}\n {{> scripts}}\n</body>\n</html>\n",
11221
- "../etherpk-blog/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} · {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n {{> head}}\n</head>\n<body class=\"layout-blog{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11565
+ "../etherpk-blog/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} · {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n {{> favicon}}\n {{> head}}\n</head>\n<body class=\"layout-blog{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11222
11566
  "../etherpk-blog/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n",
11223
- "../etherpk-blog/theme.json": "{\n \"name\": \"etherpk-blog\",\n \"title\": \"EtherPK Blog\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"A blog: dated documents as posts newest first, a top navigation from the publication's outline, the feed, and search.\",\n \"kinds\": [\"blog\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The logo before the site title in the header: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's title, navigation and search box.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the sidebar drawer): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/backlinks.html\", \"partials/before-content.html\", \"partials/after-content.html\",\n \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/search.js\"\n ]\n}\n",
11567
+ "../etherpk-blog/theme.json": "{\n \"name\": \"etherpk-blog\",\n \"title\": \"EtherPK Blog\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"A blog: dated documents as posts newest first, a top navigation from the publication's outline, the feed, and search.\",\n \"kinds\": [\"blog\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The logo before the site title in the header: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"favicon\", \"description\": \"The icon in the browser tab: a <link rel=\\\"icon\\\"> tag. Raw HTML in the page body. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's title, navigation and search box.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the sidebar drawer): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/favicon.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/backlinks.html\", \"partials/before-content.html\", \"partials/after-content.html\",\n \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/favicon.svg\", \"assets/search.js\"\n ]\n}\n",
11568
+ "../etherpk-docs/assets/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 191.82 166.13\">\n <path fill=\"#0a0a0a\" fill-rule=\"evenodd\" d=\"M67.09,132.92,47.93,166.13H9.59L0,149.52,86.33,0H105.5L19.17,149.52H38.34l19.17-33.2h76.8l9.59,16.6Zm48-83.12,9.65,16.71H143.9L115.08,16.6q-24,41.57-48,83.12H143.9q14.37,24.9,28.76,49.8h-96l-9.59,16.61H182.24l9.58-16.61-38.34-66.4H96C95.85,82.9,113.39,52.73,115.08,49.8Z\"/>\n</svg>\n",
11224
11569
  "../etherpk-docs/assets/search.js": "/*\n * Search for a Published Site: reads search.json (built over the publication's own documents\n * and nothing else) and scores documents by title and text matches. No dependencies, no\n * network beyond the one index file, and nothing runs until the search box is used. The same\n * script runs the sidebar toggle on small screens, so a page carries one script.\n */\n(function () {\n 'use strict';\n\n // -- Navigation toggle (small screens) -------------------------------------------------\n var toggle = document.querySelector('.nav-toggle');\n var backdrop = document.querySelector('.nav-backdrop');\n var nav = document.getElementById('site-nav');\n function setNavOpen(open) {\n var was = document.body.classList.contains('nav-open');\n document.body.classList.toggle('nav-open', open);\n if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');\n if (backdrop) backdrop.hidden = !open;\n // Focus follows the drawer: into its first link on open, back to the button on close,\n // so a keyboard or screen-reader user is never left on something now hidden.\n if (open && !was) {\n var first = nav.querySelector('a');\n if (first) first.focus();\n } else if (!open && was && toggle) {\n toggle.focus();\n }\n }\n if (toggle && nav) {\n toggle.addEventListener('click', function () { setNavOpen(!document.body.classList.contains('nav-open')); });\n if (backdrop) backdrop.addEventListener('click', function () { setNavOpen(false); });\n document.addEventListener('keydown', function (e) {\n if (e.key === 'Escape' && document.body.classList.contains('nav-open')) setNavOpen(false);\n });\n }\n\n // -- Search --------------------------------------------------------------------------\n var form = document.querySelector('.site-search');\n if (!form) return;\n var input = form.querySelector('input[type=\"search\"]');\n var results = form.querySelector('.site-search-results');\n var indexUrl = form.getAttribute('data-search-index') || 'search.json';\n var scriptUrl = form.getAttribute('data-search-script') || 'search-index.js';\n var index = null;\n var loading = null;\n /* True once neither route produced the index: the results then say so rather than \"nothing matches\". */\n var unavailable = false;\n\n /* The index as a script assigning window.etherpkSearchIndex: what a site opened from disk can\n load, since file:// refuses fetch but allows <script src>. */\n function loadScript() {\n return new Promise(function (resolve, reject) {\n if (window.etherpkSearchIndex) { resolve(window.etherpkSearchIndex); return; }\n var s = document.createElement('script');\n s.src = scriptUrl;\n s.onload = function () {\n if (window.etherpkSearchIndex) resolve(window.etherpkSearchIndex);\n else reject(new Error('no index'));\n };\n s.onerror = function () { reject(new Error('no script')); };\n document.head.appendChild(s);\n });\n }\n\n function load() {\n if (index) return Promise.resolve(index);\n if (!loading) {\n loading = fetch(indexUrl).then(function (r) {\n if (!r.ok) throw new Error(String(r.status));\n return r.json();\n }).catch(loadScript).then(function (data) {\n index = (data && data.documents) || [];\n return index;\n }).catch(function () { unavailable = true; index = []; return index; });\n }\n return loading;\n }\n\n function tokens(q) {\n return q.toLowerCase().split(/\\s+/).filter(function (t) { return t.length > 0; });\n }\n\n function score(doc, terms) {\n var title = (doc.title || '').toLowerCase();\n var text = (doc.text || '').toLowerCase();\n var s = 0;\n for (var i = 0; i < terms.length; i++) {\n var t = terms[i];\n if (title === t) s += 20;\n else if (title.indexOf(t) === 0) s += 10;\n else if (title.indexOf(t) !== -1) s += 6;\n var at = text.indexOf(t);\n if (at !== -1) s += 2;\n else if (title.indexOf(t) === -1) return 0; // every term must match somewhere\n }\n return s;\n }\n\n function snippet(doc, terms) {\n var text = doc.text || '';\n var lower = text.toLowerCase();\n var at = -1;\n for (var i = 0; i < terms.length && at === -1; i++) at = lower.indexOf(terms[i]);\n if (at === -1) return doc.excerpt || '';\n var start = Math.max(0, at - 60);\n var end = Math.min(text.length, at + 100);\n return (start > 0 ? '…' : '') + text.slice(start, end).replace(/\\s+/g, ' ') + (end < text.length ? '…' : '');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n function render(list, q) {\n if (!q) { results.hidden = true; results.innerHTML = ''; return; }\n if (unavailable) {\n results.innerHTML = '<p class=\"site-search-empty\">The search index could not be loaded.</p>';\n results.hidden = false;\n return;\n }\n if (list.length === 0) {\n results.innerHTML = '<p class=\"site-search-empty\">Nothing matches \"' + escapeHtml(q) + '\".</p>';\n results.hidden = false;\n return;\n }\n results.innerHTML = list.map(function (hit) {\n return '<a class=\"site-search-hit\" role=\"option\" href=\"' + escapeHtml(hit.doc.url) + '\">' +\n '<span class=\"site-search-title\">' + escapeHtml(hit.doc.title) + '</span>' +\n '<span class=\"site-search-snippet\">' + escapeHtml(hit.snippet) + '</span></a>';\n }).join('');\n results.hidden = false;\n }\n\n var timer = null;\n function run() {\n var q = input.value.trim();\n var terms = tokens(q);\n if (terms.length === 0) { render([], ''); return; }\n load().then(function (docs) {\n var hits = [];\n for (var i = 0; i < docs.length; i++) {\n var s = score(docs[i], terms);\n if (s > 0) hits.push({ doc: docs[i], score: s, snippet: snippet(docs[i], terms) });\n }\n hits.sort(function (a, b) { return b.score - a.score || a.doc.title.localeCompare(b.doc.title); });\n render(hits.slice(0, 12), q);\n });\n }\n\n input.addEventListener('input', function () {\n clearTimeout(timer);\n timer = setTimeout(run, 120);\n });\n input.addEventListener('focus', function () { load(); });\n form.addEventListener('submit', function (e) {\n e.preventDefault();\n var first = results.querySelector('a');\n if (first) window.location.href = first.getAttribute('href');\n });\n document.addEventListener('keydown', function (e) {\n if (e.key === '/' && document.activeElement !== input && !/input|textarea/i.test(document.activeElement.tagName)) {\n e.preventDefault();\n input.focus();\n }\n if (e.key === 'Escape' && !results.hidden) { render([], ''); }\n });\n document.addEventListener('click', function (e) {\n if (!form.contains(e.target)) render([], '');\n });\n})();\n",
11225
- "../etherpk-docs/assets/theme.css": "/* EtherPK docs theme. Tokens follow the app's own (agent blue, the gk surface scale); the font\n is the system stack so the site loads nothing from anywhere. Dark mode follows the reader's\n preference. Classes the publisher emits and this file styles: .wikilink, .wikilink-missing,\n .task, .task-tag, .toc, .backlinks, .site-nav-list, .diagram, .math, .tok-* (code). */\n\n:root {\n --font-sans: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n --agent-500: #007acc;\n --agent-600: #005f9e;\n --agent-100: #c2e3ff;\n --surface-0: #ffffff;\n --surface-1: #f9fafb;\n --surface-2: #e5e7eb;\n --text-strong: #030712;\n --text-default: #374151;\n --text-muted: #6b7280;\n --border-soft: rgba(3, 7, 18, 0.08);\n --border-strong: rgba(3, 7, 18, 0.16);\n --mark: #fff3a3;\n --missing: #b45309;\n --nav-width: 17rem;\n /* The header is exactly this tall, so the sticky sidebar and the drawer toggle can be\n measured against it rather than guessing at the search box's height. */\n --header-height: 3.5rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --agent-500: #5ab3ff;\n --agent-600: #94ccff;\n --agent-100: #002e4f;\n --surface-0: #18181b;\n --surface-1: #1c1c20;\n --surface-2: #2a2a30;\n --text-strong: #fafafa;\n --text-default: #d4d4d8;\n --text-muted: #a1a1aa;\n --border-soft: rgba(255, 255, 255, 0.08);\n --border-strong: rgba(255, 255, 255, 0.16);\n --mark: #6b5a00;\n --missing: #f59e0b;\n }\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { font-family: var(--font-sans); -webkit-font-smoothing: antialiased; }\nbody {\n margin: 0;\n color: var(--text-default);\n background: var(--surface-0);\n line-height: 1.6;\n font-size: 16px;\n}\na { color: var(--agent-500); }\na:hover { color: var(--agent-600); }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--surface-1); padding: 0.5rem 1rem; z-index: 100; }\n.skip-link:focus { left: 1rem; top: 1rem; }\n\n/* -- Header ----------------------------------------------------------------------------- */\n.site-header {\n display: flex;\n align-items: center;\n gap: 1rem;\n height: var(--header-height);\n padding: 0 1.5rem;\n border-bottom: 1px solid var(--border-soft);\n background: var(--surface-1);\n position: sticky;\n top: 0;\n z-index: 20;\n}\n.site-title { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; font-weight: 600; color: var(--text-strong); text-decoration: none; font-size: 1.05rem; }\n.site-title:hover { color: var(--agent-500); }\n.site-logo { flex-shrink: 0; }\n.site-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.site-search { position: relative; margin-left: auto; flex: 0 1 22rem; }\n.site-search input {\n width: 100%;\n padding: 0.4rem 0.75rem;\n line-height: 1.25;\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n background: var(--surface-0);\n color: var(--text-default);\n font: inherit;\n}\n.site-search input:focus { outline: 2px solid var(--agent-500); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n top: calc(100% + 0.35rem);\n left: 0;\n right: 0;\n max-height: 24rem;\n overflow: auto;\n background: var(--surface-0);\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 0.55rem 0.75rem; text-decoration: none; color: var(--text-default); border-bottom: 1px solid var(--border-soft); }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--surface-1); }\n.site-search-title { display: block; font-weight: 600; color: var(--text-strong); }\n.site-search-snippet { display: block; font-size: 0.9rem; color: var(--text-muted); }\n.site-search-empty { margin: 0; padding: 0.6rem 0.75rem; color: var(--text-muted); }\n\n/* -- Shell: sidebar + content ------------------------------------------------------------ */\n.shell { display: grid; grid-template-columns: var(--nav-width) minmax(0, 1fr); min-height: calc(100vh - var(--header-height)); }\n.site-nav {\n border-right: 1px solid var(--border-soft);\n background: var(--surface-1);\n padding: 1.25rem 1rem;\n overflow-y: auto;\n position: sticky;\n top: var(--header-height);\n height: calc(100vh - var(--header-height));\n}\n.site-nav-list { list-style: none; margin: 0; padding: 0; }\n.site-nav-list .site-nav-list { padding-left: 0.85rem; border-left: 1px solid var(--border-soft); margin-left: 0.5rem; }\n.site-nav li { margin: 0.1rem 0; }\n.site-nav a, .site-nav .nav-group-label {\n display: block;\n padding: 0.3rem 0.6rem;\n border-radius: 0.4rem;\n color: var(--text-default);\n text-decoration: none;\n font-size: 0.95rem;\n line-height: 1.4;\n}\n.site-nav a:hover { background: var(--surface-2); color: var(--text-strong); }\n.site-nav .nav-current > a { background: var(--agent-100); color: var(--text-strong); font-weight: 600; }\n.site-nav .nav-group-label { font-weight: 600; color: var(--text-strong); margin-top: 0.75rem; }\n.site-nav a .wikilink { color: inherit; text-decoration: none; }\n.nav-toggle { display: none; }\n.nav-backdrop { display: none; }\n\n.content { padding: 2rem 3rem 4rem; max-width: 52rem; width: 100%; }\n.page-header h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 0.5rem; color: var(--text-strong); }\n.page-header h1 .wikilink { color: inherit; text-decoration: none; }\n.page-header h1 .wikilink:hover { text-decoration: underline; }\n.page-date { color: var(--text-muted); font-size: 0.95rem; }\n.page-body h2 { margin-top: 2rem; font-size: 1.4rem; color: var(--text-strong); }\n.page-body h3 { margin-top: 1.5rem; font-size: 1.15rem; color: var(--text-strong); }\n.page-body ul { padding-left: 1.4rem; }\n.page-body li { margin: 0.2rem 0; }\n.page-body img { max-width: 100%; height: auto; border-radius: 0.35rem; }\n.page-body table { border-collapse: collapse; margin: 1rem 0; width: 100%; }\n.page-body th, .page-body td { border: 1px solid var(--border-strong); padding: 0.4rem 0.6rem; text-align: left; }\n.page-body th { background: var(--surface-1); }\n.page-body blockquote { border-left: 3px solid var(--border-strong); margin: 1rem 0; padding: 0.25rem 1rem; color: var(--text-muted); }\n.page-body code { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface-1); padding: 0.1em 0.35em; border-radius: 0.25rem; }\n.page-body pre { background: var(--surface-1); border: 1px solid var(--border-soft); border-radius: 0.5rem; padding: 0.9rem 1rem; overflow-x: auto; }\n.page-body pre code { background: none; padding: 0; font-size: 0.88rem; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; border-radius: 0.2em; }\n\n/* Wikilinks, as the editor colours them. A missing target is visibly missing. */\na.wikilink { text-decoration: none; border-bottom: 1px solid var(--agent-100); }\na.wikilink:hover { border-bottom-color: var(--agent-500); }\na.wikilink-missing { color: var(--missing); text-decoration: none; border-bottom: 1px dashed var(--missing); }\n\n/* Tasks and their badges. */\nli.task { list-style: none; margin-left: -1.4rem; padding-left: 0; }\nli.task input[type=\"checkbox\"] { margin-right: 0.35rem; vertical-align: -0.1em; }\nli.task.done { color: var(--text-muted); }\n.task-tag { display: inline-block; font-size: 0.8rem; font-weight: 600; padding: 0.05em 0.5em; border-radius: 999px; background: var(--surface-2); color: var(--text-strong); margin-right: 0.25rem; vertical-align: 0.1em; }\n.task-tag.priority-1 { background: #fee2e2; color: #991b1b; }\n.task-tag.priority-2 { background: #ffedd5; color: #9a3412; }\n.task-tag.priority-3 { background: #fef9c3; color: #854d0e; }\n.task-tag.waiting, .task-tag.doing { background: var(--agent-100); color: var(--agent-600); }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Table of contents, diagrams, maths. */\n.toc { margin: 0 0 1.5rem; font-size: 0.95rem; }\n.toc summary { cursor: pointer; font-weight: 600; color: var(--text-strong); }\n.toc ul { list-style: none; padding-left: 0; margin: 0.4rem 0 0; }\n.toc li.toc-level-3 { padding-left: 1rem; }\n.toc li.toc-level-4 { padding-left: 2rem; }\n.diagram { margin: 1rem 0; text-align: center; }\n.diagram svg { max-width: 100%; height: auto; }\n.math-block { overflow-x: auto; margin: 1rem 0; }\n\n/* Linked references, beneath the content. */\n.backlinks { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border-soft); }\n.backlinks h2 { font-size: 1.1rem; color: var(--text-strong); margin: 0 0 0.75rem; }\n.backlink-list { list-style: none; padding: 0; margin: 0; }\n.backlink-source { margin: 0 0 0.9rem; }\n.backlink-source > a { font-weight: 600; }\n.backlink-source ul { list-style: none; padding-left: 1rem; margin: 0.25rem 0 0; border-left: 2px solid var(--border-soft); }\n.backlink-context { color: var(--text-muted); font-size: 0.95rem; margin: 0.15rem 0; }\n\n/* Archive and index lists. */\n.journal-archive, .page-index-list { list-style: none; padding: 0; }\n/* The contents page: the outline as a tree, then any page it leaves out. */\n.page-index-list .page-index-list { padding-left: 1.5rem; margin-top: 0.3rem; }\n.page-index-list li { margin: 0.4rem 0; }\n.page-index-group-label { color: var(--text-muted); font-weight: 600; }\n.page-index-other { margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }\n.journal-archive li { margin: 0 0 1rem; }\n.journal-archive time { color: var(--text-muted); margin-right: 0.5rem; font-variant-numeric: tabular-nums; }\n.archive-excerpt { margin: 0.2rem 0 0; color: var(--text-muted); }\n.archive-year { margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }\n\n.site-footer { border-top: 1px solid var(--border-soft); padding: 1rem 1.5rem; color: var(--text-muted); font-size: 0.9rem; text-align: center; }\n\n/* Code tokens (lezer classHighlighter names). */\n.tok-keyword { color: #0a7; }\n.tok-string, .tok-string2 { color: #690; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #999; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #905; }\n.tok-function, .tok-definition.tok-variableName { color: #c60; }\n.tok-typeName, .tok-className, .tok-namespace { color: #b58900; }\n.tok-operator, .tok-punctuation { color: #555; }\n.tok-propertyName, .tok-attributeName { color: #268bd2; }\n.tok-tagName { color: #22863a; }\n.tok-invalid { color: #d00; }\n@media (prefers-color-scheme: dark) {\n .tok-keyword { color: #4ec9b0; }\n .tok-string, .tok-string2 { color: #b5cea8; }\n .tok-comment, .tok-lineComment, .tok-blockComment { color: #7f848e; }\n .tok-number, .tok-bool, .tok-null, .tok-atom { color: #d19a66; }\n .tok-function, .tok-definition.tok-variableName { color: #dcdcaa; }\n .tok-typeName, .tok-className, .tok-namespace { color: #e5c07b; }\n .tok-operator, .tok-punctuation { color: #abb2bf; }\n .tok-propertyName, .tok-attributeName { color: #61afef; }\n .tok-tagName { color: #98c379; }\n}\n\n/* -- Small screens: the sidebar becomes a drawer ---------------------------------------- */\n@media (max-width: 56rem) {\n /* Content flows on its own; the drawer and its toggle are fixed, so the shell reserves no\n row for either (a grid row here stretched the toggle to the full page height). */\n .shell { display: block; min-height: 0; }\n .site-header { padding-right: 4.25rem; }\n .nav-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n position: fixed;\n top: 0.5rem;\n right: 1rem;\n width: 2.5rem;\n height: 2.5rem;\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n background: var(--surface-1);\n color: var(--text-strong);\n cursor: pointer;\n z-index: 45;\n }\n .nav-toggle:hover { background: var(--surface-2); }\n .nav-toggle:focus-visible { outline: 2px solid var(--agent-500); outline-offset: 1px; }\n .site-nav {\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n height: auto;\n width: min(20rem, 85vw);\n border-right: 1px solid var(--border-strong);\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);\n transform: translateX(-100%);\n /* Hidden while closed, so its links are not tab stops behind the page; the visibility\n change waits for the slide out. */\n visibility: hidden;\n transition: transform 160ms ease-out, visibility 0s linear 160ms;\n z-index: 40;\n }\n body.nav-open .site-nav { transform: translateX(0); visibility: visible; transition: transform 160ms ease-out; }\n body.nav-open .nav-backdrop { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35); z-index: 35; }\n .content { padding: 1rem 1.25rem 3rem; }\n .site-search { flex-basis: 12rem; }\n}\n@media (prefers-reduced-motion: reduce) {\n .site-nav { transition: none; }\n body.nav-open .site-nav { transition: none; }\n}\n",
11570
+ "../etherpk-docs/assets/theme.css": "/* EtherPK docs theme. Tokens follow the app's own (agent blue, the gk surface scale); the font\n is the system stack so the site loads nothing from anywhere. Dark mode follows the reader's\n preference. Classes the publisher emits and this file styles: .wikilink, .wikilink-missing,\n .task, .task-tag, .toc, .backlinks, .site-nav-list, .diagram, .math, .tok-* (code). */\n\n:root {\n --font-sans: \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n --agent-500: #007acc;\n --agent-600: #005f9e;\n --agent-100: #c2e3ff;\n --surface-0: #ffffff;\n --surface-1: #f9fafb;\n --surface-2: #e5e7eb;\n --text-strong: #030712;\n --text-default: #374151;\n --text-muted: #6b7280;\n --border-soft: rgba(3, 7, 18, 0.08);\n --border-strong: rgba(3, 7, 18, 0.16);\n --mark: #fff3a3;\n --missing: #b45309;\n --nav-width: 17rem;\n /* The header is exactly this tall, so the sticky sidebar and the drawer toggle can be\n measured against it rather than guessing at the search box's height. */\n --header-height: 3.5rem;\n /* The width of the text column beside the sidebar. Pictures are capped to it as well. */\n --content-width: 52rem;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --agent-500: #5ab3ff;\n --agent-600: #94ccff;\n --agent-100: #002e4f;\n --surface-0: #18181b;\n --surface-1: #1c1c20;\n --surface-2: #2a2a30;\n --text-strong: #fafafa;\n --text-default: #d4d4d8;\n --text-muted: #a1a1aa;\n --border-soft: rgba(255, 255, 255, 0.08);\n --border-strong: rgba(255, 255, 255, 0.16);\n --mark: #6b5a00;\n --missing: #f59e0b;\n }\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { font-family: var(--font-sans); -webkit-font-smoothing: antialiased; }\nbody {\n margin: 0;\n color: var(--text-default);\n background: var(--surface-0);\n line-height: 1.6;\n font-size: 16px;\n}\na { color: var(--agent-500); }\na:hover { color: var(--agent-600); }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--surface-1); padding: 0.5rem 1rem; z-index: 100; }\n.skip-link:focus { left: 1rem; top: 1rem; }\n\n/* -- Header ----------------------------------------------------------------------------- */\n.site-header {\n display: flex;\n align-items: center;\n gap: 1rem;\n height: var(--header-height);\n padding: 0 1.5rem;\n border-bottom: 1px solid var(--border-soft);\n background: var(--surface-1);\n position: sticky;\n top: 0;\n z-index: 20;\n}\n.site-title { display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; font-weight: 600; color: var(--text-strong); text-decoration: none; font-size: 1.05rem; }\n.site-title:hover { color: var(--agent-500); }\n.site-logo { flex-shrink: 0; }\n.site-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.site-search { position: relative; margin-left: auto; flex: 0 1 22rem; }\n.site-search input {\n width: 100%;\n padding: 0.4rem 0.75rem;\n line-height: 1.25;\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n background: var(--surface-0);\n color: var(--text-default);\n font: inherit;\n}\n.site-search input:focus { outline: 2px solid var(--agent-500); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n top: calc(100% + 0.35rem);\n left: 0;\n right: 0;\n max-height: 24rem;\n overflow: auto;\n background: var(--surface-0);\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 0.55rem 0.75rem; text-decoration: none; color: var(--text-default); border-bottom: 1px solid var(--border-soft); }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--surface-1); }\n.site-search-title { display: block; font-weight: 600; color: var(--text-strong); }\n.site-search-snippet { display: block; font-size: 0.9rem; color: var(--text-muted); }\n.site-search-empty { margin: 0; padding: 0.6rem 0.75rem; color: var(--text-muted); }\n\n/* -- Shell: sidebar + content ------------------------------------------------------------ */\n.shell { display: grid; grid-template-columns: var(--nav-width) minmax(0, 1fr); min-height: calc(100vh - var(--header-height)); }\n.site-nav {\n border-right: 1px solid var(--border-soft);\n background: var(--surface-1);\n padding: 1.25rem 1rem;\n overflow-y: auto;\n position: sticky;\n top: var(--header-height);\n height: calc(100vh - var(--header-height));\n}\n.site-nav-list { list-style: none; margin: 0; padding: 0; }\n.site-nav-list .site-nav-list { padding-left: 0.85rem; border-left: 1px solid var(--border-soft); margin-left: 0.5rem; }\n.site-nav li { margin: 0.1rem 0; }\n.site-nav a, .site-nav .nav-group-label {\n display: block;\n padding: 0.3rem 0.6rem;\n border-radius: 0.4rem;\n color: var(--text-default);\n text-decoration: none;\n font-size: 0.95rem;\n line-height: 1.4;\n}\n.site-nav a:hover { background: var(--surface-2); color: var(--text-strong); }\n.site-nav .nav-current > a { background: var(--agent-100); color: var(--text-strong); font-weight: 600; }\n.site-nav .nav-group-label { font-weight: 600; color: var(--text-strong); margin-top: 0.75rem; }\n.site-nav a .wikilink { color: inherit; text-decoration: none; }\n.nav-toggle { display: none; }\n.nav-backdrop { display: none; }\n\n/* The padding sits outside the text column (border-box would take it from inside). */\n.content { padding: 2rem 3rem 4rem; max-width: calc(var(--content-width) + 6rem); width: 100%; }\n.page-header h1 { font-size: 2rem; line-height: 1.2; margin: 0 0 0.5rem; color: var(--text-strong); }\n.page-header h1 .wikilink { color: inherit; text-decoration: none; }\n.page-header h1 .wikilink:hover { text-decoration: underline; }\n.page-date { color: var(--text-muted); font-size: 0.95rem; }\n.page-body h2 { margin-top: 2rem; font-size: 1.4rem; color: var(--text-strong); }\n.page-body h3 { margin-top: 1.5rem; font-size: 1.15rem; color: var(--text-strong); }\n.page-body ul { padding-left: 1.4rem; }\n.page-body li { margin: 0.2rem 0; }\n.page-body img { max-width: 100%; height: auto; border-radius: 0.35rem; }\n.page-body table { border-collapse: collapse; margin: 1rem 0; width: 100%; }\n.page-body th, .page-body td { border: 1px solid var(--border-strong); padding: 0.4rem 0.6rem; text-align: left; }\n.page-body th { background: var(--surface-1); }\n.page-body blockquote { border-left: 3px solid var(--border-strong); margin: 1rem 0; padding: 0.25rem 1rem; color: var(--text-muted); }\n.page-body code { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface-1); padding: 0.1em 0.35em; border-radius: 0.25rem; }\n.page-body pre { background: var(--surface-1); border: 1px solid var(--border-soft); border-radius: 0.5rem; padding: 0.9rem 1rem; overflow-x: auto; }\n.page-body pre code { background: none; padding: 0; font-size: 0.88rem; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; border-radius: 0.2em; }\n\n/* Wikilinks, as the editor colours them. A missing target is visibly missing. */\na.wikilink { text-decoration: none; border-bottom: 1px solid var(--agent-100); }\na.wikilink:hover { border-bottom-color: var(--agent-500); }\na.wikilink-missing { color: var(--missing); text-decoration: none; border-bottom: 1px dashed var(--missing); }\n\n/* Tasks and their badges. */\nli.task { list-style: none; margin-left: -1.4rem; padding-left: 0; }\nli.task input[type=\"checkbox\"] { margin-right: 0.35rem; vertical-align: -0.1em; }\nli.task.done { color: var(--text-muted); }\n.task-tag { display: inline-block; font-size: 0.8rem; font-weight: 600; padding: 0.05em 0.5em; border-radius: 999px; background: var(--surface-2); color: var(--text-strong); margin-right: 0.25rem; vertical-align: 0.1em; }\n.task-tag.priority-1 { background: #fee2e2; color: #991b1b; }\n.task-tag.priority-2 { background: #ffedd5; color: #9a3412; }\n.task-tag.priority-3 { background: #fef9c3; color: #854d0e; }\n.task-tag.waiting, .task-tag.doing { background: var(--agent-100); color: var(--agent-600); }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Table of contents, diagrams, maths. */\n.toc { margin: 0 0 1.5rem; font-size: 0.95rem; }\n.toc summary { cursor: pointer; font-weight: 600; color: var(--text-strong); }\n.toc ul { list-style: none; padding-left: 0; margin: 0.4rem 0 0; }\n.toc li.toc-level-3 { padding-left: 1rem; }\n.toc li.toc-level-4 { padding-left: 2rem; }\n.diagram { margin: 1rem 0; text-align: center; }\n.diagram svg { max-width: 100%; height: auto; }\n.math-block { overflow-x: auto; margin: 1rem 0; }\n\n/* Linked references, beneath the content. */\n.backlinks { margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--border-soft); }\n.backlinks h2 { font-size: 1.1rem; color: var(--text-strong); margin: 0 0 0.75rem; }\n.backlink-list { list-style: none; padding: 0; margin: 0; }\n.backlink-source { margin: 0 0 0.9rem; }\n.backlink-source > a { font-weight: 600; }\n.backlink-source ul { list-style: none; padding-left: 1rem; margin: 0.25rem 0 0; border-left: 2px solid var(--border-soft); }\n.backlink-context { color: var(--text-muted); font-size: 0.95rem; margin: 0.15rem 0; }\n\n/* Archive and index lists. */\n.journal-archive, .page-index-list { list-style: none; padding: 0; }\n/* The contents page: the outline as a tree, then any page it leaves out. */\n.page-index-list .page-index-list { padding-left: 1.5rem; margin-top: 0.3rem; }\n.page-index-list li { margin: 0.4rem 0; }\n.page-index-group-label { color: var(--text-muted); font-weight: 600; }\n.page-index-other { margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }\n.journal-archive li { margin: 0 0 1rem; }\n.journal-archive time { color: var(--text-muted); margin-right: 0.5rem; font-variant-numeric: tabular-nums; }\n.archive-excerpt { margin: 0.2rem 0 0; color: var(--text-muted); }\n.archive-year { margin: 1.5rem 0 0.5rem; font-size: 1.1rem; }\n\n.site-footer { border-top: 1px solid var(--border-soft); padding: 1rem 1.5rem; color: var(--text-muted); font-size: 0.9rem; text-align: center; }\n\n/* Code tokens (lezer classHighlighter names). */\n.tok-keyword { color: #0a7; }\n.tok-string, .tok-string2 { color: #690; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #999; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #905; }\n.tok-function, .tok-definition.tok-variableName { color: #c60; }\n.tok-typeName, .tok-className, .tok-namespace { color: #b58900; }\n.tok-operator, .tok-punctuation { color: #555; }\n.tok-propertyName, .tok-attributeName { color: #268bd2; }\n.tok-tagName { color: #22863a; }\n.tok-invalid { color: #d00; }\n@media (prefers-color-scheme: dark) {\n .tok-keyword { color: #4ec9b0; }\n .tok-string, .tok-string2 { color: #b5cea8; }\n .tok-comment, .tok-lineComment, .tok-blockComment { color: #7f848e; }\n .tok-number, .tok-bool, .tok-null, .tok-atom { color: #d19a66; }\n .tok-function, .tok-definition.tok-variableName { color: #dcdcaa; }\n .tok-typeName, .tok-className, .tok-namespace { color: #e5c07b; }\n .tok-operator, .tok-punctuation { color: #abb2bf; }\n .tok-propertyName, .tok-attributeName { color: #61afef; }\n .tok-tagName { color: #98c379; }\n}\n\n/* -- Small screens: the sidebar becomes a drawer ---------------------------------------- */\n@media (max-width: 56rem) {\n /* Content flows on its own; the drawer and its toggle are fixed, so the shell reserves no\n row for either (a grid row here stretched the toggle to the full page height). */\n .shell { display: block; min-height: 0; }\n .site-header { padding-right: 4.25rem; }\n .nav-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n position: fixed;\n top: 0.5rem;\n right: 1rem;\n width: 2.5rem;\n height: 2.5rem;\n border: 1px solid var(--border-strong);\n border-radius: 0.5rem;\n background: var(--surface-1);\n color: var(--text-strong);\n cursor: pointer;\n z-index: 45;\n }\n .nav-toggle:hover { background: var(--surface-2); }\n .nav-toggle:focus-visible { outline: 2px solid var(--agent-500); outline-offset: 1px; }\n .site-nav {\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n height: auto;\n width: min(20rem, 85vw);\n border-right: 1px solid var(--border-strong);\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);\n transform: translateX(-100%);\n /* Hidden while closed, so its links are not tab stops behind the page; the visibility\n change waits for the slide out. */\n visibility: hidden;\n transition: transform 160ms ease-out, visibility 0s linear 160ms;\n z-index: 40;\n }\n body.nav-open .site-nav { transform: translateX(0); visibility: visible; transition: transform 160ms ease-out; }\n body.nav-open .nav-backdrop { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35); z-index: 35; }\n .content { padding: 1rem 1.25rem 3rem; }\n .site-search { flex-basis: 12rem; }\n}\n@media (prefers-reduced-motion: reduce) {\n .site-nav { transition: none; }\n body.nav-open .site-nav { transition: none; }\n}\n",
11226
11571
  "../etherpk-docs/layouts/404.html": "{{> shell-top}}\n <article class=\"page not-published\">\n <header class=\"page-header\">\n <h1>Not published</h1>\n </header>\n <div class=\"page-body\">\n <p>The page you followed a link to is not part of this site.</p>\n <p><a href=\"index.html\">Back to {{site.title}}</a></p>\n </div>\n </article>\n{{> shell-bottom}}\n",
11227
11572
  "../etherpk-docs/layouts/archive.html": "{{> shell-top}}\n <article class=\"page archive\">\n <header class=\"page-header\">\n <h1>{{#page.isPostsArchive}}Posts{{/page.isPostsArchive}}{{#page.isJournalArchive}}Journal{{/page.isJournalArchive}}</h1>\n </header>\n {{#page.isPostsArchive}}\n {{#site.postsByYear}}\n <h2 class=\"archive-year\" id=\"y{{year}}\">{{year}}</h2>\n <ul class=\"journal-archive\">\n {{#posts}}\n <li>\n <time datetime=\"{{date}}\">{{date}}</time>\n <a href=\"{{url}}\">{{title}}</a>\n </li>\n {{/posts}}\n </ul>\n {{/site.postsByYear}}\n {{/page.isPostsArchive}}\n {{#page.isJournalArchive}}\n <ul class=\"journal-archive\">\n {{#site.journals}}\n <li>\n <time datetime=\"{{date}}\">{{date}}</time>\n <a href=\"{{url}}\">{{title}}</a>\n {{#excerpt}}<p class=\"archive-excerpt\">{{excerpt}}</p>{{/excerpt}}\n </li>\n {{/site.journals}}\n </ul>\n {{/page.isJournalArchive}}\n </article>\n{{> shell-bottom}}\n",
11228
11573
  "../etherpk-docs/layouts/home.html": "{{> shell-top}}\n <article class=\"page home\">\n {{^page.isGeneratedIndex}}\n <header class=\"page-header\">\n <h1>{{{page.titleHtml}}}</h1>\n </header>\n{{> toc}}\n {{/page.isGeneratedIndex}}\n {{#page.isGeneratedIndex}}\n <header class=\"page-header\">\n <h1>{{site.title}}</h1>\n </header>\n {{/page.isGeneratedIndex}}\n <div class=\"page-body\">\n{{{page.content}}}\n </div>\n </article>\n{{> shell-bottom}}\n",
@@ -11231,6 +11576,7 @@ var files = /* #__PURE__ */ Object.assign({
11231
11576
  "../etherpk-docs/partials/after-content.html": "",
11232
11577
  "../etherpk-docs/partials/backlinks.html": " {{#page.hasBacklinks}}\n <section class=\"backlinks\" aria-labelledby=\"backlinks-heading\">\n <h2 id=\"backlinks-heading\">Linked references</h2>\n <ul class=\"backlink-list\">\n {{#page.backlinks}}\n <li class=\"backlink-source\">\n <a href=\"{{url}}\">{{title}}</a>\n <ul>\n {{#refs}}<li class=\"backlink-context\">{{{contextHtml}}}</li>{{/refs}}\n </ul>\n </li>\n {{/page.backlinks}}\n </ul>\n </section>\n {{/page.hasBacklinks}}\n",
11233
11578
  "../etherpk-docs/partials/before-content.html": "",
11579
+ "../etherpk-docs/partials/favicon.html": "<!-- The EtherPK icon in the browser tab: assets/favicon.svg, the same file the EtherPK apps use. Fill the \"favicon\" include with your own <link rel=\"icon\"> tag to replace it. -->\n<link rel=\"icon\" href=\"theme/favicon.svg\" type=\"image/svg+xml\">\n",
11234
11580
  "../etherpk-docs/partials/footer.html": " <footer class=\"site-footer\">\n <p>{{site.title}} · published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a></p>\n </footer>\n",
11235
11581
  "../etherpk-docs/partials/head.html": "",
11236
11582
  "../etherpk-docs/partials/header.html": " <header class=\"site-header\">\n <a class=\"site-title\" href=\"index.html\">{{> logo}}<span class=\"site-title-text\">{{site.title}}</span></a>\n <form class=\"site-search\" role=\"search\" data-search-index=\"{{site.search.url}}\" data-search-script=\"{{site.search.scriptUrl}}\">\n <label class=\"visually-hidden\" for=\"site-search-input\">Search</label>\n <input id=\"site-search-input\" type=\"search\" name=\"q\" placeholder=\"Search\" autocomplete=\"off\" aria-describedby=\"site-search-hint\">\n <span id=\"site-search-hint\" class=\"visually-hidden\">Type to search the published pages - results appear below the box.</span>\n <div class=\"site-search-results\" role=\"listbox\" hidden></div>\n </form>\n </header>\n",
@@ -11239,10 +11585,11 @@ var files = /* #__PURE__ */ Object.assign({
11239
11585
  "../etherpk-docs/partials/nav.html": " <nav id=\"site-nav\" class=\"site-nav\" aria-label=\"Site\">\n <ul class=\"site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n </ul>\n </nav>\n",
11240
11586
  "../etherpk-docs/partials/scripts.html": "",
11241
11587
  "../etherpk-docs/partials/shell-bottom.html": " {{> after-content}}\n {{> backlinks}}\n </main>\n </div>\n {{> footer}}\n {{> theme-scripts}}\n {{> scripts}}\n</body>\n</html>\n",
11242
- "../etherpk-docs/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} · {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n {{> head}}\n</head>\n<body class=\"layout-docs{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <!-- The drawer toggle lives outside the header include, so a replaced header keeps the navigation reachable on a phone. The stylesheet pins it top right on small screens and hides it otherwise. Icon: Lucide \"menu\" (https://lucide.dev/icons/menu), ISC licence. -->\n <button class=\"nav-toggle\" type=\"button\" aria-controls=\"site-nav\" aria-expanded=\"false\" aria-label=\"Menu\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M4 6h16\"/><path d=\"M4 12h16\"/><path d=\"M4 18h16\"/></svg></button>\n <div class=\"nav-backdrop\" hidden></div>\n <div class=\"shell\">\n {{> nav}}\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11588
+ "../etherpk-docs/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} · {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n {{> favicon}}\n {{> head}}\n</head>\n<body class=\"layout-docs{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <!-- The drawer toggle lives outside the header include, so a replaced header keeps the navigation reachable on a phone. The stylesheet pins it top right on small screens and hides it otherwise. Icon: Lucide \"menu\" (https://lucide.dev/icons/menu), ISC licence. -->\n <button class=\"nav-toggle\" type=\"button\" aria-controls=\"site-nav\" aria-expanded=\"false\" aria-label=\"Menu\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"22\" height=\"22\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M4 6h16\"/><path d=\"M4 12h16\"/><path d=\"M4 18h16\"/></svg></button>\n <div class=\"nav-backdrop\" hidden></div>\n <div class=\"shell\">\n {{> nav}}\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11243
11589
  "../etherpk-docs/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n",
11244
11590
  "../etherpk-docs/partials/toc.html": " {{#page.hasToc}}\n <nav class=\"toc\" aria-label=\"On this page\">\n <details open>\n <summary>On this page</summary>\n <ul>\n {{#page.toc}}<li class=\"toc-level-{{level}}\"><a href=\"#{{id}}\">{{text}}</a></li>{{/page.toc}}\n </ul>\n </details>\n </nav>\n {{/page.hasToc}}\n",
11245
- "../etherpk-docs/theme.json": "{\n \"name\": \"etherpk-docs\",\n \"title\": \"EtherPK Docs\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"Documentation: a sidebar of the publication's navigation, a table of contents, linked references beneath each page, and search.\",\n \"kinds\": [\"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The logo before the site title in the header: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's title bar and search box.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the sidebar drawer): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/header.html\",\n \"partials/nav.html\", \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/search.js\"\n ]\n}\n",
11591
+ "../etherpk-docs/theme.json": "{\n \"name\": \"etherpk-docs\",\n \"title\": \"EtherPK Docs\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"Documentation: a sidebar of the publication's navigation, a table of contents, linked references beneath each page, and search.\",\n \"kinds\": [\"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The logo before the site title in the header: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"favicon\", \"description\": \"The icon in the browser tab: a <link rel=\\\"icon\\\"> tag. Raw HTML in the page body. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's title bar and search box.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the sidebar drawer): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/favicon.html\", \"partials/header.html\",\n \"partials/nav.html\", \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/favicon.svg\", \"assets/search.js\"\n ]\n}\n",
11592
+ "../etherpk-papermod/assets/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 191.82 166.13\">\n <path fill=\"#0a0a0a\" fill-rule=\"evenodd\" d=\"M67.09,132.92,47.93,166.13H9.59L0,149.52,86.33,0H105.5L19.17,149.52H38.34l19.17-33.2h76.8l9.59,16.6Zm48-83.12,9.65,16.71H143.9L115.08,16.6q-24,41.57-48,83.12H143.9q14.37,24.9,28.76,49.8h-96l-9.59,16.61H182.24l9.58-16.61-38.34-66.4H96C95.85,82.9,113.39,52.73,115.08,49.8Z\"/>\n</svg>\n",
11246
11593
  "../etherpk-papermod/assets/search.js": "/*\n * Search for a Published Site: reads search.json (built over the publication's own documents\n * and nothing else) and scores documents by title and text matches. No dependencies, no\n * network beyond the one index file, and nothing runs until the search box is used. The same\n * script runs the sidebar toggle on small screens, so a page carries one script.\n */\n(function () {\n 'use strict';\n\n // -- Navigation toggle (small screens) -------------------------------------------------\n var toggle = document.querySelector('.nav-toggle');\n var backdrop = document.querySelector('.nav-backdrop');\n var nav = document.getElementById('site-nav');\n function setNavOpen(open) {\n var was = document.body.classList.contains('nav-open');\n document.body.classList.toggle('nav-open', open);\n if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');\n if (backdrop) backdrop.hidden = !open;\n // Focus follows the drawer: into its first link on open, back to the button on close,\n // so a keyboard or screen-reader user is never left on something now hidden.\n if (open && !was) {\n var first = nav.querySelector('a');\n if (first) first.focus();\n } else if (!open && was && toggle) {\n toggle.focus();\n }\n }\n if (toggle && nav) {\n toggle.addEventListener('click', function () { setNavOpen(!document.body.classList.contains('nav-open')); });\n if (backdrop) backdrop.addEventListener('click', function () { setNavOpen(false); });\n document.addEventListener('keydown', function (e) {\n if (e.key === 'Escape' && document.body.classList.contains('nav-open')) setNavOpen(false);\n });\n }\n\n // -- Search --------------------------------------------------------------------------\n var form = document.querySelector('.site-search');\n if (!form) return;\n var input = form.querySelector('input[type=\"search\"]');\n var results = form.querySelector('.site-search-results');\n var indexUrl = form.getAttribute('data-search-index') || 'search.json';\n var scriptUrl = form.getAttribute('data-search-script') || 'search-index.js';\n var index = null;\n var loading = null;\n /* True once neither route produced the index: the results then say so rather than \"nothing matches\". */\n var unavailable = false;\n\n /* The index as a script assigning window.etherpkSearchIndex: what a site opened from disk can\n load, since file:// refuses fetch but allows <script src>. */\n function loadScript() {\n return new Promise(function (resolve, reject) {\n if (window.etherpkSearchIndex) { resolve(window.etherpkSearchIndex); return; }\n var s = document.createElement('script');\n s.src = scriptUrl;\n s.onload = function () {\n if (window.etherpkSearchIndex) resolve(window.etherpkSearchIndex);\n else reject(new Error('no index'));\n };\n s.onerror = function () { reject(new Error('no script')); };\n document.head.appendChild(s);\n });\n }\n\n function load() {\n if (index) return Promise.resolve(index);\n if (!loading) {\n loading = fetch(indexUrl).then(function (r) {\n if (!r.ok) throw new Error(String(r.status));\n return r.json();\n }).catch(loadScript).then(function (data) {\n index = (data && data.documents) || [];\n return index;\n }).catch(function () { unavailable = true; index = []; return index; });\n }\n return loading;\n }\n\n function tokens(q) {\n return q.toLowerCase().split(/\\s+/).filter(function (t) { return t.length > 0; });\n }\n\n function score(doc, terms) {\n var title = (doc.title || '').toLowerCase();\n var text = (doc.text || '').toLowerCase();\n var s = 0;\n for (var i = 0; i < terms.length; i++) {\n var t = terms[i];\n if (title === t) s += 20;\n else if (title.indexOf(t) === 0) s += 10;\n else if (title.indexOf(t) !== -1) s += 6;\n var at = text.indexOf(t);\n if (at !== -1) s += 2;\n else if (title.indexOf(t) === -1) return 0; // every term must match somewhere\n }\n return s;\n }\n\n function snippet(doc, terms) {\n var text = doc.text || '';\n var lower = text.toLowerCase();\n var at = -1;\n for (var i = 0; i < terms.length && at === -1; i++) at = lower.indexOf(terms[i]);\n if (at === -1) return doc.excerpt || '';\n var start = Math.max(0, at - 60);\n var end = Math.min(text.length, at + 100);\n return (start > 0 ? '…' : '') + text.slice(start, end).replace(/\\s+/g, ' ') + (end < text.length ? '…' : '');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n function render(list, q) {\n if (!q) { results.hidden = true; results.innerHTML = ''; return; }\n if (unavailable) {\n results.innerHTML = '<p class=\"site-search-empty\">The search index could not be loaded.</p>';\n results.hidden = false;\n return;\n }\n if (list.length === 0) {\n results.innerHTML = '<p class=\"site-search-empty\">Nothing matches \"' + escapeHtml(q) + '\".</p>';\n results.hidden = false;\n return;\n }\n results.innerHTML = list.map(function (hit) {\n return '<a class=\"site-search-hit\" role=\"option\" href=\"' + escapeHtml(hit.doc.url) + '\">' +\n '<span class=\"site-search-title\">' + escapeHtml(hit.doc.title) + '</span>' +\n '<span class=\"site-search-snippet\">' + escapeHtml(hit.snippet) + '</span></a>';\n }).join('');\n results.hidden = false;\n }\n\n var timer = null;\n function run() {\n var q = input.value.trim();\n var terms = tokens(q);\n if (terms.length === 0) { render([], ''); return; }\n load().then(function (docs) {\n var hits = [];\n for (var i = 0; i < docs.length; i++) {\n var s = score(docs[i], terms);\n if (s > 0) hits.push({ doc: docs[i], score: s, snippet: snippet(docs[i], terms) });\n }\n hits.sort(function (a, b) { return b.score - a.score || a.doc.title.localeCompare(b.doc.title); });\n render(hits.slice(0, 12), q);\n });\n }\n\n input.addEventListener('input', function () {\n clearTimeout(timer);\n timer = setTimeout(run, 120);\n });\n input.addEventListener('focus', function () { load(); });\n form.addEventListener('submit', function (e) {\n e.preventDefault();\n var first = results.querySelector('a');\n if (first) window.location.href = first.getAttribute('href');\n });\n document.addEventListener('keydown', function (e) {\n if (e.key === '/' && document.activeElement !== input && !/input|textarea/i.test(document.activeElement.tagName)) {\n e.preventDefault();\n input.focus();\n }\n if (e.key === 'Escape' && !results.hidden) { render([], ''); }\n });\n document.addEventListener('click', function (e) {\n if (!form.contains(e.target)) render([], '');\n });\n})();\n",
11247
11594
  "../etherpk-papermod/assets/theme.css": "/* EtherPK's PaperMod: after Hugo's PaperMod (adityatelange, MIT), written afresh for EtherPK's\n view - the same quiet single column, entry cards, top menu, sun / moon toggle and post\n pagination. System fonts, so the site loads nothing from anywhere. Classes the publisher\n emits and this file styles: .wikilink, .wikilink-missing, .task, .task-tag, .toc, .backlinks,\n .site-nav-list, .diagram, .math, .tok-* (code). */\n\n:root {\n --gap: 24px;\n --content-gap: 20px;\n --nav-width: 1024px;\n --main-width: 720px;\n --header-height: 60px;\n --footer-height: 60px;\n --radius: 8px;\n --font: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen, Ubuntu, Cantarell, \"Open Sans\", \"Helvetica Neue\", sans-serif;\n --font-mono: ui-monospace, \"Cascadia Code\", \"Fira Code\", Menlo, Consolas, monospace;\n --theme: rgb(255, 255, 255);\n --entry: rgb(255, 255, 255);\n --primary: rgb(30, 30, 30);\n --secondary: rgb(108, 108, 108);\n --tertiary: rgb(214, 214, 214);\n --content: rgb(31, 31, 31);\n --code-block-bg: rgb(28, 29, 33);\n --code-bg: rgb(245, 245, 245);\n --border: rgb(238, 238, 238);\n --mark: #fff3a3;\n --missing: #b45309;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n --theme: rgb(29, 30, 32);\n --entry: rgb(46, 46, 51);\n --primary: rgb(218, 218, 219);\n --secondary: rgb(155, 156, 157);\n --tertiary: rgb(65, 66, 68);\n --content: rgb(196, 196, 197);\n --code-block-bg: rgb(46, 46, 51);\n --code-bg: rgb(55, 56, 62);\n --border: rgb(51, 51, 51);\n --mark: #6b5a00;\n --missing: #f59e0b;\n }\n}\n:root[data-theme=\"dark\"] {\n --theme: rgb(29, 30, 32);\n --entry: rgb(46, 46, 51);\n --primary: rgb(218, 218, 219);\n --secondary: rgb(155, 156, 157);\n --tertiary: rgb(65, 66, 68);\n --content: rgb(196, 196, 197);\n --code-block-bg: rgb(46, 46, 51);\n --code-bg: rgb(55, 56, 62);\n --border: rgb(51, 51, 51);\n --mark: #6b5a00;\n --missing: #f59e0b;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { -webkit-tap-highlight-color: transparent; scroll-behavior: smooth; }\n@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }\nbody {\n margin: 0;\n font-family: var(--font);\n font-size: 18px;\n line-height: 1.6;\n word-break: break-word;\n background: var(--theme);\n color: var(--content);\n -webkit-font-smoothing: antialiased;\n}\na { color: inherit; text-decoration: none; }\nimg { max-width: 100%; height: auto; display: block; border-radius: 4px; }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--entry); color: var(--primary); padding: 8px 16px; z-index: 100; }\n.skip-link:focus { left: 16px; top: 16px; }\n\n/* -- Header ----------------------------------------------------------------------------- */\n.nav {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: center;\n max-width: calc(var(--nav-width) + var(--gap) * 2);\n margin-inline: auto;\n height: var(--header-height);\n line-height: var(--header-height);\n padding: 0 var(--gap);\n}\n.logo { display: flex; align-items: center; gap: 10px; }\n.site-title { display: inline-flex; align-items: center; gap: 8px; font-size: 24px; font-weight: 700; color: var(--primary); }\n.site-logo { flex-shrink: 0; }\n.theme-toggle {\n display: inline-flex;\n align-items: center;\n padding: 0;\n border: 0;\n background: none;\n color: var(--primary);\n cursor: pointer;\n}\n.theme-toggle .sun { display: none; }\n:root[data-theme=\"dark\"] .theme-toggle .sun { display: block; }\n:root[data-theme=\"dark\"] .theme-toggle .moon { display: none; }\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) .theme-toggle .sun { display: block; }\n :root:not([data-theme=\"light\"]) .theme-toggle .moon { display: none; }\n}\n#menu { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: 0 20px; margin: 0; padding: 0; }\n#menu li { position: relative; }\n#menu li > a, #menu .nav-group-label { display: block; font-size: 16px; color: var(--primary); white-space: nowrap; }\n#menu li > a:hover { opacity: 0.8; }\n#menu .nav-current > a { border-bottom: 2px solid currentColor; line-height: calc(var(--header-height) - 2px); }\n#menu .nav-group-label { color: var(--secondary); cursor: default; }\n#menu .nav-open > a, #menu .nav-open > .nav-group-label { color: var(--primary); }\n/* A nested outline level is a drop-down under its parent, opened by hover or keyboard focus\n (the group label is focusable for that; a parent that is a page is a link as well). */\n#menu .site-nav-list {\n display: none;\n position: absolute;\n top: 100%;\n left: 0;\n flex-direction: column;\n min-width: 12rem;\n margin: 0;\n padding: 6px 0;\n list-style: none;\n background: var(--entry);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);\n line-height: 1.6;\n z-index: 25;\n}\n#menu li:hover > .site-nav-list, #menu li:focus-within > .site-nav-list { display: flex; }\n#menu .site-nav-list li > a, #menu .site-nav-list .nav-group-label { padding: 6px 14px; font-size: 15px; line-height: 1.6; border: 0; }\n#menu .site-nav-list .nav-current > a { font-weight: 600; }\n.menu-search { margin-left: auto; }\n.site-search { position: relative; line-height: normal; }\n.site-search input {\n width: 12rem;\n padding: 6px 12px;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--entry);\n color: var(--primary);\n font: inherit;\n font-size: 15px;\n}\n.site-search input:focus { outline: 2px solid var(--secondary); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n width: min(26rem, 90vw);\n max-height: 24rem;\n overflow: auto;\n background: var(--entry);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 10px 14px; color: var(--primary); border-bottom: 1px solid var(--border); font-size: 15px; line-height: 1.4; }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--code-bg); }\n.site-search-title { display: block; font-weight: 600; }\n.site-search-snippet { display: block; color: var(--secondary); font-size: 14px; }\n.site-search-empty { margin: 0; padding: 10px 14px; color: var(--secondary); font-size: 15px; }\n\n/* -- Main column ------------------------------------------------------------------------ */\n.main {\n position: relative;\n min-height: calc(100vh - var(--header-height) - var(--footer-height));\n max-width: calc(var(--main-width) + var(--gap) * 2);\n margin: auto;\n padding: var(--gap);\n}\n.page-header h1 { font-size: 40px; margin: 0 0 var(--gap); color: var(--primary); }\n\n/* Entries: the card list PaperMod is known for. The whole card is the link. */\n.post-entry {\n position: relative;\n margin-bottom: var(--gap);\n padding: var(--gap);\n background: var(--entry);\n border-radius: var(--radius);\n transition: transform 0.1s;\n border: 1px solid var(--border);\n}\n.post-entry:active { transform: scale(0.96); }\n.entry-header h2 { margin: 0; font-size: 24px; line-height: 1.3; color: var(--primary); }\n.entry-content { margin: 8px 0; color: var(--secondary); font-size: 14px; line-height: 1.6; }\n.entry-content p { margin: 0; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; }\n.entry-footer { color: var(--secondary); font-size: 13px; }\n.entry-link { position: absolute; inset: 0; }\n.post-list-empty { color: var(--secondary); }\n\n/* A single post. */\n.post-single .post-header { margin-bottom: var(--content-gap); }\n.post-title { margin: 0 0 8px; font-size: 40px; line-height: 1.2; color: var(--primary); }\n.post-title .wikilink { color: inherit; border: 0; }\n.post-meta { color: var(--secondary); font-size: 14px; display: flex; flex-wrap: wrap; gap: 6px; }\n.home-info { margin-bottom: var(--gap); }\n.post-content { color: var(--content); }\n.post-content h1, .post-content h2, .post-content h3, .post-content h4 { margin: 24px 0 16px; color: var(--primary); }\n.post-content h2 { font-size: 32px; }\n.post-content h3 { font-size: 24px; }\n.post-content h4 { font-size: 16px; }\n.post-content a:not(.wikilink):not(.wikilink-missing) { box-shadow: 0 1px 0 var(--primary); }\n.post-content a:not(.wikilink):not(.wikilink-missing):hover { box-shadow: 0 2px 0 var(--primary); }\n.post-content p { margin: 16px 0; }\n.post-content ul, .post-content ol { padding-left: 20px; }\n.post-content li { margin-top: 5px; }\n.post-content img { margin: 1rem 0; }\n.post-content figure, .post-content .diagram { margin: 1rem auto; text-align: center; }\n.post-content .diagram svg { max-width: 100%; height: auto; }\n.post-content blockquote { margin: 20px 0; padding: 0 14px; border-left: 3px solid var(--primary); color: var(--secondary); }\n.post-content hr { margin: 30px 0; height: 2px; background: var(--tertiary); border: 0; }\n.post-content table { margin: 20px 0; width: 100%; border-collapse: collapse; font-size: 16px; }\n.post-content th, .post-content td { padding: 6px 12px; border: 1px solid var(--border); text-align: left; }\n.post-content th { background: var(--code-bg); color: var(--primary); }\n.post-content code { padding: 2px 6px; background: var(--code-bg); border-radius: 2px; font-family: var(--font-mono); font-size: 0.78em; line-height: 1.5; }\n.post-content pre {\n position: relative;\n margin: 20px 0;\n padding: 12px 14px;\n background: var(--code-block-bg);\n color: rgb(213, 213, 214);\n border-radius: var(--radius);\n overflow-x: auto;\n font-family: var(--font-mono);\n font-size: 0.78em;\n line-height: 1.6;\n}\n.post-content pre code { padding: 0; background: none; color: inherit; font-size: inherit; }\n.math-block { overflow-x: auto; margin: 1rem 0; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; border-radius: 2px; }\n\n/* Wikilinks and tasks, as the editor shows them. */\na.wikilink { color: var(--primary); box-shadow: 0 1px 0 var(--tertiary); }\na.wikilink:hover { box-shadow: 0 1px 0 var(--primary); }\na.wikilink-missing { color: var(--missing); box-shadow: 0 1px 0 var(--missing); }\nli.task { list-style: none; margin-left: -20px; }\nli.task input[type=\"checkbox\"] { margin-right: 6px; vertical-align: -0.1em; }\nli.task.done { color: var(--secondary); }\n.task-tag { display: inline-block; font-size: 0.75em; font-weight: 600; padding: 0 8px; border-radius: 999px; background: var(--code-bg); color: var(--primary); margin-right: 4px; vertical-align: 0.1em; }\n.task-tag.priority-1 { background: #fee2e2; color: #991b1b; }\n.task-tag.priority-2 { background: #ffedd5; color: #9a3412; }\n.task-tag.priority-3 { background: #fef9c3; color: #854d0e; }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Table of contents, the PaperMod box. */\n.toc { margin: 0 0 40px; padding: 0 2px; border: 1px solid var(--border); background: var(--code-bg); border-radius: var(--radius); }\n.toc details summary { cursor: pointer; margin-inline-start: 20px; padding: 12px 0; font-weight: 500; color: var(--primary); }\n.toc .inner { margin: 0 0 12px 20px; padding: 0 15px; font-size: 16px; }\n.toc ul { list-style: none; margin: 0; padding: 0; }\n.toc li { margin: 4px 0; }\n.toc li.toc-level-3 { padding-left: 1rem; }\n.toc li.toc-level-4 { padding-left: 2rem; }\n.toc a { color: var(--content); }\n.toc a:hover { color: var(--primary); }\n\n/* Previous / next, PaperMod's paginav. */\n.post-footer { margin-top: 56px; }\n.paginav { display: flex; gap: 8px; margin: 10px 0; border: 1px solid var(--border); background: var(--code-bg); border-radius: var(--radius); line-height: 30px; }\n.paginav a { flex: 1; padding: 14px 16px; color: var(--primary); }\n.paginav .title { font-size: 13px; letter-spacing: 1px; text-transform: uppercase; color: var(--secondary); }\n.paginav .next { text-align: right; margin-left: auto; }\n\n/* Linked references, beneath the content. */\n.backlinks { max-width: var(--main-width); margin: 40px auto 0; padding-top: 20px; border-top: 1px solid var(--border); }\n.backlinks h2 { font-size: 20px; color: var(--primary); margin: 0 0 12px; }\n.backlink-list { list-style: none; padding: 0; margin: 0; }\n.backlink-source { margin: 0 0 14px; }\n.backlink-source > a { font-weight: 600; color: var(--primary); }\n.backlink-source ul { list-style: none; padding-left: 14px; margin: 4px 0 0; border-left: 2px solid var(--border); }\n.backlink-context { color: var(--secondary); font-size: 15px; margin: 2px 0; }\n\n/* Archive. */\n.archive-entry { position: relative; margin-bottom: 16px; padding: 24px; background: var(--entry); border: 1px solid var(--border); border-radius: var(--radius); }\n.archive-entry-title { margin: 0 0 6px; font-size: 20px; }\n.archive-entry-title a { color: var(--primary); }\n.archive-entry-content { color: var(--secondary); font-size: 14px; }\n/* The contents page of a docs site: the outline as a tree, then any page it leaves out. */\n.page-contents { padding: 24px; background: var(--entry); border: 1px solid var(--border); border-radius: var(--radius); }\n.page-index-list { list-style: none; margin: 0; padding: 0; }\n.page-index-list .page-index-list { padding-left: 20px; margin-top: 6px; }\n.page-index-list li { margin: 6px 0; }\n.page-index-list a { color: var(--primary); }\n.page-index-group-label { color: var(--secondary); font-weight: 600; }\n.page-index-other { margin: 24px 0 8px; font-size: 18px; }\n.archive-year-header { margin: 24px 0 12px; font-size: 32px; color: var(--secondary); }\n.all-posts { margin: 8px 0 0; }\n.all-posts a { color: var(--primary); }\n.not-found { text-align: center; margin: 80px 0; }\n.not-found h1 { font-size: 160px; font-weight: 700; line-height: 1; color: var(--primary); }\n\n/* Footer and the top link. */\n.footer {\n max-width: calc(var(--main-width) + var(--gap) * 2);\n margin: auto;\n padding: calc((var(--footer-height) - 36px) / 2) var(--gap);\n color: var(--secondary);\n font-size: 12px;\n text-align: center;\n line-height: 36px;\n}\n.footer span { margin-inline-start: 1px; }\n.footer a { color: inherit; border-bottom: 1px solid var(--secondary); }\n.top-link {\n position: fixed;\n bottom: 60px;\n right: 30px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: var(--tertiary);\n color: var(--secondary);\n font-size: 22px;\n line-height: 1;\n transition: opacity 0.2s;\n}\n.top-link:hover { color: var(--primary); }\n\n/* Code tokens (lezer classHighlighter names) on the dark code block. */\n.tok-keyword { color: #4ec9b0; }\n.tok-string, .tok-string2 { color: #b5cea8; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #7f848e; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #d19a66; }\n.tok-function, .tok-definition.tok-variableName { color: #dcdcaa; }\n.tok-typeName, .tok-className, .tok-namespace { color: #e5c07b; }\n.tok-operator, .tok-punctuation { color: #abb2bf; }\n.tok-propertyName, .tok-attributeName { color: #61afef; }\n.tok-tagName { color: #98c379; }\n.tok-invalid { color: #f87171; }\n\n/* -- Small screens ---------------------------------------------------------------------- */\n@media (max-width: 768px) {\n :root { --gap: 14px; }\n .nav { padding: 0 var(--gap); }\n #menu { width: 100%; height: 44px; line-height: 44px; }\n .menu-search { margin-left: auto; }\n .site-search input { width: 9rem; }\n .post-title, .page-header h1 { font-size: 30px; }\n .post-content h2 { font-size: 26px; }\n .not-found h1 { font-size: 100px; }\n .top-link { right: 16px; bottom: 30px; }\n}\n@media (max-width: 500px) {\n .nav { height: auto; padding-bottom: 8px; }\n .menu-search { flex-basis: 100%; }\n .site-search input { width: 100%; }\n}\n.nav-toggle, .nav-backdrop { display: none; }\n",
11248
11595
  "../etherpk-papermod/assets/theme.js": "/*\n * The light / dark toggle: a `.theme-toggle` button flips the page between the two and the\n * choice is remembered in this browser; with no choice made the page follows the system. The\n * stylesheet keys on `data-theme` on <html>, and shell-top applies the remembered choice before\n * the first paint so a dark reader never sees a light flash.\n */\n(function () {\n 'use strict';\n var KEY = 'etherpk-theme';\n var root = document.documentElement;\n function isDark() {\n var chosen = root.getAttribute('data-theme');\n if (chosen === 'dark' || chosen === 'light') return chosen === 'dark';\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n function apply(theme) {\n if (theme === 'dark' || theme === 'light') root.setAttribute('data-theme', theme);\n else root.removeAttribute('data-theme');\n var buttons = document.querySelectorAll('.theme-toggle');\n for (var i = 0; i < buttons.length; i++) buttons[i].setAttribute('aria-pressed', isDark() ? 'true' : 'false');\n }\n try { apply(localStorage.getItem(KEY)); } catch (_e) { apply(null); }\n var toggles = document.querySelectorAll('.theme-toggle');\n for (var i = 0; i < toggles.length; i++) {\n toggles[i].addEventListener('click', function () {\n var next = isDark() ? 'light' : 'dark';\n apply(next);\n try { localStorage.setItem(KEY, next); } catch (_e) { /* private mode: the choice lasts the page */ }\n });\n }\n})();\n",
@@ -11254,6 +11601,7 @@ var files = /* #__PURE__ */ Object.assign({
11254
11601
  "../etherpk-papermod/partials/after-content.html": "",
11255
11602
  "../etherpk-papermod/partials/backlinks.html": " {{#page.hasBacklinks}}\n <section class=\"backlinks\" aria-labelledby=\"backlinks-heading\">\n <h2 id=\"backlinks-heading\">Linked references</h2>\n <ul class=\"backlink-list\">\n {{#page.backlinks}}\n <li class=\"backlink-source\">\n <a href=\"{{url}}\">{{title}}</a>\n <ul>\n {{#refs}}<li class=\"backlink-context\">{{{contextHtml}}}</li>{{/refs}}\n </ul>\n </li>\n {{/page.backlinks}}\n </ul>\n </section>\n {{/page.hasBacklinks}}\n",
11256
11603
  "../etherpk-papermod/partials/before-content.html": "",
11604
+ "../etherpk-papermod/partials/favicon.html": "<!-- The EtherPK icon in the browser tab: assets/favicon.svg, the same file the EtherPK apps use. Fill the \"favicon\" include with your own <link rel=\"icon\"> tag to replace it. -->\n<link rel=\"icon\" href=\"theme/favicon.svg\" type=\"image/svg+xml\">\n",
11257
11605
  "../etherpk-papermod/partials/footer.html": " <footer class=\"footer\">\n <span>&copy; {{site.year}} {{site.title}}</span>\n <span>&middot;</span>\n <span>Published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a>{{#site.hasFeed}} &middot; <a href=\"feed.xml\">Feed</a>{{/site.hasFeed}}</span>\n </footer>\n",
11258
11606
  "../etherpk-papermod/partials/head.html": "",
11259
11607
  "../etherpk-papermod/partials/header.html": " <header class=\"header\">\n <nav class=\"nav\" aria-label=\"Site\">\n <div class=\"logo\">\n <a class=\"site-title\" href=\"index.html\">{{> logo}}<span class=\"site-title-text\">{{site.title}}</span></a>\n <button class=\"theme-toggle\" type=\"button\" aria-label=\"Toggle light and dark\" aria-pressed=\"false\">\n <svg class=\"moon\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>\n <svg class=\"sun\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><path d=\"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42\"/></svg>\n </button>\n </div>\n <ul id=\"menu\" class=\"site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n <li class=\"menu-search\">\n <form class=\"site-search\" role=\"search\" data-search-index=\"{{site.search.url}}\" data-search-script=\"{{site.search.scriptUrl}}\">\n <label class=\"visually-hidden\" for=\"site-search-input\">Search</label>\n <input id=\"site-search-input\" type=\"search\" name=\"q\" placeholder=\"Search\" autocomplete=\"off\">\n <div class=\"site-search-results\" role=\"listbox\" hidden></div>\n </form>\n </li>\n </ul>\n </nav>\n </header>\n",
@@ -11263,10 +11611,11 @@ var files = /* #__PURE__ */ Object.assign({
11263
11611
  "../etherpk-papermod/partials/post-meta.html": " <div class=\"post-meta\">{{#page.hasDate}}<time datetime=\"{{page.date}}\">{{page.date}}</time> · {{/page.hasDate}}<span>{{page.readingMinutes}} min</span></div>\n",
11264
11612
  "../etherpk-papermod/partials/scripts.html": "",
11265
11613
  "../etherpk-papermod/partials/shell-bottom.html": " {{> after-content}}\n {{> backlinks}}\n </main>\n {{> footer}}\n <a href=\"#top\" class=\"top-link\" aria-label=\"Go to top\" title=\"Go to top\">↑</a>\n {{> theme-scripts}}\n {{> scripts}}\n</body>\n</html>\n",
11266
- "../etherpk-papermod/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} | {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n <!-- The remembered light / dark choice, applied before the first paint (theme.js owns the toggle). -->\n <script>try{var t=localStorage.getItem('etherpk-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}<\/script>\n {{> head}}\n</head>\n<body class=\"layout-papermod{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\" id=\"top\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <main id=\"content\" class=\"main\">\n {{> before-content}}\n",
11614
+ "../etherpk-papermod/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} | {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n <!-- The remembered light / dark choice, applied before the first paint (theme.js owns the toggle). -->\n <script>try{var t=localStorage.getItem('etherpk-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}<\/script>\n {{> favicon}}\n {{> head}}\n</head>\n<body class=\"layout-papermod{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\" id=\"top\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n {{> header}}\n <main id=\"content\" class=\"main\">\n {{> before-content}}\n",
11267
11615
  "../etherpk-papermod/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n <script src=\"theme/theme.js\" defer><\/script>\n",
11268
11616
  "../etherpk-papermod/partials/toc.html": " {{#page.hasToc}}\n <div class=\"toc\">\n <details>\n <summary accesskey=\"c\">Table of contents</summary>\n <div class=\"inner\"><ul>\n {{#page.toc}}<li class=\"toc-level-{{level}}\"><a href=\"#{{id}}\">{{text}}</a></li>{{/page.toc}}\n </ul></div>\n </details>\n </div>\n {{/page.hasToc}}\n",
11269
- "../etherpk-papermod/theme.json": "{\n \"name\": \"etherpk-papermod\",\n \"title\": \"EtherPK PaperMod\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Hugo's PaperMod: a clean single column, post cards with reading time, a top menu, a light and dark toggle, previous and next between posts.\",\n \"kinds\": [\"blog\", \"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The logo before the site title in the header: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's title, menu, search box and theme toggle.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the theme toggle): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\", \"partials/post-meta.html\", \"partials/post-entry.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/search.js\", \"assets/theme.js\"\n ]\n}\n",
11617
+ "../etherpk-papermod/theme.json": "{\n \"name\": \"etherpk-papermod\",\n \"title\": \"EtherPK PaperMod\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Hugo's PaperMod: a clean single column, post cards with reading time, a top menu, a light and dark toggle, previous and next between posts.\",\n \"kinds\": [\"blog\", \"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The logo before the site title in the header: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"favicon\", \"description\": \"The icon in the browser tab: a <link rel=\\\"icon\\\"> tag. Raw HTML in the page body. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's title, menu, search box and theme toggle.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the theme toggle): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/favicon.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\", \"partials/post-meta.html\", \"partials/post-entry.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/favicon.svg\", \"assets/search.js\", \"assets/theme.js\"\n ]\n}\n",
11618
+ "../etherpk-stack/assets/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 191.82 166.13\">\n <path fill=\"#0a0a0a\" fill-rule=\"evenodd\" d=\"M67.09,132.92,47.93,166.13H9.59L0,149.52,86.33,0H105.5L19.17,149.52H38.34l19.17-33.2h76.8l9.59,16.6Zm48-83.12,9.65,16.71H143.9L115.08,16.6q-24,41.57-48,83.12H143.9q14.37,24.9,28.76,49.8h-96l-9.59,16.61H182.24l9.58-16.61-38.34-66.4H96C95.85,82.9,113.39,52.73,115.08,49.8Z\"/>\n</svg>\n",
11270
11619
  "../etherpk-stack/assets/search.js": "/*\n * Search for a Published Site: reads search.json (built over the publication's own documents\n * and nothing else) and scores documents by title and text matches. No dependencies, no\n * network beyond the one index file, and nothing runs until the search box is used. The same\n * script runs the sidebar toggle on small screens, so a page carries one script.\n */\n(function () {\n 'use strict';\n\n // -- Navigation toggle (small screens) -------------------------------------------------\n var toggle = document.querySelector('.nav-toggle');\n var backdrop = document.querySelector('.nav-backdrop');\n var nav = document.getElementById('site-nav');\n function setNavOpen(open) {\n var was = document.body.classList.contains('nav-open');\n document.body.classList.toggle('nav-open', open);\n if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');\n if (backdrop) backdrop.hidden = !open;\n // Focus follows the drawer: into its first link on open, back to the button on close,\n // so a keyboard or screen-reader user is never left on something now hidden.\n if (open && !was) {\n var first = nav.querySelector('a');\n if (first) first.focus();\n } else if (!open && was && toggle) {\n toggle.focus();\n }\n }\n if (toggle && nav) {\n toggle.addEventListener('click', function () { setNavOpen(!document.body.classList.contains('nav-open')); });\n if (backdrop) backdrop.addEventListener('click', function () { setNavOpen(false); });\n document.addEventListener('keydown', function (e) {\n if (e.key === 'Escape' && document.body.classList.contains('nav-open')) setNavOpen(false);\n });\n }\n\n // -- Search --------------------------------------------------------------------------\n var form = document.querySelector('.site-search');\n if (!form) return;\n var input = form.querySelector('input[type=\"search\"]');\n var results = form.querySelector('.site-search-results');\n var indexUrl = form.getAttribute('data-search-index') || 'search.json';\n var scriptUrl = form.getAttribute('data-search-script') || 'search-index.js';\n var index = null;\n var loading = null;\n /* True once neither route produced the index: the results then say so rather than \"nothing matches\". */\n var unavailable = false;\n\n /* The index as a script assigning window.etherpkSearchIndex: what a site opened from disk can\n load, since file:// refuses fetch but allows <script src>. */\n function loadScript() {\n return new Promise(function (resolve, reject) {\n if (window.etherpkSearchIndex) { resolve(window.etherpkSearchIndex); return; }\n var s = document.createElement('script');\n s.src = scriptUrl;\n s.onload = function () {\n if (window.etherpkSearchIndex) resolve(window.etherpkSearchIndex);\n else reject(new Error('no index'));\n };\n s.onerror = function () { reject(new Error('no script')); };\n document.head.appendChild(s);\n });\n }\n\n function load() {\n if (index) return Promise.resolve(index);\n if (!loading) {\n loading = fetch(indexUrl).then(function (r) {\n if (!r.ok) throw new Error(String(r.status));\n return r.json();\n }).catch(loadScript).then(function (data) {\n index = (data && data.documents) || [];\n return index;\n }).catch(function () { unavailable = true; index = []; return index; });\n }\n return loading;\n }\n\n function tokens(q) {\n return q.toLowerCase().split(/\\s+/).filter(function (t) { return t.length > 0; });\n }\n\n function score(doc, terms) {\n var title = (doc.title || '').toLowerCase();\n var text = (doc.text || '').toLowerCase();\n var s = 0;\n for (var i = 0; i < terms.length; i++) {\n var t = terms[i];\n if (title === t) s += 20;\n else if (title.indexOf(t) === 0) s += 10;\n else if (title.indexOf(t) !== -1) s += 6;\n var at = text.indexOf(t);\n if (at !== -1) s += 2;\n else if (title.indexOf(t) === -1) return 0; // every term must match somewhere\n }\n return s;\n }\n\n function snippet(doc, terms) {\n var text = doc.text || '';\n var lower = text.toLowerCase();\n var at = -1;\n for (var i = 0; i < terms.length && at === -1; i++) at = lower.indexOf(terms[i]);\n if (at === -1) return doc.excerpt || '';\n var start = Math.max(0, at - 60);\n var end = Math.min(text.length, at + 100);\n return (start > 0 ? '…' : '') + text.slice(start, end).replace(/\\s+/g, ' ') + (end < text.length ? '…' : '');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n function render(list, q) {\n if (!q) { results.hidden = true; results.innerHTML = ''; return; }\n if (unavailable) {\n results.innerHTML = '<p class=\"site-search-empty\">The search index could not be loaded.</p>';\n results.hidden = false;\n return;\n }\n if (list.length === 0) {\n results.innerHTML = '<p class=\"site-search-empty\">Nothing matches \"' + escapeHtml(q) + '\".</p>';\n results.hidden = false;\n return;\n }\n results.innerHTML = list.map(function (hit) {\n return '<a class=\"site-search-hit\" role=\"option\" href=\"' + escapeHtml(hit.doc.url) + '\">' +\n '<span class=\"site-search-title\">' + escapeHtml(hit.doc.title) + '</span>' +\n '<span class=\"site-search-snippet\">' + escapeHtml(hit.snippet) + '</span></a>';\n }).join('');\n results.hidden = false;\n }\n\n var timer = null;\n function run() {\n var q = input.value.trim();\n var terms = tokens(q);\n if (terms.length === 0) { render([], ''); return; }\n load().then(function (docs) {\n var hits = [];\n for (var i = 0; i < docs.length; i++) {\n var s = score(docs[i], terms);\n if (s > 0) hits.push({ doc: docs[i], score: s, snippet: snippet(docs[i], terms) });\n }\n hits.sort(function (a, b) { return b.score - a.score || a.doc.title.localeCompare(b.doc.title); });\n render(hits.slice(0, 12), q);\n });\n }\n\n input.addEventListener('input', function () {\n clearTimeout(timer);\n timer = setTimeout(run, 120);\n });\n input.addEventListener('focus', function () { load(); });\n form.addEventListener('submit', function (e) {\n e.preventDefault();\n var first = results.querySelector('a');\n if (first) window.location.href = first.getAttribute('href');\n });\n document.addEventListener('keydown', function (e) {\n if (e.key === '/' && document.activeElement !== input && !/input|textarea/i.test(document.activeElement.tagName)) {\n e.preventDefault();\n input.focus();\n }\n if (e.key === 'Escape' && !results.hidden) { render([], ''); }\n });\n document.addEventListener('click', function (e) {\n if (!form.contains(e.target)) render([], '');\n });\n})();\n",
11271
11620
  "../etherpk-stack/assets/theme.css": "/* EtherPK's Stack: after Hugo's Stack (Jimmy Cai), written afresh for EtherPK's view - the\n three-column card layout: identity and menu on the left, article cards on a soft grey ground\n in the middle, widgets on the right, rounded corners and a faint shadow on every card, and\n the sidebar folding into a header with a hamburger on a phone. System fonts, so the site\n loads nothing from anywhere. Classes the publisher emits and this file styles: .wikilink,\n .wikilink-missing, .task, .task-tag, .toc, .backlinks, .site-nav-list, .diagram, .math,\n .tok-* (code). */\n\n:root {\n --font-sans: \"Lato\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n --font-mono: ui-monospace, Menlo, Consolas, \"Liberation Mono\", monospace;\n --body-background: #f5f5fa;\n --card-background: #fff;\n --card-background-selected: #eaeaea;\n --card-text-color-main: #000;\n --card-text-color-secondary: #747474;\n --card-text-color-tertiary: #767676;\n --card-separator-color: rgba(218, 218, 218, 0.5);\n --card-border-radius: 10px;\n --card-padding: 25px;\n --accent-color: #34495e;\n --accent-color-darker: #2c3e50;\n --accent-color-text: #fff;\n --body-text-color: #bababa;\n --tag-border-radius: 4px;\n --section-separation: 40px;\n --left-sidebar-width: 300px;\n --right-sidebar-width: 300px;\n --shadow-l1: 0 4px 8px rgba(0, 0, 0, 0.04), 0 0 2px rgba(0, 0, 0, 0.06), 0 0 1px rgba(0, 0, 0, 0.04);\n --shadow-l2: 0 10px 20px rgba(0, 0, 0, 0.04), 0 2px 6px rgba(0, 0, 0, 0.04), 0 0 1px rgba(0, 0, 0, 0.04);\n --code-bg: #f8f8f8;\n --mark: #fff3a3;\n --missing: #b45309;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) {\n --body-background: #303030;\n --card-background: #424242;\n --card-background-selected: rgba(255, 255, 255, 0.16);\n --card-text-color-main: rgba(255, 255, 255, 0.9);\n --card-text-color-secondary: rgba(255, 255, 255, 0.7);\n --card-text-color-tertiary: rgba(255, 255, 255, 0.5);\n --card-separator-color: rgba(255, 255, 255, 0.12);\n --accent-color: #bdbdbd;\n --accent-color-darker: #e0e0e0;\n --accent-color-text: #000;\n --code-bg: #2e2e2e;\n --mark: #6b5a00;\n --missing: #f59e0b;\n }\n}\n:root[data-theme=\"dark\"] {\n --body-background: #303030;\n --card-background: #424242;\n --card-background-selected: rgba(255, 255, 255, 0.16);\n --card-text-color-main: rgba(255, 255, 255, 0.9);\n --card-text-color-secondary: rgba(255, 255, 255, 0.7);\n --card-text-color-tertiary: rgba(255, 255, 255, 0.5);\n --card-separator-color: rgba(255, 255, 255, 0.12);\n --accent-color: #bdbdbd;\n --accent-color-darker: #e0e0e0;\n --accent-color-text: #000;\n --code-bg: #2e2e2e;\n --mark: #6b5a00;\n --missing: #f59e0b;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { font-size: 62.5%; }\nbody {\n margin: 0;\n font-family: var(--font-sans);\n font-size: 1.6rem;\n line-height: 1.6;\n background: var(--body-background);\n color: var(--card-text-color-main);\n -webkit-font-smoothing: antialiased;\n}\na { color: var(--accent-color); text-decoration: none; }\na:hover { color: var(--accent-color-darker); }\nimg { max-width: 100%; height: auto; display: block; }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--card-background); padding: 8px 16px; z-index: 100; }\n.skip-link:focus { left: 16px; top: 16px; }\n\n/* -- The three columns ------------------------------------------------------------------ */\n.container {\n display: grid;\n grid-template-columns: var(--left-sidebar-width) minmax(0, 1fr) var(--right-sidebar-width);\n gap: var(--section-separation);\n max-width: 1280px;\n margin: 0 auto;\n padding: 0 15px;\n}\n.sidebar { position: sticky; top: 0; align-self: flex-start; max-height: 100vh; overflow-y: auto; padding: var(--section-separation) 0; }\n.main { padding: var(--section-separation) 0; }\n\n/* -- Left sidebar: identity and menu ---------------------------------------------------- */\n.site-info { display: flex; flex-direction: column; gap: 10px; margin-bottom: var(--section-separation); }\n.site-title { display: flex; flex-direction: column; gap: 15px; color: var(--card-text-color-main); }\n.site-avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 100px;\n height: 100px;\n border-radius: 100%;\n background: var(--card-background);\n box-shadow: var(--shadow-l1);\n color: var(--accent-color);\n}\n.site-avatar .site-logo { width: 48px; height: 42px; }\n.site-name { font-size: 1.6rem; font-weight: 700; }\n.site-description { margin: 0; font-size: 1.4rem; color: var(--card-text-color-secondary); }\n.menu { display: flex; flex-direction: column; gap: 10px; }\n.site-nav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }\n.site-nav-list li > a, .nav-group-label, .menu-item {\n display: flex;\n align-items: center;\n gap: 20px;\n padding: 10px 20px;\n border-radius: 6px;\n color: var(--card-text-color-main);\n font-size: 1.4rem;\n text-decoration: none;\n}\n.site-nav-list li > a:hover, .menu-item:hover { background: var(--card-background-selected); color: var(--card-text-color-main); }\n.site-nav-list .nav-current > a { background: var(--accent-color); color: var(--accent-color-text); }\n.nav-group-label { font-weight: 700; color: var(--card-text-color-secondary); font-size: 1.3rem; padding-bottom: 0; }\n.site-nav-list .site-nav-list { padding-left: 16px; margin-top: 6px; }\n.menu-item { border: 0; background: none; font: inherit; font-size: 1.4rem; cursor: pointer; width: 100%; text-align: left; }\n.nav-toggle, .nav-backdrop { display: none; }\n\n/* -- Cards ------------------------------------------------------------------------------ */\n.article-list { display: flex; flex-direction: column; gap: var(--section-separation); }\n.article-card, .article-page {\n display: block;\n background: var(--card-background);\n border-radius: var(--card-border-radius);\n box-shadow: var(--shadow-l1);\n overflow: hidden;\n transition: box-shadow 0.3s ease;\n}\n.article-card:hover { box-shadow: var(--shadow-l2); }\n.article-details { padding: var(--card-padding); }\n.article-category { display: inline-block; margin-bottom: 8px; font-size: 1.2rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--card-text-color-tertiary); }\n.article-card .article-title { margin: 0 0 10px; font-size: 2.2rem; line-height: 1.3; font-weight: 700; }\n.article-card .article-title a { color: var(--card-text-color-main); }\n.article-subtitle { margin: 0 0 15px; font-size: 1.6rem; color: var(--card-text-color-secondary); }\n.article-time { font-size: 1.4rem; color: var(--card-text-color-tertiary); }\n.article-list-empty { color: var(--card-text-color-secondary); }\n.all-posts { margin: 0; }\n.all-posts a { color: var(--accent-color); font-weight: 600; text-decoration: none; }\n/* The contents page of a docs site: the outline as a tree in one card, then any page it leaves out. */\n.page-index-list { list-style: none; margin: 0; padding: 0; }\n.page-index-list .page-index-list { padding-left: 2rem; margin-top: 0.35rem; }\n.page-index-list li { margin: 0.35rem 0; }\n.page-index-list a { color: var(--card-text-color-main); text-decoration: none; }\n.page-index-list a:hover { color: var(--accent-color); }\n.page-index-group-label { color: var(--card-text-color-secondary); font-weight: 700; text-transform: uppercase; font-size: 1.3rem; letter-spacing: 0.05em; }\n.page-index-other { margin: 2rem 0 0.5rem; font-size: 1.8rem; }\n/* The posts archive: a card per year, one line per post inside it. */\n.archive-year { padding: var(--card-padding); background: var(--card-background); border-radius: var(--card-border-radius); box-shadow: var(--shadow-l1); margin-bottom: var(--section-separation); }\n.archive-year-title { margin: 0 0 0.75rem; font-size: 1.6rem; color: var(--card-text-color-main); }\n.archive-list { list-style: none; margin: 0; padding: 0; }\n.archive-list li { padding: 0.35rem 0; }\n.archive-list time { color: var(--card-text-color-tertiary); margin-right: 0.75rem; font-variant-numeric: tabular-nums; }\n.archive-list a { color: var(--card-text-color-main); text-decoration: none; }\n.archive-list a:hover { color: var(--accent-color); }\n.section-header h1 { margin: 0 0 20px; font-size: 2.6rem; }\n\n/* A single article. */\n.article-header { padding: var(--card-padding) var(--card-padding) 0; }\n.article-page .article-title { margin: 0 0 10px; font-size: 2.6rem; line-height: 1.25; font-weight: 700; }\n.article-page .article-title .wikilink { color: inherit; }\n.article-header .article-details { padding: 0; margin-bottom: 20px; font-size: 1.4rem; color: var(--card-text-color-tertiary); }\n.article-content { padding: 0 var(--card-padding) var(--card-padding); font-size: 1.6rem; color: var(--card-text-color-main); }\n.article-content h1, .article-content h2, .article-content h3, .article-content h4 { margin: 2em 0 0.8em; line-height: 1.3; }\n.article-content h2 { font-size: 2.2rem; }\n.article-content h3 { font-size: 1.8rem; }\n.article-content p { margin: 1em 0; }\n.article-content ul, .article-content ol { padding-left: 24px; }\n.article-content li { margin: 4px 0; }\n.article-content img { margin: 1.5em auto; border-radius: var(--card-border-radius); }\n.article-content .diagram { margin: 1.5em 0; text-align: center; }\n.article-content .diagram svg { max-width: 100%; height: auto; }\n.article-content blockquote { margin: 1.5em 0; padding: 15px 22px; border-left: 4px solid var(--accent-color); background: var(--card-background-selected); border-radius: 0 var(--tag-border-radius) var(--tag-border-radius) 0; }\n.article-content blockquote p { margin: 0; }\n.article-content table { width: 100%; border-collapse: collapse; margin: 1.5em 0; }\n.article-content th, .article-content td { padding: 8px 12px; border-bottom: 1px solid var(--card-separator-color); text-align: left; }\n.article-content th { color: var(--card-text-color-secondary); font-size: 1.4rem; text-transform: uppercase; letter-spacing: 0.03em; }\n.article-content code { padding: 2px 6px; background: var(--code-bg); border-radius: var(--tag-border-radius); font-family: var(--font-mono); font-size: 0.9em; }\n.article-content pre { margin: 1.5em 0; padding: 20px; background: var(--code-bg); border-radius: var(--card-border-radius); overflow-x: auto; font-family: var(--font-mono); font-size: 1.4rem; line-height: 1.5; }\n.article-content pre code { padding: 0; background: none; font-size: inherit; }\n.article-content hr { border: 0; border-top: 1px solid var(--card-separator-color); margin: 2em 0; }\n.math-block { overflow-x: auto; margin: 1.5em 0; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; border-radius: 2px; }\n\n/* Wikilinks and tasks. */\na.wikilink { color: var(--accent-color); border-bottom: 1px solid var(--card-separator-color); }\na.wikilink:hover { border-bottom-color: var(--accent-color); }\na.wikilink-missing { color: var(--missing); border-bottom: 1px dashed var(--missing); }\nli.task { list-style: none; margin-left: -24px; }\nli.task input[type=\"checkbox\"] { accent-color: var(--accent-color); margin-right: 6px; vertical-align: -0.1em; }\nli.task.done { color: var(--card-text-color-tertiary); }\n.task-tag { display: inline-block; padding: 1px 8px; margin-right: 4px; border-radius: var(--tag-border-radius); background: var(--card-background-selected); font-size: 1.2rem; font-weight: 700; }\n.task-tag.priority-1 { background: #fee2e2; color: #991b1b; }\n.task-tag.priority-2 { background: #ffedd5; color: #9a3412; }\n.task-tag.priority-3 { background: #fef9c3; color: #854d0e; }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Previous / next as small cards. */\n.article-footer { padding: 0 var(--card-padding) var(--card-padding); }\n.article-pagination { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }\n.pagination-card { display: flex; flex-direction: column; gap: 4px; padding: 15px 20px; background: var(--body-background); box-shadow: none; color: var(--card-text-color-main); }\n.pagination-card.next { text-align: right; grid-column: 2; }\n.pagination-label { font-size: 1.2rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--card-text-color-tertiary); }\n.pagination-title { font-weight: 700; }\n\n/* Linked references, as a card. */\n.backlinks { margin-top: var(--section-separation); padding: var(--card-padding); background: var(--card-background); border-radius: var(--card-border-radius); box-shadow: var(--shadow-l1); }\n.backlinks h2 { margin: 0 0 15px; font-size: 1.8rem; }\n.backlink-list { list-style: none; margin: 0; padding: 0; }\n.backlink-source { margin: 0 0 15px; }\n.backlink-source > a { font-weight: 700; color: var(--card-text-color-main); }\n.backlink-source ul { list-style: none; margin: 4px 0 0; padding: 0 0 0 15px; border-left: 2px solid var(--card-separator-color); }\n.backlink-context { margin: 2px 0; font-size: 1.4rem; color: var(--card-text-color-secondary); }\n\n/* -- Right sidebar: widgets ------------------------------------------------------------- */\n.right-sidebar { display: flex; flex-direction: column; gap: var(--section-separation); }\n.widget { display: flex; flex-direction: column; gap: 15px; }\n.widget-title { margin: 0; font-size: 1.4rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--card-text-color-secondary); }\n.widget-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }\n.widget-list li a {\n display: block;\n padding: 10px 15px;\n background: var(--card-background);\n border-radius: var(--tag-border-radius);\n box-shadow: var(--shadow-l1);\n color: var(--card-text-color-main);\n font-size: 1.4rem;\n}\n.widget-list li a:hover { box-shadow: var(--shadow-l2); }\n.widget-list li a { display: flex; justify-content: space-between; gap: 10px; }\n.widget-count { color: var(--card-text-color-tertiary); }\n.widget-more { margin: 0; font-size: 1.4rem; }\n.toc ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }\n.toc li.toc-level-3 { padding-left: 1.5rem; }\n.toc li.toc-level-4 { padding-left: 3rem; }\n.toc a { color: var(--card-text-color-secondary); font-size: 1.4rem; }\n.toc a:hover { color: var(--accent-color); }\n.site-search { position: relative; }\n.site-search input {\n width: 100%;\n padding: 10px 15px;\n border: 0;\n border-radius: var(--card-border-radius);\n background: var(--card-background);\n box-shadow: var(--shadow-l1);\n color: var(--card-text-color-main);\n font: inherit;\n font-size: 1.4rem;\n}\n.site-search input:focus { outline: 2px solid var(--accent-color); outline-offset: 1px; }\n.site-search-results {\n position: absolute;\n left: 0;\n right: 0;\n top: calc(100% + 8px);\n max-height: 24rem;\n overflow: auto;\n background: var(--card-background);\n border-radius: var(--card-border-radius);\n box-shadow: var(--shadow-l2);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 10px 15px; color: var(--card-text-color-main); border-bottom: 1px solid var(--card-separator-color); font-size: 1.4rem; }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--card-background-selected); }\n.site-search-title { display: block; font-weight: 700; }\n.site-search-snippet { display: block; color: var(--card-text-color-secondary); font-size: 1.3rem; }\n.site-search-empty { margin: 0; padding: 10px 15px; color: var(--card-text-color-secondary); font-size: 1.4rem; }\n\n/* -- Footer ----------------------------------------------------------------------------- */\n.site-footer { margin-top: var(--section-separation); font-size: 1.4rem; color: var(--card-text-color-tertiary); }\n.site-footer p { margin: 4px 0; }\n\n/* Code tokens (lezer classHighlighter names). */\n.tok-keyword { color: #0a7; }\n.tok-string, .tok-string2 { color: #690; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: #999; font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: #905; }\n.tok-function, .tok-definition.tok-variableName { color: #c60; }\n.tok-typeName, .tok-className, .tok-namespace { color: #b58900; }\n.tok-operator, .tok-punctuation { color: #555; }\n.tok-propertyName, .tok-attributeName { color: #268bd2; }\n.tok-tagName { color: #22863a; }\n.tok-invalid { color: #d00; }\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]) .tok-keyword { color: #4ec9b0; }\n :root:not([data-theme=\"light\"]) .tok-string, :root:not([data-theme=\"light\"]) .tok-string2 { color: #b5cea8; }\n :root:not([data-theme=\"light\"]) .tok-number, :root:not([data-theme=\"light\"]) .tok-atom { color: #d19a66; }\n :root:not([data-theme=\"light\"]) .tok-function { color: #dcdcaa; }\n :root:not([data-theme=\"light\"]) .tok-typeName, :root:not([data-theme=\"light\"]) .tok-className { color: #e5c07b; }\n :root:not([data-theme=\"light\"]) .tok-operator, :root:not([data-theme=\"light\"]) .tok-punctuation { color: #abb2bf; }\n :root:not([data-theme=\"light\"]) .tok-propertyName { color: #61afef; }\n :root:not([data-theme=\"light\"]) .tok-tagName { color: #98c379; }\n}\n:root[data-theme=\"dark\"] .tok-keyword { color: #4ec9b0; }\n:root[data-theme=\"dark\"] .tok-string, :root[data-theme=\"dark\"] .tok-string2 { color: #b5cea8; }\n:root[data-theme=\"dark\"] .tok-number, :root[data-theme=\"dark\"] .tok-atom { color: #d19a66; }\n:root[data-theme=\"dark\"] .tok-function { color: #dcdcaa; }\n:root[data-theme=\"dark\"] .tok-typeName, :root[data-theme=\"dark\"] .tok-className { color: #e5c07b; }\n:root[data-theme=\"dark\"] .tok-operator, :root[data-theme=\"dark\"] .tok-punctuation { color: #abb2bf; }\n:root[data-theme=\"dark\"] .tok-propertyName { color: #61afef; }\n:root[data-theme=\"dark\"] .tok-tagName { color: #98c379; }\n\n/* -- Two columns, then one -------------------------------------------------------------- */\n@media (max-width: 1024px) {\n .container { grid-template-columns: var(--left-sidebar-width) minmax(0, 1fr); }\n .right-sidebar { display: none; }\n}\n@media (max-width: 768px) {\n .container { display: block; padding: 0 15px; }\n .left-sidebar {\n position: relative;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 15px;\n max-height: none;\n padding: 20px 0;\n overflow: visible;\n }\n .site-info { margin: 0; }\n .site-title { flex-direction: row; align-items: center; gap: 12px; }\n .site-avatar { width: 48px; height: 48px; }\n .site-avatar .site-logo { width: 24px; height: 21px; }\n .site-description { display: none; }\n .nav-toggle {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: 5px;\n width: 44px;\n height: 44px;\n padding: 10px;\n border: 0;\n border-radius: var(--tag-border-radius);\n background: var(--card-background);\n box-shadow: var(--shadow-l1);\n cursor: pointer;\n }\n .nav-toggle span { display: block; height: 2px; background: var(--card-text-color-main); border-radius: 2px; }\n .menu {\n position: fixed;\n top: 0;\n left: 0;\n bottom: 0;\n width: min(30rem, 85vw); /* rem is 10px here */\n padding: 30px 20px;\n background: var(--card-background);\n box-shadow: var(--shadow-l2);\n transform: translateX(-100%);\n visibility: hidden;\n transition: transform 160ms ease-out, visibility 0s linear 160ms;\n z-index: 40;\n overflow-y: auto;\n }\n body.nav-open .menu { transform: translateX(0); visibility: visible; transition: transform 160ms ease-out; }\n body.nav-open .nav-backdrop { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.35); z-index: 35; }\n .main { padding: 10px 0 var(--section-separation); }\n .article-card .article-title { font-size: 1.9rem; }\n .article-page .article-title { font-size: 2.2rem; }\n .article-pagination { grid-template-columns: 1fr; }\n .pagination-card.next { grid-column: auto; }\n}\n@media (prefers-reduced-motion: reduce) {\n .menu, body.nav-open .menu, .article-card { transition: none; }\n}\n",
11272
11621
  "../etherpk-stack/assets/theme.js": "/*\n * The light / dark toggle: a `.theme-toggle` button flips the page between the two and the\n * choice is remembered in this browser; with no choice made the page follows the system. The\n * stylesheet keys on `data-theme` on <html>, and shell-top applies the remembered choice before\n * the first paint so a dark reader never sees a light flash.\n */\n(function () {\n 'use strict';\n var KEY = 'etherpk-theme';\n var root = document.documentElement;\n function isDark() {\n var chosen = root.getAttribute('data-theme');\n if (chosen === 'dark' || chosen === 'light') return chosen === 'dark';\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n function apply(theme) {\n if (theme === 'dark' || theme === 'light') root.setAttribute('data-theme', theme);\n else root.removeAttribute('data-theme');\n var buttons = document.querySelectorAll('.theme-toggle');\n for (var i = 0; i < buttons.length; i++) buttons[i].setAttribute('aria-pressed', isDark() ? 'true' : 'false');\n }\n try { apply(localStorage.getItem(KEY)); } catch (_e) { apply(null); }\n var toggles = document.querySelectorAll('.theme-toggle');\n for (var i = 0; i < toggles.length; i++) {\n toggles[i].addEventListener('click', function () {\n var next = isDark() ? 'light' : 'dark';\n apply(next);\n try { localStorage.setItem(KEY, next); } catch (_e) { /* private mode: the choice lasts the page */ }\n });\n }\n})();\n",
@@ -11279,6 +11628,7 @@ var files = /* #__PURE__ */ Object.assign({
11279
11628
  "../etherpk-stack/partials/article-card.html": " <article class=\"article-card\">\n <div class=\"article-details\">\n <span class=\"article-category\">{{kind}}</span>\n <h2 class=\"article-title\"><a href=\"{{url}}\">{{title}}</a></h2>\n {{#excerpt}}<p class=\"article-subtitle\">{{excerpt}}</p>{{/excerpt}}\n <footer class=\"article-time\">{{#date}}<time datetime=\"{{date}}\">{{date}}</time>{{/date}}</footer>\n </div>\n </article>\n",
11280
11629
  "../etherpk-stack/partials/backlinks.html": " {{#page.hasBacklinks}}\n <section class=\"backlinks\" aria-labelledby=\"backlinks-heading\">\n <h2 id=\"backlinks-heading\">Linked references</h2>\n <ul class=\"backlink-list\">\n {{#page.backlinks}}\n <li class=\"backlink-source\">\n <a href=\"{{url}}\">{{title}}</a>\n <ul>\n {{#refs}}<li class=\"backlink-context\">{{{contextHtml}}}</li>{{/refs}}\n </ul>\n </li>\n {{/page.backlinks}}\n </ul>\n </section>\n {{/page.hasBacklinks}}\n",
11281
11630
  "../etherpk-stack/partials/before-content.html": "",
11631
+ "../etherpk-stack/partials/favicon.html": "<!-- The EtherPK icon in the browser tab: assets/favicon.svg, the same file the EtherPK apps use. Fill the \"favicon\" include with your own <link rel=\"icon\"> tag to replace it. -->\n<link rel=\"icon\" href=\"theme/favicon.svg\" type=\"image/svg+xml\">\n",
11282
11632
  "../etherpk-stack/partials/footer.html": " <footer class=\"site-footer\">\n <p>&copy; {{site.year}} {{site.title}}{{#site.hasFeed}} · <a href=\"feed.xml\">Feed</a>{{/site.hasFeed}}</p>\n <p>Published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a></p>\n </footer>\n",
11283
11633
  "../etherpk-stack/partials/head.html": "",
11284
11634
  "../etherpk-stack/partials/header.html": " <header class=\"site-info\">\n <a class=\"site-title\" href=\"index.html\">\n <span class=\"site-avatar\">{{> logo}}</span>\n <span class=\"site-name\">{{site.title}}</span>\n </a>\n {{#page.isHome}}{{#page.excerpt}}<p class=\"site-description\">{{page.excerpt}}</p>{{/page.excerpt}}{{/page.isHome}}\n </header>\n",
@@ -11286,11 +11636,12 @@ var files = /* #__PURE__ */ Object.assign({
11286
11636
  "../etherpk-stack/partials/nav-item.html": " <li class=\"{{#current}}nav-current {{/current}}{{#open}}nav-open {{/open}}{{^href}}nav-group{{/href}}\">\n {{#href}}<a href=\"{{href}}\"{{#current}} aria-current=\"page\"{{/current}}{{#external}} rel=\"noopener\"{{/external}}>{{label}}</a>{{/href}}\n {{^href}}<span class=\"nav-group-label\">{{{labelHtml}}}</span>{{/href}}\n {{#hasChildren}}<ul class=\"site-nav-list\">{{#children}}{{> nav-item}}{{/children}}</ul>{{/hasChildren}}\n </li>\n",
11287
11637
  "../etherpk-stack/partials/scripts.html": "",
11288
11638
  "../etherpk-stack/partials/shell-bottom.html": " {{> after-content}}\n {{> backlinks}}\n {{> footer}}\n </main>\n <aside class=\"sidebar right-sidebar\">\n {{> widgets}}\n </aside>\n </div>\n {{> theme-scripts}}\n {{> scripts}}\n</body>\n</html>\n",
11289
- "../etherpk-stack/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} - {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n <script>try{var t=localStorage.getItem('etherpk-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}<\/script>\n {{> head}}\n</head>\n<body class=\"layout-stack{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n <div class=\"container\">\n <aside class=\"sidebar left-sidebar\">\n {{> header}}\n <button class=\"nav-toggle menu-icon\" type=\"button\" aria-controls=\"site-nav\" aria-expanded=\"false\" aria-label=\"Menu\"><span></span><span></span><span></span></button>\n <div class=\"nav-backdrop\" hidden></div>\n <nav id=\"site-nav\" class=\"menu site-nav\" aria-label=\"Site\">\n <ul class=\"site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n </ul>\n <button class=\"theme-toggle menu-item\" type=\"button\" aria-pressed=\"false\"><svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg><span>Dark mode</span></button>\n </nav>\n </aside>\n <main id=\"content\" class=\"main\">\n {{> before-content}}\n",
11639
+ "../etherpk-stack/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} - {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n <script>try{var t=localStorage.getItem('etherpk-theme');if(t==='dark'||t==='light')document.documentElement.setAttribute('data-theme',t)}catch(e){}<\/script>\n {{> favicon}}\n {{> head}}\n</head>\n<body class=\"layout-stack{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n <div class=\"container\">\n <aside class=\"sidebar left-sidebar\">\n {{> header}}\n <button class=\"nav-toggle menu-icon\" type=\"button\" aria-controls=\"site-nav\" aria-expanded=\"false\" aria-label=\"Menu\"><span></span><span></span><span></span></button>\n <div class=\"nav-backdrop\" hidden></div>\n <nav id=\"site-nav\" class=\"menu site-nav\" aria-label=\"Site\">\n <ul class=\"site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n </ul>\n <button class=\"theme-toggle menu-item\" type=\"button\" aria-pressed=\"false\"><svg viewBox=\"0 0 24 24\" width=\"20\" height=\"20\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg><span>Dark mode</span></button>\n </nav>\n </aside>\n <main id=\"content\" class=\"main\">\n {{> before-content}}\n",
11290
11640
  "../etherpk-stack/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n <script src=\"theme/theme.js\" defer><\/script>\n",
11291
11641
  "../etherpk-stack/partials/toc.html": " <nav class=\"toc\" aria-label=\"Table of contents\">\n <ul>\n {{#page.toc}}<li class=\"toc-level-{{level}}\"><a href=\"#{{id}}\">{{text}}</a></li>{{/page.toc}}\n </ul>\n </nav>\n",
11292
11642
  "../etherpk-stack/partials/widgets.html": " <section class=\"widget search-widget\" aria-label=\"Search\">\n <form class=\"site-search\" role=\"search\" data-search-index=\"{{site.search.url}}\" data-search-script=\"{{site.search.scriptUrl}}\">\n <label class=\"visually-hidden\" for=\"site-search-input\">Search</label>\n <input id=\"site-search-input\" type=\"search\" name=\"q\" placeholder=\"Search\" autocomplete=\"off\">\n <div class=\"site-search-results\" role=\"listbox\" hidden></div>\n </form>\n </section>\n {{#page.hasToc}}\n <section class=\"widget toc-widget\">\n <h2 class=\"widget-title\">Table of contents</h2>\n{{> toc}}\n </section>\n {{/page.hasToc}}\n {{#site.hasPostsArchive}}\n <!-- Years with counts, as Stack's archives widget does: a list of every post would grow\n with the blog; the archive page carries them. -->\n <section class=\"widget archives\">\n <h2 class=\"widget-title\">Archives</h2>\n <ul class=\"widget-list\">\n {{#site.postsByYear}}<li><a href=\"{{site.postsUrl}}#y{{year}}\"><span>{{year}}</span><span class=\"widget-count\">{{count}}</span></a></li>{{/site.postsByYear}}\n </ul>\n <p class=\"widget-more\"><a href=\"{{site.postsUrl}}\">All posts</a></p>\n </section>\n {{/site.hasPostsArchive}}\n {{#site.hasJournals}}\n <section class=\"widget journal\">\n <h2 class=\"widget-title\">Journal</h2>\n <p class=\"widget-more\"><a href=\"journal.html\">Every entry, by day</a></p>\n </section>\n {{/site.hasJournals}}\n <!-- A blog's posts are in the archive above; only its other pages belong here. -->\n {{#site.isBlog}}{{#site.hasUndatedPages}}\n <section class=\"widget pages\">\n <h2 class=\"widget-title\">Pages</h2>\n <ul class=\"widget-list\">\n {{#site.undatedPages}}<li><a href=\"{{url}}\"><span>{{title}}</span></a></li>{{/site.undatedPages}}\n </ul>\n </section>\n {{/site.hasUndatedPages}}{{/site.isBlog}}\n {{^site.isBlog}}\n <section class=\"widget pages\">\n <h2 class=\"widget-title\">Pages</h2>\n <ul class=\"widget-list\">\n {{#site.pages}}<li><a href=\"{{url}}\"><span>{{title}}</span></a></li>{{/site.pages}}\n </ul>\n </section>\n {{/site.isBlog}}\n",
11293
- "../etherpk-stack/theme.json": "{\n \"name\": \"etherpk-stack\",\n \"title\": \"EtherPK Stack\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Hugo's Stack: a card-based blog with a left sidebar of site identity and menu, article cards in the middle, and widgets (search, contents, archive, pages) on the right.\",\n \"kinds\": [\"blog\", \"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The avatar above the site title in the sidebar: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The left sidebar's identity block (avatar, title, description). Replaces the theme's - the menu stays.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The footer under the article column.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the theme toggle): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\", \"partials/article-card.html\", \"partials/widgets.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/search.js\", \"assets/theme.js\"\n ]\n}\n",
11643
+ "../etherpk-stack/theme.json": "{\n \"name\": \"etherpk-stack\",\n \"title\": \"EtherPK Stack\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Hugo's Stack: a card-based blog with a left sidebar of site identity and menu, article cards in the middle, and widgets (search, contents, archive, pages) on the right.\",\n \"kinds\": [\"blog\", \"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The avatar above the site title in the sidebar: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"favicon\", \"description\": \"The icon in the browser tab: a <link rel=\\\"icon\\\"> tag. Raw HTML in the page body. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The left sidebar's identity block (avatar, title, description). Replaces the theme's - the menu stays.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The footer under the article column.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search, the theme toggle): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/favicon.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\", \"partials/article-card.html\", \"partials/widgets.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/favicon.svg\", \"assets/search.js\", \"assets/theme.js\"\n ]\n}\n",
11644
+ "../etherpk-terminal/assets/favicon.svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 191.82 166.13\">\n <path fill=\"#0a0a0a\" fill-rule=\"evenodd\" d=\"M67.09,132.92,47.93,166.13H9.59L0,149.52,86.33,0H105.5L19.17,149.52H38.34l19.17-33.2h76.8l9.59,16.6Zm48-83.12,9.65,16.71H143.9L115.08,16.6q-24,41.57-48,83.12H143.9q14.37,24.9,28.76,49.8h-96l-9.59,16.61H182.24l9.58-16.61-38.34-66.4H96C95.85,82.9,113.39,52.73,115.08,49.8Z\"/>\n</svg>\n",
11294
11645
  "../etherpk-terminal/assets/search.js": "/*\n * Search for a Published Site: reads search.json (built over the publication's own documents\n * and nothing else) and scores documents by title and text matches. No dependencies, no\n * network beyond the one index file, and nothing runs until the search box is used. The same\n * script runs the sidebar toggle on small screens, so a page carries one script.\n */\n(function () {\n 'use strict';\n\n // -- Navigation toggle (small screens) -------------------------------------------------\n var toggle = document.querySelector('.nav-toggle');\n var backdrop = document.querySelector('.nav-backdrop');\n var nav = document.getElementById('site-nav');\n function setNavOpen(open) {\n var was = document.body.classList.contains('nav-open');\n document.body.classList.toggle('nav-open', open);\n if (toggle) toggle.setAttribute('aria-expanded', open ? 'true' : 'false');\n if (backdrop) backdrop.hidden = !open;\n // Focus follows the drawer: into its first link on open, back to the button on close,\n // so a keyboard or screen-reader user is never left on something now hidden.\n if (open && !was) {\n var first = nav.querySelector('a');\n if (first) first.focus();\n } else if (!open && was && toggle) {\n toggle.focus();\n }\n }\n if (toggle && nav) {\n toggle.addEventListener('click', function () { setNavOpen(!document.body.classList.contains('nav-open')); });\n if (backdrop) backdrop.addEventListener('click', function () { setNavOpen(false); });\n document.addEventListener('keydown', function (e) {\n if (e.key === 'Escape' && document.body.classList.contains('nav-open')) setNavOpen(false);\n });\n }\n\n // -- Search --------------------------------------------------------------------------\n var form = document.querySelector('.site-search');\n if (!form) return;\n var input = form.querySelector('input[type=\"search\"]');\n var results = form.querySelector('.site-search-results');\n var indexUrl = form.getAttribute('data-search-index') || 'search.json';\n var scriptUrl = form.getAttribute('data-search-script') || 'search-index.js';\n var index = null;\n var loading = null;\n /* True once neither route produced the index: the results then say so rather than \"nothing matches\". */\n var unavailable = false;\n\n /* The index as a script assigning window.etherpkSearchIndex: what a site opened from disk can\n load, since file:// refuses fetch but allows <script src>. */\n function loadScript() {\n return new Promise(function (resolve, reject) {\n if (window.etherpkSearchIndex) { resolve(window.etherpkSearchIndex); return; }\n var s = document.createElement('script');\n s.src = scriptUrl;\n s.onload = function () {\n if (window.etherpkSearchIndex) resolve(window.etherpkSearchIndex);\n else reject(new Error('no index'));\n };\n s.onerror = function () { reject(new Error('no script')); };\n document.head.appendChild(s);\n });\n }\n\n function load() {\n if (index) return Promise.resolve(index);\n if (!loading) {\n loading = fetch(indexUrl).then(function (r) {\n if (!r.ok) throw new Error(String(r.status));\n return r.json();\n }).catch(loadScript).then(function (data) {\n index = (data && data.documents) || [];\n return index;\n }).catch(function () { unavailable = true; index = []; return index; });\n }\n return loading;\n }\n\n function tokens(q) {\n return q.toLowerCase().split(/\\s+/).filter(function (t) { return t.length > 0; });\n }\n\n function score(doc, terms) {\n var title = (doc.title || '').toLowerCase();\n var text = (doc.text || '').toLowerCase();\n var s = 0;\n for (var i = 0; i < terms.length; i++) {\n var t = terms[i];\n if (title === t) s += 20;\n else if (title.indexOf(t) === 0) s += 10;\n else if (title.indexOf(t) !== -1) s += 6;\n var at = text.indexOf(t);\n if (at !== -1) s += 2;\n else if (title.indexOf(t) === -1) return 0; // every term must match somewhere\n }\n return s;\n }\n\n function snippet(doc, terms) {\n var text = doc.text || '';\n var lower = text.toLowerCase();\n var at = -1;\n for (var i = 0; i < terms.length && at === -1; i++) at = lower.indexOf(terms[i]);\n if (at === -1) return doc.excerpt || '';\n var start = Math.max(0, at - 60);\n var end = Math.min(text.length, at + 100);\n return (start > 0 ? '…' : '') + text.slice(start, end).replace(/\\s+/g, ' ') + (end < text.length ? '…' : '');\n }\n\n function escapeHtml(s) {\n return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;');\n }\n\n function render(list, q) {\n if (!q) { results.hidden = true; results.innerHTML = ''; return; }\n if (unavailable) {\n results.innerHTML = '<p class=\"site-search-empty\">The search index could not be loaded.</p>';\n results.hidden = false;\n return;\n }\n if (list.length === 0) {\n results.innerHTML = '<p class=\"site-search-empty\">Nothing matches \"' + escapeHtml(q) + '\".</p>';\n results.hidden = false;\n return;\n }\n results.innerHTML = list.map(function (hit) {\n return '<a class=\"site-search-hit\" role=\"option\" href=\"' + escapeHtml(hit.doc.url) + '\">' +\n '<span class=\"site-search-title\">' + escapeHtml(hit.doc.title) + '</span>' +\n '<span class=\"site-search-snippet\">' + escapeHtml(hit.snippet) + '</span></a>';\n }).join('');\n results.hidden = false;\n }\n\n var timer = null;\n function run() {\n var q = input.value.trim();\n var terms = tokens(q);\n if (terms.length === 0) { render([], ''); return; }\n load().then(function (docs) {\n var hits = [];\n for (var i = 0; i < docs.length; i++) {\n var s = score(docs[i], terms);\n if (s > 0) hits.push({ doc: docs[i], score: s, snippet: snippet(docs[i], terms) });\n }\n hits.sort(function (a, b) { return b.score - a.score || a.doc.title.localeCompare(b.doc.title); });\n render(hits.slice(0, 12), q);\n });\n }\n\n input.addEventListener('input', function () {\n clearTimeout(timer);\n timer = setTimeout(run, 120);\n });\n input.addEventListener('focus', function () { load(); });\n form.addEventListener('submit', function (e) {\n e.preventDefault();\n var first = results.querySelector('a');\n if (first) window.location.href = first.getAttribute('href');\n });\n document.addEventListener('keydown', function (e) {\n if (e.key === '/' && document.activeElement !== input && !/input|textarea/i.test(document.activeElement.tagName)) {\n e.preventDefault();\n input.focus();\n }\n if (e.key === 'Escape' && !results.hidden) { render([], ''); }\n });\n document.addEventListener('click', function (e) {\n if (!form.contains(e.target)) render([], '');\n });\n})();\n",
11295
11646
  "../etherpk-terminal/assets/theme.css": "/* EtherPK's Terminal: after Hugo's Terminal (panr, MIT), written afresh for EtherPK's view - one\n monospace face, a dark ground, no colour at all - the accent is a brighter shade of the text,\n so everything is drawn in tones of one ink - the block logo with its blinking cursor,\n underlined titles, framed boxes and bordered buttons. Uses Fira Code\n or the system monospace, so the site loads nothing from anywhere. Classes the publisher emits\n and this file styles: .wikilink, .wikilink-missing, .task, .task-tag, .toc, .backlinks,\n .site-nav-list, .diagram, .math, .tok-* (code). */\n\n:root {\n --accent: #f2f2f2;\n --background: #1d1e28;\n --color: #cfcfd6;\n --color-muted: #8f8f9a;\n --border-color: rgba(255, 255, 255, 0.1);\n --code-bg: rgba(255, 255, 255, 0.06);\n --font: \"Fira Code\", ui-monospace, Monaco, Consolas, \"Ubuntu Mono\", monospace;\n --container-width: 1080px;\n --mark: rgba(255, 255, 255, 0.22);\n --missing: var(--color-muted);\n}\n/* Terminal is dark by nature; a light system preference is respected, the ink inverted. */\n@media (prefers-color-scheme: light) {\n :root:not([data-theme=\"dark\"]) {\n --accent: #111111;\n --background: #f7f4ef;\n --color: #2b2b2b;\n --color-muted: #6a6a6a;\n --mark: rgba(0, 0, 0, 0.14);\n --border-color: rgba(0, 0, 0, 0.12);\n --code-bg: rgba(0, 0, 0, 0.05);\n }\n}\n\n*, *::before, *::after { box-sizing: border-box; }\nhtml { color-scheme: dark light; }\nbody {\n margin: 0;\n font-family: var(--font);\n font-size: 0.9375rem;\n line-height: 1.6;\n background: var(--background);\n color: var(--color);\n text-rendering: optimizeLegibility;\n -webkit-font-smoothing: antialiased;\n font-feature-settings: \"liga\", \"tnum\", \"zero\", \"ss01\", \"locl\";\n}\na { color: var(--accent); }\na:hover { text-decoration: none; }\nimg { max-width: 100%; height: auto; display: block; }\n.visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.skip-link { position: absolute; left: -999px; top: 0; background: var(--accent); color: var(--background); padding: 8px 16px; z-index: 100; }\n.skip-link:focus { left: 16px; top: 16px; }\n\n.container { max-width: var(--container-width); margin: 0 auto; padding: 40px; }\n\n/* -- Header: the prompt-style logo ------------------------------------------------------ */\n.header { margin-bottom: 40px; }\n.header__inner { display: flex; align-items: center; justify-content: space-between; gap: 20px; }\n/* The block logo: the mark, the title and a blinking cursor on one line, the mark's box sized\n to the text's cap height so the two read as one glyph run. */\n.logo {\n display: inline-flex;\n align-items: center;\n gap: 12px;\n padding: 8px 16px;\n background: var(--accent);\n color: var(--background);\n font-size: 1.375rem;\n line-height: 1;\n font-weight: bold;\n text-decoration: none;\n}\n.logo:hover { text-decoration: none; }\n.logo__mark { display: inline-flex; align-items: center; line-height: 0; }\n.logo__mark .site-logo { display: block; width: 1.3em; height: 1.125em; }\n.logo__cursor {\n display: inline-block;\n width: 0.5em;\n height: 1em;\n background: var(--background);\n animation: cursor 1s infinite;\n}\n@keyframes cursor { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } }\n@media (prefers-reduced-motion: reduce) { .logo__cursor { animation: none; } }\n.menu { margin-top: 20px; display: flex; flex-wrap: wrap; align-items: center; gap: 12px 30px; }\n.menu__inner { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px 30px; }\n.menu__inner li a, .menu__inner .nav-group-label { color: var(--color); text-decoration: none; }\n.menu__inner li a:hover { color: var(--accent); }\n.menu__inner .nav-current > a { color: var(--accent); }\n.menu__inner .nav-current > a::after { content: \" *\"; }\n.menu__inner .nav-group-label { color: var(--color-muted); }\n.menu__inner .nav-group-label::after { content: \":\"; }\n/* A nested outline level follows its parent on the same line, the way a path reads. */\n.menu__inner li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 12px; }\n.menu__inner .site-nav-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0 12px; }\n.menu__inner .site-nav-list li::before { content: \"/\"; color: var(--color-muted); margin-right: 12px; }\n.nav-toggle { display: none; }\n.nav-backdrop { display: none; }\n.site-search { position: relative; margin-left: auto; flex: 0 1 16rem; }\n.site-search input {\n width: 100%;\n padding: 5px 10px;\n border: 1px solid var(--accent);\n background: transparent;\n color: var(--color);\n font: inherit;\n}\n.site-search input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }\n.site-search input::placeholder { color: var(--color-muted); }\n.site-search-results {\n position: absolute;\n right: 0;\n top: calc(100% + 6px);\n width: min(26rem, 90vw);\n max-height: 24rem;\n overflow: auto;\n background: var(--background);\n border: 1px solid var(--accent);\n z-index: 30;\n}\n.site-search-hit { display: block; padding: 8px 12px; color: var(--color); text-decoration: none; border-bottom: 1px solid var(--border-color); }\n.site-search-hit:hover, .site-search-hit:focus { background: var(--code-bg); }\n.site-search-title { display: block; color: var(--accent); }\n.site-search-snippet { display: block; color: var(--color-muted); font-size: 0.9rem; }\n.site-search-empty { margin: 0; padding: 8px 12px; color: var(--color-muted); }\n\n/* -- Posts ------------------------------------------------------------------------------ */\n.posts { width: 100%; }\n.post { width: 100%; }\n.post.on-list { margin-bottom: 40px; padding-bottom: 40px; border-bottom: 1px dotted var(--accent); }\n.post.on-list:last-child { border-bottom: 0; }\n.post-title {\n position: relative;\n margin: 0 0 15px;\n font-size: 1.6rem;\n line-height: 1.3;\n color: var(--accent);\n text-decoration: underline;\n text-decoration-color: var(--accent);\n text-decoration-thickness: 2px;\n text-underline-offset: 6px;\n}\n.post-title a { color: inherit; text-decoration: none; }\n.post-title .wikilink { color: inherit; text-decoration: none; }\n.index-title { text-decoration: none; }\n.index-title::before { content: \"# \"; }\n.post-meta { margin: 0 0 20px; font-size: 0.9rem; color: var(--color-muted); }\n.post-content { margin-top: 20px; }\n.post-content h1, .post-content h2, .post-content h3, .post-content h4 { font-weight: bold; line-height: 1.3; margin: 40px 0 20px; }\n.post-content h2 { font-size: 1.3rem; }\n.post-content h3 { font-size: 1.2rem; }\n.post-content h2::before { content: \"## \"; color: var(--accent); }\n.post-content h3::before { content: \"### \"; color: var(--accent); }\n.post-content p { margin: 0 0 20px; }\n.post-content ul, .post-content ol { margin: 0 0 20px; padding-left: 24px; }\n.post-content li { margin: 4px 0; }\n.post-content ul li::marker { color: var(--accent); }\n.post-content img { margin: 20px 0; border: 1px solid var(--border-color); }\n.post-content .diagram { margin: 20px 0; text-align: center; }\n.post-content .diagram svg { max-width: 100%; height: auto; }\n.post-content blockquote {\n position: relative;\n margin: 40px 0;\n padding: 25px;\n border-top: 1px solid var(--accent);\n border-bottom: 1px solid var(--accent);\n font-style: italic;\n}\n.post-content blockquote::before { content: \"”\"; position: absolute; top: -40px; left: 10px; font-size: 3.875rem; font-style: normal; color: var(--accent); }\n.post-content blockquote p:last-child { margin-bottom: 0; }\n.post-content hr { border: 0; border-top: 1px dashed var(--accent); margin: 40px 0; }\n.post-content table { width: 100%; border-collapse: collapse; margin: 20px 0; }\n.post-content th, .post-content td { padding: 8px 12px; border: 1px dashed var(--accent); text-align: left; }\n.post-content th { color: var(--accent); }\n.post-content code { padding: 1px 6px; background: var(--code-bg); font-family: inherit; font-size: 0.95em; }\n.post-content pre {\n margin: 20px 0;\n padding: 20px;\n background: var(--code-bg);\n border-left: 3px solid var(--accent);\n overflow-x: auto;\n font-family: inherit;\n font-size: 0.95rem;\n line-height: 1.5;\n}\n.post-content pre code { padding: 0; background: none; font-size: inherit; }\n.math-block { overflow-x: auto; margin: 20px 0; }\nmark { background: var(--mark); color: inherit; padding: 0 0.15em; }\n\n/* Wikilinks and tasks. */\na.wikilink { color: var(--accent); }\na.wikilink-missing { color: var(--missing); text-decoration-style: dotted; }\nli.task { list-style: none; margin-left: -24px; }\nli.task input[type=\"checkbox\"] { accent-color: var(--accent); margin-right: 6px; vertical-align: -0.1em; }\nli.task.done { color: var(--color-muted); text-decoration: line-through; }\n.task-tag { display: inline-block; padding: 0 6px; margin-right: 4px; border: 1px solid var(--accent); color: var(--accent); font-size: 0.8rem; }\n.task-tag.cancelled { text-decoration: line-through; }\n\n/* Buttons, frames, the table of contents and pagination. */\n.button {\n display: inline-flex;\n align-items: center;\n gap: 10px;\n padding: 8px 18px;\n border: 1px solid var(--accent);\n color: var(--accent);\n font: inherit;\n text-decoration: none;\n background: transparent;\n cursor: pointer;\n}\n.button:hover { background: var(--accent); color: var(--background); }\n.button.inline { display: inline-flex; }\n.read-more { margin-top: 10px; }\n.all-posts { margin: 30px 0 0; }\n/* The contents page of a docs site: the outline as a tree, then any page it leaves out. */\n.page-index-list { list-style: none; margin: 0; padding: 0; }\n.page-index-list .page-index-list { padding-left: 24px; }\n.page-index-list li { margin: 6px 0; }\n.page-index-list li::before { content: \"- \"; color: var(--accent); }\n.page-index-list a { color: var(--color); }\n.page-index-list a:hover { color: var(--accent); }\n.page-index-group-label { color: var(--color-muted); }\n.page-index-group-label::after { content: \":\"; }\n.page-index-other { margin: 30px 0 10px; font-size: 1.1rem; }\n.page-index-other::before { content: \"## \"; color: var(--accent); }\n/* The archives: a year as a prompt-style heading, then one line per post. */\n.archive-year { margin: 30px 0 10px; font-size: 1.1rem; }\n.archive-year::before { content: \"## \"; color: var(--accent); }\n.archive-list { list-style: none; margin: 0; padding: 0; }\n.archive-list li { margin: 6px 0; }\n.archive-list time { color: var(--color-muted); margin-right: 12px; font-variant-numeric: tabular-nums; }\n.archive-list a { color: var(--color); }\n.archive-list a:hover { color: var(--accent); }\n.archive-excerpt { color: var(--color-muted); }\n.framed { border: 1px solid var(--accent); padding: 20px; }\n.toc { margin: 20px 0 40px; }\n.toc__title { margin: 0 0 10px; color: var(--accent); }\n.toc ul { list-style: none; margin: 0; padding: 0; }\n.toc li { margin: 4px 0; }\n.toc li::before { content: \"- \"; color: var(--accent); }\n.toc li.toc-level-3 { padding-left: 1.5rem; }\n.toc li.toc-level-4 { padding-left: 3rem; }\n.toc a { color: var(--color); text-decoration: none; }\n.toc a:hover { color: var(--accent); }\n.pagination { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 10px; margin-top: 50px; }\n.pagination__next { margin-left: auto; }\n.button__text { max-width: 20rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n\n/* Linked references, framed like everything else worth a box. */\n.backlinks { margin-top: 40px; border: 1px solid var(--accent); padding: 20px; }\n.backlinks h2 { margin: 0 0 12px; font-size: 1rem; color: var(--accent); }\n.backlinks h2::before { content: \":: \"; }\n.backlink-list { list-style: none; margin: 0; padding: 0; }\n.backlink-source { margin: 0 0 12px; }\n.backlink-source > a { color: var(--color); }\n.backlink-source ul { list-style: none; margin: 4px 0 0; padding: 0 0 0 16px; border-left: 1px dashed var(--accent); }\n.backlink-context { color: var(--color-muted); font-size: 0.9rem; margin: 2px 0; }\n\n/* -- Footer ----------------------------------------------------------------------------- */\n.footer { margin-top: 60px; padding-top: 20px; border-top: 1px dotted var(--accent); font-size: 0.9rem; color: var(--color-muted); }\n.footer__inner { display: flex; flex-wrap: wrap; gap: 10px; }\n.copyright { display: flex; flex-wrap: wrap; gap: 0 8px; }\n.footer__separator { color: var(--accent); }\n\n/* Code tokens (lezer classHighlighter names). */\n/* Code tokens (lezer classHighlighter names), in tones of the one ink: weight and italics carry\n the distinction the usual colours would. */\n.tok-keyword { color: var(--accent); font-weight: bold; }\n.tok-string, .tok-string2 { color: var(--color); font-style: italic; }\n.tok-comment, .tok-lineComment, .tok-blockComment { color: var(--color-muted); font-style: italic; }\n.tok-number, .tok-bool, .tok-null, .tok-atom { color: var(--accent); }\n.tok-function, .tok-definition.tok-variableName { color: var(--accent); }\n.tok-typeName, .tok-className, .tok-namespace { color: var(--accent); font-weight: bold; }\n.tok-operator, .tok-punctuation { color: var(--color-muted); }\n.tok-propertyName, .tok-attributeName { color: var(--color); }\n.tok-tagName { color: var(--accent); }\n.tok-invalid { color: var(--missing); text-decoration: underline wavy; }\n\n/* -- Small screens: the menu behind a Terminal-style button ----------------------------- */\n@media (max-width: 684px) {\n .container { padding: 20px; }\n .nav-toggle {\n display: inline-block;\n padding: 5px 10px;\n border: 1px solid var(--accent);\n background: transparent;\n color: var(--accent);\n font: inherit;\n cursor: pointer;\n }\n .nav-toggle::before { content: \"$ \"; }\n .menu { display: none; flex-direction: column; align-items: stretch; gap: 12px; margin-top: 20px; padding: 20px; border: 1px solid var(--accent); }\n body.nav-open .menu { display: flex; }\n .menu__inner { flex-direction: column; gap: 8px; }\n .menu__inner li { flex-direction: column; align-items: flex-start; }\n .menu__inner .site-nav-list { flex-direction: column; padding-left: 12px; }\n .menu__inner .site-nav-list li::before { margin-right: 6px; }\n .site-search { margin-left: 0; flex-basis: auto; }\n .post-title { font-size: 1.3rem; }\n .pagination__prev, .pagination__next { flex-basis: 100%; }\n .pagination__next { justify-content: flex-end; }\n}\n",
11296
11647
  "../etherpk-terminal/layouts/404.html": "{{> shell-top}}\n <article class=\"post\">\n <h1 class=\"post-title\">404 :: not published</h1>\n <div class=\"post-content\">\n <p>$ the page you followed a link to is not part of this site.</p>\n <p><a class=\"button inline\" href=\"index.html\">← {{site.title}}</a></p>\n </div>\n </article>\n{{> shell-bottom}}\n",
@@ -11301,6 +11652,7 @@ var files = /* #__PURE__ */ Object.assign({
11301
11652
  "../etherpk-terminal/partials/after-content.html": "",
11302
11653
  "../etherpk-terminal/partials/backlinks.html": " {{#page.hasBacklinks}}\n <section class=\"backlinks\" aria-labelledby=\"backlinks-heading\">\n <h2 id=\"backlinks-heading\">Linked references</h2>\n <ul class=\"backlink-list\">\n {{#page.backlinks}}\n <li class=\"backlink-source\">\n <a href=\"{{url}}\">{{title}}</a>\n <ul>\n {{#refs}}<li class=\"backlink-context\">{{{contextHtml}}}</li>{{/refs}}\n </ul>\n </li>\n {{/page.backlinks}}\n </ul>\n </section>\n {{/page.hasBacklinks}}\n",
11303
11654
  "../etherpk-terminal/partials/before-content.html": "",
11655
+ "../etherpk-terminal/partials/favicon.html": "<!-- The EtherPK icon in the browser tab: assets/favicon.svg, the same file the EtherPK apps use. Fill the \"favicon\" include with your own <link rel=\"icon\"> tag to replace it. -->\n<link rel=\"icon\" href=\"theme/favicon.svg\" type=\"image/svg+xml\">\n",
11304
11656
  "../etherpk-terminal/partials/footer.html": " <footer class=\"footer\">\n <div class=\"footer__inner\">\n <div class=\"copyright\">\n <span>&copy; {{site.year}} {{site.title}}</span>\n <span class=\"footer__separator\" aria-hidden=\"true\">::</span>\n <span>Published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a></span>\n {{#site.hasFeed}}<span class=\"footer__separator\" aria-hidden=\"true\">::</span><a href=\"feed.xml\">Feed</a>{{/site.hasFeed}}\n </div>\n </div>\n </footer>\n",
11305
11657
  "../etherpk-terminal/partials/head.html": "",
11306
11658
  "../etherpk-terminal/partials/header.html": " <header class=\"header\">\n <div class=\"header__inner\">\n <a class=\"site-title logo\" href=\"index.html\">\n <span class=\"logo__mark\">{{> logo}}</span>\n <span class=\"logo__text\">{{site.title}}</span>\n <span class=\"logo__cursor\" aria-hidden=\"true\"></span>\n </a>\n <button class=\"nav-toggle menu-trigger\" type=\"button\" aria-controls=\"site-nav\" aria-expanded=\"false\" aria-label=\"Menu\">Menu</button>\n </div>\n <div class=\"nav-backdrop\" hidden></div>\n <nav id=\"site-nav\" class=\"menu site-nav\" aria-label=\"Site\">\n <ul class=\"menu__inner site-nav-list\">\n{{#nav}}{{> nav-item}}{{/nav}}\n </ul>\n <form class=\"site-search\" role=\"search\" data-search-index=\"{{site.search.url}}\" data-search-script=\"{{site.search.scriptUrl}}\">\n <label class=\"visually-hidden\" for=\"site-search-input\">Search</label>\n <input id=\"site-search-input\" type=\"search\" name=\"q\" placeholder=\"$ search\" autocomplete=\"off\">\n <div class=\"site-search-results\" role=\"listbox\" hidden></div>\n </form>\n </nav>\n </header>\n",
@@ -11310,10 +11662,10 @@ var files = /* #__PURE__ */ Object.assign({
11310
11662
  "../etherpk-terminal/partials/post-meta.html": " <div class=\"post-meta\">{{#page.hasDate}}<time class=\"post-date\" datetime=\"{{page.date}}\">{{page.date}}</time> :: {{/page.hasDate}}<span class=\"post-reading\">{{page.readingMinutes}} min read</span></div>\n",
11311
11663
  "../etherpk-terminal/partials/scripts.html": "",
11312
11664
  "../etherpk-terminal/partials/shell-bottom.html": " {{> after-content}}\n {{> backlinks}}\n </main>\n {{> footer}}\n </div>\n {{> theme-scripts}}\n {{> scripts}}\n</body>\n</html>\n",
11313
- "../etherpk-terminal/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} :: {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n {{> head}}\n</head>\n<body class=\"layout-terminal{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n <div class=\"container\">\n {{> header}}\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11665
+ "../etherpk-terminal/partials/shell-top.html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <meta name=\"generator\" content=\"EtherPK\">\n <title>{{page.title}}{{^page.isHome}} :: {{site.title}}{{/page.isHome}}</title>\n {{#page.excerpt}}<meta name=\"description\" content=\"{{page.excerpt}}\">{{/page.excerpt}}\n <link rel=\"stylesheet\" href=\"theme/theme.css\">\n {{#site.hasMath}}<link rel=\"stylesheet\" href=\"theme/katex/katex.min.css\">{{/site.hasMath}}\n {{#site.hasCustomCss}}<link rel=\"stylesheet\" href=\"theme/custom.css\">{{/site.hasCustomCss}}\n {{#site.hasFeed}}<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{{site.title}}\" href=\"feed.xml\">{{/site.hasFeed}}\n {{> favicon}}\n {{> head}}\n</head>\n<body class=\"layout-terminal{{#page.isHome}} is-home{{/page.isHome}}{{#page.isJournal}} is-journal{{/page.isJournal}}\">\n <a class=\"skip-link\" href=\"#content\">Skip to content</a>\n <div class=\"container\">\n {{> header}}\n <main id=\"content\" class=\"content\">\n {{> before-content}}\n",
11314
11666
  "../etherpk-terminal/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n",
11315
11667
  "../etherpk-terminal/partials/toc.html": " {{#page.hasToc}}\n <nav class=\"toc framed\" aria-label=\"Table of contents\">\n <p class=\"toc__title\">:: Table of contents</p>\n <ul>\n {{#page.toc}}<li class=\"toc-level-{{level}}\"><a href=\"#{{id}}\">{{text}}</a></li>{{/page.toc}}\n </ul>\n </nav>\n {{/page.hasToc}}\n",
11316
- "../etherpk-terminal/theme.json": "{\n \"name\": \"etherpk-terminal\",\n \"title\": \"EtherPK Terminal\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Hugo's Terminal: monospace, dark, one ink in shades, a block logo with a blinking cursor, underlined titles and framed boxes.\",\n \"kinds\": [\"blog\", \"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The mark before the site title in the header's prompt box: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's prompt-style logo, menu and search box.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\", \"partials/post-meta.html\", \"partials/post-entry.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/search.js\"\n ]\n}\n"
11668
+ "../etherpk-terminal/theme.json": "{\n \"name\": \"etherpk-terminal\",\n \"title\": \"EtherPK Terminal\",\n \"version\": \"1.0.0\",\n \"contract\": 1,\n \"description\": \"In the mould of Hugo's Terminal: monospace, dark, one ink in shades, a block logo with a blinking cursor, underlined titles and framed boxes.\",\n \"kinds\": [\"blog\", \"docs\"],\n \"includes\": [\n { \"name\": \"head\", \"description\": \"Extra markup inside <head>: analytics, fonts, meta tags. Raw HTML in the page body.\" },\n { \"name\": \"logo\", \"description\": \"The mark before the site title in the header's prompt box: an image or inline SVG. Replaces the EtherPK icon.\" },\n { \"name\": \"favicon\", \"description\": \"The icon in the browser tab: a <link rel=\\\"icon\\\"> tag. Raw HTML in the page body. Replaces the EtherPK icon.\" },\n { \"name\": \"header\", \"description\": \"The site header. Replaces the theme's prompt-style logo, menu and search box.\" },\n { \"name\": \"before-content\", \"description\": \"Rendered above every page's content: a banner, a notice.\" },\n { \"name\": \"after-content\", \"description\": \"Rendered below every page's content, above the linked references: an author bio, a comments widget.\" },\n { \"name\": \"footer\", \"description\": \"The site footer.\" },\n { \"name\": \"scripts\", \"description\": \"Scripts before </body>, added after the theme's own (search): analytics, a widget. Raw HTML in the page body.\" },\n { \"name\": \"styles\", \"description\": \"CSS appended after the theme's stylesheet: the page's first css fence.\", \"kind\": \"css\" }\n ],\n \"files\": [\n \"layouts/page.html\", \"layouts/home.html\", \"layouts/journal.html\", \"layouts/archive.html\", \"layouts/404.html\",\n \"partials/shell-top.html\", \"partials/shell-bottom.html\", \"partials/head.html\", \"partials/logo.html\", \"partials/favicon.html\", \"partials/header.html\",\n \"partials/nav-item.html\", \"partials/toc.html\", \"partials/backlinks.html\", \"partials/post-meta.html\", \"partials/post-entry.html\",\n \"partials/before-content.html\", \"partials/after-content.html\", \"partials/footer.html\", \"partials/theme-scripts.html\", \"partials/scripts.html\",\n \"assets/theme.css\", \"assets/favicon.svg\", \"assets/search.js\"\n ]\n}\n"
11317
11669
  });
11318
11670
  var themes = /* @__PURE__ */ new Map();
11319
11671
  for (const [path, text] of Object.entries(files)) {
@@ -11978,6 +12330,14 @@ function isSingleFileName(name) {
11978
12330
  */
11979
12331
  /** Matches a display-size hint at the end of alt text: `|300` or `|300x200` (with optional trailing space). */
11980
12332
  var DISPLAY_SIZE_HINT_REGEX = /\|(\d{1,5})(?:[xX](\d{1,5}))?\s*$/;
12333
+ /**
12334
+ * The CSS `max-width` for a hinted width: the hint, but never wider than the container. Both the
12335
+ * editor and the publisher set it inline, and an inline value outranks a stylesheet's
12336
+ * `img { max-width: 100% }`, so a bare `${px}px` would push a wide image out of a narrower column.
12337
+ */
12338
+ function displayMaxWidth(px) {
12339
+ return `min(${px}px, 100%)`;
12340
+ }
11981
12341
  /** Parse the alt text into its clean label and any display-size hint. A width < 1 (or no hint) yields no dimensions. */
11982
12342
  function parseImageDisplaySizeHint(alt) {
11983
12343
  const match = DISPLAY_SIZE_HINT_REGEX.exec(alt);
@@ -12290,6 +12650,10 @@ function wikilinkRule(md, resolve) {
12290
12650
  * grammars for code), so they are pre-rendered by the orchestrator and handed in as lookups;
12291
12651
  * the renderer itself is synchronous and pure over its inputs.
12292
12652
  */
12653
+ /** The names on a Map Block, one line for the search index and the excerpt; never its coordinates. */
12654
+ function mapNames(source) {
12655
+ return readMapBody(source.split("\n")).items.map((item) => item.name).filter((name) => name !== "").join(", ");
12656
+ }
12293
12657
  /** A document-relative asset reference: any number of `../`, `assets/`, the name. */
12294
12658
  var ASSET_REF$1 = /^(?:\.\.\/)*assets\/(.+)$/;
12295
12659
  /**
@@ -12329,7 +12693,11 @@ function plainText(tokens) {
12329
12693
  else if (child.type === "softbreak" || child.type === "hardbreak") line += " ";
12330
12694
  if (line.trim() !== "") lines.push(line.trim());
12331
12695
  } else if (token.type === "fence" || token.type === "code_block") {
12332
- if (token.info.trim() !== "mermaid" && token.info.trim() !== "math") lines.push(token.content.trim());
12696
+ const info = token.info.trim().split(/\s+/)[0] ?? "";
12697
+ if (info === "map") {
12698
+ const names = mapNames(token.content);
12699
+ if (names !== "") lines.push(names);
12700
+ } else if (info !== "mermaid" && info !== "math") lines.push(token.content.trim());
12333
12701
  }
12334
12702
  return lines.join("\n");
12335
12703
  }
@@ -12368,7 +12736,7 @@ function createDocumentRenderer(options) {
12368
12736
  const parsed = parseImageDisplaySizeHint(self.renderInlineAsText(token.children ?? [], opts, env));
12369
12737
  token.attrSet("alt", parsed.cleanAlt);
12370
12738
  if (parsed.maxWidth !== void 0) {
12371
- const style = [`max-width:${parsed.maxWidth}px`];
12739
+ const style = [`max-width:${displayMaxWidth(parsed.maxWidth)}`];
12372
12740
  if (parsed.maxHeight !== void 0) style.push(`max-height:${parsed.maxHeight}px`);
12373
12741
  token.attrSet("style", style.join(";"));
12374
12742
  }
@@ -12403,6 +12771,11 @@ function createDocumentRenderer(options) {
12403
12771
  return `<div class="math math-block">${renderMath(code.replace(/\n$/, ""), true)}</div>\n`;
12404
12772
  }
12405
12773
  if (lang === "etherpk-cipher") return "";
12774
+ if (lang === "map") {
12775
+ const picture = options.mapPicture?.(code);
12776
+ if (!picture) return "";
12777
+ return `<figure class="map"><img src="${escapeHtml$3(picture.src)}" alt="${escapeHtml$3(picture.alt)}" width="${picture.width}" height="${picture.height}" loading="lazy"></figure>\n`;
12778
+ }
12406
12779
  const highlighted = options.highlighted?.(lang, code);
12407
12780
  return `<pre><code${lang === "" ? "" : ` class="language-${escapeHtml$3(lang)}"`}>${highlighted ?? escapeHtml$3(code)}</code></pre>\n`;
12408
12781
  };
@@ -12844,6 +13217,16 @@ function createThemeRenderer(theme, includes) {
12844
13217
  * a pre-render pass for the asynchronous bits (diagrams, code); then rendering, includes, the
12845
13218
  * theme, the derived files. Everything derived is computed from the included documents alone.
12846
13219
  */
13220
+ /**
13221
+ * Bumped when the way a map is drawn changes, so a site published again holds the new picture
13222
+ * under a new name rather than a cached one under the old.
13223
+ */
13224
+ var MAP_PICTURE_VERSION = 1;
13225
+ /** A picture's file name: from what the map holds and how it is drawn, so an unchanged map publishes the same file. */
13226
+ async function mapPictureName(source) {
13227
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${MAP_PICTURE_VERSION}\n${source}`));
13228
+ return [...new Uint8Array(digest)].slice(0, 8).map((byte) => byte.toString(16).padStart(2, "0")).join("");
13229
+ }
12847
13230
  function assetHrefOf(name) {
12848
13231
  return `assets/${encodeURIComponent(name)}`;
12849
13232
  }
@@ -13021,6 +13404,7 @@ async function publishPublication(source, publication, env, options = {}) {
13021
13404
  includeDocs.set(slot, doc);
13022
13405
  }
13023
13406
  const mermaidSvg = /* @__PURE__ */ new Map();
13407
+ const mapPictures = /* @__PURE__ */ new Map();
13024
13408
  const highlighted = /* @__PURE__ */ new Map();
13025
13409
  const highlight = env.highlightCode ?? highlightCode;
13026
13410
  const probe = createDocumentRenderer({
@@ -13043,12 +13427,44 @@ async function publishPublication(source, publication, env, options = {}) {
13043
13427
  concept: doc.concept
13044
13428
  });
13045
13429
  }
13430
+ } else if (fence.lang === "map") {
13431
+ if (mapPictures.has(fence.code) || errors.some((e) => e.code === "map-not-drawn" && e.concept === doc.concept)) continue;
13432
+ if (!env.renderMap) {
13433
+ errors.push({
13434
+ level: "error",
13435
+ code: "map-not-drawn",
13436
+ message: `"${doc.concept}" holds a map, which this publisher can't draw. Publish the site from EtherPK in a browser, where maps are drawn.`,
13437
+ concept: doc.concept
13438
+ });
13439
+ continue;
13440
+ }
13441
+ try {
13442
+ const picture = await env.renderMap(fence.code);
13443
+ const path = `maps/${await mapPictureName(fence.code)}.${picture.type === "image/webp" ? "webp" : "png"}`;
13444
+ mapPictures.set(fence.code, {
13445
+ ...picture,
13446
+ path,
13447
+ alt: mapAltText(fence.code.split("\n"))
13448
+ });
13449
+ } catch (error) {
13450
+ const why = error instanceof Error ? error.message : String(error);
13451
+ errors.push({
13452
+ level: "error",
13453
+ code: "map-not-drawn",
13454
+ message: `The map in "${doc.concept}" could not be drawn: ${why}. Nothing was published, so try again once it can be.`,
13455
+ concept: doc.concept
13456
+ });
13457
+ }
13046
13458
  } else if (fence.lang !== "" && fence.lang !== "math" && fence.lang !== "etherpk-cipher") {
13047
13459
  const key = `${fence.lang}\n${fence.code}`;
13048
13460
  if (highlighted.has(key)) continue;
13049
13461
  const html = await highlight(fence.lang, fence.code);
13050
13462
  if (html !== null) highlighted.set(key, html);
13051
13463
  }
13464
+ if (errors.length > 0) return {
13465
+ bundle,
13466
+ report
13467
+ };
13052
13468
  let diagramScope = {
13053
13469
  prefix: "mermaid",
13054
13470
  n: 0
@@ -13066,7 +13482,16 @@ async function publishPublication(source, publication, env, options = {}) {
13066
13482
  const svg = mermaidSvg.get(src);
13067
13483
  return svg === void 0 ? void 0 : withDiagramId(svg, `${diagramScope.prefix}_${++diagramScope.n}`);
13068
13484
  },
13069
- highlighted: (lang, code) => highlighted.get(`${lang}\n${code}`)
13485
+ highlighted: (lang, code) => highlighted.get(`${lang}\n${code}`),
13486
+ mapPicture: (src) => {
13487
+ const picture = mapPictures.get(src);
13488
+ return picture && {
13489
+ src: picture.path,
13490
+ alt: picture.alt,
13491
+ width: picture.width,
13492
+ height: picture.height
13493
+ };
13494
+ }
13070
13495
  });
13071
13496
  const pages = [];
13072
13497
  let done = 0;
@@ -13324,6 +13749,7 @@ async function publishPublication(source, publication, env, options = {}) {
13324
13749
  bundle.set(`assets/${name}`, asset.bytes);
13325
13750
  report.assets.copied.push(name);
13326
13751
  }
13752
+ for (const picture of mapPictures.values()) bundle.set(picture.path, picture.bytes);
13327
13753
  for (const [path, text] of theme.files) if (path.startsWith("assets/")) bundle.set(`theme/${path.slice(7)}`, text);
13328
13754
  if (customCss !== "") bundle.set("theme/custom.css", customCss.endsWith("\n") ? customCss : `${customCss}\n`);
13329
13755
  if (hasMath) {
@@ -13675,7 +14101,7 @@ async function runPublish(publication, deps) {
13675
14101
  * Client bundles, so both hosts draw the same SVG, and the post-processing is the Client's
13676
14102
  * (`browser-environment.ts`): the minted id dropped (it collides across pages), `role="img"`.
13677
14103
  */
13678
- var require$1 = createRequire(import.meta.url);
14104
+ var require$2 = createRequire(import.meta.url);
13679
14105
  /** Where Playwright keeps the browsers this package installs: under our cache, not the user's global one. */
13680
14106
  function browsersDir(env) {
13681
14107
  return join(cacheRoot(env), "browsers");
@@ -13718,7 +14144,7 @@ async function setupDiagrams(env, say) {
13718
14144
  say(before.source === "env" ? `Using the Chromium named by ETHERPK_CHROMIUM: ${before.executable}` : `Chromium is already installed: ${before.executable}`);
13719
14145
  return before;
13720
14146
  }
13721
- const cli = require$1.resolve("playwright-core/cli.js");
14147
+ const cli = require$2.resolve("playwright-core/cli.js");
13722
14148
  const dir = env.PLAYWRIGHT_BROWSERS_PATH?.trim() || browsersDir(env);
13723
14149
  say(`Installing Chromium into ${dir} (about 170 MB, once per computer - a diagram is rendered by a browser, and this is the one etherpk-mcp drives)…`);
13724
14150
  await new Promise((resolve, reject) => {
@@ -13751,7 +14177,7 @@ async function setupDiagrams(env, say) {
13751
14177
  return after;
13752
14178
  }
13753
14179
  /** The page every render runs in: Mermaid loaded once, configured as the Client configures it. */
13754
- var PAGE = "<!doctype html><html><head><meta charset=\"utf-8\"></head><body></body></html>";
14180
+ var PAGE$1 = "<!doctype html><html><head><meta charset=\"utf-8\"></head><body></body></html>";
13755
14181
  /** A headless browser from whichever Chromium is available, or null. The caller closes it. */
13756
14182
  async function launchChromium(env) {
13757
14183
  const status = await chromiumStatus(env);
@@ -13773,8 +14199,8 @@ async function openDiagramRenderer(env) {
13773
14199
  const page = () => {
13774
14200
  if (!pagePromise) pagePromise = (async () => {
13775
14201
  const p = await (await browser.newContext({ javaScriptEnabled: true })).newPage();
13776
- await p.setContent(PAGE);
13777
- await p.addScriptTag({ path: require$1.resolve("mermaid/dist/mermaid.min.js") });
14202
+ await p.setContent(PAGE$1);
14203
+ await p.addScriptTag({ path: require$2.resolve("mermaid/dist/mermaid.min.js") });
13778
14204
  await p.evaluate(() => {
13779
14205
  window.mermaid.initialize({
13780
14206
  startOnLoad: false,
@@ -13811,6 +14237,131 @@ async function openDiagramRenderer(env) {
13811
14237
  };
13812
14238
  }
13813
14239
  //#endregion
14240
+ //#region ../../extensions/maps/src/map-host.ts
14241
+ var HOST_STYLE = "https://maps.etherpk.com/styles/light.json";
14242
+ var DEFAULT_MAP_STYLES = {
14243
+ light: HOST_STYLE,
14244
+ dark: HOST_STYLE
14245
+ };
14246
+ //#endregion
14247
+ //#region src/map-pictures.ts
14248
+ /**
14249
+ * Map pictures for a publish from the Headless Client (ADR 0118): a [[Map Block]] is published as
14250
+ * a picture of the map, never as its lines, and the picture is drawn in a real Chromium, the one
14251
+ * Mermaid diagrams use (diagrams.ts), because MapLibre needs WebGL and a page to draw in.
14252
+ *
14253
+ * The page runs the maps extension's own drawing code (`map-picture-page.js`, built from
14254
+ * `src/map-picture-page.ts` beside the Node bundle), so a map published from here looks as it
14255
+ * does published from the browser. It is served, with the MapLibre this package installs, from a
14256
+ * server on the loopback address that answers only those files, so MapLibre's worker loads as it
14257
+ * does in the Client. The basemap comes from EtherPK's map host, or the style
14258
+ * `ETHERPK_MAP_STYLE_URL` names; set empty, maps are drawn on a plain background.
14259
+ */
14260
+ var require$1 = createRequire(import.meta.url);
14261
+ /** The light basemap style a publish draws with, or null for a plain background. */
14262
+ function publishMapStyle(env) {
14263
+ const named = env.ETHERPK_MAP_STYLE_URL;
14264
+ if (named === void 0) return DEFAULT_MAP_STYLES.light;
14265
+ return named.trim() === "" ? null : named.trim();
14266
+ }
14267
+ /** Whether a publish of these bodies draws a map: a fenced block whose info string is `map`. */
14268
+ function needsMaps(bodies) {
14269
+ for (const body of bodies) if (/^\s*(?:-\s+)?```map\s*$/m.test(body)) return true;
14270
+ return false;
14271
+ }
14272
+ /** The page: MapLibre by the import map, then the drawing code, which puts its function on `window`. */
14273
+ var PAGE = `<!doctype html>
14274
+ <html><head><meta charset="utf-8">
14275
+ <script type="importmap">{"imports":{"maplibre-gl":"/maplibre-gl.mjs"}}<\/script>
14276
+ <script type="module" src="/map-picture-page.js"><\/script>
14277
+ </head><body></body></html>`;
14278
+ /** The files the page loads, by path, and where each is on disk. */
14279
+ function pageFiles() {
14280
+ const maplibre = dirname(require$1.resolve("maplibre-gl/dist/maplibre-gl.mjs"));
14281
+ const here = dirname(fileURLToPath(import.meta.url));
14282
+ const bundle = here.endsWith("dist") ? join(here, "map-picture-page.js") : join(here, "..", "dist", "map-picture-page.js");
14283
+ return /* @__PURE__ */ new Map([
14284
+ ["/maplibre-gl.mjs", join(maplibre, "maplibre-gl.mjs")],
14285
+ ["/maplibre-gl-shared.mjs", join(maplibre, "maplibre-gl-shared.mjs")],
14286
+ ["/maplibre-gl-worker.mjs", join(maplibre, "maplibre-gl-worker.mjs")],
14287
+ ["/map-picture-page.js", bundle]
14288
+ ]);
14289
+ }
14290
+ /** A server on the loopback address that answers the page and its files, and nothing else. */
14291
+ async function servePage() {
14292
+ const files = pageFiles();
14293
+ const server = createServer$1((request, response) => {
14294
+ const path = new URL(request.url ?? "/", "http://localhost").pathname;
14295
+ if (path === "/map.html") {
14296
+ response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }).end(PAGE);
14297
+ return;
14298
+ }
14299
+ const file = files.get(path);
14300
+ if (!file) {
14301
+ response.writeHead(404).end();
14302
+ return;
14303
+ }
14304
+ readFile(file).then((bytes) => response.writeHead(200, { "Content-Type": "text/javascript; charset=utf-8" }).end(bytes), () => response.writeHead(404).end());
14305
+ });
14306
+ await new Promise((resolve, reject) => {
14307
+ server.once("error", reject);
14308
+ server.listen(0, "127.0.0.1", () => resolve());
14309
+ });
14310
+ const address = server.address();
14311
+ if (!address || typeof address === "string") throw new Error("The map page server has no port.");
14312
+ return {
14313
+ server,
14314
+ origin: `http://127.0.0.1:${address.port}`
14315
+ };
14316
+ }
14317
+ /**
14318
+ * A renderer over one headless browser, opened for a publish and closed after it. Null when no
14319
+ * browser is available; the caller decides what that means (a publish with maps refuses).
14320
+ */
14321
+ async function openMapRenderer(env) {
14322
+ const browser = await launchChromium(env);
14323
+ if (!browser) return null;
14324
+ const style = publishMapStyle(env);
14325
+ let served = null;
14326
+ let pagePromise;
14327
+ const page = () => {
14328
+ pagePromise ??= (async () => {
14329
+ served = await servePage();
14330
+ const p = await (await browser.newContext()).newPage();
14331
+ await p.goto(`${served.origin}/map.html`);
14332
+ await p.waitForFunction(() => typeof window.etherpkDrawMapPicture === "function", void 0, { timeout: 15e3 });
14333
+ return p;
14334
+ })();
14335
+ return pagePromise;
14336
+ };
14337
+ return {
14338
+ async render(source) {
14339
+ const drawn = await (await page()).evaluate(async ([text, basemap]) => {
14340
+ const picture = await window.etherpkDrawMapPicture(text, { style: basemap });
14341
+ let binary = "";
14342
+ for (const byte of picture.bytes) binary += String.fromCharCode(byte);
14343
+ return {
14344
+ type: picture.type,
14345
+ width: picture.width,
14346
+ height: picture.height,
14347
+ data: btoa(binary)
14348
+ };
14349
+ }, [source, style]);
14350
+ return {
14351
+ type: drawn.type,
14352
+ width: drawn.width,
14353
+ height: drawn.height,
14354
+ bytes: new Uint8Array(Buffer.from(drawn.data, "base64"))
14355
+ };
14356
+ },
14357
+ async dispose() {
14358
+ await browser.close().catch(() => {});
14359
+ const open = served;
14360
+ await new Promise((resolve) => open ? open.server.close(() => resolve()) : resolve());
14361
+ }
14362
+ };
14363
+ }
14364
+ //#endregion
13814
14365
  //#region src/public-fetch.ts
13815
14366
  /**
13816
14367
  * Fetching text from the public internet, and nowhere else: the only network reads a tool makes
@@ -13959,8 +14510,8 @@ async function fetchPublicText(href, options = {}) {
13959
14510
  /**
13960
14511
  * The publisher's environment in Node (ADR 0082, ADR 0084): themes resolved the way the Client
13961
14512
  * resolves them (bundled, the graph's own, a url), KaTeX's stylesheet and fonts read from the
13962
- * package this depends on, Mermaid through a real browser when one is available, and a site
13963
- * folder over `fs` under the same rules as the browser's directory handle. Browser-free
14513
+ * package this depends on, Mermaid and map pictures through a real browser when one is available,
14514
+ * and a site folder over `fs` under the same rules as the browser's directory handle. Browser-free
13964
14515
  * counterpart of `host/browser-environment.ts`; the core is shared.
13965
14516
  */
13966
14517
  var require = createRequire(import.meta.url);
@@ -13992,6 +14543,7 @@ function createNodePublishEnvironment(deps) {
13992
14543
  graphTheme: deps.graphTheme
13993
14544
  }),
13994
14545
  ...renderer ? { renderMermaid: (source) => renderer.render(source) } : {},
14546
+ ...deps.maps ? { renderMap: (source) => deps.maps.render(source) } : {},
13995
14547
  katexAssets
13996
14548
  };
13997
14549
  }
@@ -15243,35 +15795,19 @@ async function writeDownload(directory, name, bytes) {
15243
15795
  }
15244
15796
  throw new Error(`${directory} already holds a thousand files named like ${safe}.`);
15245
15797
  }
15246
- /**
15247
- * Namespace a View kind to the extension that contributes it, so two extensions
15248
- * can each contribute a kind without colliding. First-party (core) kinds are
15249
- * unprefixed (`document`, `backlinks`, …); extension kinds are
15250
- * `"<extensionId>.<kind>"`, mirroring the `publisher.command` convention.
15251
- */
15252
- function namespacedViewKind(extensionId, kind) {
15253
- return `${extensionId}.${kind}`;
15254
- }
15255
15798
  //#endregion
15256
- //#region ../client/src/lib/graph-view/identity.ts
15799
+ //#region ../client/src/lib/navigation/concept-path.ts
15257
15800
  /**
15258
- * The [[Graph View]]'s names: its extension id, its two View kinds and their identities, and its
15259
- * Command ids. Kept apart from the registration so the workspace can ensure the Sidebar
15260
- * resident, and the address code can name the whole graph, without importing the View itself.
15261
- *
15262
- * Two kinds rather than one kind with two targets, because the Layout finds a resident by kind
15263
- * (`findView`, residents.ts) and places a View by its kind's natural region: the resident must
15264
- * never be confused with the whole graph, and each has its own home.
15801
+ * A concept in a URL path (ADR 0023): each `/`-separated segment encoded on its own, so a
15802
+ * concept containing `/` spans several path segments and round-trips through a SvelteKit rest
15803
+ * param, which re-joins segments with `/` and decodes each. Its own module so the extension
15804
+ * address book and the document URLs share it without importing each other.
15265
15805
  */
15266
- var GRAPH_VIEW_EXTENSION_ID = "graph-view";
15267
- namespacedViewKind(GRAPH_VIEW_EXTENSION_ID, "local");
15268
- namespacedViewKind(GRAPH_VIEW_EXTENSION_ID, "whole");
15269
- //#endregion
15270
- //#region ../client/src/lib/navigation/document-url.ts
15271
- /** Encode a concept for the `[...concept]` rest param: per-segment, `/` kept. */
15272
15806
  function encodeConceptPath(concept) {
15273
15807
  return concept.split("/").map(encodeURIComponent).join("/");
15274
15808
  }
15809
+ //#endregion
15810
+ //#region ../client/src/lib/navigation/document-url.ts
15275
15811
  /** The Document URL for a concept within a graph. */
15276
15812
  function documentUrl(graphId, concept) {
15277
15813
  return `${graphUrl(graphId)}/d/${encodeConceptPath(concept)}`;
@@ -15424,7 +15960,7 @@ async function taskLinesWith(lines, fingerprint) {
15424
15960
  return out;
15425
15961
  }
15426
15962
  //#endregion
15427
- //#region ../client/src/lib/graph-view/model/graph-model.ts
15963
+ //#region ../../extensions/graph-view/src/model/graph-model.ts
15428
15964
  /**
15429
15965
  * The [[Graph View]]'s model: the index's {@link LinkGraph} turned into a graphology graph the
15430
15966
  * drawing and the insight lists both read, and the questions asked of it (what is near a
@@ -15543,7 +16079,7 @@ function shortestPath(graph, from, to) {
15543
16079
  return null;
15544
16080
  }
15545
16081
  //#endregion
15546
- //#region ../client/src/lib/graph-view/model/insights.ts
16082
+ //#region ../../extensions/graph-view/src/model/insights.ts
15547
16083
  /**
15548
16084
  * The lists the whole-graph [[Graph View]] shows beside the picture, and the Headless Client's
15549
16085
  * `graph_insights` tool returns: [[Hub]]s, [[Pageless Concept]]s by how many documents mention
@@ -17011,20 +17547,31 @@ async function publish(graph, args, host) {
17011
17547
  }
17012
17548
  const selected = selectDocuments(source.documents, publication, summary.publications);
17013
17549
  const includeTexts = Object.values(publication.includes).map((concept) => source.documents.find((d) => d.concept.toLowerCase() === concept.toLowerCase())?.text ?? "");
17550
+ const bodies = [...selected.included.map((d) => d.text), ...includeTexts];
17014
17551
  let renderer = null;
17015
- if (needsMermaid([...selected.included.map((d) => d.text), ...includeTexts])) {
17552
+ if (needsMermaid(bodies)) {
17016
17553
  renderer = await openDiagramRenderer(env);
17017
17554
  if (!renderer) {
17018
17555
  const status = await chromiumStatus(env, cmd);
17019
17556
  throw new ToolError("chromium_unavailable", `"${publication.name}" has Mermaid diagrams, which a publish draws with a browser, and none is set up on this computer. Ask the user to run: ${status.setupCommand} (or set ETHERPK_CHROMIUM to a Chromium on this machine). Nothing was published.`);
17020
17557
  }
17021
17558
  }
17559
+ let maps = null;
17560
+ if (needsMaps(bodies)) {
17561
+ maps = await openMapRenderer(env);
17562
+ if (!maps) {
17563
+ await renderer?.dispose();
17564
+ const status = await chromiumStatus(env, cmd);
17565
+ throw new ToolError("chromium_unavailable", `"${publication.name}" has maps, which a publish draws as pictures with a browser, and none is set up on this computer. Ask the user to run: ${status.setupCommand} (or set ETHERPK_CHROMIUM to a Chromium on this machine). Nothing was published.`);
17566
+ }
17567
+ }
17022
17568
  try {
17023
17569
  const run = await runPublish(publication, {
17024
17570
  source,
17025
17571
  environment: createNodePublishEnvironment({
17026
17572
  graphTheme: (id) => graph.publishing.graphTheme(id),
17027
- renderer
17573
+ renderer,
17574
+ maps
17028
17575
  }),
17029
17576
  unsettled
17030
17577
  });
@@ -17064,6 +17611,7 @@ async function publish(graph, args, host) {
17064
17611
  };
17065
17612
  } finally {
17066
17613
  await renderer?.dispose();
17614
+ await maps?.dispose();
17067
17615
  }
17068
17616
  }
17069
17617
  /**
@@ -18023,7 +18571,7 @@ function createMcpServer(source, info) {
18023
18571
  }, async (args) => run((graph) => updatePublication(graph, args, host)));
18024
18572
  server.registerTool("publish", {
18025
18573
  title: "Publish",
18026
- description: "Render a publication to its publish folder on this machine and return the report: what was included and why documents were left out, missing links, assets, warnings. The folder is the one the user set with \"etherpk-mcp publish --publication <id> --out <dir>\" (error \"no_publish_folder\" until then - the tool never chooses a folder). Pages with Mermaid diagrams need the browser from \"diagrams setup\" (error \"chromium_unavailable\"). The report is this result, trimmed to counts and first entries - nothing of it is written into the folder, so the site never names the documents it leaves out. Publishing writes files - it does not deploy them.",
18574
+ description: "Render a publication to its publish folder on this machine and return the report: what was included and why documents were left out, missing links, assets, warnings. The folder is the one the user set with \"etherpk-mcp publish --publication <id> --out <dir>\" (error \"no_publish_folder\" until then - the tool never chooses a folder). Pages with Mermaid diagrams or maps need the browser from \"diagrams setup\" (error \"chromium_unavailable\"). The report is this result, trimmed to counts and first entries - nothing of it is written into the folder, so the site never names the documents it leaves out. Publishing writes files - it does not deploy them.",
18027
18575
  inputSchema: { id: z.string().min(1) }
18028
18576
  }, async (args) => run((graph) => publish(graph, args, host)));
18029
18577
  server.registerTool("list_themes", {
@@ -18619,19 +19167,6 @@ function sameNames(a, b) {
18619
19167
  }
18620
19168
  //#endregion
18621
19169
  //#region ../client/src/lib/document/index-link-graph.ts
18622
- /**
18623
- * The [[Graph View]]'s query over the [[Derived Index]]: every [[Concept]] and every pair of
18624
- * concepts a [[Wikilink]] joins, in one answer.
18625
- *
18626
- * Everything comes from rows the index already holds: the `pages` and `aliases` tables, and the
18627
- * `links` table grouped by source document. No document is opened or parsed, which is what lets
18628
- * a whole graph be drawn from one round trip.
18629
- *
18630
- * A [[Protected Document]] contributes no body links (the parser suppresses links inside the
18631
- * cipher fence, and the index never sees plaintext), so it appears here as a name with lines in
18632
- * from plaintext documents and none out. Its title scopes still draw a line, because a page's
18633
- * name is visible wherever the page is.
18634
- */
18635
19170
  function linkGraph(db) {
18636
19171
  const generation = activeIndexGeneration(db);
18637
19172
  const pages = db.all("SELECT concept, concept_key AS key, kind, protected FROM pages WHERE generation = ? ORDER BY id", [generation]);
@@ -18648,6 +19183,7 @@ function linkGraph(db) {
18648
19183
  name: page.concept,
18649
19184
  kind: page.kind
18650
19185
  };
19186
+ if (page.kind === "journal" && isJournalConcept(page.concept)) concept.day = page.concept;
18651
19187
  concepts.set(page.key, page.protected ? {
18652
19188
  ...concept,
18653
19189
  protected: true
@@ -18706,6 +19242,57 @@ function linkGraph(db) {
18706
19242
  };
18707
19243
  }
18708
19244
  //#endregion
19245
+ //#region ../client/src/lib/document/index-map-items.ts
19246
+ /**
19247
+ * The most items one answer carries. Far beyond a real graph's maps (hundreds of Map Blocks of
19248
+ * tens of places each), and small enough that a pathological graph cannot stall the worker or
19249
+ * the drawing.
19250
+ */
19251
+ var MAP_ITEMS_CAP = 2e4;
19252
+ /**
19253
+ * Whether the document with this concept key holds any place or route in the active generation.
19254
+ * Asked before and after a document is indexed again, so an update can say whether it touched a
19255
+ * map and an open Map View reads again only when one did.
19256
+ */
19257
+ function pageHasMapItems(db, key) {
19258
+ return db.all(`SELECT 1 AS found FROM map_items m JOIN pages p ON p.id = m.page_id
19259
+ WHERE p.generation = ? AND p.concept_key = ? LIMIT 1`, [activeIndexGeneration(db), key]).length > 0;
19260
+ }
19261
+ /** Every place and route answering to `concept`, or in the whole graph when it is null. */
19262
+ function mapItems(db, concept, cap = MAP_ITEMS_CAP) {
19263
+ const where = ["p.generation = ?", "p.protected = 0"];
19264
+ const params = [activeIndexGeneration(db)];
19265
+ if (concept !== null) {
19266
+ const names = conceptFamilyKeys(db, conceptKey$1(concept));
19267
+ where.push(`EXISTS (SELECT 1 FROM map_concepts mc
19268
+ WHERE mc.page_id = m.page_id AND mc.block_local_id = m.block_local_id
19269
+ AND mc.concept_key IN (${names.map(() => "?").join(",")}))`);
19270
+ params.push(...names);
19271
+ }
19272
+ const rows = db.all(`SELECT p.concept, p.kind, m.line, m.fence_line, m.text
19273
+ FROM map_items m JOIN pages p ON p.id = m.page_id
19274
+ WHERE ${where.join(" AND ")}
19275
+ ORDER BY p.concept_key, p.id, m.line
19276
+ LIMIT ?`, [...params, cap + 1]);
19277
+ const truncated = rows.length > cap;
19278
+ const items = [];
19279
+ for (const row of truncated ? rows.slice(0, cap) : rows) {
19280
+ const item = readMapLine(row.text);
19281
+ if (item) items.push({
19282
+ concept: row.concept,
19283
+ kind: row.kind,
19284
+ line: row.line,
19285
+ fenceLine: row.fence_line,
19286
+ text: row.text,
19287
+ item
19288
+ });
19289
+ }
19290
+ return {
19291
+ items,
19292
+ truncated
19293
+ };
19294
+ }
19295
+ //#endregion
18709
19296
  //#region ../client/src/lib/document/index-worker/core.ts
18710
19297
  /**
18711
19298
  * The index server, free of any transport (ADR 0041 §2). It owns the SQLite database and
@@ -18805,7 +19392,7 @@ function createIndexCore(host) {
18805
19392
  return db.all("SELECT COUNT(*) AS n FROM pages WHERE generation = ?", [activeIndexGeneration(db)])[0]?.n ?? 0;
18806
19393
  }
18807
19394
  const candidateExists = (candidate) => candidate !== void 0 && candidate.kind !== "pageless";
18808
- function deltaFor(revision, before, after, backlinkTargetsChanged) {
19395
+ function deltaFor(revision, before, after, backlinkTargetsChanged, mapsChanged) {
18809
19396
  const existingAdded = [];
18810
19397
  const existingRemoved = [];
18811
19398
  const candidateUpserts = [];
@@ -18824,7 +19411,8 @@ function createIndexCore(host) {
18824
19411
  existingRemoved,
18825
19412
  candidateUpserts,
18826
19413
  candidateRemoved,
18827
- backlinkTargetsChanged: [...backlinkTargetsChanged]
19414
+ backlinkTargetsChanged: [...backlinkTargetsChanged],
19415
+ mapsChanged
18828
19416
  };
18829
19417
  }
18830
19418
  /**
@@ -18952,10 +19540,19 @@ function createIndexCore(host) {
18952
19540
  const before = conceptCandidatesForKeys(db, affected);
18953
19541
  const previousSnapshot = visibleSnapshot;
18954
19542
  visibleSnapshot = void 0;
18955
- for (const key of removed) removeDocument(db, key);
18956
- for (const doc of changed) ingestOne(db, doc);
19543
+ let mapsChanged = false;
19544
+ for (const key of removed) {
19545
+ mapsChanged ||= pageHasMapItems(db, key);
19546
+ removeDocument(db, key);
19547
+ }
19548
+ for (const doc of changed) {
19549
+ const key = conceptKey$1(doc.concept);
19550
+ const hadMaps = pageHasMapItems(db, key);
19551
+ ingestOne(db, doc);
19552
+ mapsChanged ||= hadMaps || pageHasMapItems(db, key);
19553
+ }
18957
19554
  const after = conceptCandidatesForKeys(db, affected);
18958
- const delta = deltaFor(advanceIndexRevision(db), before, after, backlinkTargetsChanged);
19555
+ const delta = deltaFor(advanceIndexRevision(db), before, after, backlinkTargetsChanged, mapsChanged);
18959
19556
  applyDeltaToSnapshot(previousSnapshot, delta);
18960
19557
  return [{
18961
19558
  type: "delta",
@@ -19054,6 +19651,17 @@ function createIndexCore(host) {
19054
19651
  graph
19055
19652
  }];
19056
19653
  }
19654
+ case "map-items": {
19655
+ const result = db ? mapItems(db, request.concept) : {
19656
+ items: [],
19657
+ truncated: false
19658
+ };
19659
+ return [{
19660
+ type: "map-items",
19661
+ id: request.id,
19662
+ result
19663
+ }];
19664
+ }
19057
19665
  case "semantic-status": {
19058
19666
  const status = db ? semanticStatus(db, request.model) : {
19059
19667
  available: false,
@@ -19268,7 +19876,8 @@ function createRemoteGraphIndex(source, transport, options) {
19268
19876
  for (const listener of updated) listener({
19269
19877
  full: true,
19270
19878
  changedConceptKeys,
19271
- backlinkTargetsChanged: /* @__PURE__ */ new Set()
19879
+ backlinkTargetsChanged: /* @__PURE__ */ new Set(),
19880
+ mapsChanged: true
19272
19881
  });
19273
19882
  if (response.rebuildId && activeRebuildId) {
19274
19883
  const shouldRetry = rebuildInvalidated;
@@ -19304,7 +19913,8 @@ function createRemoteGraphIndex(source, transport, options) {
19304
19913
  const update = {
19305
19914
  full: false,
19306
19915
  changedConceptKeys,
19307
- backlinkTargetsChanged: new Set(response.backlinkTargetsChanged)
19916
+ backlinkTargetsChanged: new Set(response.backlinkTargetsChanged),
19917
+ mapsChanged: response.mapsChanged
19308
19918
  };
19309
19919
  performanceRecorder.mark("index.delta.received", {
19310
19920
  bytes: new TextEncoder().encode(JSON.stringify(response)).byteLength,
@@ -19853,6 +20463,13 @@ function createRemoteGraphIndex(source, transport, options) {
19853
20463
  id
19854
20464
  }))).graph;
19855
20465
  },
20466
+ async mapItems(concept) {
20467
+ return (await request((id) => ({
20468
+ type: "map-items",
20469
+ id,
20470
+ concept
20471
+ }))).result;
20472
+ },
19856
20473
  async searchText(query, offset, limit, filters) {
19857
20474
  const response = await request((id) => ({
19858
20475
  type: "search-text",
@@ -24485,9 +25102,9 @@ var USAGE = `etherpk-mcp - EtherPK Headless Client (an MCP server over one synce
24485
25102
  removed - everything else in it is left alone. Publishing writes files - deploying them
24486
25103
  (a git push, say) is yours. Run it from cron for a site that republishes itself.
24487
25104
  ${CMD} diagrams setup
24488
- Let a publish draw Mermaid diagrams. Downloads a Chromium (about 170 MB) into the cache
24489
- directory with Playwright's installer, once per computer - a publish whose pages hold
24490
- diagrams refuses until this has run. Set ETHERPK_CHROMIUM=<path> to use a browser
25105
+ Let a publish draw Mermaid diagrams and maps. Downloads a Chromium (about 170 MB) into the
25106
+ cache directory with Playwright's installer, once per computer - a publish whose pages hold
25107
+ diagrams or maps refuses until this has run. Set ETHERPK_CHROMIUM=<path> to use a browser
24491
25108
  already on this machine instead (NixOS needs this).
24492
25109
  ${CMD} diagrams status
24493
25110
  Which browser a publish would use, if any.
@@ -24874,7 +25491,7 @@ async function diagramsCommand(what) {
24874
25491
  return;
24875
25492
  case "status": {
24876
25493
  const status = await chromiumStatus(process.env, CMD);
24877
- if (status.executable) console.log(`Chromium: ${status.executable} (${status.source === "env" ? "from ETHERPK_CHROMIUM" : "installed by diagrams setup"}). A publish can draw Mermaid diagrams.`);
25494
+ if (status.executable) console.log(`Chromium: ${status.executable} (${status.source === "env" ? "from ETHERPK_CHROMIUM" : "installed by diagrams setup"}). A publish can draw Mermaid diagrams and maps.`);
24878
25495
  else console.log(status.source === "env" ? `ETHERPK_CHROMIUM is set but names no file: ${process.env.ETHERPK_CHROMIUM}` : `No browser is set up. Run: ${status.setupCommand}`);
24879
25496
  return;
24880
25497
  }
@@ -25218,7 +25835,7 @@ async function hostCommand(args) {
25218
25835
  process.on("SIGHUP", () => {});
25219
25836
  chromiumStatus(process.env, CMD).then((chromium) => {
25220
25837
  if (chromium.executable) return;
25221
- console.error(`etherpk-mcp: no browser for diagrams (run: ${chromium.setupCommand}, or set ETHERPK_CHROMIUM) - a publish or theme preview with Mermaid diagrams refuses until then.`);
25838
+ console.error(`etherpk-mcp: no browser for diagrams or maps (run: ${chromium.setupCommand}, or set ETHERPK_CHROMIUM) - a publish with Mermaid diagrams or maps, or a theme preview with diagrams, refuses until then.`);
25222
25839
  });
25223
25840
  }
25224
25841
  /**