axstack 0.36.1 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/bin/axstack.js +4 -18
- package/docs/getting-started.md +6 -6
- package/docs/installation.md +20 -41
- package/docs/workflows.md +12 -9
- package/package.json +1 -1
- package/profiles/presets/claude-only.json +3 -3
- package/profiles/presets/codex-only.json +3 -3
- package/profiles/presets/mixed.json +5 -4
- package/skills/axstack/references/role-roster.md +5 -3
- package/skills/axstack/references/ui-verification.md +6 -10
- package/skills/axstack-anatomy/ANATOMY.md +172 -0
- package/skills/axstack-anatomy/CHANGELOG.md +70 -0
- package/skills/axstack-anatomy/LICENSE +21 -0
- package/skills/axstack-anatomy/PROVENANCE.md +21 -0
- package/skills/axstack-anatomy/README.md +316 -0
- package/skills/axstack-anatomy/SKILL.md +74 -0
- package/skills/axstack-anatomy/SOURCE-PROOF.json +107 -0
- package/skills/axstack-anatomy/examples/arcade-cabinet/arcade-cabinet.css +44 -0
- package/skills/axstack-anatomy/examples/arcade-cabinet/build.mjs +714 -0
- package/skills/axstack-anatomy/examples/arcade-cabinet/live.js +597 -0
- package/skills/axstack-anatomy/examples/arcade-cabinet/parts.mjs +89 -0
- package/skills/axstack-anatomy/examples/arcade-cabinet/screen.mjs +114 -0
- package/skills/axstack-anatomy/examples/desk-computer/build.mjs +435 -0
- package/skills/axstack-anatomy/examples/dial-indicator/build.mjs +275 -0
- package/skills/axstack-anatomy/examples/raptor-engine/build.mjs +938 -0
- package/skills/axstack-anatomy/examples/raptor-engine/live.js +907 -0
- package/skills/axstack-anatomy/examples/raptor-engine/raptor.css +359 -0
- package/skills/axstack-anatomy/examples/ripple-tank/build.mjs +558 -0
- package/skills/axstack-anatomy/examples/ripple-tank/live.js +579 -0
- package/skills/axstack-anatomy/examples/ripple-tank/model.mjs +131 -0
- package/skills/axstack-anatomy/examples/ripple-tank/ripple.css +103 -0
- package/skills/axstack-anatomy/examples/spot-plate/art.tsx +110 -0
- package/skills/axstack-anatomy/examples/spot-plate/drops.ts +541 -0
- package/skills/axstack-anatomy/examples/spot-plate/fan.ts +84 -0
- package/skills/axstack-anatomy/examples/spot-plate/figure.tsx +25 -0
- package/skills/axstack-anatomy/examples/spot-plate/geometry.ts +280 -0
- package/skills/axstack-anatomy/examples/spot-plate/live.tsx +339 -0
- package/skills/axstack-anatomy/examples/spot-plate/rig.ts +230 -0
- package/skills/axstack-anatomy/examples/spot-plate/spot-plate.css +92 -0
- package/skills/axstack-anatomy/examples/test-rig/art.tsx +196 -0
- package/skills/axstack-anatomy/examples/test-rig/figure.tsx +23 -0
- package/skills/axstack-anatomy/examples/test-rig/geometry.ts +305 -0
- package/skills/axstack-anatomy/examples/test-rig/live.tsx +511 -0
- package/skills/axstack-anatomy/examples/test-rig/pose.ts +107 -0
- package/skills/axstack-anatomy/examples/test-rig/rig.ts +170 -0
- package/skills/axstack-anatomy/examples/test-rig/stretch.css +80 -0
- package/skills/axstack-anatomy/examples/test-rig/view.ts +99 -0
- package/skills/axstack-anatomy/examples/turning-dial/build.mjs +479 -0
- package/skills/axstack-anatomy/examples/turning-dial/dial.css +10 -0
- package/skills/axstack-anatomy/examples/turning-dial/live.js +342 -0
- package/skills/axstack-anatomy/examples/turning-dial/stress.mjs +269 -0
- package/skills/axstack-anatomy/kit/audit.mjs +1913 -0
- package/skills/axstack-anatomy/kit/canvas-painter.mjs +722 -0
- package/skills/axstack-anatomy/kit/gl-shared.mjs +184 -0
- package/skills/axstack-anatomy/kit/gl.mjs +184 -0
- package/skills/axstack-anatomy/kit/iso-kit.mjs +674 -0
- package/skills/axstack-anatomy/kit/iso-kit.ts +842 -0
- package/skills/axstack-anatomy/kit/iso.css +106 -0
- package/skills/axstack-anatomy/kit/lathe.mjs +1184 -0
- package/skills/axstack-anatomy/kit/react/draw.tsx +205 -0
- package/skills/axstack-anatomy/kit/react/live.tsx +140 -0
- package/skills/axstack-anatomy/kit/tube.mjs +306 -0
- package/skills/axstack-anatomy/kit/turn-audit.mjs +1096 -0
- package/skills/axstack-anatomy/kit/turn-build.mjs +1355 -0
- package/skills/axstack-anatomy/kit/turn-fixed.mjs +3800 -0
- package/skills/axstack-anatomy/kit/turn.mjs +3610 -0
- package/skills/axstack-anatomy/references/3d.md +352 -0
- package/skills/axstack-anatomy/references/craft.md +325 -0
- package/skills/axstack-anatomy/references/kit.md +419 -0
- package/skills/axstack-anatomy/references/motion.md +165 -0
- package/skills/axstack-anatomy/references/react.md +42 -0
- package/skills/axstack-anatomy/references/verify.md +351 -0
- package/skills/axstack-anatomy/references/walkthrough.md +75 -0
- package/skills/axstack-anatomy/references/webgl.md +473 -0
- package/skills/axstack-anatomy/scripts/audit-affine-bounds.test.mjs +54 -0
- package/skills/axstack-anatomy/scripts/audit-cache.test.mjs +37 -0
- package/skills/axstack-anatomy/scripts/audit-clearance-envelope.test.mjs +39 -0
- package/skills/axstack-anatomy/scripts/audit-empty-clipping.test.mjs +38 -0
- package/skills/axstack-anatomy/scripts/audit-order-convergence.test.mjs +55 -0
- package/skills/axstack-anatomy/scripts/audit-path-origin.test.mjs +26 -0
- package/skills/axstack-anatomy/scripts/audit-profile.test.mjs +41 -0
- package/skills/axstack-anatomy/scripts/audit-projection.test.mjs +36 -0
- package/skills/axstack-anatomy/scripts/audit-slide-clearance.test.mjs +58 -0
- package/skills/axstack-anatomy/scripts/audit-svg-attributes.test.mjs +46 -0
- package/skills/axstack-anatomy/scripts/audit-taper.test.mjs +29 -0
- package/skills/axstack-anatomy/scripts/capture-selector.test.mjs +131 -0
- package/skills/axstack-anatomy/scripts/capture.mjs +228 -0
- package/skills/axstack-anatomy/scripts/capture.test.mjs +42 -0
- package/skills/axstack-anatomy/scripts/chrome-port.mjs +36 -0
- package/skills/axstack-anatomy/scripts/chrome-startup.test.mjs +38 -0
- package/skills/axstack-anatomy/scripts/drive-actions.test.mjs +46 -0
- package/skills/axstack-anatomy/scripts/drive.mjs +302 -0
- package/skills/axstack-anatomy/scripts/drive.test.mjs +72 -0
- package/skills/axstack-anatomy/scripts/enter-key.test.mjs +38 -0
- package/skills/axstack-anatomy/scripts/fidelity-cut.test.mjs +54 -0
- package/skills/axstack-anatomy/scripts/fidelity-tube-coverage.test.mjs +40 -0
- package/skills/axstack-anatomy/scripts/inline-kit.mjs +26 -0
- package/skills/axstack-anatomy/scripts/line-ends.mjs +306 -0
- package/skills/axstack-anatomy/scripts/lines-arcs.test.mjs +50 -0
- package/skills/axstack-anatomy/scripts/lines-bezier.test.mjs +28 -0
- package/skills/axstack-anatomy/scripts/lines-round.test.mjs +56 -0
- package/skills/axstack-anatomy/scripts/lines-rounded-rect.test.mjs +32 -0
- package/skills/axstack-anatomy/scripts/lines.mjs +31 -0
- package/skills/axstack-anatomy/scripts/lines.test.mjs +38 -0
- package/skills/axstack-anatomy/scripts/navigation.test.mjs +55 -0
- package/skills/axstack-anatomy/scripts/orbit-sampling.test.mjs +50 -0
- package/skills/axstack-anatomy/scripts/react-dots-opacity.test.mjs +51 -0
- package/skills/axstack-anatomy/scripts/react-readout-first-write.test.mjs +37 -0
- package/skills/axstack-anatomy/scripts/react-solid-seamless.test.mjs +47 -0
- package/skills/axstack-anatomy/scripts/react-visibility-target.test.mjs +38 -0
- package/skills/axstack-anatomy/scripts/turn-bench.mjs +74 -0
- package/skills/axstack-anatomy/scripts/turn-check.mjs +641 -0
- package/skills/axstack-anatomy/scripts/turn-fidelity.mjs +413 -0
- package/skills/axstack-anatomy/scripts/turn-group-reference.test.mjs +40 -0
- package/skills/axstack-anatomy/scripts/turn-split-owner.test.mjs +62 -0
- package/skills/axstack-anatomy/theme.js +56 -0
- package/skills/axstack-explain/SKILL.md +28 -15
- package/skills/axstack-explain/references/delivery.md +22 -0
- package/skills/{axstack-diagram → axstack-explain}/references/fidelity.md +1 -7
- package/skills/axstack-explain/references/inline-pages.md +8 -8
- package/skills/axstack-explain/references/visual-qa.md +3 -5
- package/skills/axstack-explain/references/whiteboard-defense.md +59 -0
- package/src/archify-legacy.js +70 -0
- package/src/capabilities.js +1 -29
- package/src/installer.js +11 -31
- package/skills/axstack-diagram/SKILL.md +0 -28
- package/skills/axstack-diagram/references/archify.md +0 -76
- package/src/archify-pin.js +0 -5
- package/src/archify.js +0 -151
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import * as k from "../../kit/iso-kit.mjs";
|
|
5
|
+
import { kitScript } from "../../scripts/inline-kit.mjs";
|
|
6
|
+
|
|
7
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const theme = process.argv.includes("--light") ? "light" : "dark";
|
|
9
|
+
|
|
10
|
+
const WIDTH = 600;
|
|
11
|
+
const HEIGHT = 380;
|
|
12
|
+
|
|
13
|
+
const PLATE = { x: -128, y: -64, w: 262, d: 128, r: 8 };
|
|
14
|
+
const PLATE_H = 12;
|
|
15
|
+
const FOOT_H = 5;
|
|
16
|
+
|
|
17
|
+
const MAG = { x: -112, y: -20, w: 44, d: 40, r: 3 };
|
|
18
|
+
const MAG_H = 24;
|
|
19
|
+
const POST = [-98, 0];
|
|
20
|
+
const POST_TOP = 122;
|
|
21
|
+
const CLAMP_Z = 100;
|
|
22
|
+
|
|
23
|
+
const DIAL = [58, 0];
|
|
24
|
+
const DIAL_R = 20;
|
|
25
|
+
const DIAL_Z = 82;
|
|
26
|
+
const DIAL_H = 10;
|
|
27
|
+
const DIAL_TOP = DIAL_Z + DIAL_H;
|
|
28
|
+
const PER_TURN = 20;
|
|
29
|
+
|
|
30
|
+
const RAIL_FROM = 2;
|
|
31
|
+
const RAIL_TO = 124;
|
|
32
|
+
const CARRIAGE = { x: -28, y: -24, w: 56, d: 48, r: 3 };
|
|
33
|
+
const CARRIAGE_Z = 3;
|
|
34
|
+
const CARRIAGE_TOP = 8;
|
|
35
|
+
const BLOCKS = [18, 14, 10];
|
|
36
|
+
const BLOCK_W = 15.6;
|
|
37
|
+
const STEP = 16;
|
|
38
|
+
const TIP_R = 2.6;
|
|
39
|
+
const CENTRE = -24 + BLOCK_W / 2;
|
|
40
|
+
const STOPS = BLOCKS.map((_, index) => DIAL[0] - CENTRE - STEP * index);
|
|
41
|
+
const REACH = [STOPS[STOPS.length - 1] - 6, STOPS[0] + 6];
|
|
42
|
+
|
|
43
|
+
const P = k.fitProjection(
|
|
44
|
+
[...k.boxCorners(PLATE, -PLATE_H - FOOT_H, 0), [POST[0], POST[1], POST_TOP + 2], [DIAL[0] + DIAL_R, 0, DIAL_TOP]],
|
|
45
|
+
WIDTH,
|
|
46
|
+
HEIGHT,
|
|
47
|
+
{ pad: 30, azimuth: 42 },
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const at = (point) => k.iso(point, P);
|
|
51
|
+
const S = (paths, style) => k.solidSvg(paths, style);
|
|
52
|
+
const L = (d, style) => k.lineSvg(d, style);
|
|
53
|
+
const D = (points, style) => k.dotsSvg(points, style);
|
|
54
|
+
const byDepth = (points) => [...points].sort((a, b) => k.depthOf([a[0], a[1], 0], P) - k.depthOf([b[0], b[1], 0], P));
|
|
55
|
+
|
|
56
|
+
function plate() {
|
|
57
|
+
const out = [`<path class="iso-halo" d="${k.haloOf(PLATE, -PLATE_H - FOOT_H, PLATE_H, P)}"/>`];
|
|
58
|
+
for (const [x, y] of byDepth(k.corners(PLATE, 22))) {
|
|
59
|
+
out.push(S(k.cylinder(x, y, 7, -PLATE_H - FOOT_H, 1.6, P, 28), { tone: "lo" }));
|
|
60
|
+
out.push(S(k.cylinder(x, y, 5.4, -PLATE_H - FOOT_H + 1.6, FOOT_H - 1.6, P, 28), { tone: "mid" }));
|
|
61
|
+
}
|
|
62
|
+
out.push(S(k.slabOf(PLATE, -PLATE_H, PLATE_H, P, 8, 1.6), { tone: "mid" }));
|
|
63
|
+
out.push(L(k.planOutline(k.insetPlan(PLATE, 5), 0, P), { tone: "lo" }));
|
|
64
|
+
const screws = k.corners(PLATE, 10);
|
|
65
|
+
out.push(L(screws.map(([x, y]) => k.ring(x, y, 2.2, 0, P, 16)).join(""), { tone: "lo" }));
|
|
66
|
+
out.push(D(screws.map(([x, y]) => at([x, y, 0])), { size: 0.5 }));
|
|
67
|
+
const strip = { x: -110, y: PLATE.y + PLATE.d - 15, w: 230, d: 7, r: 1 };
|
|
68
|
+
out.push(S(k.slabOf(strip, 0, 0.6, P, 3), { tone: "lo", crease: "none" }));
|
|
69
|
+
const ticks = k.topTicks(strip.x + 4, strip.x + strip.w - 4, 4, 5, strip.y + 0.8, 0.6, [2, 3.6], P);
|
|
70
|
+
out.push(L(ticks.minor, { tone: "lo" }), L(ticks.major, { tone: "mid" }));
|
|
71
|
+
out.push(D([at([strip.x + 2, strip.y + 3.5, 0.6]), at([strip.x + strip.w - 2, strip.y + 3.5, 0.6])], { size: 0.45 }));
|
|
72
|
+
return out.join("");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rails() {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const y of [-20, 15]) {
|
|
78
|
+
out.push(S(k.slabOf({ x: RAIL_FROM, y, w: RAIL_TO - RAIL_FROM, d: 5, r: 1 }, 0, 3, P, 3, 0.4), { tone: "mid" }));
|
|
79
|
+
out.push(D([RAIL_FROM + 6, RAIL_TO - 6].map((x) => at([x, y + 2.5, 3])), { size: 0.4 }));
|
|
80
|
+
}
|
|
81
|
+
for (const x of [RAIL_FROM - 5, RAIL_TO]) {
|
|
82
|
+
out.push(S(k.slabOf({ x, y: -23, w: 5, d: 46, r: 1.2 }, 0, 7, P, 3, 0.5), { tone: "mid" }));
|
|
83
|
+
out.push(D([at([x + 2.5, -18, 7]), at([x + 2.5, 18, 7])], { size: 0.45 }));
|
|
84
|
+
}
|
|
85
|
+
return out.join("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function fixture() {
|
|
89
|
+
const out = [];
|
|
90
|
+
out.push(S(k.slabOf(MAG, 0, 11, P, 6, 0.8), { tone: "mid" }));
|
|
91
|
+
out.push(S(k.slabOf(k.insetPlan(MAG, 0.8), 11, MAG_H - 11, P, 6, 0.8), { tone: "mid" }));
|
|
92
|
+
out.push(L(k.planOutline(k.insetPlan(MAG, 3), MAG_H, P), { tone: "faint" }));
|
|
93
|
+
out.push(D([at([MAG.x + 5, MAG.y + 5, MAG_H]), at([MAG.x + 5, MAG.y + MAG.d - 5, MAG_H]), at([MAG.x + MAG.w - 5, MAG.y + 5, MAG_H])], { size: 0.45 }));
|
|
94
|
+
const knob = [MAG.x + 32, MAG.y + 28];
|
|
95
|
+
out.push(S(k.cylinder(knob[0], knob[1], 6, MAG_H, 4, P, 32), { tone: "mid" }));
|
|
96
|
+
out.push(L(k.knurl(knob[0], knob[1], 6, MAG_H, MAG_H + 4, 36, P), { tone: "lo" }));
|
|
97
|
+
out.push(L(k.lineOnTop([knob[0], knob[1]], [knob[0] + 6, knob[1]], MAG_H + 4, P), { tone: "hi" }));
|
|
98
|
+
out.push(D([at([knob[0], knob[1], MAG_H + 4])], { size: 0.5, tone: "hi" }));
|
|
99
|
+
out.push(S(k.cylinder(POST[0], POST[1], 5, MAG_H, 4, P, 28), { tone: "mid" }));
|
|
100
|
+
const clamp = { x: POST[0] - 7, y: POST[1] - 7, w: 14, d: 14, r: 2.5 };
|
|
101
|
+
out.push(S(k.cylinder(POST[0], POST[1], 3.2, MAG_H + 4, CLAMP_Z - 4 - MAG_H - 4, P, 28), { tone: "mid", crease: "none" }));
|
|
102
|
+
out.push(L([60, 80].map((z) => k.sideArc(POST[0], POST[1], 3.2, z, P, 16)).join(""), { tone: "faint" }));
|
|
103
|
+
out.push(S(k.slabOf(clamp, CLAMP_Z - 4, 12, P, 4, 0.6), { tone: "mid" }));
|
|
104
|
+
out.push(S(k.cylinder(POST[0], POST[1], 3.2, CLAMP_Z + 8, POST_TOP - CLAMP_Z - 8, P, 28), { tone: "mid", crease: "none" }));
|
|
105
|
+
out.push(S(k.cylinder(POST[0], POST[1], 3.8, POST_TOP, 2, P, 28), { tone: "mid" }));
|
|
106
|
+
const arm = { x: POST[0] + 7, y: -3, w: DIAL[0] - DIAL_R - POST[0] - 7 + 2, d: 6, r: 2 };
|
|
107
|
+
out.push(S(k.slabOf(arm, CLAMP_Z, 4, P, 4, 0.5), { tone: "mid" }));
|
|
108
|
+
out.push(D([at([arm.x + 4, 0, CLAMP_Z + 4]), at([arm.x + arm.w - 5, 0, CLAMP_Z + 4])], { size: 0.45 }));
|
|
109
|
+
const thumb = [clamp.x + 7, clamp.y + clamp.d + 3];
|
|
110
|
+
out.push(S(k.slabOf({ x: thumb[0] - 1.4, y: clamp.y + clamp.d, w: 2.8, d: 3, r: 0.8 }, CLAMP_Z + 1, 4, P, 2), { tone: "mid", crease: "none" }));
|
|
111
|
+
out.push(S(k.cylinder(thumb[0], thumb[1] + 1.4, 2.6, CLAMP_Z, 6, P, 20), { tone: "mid" }));
|
|
112
|
+
out.push(L(k.knurl(thumb[0], thumb[1] + 1.4, 2.6, CLAMP_Z, CLAMP_Z + 6, 18, P), { tone: "lo" }));
|
|
113
|
+
return out.join("");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function carriage() {
|
|
117
|
+
const out = [`<path class="iso-halo" d="${k.haloOf(k.insetPlan(CARRIAGE, 3), CARRIAGE_Z, 0.1, P)}" style="filter:blur(4px)"/>`];
|
|
118
|
+
out.push(S(k.slabOf(CARRIAGE, CARRIAGE_Z, CARRIAGE_TOP - CARRIAGE_Z, P, 5, 0.6), { tone: "mid" }));
|
|
119
|
+
out.push(D([-22, -8, 8, 22].map((x) => at([x, CARRIAGE.y + CARRIAGE.d - 2.6, CARRIAGE_TOP])), { size: 0.4, tone: "lo" }));
|
|
120
|
+
out.push(L(k.lineOnTop([CARRIAGE.x, -15.5], [CARRIAGE.x + CARRIAGE.w, -15.5], CARRIAGE_TOP, P), { tone: "faint" }));
|
|
121
|
+
out.push(L(k.lineOnTop([CARRIAGE.x, 15.5], [CARRIAGE.x + CARRIAGE.w, 15.5], CARRIAGE_TOP, P), { tone: "faint" }));
|
|
122
|
+
BLOCKS.forEach((height, index) => {
|
|
123
|
+
const plan = { x: -24 + STEP * index, y: -12, w: BLOCK_W, d: 24, r: 1 };
|
|
124
|
+
out.push(S(k.slabOf(plan, CARRIAGE_TOP, height, P, 3, 0.5), { tone: "hi", crease: "lo" }));
|
|
125
|
+
out.push(L(k.planOutline(k.insetPlan(plan, 2), CARRIAGE_TOP + height, P, 3), { tone: "faint" }));
|
|
126
|
+
});
|
|
127
|
+
const handle = [CARRIAGE.x + CARRIAGE.w - 8, CARRIAGE.y + CARRIAGE.d + 3];
|
|
128
|
+
out.push(S(k.slabOf({ x: handle[0] - 2, y: CARRIAGE.y + CARRIAGE.d - 1, w: 4, d: 4, r: 1 }, CARRIAGE_Z + 1, 3, P, 2), { tone: "mid", crease: "none" }));
|
|
129
|
+
out.push(S(k.cylinder(handle[0], handle[1] + 1, 3, CARRIAGE_Z, 5, P, 20), { tone: "mid" }));
|
|
130
|
+
out.push(L(k.knurl(handle[0], handle[1] + 1, 3, CARRIAGE_Z, CARRIAGE_Z + 5, 18, P), { tone: "lo" }));
|
|
131
|
+
return out.join("");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function plunger() {
|
|
135
|
+
const out = [];
|
|
136
|
+
out.push(S(k.cylinder(DIAL[0], DIAL[1], TIP_R, 0, 2.4, P, 20), { tone: "hi", lit: true }));
|
|
137
|
+
out.push(S(k.cylinder(DIAL[0], DIAL[1], 1.4, 2.4, 50, P, 16), { tone: "hi", crease: "none", lit: true }));
|
|
138
|
+
return out.join("");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function dial() {
|
|
142
|
+
const out = [];
|
|
143
|
+
const [x, y] = DIAL;
|
|
144
|
+
out.push(S(k.cylinder(x, y, 2.9, 54, DIAL_Z - 54, P, 20), { tone: "mid" }));
|
|
145
|
+
out.push(L(k.sideArc(x, y, 2.9, 60, P, 12), { tone: "lo" }));
|
|
146
|
+
out.push(S(k.slabOf({ x: x - DIAL_R - 6, y: -4, w: 8, d: 8, r: 1.5 }, DIAL_Z + 2, CLAMP_Z - DIAL_Z - 2, P, 3, 0.4), { tone: "mid" }));
|
|
147
|
+
out.push(S(k.cylinder(x, y, DIAL_R, DIAL_Z, DIAL_H, P, 64, 0.8), { tone: "hi", crease: "lo" }));
|
|
148
|
+
out.push(L(k.knurl(x, y, DIAL_R, DIAL_TOP - 3.4, DIAL_TOP, 84, P, 0.3), { tone: "lo" }));
|
|
149
|
+
out.push(L(k.sideArc(x, y, DIAL_R, DIAL_TOP - 3.4, P, 40), { tone: "faint" }));
|
|
150
|
+
out.push(L(k.ring(x, y, DIAL_R - 2.2, DIAL_TOP, P, 64), { tone: "lo" }));
|
|
151
|
+
const ticks = k.radialTicks(x, y, DIAL_R - 3, 50, 5, DIAL_TOP, [1.3, 2.8], P);
|
|
152
|
+
out.push(L(ticks.minor, { tone: "lo" }), L(ticks.major, { tone: "mid" }));
|
|
153
|
+
out.push(S(k.slabOf({ x: x + DIAL_R - 1, y: -2.4, w: 5, d: 4.8, r: 1.2 }, DIAL_Z + 2, 5, P, 3), { tone: "mid" }));
|
|
154
|
+
out.push(S(k.slabOf({ x: x + DIAL_R + 3.6, y: -3.2, w: 2.6, d: 6.4, r: 1.2 }, DIAL_Z + 1.2, 6.6, P, 3), { tone: "mid" }));
|
|
155
|
+
out.push(L(k.ring(x, y, 1.6, DIAL_TOP, P, 16), { tone: "mid" }));
|
|
156
|
+
out.push(k.faceTextSvg(k.topMatrix([x, y + 9], DIAL_TOP, P, "x"), "0.01", { size: 2.4, tone: "lo", anchor: "middle" }));
|
|
157
|
+
return out.join("");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const needleAt = (turn) => {
|
|
161
|
+
const angle = turn * Math.PI * 2;
|
|
162
|
+
const [x, y] = DIAL;
|
|
163
|
+
return k.lineOnTop([x - Math.sin(angle) * 3.4, y + Math.cos(angle) * 3.4], [x + Math.sin(angle) * (DIAL_R - 4), y - Math.cos(angle) * (DIAL_R - 4)], DIAL_TOP + 0.2, P);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const startX = STOPS[0];
|
|
167
|
+
const svg = k.figureSvg({
|
|
168
|
+
width: WIDTH,
|
|
169
|
+
height: HEIGHT,
|
|
170
|
+
label:
|
|
171
|
+
"A dial indicator on a surface plate. A magnetic base holds a post and an arm; the arm carries a dial whose plunger rests on three gauge blocks, 18, 14 and 10 tall, on a carriage that slides along two rails. Wherever the carriage stops, the plunger rises to the block under it and the needle turns by the same amount.",
|
|
172
|
+
body: [
|
|
173
|
+
plate(),
|
|
174
|
+
rails(),
|
|
175
|
+
fixture(),
|
|
176
|
+
`<g data-part="carriage" transform="${k.translateAlong(P, [startX, 0, 0])}">${carriage()}</g>`,
|
|
177
|
+
`<g data-part="plunger" transform="${k.translateAlong(P, [0, 0, CARRIAGE_TOP + BLOCKS[0]])}">${plunger()}</g>`,
|
|
178
|
+
dial(),
|
|
179
|
+
`<path data-part="needle" class="iso-line" data-tone="lit" data-free="start" d="${needleAt(BLOCKS[0] / PER_TURN)}" style="stroke-width:1.1"/>`,
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const live = `
|
|
184
|
+
const P = ${JSON.stringify(P)};
|
|
185
|
+
const BLOCKS = ${JSON.stringify(BLOCKS)};
|
|
186
|
+
const DIAL = ${JSON.stringify(DIAL)};
|
|
187
|
+
const DIAL_R = ${DIAL_R}, DIAL_TOP = ${DIAL_TOP}, PER_TURN = ${PER_TURN}, STEP = ${STEP}, BLOCK_W = ${BLOCK_W}, TIP_R = ${TIP_R}, BASE = ${CARRIAGE_TOP};
|
|
188
|
+
const REACH = ${JSON.stringify(REACH)};
|
|
189
|
+
const stage = document.querySelector(".iso-stage");
|
|
190
|
+
const svg = stage.querySelector("svg");
|
|
191
|
+
const carriage = svg.querySelector('[data-part="carriage"]');
|
|
192
|
+
const plunger = svg.querySelector('[data-part="plunger"]');
|
|
193
|
+
const needle = svg.querySelector('[data-part="needle"]');
|
|
194
|
+
const readout = document.querySelector("[data-readout]");
|
|
195
|
+
const still = matchMedia("(prefers-reduced-motion: reduce)");
|
|
196
|
+
const stops = ${JSON.stringify(STOPS)};
|
|
197
|
+
const clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
198
|
+
const heightUnder = (cx) => {
|
|
199
|
+
let top = BASE;
|
|
200
|
+
BLOCKS.forEach((height, index) => {
|
|
201
|
+
const from = cx - 24 + STEP * index, to = from + BLOCK_W;
|
|
202
|
+
if (to > DIAL[0] - TIP_R && from < DIAL[0] + TIP_R) top = Math.max(top, BASE + height);
|
|
203
|
+
});
|
|
204
|
+
return top;
|
|
205
|
+
};
|
|
206
|
+
const blockUnder = (cx) => BLOCKS.findIndex((_, index) => { const from = cx - 24 + STEP * index; return DIAL[0] >= from && DIAL[0] <= from + BLOCK_W; });
|
|
207
|
+
const needleAt = ${needleAt.toString().replace("k.lineOnTop", "lineOnTop")};
|
|
208
|
+
let x = ${startX}, target = x, tip = { at: BASE + BLOCKS[0], rate: 0 };
|
|
209
|
+
let frame = 0, last = 0, clock = 0, touring = true, visible = false, idleTimer = 0;
|
|
210
|
+
const tourAt = (t) => stops[Math.floor(t / 2.2) % stops.length];
|
|
211
|
+
function draw() {
|
|
212
|
+
carriage.setAttribute("transform", translateAlong(P, [x, 0, 0]));
|
|
213
|
+
plunger.setAttribute("transform", translateAlong(P, [0, 0, tip.at]));
|
|
214
|
+
needle.setAttribute("d", needleAt((tip.at - BASE) / PER_TURN));
|
|
215
|
+
const index = blockUnder(x);
|
|
216
|
+
const text = (index < 0 ? "between blocks" : "block " + (index + 1)) + " · reads " + (tip.at - BASE).toFixed(1) + " px";
|
|
217
|
+
if (readout.textContent !== text) readout.textContent = text;
|
|
218
|
+
stage.setAttribute("aria-valuenow", String(Math.round(tip.at - BASE)));
|
|
219
|
+
stage.setAttribute("aria-valuetext", text);
|
|
220
|
+
}
|
|
221
|
+
function tick(now) {
|
|
222
|
+
const dt = last ? Math.min((now - last) / 1000, 1 / 30) : 1 / 60;
|
|
223
|
+
last = now;
|
|
224
|
+
const calm = still.matches;
|
|
225
|
+
if (touring) { clock += dt; target = tourAt(clock); }
|
|
226
|
+
x = calm ? target : x + (target - x) * (1 - Math.exp(-dt / 0.28));
|
|
227
|
+
if (Math.abs(target - x) < 0.01) x = target;
|
|
228
|
+
const want = heightUnder(x);
|
|
229
|
+
const stiff = 420, drag = calm ? 2 * Math.sqrt(420) : 26;
|
|
230
|
+
for (let i = 0; i < 4; i++) { tip.rate += ((want - tip.at) * stiff - tip.rate * drag) * dt / 4; tip.at += tip.rate * dt / 4; }
|
|
231
|
+
if (Math.abs(want - tip.at) < 0.005 && Math.abs(tip.rate) < 0.005) { tip.at = want; tip.rate = 0; }
|
|
232
|
+
draw();
|
|
233
|
+
const settled = !touring && x === target && tip.rate === 0 && tip.at === want;
|
|
234
|
+
frame = visible && !settled ? requestAnimationFrame(tick) : 0;
|
|
235
|
+
}
|
|
236
|
+
const run = () => { if (!frame && visible) { last = 0; frame = requestAnimationFrame(tick); } };
|
|
237
|
+
const steer = (next) => { touring = false; target = clamp(next, REACH[0], REACH[1]); clearTimeout(idleTimer); idleTimer = setTimeout(() => { touring = !still.matches; clock = 0; run(); }, 3600); run(); };
|
|
238
|
+
new IntersectionObserver((entries) => { visible = entries[entries.length - 1].isIntersecting; if (visible) run(); }, { rootMargin: "120px 0px" }).observe(stage);
|
|
239
|
+
if (still.matches) touring = false;
|
|
240
|
+
stage.addEventListener("pointermove", (event) => {
|
|
241
|
+
const box = svg.getBoundingClientRect();
|
|
242
|
+
const share = clamp(((event.clientX - box.left) / box.width - 0.25) / 0.5, 0, 1);
|
|
243
|
+
steer(REACH[0] + share * (REACH[1] - REACH[0]));
|
|
244
|
+
});
|
|
245
|
+
stage.addEventListener("pointerleave", () => steer(target));
|
|
246
|
+
stage.addEventListener("keydown", (event) => {
|
|
247
|
+
const here = stops.reduce((best, stop, index) => (Math.abs(stop - target) < Math.abs(stops[best] - target) ? index : best), 0);
|
|
248
|
+
let to = null;
|
|
249
|
+
if (event.key === "ArrowRight" || event.key === "ArrowUp") to = Math.max(0, here - 1);
|
|
250
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowDown") to = Math.min(stops.length - 1, here + 1);
|
|
251
|
+
if (event.key === "Home") to = 0;
|
|
252
|
+
if (event.key === "End") to = stops.length - 1;
|
|
253
|
+
if (to === null) return;
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
steer(stops[to]);
|
|
256
|
+
});
|
|
257
|
+
draw();
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
const body = k.plateHtml({
|
|
261
|
+
fig: "Fig 1",
|
|
262
|
+
title: "Dial indicator",
|
|
263
|
+
hint: "Hover to slide the carriage",
|
|
264
|
+
readout: `block 1 · reads ${BLOCKS[0].toFixed(1)} px`,
|
|
265
|
+
keys: [
|
|
266
|
+
{ mark: "lit", label: "Plunger and needle: what moves when the height changes" },
|
|
267
|
+
{ mark: "raised", label: "Three gauge blocks, 18, 14 and 10 tall" },
|
|
268
|
+
],
|
|
269
|
+
caption:
|
|
270
|
+
"A dial indicator on a surface plate. Slide the carriage and the plunger rides up or down onto the block beneath it; one turn of the needle is 20 px of travel, so every block reads its own height.",
|
|
271
|
+
body: `<div class="iso-stage" tabindex="0" role="slider" aria-label="Dial indicator: arrow keys move the carriage from block to block" aria-valuemin="0" aria-valuemax="18" aria-valuenow="18">${svg}</div>`,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
writeFileSync(join(HERE, theme === "light" ? "dial-indicator-light.html" : "dial-indicator.html"), k.pageHtml({ title: "Dial indicator", theme, body, script: kitScript() + live }));
|
|
275
|
+
console.log("wrote", theme === "light" ? "dial-indicator-light.html" : "dial-indicator.html");
|