@echomem/mcp 1.4.7 → 1.4.9

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.
Files changed (46) hide show
  1. package/README.md +35 -9
  2. package/assets/canonical-scorer/README.md +18 -0
  3. package/assets/canonical-scorer/analyze-10-problems.mjs +857 -0
  4. package/assets/canonical-scorer/build-session-waste-dashboard.mjs +1628 -0
  5. package/assets/canonical-scorer/golden_anchors.mjs +83 -0
  6. package/assets/canonical-scorer/optimizable_detail.mjs +633 -0
  7. package/assets/hud/claude.svg +1 -0
  8. package/assets/hud/codex.svg +1 -0
  9. package/assets/hud/session-viewer.html +35 -0
  10. package/dist/city/chaos-to-clarity-pencil.html +582 -0
  11. package/dist/city/echo-ai-city-only.html +1126 -109
  12. package/dist/city/echo-ai-city-only.template.html +1126 -109
  13. package/dist/city/echo-face-cutout.png +0 -0
  14. package/dist/city/pencil-pie-generator.html +883 -0
  15. package/dist/city/pencil-webgl-landscape.html +1239 -0
  16. package/dist/city/spatial-fan-story.html +479 -0
  17. package/dist/codex-session-files.js +283 -0
  18. package/dist/codex-sync.js +7 -2
  19. package/dist/context-analysis/canonical-golden.js +47 -0
  20. package/dist/context-analysis/claude-native-canonical.js +1193 -0
  21. package/dist/context-analysis/vendored-canonical.js +793 -0
  22. package/dist/context-analysis/workspace-report.js +1838 -0
  23. package/dist/context-metrics/calculate.js +56 -0
  24. package/dist/context-metrics/model-limits.js +26 -0
  25. package/dist/context-metrics/types.js +1 -0
  26. package/dist/forensics-10-problems.js +7 -6
  27. package/dist/forensics.js +863 -132
  28. package/dist/hud/adapters.js +8 -4
  29. package/dist/hud/autostart.js +66 -0
  30. package/dist/hud/cli.js +31 -0
  31. package/dist/hud/electron-main.js +182 -19
  32. package/dist/hud/metric.js +13 -4
  33. package/dist/hud/monitor.js +171 -84
  34. package/dist/hud/preload.cjs +3 -0
  35. package/dist/hud/server.js +321 -4
  36. package/dist/hud/web.js +880 -270
  37. package/dist/index.js +122 -24
  38. package/dist/local-data-paths.js +87 -0
  39. package/dist/migrate.js +55 -29
  40. package/dist/report.js +101 -40
  41. package/dist/setup-page.js +4257 -245
  42. package/dist/setup-preview.js +245 -0
  43. package/dist/setup.js +786 -75
  44. package/dist/v1-contract.js +20 -2
  45. package/package.json +6 -4
  46. package/templates/echomem-recall.md +2 -2
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Claude</title><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z" fill="#D97757" fill-rule="nonzero"></path></svg>
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Codex</title><path d="M19.503 0H4.496A4.496 4.496 0 000 4.496v15.007A4.496 4.496 0 004.496 24h15.007A4.496 4.496 0 0024 19.503V4.496A4.496 4.496 0 0019.503 0z" fill="#fff"></path><path d="M9.064 3.344a4.578 4.578 0 012.285-.312c1 .115 1.891.54 2.673 1.275.01.01.024.017.037.021a.09.09 0 00.043 0 4.55 4.55 0 013.046.275l.047.022.116.057a4.581 4.581 0 012.188 2.399c.209.51.313 1.041.315 1.595a4.24 4.24 0 01-.134 1.223.123.123 0 00.03.115c.594.607.988 1.33 1.183 2.17.289 1.425-.007 2.71-.887 3.854l-.136.166a4.548 4.548 0 01-2.201 1.388.123.123 0 00-.081.076c-.191.551-.383 1.023-.74 1.494-.9 1.187-2.222 1.846-3.711 1.838-1.187-.006-2.239-.44-3.157-1.302a.107.107 0 00-.105-.024c-.388.125-.78.143-1.204.138a4.441 4.441 0 01-1.945-.466 4.544 4.544 0 01-1.61-1.335c-.152-.202-.303-.392-.414-.617a5.81 5.81 0 01-.37-.961 4.582 4.582 0 01-.014-2.298.124.124 0 00.006-.056.085.085 0 00-.027-.048 4.467 4.467 0 01-1.034-1.651 3.896 3.896 0 01-.251-1.192 5.189 5.189 0 01.141-1.6c.337-1.112.982-1.985 1.933-2.618.212-.141.413-.251.601-.33.215-.089.43-.164.646-.227a.098.098 0 00.065-.066 4.51 4.51 0 01.829-1.615 4.535 4.535 0 011.837-1.388zm3.482 10.565a.637.637 0 000 1.272h3.636a.637.637 0 100-1.272h-3.636zM8.462 9.23a.637.637 0 00-1.106.631l1.272 2.224-1.266 2.136a.636.636 0 101.095.649l1.454-2.455a.636.636 0 00.005-.64L8.462 9.23z" fill="url(#lobe-icons-codex-gradient)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-codex-gradient" x1="12" x2="12" y1="3" y2="21"><stop stop-color="#B1A7FF"></stop><stop offset=".5" stop-color="#7A9DFF"></stop><stop offset="1" stop-color="#3941FF"></stop></linearGradient></defs></svg>
@@ -1668,6 +1668,40 @@
1668
1668
  document.body.prepend(bar);
1669
1669
  }
1670
1670
 
1671
+ // Session switcher: one tab per recent Codex / Claude Code / Claude Desktop session (from the HUD).
1672
+ async function injectSessionTabs(currentPath, mode) {
1673
+ if (document.getElementById("echoSessionBar")) return;
1674
+ let sessions = [];
1675
+ try {
1676
+ const state = await (await fetch("/state")).json();
1677
+ sessions = (state && state.recentSessions) || [];
1678
+ } catch { return; }
1679
+ if (!sessions.length) return;
1680
+ const clientName = (c) => c === "codex" ? "Codex" : c === "claude-code" ? "Claude Code" : c === "claude-desktop" ? "Claude Desktop" : c;
1681
+ const bar = document.createElement("div");
1682
+ bar.id = "echoSessionBar";
1683
+ bar.style.cssText = "position:sticky;top:0;z-index:61;display:flex;gap:6px;align-items:center;padding:8px 14px;background:#010409;border-bottom:1px solid #30363d;overflow-x:auto;font:12px system-ui,-apple-system,sans-serif";
1684
+ const tag = document.createElement("span");
1685
+ tag.textContent = "session:"; tag.style.cssText = "color:#8b949e;margin-right:2px;flex:0 0 auto";
1686
+ bar.append(tag);
1687
+ sessions.forEach((sess) => {
1688
+ const active = sess.sourcePath === currentPath;
1689
+ const b = document.createElement("button");
1690
+ const title = (sess.title || "").length > 24 ? (sess.title || "").slice(0, 23) + "…" : (sess.title || "");
1691
+ b.textContent = (sess.live ? "● " : "") + clientName(sess.client) + " · " + title;
1692
+ b.title = clientName(sess.client) + " · " + (sess.title || "");
1693
+ b.style.cssText = "flex:0 0 auto;padding:6px 12px;border-radius:999px;cursor:pointer;color:#c9d1d9;white-space:nowrap;border:1px solid " + (active ? "#1f6feb" : "#30363d") + ";background:" + (active ? "#132f5e" : "#161b22");
1694
+ b.onclick = () => {
1695
+ const u = new URL(window.location.href);
1696
+ u.searchParams.set("path", sess.sourcePath);
1697
+ u.searchParams.set("mode", mode);
1698
+ window.location.href = u.toString();
1699
+ };
1700
+ bar.append(b);
1701
+ });
1702
+ document.body.prepend(bar); // above the mode bar → session on top, then event-log/payload
1703
+ }
1704
+
1671
1705
  async function initFromQuery() {
1672
1706
  const params = new URLSearchParams(window.location.search);
1673
1707
  const sessionPath = params.get("path");
@@ -1675,6 +1709,7 @@
1675
1709
  let src = params.get("src");
1676
1710
  if (sessionPath) {
1677
1711
  injectModeToggle(sessionPath, mode);
1712
+ await injectSessionTabs(sessionPath, mode);
1678
1713
  src = (mode === "payload" ? "/session-payload" : "/session-raw") + "?path=" + encodeURIComponent(sessionPath);
1679
1714
  }
1680
1715
  if (!src) return;
@@ -0,0 +1,582 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Chaos to Clarity — Pencil Study</title>
7
+ <style>
8
+ * { box-sizing: border-box; }
9
+ html, body { min-height: 100%; }
10
+ body {
11
+ margin: 0;
12
+ display: grid;
13
+ place-items: center;
14
+ padding: clamp(12px, 4vw, 48px);
15
+ background: #d8d4ca;
16
+ }
17
+ .sheet {
18
+ position: relative;
19
+ width: min(92vw, 900px);
20
+ aspect-ratio: 1;
21
+ overflow: hidden;
22
+ background: #f2eee4;
23
+ box-shadow: 0 24px 70px rgba(39, 38, 31, .18), 0 2px 5px rgba(39, 38, 31, .12);
24
+ }
25
+ canvas {
26
+ display: block;
27
+ width: 100%;
28
+ height: 100%;
29
+ }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <main class="sheet" aria-label="混乱经过透镜转化为清晰方向的铅笔插画">
34
+ <canvas id="art" width="1200" height="1200"></canvas>
35
+ </main>
36
+
37
+ <script>
38
+ const canvas = document.getElementById("art");
39
+ const g = canvas.getContext("2d");
40
+ const TAU = Math.PI * 2;
41
+ const ink = "#292a25";
42
+ const paper = "#f2eee4";
43
+
44
+ function randomFactory(seed) {
45
+ return function random() {
46
+ let t = seed += 0x6D2B79F5;
47
+ t = Math.imul(t ^ t >>> 15, t | 1);
48
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
49
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
50
+ };
51
+ }
52
+
53
+ function pencil(path, width = 2, alpha = .72, repeats = 2, seed = 1) {
54
+ const random = randomFactory(seed);
55
+ for (let i = 0; i < repeats; i++) {
56
+ g.save();
57
+ g.translate((random() - .5) * 2.1, (random() - .5) * 1.6);
58
+ path();
59
+ g.strokeStyle = ink;
60
+ g.globalAlpha = alpha / Math.max(1, repeats * .7);
61
+ g.lineWidth = width + random() * .7;
62
+ g.lineCap = "round";
63
+ g.lineJoin = "round";
64
+ g.stroke();
65
+ g.restore();
66
+ }
67
+ }
68
+
69
+ function line(x1, y1, x2, y2, width = 1.5, alpha = .6, seed = 1) {
70
+ pencil(() => {
71
+ g.beginPath();
72
+ g.moveTo(x1, y1);
73
+ g.lineTo(x2, y2);
74
+ }, width, alpha, 2, seed);
75
+ }
76
+
77
+ function poly(points, close = false, width = 1.8, alpha = .7, seed = 1) {
78
+ pencil(() => {
79
+ g.beginPath();
80
+ g.moveTo(points[0][0], points[0][1]);
81
+ for (let i = 1; i < points.length; i++) g.lineTo(points[i][0], points[i][1]);
82
+ if (close) g.closePath();
83
+ }, width, alpha, 2, seed);
84
+ }
85
+
86
+ function ellipse(cx, cy, rx, ry, width = 1.7, alpha = .68, seed = 1) {
87
+ pencil(() => {
88
+ g.beginPath();
89
+ g.ellipse(cx, cy, rx, ry, 0, 0, TAU);
90
+ }, width, alpha, 2, seed);
91
+ }
92
+
93
+ function arrow(x1, y1, x2, y2, seed = 1, alpha = .68) {
94
+ line(x1, y1, x2, y2, 1.5, alpha, seed);
95
+ const angle = Math.atan2(y2 - y1, x2 - x1);
96
+ const size = 12;
97
+ poly([
98
+ [x2 - Math.cos(angle - .55) * size, y2 - Math.sin(angle - .55) * size],
99
+ [x2, y2],
100
+ [x2 - Math.cos(angle + .55) * size, y2 - Math.sin(angle + .55) * size]
101
+ ], false, 1.5, alpha, seed + 2);
102
+ }
103
+
104
+ /*
105
+ PencilLight is the reusable drawing model for this document.
106
+
107
+ 1. Curve tangent = direction of the form.
108
+ 2. Curve normal = candidate side for light or shadow.
109
+ 3. Depth controls width, contrast and spacing: far is thin/light/open;
110
+ near is heavier/darker/closer.
111
+ 4. Shadow is made from continuous pressure strokes following the form,
112
+ never from mechanical dashed fills.
113
+ 5. A ribbon is a perspective surface: its two edges come from the curve
114
+ normal, while its width grows toward the viewer.
115
+ */
116
+ const PencilLight = Object.freeze({
117
+ light: { x: -.82, y: -.57 },
118
+
119
+ cubic(p0, p1, p2, p3, steps = 36) {
120
+ const points = [];
121
+ for (let i = 0; i <= steps; i++) {
122
+ const t = i / steps;
123
+ const u = 1 - t;
124
+ points.push({
125
+ x: u*u*u*p0.x + 3*u*u*t*p1.x + 3*u*t*t*p2.x + t*t*t*p3.x,
126
+ y: u*u*u*p0.y + 3*u*u*t*p1.y + 3*u*t*t*p2.y + t*t*t*p3.y,
127
+ depth: t
128
+ });
129
+ }
130
+ return points;
131
+ },
132
+
133
+ frames(points) {
134
+ return points.map((point, i) => {
135
+ const a = points[Math.max(0, i - 1)];
136
+ const b = points[Math.min(points.length - 1, i + 1)];
137
+ const tx = b.x - a.x;
138
+ const ty = b.y - a.y;
139
+ const length = Math.hypot(tx, ty) || 1;
140
+ return { ...point, tx: tx / length, ty: ty / length, nx: -ty / length, ny: tx / length };
141
+ });
142
+ },
143
+
144
+ curve(points, { width = 1.4, alpha = .58, seed = 1, repeats = 2 } = {}) {
145
+ pencil(() => {
146
+ g.beginPath();
147
+ g.moveTo(points[0].x, points[0].y);
148
+ for (let i = 1; i < points.length; i++) g.lineTo(points[i].x, points[i].y);
149
+ }, width, alpha, repeats, seed);
150
+ },
151
+
152
+ ribbon(points, widthAt, { seed = 1 } = {}) {
153
+ const frames = this.frames(points);
154
+ const left = [];
155
+ const right = [];
156
+ frames.forEach((frame, i) => {
157
+ const half = widthAt(frame.depth, i) * .5;
158
+ left.push({ x: frame.x + frame.nx * half, y: frame.y + frame.ny * half });
159
+ right.push({ x: frame.x - frame.nx * half, y: frame.y - frame.ny * half });
160
+ });
161
+
162
+ g.beginPath();
163
+ g.moveTo(left[0].x, left[0].y);
164
+ for (let i = 1; i < left.length; i++) g.lineTo(left[i].x, left[i].y);
165
+ for (let i = right.length - 1; i >= 0; i--) g.lineTo(right[i].x, right[i].y);
166
+ g.closePath();
167
+ g.fillStyle = "rgba(244,241,233,.96)";
168
+ g.fill();
169
+
170
+ this.curve(left, { width: 1.2, alpha: .54, seed });
171
+ this.curve(right, { width: 1.2, alpha: .54, seed: seed + 1 });
172
+
173
+ // The edge whose normal points away from the light receives pressure.
174
+ const mid = frames[Math.floor(frames.length * .58)];
175
+ const shadowSign = mid.nx * -this.light.x + mid.ny * -this.light.y >= 0 ? 1 : -1;
176
+ const shadowEdge = shadowSign > 0 ? left : right;
177
+ for (let band = 0; band < 4; band++) {
178
+ const shaded = shadowEdge.map((point, i) => {
179
+ const frame = frames[i];
180
+ const depth = frame.depth;
181
+ const distance = 2 + band * (2.2 + depth * 1.8);
182
+ return {
183
+ x: point.x + frame.nx * shadowSign * distance,
184
+ y: point.y + frame.ny * shadowSign * distance
185
+ };
186
+ });
187
+ this.curve(shaded, {
188
+ width: .75 + band * .08,
189
+ alpha: .34 - band * .055,
190
+ seed: seed + 20 + band,
191
+ repeats: 1
192
+ });
193
+ }
194
+ },
195
+
196
+ slopeFacet({ ridge, foot, spread, strokes, darkness, seed }) {
197
+ const random = randomFactory(seed);
198
+ for (let i = 0; i < strokes; i++) {
199
+ const lane = strokes === 1 ? 0 : i / (strokes - 1) - .5;
200
+ const start = {
201
+ x: ridge.x + lane * spread,
202
+ y: ridge.y + Math.abs(lane) * spread * .28
203
+ };
204
+ const end = {
205
+ x: foot.x + lane * spread * 1.5,
206
+ y: foot.y - random() * 8
207
+ };
208
+ const points = this.cubic(
209
+ start,
210
+ { x: start.x + (end.x - start.x) * .22, y: start.y + (end.y - start.y) * .4 },
211
+ { x: start.x + (end.x - start.x) * .72, y: start.y + (end.y - start.y) * .82 },
212
+ end,
213
+ 18
214
+ );
215
+ this.curve(points, {
216
+ width: .65 + darkness * .55,
217
+ alpha: .12 + darkness * .3,
218
+ seed: seed + i,
219
+ repeats: 1
220
+ });
221
+ }
222
+ },
223
+
224
+ meshSurface({ height, project, columns = 17, rows = 12, samples = 58, seed = 1 }) {
225
+ const clamp = value => Math.max(0, Math.min(1, value));
226
+
227
+ // Depth rows behave like soft contour lines across the terrain.
228
+ for (let row = 0; row < rows; row++) {
229
+ const v = row / (rows - 1);
230
+ const points = [];
231
+ for (let i = 0; i <= samples; i++) {
232
+ const u = -1 + i / samples * 2;
233
+ points.push(project(u, v, height(u, v)));
234
+ }
235
+ this.curve(points, {
236
+ width: .52 + v * .42,
237
+ alpha: .13 + v * .16,
238
+ seed: seed + row,
239
+ repeats: 1
240
+ });
241
+ }
242
+
243
+ // Flow lines run continuously from the far ridge toward the viewer.
244
+ // Their pressure is derived from surface slope relative to the light.
245
+ for (let column = 0; column < columns; column++) {
246
+ const u = -1 + column / (columns - 1) * 2;
247
+ const points = [];
248
+ for (let i = 0; i <= samples; i++) {
249
+ const v = i / samples;
250
+ points.push(project(u, v, height(u, v)));
251
+ }
252
+ const slope = height(Math.min(1, u + .025), .42) - height(Math.max(-1, u - .025), .42);
253
+ const darkness = clamp(.48 + slope * 2.4);
254
+ this.curve(points, {
255
+ width: .55 + darkness * .36,
256
+ alpha: .15 + darkness * .2,
257
+ seed: seed + 100 + column,
258
+ repeats: 1
259
+ });
260
+ }
261
+ }
262
+ });
263
+
264
+ function drawPaper() {
265
+ const random = randomFactory(8041);
266
+ g.fillStyle = paper;
267
+ g.fillRect(0, 0, canvas.width, canvas.height);
268
+ const colors = ["rgba(58,55,46,.025)", "rgba(255,255,255,.065)"];
269
+ for (let pass = 0; pass < 2; pass++) {
270
+ g.beginPath();
271
+ for (let i = 0; i < 4200; i++) {
272
+ const x = random() * canvas.width;
273
+ const y = random() * canvas.height;
274
+ const w = .35 + random() * 1.5;
275
+ g.rect(x, y, w, w * (.4 + random()));
276
+ }
277
+ g.fillStyle = colors[pass];
278
+ g.fill();
279
+ }
280
+ g.beginPath();
281
+ for (let i = 0; i < 180; i++) {
282
+ const x = random() * canvas.width;
283
+ const y = random() * canvas.height;
284
+ g.moveTo(x, y);
285
+ g.lineTo(x + 8 + random() * 24, y + (random() - .5) * 3);
286
+ }
287
+ g.strokeStyle = "rgba(69,65,54,.028)";
288
+ g.lineWidth = .55;
289
+ g.stroke();
290
+ }
291
+
292
+ function dottedJourney() {
293
+ g.save();
294
+ g.strokeStyle = "rgba(41,42,37,.45)";
295
+ g.lineWidth = 1.5;
296
+ g.setLineDash([3, 11]);
297
+ g.lineCap = "round";
298
+ g.beginPath();
299
+ g.moveTo(88, 130);
300
+ g.bezierCurveTo(55, 240, 232, 225, 178, 330);
301
+ g.bezierCurveTo(130, 430, 348, 366, 336, 510);
302
+ g.bezierCurveTo(325, 635, 118, 608, 182, 742);
303
+ g.bezierCurveTo(225, 822, 336, 788, 397, 862);
304
+ g.stroke();
305
+ g.restore();
306
+ }
307
+
308
+ function drawChaos() {
309
+ const random = randomFactory(3319);
310
+ const cx = 238;
311
+ const cy = 550;
312
+ for (let layer = 0; layer < 3; layer++) {
313
+ g.beginPath();
314
+ const strands = 17 + layer * 7;
315
+ for (let strand = 0; strand < strands; strand++) {
316
+ let x = cx + (random() - .5) * (174 - layer * 22);
317
+ let y = cy + (random() - .5) * (184 - layer * 20);
318
+ g.moveTo(x, y);
319
+ const steps = 8 + Math.floor(random() * 7);
320
+ for (let step = 0; step < steps; step++) {
321
+ const pull = .06 + layer * .018;
322
+ x += (random() - .5) * (72 - layer * 9) + (cx - x) * pull;
323
+ y += (random() - .5) * (68 - layer * 8) + (cy - y) * pull;
324
+ g.lineTo(x, y);
325
+ }
326
+ }
327
+ g.strokeStyle = `rgba(35,36,32,${.11 + layer * .045})`;
328
+ g.lineWidth = .74 + layer * .14;
329
+ g.lineCap = "round";
330
+ g.stroke();
331
+ }
332
+ for (let i = 0; i < 7; i++) {
333
+ const x = cx + (random() - .5) * 200;
334
+ const y = cy + (random() - .5) * 200;
335
+ ellipse(x, y, 16 + random() * 46, 10 + random() * 34, .9, .24, 500 + i);
336
+ }
337
+ }
338
+
339
+ function drawQuestion(x, y, s, seed) {
340
+ pencil(() => {
341
+ g.beginPath();
342
+ g.moveTo(x - s * .25, y - s * .28);
343
+ g.bezierCurveTo(x - s * .1, y - s * .66, x + s * .55, y - s * .48, x + s * .34, y - s * .08);
344
+ g.bezierCurveTo(x + s * .22, y + s * .12, x, y + s * .12, x, y + s * .38);
345
+ }, 1.6, .57, 2, seed);
346
+ ellipse(x, y + s * .62, 2.4, 2.4, 1.2, .65, seed + 1);
347
+ }
348
+
349
+ function drawGear(x, y, r, seed) {
350
+ ellipse(x, y, r * .58, r * .58, 1.4, .55, seed);
351
+ ellipse(x, y, r * .16, r * .16, 1.2, .55, seed + 1);
352
+ for (let i = 0; i < 9; i++) {
353
+ const a = i / 9 * TAU;
354
+ line(x + Math.cos(a) * r * .63, y + Math.sin(a) * r * .63,
355
+ x + Math.cos(a) * r, y + Math.sin(a) * r, 1.4, .55, seed + i + 2);
356
+ }
357
+ }
358
+
359
+ function drawCloud(x, y, s, seed) {
360
+ pencil(() => {
361
+ g.beginPath();
362
+ g.moveTo(x - s * .65, y + s * .2);
363
+ g.bezierCurveTo(x - s * .82, y - s * .18, x - s * .42, y - s * .4, x - s * .16, y - s * .24);
364
+ g.bezierCurveTo(x, y - s * .72, x + s * .5, y - s * .55, x + s * .48, y - s * .2);
365
+ g.bezierCurveTo(x + s * .86, y - s * .18, x + s * .82, y + s * .25, x + s * .55, y + s * .26);
366
+ g.lineTo(x - s * .65, y + s * .2);
367
+ }, 1.4, .5, 2, seed);
368
+ }
369
+
370
+ function drawDoodles() {
371
+ drawQuestion(40, 298, 32, 10);
372
+ drawQuestion(122, 756, 25, 11);
373
+ drawQuestion(390, 430, 20, 12);
374
+ drawGear(210, 206, 26, 20);
375
+ drawCloud(67, 377, 30, 31);
376
+ drawCloud(120, 850, 22, 32);
377
+ poly([[252,137],[306,115],[278,171],[268,143],[252,137]], true, 1.4, .6, 40);
378
+ line(268,143,306,115,1.1,.46,41);
379
+ poly([[389,207],[398,226],[419,229],[402,241],[407,262],[389,250],[372,263],[377,241],[359,229],[381,226]], true, 1.2, .55, 50);
380
+ poly([[296,825],[315,790],[322,812],[344,810],[318,853],[314,827]], true, 1.5, .62, 60);
381
+ poly([[94,698],[123,684],[145,699],[142,725],[110,730],[91,715]], false, 1.3, .45, 70);
382
+ ellipse(360,886,19,19,1.3,.5,80);
383
+ line(341,886,379,886,1,.34,81);
384
+ line(360,867,360,905,1,.34,82);
385
+ poly([[39,526],[51,510],[66,526],[51,542]], true, 1.2,.47,90);
386
+ ellipse(418,745,9,9,1.1,.5,91);
387
+ line(418,754,418,779,1.2,.48,92);
388
+ line(405,766,431,766,1.2,.48,93);
389
+ poly([[151,278],[166,265],[183,273],[181,293],[160,299],[148,288]], true, 1.1,.45,94);
390
+ line(55,214,85,214,1.2,.42,95);
391
+ line(70,199,70,229,1.2,.42,96);
392
+ ellipse(69,214,22,22,1.1,.38,97);
393
+ }
394
+
395
+ function lensPath() {
396
+ g.beginPath();
397
+ g.moveTo(501, 242);
398
+ g.bezierCurveTo(552, 335, 551, 676, 501, 785);
399
+ g.bezierCurveTo(451, 682, 450, 340, 501, 242);
400
+ g.closePath();
401
+ }
402
+
403
+ function drawLens() {
404
+ lensPath();
405
+ g.fillStyle = "rgba(231,226,215,.82)";
406
+ g.fill();
407
+ g.save();
408
+ lensPath();
409
+ g.clip();
410
+ g.beginPath();
411
+ for (let x = 452; x < 550; x += 9) {
412
+ g.moveTo(x, 210);
413
+ g.lineTo(x - 84, 830);
414
+ }
415
+ g.strokeStyle = "rgba(39,40,35,.17)";
416
+ g.lineWidth = .8;
417
+ g.stroke();
418
+ g.restore();
419
+ pencil(lensPath, 2.3, .82, 4, 301);
420
+ pencil(() => {
421
+ g.beginPath();
422
+ g.moveTo(494,252);
423
+ g.bezierCurveTo(468,382,469,647,494,775);
424
+ }, .8, .35, 2, 302);
425
+ }
426
+
427
+ function drawRays() {
428
+ const rays = [
429
+ [512,395,760,285],
430
+ [520,440,790,380],
431
+ [523,492,756,492],
432
+ [521,545,792,612],
433
+ [512,594,756,742]
434
+ ];
435
+ rays.forEach((ray, i) => arrow(...ray, 410 + i, .55));
436
+ g.save();
437
+ g.setLineDash([2,8]);
438
+ g.strokeStyle = "rgba(39,40,35,.28)";
439
+ g.lineWidth = 1;
440
+ g.beginPath();
441
+ g.moveTo(510,348); g.lineTo(900,202);
442
+ g.moveTo(516,646); g.lineTo(920,840);
443
+ g.stroke();
444
+ g.restore();
445
+ }
446
+
447
+ function drawSunAndCloud() {
448
+ ellipse(925,203,42,42,1.8,.68,500);
449
+ for (let i = 0; i < 12; i++) {
450
+ const a = i / 12 * TAU;
451
+ line(925 + Math.cos(a)*55, 203 + Math.sin(a)*55,
452
+ 925 + Math.cos(a)*82, 203 + Math.sin(a)*82, 1.1,.45,510+i);
453
+ }
454
+ drawCloud(780,220,48,540);
455
+ }
456
+
457
+ function drawMountain() {
458
+ const bell = (u, v, cu, cv, su, sv, amplitude) => {
459
+ const du = (u - cu) / su;
460
+ const dv = (v - cv) / sv;
461
+ return amplitude * Math.exp(-(du * du + dv * dv));
462
+ };
463
+ const height = (u, v) => Math.max(-.1,
464
+ bell(u,v,.02,.18,.43,.34,1.02) +
465
+ bell(u,v,-.7,.42,.29,.31,.58) +
466
+ bell(u,v,.69,.39,.3,.3,.66) -
467
+ bell(u,v,.02,.78,.42,.2,.2)
468
+ );
469
+ const project = (u, v, elevation) => ({
470
+ x: 895 + u * (176 + v * 78),
471
+ y: 326 + v * 182 - elevation * 154,
472
+ depth: v
473
+ });
474
+
475
+ PencilLight.meshSurface({
476
+ height,
477
+ project,
478
+ columns: 17,
479
+ rows: 12,
480
+ samples: 62,
481
+ seed: 600
482
+ });
483
+
484
+ const road = [
485
+ ...PencilLight.cubic(
486
+ {x:930,y:220},{x:858,y:264},{x:966,y:303},{x:890,y:345},18
487
+ ).slice(0,-1),
488
+ ...PencilLight.cubic(
489
+ {x:890,y:345},{x:808,y:382},{x:937,y:411},{x:831,y:455},20
490
+ ).slice(0,-1),
491
+ ...PencilLight.cubic(
492
+ {x:831,y:455},{x:762,y:483},{x:831,y:511},{x:758,y:544},18
493
+ )
494
+ ];
495
+ road.forEach((point, i) => point.depth = i / (road.length - 1));
496
+ PencilLight.ribbon(road, depth => 8 + Math.pow(depth, 1.7) * 28, { seed: 710 });
497
+ }
498
+
499
+ function drawBars(x,y) {
500
+ line(x-12,y+61,x+102,y+61,1.5,.55,800);
501
+ line(x-12,y+61,x-12,y-10,1.5,.55,801);
502
+ const heights=[21,40,69,52,88];
503
+ heights.forEach((h,i)=>{
504
+ const bx=x+i*21;
505
+ poly([[bx,y+60],[bx,y+60-h],[bx+13,y+60-h],[bx+13,y+60]],false,1.2,.5,810+i);
506
+ for(let yy=y+56;yy>y+60-h;yy-=7) line(bx+2,yy,bx+11,yy,0.7,.22,830+i+yy);
507
+ });
508
+ }
509
+
510
+ function drawNetwork(x,y) {
511
+ const nodes=[[0,0],[55,-28],[95,18],[74,70],[20,82],[-22,42],[42,32]];
512
+ const edges=[[0,1],[1,2],[2,3],[3,4],[4,5],[5,0],[0,6],[1,6],[2,6],[3,6],[4,6]];
513
+ edges.forEach((e,i)=>line(x+nodes[e[0]][0],y+nodes[e[0]][1],x+nodes[e[1]][0],y+nodes[e[1]][1],1.1,.45,900+i));
514
+ nodes.forEach((n,i)=>ellipse(x+n[0],y+n[1],7,7,1.4,.6,930+i));
515
+ }
516
+
517
+ function drawGlobe(x,y,r) {
518
+ ellipse(x,y,r,r,1.7,.62,950);
519
+ pencil(()=>{g.beginPath();g.ellipse(x,y,r*.46,r,0,0,TAU);},1,.42,2,951);
520
+ pencil(()=>{g.beginPath();g.ellipse(x,y,r,r*.42,0,0,TAU);},1,.42,2,952);
521
+ pencil(()=>{
522
+ g.beginPath();
523
+ g.moveTo(x-r*.72,y-r*.18);
524
+ g.bezierCurveTo(x-r*.25,y-r*.52,x-r*.08,y-r*.18,x+r*.12,y-r*.35);
525
+ g.bezierCurveTo(x+r*.32,y-r*.51,x+r*.5,y-r*.12,x+r*.72,y-r*.1);
526
+ g.moveTo(x-r*.58,y+r*.15);
527
+ g.bezierCurveTo(x-r*.28,y+r*.02,x-r*.18,y+r*.5,x+r*.18,y+r*.28);
528
+ g.bezierCurveTo(x+r*.36,y+r*.14,x+r*.55,y+r*.44,x+r*.66,y+r*.22);
529
+ },1.2,.5,2,953);
530
+ }
531
+
532
+ function drawPlant(x,y) {
533
+ line(x,y+80,x,y,1.8,.58,1000);
534
+ pencil(()=>{g.beginPath();g.moveTo(x,y+47);g.bezierCurveTo(x-48,y+24,x-50,y-9,x-6,y+17);g.bezierCurveTo(x+6,y+26,x+3,y+39,x,y+47);},1.5,.54,2,1001);
535
+ pencil(()=>{g.beginPath();g.moveTo(x,y+58);g.bezierCurveTo(x+48,y+35,x+53,y+4,x+7,y+29);g.bezierCurveTo(x-1,y+38,x-2,y+49,x,y+58);},1.5,.54,2,1002);
536
+ line(x-38,y+82,x+39,y+82,1.2,.42,1003);
537
+ }
538
+
539
+ function drawPeople(x,y) {
540
+ const people=[[-38,3,20],[0,-7,24],[41,4,19]];
541
+ people.forEach((p,i)=>{
542
+ ellipse(x+p[0],y+p[1]-p[2]*1.8,p[2]*.36,p[2]*.36,1.4,.55,1050+i);
543
+ pencil(()=>{
544
+ g.beginPath();
545
+ g.moveTo(x+p[0],y+p[1]-p[2]*1.35);
546
+ g.lineTo(x+p[0],y+p[1]+p[2]*.45);
547
+ g.moveTo(x+p[0]-p[2]*.65,y+p[1]-p[2]*.48);
548
+ g.lineTo(x+p[0]+p[2]*.65,y+p[1]-p[2]*.48);
549
+ g.moveTo(x+p[0],y+p[1]+p[2]*.45);
550
+ g.lineTo(x+p[0]-p[2]*.42,y+p[1]+p[2]*1.25);
551
+ g.moveTo(x+p[0],y+p[1]+p[2]*.45);
552
+ g.lineTo(x+p[0]+p[2]*.42,y+p[1]+p[2]*1.25);
553
+ },1.4,.55,2,1060+i);
554
+ });
555
+ }
556
+
557
+ function drawRightSymbols() {
558
+ drawBars(1062,554);
559
+ drawNetwork(835,670);
560
+ drawGlobe(1050,760,60);
561
+ drawPlant(748,865);
562
+ drawPeople(925,915);
563
+ ellipse(1114,890,9,9,1.2,.44,1100);
564
+ poly([[1110,899],[1120,925],[1104,925]],false,1.1,.4,1101);
565
+ }
566
+
567
+ function render() {
568
+ drawPaper();
569
+ dottedJourney();
570
+ drawChaos();
571
+ drawDoodles();
572
+ drawSunAndCloud();
573
+ drawRays();
574
+ drawMountain();
575
+ drawRightSymbols();
576
+ drawLens();
577
+ }
578
+
579
+ render();
580
+ </script>
581
+ </body>
582
+ </html>