@aitrien/motifspec 1.13.0 → 1.13.3
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/CHANGELOG.md +275 -257
- package/LICENSE +21 -21
- package/README.md +128 -150
- package/dist/backdrop.html +1 -1
- package/dist/chroma.css +36 -36
- package/dist/codex.css +1145 -1145
- package/dist/codex.js +126 -126
- package/dist/contrast.md +101 -101
- package/dist/fonts.css +3 -3
- package/dist/icons.svg +18 -18
- package/dist/plate.html +1 -1
- package/dist/plate.js +292 -292
- package/dist/theme-init.js +1 -1
- package/dist/tokens.css +466 -466
- package/dist/tokens.js +3330 -3330
- package/dist/tokens.json +3330 -3330
- package/docs/components/_template.md +37 -37
- package/docs/components/accession-label.md +106 -106
- package/docs/components/backdrop.md +83 -83
- package/docs/components/button-and-link.md +87 -87
- package/docs/components/callout.md +75 -75
- package/docs/components/code-block.md +109 -109
- package/docs/components/data-table.md +73 -73
- package/docs/components/figure.md +68 -68
- package/docs/components/heading-anchor.md +76 -76
- package/docs/components/icon.md +63 -63
- package/docs/components/ornament.md +77 -77
- package/docs/components/pagination.md +78 -78
- package/docs/components/post-list-item.md +91 -91
- package/docs/components/project-card.md +82 -82
- package/docs/components/prose.md +122 -122
- package/docs/components/site-footer.md +99 -99
- package/docs/components/site-header.md +133 -133
- package/docs/components/specimen-plate.md +92 -92
- package/docs/components/table-of-contents.md +83 -83
- package/docs/components/tag.md +70 -70
- package/docs/components/tooltip.md +67 -67
- package/docs/hugo.md +132 -120
- package/docs/syntax-highlighting.md +55 -55
- package/docs/templates.md +21 -30
- package/fonts/OFL-jetbrains-mono.txt +93 -93
- package/fonts/OFL-national-park.txt +93 -93
- package/fonts/OFL-yarndings-12.txt +93 -93
- package/icons/LICENSE +202 -202
- package/icons/manifest.json +75 -75
- package/layouts/_markup/render-blockquote.html +30 -30
- package/layouts/_markup/render-codeblock.html +18 -18
- package/layouts/_markup/render-heading.html +2 -2
- package/layouts/_markup/render-image.html +14 -14
- package/layouts/_markup/render-link.html +14 -14
- package/layouts/_markup/render-table.html +31 -31
- package/layouts/_partials/motifspec/accession/id.html +2 -2
- package/layouts/_partials/motifspec/accession/label.html +18 -18
- package/layouts/_partials/motifspec/accession/status.html +2 -2
- package/layouts/_partials/motifspec/article.html +24 -24
- package/layouts/_partials/motifspec/back-link.html +2 -2
- package/layouts/_partials/motifspec/body-end.html +5 -5
- package/layouts/_partials/motifspec/body-start.html +5 -5
- package/layouts/_partials/motifspec/figure.html +14 -14
- package/layouts/_partials/motifspec/filed-under.html +5 -5
- package/layouts/_partials/motifspec/h2s.html +7 -7
- package/layouts/_partials/motifspec/head.html +15 -15
- package/layouts/_partials/motifspec/pagination-entries.html +34 -34
- package/layouts/_partials/motifspec/pagination-pages.html +30 -30
- package/layouts/_partials/motifspec/post-list-item.html +15 -15
- package/layouts/_partials/motifspec/project-card.html +23 -23
- package/layouts/_partials/motifspec/section-heading.html +3 -3
- package/layouts/_partials/motifspec/site-header.html +27 -27
- package/layouts/_partials/motifspec/table-of-contents.html +21 -21
- package/layouts/_partials/motifspec/tag-filter.html +22 -22
- package/layouts/_partials/motifspec/tags.html +8 -8
- package/layouts/_partials/motifspec/tokens.html +9 -9
- package/package.json +68 -68
- package/scripts/check.mjs +153 -153
- package/scripts/lib.mjs +176 -176
- package/tokens/README.md +3 -3
- package/tokens/border.json +63 -63
- package/tokens/color.json +1902 -1902
- package/tokens/motion.json +207 -207
- package/tokens/radius.json +53 -53
- package/tokens/size.json +378 -378
- package/tokens/space.json +289 -289
- package/tokens/typography.json +859 -859
package/dist/plate.js
CHANGED
|
@@ -1,292 +1,292 @@
|
|
|
1
|
-
// Specimen plate and backdrop: a contour survey of simplex-noise terrain.
|
|
2
|
-
// The build runs contours() once (t = 0) to write the static SVG, so the plate and the backdrop are complete
|
|
3
|
-
// without JavaScript.
|
|
4
|
-
// In the browser, mount() redraws the same contours as the terrain drifts through time. Same code, same seed,
|
|
5
|
-
// so the first animated frame matches the static one exactly.
|
|
6
|
-
(function (g) {
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
// ------------------------------------------------------------ seeded 3D simplex noise (after Gustavson)
|
|
10
|
-
function rng(seed) {
|
|
11
|
-
return function () {
|
|
12
|
-
seed |= 0; seed = (seed + 0x6d2b79f5) | 0;
|
|
13
|
-
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
14
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
15
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
var GRAD = [1,1,0, -1,1,0, 1,-1,0, -1,-1,0, 1,0,1, -1,0,1, 1,0,-1, -1,0,-1, 0,1,1, 0,-1,1, 0,1,-1, 0,-1,-1];
|
|
19
|
-
var F3 = 1 / 3, G3 = 1 / 6;
|
|
20
|
-
function simplex(seed) {
|
|
21
|
-
var r = rng(seed), p = new Uint8Array(256), perm = new Uint8Array(512), pm = new Uint8Array(512), i;
|
|
22
|
-
for (i = 0; i < 256; i++) p[i] = i;
|
|
23
|
-
for (i = 255; i > 0; i--) { var k = Math.floor(r() * (i + 1)), s = p[i]; p[i] = p[k]; p[k] = s; }
|
|
24
|
-
for (i = 0; i < 512; i++) { perm[i] = p[i & 255]; pm[i] = (perm[i] % 12) * 3; }
|
|
25
|
-
return function (x, y, z) {
|
|
26
|
-
var s = (x + y + z) * F3, i = Math.floor(x + s), j = Math.floor(y + s), k = Math.floor(z + s);
|
|
27
|
-
var t = (i + j + k) * G3, x0 = x - i + t, y0 = y - j + t, z0 = z - k + t;
|
|
28
|
-
var i1, j1, k1, i2, j2, k2;
|
|
29
|
-
if (x0 >= y0) {
|
|
30
|
-
if (y0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; }
|
|
31
|
-
else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; }
|
|
32
|
-
else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; }
|
|
33
|
-
} else {
|
|
34
|
-
if (y0 < z0) { i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1; }
|
|
35
|
-
else if (x0 < z0) { i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1; }
|
|
36
|
-
else { i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0; }
|
|
37
|
-
}
|
|
38
|
-
var x1 = x0 - i1 + G3, y1 = y0 - j1 + G3, z1 = z0 - k1 + G3;
|
|
39
|
-
var x2 = x0 - i2 + 2 * G3, y2 = y0 - j2 + 2 * G3, z2 = z0 - k2 + 2 * G3;
|
|
40
|
-
var x3 = x0 - 1 + 0.5, y3 = y0 - 1 + 0.5, z3 = z0 - 1 + 0.5;
|
|
41
|
-
var ii = i & 255, jj = j & 255, kk = k & 255, n = 0, tt, gi;
|
|
42
|
-
tt = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
|
|
43
|
-
if (tt > 0) { gi = pm[ii + perm[jj + perm[kk]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x0 + GRAD[gi + 1] * y0 + GRAD[gi + 2] * z0); }
|
|
44
|
-
tt = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
45
|
-
if (tt > 0) { gi = pm[ii + i1 + perm[jj + j1 + perm[kk + k1]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x1 + GRAD[gi + 1] * y1 + GRAD[gi + 2] * z1); }
|
|
46
|
-
tt = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
47
|
-
if (tt > 0) { gi = pm[ii + i2 + perm[jj + j2 + perm[kk + k2]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x2 + GRAD[gi + 1] * y2 + GRAD[gi + 2] * z2); }
|
|
48
|
-
tt = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
49
|
-
if (tt > 0) { gi = pm[ii + 1 + perm[jj + 1 + perm[kk + 1]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x3 + GRAD[gi + 1] * y3 + GRAD[gi + 2] * z3); }
|
|
50
|
-
return 32 * n; // about -1..1
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ------------------------------------------------------------ the survey
|
|
55
|
-
var DEFAULTS = {
|
|
56
|
-
seed: 3, // which terrain
|
|
57
|
-
size: 240, // viewBox units
|
|
58
|
-
cells: 32, // marching-squares grid per side
|
|
59
|
-
scale: 1.4, // noise features across the plate
|
|
60
|
-
octaves: 2,
|
|
61
|
-
massif: 1.1, // how strongly the terrain rises toward the centre
|
|
62
|
-
levels: 14, // contour lines
|
|
63
|
-
index: 5 // every nth line is an index contour
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
function survey(opts) {
|
|
67
|
-
var o = {}, k;
|
|
68
|
-
for (k in DEFAULTS) o[k] = opts && opts[k] != null ? opts[k] : DEFAULTS[k];
|
|
69
|
-
var noise = simplex(o.seed), N = o.cells, S = o.size, cell = S / N;
|
|
70
|
-
var lo = -0.45, hi = 1.75, step = (hi - lo) / (o.levels + 1);
|
|
71
|
-
var levels = [];
|
|
72
|
-
for (k = 1; k <= o.levels; k++) levels.push(lo + k * step);
|
|
73
|
-
var h = new Float32Array((N + 1) * (N + 1));
|
|
74
|
-
|
|
75
|
-
function height(t) {
|
|
76
|
-
for (var j = 0; j <= N; j++) for (var i = 0; i <= N; i++) {
|
|
77
|
-
var x = i / N, y = j / N, f = o.scale, a = 1, v = 0, norm = 0;
|
|
78
|
-
for (var oc = 0; oc < o.octaves; oc++) { v += a * noise(x * f, y * f, t + oc * 13.7); norm += a; a *= 0.35; f *= 2.3; }
|
|
79
|
-
var dx = x - 0.5, dy = y - 0.5, r = Math.min(1, Math.sqrt(dx * dx + dy * dy) / 0.62);
|
|
80
|
-
h[j * (N + 1) + i] = v / norm + o.massif * (1 - r * r);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Marching squares for one level. Points sit on cell edges and are numbered by edge, so neighbouring cells
|
|
85
|
-
// share them exactly. Horizontal edges come first (N per row, N + 1 rows), then vertical ones.
|
|
86
|
-
var E = 2 * N * (N + 1), px = new Float32Array(E), py = new Float32Array(E), lk = new Int32Array(2 * E), stamp = new Int32Array(E), pass = 0;
|
|
87
|
-
function trace(level) {
|
|
88
|
-
pass++;
|
|
89
|
-
var touched = [];
|
|
90
|
-
function pt(i, j, vert) {
|
|
91
|
-
var id = vert ? N * (N + 1) + j * (N + 1) + i : j * N + i;
|
|
92
|
-
if (stamp[id] !== pass) {
|
|
93
|
-
stamp[id] = pass; lk[2 * id] = -1; lk[2 * id + 1] = -1; touched.push(id);
|
|
94
|
-
var a = h[j * (N + 1) + i], b = vert ? h[(j + 1) * (N + 1) + i] : h[j * (N + 1) + i + 1], u = (level - a) / (b - a);
|
|
95
|
-
px[id] = vert ? i * cell : (i + u) * cell; py[id] = vert ? (j + u) * cell : j * cell;
|
|
96
|
-
}
|
|
97
|
-
return id;
|
|
98
|
-
}
|
|
99
|
-
function link(a, b) { lk[2 * a + (lk[2 * a] === -1 ? 0 : 1)] = b; lk[2 * b + (lk[2 * b] === -1 ? 0 : 1)] = a; }
|
|
100
|
-
for (var j = 0; j < N; j++) for (var i = 0; i < N; i++) {
|
|
101
|
-
var tl = h[j * (N + 1) + i], tr = h[j * (N + 1) + i + 1], br = h[(j + 1) * (N + 1) + i + 1], bl = h[(j + 1) * (N + 1) + i];
|
|
102
|
-
var c = (tl > level ? 8 : 0) | (tr > level ? 4 : 0) | (br > level ? 2 : 0) | (bl > level ? 1 : 0);
|
|
103
|
-
if (c === 0 || c === 15) continue;
|
|
104
|
-
var centre = (tl + tr + br + bl) / 4 > level;
|
|
105
|
-
switch (c) {
|
|
106
|
-
case 1: case 14: link(pt(i, j, 1), pt(i, j + 1, 0)); break;
|
|
107
|
-
case 2: case 13: link(pt(i, j + 1, 0), pt(i + 1, j, 1)); break;
|
|
108
|
-
case 3: case 12: link(pt(i, j, 1), pt(i + 1, j, 1)); break;
|
|
109
|
-
case 4: case 11: link(pt(i, j, 0), pt(i + 1, j, 1)); break;
|
|
110
|
-
case 6: case 9: link(pt(i, j, 0), pt(i, j + 1, 0)); break;
|
|
111
|
-
case 7: case 8: link(pt(i, j, 1), pt(i, j, 0)); break;
|
|
112
|
-
case 5:
|
|
113
|
-
if (centre) { link(pt(i, j, 1), pt(i, j, 0)); link(pt(i, j + 1, 0), pt(i + 1, j, 1)); }
|
|
114
|
-
else { link(pt(i, j, 1), pt(i, j + 1, 0)); link(pt(i, j, 0), pt(i + 1, j, 1)); }
|
|
115
|
-
break;
|
|
116
|
-
case 10:
|
|
117
|
-
if (centre) { link(pt(i, j, 0), pt(i + 1, j, 1)); link(pt(i, j, 1), pt(i, j + 1, 0)); }
|
|
118
|
-
else { link(pt(i, j, 1), pt(i, j, 0)); link(pt(i, j + 1, 0), pt(i + 1, j, 1)); }
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// Chain the segments into lines: open lines start at an end (one link), then whatever is left is a loop.
|
|
123
|
-
var lines = [], seen = pass * 2; // reuse stamp: a point is used once stamp[id] === -seen
|
|
124
|
-
function walk(start) {
|
|
125
|
-
var pts = [], prev = -1, cur = start;
|
|
126
|
-
while (cur !== -1 && stamp[cur] !== -seen) {
|
|
127
|
-
stamp[cur] = -seen; pts.push(px[cur], py[cur]);
|
|
128
|
-
var n0 = lk[2 * cur], n1 = lk[2 * cur + 1], next = n0 !== prev && n0 !== -1 && stamp[n0] !== -seen ? n0 : n1 !== prev && n1 !== -1 && stamp[n1] !== -seen ? n1 : -1;
|
|
129
|
-
prev = cur; cur = next;
|
|
130
|
-
}
|
|
131
|
-
var closed = lk[2 * prev] === start || lk[2 * prev + 1] === start;
|
|
132
|
-
return { pts: pts, closed: closed && pts.length > 4 };
|
|
133
|
-
}
|
|
134
|
-
var k, id;
|
|
135
|
-
for (k = 0; k < touched.length; k++) { id = touched[k]; if (stamp[id] === pass && lk[2 * id + 1] === -1) lines.push(walk(id)); }
|
|
136
|
-
for (k = 0; k < touched.length; k++) { id = touched[k]; if (stamp[id] === pass) lines.push(walk(id)); }
|
|
137
|
-
return lines;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Clean each traced line before drawing it. Marching squares puts two points almost on top of each other
|
|
141
|
-
// wherever a contour passes close to a grid corner; merging them removes the burrs that flicker as the
|
|
142
|
-
// terrain moves. Loops smaller than two cells, and edge stubs shorter than that, pop in and out, so they are dropped.
|
|
143
|
-
var minGap = cell * 0.4, minLoop = cell * 2;
|
|
144
|
-
function clean(ln) {
|
|
145
|
-
var p = ln.pts, n = p.length / 2, q = [p[0], p[1]], k, dx, dy;
|
|
146
|
-
for (k = 1; k < n; k++) {
|
|
147
|
-
dx = p[2 * k] - q[q.length - 2]; dy = p[2 * k + 1] - q[q.length - 1];
|
|
148
|
-
if (dx * dx + dy * dy >= minGap * minGap) q.push(p[2 * k], p[2 * k + 1]);
|
|
149
|
-
else if (!ln.closed && k === n - 1) { q[q.length - 2] = p[2 * k]; q[q.length - 1] = p[2 * k + 1]; } // keep the true end on the edge
|
|
150
|
-
}
|
|
151
|
-
if (ln.closed) {
|
|
152
|
-
dx = q[0] - q[q.length - 2]; dy = q[1] - q[q.length - 1];
|
|
153
|
-
if (q.length > 2 && dx * dx + dy * dy < minGap * minGap) q.length -= 2;
|
|
154
|
-
var x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
|
|
155
|
-
for (k = 0; k < q.length; k += 2) { x0 = Math.min(x0, q[k]); x1 = Math.max(x1, q[k]); y0 = Math.min(y0, q[k + 1]); y1 = Math.max(y1, q[k + 1]); }
|
|
156
|
-
if (q.length < 8 || Math.max(x1 - x0, y1 - y0) < minLoop) return null;
|
|
157
|
-
}
|
|
158
|
-
if (!ln.closed) { // stubs clipped by the plate's edge flicker the same way
|
|
159
|
-
var len = 0;
|
|
160
|
-
for (k = 2; k < q.length; k += 2) len += Math.sqrt((q[k] - q[k - 2]) * (q[k] - q[k - 2]) + (q[k + 1] - q[k - 1]) * (q[k + 1] - q[k - 1]));
|
|
161
|
-
if (len < minLoop) return null;
|
|
162
|
-
}
|
|
163
|
-
return q.length >= 4 ? { pts: q, closed: ln.closed } : null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Centripetal Catmull-Rom through the points (alpha 0.5), written as cubic Béziers rounded to 0.1 units.
|
|
167
|
-
// Unlike the uniform form, it never overshoots into a cusp or a loop when neighbouring points are unevenly spaced.
|
|
168
|
-
function r1(n) { return Math.round(n * 10) / 10; }
|
|
169
|
-
function path(lines) {
|
|
170
|
-
var d = [];
|
|
171
|
-
for (var l = 0; l < lines.length; l++) {
|
|
172
|
-
var ln = clean(lines[l]);
|
|
173
|
-
if (!ln) continue;
|
|
174
|
-
var p = ln.pts, n = p.length / 2, closed = ln.closed;
|
|
175
|
-
d.push('M', r1(p[0]), ' ', r1(p[1]));
|
|
176
|
-
var segs = closed ? n : n - 1;
|
|
177
|
-
for (var k = 0; k < segs; k++) {
|
|
178
|
-
var a = k === 0 ? (closed ? n - 1 : 0) : k - 1, b = k, c = k + 1 < n ? k + 1 : (closed ? 0 : n - 1), e = k + 2 < n ? k + 2 : (closed ? k + 2 - n : n - 1);
|
|
179
|
-
a *= 2; b *= 2; c *= 2; e *= 2;
|
|
180
|
-
var d1 = Math.pow((p[b] - p[a]) * (p[b] - p[a]) + (p[b + 1] - p[a + 1]) * (p[b + 1] - p[a + 1]), 0.25);
|
|
181
|
-
var d2 = Math.pow((p[c] - p[b]) * (p[c] - p[b]) + (p[c + 1] - p[b + 1]) * (p[c + 1] - p[b + 1]), 0.25);
|
|
182
|
-
var d3 = Math.pow((p[e] - p[c]) * (p[e] - p[c]) + (p[e + 1] - p[c + 1]) * (p[e + 1] - p[c + 1]), 0.25);
|
|
183
|
-
var c1x, c1y, c2x, c2y;
|
|
184
|
-
if (d1 < 1e-6) { c1x = p[b] + (p[c] - p[b]) / 3; c1y = p[b + 1] + (p[c + 1] - p[b + 1]) / 3; }
|
|
185
|
-
else {
|
|
186
|
-
var m1 = 3 * d1 * (d1 + d2), w1 = 2 * d1 * d1 + 3 * d1 * d2 + d2 * d2;
|
|
187
|
-
c1x = (d1 * d1 * p[c] - d2 * d2 * p[a] + w1 * p[b]) / m1; c1y = (d1 * d1 * p[c + 1] - d2 * d2 * p[a + 1] + w1 * p[b + 1]) / m1;
|
|
188
|
-
}
|
|
189
|
-
if (d3 < 1e-6) { c2x = p[c] + (p[b] - p[c]) / 3; c2y = p[c + 1] + (p[b + 1] - p[c + 1]) / 3; }
|
|
190
|
-
else {
|
|
191
|
-
var m2 = 3 * d3 * (d3 + d2), w2 = 2 * d3 * d3 + 3 * d3 * d2 + d2 * d2;
|
|
192
|
-
c2x = (d3 * d3 * p[b] - d2 * d2 * p[e] + w2 * p[c]) / m2; c2y = (d3 * d3 * p[b + 1] - d2 * d2 * p[e + 1] + w2 * p[c + 1]) / m2;
|
|
193
|
-
}
|
|
194
|
-
d.push('C', r1(c1x), ' ', r1(c1y), ' ', r1(c2x), ' ', r1(c2y), ' ', r1(p[c]), ' ', r1(p[c + 1]));
|
|
195
|
-
}
|
|
196
|
-
if (closed) d.push('Z');
|
|
197
|
-
}
|
|
198
|
-
return d.join('');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return {
|
|
202
|
-
options: o,
|
|
203
|
-
levels: levels,
|
|
204
|
-
// Path data for every level at time t (in noise units).
|
|
205
|
-
contours: function (t) {
|
|
206
|
-
height(t || 0);
|
|
207
|
-
return levels.map(function (lv, i) { return { level: i + 1, index: (i + 1) % o.index === 0, d: path(trace(lv)) }; });
|
|
208
|
-
}
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ------------------------------------------------------------ static markup (used by the build)
|
|
213
|
-
// kind 'backdrop' draws the same survey full-bleed behind a page (docs/components/backdrop.md): sliced to cover
|
|
214
|
-
// the viewport instead of fitted into the plate's square.
|
|
215
|
-
function markup(opts, kind) {
|
|
216
|
-
var bg = kind === 'backdrop';
|
|
217
|
-
var s = survey(opts), S = s.options.size, q = S / 4;
|
|
218
|
-
var grid = [q, 2 * q, 3 * q].map(function (v) { return 'M' + v + ' 0V' + S + 'M0 ' + v + 'H' + S; }).join('');
|
|
219
|
-
var paths = s.contours(0).map(function (c, i) {
|
|
220
|
-
return '<path class="cx-plate__line' + (c.index ? ' cx-plate__line--index' : '') + '" style="--i: ' + i + '" pathLength="1" d="' + c.d + '"></path>';
|
|
221
|
-
}).join('');
|
|
222
|
-
return '<div class="' + (bg ? 'cx-backdrop' : 'cx-plate') + '" aria-hidden="true" data-plate-seed="' + s.options.seed + '">' +
|
|
223
|
-
'<svg class="cx-plate__map" viewBox="0 0 ' + S + ' ' + S + '"' + (bg ? ' preserveAspectRatio="xMidYMid slice"' : '') + ' focusable="false">' +
|
|
224
|
-
'<path class="cx-plate__grid" d="' + grid + '"></path>' + paths + '</svg></div>';
|
|
225
|
-
}
|
|
226
|
-
function backdrop(opts) { return markup(opts, 'backdrop'); }
|
|
227
|
-
|
|
228
|
-
// ------------------------------------------------------------ animation (browser only)
|
|
229
|
-
function mount(root) {
|
|
230
|
-
var doc = root.ownerDocument, win = doc.defaultView;
|
|
231
|
-
// A plate's pause control sits in its frame; the backdrop's sits in the site header.
|
|
232
|
-
var toggle = root.classList.contains('cx-backdrop')
|
|
233
|
-
? doc.querySelector('.cx-backdrop__pause')
|
|
234
|
-
: (root.closest('.cx-plate-frame') || root.parentElement).querySelector('.cx-plate__pause');
|
|
235
|
-
var input = toggle && toggle.querySelector('input');
|
|
236
|
-
// The backdrop's pause is a reader preference, like the theme: it is stored under cx-backdrop ("paused", or no key
|
|
237
|
-
// for running) and restored on every page. A plate's pause belongs to that plate and isn't kept.
|
|
238
|
-
if (input && root.classList.contains('cx-backdrop')) {
|
|
239
|
-
try { if (win.localStorage.getItem('cx-backdrop') === 'paused') input.checked = true; } catch (e) { /* storage blocked */ }
|
|
240
|
-
input.addEventListener('change', function () {
|
|
241
|
-
try { if (input.checked) win.localStorage.setItem('cx-backdrop', 'paused'); else win.localStorage.removeItem('cx-backdrop'); }
|
|
242
|
-
catch (e) { /* storage blocked: the pause still applies to this page */ }
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
var paths = root.querySelectorAll('.cx-plate__line');
|
|
246
|
-
var reduce = win.matchMedia('(prefers-reduced-motion: reduce)');
|
|
247
|
-
function ms(name) { // durations come from the tokens, through custom properties on the plate
|
|
248
|
-
var v = win.getComputedStyle(root).getPropertyValue(name).trim();
|
|
249
|
-
return v.endsWith('ms') ? parseFloat(v) : parseFloat(v) * 1000;
|
|
250
|
-
}
|
|
251
|
-
var drift = ms('--cx-plate-drift'), delay = ms('--cx-plate-draw');
|
|
252
|
-
var s = survey({ seed: Number(root.getAttribute('data-plate-seed')) || DEFAULTS.seed });
|
|
253
|
-
var backdrop = root.classList.contains('cx-backdrop');
|
|
254
|
-
// The drift never starts at full speed: from rest its speed eases up over one draw duration, on load and again
|
|
255
|
-
// after a pause, a hidden tab or scrolling back into view. Pausing still stops it at once.
|
|
256
|
-
var elapsed = 0, last = null, raf = 0, frames = 0, visible = true, ready = false, warm = 0;
|
|
257
|
-
|
|
258
|
-
function running() { return ready && visible && !doc.hidden && !reduce.matches && !(input && input.checked); }
|
|
259
|
-
function tick(now) {
|
|
260
|
-
raf = 0;
|
|
261
|
-
if (!running()) { last = null; warm = 0; return; }
|
|
262
|
-
if (last !== null) {
|
|
263
|
-
var dt = Math.min(now - last, delay / 16); // a long gap (background tab) never jumps the terrain
|
|
264
|
-
warm = Math.min(1, warm + dt / delay);
|
|
265
|
-
elapsed += dt * warm * warm * (3 - 2 * warm); // speed follows a smoothstep from 0 to full
|
|
266
|
-
}
|
|
267
|
-
last = now;
|
|
268
|
-
// Redraw on every second frame: the terrain moves so little per frame that this looks the same at half the cost.
|
|
269
|
-
if ((frames++ & 1) === 0) s.contours(elapsed / drift).forEach(function (c, i) { paths[i].setAttribute('d', c.d); });
|
|
270
|
-
raf = win.requestAnimationFrame(tick);
|
|
271
|
-
}
|
|
272
|
-
function update() {
|
|
273
|
-
if (toggle) toggle.hidden = reduce.matches;
|
|
274
|
-
if (running() && !raf) raf = win.requestAnimationFrame(tick);
|
|
275
|
-
}
|
|
276
|
-
if (input) input.addEventListener('change', update);
|
|
277
|
-
if (reduce.addEventListener) reduce.addEventListener('change', update);
|
|
278
|
-
doc.addEventListener('visibilitychange', update);
|
|
279
|
-
if ('IntersectionObserver' in win) new win.IntersectionObserver(function (e) { visible = e[0].isIntersecting; update(); }).observe(root);
|
|
280
|
-
// A plate draws its lines in first; the backdrop has no draw-in, so it starts as soon as the script runs.
|
|
281
|
-
if (backdrop) ready = true; else win.setTimeout(function () { ready = true; update(); }, delay);
|
|
282
|
-
update();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
g.cxPlate = { survey: survey, markup: markup, backdrop: backdrop, mount: mount, defaults: DEFAULTS };
|
|
286
|
-
|
|
287
|
-
// In a browser, start every plate and backdrop on the page. Without this script the static contours stay as they are.
|
|
288
|
-
if (typeof document !== 'undefined' && document.querySelectorAll) {
|
|
289
|
-
var start = function () { Array.prototype.forEach.call(document.querySelectorAll('.cx-plate[data-plate-seed], .cx-backdrop[data-plate-seed]'), mount); };
|
|
290
|
-
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
|
|
291
|
-
}
|
|
292
|
-
})(typeof window !== 'undefined' ? window : globalThis);
|
|
1
|
+
// Specimen plate and backdrop: a contour survey of simplex-noise terrain.
|
|
2
|
+
// The build runs contours() once (t = 0) to write the static SVG, so the plate and the backdrop are complete
|
|
3
|
+
// without JavaScript.
|
|
4
|
+
// In the browser, mount() redraws the same contours as the terrain drifts through time. Same code, same seed,
|
|
5
|
+
// so the first animated frame matches the static one exactly.
|
|
6
|
+
(function (g) {
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
// ------------------------------------------------------------ seeded 3D simplex noise (after Gustavson)
|
|
10
|
+
function rng(seed) {
|
|
11
|
+
return function () {
|
|
12
|
+
seed |= 0; seed = (seed + 0x6d2b79f5) | 0;
|
|
13
|
+
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
14
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
15
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
var GRAD = [1,1,0, -1,1,0, 1,-1,0, -1,-1,0, 1,0,1, -1,0,1, 1,0,-1, -1,0,-1, 0,1,1, 0,-1,1, 0,1,-1, 0,-1,-1];
|
|
19
|
+
var F3 = 1 / 3, G3 = 1 / 6;
|
|
20
|
+
function simplex(seed) {
|
|
21
|
+
var r = rng(seed), p = new Uint8Array(256), perm = new Uint8Array(512), pm = new Uint8Array(512), i;
|
|
22
|
+
for (i = 0; i < 256; i++) p[i] = i;
|
|
23
|
+
for (i = 255; i > 0; i--) { var k = Math.floor(r() * (i + 1)), s = p[i]; p[i] = p[k]; p[k] = s; }
|
|
24
|
+
for (i = 0; i < 512; i++) { perm[i] = p[i & 255]; pm[i] = (perm[i] % 12) * 3; }
|
|
25
|
+
return function (x, y, z) {
|
|
26
|
+
var s = (x + y + z) * F3, i = Math.floor(x + s), j = Math.floor(y + s), k = Math.floor(z + s);
|
|
27
|
+
var t = (i + j + k) * G3, x0 = x - i + t, y0 = y - j + t, z0 = z - k + t;
|
|
28
|
+
var i1, j1, k1, i2, j2, k2;
|
|
29
|
+
if (x0 >= y0) {
|
|
30
|
+
if (y0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; }
|
|
31
|
+
else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; }
|
|
32
|
+
else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; }
|
|
33
|
+
} else {
|
|
34
|
+
if (y0 < z0) { i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1; }
|
|
35
|
+
else if (x0 < z0) { i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1; }
|
|
36
|
+
else { i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0; }
|
|
37
|
+
}
|
|
38
|
+
var x1 = x0 - i1 + G3, y1 = y0 - j1 + G3, z1 = z0 - k1 + G3;
|
|
39
|
+
var x2 = x0 - i2 + 2 * G3, y2 = y0 - j2 + 2 * G3, z2 = z0 - k2 + 2 * G3;
|
|
40
|
+
var x3 = x0 - 1 + 0.5, y3 = y0 - 1 + 0.5, z3 = z0 - 1 + 0.5;
|
|
41
|
+
var ii = i & 255, jj = j & 255, kk = k & 255, n = 0, tt, gi;
|
|
42
|
+
tt = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
|
|
43
|
+
if (tt > 0) { gi = pm[ii + perm[jj + perm[kk]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x0 + GRAD[gi + 1] * y0 + GRAD[gi + 2] * z0); }
|
|
44
|
+
tt = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
45
|
+
if (tt > 0) { gi = pm[ii + i1 + perm[jj + j1 + perm[kk + k1]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x1 + GRAD[gi + 1] * y1 + GRAD[gi + 2] * z1); }
|
|
46
|
+
tt = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
47
|
+
if (tt > 0) { gi = pm[ii + i2 + perm[jj + j2 + perm[kk + k2]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x2 + GRAD[gi + 1] * y2 + GRAD[gi + 2] * z2); }
|
|
48
|
+
tt = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
49
|
+
if (tt > 0) { gi = pm[ii + 1 + perm[jj + 1 + perm[kk + 1]]]; tt *= tt; n += tt * tt * (GRAD[gi] * x3 + GRAD[gi + 1] * y3 + GRAD[gi + 2] * z3); }
|
|
50
|
+
return 32 * n; // about -1..1
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ------------------------------------------------------------ the survey
|
|
55
|
+
var DEFAULTS = {
|
|
56
|
+
seed: 3, // which terrain
|
|
57
|
+
size: 240, // viewBox units
|
|
58
|
+
cells: 32, // marching-squares grid per side
|
|
59
|
+
scale: 1.4, // noise features across the plate
|
|
60
|
+
octaves: 2,
|
|
61
|
+
massif: 1.1, // how strongly the terrain rises toward the centre
|
|
62
|
+
levels: 14, // contour lines
|
|
63
|
+
index: 5 // every nth line is an index contour
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
function survey(opts) {
|
|
67
|
+
var o = {}, k;
|
|
68
|
+
for (k in DEFAULTS) o[k] = opts && opts[k] != null ? opts[k] : DEFAULTS[k];
|
|
69
|
+
var noise = simplex(o.seed), N = o.cells, S = o.size, cell = S / N;
|
|
70
|
+
var lo = -0.45, hi = 1.75, step = (hi - lo) / (o.levels + 1);
|
|
71
|
+
var levels = [];
|
|
72
|
+
for (k = 1; k <= o.levels; k++) levels.push(lo + k * step);
|
|
73
|
+
var h = new Float32Array((N + 1) * (N + 1));
|
|
74
|
+
|
|
75
|
+
function height(t) {
|
|
76
|
+
for (var j = 0; j <= N; j++) for (var i = 0; i <= N; i++) {
|
|
77
|
+
var x = i / N, y = j / N, f = o.scale, a = 1, v = 0, norm = 0;
|
|
78
|
+
for (var oc = 0; oc < o.octaves; oc++) { v += a * noise(x * f, y * f, t + oc * 13.7); norm += a; a *= 0.35; f *= 2.3; }
|
|
79
|
+
var dx = x - 0.5, dy = y - 0.5, r = Math.min(1, Math.sqrt(dx * dx + dy * dy) / 0.62);
|
|
80
|
+
h[j * (N + 1) + i] = v / norm + o.massif * (1 - r * r);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Marching squares for one level. Points sit on cell edges and are numbered by edge, so neighbouring cells
|
|
85
|
+
// share them exactly. Horizontal edges come first (N per row, N + 1 rows), then vertical ones.
|
|
86
|
+
var E = 2 * N * (N + 1), px = new Float32Array(E), py = new Float32Array(E), lk = new Int32Array(2 * E), stamp = new Int32Array(E), pass = 0;
|
|
87
|
+
function trace(level) {
|
|
88
|
+
pass++;
|
|
89
|
+
var touched = [];
|
|
90
|
+
function pt(i, j, vert) {
|
|
91
|
+
var id = vert ? N * (N + 1) + j * (N + 1) + i : j * N + i;
|
|
92
|
+
if (stamp[id] !== pass) {
|
|
93
|
+
stamp[id] = pass; lk[2 * id] = -1; lk[2 * id + 1] = -1; touched.push(id);
|
|
94
|
+
var a = h[j * (N + 1) + i], b = vert ? h[(j + 1) * (N + 1) + i] : h[j * (N + 1) + i + 1], u = (level - a) / (b - a);
|
|
95
|
+
px[id] = vert ? i * cell : (i + u) * cell; py[id] = vert ? (j + u) * cell : j * cell;
|
|
96
|
+
}
|
|
97
|
+
return id;
|
|
98
|
+
}
|
|
99
|
+
function link(a, b) { lk[2 * a + (lk[2 * a] === -1 ? 0 : 1)] = b; lk[2 * b + (lk[2 * b] === -1 ? 0 : 1)] = a; }
|
|
100
|
+
for (var j = 0; j < N; j++) for (var i = 0; i < N; i++) {
|
|
101
|
+
var tl = h[j * (N + 1) + i], tr = h[j * (N + 1) + i + 1], br = h[(j + 1) * (N + 1) + i + 1], bl = h[(j + 1) * (N + 1) + i];
|
|
102
|
+
var c = (tl > level ? 8 : 0) | (tr > level ? 4 : 0) | (br > level ? 2 : 0) | (bl > level ? 1 : 0);
|
|
103
|
+
if (c === 0 || c === 15) continue;
|
|
104
|
+
var centre = (tl + tr + br + bl) / 4 > level;
|
|
105
|
+
switch (c) {
|
|
106
|
+
case 1: case 14: link(pt(i, j, 1), pt(i, j + 1, 0)); break;
|
|
107
|
+
case 2: case 13: link(pt(i, j + 1, 0), pt(i + 1, j, 1)); break;
|
|
108
|
+
case 3: case 12: link(pt(i, j, 1), pt(i + 1, j, 1)); break;
|
|
109
|
+
case 4: case 11: link(pt(i, j, 0), pt(i + 1, j, 1)); break;
|
|
110
|
+
case 6: case 9: link(pt(i, j, 0), pt(i, j + 1, 0)); break;
|
|
111
|
+
case 7: case 8: link(pt(i, j, 1), pt(i, j, 0)); break;
|
|
112
|
+
case 5:
|
|
113
|
+
if (centre) { link(pt(i, j, 1), pt(i, j, 0)); link(pt(i, j + 1, 0), pt(i + 1, j, 1)); }
|
|
114
|
+
else { link(pt(i, j, 1), pt(i, j + 1, 0)); link(pt(i, j, 0), pt(i + 1, j, 1)); }
|
|
115
|
+
break;
|
|
116
|
+
case 10:
|
|
117
|
+
if (centre) { link(pt(i, j, 0), pt(i + 1, j, 1)); link(pt(i, j, 1), pt(i, j + 1, 0)); }
|
|
118
|
+
else { link(pt(i, j, 1), pt(i, j, 0)); link(pt(i, j + 1, 0), pt(i + 1, j, 1)); }
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Chain the segments into lines: open lines start at an end (one link), then whatever is left is a loop.
|
|
123
|
+
var lines = [], seen = pass * 2; // reuse stamp: a point is used once stamp[id] === -seen
|
|
124
|
+
function walk(start) {
|
|
125
|
+
var pts = [], prev = -1, cur = start;
|
|
126
|
+
while (cur !== -1 && stamp[cur] !== -seen) {
|
|
127
|
+
stamp[cur] = -seen; pts.push(px[cur], py[cur]);
|
|
128
|
+
var n0 = lk[2 * cur], n1 = lk[2 * cur + 1], next = n0 !== prev && n0 !== -1 && stamp[n0] !== -seen ? n0 : n1 !== prev && n1 !== -1 && stamp[n1] !== -seen ? n1 : -1;
|
|
129
|
+
prev = cur; cur = next;
|
|
130
|
+
}
|
|
131
|
+
var closed = lk[2 * prev] === start || lk[2 * prev + 1] === start;
|
|
132
|
+
return { pts: pts, closed: closed && pts.length > 4 };
|
|
133
|
+
}
|
|
134
|
+
var k, id;
|
|
135
|
+
for (k = 0; k < touched.length; k++) { id = touched[k]; if (stamp[id] === pass && lk[2 * id + 1] === -1) lines.push(walk(id)); }
|
|
136
|
+
for (k = 0; k < touched.length; k++) { id = touched[k]; if (stamp[id] === pass) lines.push(walk(id)); }
|
|
137
|
+
return lines;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Clean each traced line before drawing it. Marching squares puts two points almost on top of each other
|
|
141
|
+
// wherever a contour passes close to a grid corner; merging them removes the burrs that flicker as the
|
|
142
|
+
// terrain moves. Loops smaller than two cells, and edge stubs shorter than that, pop in and out, so they are dropped.
|
|
143
|
+
var minGap = cell * 0.4, minLoop = cell * 2;
|
|
144
|
+
function clean(ln) {
|
|
145
|
+
var p = ln.pts, n = p.length / 2, q = [p[0], p[1]], k, dx, dy;
|
|
146
|
+
for (k = 1; k < n; k++) {
|
|
147
|
+
dx = p[2 * k] - q[q.length - 2]; dy = p[2 * k + 1] - q[q.length - 1];
|
|
148
|
+
if (dx * dx + dy * dy >= minGap * minGap) q.push(p[2 * k], p[2 * k + 1]);
|
|
149
|
+
else if (!ln.closed && k === n - 1) { q[q.length - 2] = p[2 * k]; q[q.length - 1] = p[2 * k + 1]; } // keep the true end on the edge
|
|
150
|
+
}
|
|
151
|
+
if (ln.closed) {
|
|
152
|
+
dx = q[0] - q[q.length - 2]; dy = q[1] - q[q.length - 1];
|
|
153
|
+
if (q.length > 2 && dx * dx + dy * dy < minGap * minGap) q.length -= 2;
|
|
154
|
+
var x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity;
|
|
155
|
+
for (k = 0; k < q.length; k += 2) { x0 = Math.min(x0, q[k]); x1 = Math.max(x1, q[k]); y0 = Math.min(y0, q[k + 1]); y1 = Math.max(y1, q[k + 1]); }
|
|
156
|
+
if (q.length < 8 || Math.max(x1 - x0, y1 - y0) < minLoop) return null;
|
|
157
|
+
}
|
|
158
|
+
if (!ln.closed) { // stubs clipped by the plate's edge flicker the same way
|
|
159
|
+
var len = 0;
|
|
160
|
+
for (k = 2; k < q.length; k += 2) len += Math.sqrt((q[k] - q[k - 2]) * (q[k] - q[k - 2]) + (q[k + 1] - q[k - 1]) * (q[k + 1] - q[k - 1]));
|
|
161
|
+
if (len < minLoop) return null;
|
|
162
|
+
}
|
|
163
|
+
return q.length >= 4 ? { pts: q, closed: ln.closed } : null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Centripetal Catmull-Rom through the points (alpha 0.5), written as cubic Béziers rounded to 0.1 units.
|
|
167
|
+
// Unlike the uniform form, it never overshoots into a cusp or a loop when neighbouring points are unevenly spaced.
|
|
168
|
+
function r1(n) { return Math.round(n * 10) / 10; }
|
|
169
|
+
function path(lines) {
|
|
170
|
+
var d = [];
|
|
171
|
+
for (var l = 0; l < lines.length; l++) {
|
|
172
|
+
var ln = clean(lines[l]);
|
|
173
|
+
if (!ln) continue;
|
|
174
|
+
var p = ln.pts, n = p.length / 2, closed = ln.closed;
|
|
175
|
+
d.push('M', r1(p[0]), ' ', r1(p[1]));
|
|
176
|
+
var segs = closed ? n : n - 1;
|
|
177
|
+
for (var k = 0; k < segs; k++) {
|
|
178
|
+
var a = k === 0 ? (closed ? n - 1 : 0) : k - 1, b = k, c = k + 1 < n ? k + 1 : (closed ? 0 : n - 1), e = k + 2 < n ? k + 2 : (closed ? k + 2 - n : n - 1);
|
|
179
|
+
a *= 2; b *= 2; c *= 2; e *= 2;
|
|
180
|
+
var d1 = Math.pow((p[b] - p[a]) * (p[b] - p[a]) + (p[b + 1] - p[a + 1]) * (p[b + 1] - p[a + 1]), 0.25);
|
|
181
|
+
var d2 = Math.pow((p[c] - p[b]) * (p[c] - p[b]) + (p[c + 1] - p[b + 1]) * (p[c + 1] - p[b + 1]), 0.25);
|
|
182
|
+
var d3 = Math.pow((p[e] - p[c]) * (p[e] - p[c]) + (p[e + 1] - p[c + 1]) * (p[e + 1] - p[c + 1]), 0.25);
|
|
183
|
+
var c1x, c1y, c2x, c2y;
|
|
184
|
+
if (d1 < 1e-6) { c1x = p[b] + (p[c] - p[b]) / 3; c1y = p[b + 1] + (p[c + 1] - p[b + 1]) / 3; }
|
|
185
|
+
else {
|
|
186
|
+
var m1 = 3 * d1 * (d1 + d2), w1 = 2 * d1 * d1 + 3 * d1 * d2 + d2 * d2;
|
|
187
|
+
c1x = (d1 * d1 * p[c] - d2 * d2 * p[a] + w1 * p[b]) / m1; c1y = (d1 * d1 * p[c + 1] - d2 * d2 * p[a + 1] + w1 * p[b + 1]) / m1;
|
|
188
|
+
}
|
|
189
|
+
if (d3 < 1e-6) { c2x = p[c] + (p[b] - p[c]) / 3; c2y = p[c + 1] + (p[b + 1] - p[c + 1]) / 3; }
|
|
190
|
+
else {
|
|
191
|
+
var m2 = 3 * d3 * (d3 + d2), w2 = 2 * d3 * d3 + 3 * d3 * d2 + d2 * d2;
|
|
192
|
+
c2x = (d3 * d3 * p[b] - d2 * d2 * p[e] + w2 * p[c]) / m2; c2y = (d3 * d3 * p[b + 1] - d2 * d2 * p[e + 1] + w2 * p[c + 1]) / m2;
|
|
193
|
+
}
|
|
194
|
+
d.push('C', r1(c1x), ' ', r1(c1y), ' ', r1(c2x), ' ', r1(c2y), ' ', r1(p[c]), ' ', r1(p[c + 1]));
|
|
195
|
+
}
|
|
196
|
+
if (closed) d.push('Z');
|
|
197
|
+
}
|
|
198
|
+
return d.join('');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
options: o,
|
|
203
|
+
levels: levels,
|
|
204
|
+
// Path data for every level at time t (in noise units).
|
|
205
|
+
contours: function (t) {
|
|
206
|
+
height(t || 0);
|
|
207
|
+
return levels.map(function (lv, i) { return { level: i + 1, index: (i + 1) % o.index === 0, d: path(trace(lv)) }; });
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ------------------------------------------------------------ static markup (used by the build)
|
|
213
|
+
// kind 'backdrop' draws the same survey full-bleed behind a page (docs/components/backdrop.md): sliced to cover
|
|
214
|
+
// the viewport instead of fitted into the plate's square.
|
|
215
|
+
function markup(opts, kind) {
|
|
216
|
+
var bg = kind === 'backdrop';
|
|
217
|
+
var s = survey(opts), S = s.options.size, q = S / 4;
|
|
218
|
+
var grid = [q, 2 * q, 3 * q].map(function (v) { return 'M' + v + ' 0V' + S + 'M0 ' + v + 'H' + S; }).join('');
|
|
219
|
+
var paths = s.contours(0).map(function (c, i) {
|
|
220
|
+
return '<path class="cx-plate__line' + (c.index ? ' cx-plate__line--index' : '') + '" style="--i: ' + i + '" pathLength="1" d="' + c.d + '"></path>';
|
|
221
|
+
}).join('');
|
|
222
|
+
return '<div class="' + (bg ? 'cx-backdrop' : 'cx-plate') + '" aria-hidden="true" data-plate-seed="' + s.options.seed + '">' +
|
|
223
|
+
'<svg class="cx-plate__map" viewBox="0 0 ' + S + ' ' + S + '"' + (bg ? ' preserveAspectRatio="xMidYMid slice"' : '') + ' focusable="false">' +
|
|
224
|
+
'<path class="cx-plate__grid" d="' + grid + '"></path>' + paths + '</svg></div>';
|
|
225
|
+
}
|
|
226
|
+
function backdrop(opts) { return markup(opts, 'backdrop'); }
|
|
227
|
+
|
|
228
|
+
// ------------------------------------------------------------ animation (browser only)
|
|
229
|
+
function mount(root) {
|
|
230
|
+
var doc = root.ownerDocument, win = doc.defaultView;
|
|
231
|
+
// A plate's pause control sits in its frame; the backdrop's sits in the site header.
|
|
232
|
+
var toggle = root.classList.contains('cx-backdrop')
|
|
233
|
+
? doc.querySelector('.cx-backdrop__pause')
|
|
234
|
+
: (root.closest('.cx-plate-frame') || root.parentElement).querySelector('.cx-plate__pause');
|
|
235
|
+
var input = toggle && toggle.querySelector('input');
|
|
236
|
+
// The backdrop's pause is a reader preference, like the theme: it is stored under cx-backdrop ("paused", or no key
|
|
237
|
+
// for running) and restored on every page. A plate's pause belongs to that plate and isn't kept.
|
|
238
|
+
if (input && root.classList.contains('cx-backdrop')) {
|
|
239
|
+
try { if (win.localStorage.getItem('cx-backdrop') === 'paused') input.checked = true; } catch (e) { /* storage blocked */ }
|
|
240
|
+
input.addEventListener('change', function () {
|
|
241
|
+
try { if (input.checked) win.localStorage.setItem('cx-backdrop', 'paused'); else win.localStorage.removeItem('cx-backdrop'); }
|
|
242
|
+
catch (e) { /* storage blocked: the pause still applies to this page */ }
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
var paths = root.querySelectorAll('.cx-plate__line');
|
|
246
|
+
var reduce = win.matchMedia('(prefers-reduced-motion: reduce)');
|
|
247
|
+
function ms(name) { // durations come from the tokens, through custom properties on the plate
|
|
248
|
+
var v = win.getComputedStyle(root).getPropertyValue(name).trim();
|
|
249
|
+
return v.endsWith('ms') ? parseFloat(v) : parseFloat(v) * 1000;
|
|
250
|
+
}
|
|
251
|
+
var drift = ms('--cx-plate-drift'), delay = ms('--cx-plate-draw');
|
|
252
|
+
var s = survey({ seed: Number(root.getAttribute('data-plate-seed')) || DEFAULTS.seed });
|
|
253
|
+
var backdrop = root.classList.contains('cx-backdrop');
|
|
254
|
+
// The drift never starts at full speed: from rest its speed eases up over one draw duration, on load and again
|
|
255
|
+
// after a pause, a hidden tab or scrolling back into view. Pausing still stops it at once.
|
|
256
|
+
var elapsed = 0, last = null, raf = 0, frames = 0, visible = true, ready = false, warm = 0;
|
|
257
|
+
|
|
258
|
+
function running() { return ready && visible && !doc.hidden && !reduce.matches && !(input && input.checked); }
|
|
259
|
+
function tick(now) {
|
|
260
|
+
raf = 0;
|
|
261
|
+
if (!running()) { last = null; warm = 0; return; }
|
|
262
|
+
if (last !== null) {
|
|
263
|
+
var dt = Math.min(now - last, delay / 16); // a long gap (background tab) never jumps the terrain
|
|
264
|
+
warm = Math.min(1, warm + dt / delay);
|
|
265
|
+
elapsed += dt * warm * warm * (3 - 2 * warm); // speed follows a smoothstep from 0 to full
|
|
266
|
+
}
|
|
267
|
+
last = now;
|
|
268
|
+
// Redraw on every second frame: the terrain moves so little per frame that this looks the same at half the cost.
|
|
269
|
+
if ((frames++ & 1) === 0) s.contours(elapsed / drift).forEach(function (c, i) { paths[i].setAttribute('d', c.d); });
|
|
270
|
+
raf = win.requestAnimationFrame(tick);
|
|
271
|
+
}
|
|
272
|
+
function update() {
|
|
273
|
+
if (toggle) toggle.hidden = reduce.matches;
|
|
274
|
+
if (running() && !raf) raf = win.requestAnimationFrame(tick);
|
|
275
|
+
}
|
|
276
|
+
if (input) input.addEventListener('change', update);
|
|
277
|
+
if (reduce.addEventListener) reduce.addEventListener('change', update);
|
|
278
|
+
doc.addEventListener('visibilitychange', update);
|
|
279
|
+
if ('IntersectionObserver' in win) new win.IntersectionObserver(function (e) { visible = e[0].isIntersecting; update(); }).observe(root);
|
|
280
|
+
// A plate draws its lines in first; the backdrop has no draw-in, so it starts as soon as the script runs.
|
|
281
|
+
if (backdrop) ready = true; else win.setTimeout(function () { ready = true; update(); }, delay);
|
|
282
|
+
update();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
g.cxPlate = { survey: survey, markup: markup, backdrop: backdrop, mount: mount, defaults: DEFAULTS };
|
|
286
|
+
|
|
287
|
+
// In a browser, start every plate and backdrop on the page. Without this script the static contours stay as they are.
|
|
288
|
+
if (typeof document !== 'undefined' && document.querySelectorAll) {
|
|
289
|
+
var start = function () { Array.prototype.forEach.call(document.querySelectorAll('.cx-plate[data-plate-seed], .cx-backdrop[data-plate-seed]'), mount); };
|
|
290
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
|
|
291
|
+
}
|
|
292
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
package/dist/theme-init.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{var t=localStorage.getItem('cx-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t)}catch(e){}
|
|
1
|
+
try{var t=localStorage.getItem('cx-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t)}catch(e){}
|