@appsoftwareltd/etherpk-mcp 0.9.3 → 0.9.4
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/README.md +8 -5
- package/dist/main.js +672 -96
- package/dist/main.js.map +1 -1
- package/dist/map-picture-page.js +292 -0
- package/package.json +9 -6
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,50 @@ function assetChunkCount(size) {
|
|
|
627
629
|
}
|
|
628
630
|
var DAY = 864e5;
|
|
629
631
|
365 * DAY, 30 * DAY, 7 * DAY;
|
|
632
|
+
var latitude = z.number().min(-90).max(90);
|
|
633
|
+
var longitude = z.number().min(-180).max(180);
|
|
634
|
+
z.strictObject({
|
|
635
|
+
q: z.string().trim().min(1).max(200),
|
|
636
|
+
/** Where the map is looking, so the nearest places come first. */
|
|
637
|
+
near: z.strictObject({
|
|
638
|
+
lat: latitude,
|
|
639
|
+
lon: longitude
|
|
640
|
+
}).optional(),
|
|
641
|
+
/** The reader's language, two letters, for the names of places. */
|
|
642
|
+
lang: z.string().regex(/^[a-z]{2}$/).optional()
|
|
643
|
+
});
|
|
644
|
+
/** A place's address fields, as Photon names them. Anything else a service sends is dropped. */
|
|
645
|
+
var placeProperties = z.object({
|
|
646
|
+
name: z.string().optional(),
|
|
647
|
+
housenumber: z.string().optional(),
|
|
648
|
+
street: z.string().optional(),
|
|
649
|
+
postcode: z.string().optional(),
|
|
650
|
+
district: z.string().optional(),
|
|
651
|
+
city: z.string().optional(),
|
|
652
|
+
county: z.string().optional(),
|
|
653
|
+
state: z.string().optional(),
|
|
654
|
+
country: z.string().optional(),
|
|
655
|
+
countrycode: z.string().optional(),
|
|
656
|
+
/** What sort of place it is: a house, a street, a city, a postcode. */
|
|
657
|
+
type: z.string().optional()
|
|
658
|
+
});
|
|
659
|
+
var placeSearchFeatureSchema = z.object({
|
|
660
|
+
type: z.literal("Feature"),
|
|
661
|
+
geometry: z.object({
|
|
662
|
+
type: z.literal("Point"),
|
|
663
|
+
coordinates: z.tuple([longitude, latitude])
|
|
664
|
+
}),
|
|
665
|
+
properties: placeProperties
|
|
666
|
+
});
|
|
667
|
+
var placeSearchCreditSchema = z.object({
|
|
668
|
+
text: z.string().trim().min(1).max(100),
|
|
669
|
+
url: z.url({ protocol: /^https$/ })
|
|
670
|
+
});
|
|
671
|
+
z.object({
|
|
672
|
+
type: z.literal("FeatureCollection"),
|
|
673
|
+
features: z.array(placeSearchFeatureSchema).max(8),
|
|
674
|
+
credits: z.array(placeSearchCreditSchema).max(4).optional()
|
|
675
|
+
});
|
|
630
676
|
var AGENT_SETUP_CODE_PREFIX = "epk_setup_";
|
|
631
677
|
/** Every token and setup code carries 32 random bytes, which base64url writes as 43 characters. */
|
|
632
678
|
var SECRET_LENGTH = 43;
|
|
@@ -648,7 +694,7 @@ function looksLikeAgentSetupCode(value) {
|
|
|
648
694
|
}
|
|
649
695
|
var package_default = {
|
|
650
696
|
name: "@appsoftwareltd/etherpk-mcp",
|
|
651
|
-
version: "0.9.
|
|
697
|
+
version: "0.9.4",
|
|
652
698
|
license: "Elastic-2.0",
|
|
653
699
|
description: "EtherPK Headless Client: an MCP server over a synced knowledge graph, run beside the agent on the user's own machine.",
|
|
654
700
|
type: "module",
|
|
@@ -657,7 +703,7 @@ var package_default = {
|
|
|
657
703
|
files: ["bin", "dist"],
|
|
658
704
|
scripts: {
|
|
659
705
|
"client-tsconfig": "pnpm --dir ../client exec svelte-kit sync",
|
|
660
|
-
"build": "pnpm client-tsconfig && vite build",
|
|
706
|
+
"build": "pnpm client-tsconfig && vite build && vite build --config vite.map-page.config.ts",
|
|
661
707
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
662
708
|
"test": "pnpm client-tsconfig && vitest run",
|
|
663
709
|
"test:watch": "pnpm client-tsconfig && vitest",
|
|
@@ -669,7 +715,7 @@ var package_default = {
|
|
|
669
715
|
"@huggingface/tokenizers": "0.2.0",
|
|
670
716
|
"@lezer/highlight": "^1.2.3",
|
|
671
717
|
"@lezer/markdown": "^1.6.4",
|
|
672
|
-
"@modelcontextprotocol/sdk": "1.
|
|
718
|
+
"@modelcontextprotocol/sdk": "1.31.0",
|
|
673
719
|
"@noble/curves": "2.2.0",
|
|
674
720
|
"@noble/hashes": "2.2.0",
|
|
675
721
|
"@sqlite.org/sqlite-wasm": "3.53.0-build1",
|
|
@@ -677,8 +723,9 @@ var package_default = {
|
|
|
677
723
|
"graphology": "^0.26.0",
|
|
678
724
|
"graphology-communities-louvain": "^2.0.2",
|
|
679
725
|
"graphology-types": "^0.24.8",
|
|
680
|
-
"katex": "^0.
|
|
726
|
+
"katex": "^0.18.2",
|
|
681
727
|
"lib0": "0.2.117",
|
|
728
|
+
"maplibre-gl": "6.11.2",
|
|
682
729
|
"markdown-it": "15.0.2",
|
|
683
730
|
"mermaid": "^11.16.1",
|
|
684
731
|
"mustache": "4.2.0",
|
|
@@ -689,6 +736,8 @@ var package_default = {
|
|
|
689
736
|
"zod": "^4.3.6"
|
|
690
737
|
},
|
|
691
738
|
devDependencies: {
|
|
739
|
+
"@appsoftwareltd/etherpk-extension-graph-view": "workspace:*",
|
|
740
|
+
"@appsoftwareltd/etherpk-extension-maps": "workspace:*",
|
|
692
741
|
"@appsoftwareltd/etherpk-shared": "workspace:*",
|
|
693
742
|
"@appsoftwareltd/etherpk-themes": "workspace:*",
|
|
694
743
|
"@codemirror/view": "^6.43.0",
|
|
@@ -1392,7 +1441,8 @@ var KNOWN_FIELDS = [
|
|
|
1392
1441
|
"signingPublicKey",
|
|
1393
1442
|
"keyrings",
|
|
1394
1443
|
"protection",
|
|
1395
|
-
"pins"
|
|
1444
|
+
"pins",
|
|
1445
|
+
"settings"
|
|
1396
1446
|
];
|
|
1397
1447
|
async function openContent(envelope, vaultKey, aad) {
|
|
1398
1448
|
const { plaintext } = await openSymmetric({
|
|
@@ -1413,6 +1463,7 @@ async function openContent(envelope, vaultKey, aad) {
|
|
|
1413
1463
|
keyrings: deserializeKeyrings(utf8(content.keyrings)),
|
|
1414
1464
|
...content.protection ? { protection: content.protection } : {},
|
|
1415
1465
|
...content.pins ? { pins: content.pins } : {},
|
|
1466
|
+
...content.settings ? { settings: content.settings } : {},
|
|
1416
1467
|
...Object.keys(otherFields).length > 0 ? { otherFields } : {}
|
|
1417
1468
|
};
|
|
1418
1469
|
}
|
|
@@ -1608,6 +1659,16 @@ function createSyncApi(deps) {
|
|
|
1608
1659
|
return {
|
|
1609
1660
|
/** Resolve the authenticated service-local account before loading account-scoped data. */
|
|
1610
1661
|
me: () => call("/api/v1/sync/me"),
|
|
1662
|
+
/**
|
|
1663
|
+
* Search for a place by name, address or postcode (ADR 0119). The query travels in the
|
|
1664
|
+
* body, so no request log holds it. The answer is unchecked JSON in Photon's form, which
|
|
1665
|
+
* the caller reads with `readPlaceFeatures`; a refusal carries a `PLACE_SEARCH_REFUSAL` code.
|
|
1666
|
+
*/
|
|
1667
|
+
searchPlaces: (request, signal) => call("/api/v1/sync/maps/search", {
|
|
1668
|
+
method: "POST",
|
|
1669
|
+
body: JSON.stringify(request),
|
|
1670
|
+
signal
|
|
1671
|
+
}),
|
|
1611
1672
|
createGraph: () => call("/api/v1/sync/graphs", { method: "POST" }),
|
|
1612
1673
|
listGraphs: () => call("/api/v1/sync/graphs").then((r) => r.graphs),
|
|
1613
1674
|
/** Graphs plus the owned-storage rollup (one request - same endpoint as listGraphs). */
|
|
@@ -3649,6 +3710,33 @@ function fencedBlocks(lines, closerTolerance = 0) {
|
|
|
3649
3710
|
}
|
|
3650
3711
|
return blocks;
|
|
3651
3712
|
}
|
|
3713
|
+
/** {@link fencedBlockTree} by scan: a scan never changes, so neither does its tree. */
|
|
3714
|
+
var treeByBlocks = /* @__PURE__ */ new WeakMap();
|
|
3715
|
+
/**
|
|
3716
|
+
* The blocks of one scan as a tree. A code sample can hold a fenced block of its own, written deeper (a
|
|
3717
|
+
* markdown sample of a list item with code in it): the scan pairs the inner fences inside the outer
|
|
3718
|
+
* block, and lists blocks in the order they close, the inner pair before the block around it. Blocks
|
|
3719
|
+
* nest or are disjoint, never overlap partly. Read once per scan and shared, as the lookups run on
|
|
3720
|
+
* every caret move.
|
|
3721
|
+
*/
|
|
3722
|
+
function fencedBlockTree(blocks) {
|
|
3723
|
+
const cached = treeByBlocks.get(blocks);
|
|
3724
|
+
if (cached) return cached;
|
|
3725
|
+
const sorted = [...blocks].sort((a, b) => a.start - b.start);
|
|
3726
|
+
const parent = [];
|
|
3727
|
+
const open = [];
|
|
3728
|
+
sorted.forEach((block, i) => {
|
|
3729
|
+
while (open.length && sorted[open[open.length - 1]].end < block.start) open.pop();
|
|
3730
|
+
parent.push(open.length ? open[open.length - 1] : -1);
|
|
3731
|
+
open.push(i);
|
|
3732
|
+
});
|
|
3733
|
+
const tree = {
|
|
3734
|
+
sorted,
|
|
3735
|
+
parent
|
|
3736
|
+
};
|
|
3737
|
+
treeByBlocks.set(blocks, tree);
|
|
3738
|
+
return tree;
|
|
3739
|
+
}
|
|
3652
3740
|
/**
|
|
3653
3741
|
* A line of a fenced block as code: the indentation up to the fence's column is structure (the
|
|
3654
3742
|
* block's place in the outline) and goes; whatever lies past it is the code's own and stays.
|
|
@@ -4033,6 +4121,105 @@ function parseBlocks(markdown) {
|
|
|
4033
4121
|
return roots;
|
|
4034
4122
|
}
|
|
4035
4123
|
//#endregion
|
|
4124
|
+
//#region ../client/src/lib/document/map-text.ts
|
|
4125
|
+
var NUMBER = String.raw`[+-]?(?:\d+(?:\.\d*)?|\.\d+)`;
|
|
4126
|
+
var POINT = new RegExp(String.raw`^\s*(${NUMBER})\s*,\s*(${NUMBER})\s*$`);
|
|
4127
|
+
function readPoint(text) {
|
|
4128
|
+
const match = POINT.exec(text);
|
|
4129
|
+
if (!match) return null;
|
|
4130
|
+
const lat = Number(match[1]);
|
|
4131
|
+
const lon = Number(match[2]);
|
|
4132
|
+
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
|
|
4133
|
+
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) return null;
|
|
4134
|
+
return {
|
|
4135
|
+
lat,
|
|
4136
|
+
lon
|
|
4137
|
+
};
|
|
4138
|
+
}
|
|
4139
|
+
/**
|
|
4140
|
+
* The part after the name: points joined by `>`, and for a route an optional `(recording)`.
|
|
4141
|
+
* The recording starts at the first `(`, which no point can hold, so a file name with brackets
|
|
4142
|
+
* of its own is kept whole.
|
|
4143
|
+
*/
|
|
4144
|
+
function readLocation(text, name) {
|
|
4145
|
+
let pointsText = text.trim();
|
|
4146
|
+
let track = null;
|
|
4147
|
+
const open = pointsText.indexOf("(");
|
|
4148
|
+
if (open >= 0) {
|
|
4149
|
+
if (!pointsText.endsWith(")")) return null;
|
|
4150
|
+
track = pointsText.slice(open + 1, -1).trim();
|
|
4151
|
+
if (track === "") return null;
|
|
4152
|
+
pointsText = pointsText.slice(0, open);
|
|
4153
|
+
}
|
|
4154
|
+
const points = [];
|
|
4155
|
+
for (const part of pointsText.split(">")) {
|
|
4156
|
+
const point = readPoint(part);
|
|
4157
|
+
if (!point) return null;
|
|
4158
|
+
points.push(point);
|
|
4159
|
+
}
|
|
4160
|
+
if (points.length === 1) return track === null ? {
|
|
4161
|
+
kind: "place",
|
|
4162
|
+
name,
|
|
4163
|
+
point: points[0]
|
|
4164
|
+
} : null;
|
|
4165
|
+
return {
|
|
4166
|
+
kind: "route",
|
|
4167
|
+
name,
|
|
4168
|
+
points,
|
|
4169
|
+
track
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
/** What one line of a Map Block's body says, or null when it is not a place or a route. */
|
|
4173
|
+
function readMapLine(text) {
|
|
4174
|
+
const trimmed = text.trim();
|
|
4175
|
+
if (trimmed === "") return null;
|
|
4176
|
+
for (let at = trimmed.lastIndexOf("@"); at >= 0; at = at === 0 ? -1 : trimmed.lastIndexOf("@", at - 1)) {
|
|
4177
|
+
const line = readLocation(trimmed.slice(at + 1), trimmed.slice(0, at).trim());
|
|
4178
|
+
if (line) return line;
|
|
4179
|
+
}
|
|
4180
|
+
return readLocation(trimmed, "");
|
|
4181
|
+
}
|
|
4182
|
+
/** Every item in a Map Block's body, numbered by line. Blank lines are skipped, not reported. */
|
|
4183
|
+
function readMapBody(lines) {
|
|
4184
|
+
const body = {
|
|
4185
|
+
items: [],
|
|
4186
|
+
unread: []
|
|
4187
|
+
};
|
|
4188
|
+
lines.forEach((text, line) => {
|
|
4189
|
+
if (text.trim() === "") return;
|
|
4190
|
+
const read = readMapLine(text);
|
|
4191
|
+
if (read) body.items.push({
|
|
4192
|
+
...read,
|
|
4193
|
+
line
|
|
4194
|
+
});
|
|
4195
|
+
else body.unread.push({
|
|
4196
|
+
line,
|
|
4197
|
+
text
|
|
4198
|
+
});
|
|
4199
|
+
});
|
|
4200
|
+
return body;
|
|
4201
|
+
}
|
|
4202
|
+
/** How many names a picture's alt text lists before it counts the rest. */
|
|
4203
|
+
var NAMED_IN_ALT = 10;
|
|
4204
|
+
/**
|
|
4205
|
+
* The words for a picture of a map (a published Map Block, ADR 0118): what is on it, by name,
|
|
4206
|
+
* never where. Places and routes without a name are counted, and past the first ten names the
|
|
4207
|
+
* rest are counted too, so the text stays a sentence a screen reader can read out.
|
|
4208
|
+
*/
|
|
4209
|
+
function mapAltText(body) {
|
|
4210
|
+
const { items } = readMapBody(body);
|
|
4211
|
+
if (items.length === 0) return "An empty map";
|
|
4212
|
+
const named = items.filter((item) => item.name !== "").map((item) => item.name);
|
|
4213
|
+
const parts = named.slice(0, NAMED_IN_ALT);
|
|
4214
|
+
const more = named.length - parts.length;
|
|
4215
|
+
if (more > 0) parts.push(`${more} more`);
|
|
4216
|
+
const unnamedPlaces = items.filter((item) => item.name === "" && item.kind === "place").length;
|
|
4217
|
+
const unnamedRoutes = items.filter((item) => item.name === "" && item.kind === "route").length;
|
|
4218
|
+
if (unnamedPlaces > 0) parts.push(`${unnamedPlaces} unnamed ${unnamedPlaces === 1 ? "place" : "places"}`);
|
|
4219
|
+
if (unnamedRoutes > 0) parts.push(`${unnamedRoutes} unnamed ${unnamedRoutes === 1 ? "route" : "routes"}`);
|
|
4220
|
+
return `Map of ${parts.length === 1 ? parts[0] : `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`}`;
|
|
4221
|
+
}
|
|
4222
|
+
//#endregion
|
|
4036
4223
|
//#region ../client/src/lib/document/protection/fence-info.ts
|
|
4037
4224
|
/**
|
|
4038
4225
|
* The one name for the protected fence, and the one way to recognise one in raw text.
|
|
@@ -4296,7 +4483,7 @@ function blocksAndLinks(text) {
|
|
|
4296
4483
|
}
|
|
4297
4484
|
/** Derive the index rows for a single document's text. Pure. */
|
|
4298
4485
|
function deriveDoc(text) {
|
|
4299
|
-
const { blocks, links } = blocksAndLinks(text);
|
|
4486
|
+
const { blocks, links, lineToBlock } = blocksAndLinks(text);
|
|
4300
4487
|
const candidateTasks = blocks.filter((b) => b.kind === "task");
|
|
4301
4488
|
const fenced = candidateTasks.length === 0 ? () => false : fencedLines(text);
|
|
4302
4489
|
const taskBlocks = candidateTasks.filter((b) => !fenced(b.startLine));
|
|
@@ -4319,7 +4506,67 @@ function deriveDoc(text) {
|
|
|
4319
4506
|
scheduled: tags.scheduled
|
|
4320
4507
|
};
|
|
4321
4508
|
}),
|
|
4322
|
-
taskConcepts: deriveTaskConcepts(blocks, links, taskBlocks)
|
|
4509
|
+
taskConcepts: deriveTaskConcepts(blocks, links, taskBlocks),
|
|
4510
|
+
...deriveMaps(text, blocks, links, lineToBlock)
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
4513
|
+
/**
|
|
4514
|
+
* The [[Place]]s and [[Route]]s a document's [[Map Block]]s hold (ADR 0118), the [[Block
|
|
4515
|
+
* Concept]]s of the blocks they sit in, and what text search reads for those blocks instead of
|
|
4516
|
+
* their coordinates.
|
|
4517
|
+
*
|
|
4518
|
+
* A Map Block is a complete, outermost fence whose info word is `map`: one shown as an example
|
|
4519
|
+
* inside another fence is text. A document holding any protected content gives nothing, as the
|
|
4520
|
+
* Map View leaves Protected Documents out: the index is plaintext at rest, and the conservative
|
|
4521
|
+
* reading of a page with a cipher fence beside other text is the only safe one.
|
|
4522
|
+
*/
|
|
4523
|
+
function deriveMaps(text, blocks, links, lineToBlock) {
|
|
4524
|
+
const searchText = /* @__PURE__ */ new Map();
|
|
4525
|
+
if (!text.includes("```map") || containsCipherFence(text)) return {
|
|
4526
|
+
mapItems: [],
|
|
4527
|
+
mapConcepts: [],
|
|
4528
|
+
searchText
|
|
4529
|
+
};
|
|
4530
|
+
const lines = text.split("\n");
|
|
4531
|
+
const { sorted, parent } = fencedBlockTree(fencedBlocks(lines));
|
|
4532
|
+
const mapItems = [];
|
|
4533
|
+
const mapBlocks = /* @__PURE__ */ new Set();
|
|
4534
|
+
const searchLines = /* @__PURE__ */ new Map();
|
|
4535
|
+
sorted.forEach((fence, i) => {
|
|
4536
|
+
if (parent[i] !== -1 || fenceLineInfo(lines[fence.start])?.info !== "map") return;
|
|
4537
|
+
const blockLocalId = lineToBlock.get(fence.start);
|
|
4538
|
+
if (blockLocalId === void 0) return;
|
|
4539
|
+
mapBlocks.add(blockLocalId);
|
|
4540
|
+
const body = lines.slice(fence.start + 1, fence.end).map((line) => codeLineText(line, fence.fenceColumn));
|
|
4541
|
+
body.forEach((_, n) => searchLines.set(fence.start + 1 + n, ""));
|
|
4542
|
+
for (const item of readMapBody(body).items) {
|
|
4543
|
+
const line = fence.start + 1 + item.line;
|
|
4544
|
+
searchLines.set(line, item.name);
|
|
4545
|
+
mapItems.push({
|
|
4546
|
+
blockLocalId,
|
|
4547
|
+
fenceLine: fence.start,
|
|
4548
|
+
line,
|
|
4549
|
+
kind: item.kind,
|
|
4550
|
+
name: item.name,
|
|
4551
|
+
text: body[item.line]
|
|
4552
|
+
});
|
|
4553
|
+
}
|
|
4554
|
+
});
|
|
4555
|
+
for (const id of mapBlocks) {
|
|
4556
|
+
const block = blocks[id];
|
|
4557
|
+
const read = block.text.split("\n").map((line, n) => searchLines.get(block.startLine + n) ?? line);
|
|
4558
|
+
searchText.set(id, read.join("\n"));
|
|
4559
|
+
}
|
|
4560
|
+
const byBlock = linksByBlock(links);
|
|
4561
|
+
const mapConcepts = [];
|
|
4562
|
+
if (byBlock.size > 0) for (const id of mapBlocks) for (const { concept } of conceptChain(blocks, byBlock, id)) mapConcepts.push({
|
|
4563
|
+
blockLocalId: id,
|
|
4564
|
+
concept
|
|
4565
|
+
});
|
|
4566
|
+
return {
|
|
4567
|
+
mapItems,
|
|
4568
|
+
mapConcepts,
|
|
4569
|
+
searchText
|
|
4323
4570
|
};
|
|
4324
4571
|
}
|
|
4325
4572
|
/**
|
|
@@ -4841,6 +5088,15 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
4841
5088
|
-- the canonical name in would go stale the moment a page is renamed.
|
|
4842
5089
|
CREATE TABLE IF NOT EXISTS task_concepts (
|
|
4843
5090
|
page_id INTEGER NOT NULL, block_local_id INTEGER NOT NULL, concept_key TEXT NOT NULL);
|
|
5091
|
+
-- [[Place]]s and [[Route]]s (ADR 0118): one row per line of a [[Map Block]] that reads, under the
|
|
5092
|
+
-- block its fence belongs to. text is the line as written; the query reads its points from it.
|
|
5093
|
+
CREATE TABLE IF NOT EXISTS map_items (
|
|
5094
|
+
page_id INTEGER NOT NULL, block_local_id INTEGER NOT NULL, fence_line INTEGER NOT NULL,
|
|
5095
|
+
line INTEGER NOT NULL, kind TEXT NOT NULL, name TEXT NOT NULL, text TEXT NOT NULL);
|
|
5096
|
+
-- The [[Block Concept]]s of each block holding a Map Block, as task_concepts holds a task's, so a
|
|
5097
|
+
-- [[Map View]] and a [[Kanban Board]] for one concept agree on what lies under it.
|
|
5098
|
+
CREATE TABLE IF NOT EXISTS map_concepts (
|
|
5099
|
+
page_id INTEGER NOT NULL, block_local_id INTEGER NOT NULL, concept_key TEXT NOT NULL);
|
|
4844
5100
|
-- [[Passage]]s for [[Semantic Search]] (ADR 0076): the pieces of a document that get an
|
|
4845
5101
|
-- [[Embedding]] each, keyed by the hash of their text (semantic/passages.ts). The vectors
|
|
4846
5102
|
-- themselves live in the ATTACHed embeddings schema (semantic/embedding-db.ts), OUTSIDE this
|
|
@@ -4875,6 +5131,7 @@ CREATE INDEX IF NOT EXISTS tasks_page ON tasks(page_id);
|
|
|
4875
5131
|
-- The Name Filter's whole selectivity: without it, filtering to a concept scans every task
|
|
4876
5132
|
-- row in the graph. Mirrors links_concept_key, and like it stays put during a bulk rebuild.
|
|
4877
5133
|
CREATE INDEX IF NOT EXISTS task_concepts_key ON task_concepts(concept_key);
|
|
5134
|
+
CREATE INDEX IF NOT EXISTS map_concepts_key ON map_concepts(concept_key);
|
|
4878
5135
|
-- Search's text index. One row per block, so a document's rank can be "how many blocks
|
|
4879
5136
|
-- mention this" - the ordering the results actually display. See the block_fts notes above
|
|
4880
5137
|
-- createSchema for why unicode61 rather than trigram, and why the rowid encodes the block.
|
|
@@ -4907,7 +5164,7 @@ function createSchema(db) {
|
|
|
4907
5164
|
db.exec(SCHEMA$1);
|
|
4908
5165
|
db.run("INSERT OR IGNORE INTO index_metadata (key, value) VALUES ('active_generation', 1)");
|
|
4909
5166
|
db.run("INSERT OR IGNORE INTO index_metadata (key, value) VALUES ('revision', 0)");
|
|
4910
|
-
db.exec(`PRAGMA user_version =
|
|
5167
|
+
db.exec(`PRAGMA user_version = 16`);
|
|
4911
5168
|
}
|
|
4912
5169
|
function activeIndexGeneration(db) {
|
|
4913
5170
|
return db.all("SELECT value FROM index_metadata WHERE key = 'active_generation'")[0]?.value ?? 1;
|
|
@@ -4928,7 +5185,7 @@ function advanceIndexRevision(db) {
|
|
|
4928
5185
|
*/
|
|
4929
5186
|
function isUsableIndex(db) {
|
|
4930
5187
|
try {
|
|
4931
|
-
return db.all("PRAGMA user_version")[0]?.user_version ===
|
|
5188
|
+
return db.all("PRAGMA user_version")[0]?.user_version === 16;
|
|
4932
5189
|
} catch {
|
|
4933
5190
|
return false;
|
|
4934
5191
|
}
|
|
@@ -5043,7 +5300,7 @@ function insertDerived(db, pageId, doc) {
|
|
|
5043
5300
|
t.matchEnd,
|
|
5044
5301
|
null
|
|
5045
5302
|
]);
|
|
5046
|
-
const { blocks, links, tasks, taskConcepts } = deriveDoc(doc.text);
|
|
5303
|
+
const { blocks, links, tasks, taskConcepts, mapItems, mapConcepts, searchText } = deriveDoc(doc.text);
|
|
5047
5304
|
for (const b of blocks) {
|
|
5048
5305
|
db.run("INSERT INTO blocks (page_id, local_id, parent_local_id, ord, kind, depth, done, start_line, end_line, label, text) VALUES (?,?,?,?,?,?,?,?,?,?,?)", [
|
|
5049
5306
|
pageId,
|
|
@@ -5059,8 +5316,9 @@ function insertDerived(db, pageId, doc) {
|
|
|
5059
5316
|
b.text
|
|
5060
5317
|
]);
|
|
5061
5318
|
if (isTextSearchable(b) && b.localId < 1048576) {
|
|
5062
|
-
|
|
5063
|
-
|
|
5319
|
+
const text = searchText.get(b.localId) ?? b.text;
|
|
5320
|
+
db.run("INSERT INTO block_fts (rowid, text) VALUES (?,?)", [pageId * BLOCK_FTS_STRIDE + b.localId, text]);
|
|
5321
|
+
searchable.push(text);
|
|
5064
5322
|
}
|
|
5065
5323
|
}
|
|
5066
5324
|
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 +5348,12 @@ function insertDerived(db, pageId, doc) {
|
|
|
5090
5348
|
t.scheduled
|
|
5091
5349
|
]);
|
|
5092
5350
|
insertTaskConcepts(db, pageId, doc, tasks, taskConcepts);
|
|
5093
|
-
|
|
5351
|
+
insertMapRows(db, pageId, doc, mapItems, mapConcepts);
|
|
5352
|
+
const passageBlocks = searchText.size === 0 ? blocks : blocks.map((b) => ({
|
|
5353
|
+
...b,
|
|
5354
|
+
text: searchText.get(b.localId) ?? b.text
|
|
5355
|
+
}));
|
|
5356
|
+
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
5357
|
pageId,
|
|
5095
5358
|
passage.ord,
|
|
5096
5359
|
passage.startLine,
|
|
@@ -5164,6 +5427,37 @@ function insertTaskConcepts(db, pageId, doc, tasks, taskConcepts) {
|
|
|
5164
5427
|
for (const tc of taskConcepts) write(tc.blockLocalId, conceptKey$1(tc.concept));
|
|
5165
5428
|
}
|
|
5166
5429
|
/**
|
|
5430
|
+
* Write one document's [[Map Block]] rows (ADR 0118): its items, and the [[Block Concept]]s of
|
|
5431
|
+
* each block holding a Map Block. The document's own concept and the scopes in its name sit at
|
|
5432
|
+
* the root of every block's chain, added here as {@link insertTaskConcepts} adds them for tasks.
|
|
5433
|
+
*/
|
|
5434
|
+
function insertMapRows(db, pageId, doc, mapItems, mapConcepts) {
|
|
5435
|
+
if (mapItems.length === 0) return;
|
|
5436
|
+
for (const item of mapItems) db.run("INSERT INTO map_items (page_id, block_local_id, fence_line, line, kind, name, text) VALUES (?,?,?,?,?,?,?)", [
|
|
5437
|
+
pageId,
|
|
5438
|
+
item.blockLocalId,
|
|
5439
|
+
item.fenceLine,
|
|
5440
|
+
item.line,
|
|
5441
|
+
item.kind,
|
|
5442
|
+
item.name,
|
|
5443
|
+
item.text
|
|
5444
|
+
]);
|
|
5445
|
+
const written = /* @__PURE__ */ new Set();
|
|
5446
|
+
const write = (blockLocalId, key) => {
|
|
5447
|
+
const seen = `${blockLocalId} ${key}`;
|
|
5448
|
+
if (written.has(seen)) return;
|
|
5449
|
+
written.add(seen);
|
|
5450
|
+
db.run("INSERT INTO map_concepts (page_id, block_local_id, concept_key) VALUES (?,?,?)", [
|
|
5451
|
+
pageId,
|
|
5452
|
+
blockLocalId,
|
|
5453
|
+
key
|
|
5454
|
+
]);
|
|
5455
|
+
};
|
|
5456
|
+
const rootKeys = documentTaskConcepts(doc.concept).map(conceptKey$1);
|
|
5457
|
+
for (const blockLocalId of new Set(mapItems.map((item) => item.blockLocalId))) for (const key of rootKeys) write(blockLocalId, key);
|
|
5458
|
+
for (const row of mapConcepts) write(row.blockLocalId, conceptKey$1(row.concept));
|
|
5459
|
+
}
|
|
5460
|
+
/**
|
|
5167
5461
|
* Run `work` as ONE transaction. Without this every INSERT is its own implicit transaction,
|
|
5168
5462
|
* which is most of what made a full rebuild cost seconds rather than milliseconds.
|
|
5169
5463
|
*/
|
|
@@ -5235,6 +5529,8 @@ function deleteGeneration(db, generation) {
|
|
|
5235
5529
|
"links",
|
|
5236
5530
|
"tasks",
|
|
5237
5531
|
"task_concepts",
|
|
5532
|
+
"map_items",
|
|
5533
|
+
"map_concepts",
|
|
5238
5534
|
"passages",
|
|
5239
5535
|
"properties"
|
|
5240
5536
|
]) db.run(`DELETE FROM ${table} WHERE page_id IN (SELECT id FROM pages WHERE generation = ?)`, [generation]);
|
|
@@ -5339,6 +5635,8 @@ function deleteDerivedRows(db, pageId) {
|
|
|
5339
5635
|
db.run("DELETE FROM links WHERE page_id = ?", [pageId]);
|
|
5340
5636
|
db.run("DELETE FROM tasks WHERE page_id = ?", [pageId]);
|
|
5341
5637
|
db.run("DELETE FROM task_concepts WHERE page_id = ?", [pageId]);
|
|
5638
|
+
db.run("DELETE FROM map_items WHERE page_id = ?", [pageId]);
|
|
5639
|
+
db.run("DELETE FROM map_concepts WHERE page_id = ?", [pageId]);
|
|
5342
5640
|
db.run("DELETE FROM passages WHERE page_id = ?", [pageId]);
|
|
5343
5641
|
db.run("DELETE FROM properties WHERE page_id = ?", [pageId]);
|
|
5344
5642
|
}
|
|
@@ -6529,7 +6827,7 @@ function cacheRoot(env) {
|
|
|
6529
6827
|
* recovered from a joined path (see there).
|
|
6530
6828
|
*/
|
|
6531
6829
|
var CACHE_FILE_NAME = `local-cache.v${CACHE_DB_VERSION}.bin`;
|
|
6532
|
-
var INDEX_FILE_NAME = `index.
|
|
6830
|
+
var INDEX_FILE_NAME = `index.v16.sqlite`;
|
|
6533
6831
|
var VECTORS_FILE_NAME = `vectors.v1.sqlite`;
|
|
6534
6832
|
function cacheFile(dir) {
|
|
6535
6833
|
return join(dir, CACHE_FILE_NAME);
|
|
@@ -10768,16 +11066,17 @@ async function hostRequest(endpoint, request, options = {}) {
|
|
|
10768
11066
|
}
|
|
10769
11067
|
/**
|
|
10770
11068
|
* 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,
|
|
10772
|
-
*
|
|
10773
|
-
* own browser.
|
|
11069
|
+
* the publish: where the publish folders are remembered, which browser draws diagrams and maps,
|
|
11070
|
+
* and which style a map's picture is drawn over. The host was started from another environment
|
|
11071
|
+
* (an agent's), and a scheduled publish may name its own browser or its own map host.
|
|
10774
11072
|
*/
|
|
10775
11073
|
var PUBLISH_SETTINGS = [
|
|
10776
11074
|
"ETHERPK_MCP_PUBLISH_CONFIG",
|
|
10777
11075
|
"XDG_CONFIG_HOME",
|
|
10778
11076
|
"ETHERPK_CHROMIUM",
|
|
10779
11077
|
"PLAYWRIGHT_BROWSERS_PATH",
|
|
10780
|
-
"PLAYWRIGHT_CHROMIUM_EXECUTABLE"
|
|
11078
|
+
"PLAYWRIGHT_CHROMIUM_EXECUTABLE",
|
|
11079
|
+
"ETHERPK_MAP_STYLE_URL"
|
|
10781
11080
|
];
|
|
10782
11081
|
/** The publish settings in `env`, for a `publish` request. */
|
|
10783
11082
|
function publishSettings(env) {
|
|
@@ -11179,6 +11478,7 @@ async function writeSite(folder, bundle, options) {
|
|
|
11179
11478
|
//#endregion
|
|
11180
11479
|
//#region ../../packages/themes/src/index.ts
|
|
11181
11480
|
var files = /* #__PURE__ */ Object.assign({
|
|
11481
|
+
"../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
11482
|
"../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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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
11483
|
"../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
11484
|
"../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 +11490,7 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11190
11490
|
"../etherpk-al-folio/partials/after-content.html": "",
|
|
11191
11491
|
"../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
11492
|
"../etherpk-al-folio/partials/before-content.html": "",
|
|
11493
|
+
"../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
11494
|
"../etherpk-al-folio/partials/footer.html": " <footer class=\"footer\">\n <div class=\"container\">\n © {{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
11495
|
"../etherpk-al-folio/partials/head.html": "",
|
|
11195
11496
|
"../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 +11498,13 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11197
11498
|
"../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
11499
|
"../etherpk-al-folio/partials/scripts.html": "",
|
|
11199
11500
|
"../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",
|
|
11501
|
+
"../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
11502
|
"../etherpk-al-folio/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n <script src=\"theme/theme.js\" defer><\/script>\n",
|
|
11202
11503
|
"../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",
|
|
11504
|
+
"../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",
|
|
11505
|
+
"../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
11506
|
"../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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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",
|
|
11507
|
+
"../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
11508
|
"../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
11509
|
"../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
11510
|
"../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 +11513,7 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11211
11513
|
"../etherpk-blog/partials/after-content.html": "",
|
|
11212
11514
|
"../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
11515
|
"../etherpk-blog/partials/before-content.html": "",
|
|
11516
|
+
"../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
11517
|
"../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
11518
|
"../etherpk-blog/partials/head.html": "",
|
|
11216
11519
|
"../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 +11521,12 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11218
11521
|
"../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
11522
|
"../etherpk-blog/partials/scripts.html": "",
|
|
11220
11523
|
"../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",
|
|
11524
|
+
"../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
11525
|
"../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",
|
|
11526
|
+
"../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",
|
|
11527
|
+
"../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
11528
|
"../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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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",
|
|
11529
|
+
"../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
11530
|
"../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
11531
|
"../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
11532
|
"../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 +11535,7 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11231
11535
|
"../etherpk-docs/partials/after-content.html": "",
|
|
11232
11536
|
"../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
11537
|
"../etherpk-docs/partials/before-content.html": "",
|
|
11538
|
+
"../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
11539
|
"../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
11540
|
"../etherpk-docs/partials/head.html": "",
|
|
11236
11541
|
"../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 +11544,11 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11239
11544
|
"../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
11545
|
"../etherpk-docs/partials/scripts.html": "",
|
|
11241
11546
|
"../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",
|
|
11547
|
+
"../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
11548
|
"../etherpk-docs/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n",
|
|
11244
11549
|
"../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",
|
|
11550
|
+
"../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",
|
|
11551
|
+
"../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
11552
|
"../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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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
11553
|
"../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
11554
|
"../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 +11560,7 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11254
11560
|
"../etherpk-papermod/partials/after-content.html": "",
|
|
11255
11561
|
"../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
11562
|
"../etherpk-papermod/partials/before-content.html": "",
|
|
11563
|
+
"../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
11564
|
"../etherpk-papermod/partials/footer.html": " <footer class=\"footer\">\n <span>© {{site.year}} {{site.title}}</span>\n <span>·</span>\n <span>Published with <a href=\"https://etherpk.com\" rel=\"noopener\">EtherPK</a>{{#site.hasFeed}} · <a href=\"feed.xml\">Feed</a>{{/site.hasFeed}}</span>\n </footer>\n",
|
|
11258
11565
|
"../etherpk-papermod/partials/head.html": "",
|
|
11259
11566
|
"../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 +11570,11 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11263
11570
|
"../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
11571
|
"../etherpk-papermod/partials/scripts.html": "",
|
|
11265
11572
|
"../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",
|
|
11573
|
+
"../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
11574
|
"../etherpk-papermod/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n <script src=\"theme/theme.js\" defer><\/script>\n",
|
|
11268
11575
|
"../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",
|
|
11576
|
+
"../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",
|
|
11577
|
+
"../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
11578
|
"../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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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
11579
|
"../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
11580
|
"../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 +11587,7 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11279
11587
|
"../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
11588
|
"../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
11589
|
"../etherpk-stack/partials/before-content.html": "",
|
|
11590
|
+
"../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
11591
|
"../etherpk-stack/partials/footer.html": " <footer class=\"site-footer\">\n <p>© {{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
11592
|
"../etherpk-stack/partials/head.html": "",
|
|
11284
11593
|
"../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 +11595,12 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11286
11595
|
"../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
11596
|
"../etherpk-stack/partials/scripts.html": "",
|
|
11288
11597
|
"../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",
|
|
11598
|
+
"../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
11599
|
"../etherpk-stack/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n <script src=\"theme/theme.js\" defer><\/script>\n",
|
|
11291
11600
|
"../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
11601
|
"../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",
|
|
11602
|
+
"../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",
|
|
11603
|
+
"../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
11604
|
"../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, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\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
11605
|
"../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
11606
|
"../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 +11611,7 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11301
11611
|
"../etherpk-terminal/partials/after-content.html": "",
|
|
11302
11612
|
"../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
11613
|
"../etherpk-terminal/partials/before-content.html": "",
|
|
11614
|
+
"../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
11615
|
"../etherpk-terminal/partials/footer.html": " <footer class=\"footer\">\n <div class=\"footer__inner\">\n <div class=\"copyright\">\n <span>© {{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
11616
|
"../etherpk-terminal/partials/head.html": "",
|
|
11306
11617
|
"../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 +11621,10 @@ var files = /* #__PURE__ */ Object.assign({
|
|
|
11310
11621
|
"../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
11622
|
"../etherpk-terminal/partials/scripts.html": "",
|
|
11312
11623
|
"../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",
|
|
11624
|
+
"../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
11625
|
"../etherpk-terminal/partials/theme-scripts.html": " <script src=\"theme/search.js\" defer><\/script>\n",
|
|
11315
11626
|
"../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"
|
|
11627
|
+
"../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
11628
|
});
|
|
11318
11629
|
var themes = /* @__PURE__ */ new Map();
|
|
11319
11630
|
for (const [path, text] of Object.entries(files)) {
|
|
@@ -11978,6 +12289,14 @@ function isSingleFileName(name) {
|
|
|
11978
12289
|
*/
|
|
11979
12290
|
/** Matches a display-size hint at the end of alt text: `|300` or `|300x200` (with optional trailing space). */
|
|
11980
12291
|
var DISPLAY_SIZE_HINT_REGEX = /\|(\d{1,5})(?:[xX](\d{1,5}))?\s*$/;
|
|
12292
|
+
/**
|
|
12293
|
+
* The CSS `max-width` for a hinted width: the hint, but never wider than the container. Both the
|
|
12294
|
+
* editor and the publisher set it inline, and an inline value outranks a stylesheet's
|
|
12295
|
+
* `img { max-width: 100% }`, so a bare `${px}px` would push a wide image out of a narrower column.
|
|
12296
|
+
*/
|
|
12297
|
+
function displayMaxWidth(px) {
|
|
12298
|
+
return `min(${px}px, 100%)`;
|
|
12299
|
+
}
|
|
11981
12300
|
/** Parse the alt text into its clean label and any display-size hint. A width < 1 (or no hint) yields no dimensions. */
|
|
11982
12301
|
function parseImageDisplaySizeHint(alt) {
|
|
11983
12302
|
const match = DISPLAY_SIZE_HINT_REGEX.exec(alt);
|
|
@@ -12290,6 +12609,10 @@ function wikilinkRule(md, resolve) {
|
|
|
12290
12609
|
* grammars for code), so they are pre-rendered by the orchestrator and handed in as lookups;
|
|
12291
12610
|
* the renderer itself is synchronous and pure over its inputs.
|
|
12292
12611
|
*/
|
|
12612
|
+
/** The names on a Map Block, one line for the search index and the excerpt; never its coordinates. */
|
|
12613
|
+
function mapNames(source) {
|
|
12614
|
+
return readMapBody(source.split("\n")).items.map((item) => item.name).filter((name) => name !== "").join(", ");
|
|
12615
|
+
}
|
|
12293
12616
|
/** A document-relative asset reference: any number of `../`, `assets/`, the name. */
|
|
12294
12617
|
var ASSET_REF$1 = /^(?:\.\.\/)*assets\/(.+)$/;
|
|
12295
12618
|
/**
|
|
@@ -12329,7 +12652,11 @@ function plainText(tokens) {
|
|
|
12329
12652
|
else if (child.type === "softbreak" || child.type === "hardbreak") line += " ";
|
|
12330
12653
|
if (line.trim() !== "") lines.push(line.trim());
|
|
12331
12654
|
} else if (token.type === "fence" || token.type === "code_block") {
|
|
12332
|
-
|
|
12655
|
+
const info = token.info.trim().split(/\s+/)[0] ?? "";
|
|
12656
|
+
if (info === "map") {
|
|
12657
|
+
const names = mapNames(token.content);
|
|
12658
|
+
if (names !== "") lines.push(names);
|
|
12659
|
+
} else if (info !== "mermaid" && info !== "math") lines.push(token.content.trim());
|
|
12333
12660
|
}
|
|
12334
12661
|
return lines.join("\n");
|
|
12335
12662
|
}
|
|
@@ -12368,7 +12695,7 @@ function createDocumentRenderer(options) {
|
|
|
12368
12695
|
const parsed = parseImageDisplaySizeHint(self.renderInlineAsText(token.children ?? [], opts, env));
|
|
12369
12696
|
token.attrSet("alt", parsed.cleanAlt);
|
|
12370
12697
|
if (parsed.maxWidth !== void 0) {
|
|
12371
|
-
const style = [`max-width:${parsed.maxWidth}
|
|
12698
|
+
const style = [`max-width:${displayMaxWidth(parsed.maxWidth)}`];
|
|
12372
12699
|
if (parsed.maxHeight !== void 0) style.push(`max-height:${parsed.maxHeight}px`);
|
|
12373
12700
|
token.attrSet("style", style.join(";"));
|
|
12374
12701
|
}
|
|
@@ -12403,6 +12730,11 @@ function createDocumentRenderer(options) {
|
|
|
12403
12730
|
return `<div class="math math-block">${renderMath(code.replace(/\n$/, ""), true)}</div>\n`;
|
|
12404
12731
|
}
|
|
12405
12732
|
if (lang === "etherpk-cipher") return "";
|
|
12733
|
+
if (lang === "map") {
|
|
12734
|
+
const picture = options.mapPicture?.(code);
|
|
12735
|
+
if (!picture) return "";
|
|
12736
|
+
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`;
|
|
12737
|
+
}
|
|
12406
12738
|
const highlighted = options.highlighted?.(lang, code);
|
|
12407
12739
|
return `<pre><code${lang === "" ? "" : ` class="language-${escapeHtml$3(lang)}"`}>${highlighted ?? escapeHtml$3(code)}</code></pre>\n`;
|
|
12408
12740
|
};
|
|
@@ -12844,6 +13176,16 @@ function createThemeRenderer(theme, includes) {
|
|
|
12844
13176
|
* a pre-render pass for the asynchronous bits (diagrams, code); then rendering, includes, the
|
|
12845
13177
|
* theme, the derived files. Everything derived is computed from the included documents alone.
|
|
12846
13178
|
*/
|
|
13179
|
+
/**
|
|
13180
|
+
* Bumped when the way a map is drawn changes, so a site published again holds the new picture
|
|
13181
|
+
* under a new name rather than a cached one under the old.
|
|
13182
|
+
*/
|
|
13183
|
+
var MAP_PICTURE_VERSION = 1;
|
|
13184
|
+
/** A picture's file name: from what the map holds and how it is drawn, so an unchanged map publishes the same file. */
|
|
13185
|
+
async function mapPictureName(source) {
|
|
13186
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${MAP_PICTURE_VERSION}\n${source}`));
|
|
13187
|
+
return [...new Uint8Array(digest)].slice(0, 8).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
13188
|
+
}
|
|
12847
13189
|
function assetHrefOf(name) {
|
|
12848
13190
|
return `assets/${encodeURIComponent(name)}`;
|
|
12849
13191
|
}
|
|
@@ -13021,6 +13363,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
13021
13363
|
includeDocs.set(slot, doc);
|
|
13022
13364
|
}
|
|
13023
13365
|
const mermaidSvg = /* @__PURE__ */ new Map();
|
|
13366
|
+
const mapPictures = /* @__PURE__ */ new Map();
|
|
13024
13367
|
const highlighted = /* @__PURE__ */ new Map();
|
|
13025
13368
|
const highlight = env.highlightCode ?? highlightCode;
|
|
13026
13369
|
const probe = createDocumentRenderer({
|
|
@@ -13043,12 +13386,44 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
13043
13386
|
concept: doc.concept
|
|
13044
13387
|
});
|
|
13045
13388
|
}
|
|
13389
|
+
} else if (fence.lang === "map") {
|
|
13390
|
+
if (mapPictures.has(fence.code) || errors.some((e) => e.code === "map-not-drawn" && e.concept === doc.concept)) continue;
|
|
13391
|
+
if (!env.renderMap) {
|
|
13392
|
+
errors.push({
|
|
13393
|
+
level: "error",
|
|
13394
|
+
code: "map-not-drawn",
|
|
13395
|
+
message: `"${doc.concept}" holds a map, which this publisher can't draw. Publish the site from EtherPK in a browser, where maps are drawn.`,
|
|
13396
|
+
concept: doc.concept
|
|
13397
|
+
});
|
|
13398
|
+
continue;
|
|
13399
|
+
}
|
|
13400
|
+
try {
|
|
13401
|
+
const picture = await env.renderMap(fence.code);
|
|
13402
|
+
const path = `maps/${await mapPictureName(fence.code)}.${picture.type === "image/webp" ? "webp" : "png"}`;
|
|
13403
|
+
mapPictures.set(fence.code, {
|
|
13404
|
+
...picture,
|
|
13405
|
+
path,
|
|
13406
|
+
alt: mapAltText(fence.code.split("\n"))
|
|
13407
|
+
});
|
|
13408
|
+
} catch (error) {
|
|
13409
|
+
const why = error instanceof Error ? error.message : String(error);
|
|
13410
|
+
errors.push({
|
|
13411
|
+
level: "error",
|
|
13412
|
+
code: "map-not-drawn",
|
|
13413
|
+
message: `The map in "${doc.concept}" could not be drawn: ${why}. Nothing was published, so try again once it can be.`,
|
|
13414
|
+
concept: doc.concept
|
|
13415
|
+
});
|
|
13416
|
+
}
|
|
13046
13417
|
} else if (fence.lang !== "" && fence.lang !== "math" && fence.lang !== "etherpk-cipher") {
|
|
13047
13418
|
const key = `${fence.lang}\n${fence.code}`;
|
|
13048
13419
|
if (highlighted.has(key)) continue;
|
|
13049
13420
|
const html = await highlight(fence.lang, fence.code);
|
|
13050
13421
|
if (html !== null) highlighted.set(key, html);
|
|
13051
13422
|
}
|
|
13423
|
+
if (errors.length > 0) return {
|
|
13424
|
+
bundle,
|
|
13425
|
+
report
|
|
13426
|
+
};
|
|
13052
13427
|
let diagramScope = {
|
|
13053
13428
|
prefix: "mermaid",
|
|
13054
13429
|
n: 0
|
|
@@ -13066,7 +13441,16 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
13066
13441
|
const svg = mermaidSvg.get(src);
|
|
13067
13442
|
return svg === void 0 ? void 0 : withDiagramId(svg, `${diagramScope.prefix}_${++diagramScope.n}`);
|
|
13068
13443
|
},
|
|
13069
|
-
highlighted: (lang, code) => highlighted.get(`${lang}\n${code}`)
|
|
13444
|
+
highlighted: (lang, code) => highlighted.get(`${lang}\n${code}`),
|
|
13445
|
+
mapPicture: (src) => {
|
|
13446
|
+
const picture = mapPictures.get(src);
|
|
13447
|
+
return picture && {
|
|
13448
|
+
src: picture.path,
|
|
13449
|
+
alt: picture.alt,
|
|
13450
|
+
width: picture.width,
|
|
13451
|
+
height: picture.height
|
|
13452
|
+
};
|
|
13453
|
+
}
|
|
13070
13454
|
});
|
|
13071
13455
|
const pages = [];
|
|
13072
13456
|
let done = 0;
|
|
@@ -13324,6 +13708,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
13324
13708
|
bundle.set(`assets/${name}`, asset.bytes);
|
|
13325
13709
|
report.assets.copied.push(name);
|
|
13326
13710
|
}
|
|
13711
|
+
for (const picture of mapPictures.values()) bundle.set(picture.path, picture.bytes);
|
|
13327
13712
|
for (const [path, text] of theme.files) if (path.startsWith("assets/")) bundle.set(`theme/${path.slice(7)}`, text);
|
|
13328
13713
|
if (customCss !== "") bundle.set("theme/custom.css", customCss.endsWith("\n") ? customCss : `${customCss}\n`);
|
|
13329
13714
|
if (hasMath) {
|
|
@@ -13675,7 +14060,7 @@ async function runPublish(publication, deps) {
|
|
|
13675
14060
|
* Client bundles, so both hosts draw the same SVG, and the post-processing is the Client's
|
|
13676
14061
|
* (`browser-environment.ts`): the minted id dropped (it collides across pages), `role="img"`.
|
|
13677
14062
|
*/
|
|
13678
|
-
var require$
|
|
14063
|
+
var require$2 = createRequire(import.meta.url);
|
|
13679
14064
|
/** Where Playwright keeps the browsers this package installs: under our cache, not the user's global one. */
|
|
13680
14065
|
function browsersDir(env) {
|
|
13681
14066
|
return join(cacheRoot(env), "browsers");
|
|
@@ -13718,7 +14103,7 @@ async function setupDiagrams(env, say) {
|
|
|
13718
14103
|
say(before.source === "env" ? `Using the Chromium named by ETHERPK_CHROMIUM: ${before.executable}` : `Chromium is already installed: ${before.executable}`);
|
|
13719
14104
|
return before;
|
|
13720
14105
|
}
|
|
13721
|
-
const cli = require$
|
|
14106
|
+
const cli = require$2.resolve("playwright-core/cli.js");
|
|
13722
14107
|
const dir = env.PLAYWRIGHT_BROWSERS_PATH?.trim() || browsersDir(env);
|
|
13723
14108
|
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
14109
|
await new Promise((resolve, reject) => {
|
|
@@ -13751,7 +14136,7 @@ async function setupDiagrams(env, say) {
|
|
|
13751
14136
|
return after;
|
|
13752
14137
|
}
|
|
13753
14138
|
/** 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>";
|
|
14139
|
+
var PAGE$1 = "<!doctype html><html><head><meta charset=\"utf-8\"></head><body></body></html>";
|
|
13755
14140
|
/** A headless browser from whichever Chromium is available, or null. The caller closes it. */
|
|
13756
14141
|
async function launchChromium(env) {
|
|
13757
14142
|
const status = await chromiumStatus(env);
|
|
@@ -13773,8 +14158,8 @@ async function openDiagramRenderer(env) {
|
|
|
13773
14158
|
const page = () => {
|
|
13774
14159
|
if (!pagePromise) pagePromise = (async () => {
|
|
13775
14160
|
const p = await (await browser.newContext({ javaScriptEnabled: true })).newPage();
|
|
13776
|
-
await p.setContent(PAGE);
|
|
13777
|
-
await p.addScriptTag({ path: require$
|
|
14161
|
+
await p.setContent(PAGE$1);
|
|
14162
|
+
await p.addScriptTag({ path: require$2.resolve("mermaid/dist/mermaid.min.js") });
|
|
13778
14163
|
await p.evaluate(() => {
|
|
13779
14164
|
window.mermaid.initialize({
|
|
13780
14165
|
startOnLoad: false,
|
|
@@ -13811,6 +14196,131 @@ async function openDiagramRenderer(env) {
|
|
|
13811
14196
|
};
|
|
13812
14197
|
}
|
|
13813
14198
|
//#endregion
|
|
14199
|
+
//#region ../../extensions/maps/src/map-host.ts
|
|
14200
|
+
var HOST_STYLE = "https://maps.etherpk.com/styles/light.json";
|
|
14201
|
+
var DEFAULT_MAP_STYLES = {
|
|
14202
|
+
light: HOST_STYLE,
|
|
14203
|
+
dark: HOST_STYLE
|
|
14204
|
+
};
|
|
14205
|
+
//#endregion
|
|
14206
|
+
//#region src/map-pictures.ts
|
|
14207
|
+
/**
|
|
14208
|
+
* Map pictures for a publish from the Headless Client (ADR 0118): a [[Map Block]] is published as
|
|
14209
|
+
* a picture of the map, never as its lines, and the picture is drawn in a real Chromium, the one
|
|
14210
|
+
* Mermaid diagrams use (diagrams.ts), because MapLibre needs WebGL and a page to draw in.
|
|
14211
|
+
*
|
|
14212
|
+
* The page runs the maps extension's own drawing code (`map-picture-page.js`, built from
|
|
14213
|
+
* `src/map-picture-page.ts` beside the Node bundle), so a map published from here looks as it
|
|
14214
|
+
* does published from the browser. It is served, with the MapLibre this package installs, from a
|
|
14215
|
+
* server on the loopback address that answers only those files, so MapLibre's worker loads as it
|
|
14216
|
+
* does in the Client. The basemap comes from EtherPK's map host, or the style
|
|
14217
|
+
* `ETHERPK_MAP_STYLE_URL` names; set empty, maps are drawn on a plain background.
|
|
14218
|
+
*/
|
|
14219
|
+
var require$1 = createRequire(import.meta.url);
|
|
14220
|
+
/** The light basemap style a publish draws with, or null for a plain background. */
|
|
14221
|
+
function publishMapStyle(env) {
|
|
14222
|
+
const named = env.ETHERPK_MAP_STYLE_URL;
|
|
14223
|
+
if (named === void 0) return DEFAULT_MAP_STYLES.light;
|
|
14224
|
+
return named.trim() === "" ? null : named.trim();
|
|
14225
|
+
}
|
|
14226
|
+
/** Whether a publish of these bodies draws a map: a fenced block whose info string is `map`. */
|
|
14227
|
+
function needsMaps(bodies) {
|
|
14228
|
+
for (const body of bodies) if (/^\s*(?:-\s+)?```map\s*$/m.test(body)) return true;
|
|
14229
|
+
return false;
|
|
14230
|
+
}
|
|
14231
|
+
/** The page: MapLibre by the import map, then the drawing code, which puts its function on `window`. */
|
|
14232
|
+
var PAGE = `<!doctype html>
|
|
14233
|
+
<html><head><meta charset="utf-8">
|
|
14234
|
+
<script type="importmap">{"imports":{"maplibre-gl":"/maplibre-gl.mjs"}}<\/script>
|
|
14235
|
+
<script type="module" src="/map-picture-page.js"><\/script>
|
|
14236
|
+
</head><body></body></html>`;
|
|
14237
|
+
/** The files the page loads, by path, and where each is on disk. */
|
|
14238
|
+
function pageFiles() {
|
|
14239
|
+
const maplibre = dirname(require$1.resolve("maplibre-gl/dist/maplibre-gl.mjs"));
|
|
14240
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14241
|
+
const bundle = here.endsWith("dist") ? join(here, "map-picture-page.js") : join(here, "..", "dist", "map-picture-page.js");
|
|
14242
|
+
return /* @__PURE__ */ new Map([
|
|
14243
|
+
["/maplibre-gl.mjs", join(maplibre, "maplibre-gl.mjs")],
|
|
14244
|
+
["/maplibre-gl-shared.mjs", join(maplibre, "maplibre-gl-shared.mjs")],
|
|
14245
|
+
["/maplibre-gl-worker.mjs", join(maplibre, "maplibre-gl-worker.mjs")],
|
|
14246
|
+
["/map-picture-page.js", bundle]
|
|
14247
|
+
]);
|
|
14248
|
+
}
|
|
14249
|
+
/** A server on the loopback address that answers the page and its files, and nothing else. */
|
|
14250
|
+
async function servePage() {
|
|
14251
|
+
const files = pageFiles();
|
|
14252
|
+
const server = createServer$1((request, response) => {
|
|
14253
|
+
const path = new URL(request.url ?? "/", "http://localhost").pathname;
|
|
14254
|
+
if (path === "/map.html") {
|
|
14255
|
+
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }).end(PAGE);
|
|
14256
|
+
return;
|
|
14257
|
+
}
|
|
14258
|
+
const file = files.get(path);
|
|
14259
|
+
if (!file) {
|
|
14260
|
+
response.writeHead(404).end();
|
|
14261
|
+
return;
|
|
14262
|
+
}
|
|
14263
|
+
readFile(file).then((bytes) => response.writeHead(200, { "Content-Type": "text/javascript; charset=utf-8" }).end(bytes), () => response.writeHead(404).end());
|
|
14264
|
+
});
|
|
14265
|
+
await new Promise((resolve, reject) => {
|
|
14266
|
+
server.once("error", reject);
|
|
14267
|
+
server.listen(0, "127.0.0.1", () => resolve());
|
|
14268
|
+
});
|
|
14269
|
+
const address = server.address();
|
|
14270
|
+
if (!address || typeof address === "string") throw new Error("The map page server has no port.");
|
|
14271
|
+
return {
|
|
14272
|
+
server,
|
|
14273
|
+
origin: `http://127.0.0.1:${address.port}`
|
|
14274
|
+
};
|
|
14275
|
+
}
|
|
14276
|
+
/**
|
|
14277
|
+
* A renderer over one headless browser, opened for a publish and closed after it. Null when no
|
|
14278
|
+
* browser is available; the caller decides what that means (a publish with maps refuses).
|
|
14279
|
+
*/
|
|
14280
|
+
async function openMapRenderer(env) {
|
|
14281
|
+
const browser = await launchChromium(env);
|
|
14282
|
+
if (!browser) return null;
|
|
14283
|
+
const style = publishMapStyle(env);
|
|
14284
|
+
let served = null;
|
|
14285
|
+
let pagePromise;
|
|
14286
|
+
const page = () => {
|
|
14287
|
+
pagePromise ??= (async () => {
|
|
14288
|
+
served = await servePage();
|
|
14289
|
+
const p = await (await browser.newContext()).newPage();
|
|
14290
|
+
await p.goto(`${served.origin}/map.html`);
|
|
14291
|
+
await p.waitForFunction(() => typeof window.etherpkDrawMapPicture === "function", void 0, { timeout: 15e3 });
|
|
14292
|
+
return p;
|
|
14293
|
+
})();
|
|
14294
|
+
return pagePromise;
|
|
14295
|
+
};
|
|
14296
|
+
return {
|
|
14297
|
+
async render(source) {
|
|
14298
|
+
const drawn = await (await page()).evaluate(async ([text, basemap]) => {
|
|
14299
|
+
const picture = await window.etherpkDrawMapPicture(text, { style: basemap });
|
|
14300
|
+
let binary = "";
|
|
14301
|
+
for (const byte of picture.bytes) binary += String.fromCharCode(byte);
|
|
14302
|
+
return {
|
|
14303
|
+
type: picture.type,
|
|
14304
|
+
width: picture.width,
|
|
14305
|
+
height: picture.height,
|
|
14306
|
+
data: btoa(binary)
|
|
14307
|
+
};
|
|
14308
|
+
}, [source, style]);
|
|
14309
|
+
return {
|
|
14310
|
+
type: drawn.type,
|
|
14311
|
+
width: drawn.width,
|
|
14312
|
+
height: drawn.height,
|
|
14313
|
+
bytes: new Uint8Array(Buffer.from(drawn.data, "base64"))
|
|
14314
|
+
};
|
|
14315
|
+
},
|
|
14316
|
+
async dispose() {
|
|
14317
|
+
await browser.close().catch(() => {});
|
|
14318
|
+
const open = served;
|
|
14319
|
+
await new Promise((resolve) => open ? open.server.close(() => resolve()) : resolve());
|
|
14320
|
+
}
|
|
14321
|
+
};
|
|
14322
|
+
}
|
|
14323
|
+
//#endregion
|
|
13814
14324
|
//#region src/public-fetch.ts
|
|
13815
14325
|
/**
|
|
13816
14326
|
* Fetching text from the public internet, and nowhere else: the only network reads a tool makes
|
|
@@ -13959,8 +14469,8 @@ async function fetchPublicText(href, options = {}) {
|
|
|
13959
14469
|
/**
|
|
13960
14470
|
* The publisher's environment in Node (ADR 0082, ADR 0084): themes resolved the way the Client
|
|
13961
14471
|
* 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,
|
|
13963
|
-
* folder over `fs` under the same rules as the browser's directory handle. Browser-free
|
|
14472
|
+
* package this depends on, Mermaid and map pictures through a real browser when one is available,
|
|
14473
|
+
* and a site folder over `fs` under the same rules as the browser's directory handle. Browser-free
|
|
13964
14474
|
* counterpart of `host/browser-environment.ts`; the core is shared.
|
|
13965
14475
|
*/
|
|
13966
14476
|
var require = createRequire(import.meta.url);
|
|
@@ -13992,6 +14502,7 @@ function createNodePublishEnvironment(deps) {
|
|
|
13992
14502
|
graphTheme: deps.graphTheme
|
|
13993
14503
|
}),
|
|
13994
14504
|
...renderer ? { renderMermaid: (source) => renderer.render(source) } : {},
|
|
14505
|
+
...deps.maps ? { renderMap: (source) => deps.maps.render(source) } : {},
|
|
13995
14506
|
katexAssets
|
|
13996
14507
|
};
|
|
13997
14508
|
}
|
|
@@ -15243,35 +15754,19 @@ async function writeDownload(directory, name, bytes) {
|
|
|
15243
15754
|
}
|
|
15244
15755
|
throw new Error(`${directory} already holds a thousand files named like ${safe}.`);
|
|
15245
15756
|
}
|
|
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
15757
|
//#endregion
|
|
15256
|
-
//#region ../client/src/lib/
|
|
15758
|
+
//#region ../client/src/lib/navigation/concept-path.ts
|
|
15257
15759
|
/**
|
|
15258
|
-
*
|
|
15259
|
-
*
|
|
15260
|
-
*
|
|
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.
|
|
15760
|
+
* A concept in a URL path (ADR 0023): each `/`-separated segment encoded on its own, so a
|
|
15761
|
+
* concept containing `/` spans several path segments and round-trips through a SvelteKit rest
|
|
15762
|
+
* param, which re-joins segments with `/` and decodes each. Its own module so the extension
|
|
15763
|
+
* address book and the document URLs share it without importing each other.
|
|
15265
15764
|
*/
|
|
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
15765
|
function encodeConceptPath(concept) {
|
|
15273
15766
|
return concept.split("/").map(encodeURIComponent).join("/");
|
|
15274
15767
|
}
|
|
15768
|
+
//#endregion
|
|
15769
|
+
//#region ../client/src/lib/navigation/document-url.ts
|
|
15275
15770
|
/** The Document URL for a concept within a graph. */
|
|
15276
15771
|
function documentUrl(graphId, concept) {
|
|
15277
15772
|
return `${graphUrl(graphId)}/d/${encodeConceptPath(concept)}`;
|
|
@@ -15424,7 +15919,7 @@ async function taskLinesWith(lines, fingerprint) {
|
|
|
15424
15919
|
return out;
|
|
15425
15920
|
}
|
|
15426
15921
|
//#endregion
|
|
15427
|
-
//#region
|
|
15922
|
+
//#region ../../extensions/graph-view/src/model/graph-model.ts
|
|
15428
15923
|
/**
|
|
15429
15924
|
* The [[Graph View]]'s model: the index's {@link LinkGraph} turned into a graphology graph the
|
|
15430
15925
|
* drawing and the insight lists both read, and the questions asked of it (what is near a
|
|
@@ -15543,7 +16038,7 @@ function shortestPath(graph, from, to) {
|
|
|
15543
16038
|
return null;
|
|
15544
16039
|
}
|
|
15545
16040
|
//#endregion
|
|
15546
|
-
//#region
|
|
16041
|
+
//#region ../../extensions/graph-view/src/model/insights.ts
|
|
15547
16042
|
/**
|
|
15548
16043
|
* The lists the whole-graph [[Graph View]] shows beside the picture, and the Headless Client's
|
|
15549
16044
|
* `graph_insights` tool returns: [[Hub]]s, [[Pageless Concept]]s by how many documents mention
|
|
@@ -17011,20 +17506,31 @@ async function publish(graph, args, host) {
|
|
|
17011
17506
|
}
|
|
17012
17507
|
const selected = selectDocuments(source.documents, publication, summary.publications);
|
|
17013
17508
|
const includeTexts = Object.values(publication.includes).map((concept) => source.documents.find((d) => d.concept.toLowerCase() === concept.toLowerCase())?.text ?? "");
|
|
17509
|
+
const bodies = [...selected.included.map((d) => d.text), ...includeTexts];
|
|
17014
17510
|
let renderer = null;
|
|
17015
|
-
if (needsMermaid(
|
|
17511
|
+
if (needsMermaid(bodies)) {
|
|
17016
17512
|
renderer = await openDiagramRenderer(env);
|
|
17017
17513
|
if (!renderer) {
|
|
17018
17514
|
const status = await chromiumStatus(env, cmd);
|
|
17019
17515
|
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
17516
|
}
|
|
17021
17517
|
}
|
|
17518
|
+
let maps = null;
|
|
17519
|
+
if (needsMaps(bodies)) {
|
|
17520
|
+
maps = await openMapRenderer(env);
|
|
17521
|
+
if (!maps) {
|
|
17522
|
+
await renderer?.dispose();
|
|
17523
|
+
const status = await chromiumStatus(env, cmd);
|
|
17524
|
+
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.`);
|
|
17525
|
+
}
|
|
17526
|
+
}
|
|
17022
17527
|
try {
|
|
17023
17528
|
const run = await runPublish(publication, {
|
|
17024
17529
|
source,
|
|
17025
17530
|
environment: createNodePublishEnvironment({
|
|
17026
17531
|
graphTheme: (id) => graph.publishing.graphTheme(id),
|
|
17027
|
-
renderer
|
|
17532
|
+
renderer,
|
|
17533
|
+
maps
|
|
17028
17534
|
}),
|
|
17029
17535
|
unsettled
|
|
17030
17536
|
});
|
|
@@ -17064,6 +17570,7 @@ async function publish(graph, args, host) {
|
|
|
17064
17570
|
};
|
|
17065
17571
|
} finally {
|
|
17066
17572
|
await renderer?.dispose();
|
|
17573
|
+
await maps?.dispose();
|
|
17067
17574
|
}
|
|
17068
17575
|
}
|
|
17069
17576
|
/**
|
|
@@ -18023,7 +18530,7 @@ function createMcpServer(source, info) {
|
|
|
18023
18530
|
}, async (args) => run((graph) => updatePublication(graph, args, host)));
|
|
18024
18531
|
server.registerTool("publish", {
|
|
18025
18532
|
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.",
|
|
18533
|
+
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
18534
|
inputSchema: { id: z.string().min(1) }
|
|
18028
18535
|
}, async (args) => run((graph) => publish(graph, args, host)));
|
|
18029
18536
|
server.registerTool("list_themes", {
|
|
@@ -18619,19 +19126,6 @@ function sameNames(a, b) {
|
|
|
18619
19126
|
}
|
|
18620
19127
|
//#endregion
|
|
18621
19128
|
//#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
19129
|
function linkGraph(db) {
|
|
18636
19130
|
const generation = activeIndexGeneration(db);
|
|
18637
19131
|
const pages = db.all("SELECT concept, concept_key AS key, kind, protected FROM pages WHERE generation = ? ORDER BY id", [generation]);
|
|
@@ -18648,6 +19142,7 @@ function linkGraph(db) {
|
|
|
18648
19142
|
name: page.concept,
|
|
18649
19143
|
kind: page.kind
|
|
18650
19144
|
};
|
|
19145
|
+
if (page.kind === "journal" && isJournalConcept(page.concept)) concept.day = page.concept;
|
|
18651
19146
|
concepts.set(page.key, page.protected ? {
|
|
18652
19147
|
...concept,
|
|
18653
19148
|
protected: true
|
|
@@ -18706,6 +19201,57 @@ function linkGraph(db) {
|
|
|
18706
19201
|
};
|
|
18707
19202
|
}
|
|
18708
19203
|
//#endregion
|
|
19204
|
+
//#region ../client/src/lib/document/index-map-items.ts
|
|
19205
|
+
/**
|
|
19206
|
+
* The most items one answer carries. Far beyond a real graph's maps (hundreds of Map Blocks of
|
|
19207
|
+
* tens of places each), and small enough that a pathological graph cannot stall the worker or
|
|
19208
|
+
* the drawing.
|
|
19209
|
+
*/
|
|
19210
|
+
var MAP_ITEMS_CAP = 2e4;
|
|
19211
|
+
/**
|
|
19212
|
+
* Whether the document with this concept key holds any place or route in the active generation.
|
|
19213
|
+
* Asked before and after a document is indexed again, so an update can say whether it touched a
|
|
19214
|
+
* map and an open Map View reads again only when one did.
|
|
19215
|
+
*/
|
|
19216
|
+
function pageHasMapItems(db, key) {
|
|
19217
|
+
return db.all(`SELECT 1 AS found FROM map_items m JOIN pages p ON p.id = m.page_id
|
|
19218
|
+
WHERE p.generation = ? AND p.concept_key = ? LIMIT 1`, [activeIndexGeneration(db), key]).length > 0;
|
|
19219
|
+
}
|
|
19220
|
+
/** Every place and route answering to `concept`, or in the whole graph when it is null. */
|
|
19221
|
+
function mapItems(db, concept, cap = MAP_ITEMS_CAP) {
|
|
19222
|
+
const where = ["p.generation = ?", "p.protected = 0"];
|
|
19223
|
+
const params = [activeIndexGeneration(db)];
|
|
19224
|
+
if (concept !== null) {
|
|
19225
|
+
const names = conceptFamilyKeys(db, conceptKey$1(concept));
|
|
19226
|
+
where.push(`EXISTS (SELECT 1 FROM map_concepts mc
|
|
19227
|
+
WHERE mc.page_id = m.page_id AND mc.block_local_id = m.block_local_id
|
|
19228
|
+
AND mc.concept_key IN (${names.map(() => "?").join(",")}))`);
|
|
19229
|
+
params.push(...names);
|
|
19230
|
+
}
|
|
19231
|
+
const rows = db.all(`SELECT p.concept, p.kind, m.line, m.fence_line, m.text
|
|
19232
|
+
FROM map_items m JOIN pages p ON p.id = m.page_id
|
|
19233
|
+
WHERE ${where.join(" AND ")}
|
|
19234
|
+
ORDER BY p.concept_key, p.id, m.line
|
|
19235
|
+
LIMIT ?`, [...params, cap + 1]);
|
|
19236
|
+
const truncated = rows.length > cap;
|
|
19237
|
+
const items = [];
|
|
19238
|
+
for (const row of truncated ? rows.slice(0, cap) : rows) {
|
|
19239
|
+
const item = readMapLine(row.text);
|
|
19240
|
+
if (item) items.push({
|
|
19241
|
+
concept: row.concept,
|
|
19242
|
+
kind: row.kind,
|
|
19243
|
+
line: row.line,
|
|
19244
|
+
fenceLine: row.fence_line,
|
|
19245
|
+
text: row.text,
|
|
19246
|
+
item
|
|
19247
|
+
});
|
|
19248
|
+
}
|
|
19249
|
+
return {
|
|
19250
|
+
items,
|
|
19251
|
+
truncated
|
|
19252
|
+
};
|
|
19253
|
+
}
|
|
19254
|
+
//#endregion
|
|
18709
19255
|
//#region ../client/src/lib/document/index-worker/core.ts
|
|
18710
19256
|
/**
|
|
18711
19257
|
* The index server, free of any transport (ADR 0041 §2). It owns the SQLite database and
|
|
@@ -18805,7 +19351,7 @@ function createIndexCore(host) {
|
|
|
18805
19351
|
return db.all("SELECT COUNT(*) AS n FROM pages WHERE generation = ?", [activeIndexGeneration(db)])[0]?.n ?? 0;
|
|
18806
19352
|
}
|
|
18807
19353
|
const candidateExists = (candidate) => candidate !== void 0 && candidate.kind !== "pageless";
|
|
18808
|
-
function deltaFor(revision, before, after, backlinkTargetsChanged) {
|
|
19354
|
+
function deltaFor(revision, before, after, backlinkTargetsChanged, mapsChanged) {
|
|
18809
19355
|
const existingAdded = [];
|
|
18810
19356
|
const existingRemoved = [];
|
|
18811
19357
|
const candidateUpserts = [];
|
|
@@ -18824,7 +19370,8 @@ function createIndexCore(host) {
|
|
|
18824
19370
|
existingRemoved,
|
|
18825
19371
|
candidateUpserts,
|
|
18826
19372
|
candidateRemoved,
|
|
18827
|
-
backlinkTargetsChanged: [...backlinkTargetsChanged]
|
|
19373
|
+
backlinkTargetsChanged: [...backlinkTargetsChanged],
|
|
19374
|
+
mapsChanged
|
|
18828
19375
|
};
|
|
18829
19376
|
}
|
|
18830
19377
|
/**
|
|
@@ -18952,10 +19499,19 @@ function createIndexCore(host) {
|
|
|
18952
19499
|
const before = conceptCandidatesForKeys(db, affected);
|
|
18953
19500
|
const previousSnapshot = visibleSnapshot;
|
|
18954
19501
|
visibleSnapshot = void 0;
|
|
18955
|
-
|
|
18956
|
-
for (const
|
|
19502
|
+
let mapsChanged = false;
|
|
19503
|
+
for (const key of removed) {
|
|
19504
|
+
mapsChanged ||= pageHasMapItems(db, key);
|
|
19505
|
+
removeDocument(db, key);
|
|
19506
|
+
}
|
|
19507
|
+
for (const doc of changed) {
|
|
19508
|
+
const key = conceptKey$1(doc.concept);
|
|
19509
|
+
const hadMaps = pageHasMapItems(db, key);
|
|
19510
|
+
ingestOne(db, doc);
|
|
19511
|
+
mapsChanged ||= hadMaps || pageHasMapItems(db, key);
|
|
19512
|
+
}
|
|
18957
19513
|
const after = conceptCandidatesForKeys(db, affected);
|
|
18958
|
-
const delta = deltaFor(advanceIndexRevision(db), before, after, backlinkTargetsChanged);
|
|
19514
|
+
const delta = deltaFor(advanceIndexRevision(db), before, after, backlinkTargetsChanged, mapsChanged);
|
|
18959
19515
|
applyDeltaToSnapshot(previousSnapshot, delta);
|
|
18960
19516
|
return [{
|
|
18961
19517
|
type: "delta",
|
|
@@ -19054,6 +19610,17 @@ function createIndexCore(host) {
|
|
|
19054
19610
|
graph
|
|
19055
19611
|
}];
|
|
19056
19612
|
}
|
|
19613
|
+
case "map-items": {
|
|
19614
|
+
const result = db ? mapItems(db, request.concept) : {
|
|
19615
|
+
items: [],
|
|
19616
|
+
truncated: false
|
|
19617
|
+
};
|
|
19618
|
+
return [{
|
|
19619
|
+
type: "map-items",
|
|
19620
|
+
id: request.id,
|
|
19621
|
+
result
|
|
19622
|
+
}];
|
|
19623
|
+
}
|
|
19057
19624
|
case "semantic-status": {
|
|
19058
19625
|
const status = db ? semanticStatus(db, request.model) : {
|
|
19059
19626
|
available: false,
|
|
@@ -19268,7 +19835,8 @@ function createRemoteGraphIndex(source, transport, options) {
|
|
|
19268
19835
|
for (const listener of updated) listener({
|
|
19269
19836
|
full: true,
|
|
19270
19837
|
changedConceptKeys,
|
|
19271
|
-
backlinkTargetsChanged: /* @__PURE__ */ new Set()
|
|
19838
|
+
backlinkTargetsChanged: /* @__PURE__ */ new Set(),
|
|
19839
|
+
mapsChanged: true
|
|
19272
19840
|
});
|
|
19273
19841
|
if (response.rebuildId && activeRebuildId) {
|
|
19274
19842
|
const shouldRetry = rebuildInvalidated;
|
|
@@ -19304,7 +19872,8 @@ function createRemoteGraphIndex(source, transport, options) {
|
|
|
19304
19872
|
const update = {
|
|
19305
19873
|
full: false,
|
|
19306
19874
|
changedConceptKeys,
|
|
19307
|
-
backlinkTargetsChanged: new Set(response.backlinkTargetsChanged)
|
|
19875
|
+
backlinkTargetsChanged: new Set(response.backlinkTargetsChanged),
|
|
19876
|
+
mapsChanged: response.mapsChanged
|
|
19308
19877
|
};
|
|
19309
19878
|
performanceRecorder.mark("index.delta.received", {
|
|
19310
19879
|
bytes: new TextEncoder().encode(JSON.stringify(response)).byteLength,
|
|
@@ -19853,6 +20422,13 @@ function createRemoteGraphIndex(source, transport, options) {
|
|
|
19853
20422
|
id
|
|
19854
20423
|
}))).graph;
|
|
19855
20424
|
},
|
|
20425
|
+
async mapItems(concept) {
|
|
20426
|
+
return (await request((id) => ({
|
|
20427
|
+
type: "map-items",
|
|
20428
|
+
id,
|
|
20429
|
+
concept
|
|
20430
|
+
}))).result;
|
|
20431
|
+
},
|
|
19856
20432
|
async searchText(query, offset, limit, filters) {
|
|
19857
20433
|
const response = await request((id) => ({
|
|
19858
20434
|
type: "search-text",
|
|
@@ -24485,9 +25061,9 @@ var USAGE = `etherpk-mcp - EtherPK Headless Client (an MCP server over one synce
|
|
|
24485
25061
|
removed - everything else in it is left alone. Publishing writes files - deploying them
|
|
24486
25062
|
(a git push, say) is yours. Run it from cron for a site that republishes itself.
|
|
24487
25063
|
${CMD} diagrams setup
|
|
24488
|
-
Let a publish draw Mermaid diagrams. Downloads a Chromium (about 170 MB) into the
|
|
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
|
|
25064
|
+
Let a publish draw Mermaid diagrams and maps. Downloads a Chromium (about 170 MB) into the
|
|
25065
|
+
cache directory with Playwright's installer, once per computer - a publish whose pages hold
|
|
25066
|
+
diagrams or maps refuses until this has run. Set ETHERPK_CHROMIUM=<path> to use a browser
|
|
24491
25067
|
already on this machine instead (NixOS needs this).
|
|
24492
25068
|
${CMD} diagrams status
|
|
24493
25069
|
Which browser a publish would use, if any.
|
|
@@ -24874,7 +25450,7 @@ async function diagramsCommand(what) {
|
|
|
24874
25450
|
return;
|
|
24875
25451
|
case "status": {
|
|
24876
25452
|
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.`);
|
|
25453
|
+
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
25454
|
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
25455
|
return;
|
|
24880
25456
|
}
|
|
@@ -25218,7 +25794,7 @@ async function hostCommand(args) {
|
|
|
25218
25794
|
process.on("SIGHUP", () => {});
|
|
25219
25795
|
chromiumStatus(process.env, CMD).then((chromium) => {
|
|
25220
25796
|
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
|
|
25797
|
+
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
25798
|
});
|
|
25223
25799
|
}
|
|
25224
25800
|
/**
|