morphico 0.3.0 → 0.5.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 +98 -41
- package/dist/esm/all.js +50 -0
- package/dist/esm/clip.js +283 -0
- package/dist/esm/engine.js +25 -0
- package/dist/esm/face.js +153 -0
- package/dist/esm/geometry.js +459 -0
- package/dist/esm/icon.js +41 -0
- package/dist/esm/index.js +50 -0
- package/dist/esm/morph.js +107 -0
- package/dist/esm/roundness.js +37 -0
- package/dist/esm/sets/battery.js +87 -0
- package/dist/esm/sets/bell.js +101 -0
- package/dist/esm/sets/characters.js +43 -0
- package/dist/esm/sets/check-mark.js +74 -0
- package/dist/esm/sets/checkbox.js +91 -0
- package/dist/esm/sets/chevron.js +70 -0
- package/dist/esm/sets/condensed.js +137 -0
- package/dist/esm/sets/copy.js +87 -0
- package/dist/esm/sets/eye.js +105 -0
- package/dist/esm/sets/faces.js +482 -0
- package/dist/esm/sets/folder.js +112 -0
- package/dist/esm/sets/handwritten.js +136 -0
- package/dist/esm/sets/lock.js +75 -0
- package/dist/esm/sets/lucide.js +66 -0
- package/dist/esm/sets/media.js +109 -0
- package/dist/esm/sets/menu.js +84 -0
- package/dist/esm/sets/mic.js +102 -0
- package/dist/esm/sets/radio.js +84 -0
- package/dist/esm/sets/round.js +244 -0
- package/dist/esm/sets/shapes.js +455 -0
- package/dist/esm/sets/slim.js +148 -0
- package/dist/esm/sets/square.js +149 -0
- package/dist/esm/sets/theme.js +107 -0
- package/dist/esm/sets/toggle.js +73 -0
- package/dist/esm/sets/volume.js +89 -0
- package/dist/esm/sets/weather.js +691 -0
- package/dist/esm/sets/wifi.js +68 -0
- package/dist/esm/svg.js +31 -0
- package/dist/esm/text.js +88 -0
- package/dist/esm/types.js +3 -0
- package/dist/morphico.js +4033 -3380
- package/dist/types/{iconSets.d.ts → all.d.ts} +0 -2
- package/dist/types/clip.d.ts +6 -11
- package/dist/types/face.d.ts +27 -0
- package/dist/types/icon.d.ts +4 -9
- package/dist/types/index.d.ts +5 -3
- package/dist/types/roundness.d.ts +8 -0
- package/dist/types/sets/battery.d.ts +1 -1
- package/dist/types/sets/bell.d.ts +1 -1
- package/dist/types/sets/checkbox.d.ts +1 -1
- package/dist/types/sets/chevron.d.ts +1 -1
- package/dist/types/sets/condensed.d.ts +1 -1
- package/dist/types/sets/copy.d.ts +1 -1
- package/dist/types/sets/eye.d.ts +1 -1
- package/dist/types/sets/faces.d.ts +25 -0
- package/dist/types/sets/folder.d.ts +1 -1
- package/dist/types/sets/handwritten.d.ts +1 -1
- package/dist/types/sets/lock.d.ts +1 -1
- package/dist/types/sets/media.d.ts +1 -1
- package/dist/types/sets/menu.d.ts +1 -1
- package/dist/types/sets/mic.d.ts +1 -1
- package/dist/types/sets/radio.d.ts +1 -1
- package/dist/types/sets/round.d.ts +1 -1
- package/dist/types/sets/slim.d.ts +1 -1
- package/dist/types/sets/square.d.ts +1 -1
- package/dist/types/sets/theme.d.ts +1 -1
- package/dist/types/sets/toggle.d.ts +1 -1
- package/dist/types/sets/volume.d.ts +1 -1
- package/dist/types/sets/weather.d.ts +1 -1
- package/dist/types/sets/wifi.d.ts +1 -1
- package/dist/types/text.d.ts +8 -13
- package/package.json +95 -3
- package/dist/morphico.mjs +0 -4003
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/*! morphico v0.5.0 | code: MIT License, Copyright (c) 2026 Raycho Ivanov (see LICENSE)
|
|
2
|
+
* Icons based on Lucide (https://lucide.dev), some of them from Feather (https://feathericons.com):
|
|
3
|
+
*
|
|
4
|
+
* ISC License
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) 2026 Lucide Icons and Contributors
|
|
7
|
+
*
|
|
8
|
+
* Permission to use, copy, modify, and/or distribute this software for any
|
|
9
|
+
* purpose with or without fee is hereby granted, provided that the above
|
|
10
|
+
* copyright notice and this permission notice appear in all copies.
|
|
11
|
+
*
|
|
12
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
13
|
+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
14
|
+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
15
|
+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
16
|
+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
17
|
+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
18
|
+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
19
|
+
*
|
|
20
|
+
* The MIT License (MIT) (for the Lucide icons derived from Feather, listed in LICENSE-lucide)
|
|
21
|
+
*
|
|
22
|
+
* Copyright (c) 2013-present Cole Bemis
|
|
23
|
+
*
|
|
24
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
25
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
26
|
+
* in the Software without restriction, including without limitation the rights
|
|
27
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
28
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
29
|
+
* furnished to do so, subject to the following conditions:
|
|
30
|
+
*
|
|
31
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
32
|
+
* copies or substantial portions of the Software.
|
|
33
|
+
*
|
|
34
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
36
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
37
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
38
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
39
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
40
|
+
* SOFTWARE.
|
|
41
|
+
*/
|
|
42
|
+
// Shapes for clip(): closed outlines that cut out an element. From Lucide 1.61.0 (ISC, see LICENSE-lucide), imported
|
|
43
|
+
// by `node tools/import-lucide.ts <name>`; tag without its dot, club without its stem. The states are the Lucide outlines made comparable for
|
|
44
|
+
// morphing: clockwise, starting where the outline crosses the vertical middle at the top, scaled evenly to fill the
|
|
45
|
+
// 24×24 box and centred (computed on first use, not at import). Roundness 1 = Lucide's own corners … 0 = sharp.
|
|
46
|
+
import { arc, cubic, ellipse, line, reversedStroke, strokePiece } from '../geometry.js';
|
|
47
|
+
const DEGREE = Math.PI / 180;
|
|
48
|
+
const GRID_SIZE = 24;
|
|
49
|
+
const GRID_CENTER = 12;
|
|
50
|
+
const SAMPLES_PER_STROKE = 48;
|
|
51
|
+
/** Enough for the shape that needs the most (puzzle: 24 strokes, its knobs ≈ 40 segments); the rest spreads by length. */
|
|
52
|
+
const SEGMENT_COUNT = 48;
|
|
53
|
+
/** The shapes as Lucide draws them (Lucide coordinates); the set's states are made from these. */
|
|
54
|
+
export const lucideShapeOutlines = {
|
|
55
|
+
circle: [arc(12, 12, 10, -90, 270)],
|
|
56
|
+
ellipse: ellipse(12, 12, 10, 6),
|
|
57
|
+
heart: [
|
|
58
|
+
arc(7.5, 9.5001, 5.5, -179.9989, -41.9423),
|
|
59
|
+
arc(12, 5.4415, 0.56, 136.9163, 43.0837),
|
|
60
|
+
arc(16.5101, 9.4738, 5.49, -138.3317, 0.2729),
|
|
61
|
+
cubic(22, 9.5, 22, 11.79, 20.5, 13.5, 19, 15),
|
|
62
|
+
line(19, 15, 13.508, 20.313),
|
|
63
|
+
arc(11.9996, 18.9997, 2, 41.0455, 138.2288),
|
|
64
|
+
line(10.508, 20.332, 5, 15),
|
|
65
|
+
cubic(5, 15, 3.5, 13.5, 2, 11.8, 2, 9.5),
|
|
66
|
+
],
|
|
67
|
+
sparkle: [
|
|
68
|
+
arc(12, 2.9976, 1, -169.4202, -10.5798),
|
|
69
|
+
line(12.983, 2.814, 14.034, 8.372),
|
|
70
|
+
arc(15.9992, 8.0008, 2, 169.3026, 100.6974),
|
|
71
|
+
line(15.628, 9.966, 21.186, 11.017),
|
|
72
|
+
arc(21.0024, 12, 1, -79.4202, 79.4202),
|
|
73
|
+
line(21.186, 12.983, 15.628, 14.034),
|
|
74
|
+
arc(15.9992, 15.9992, 2, -100.6974, -169.3026),
|
|
75
|
+
line(14.034, 15.628, 12.983, 21.186),
|
|
76
|
+
arc(12, 21.0024, 1, 10.5798, 169.4202),
|
|
77
|
+
line(11.017, 21.186, 9.966, 15.628),
|
|
78
|
+
arc(8.0008, 15.9992, 2, -10.6974, -79.3026),
|
|
79
|
+
line(8.372, 14.034, 2.814, 12.983),
|
|
80
|
+
arc(2.9976, 12, 1, 100.5798, 259.4202),
|
|
81
|
+
line(2.814, 11.017, 8.372, 9.966),
|
|
82
|
+
arc(8.0008, 8.0008, 2, 79.3026, 10.6974),
|
|
83
|
+
line(9.966, 8.372, 11.017, 2.814),
|
|
84
|
+
],
|
|
85
|
+
astroid: [
|
|
86
|
+
arc(12, 21.0024, 1, 10.5798, 169.4202),
|
|
87
|
+
arc(1.1555, 22.8445, 10, -9.5467, -80.4533),
|
|
88
|
+
arc(2.9976, 12, 1, 100.5798, 259.4202),
|
|
89
|
+
arc(1.1555, 1.1555, 10, 80.4533, 9.5467),
|
|
90
|
+
arc(12, 2.9976, 1, -169.4202, -10.5798),
|
|
91
|
+
arc(22.8445, 1.1555, 10, 170.4533, 99.5467),
|
|
92
|
+
arc(21.0024, 12, 1, -79.4202, 79.4202),
|
|
93
|
+
arc(22.8445, 22.8445, 10, -99.5467, -170.4533),
|
|
94
|
+
],
|
|
95
|
+
bookmark: [
|
|
96
|
+
arc(17, 5, 2, -90, 0),
|
|
97
|
+
line(19, 5, 19, 20),
|
|
98
|
+
arc(18, 19.9997, 1, 0.0185, 119.7357),
|
|
99
|
+
line(17.504, 20.868, 12.992, 18.29),
|
|
100
|
+
arc(12, 20.0266, 2, -60.2643, -119.7357),
|
|
101
|
+
line(11.008, 18.29, 6.496, 20.868),
|
|
102
|
+
arc(6, 19.9997, 1, 60.2643, 179.9815),
|
|
103
|
+
line(5, 20, 5, 5),
|
|
104
|
+
arc(7, 5, 2, 180, 270),
|
|
105
|
+
line(7, 3, 17, 3),
|
|
106
|
+
],
|
|
107
|
+
shield: [
|
|
108
|
+
cubic(20, 13, 20, 18, 16.5, 20.5, 12.34, 21.95),
|
|
109
|
+
arc(12.0191, 21.0029, 1, 71.2803, 110.4299),
|
|
110
|
+
cubic(11.67, 21.94, 7.5, 20.5, 4, 18, 4, 13),
|
|
111
|
+
line(4, 13, 4, 6),
|
|
112
|
+
arc(5, 6, 1, 180, 270),
|
|
113
|
+
cubic(5, 5, 7, 5, 9.5, 3.8, 11.24, 2.28),
|
|
114
|
+
arc(12, 3.1696, 1.17, -130.5094, -49.4906),
|
|
115
|
+
cubic(12.76, 2.28, 14.51, 3.81, 17, 5, 19, 5),
|
|
116
|
+
arc(19, 6, 1, -90, 0),
|
|
117
|
+
line(20, 6, 20, 13),
|
|
118
|
+
],
|
|
119
|
+
moon: [
|
|
120
|
+
arc(11.9981, 12.0009, 9, 3.09, 266.904),
|
|
121
|
+
cubic(11.512, 3.014, 11.917, 2.992, 12.129, 3.474, 11.914, 3.817),
|
|
122
|
+
arc(17.002, 6.997, 6, -147.9941, -302.0059),
|
|
123
|
+
cubic(20.182, 12.085, 20.526, 11.87, 21.007, 12.081, 20.985, 12.486),
|
|
124
|
+
],
|
|
125
|
+
tag: [
|
|
126
|
+
arc(11.1716, 4, 2, -44.9913, -89.9878),
|
|
127
|
+
line(11.172, 2, 4, 2),
|
|
128
|
+
arc(4, 4, 2, -90, -180),
|
|
129
|
+
line(2, 4, 2, 11.172),
|
|
130
|
+
arc(4, 11.1716, 2, 179.9878, 134.9913),
|
|
131
|
+
line(2.586, 12.586, 11.29, 21.29),
|
|
132
|
+
arc(13, 19.5691, 2.426, 134.8186, 45.1814),
|
|
133
|
+
line(14.71, 21.29, 21.29, 14.71),
|
|
134
|
+
arc(19.5691, 13, 2.426, 44.8186, -44.8186),
|
|
135
|
+
line(21.29, 11.29, 12.586, 2.586),
|
|
136
|
+
],
|
|
137
|
+
square: [
|
|
138
|
+
line(5, 3, 19, 3),
|
|
139
|
+
arc(19, 5, 2, -90, 0),
|
|
140
|
+
line(21, 5, 21, 19),
|
|
141
|
+
arc(19, 19, 2, 0, 90),
|
|
142
|
+
line(19, 21, 5, 21),
|
|
143
|
+
arc(5, 19, 2, 90, 180),
|
|
144
|
+
line(3, 19, 3, 5),
|
|
145
|
+
arc(5, 5, 2, 180, 270),
|
|
146
|
+
],
|
|
147
|
+
badge: [
|
|
148
|
+
arc(7.7549, 7.7531, 4, 167.4834, 282.6366),
|
|
149
|
+
arc(12, 6.0048, 4, -147.4051, -32.5949),
|
|
150
|
+
arc(16.2474, 7.7526, 4, -102.6711, 12.6711),
|
|
151
|
+
arc(17.9952, 12, 4, -57.4051, 57.4051),
|
|
152
|
+
arc(16.2469, 16.2451, 4, -12.6366, 102.5166),
|
|
153
|
+
arc(12.005, 18.0031, 4, 32.4617, 147.5383),
|
|
154
|
+
arc(7.7549, 16.2469, 4, 77.3634, 192.5166),
|
|
155
|
+
arc(5.9891, 12, 4, 122.3281, 237.6719),
|
|
156
|
+
],
|
|
157
|
+
triangle: [
|
|
158
|
+
arc(12, 5.0035, 2, -30.1173, -149.8827),
|
|
159
|
+
line(10.27, 4, 2.27, 18),
|
|
160
|
+
arc(4.0021, 19, 2, -150, -269.9413),
|
|
161
|
+
line(4, 21, 20, 21),
|
|
162
|
+
arc(19.9979, 19, 2, 89.9413, -30),
|
|
163
|
+
line(21.73, 18, 13.73, 4),
|
|
164
|
+
],
|
|
165
|
+
droplet: [
|
|
166
|
+
arc(12, 15, 7, 90, 0),
|
|
167
|
+
cubic(19, 15, 19, 13, 18, 11.1, 16, 9.5),
|
|
168
|
+
cubic(16, 9.5, 14, 7.9, 12.5, 5.5, 12, 3),
|
|
169
|
+
cubic(12, 3, 11.5, 5.5, 10, 7.9, 8, 9.5),
|
|
170
|
+
cubic(8, 9.5, 6, 11.1, 5, 13, 5, 15),
|
|
171
|
+
arc(12, 15, 7, 180, 90),
|
|
172
|
+
],
|
|
173
|
+
pentagon: [
|
|
174
|
+
arc(12, 4.0021, 2, -125.803, -54.197),
|
|
175
|
+
line(13.17, 2.38, 21.17, 8.12),
|
|
176
|
+
arc(20.0009, 9.7427, 2, -54.2293, 18.2786),
|
|
177
|
+
line(21.9, 10.37, 18.86, 19.63),
|
|
178
|
+
arc(16.9618, 19, 2, 18.3608, 90.052),
|
|
179
|
+
line(16.96, 21, 7.04, 21),
|
|
180
|
+
arc(7.0382, 19, 2, 89.948, 161.6392),
|
|
181
|
+
line(5.14, 19.63, 2.1, 10.37),
|
|
182
|
+
arc(3.9991, 9.7427, 2, 161.7214, 234.2293),
|
|
183
|
+
line(2.83, 8.12, 10.83, 2.38),
|
|
184
|
+
],
|
|
185
|
+
hexagon: [
|
|
186
|
+
line(21, 16, 21, 8),
|
|
187
|
+
arc(19, 8.0021, 2, -0.0588, -60),
|
|
188
|
+
line(20, 6.27, 13, 2.27),
|
|
189
|
+
arc(12, 4.0021, 2, -60, -120),
|
|
190
|
+
line(11, 2.27, 4, 6.27),
|
|
191
|
+
arc(5, 8.0021, 2, -120, -179.9412),
|
|
192
|
+
line(3, 8, 3, 16),
|
|
193
|
+
arc(5, 15.9979, 2, 179.9412, 120),
|
|
194
|
+
line(4, 17.73, 11, 21.73),
|
|
195
|
+
arc(12, 19.9979, 2, 120, 60),
|
|
196
|
+
line(13, 21.73, 20, 17.73),
|
|
197
|
+
arc(19, 15.9979, 2, 60, 0.0588),
|
|
198
|
+
],
|
|
199
|
+
octagon: [
|
|
200
|
+
arc(4, 15.3116, 2, 134.9913, 179.9878),
|
|
201
|
+
line(2, 15.312, 2, 8.688),
|
|
202
|
+
arc(4, 8.6884, 2, -179.9878, -134.9913),
|
|
203
|
+
line(2.586, 7.274, 7.274, 2.586),
|
|
204
|
+
arc(8.6884, 4, 2, -135.0087, -90.0122),
|
|
205
|
+
line(8.688, 2, 15.312, 2),
|
|
206
|
+
arc(15.3116, 4, 2, -89.9878, -44.9913),
|
|
207
|
+
line(16.726, 2.586, 21.414, 7.274),
|
|
208
|
+
arc(20, 8.6884, 2, -45.0087, -0.0122),
|
|
209
|
+
line(22, 8.688, 22, 15.312),
|
|
210
|
+
arc(20, 15.3116, 2, 0.0122, 45.0087),
|
|
211
|
+
line(21.414, 16.726, 16.726, 21.414),
|
|
212
|
+
arc(15.3116, 20, 2, 44.9913, 89.9878),
|
|
213
|
+
line(15.312, 22, 8.688, 22),
|
|
214
|
+
arc(8.6884, 20, 2, 90.0122, 135.0087),
|
|
215
|
+
line(7.274, 21.414, 2.586, 16.726),
|
|
216
|
+
],
|
|
217
|
+
squircle: [
|
|
218
|
+
cubic(12, 3, 19.2, 3, 21, 4.8, 21, 12),
|
|
219
|
+
cubic(21, 12, 21, 19.2, 19.2, 21, 12, 21),
|
|
220
|
+
cubic(12, 21, 4.8, 21, 3, 19.2, 3, 12),
|
|
221
|
+
cubic(3, 12, 3, 4.8, 4.8, 3, 12, 3),
|
|
222
|
+
],
|
|
223
|
+
star: [
|
|
224
|
+
arc(12, 2.5301, 0.53, -153.6664, -26.3336),
|
|
225
|
+
line(12.475, 2.295, 14.785, 6.974),
|
|
226
|
+
arc(16.6883, 6.0335, 2.123, 153.7044, 98.3503),
|
|
227
|
+
line(16.38, 8.134, 21.546, 8.89),
|
|
228
|
+
arc(21.47, 9.4145, 0.53, -81.756, 45.7247),
|
|
229
|
+
line(21.84, 9.794, 18.104, 13.432),
|
|
230
|
+
arc(19.5857, 14.9525, 2.123, -134.26, -189.6958),
|
|
231
|
+
line(17.493, 15.31, 18.375, 20.45),
|
|
232
|
+
arc(17.8531, 20.5422, 0.53, -10.0155, 118.0314),
|
|
233
|
+
line(17.604, 21.01, 12.986, 18.582),
|
|
234
|
+
arc(11.9995, 20.4608, 2.122, -62.2968, -117.7032),
|
|
235
|
+
line(11.013, 18.582, 6.396, 21.01),
|
|
236
|
+
arc(6.148, 20.5416, 0.53, 62.1036, 189.9512),
|
|
237
|
+
line(5.626, 20.45, 6.507, 15.311),
|
|
238
|
+
arc(4.4155, 14.9522, 2.122, 9.7335, -45.7597),
|
|
239
|
+
line(5.896, 13.432, 2.16, 9.795),
|
|
240
|
+
arc(2.5282, 9.4138, 0.53, 134.0047, 261.9521),
|
|
241
|
+
line(2.454, 8.889, 7.619, 8.134),
|
|
242
|
+
arc(7.3135, 6.0341, 2.122, 81.7227, 26.2909),
|
|
243
|
+
line(9.216, 6.974, 11.525, 2.295),
|
|
244
|
+
],
|
|
245
|
+
'rectangle-horizontal': [
|
|
246
|
+
line(4, 6, 20, 6),
|
|
247
|
+
arc(20, 8, 2, -90, 0),
|
|
248
|
+
line(22, 8, 22, 16),
|
|
249
|
+
arc(20, 16, 2, 0, 90),
|
|
250
|
+
line(20, 18, 4, 18),
|
|
251
|
+
arc(4, 16, 2, 90, 180),
|
|
252
|
+
line(2, 16, 2, 8),
|
|
253
|
+
arc(4, 8, 2, 180, 270),
|
|
254
|
+
],
|
|
255
|
+
'message-circle': [
|
|
256
|
+
arc(1.1328, 17.079, 2, -21.6245, 12.4142),
|
|
257
|
+
line(3.086, 17.509, 2.021, 20.799),
|
|
258
|
+
arc(3.0005, 21.0005, 1, -168.3779, -284.8626),
|
|
259
|
+
line(3.257, 21.967, 6.67, 20.969),
|
|
260
|
+
arc(7.0591, 22.9308, 2, -101.2192, -69.2104),
|
|
261
|
+
arc(12.0003, 12.0003, 10, 115.0325, -205.7324),
|
|
262
|
+
],
|
|
263
|
+
'message-square': [
|
|
264
|
+
arc(20, 17, 2, 0, 90),
|
|
265
|
+
line(20, 19, 6.828, 19),
|
|
266
|
+
arc(6.8284, 21, 2, -90.0122, -135.0087),
|
|
267
|
+
line(5.414, 19.586, 3.212, 21.788),
|
|
268
|
+
arc(2.71, 21.2859, 0.71, 45.0052, 179.9926),
|
|
269
|
+
line(2, 21.286, 2, 5),
|
|
270
|
+
arc(4, 5, 2, 180, 270),
|
|
271
|
+
line(4, 3, 20, 3),
|
|
272
|
+
arc(20, 5, 2, -90, 0),
|
|
273
|
+
line(22, 5, 22, 17),
|
|
274
|
+
],
|
|
275
|
+
cloud: [
|
|
276
|
+
line(17.5, 19, 9, 19),
|
|
277
|
+
arc(9.0018, 12, 7, 90.0147, 343.3984),
|
|
278
|
+
line(15.71, 10, 17.5, 10),
|
|
279
|
+
arc(17.5, 14.5, 4.5, -90, 90),
|
|
280
|
+
],
|
|
281
|
+
puzzle: [
|
|
282
|
+
arc(16.0975, 3.6833, 1, 135.0289, 13.459),
|
|
283
|
+
arc(19.5008, 4.5, 2.5, -166.4917, 76.5107),
|
|
284
|
+
arc(20.3167, 7.9035, 1, -103.459, -225.0289),
|
|
285
|
+
line(19.61, 8.611, 21.293, 10.293),
|
|
286
|
+
arc(19.5861, 12, 2.414, -45.0015, 45.0015),
|
|
287
|
+
line(21.293, 13.707, 19.61, 15.39),
|
|
288
|
+
arc(18.9025, 14.6833, 1, 44.9711, 166.541),
|
|
289
|
+
arc(15.4992, 15.5, 2.5, -13.5083, -256.5107),
|
|
290
|
+
arc(14.6833, 18.9035, 1, -76.541, 45.0289),
|
|
291
|
+
line(15.39, 19.611, 13.707, 21.293),
|
|
292
|
+
arc(12, 19.5861, 2.414, 44.9985, 135.0015),
|
|
293
|
+
line(10.293, 21.293, 8.61, 19.61),
|
|
294
|
+
arc(7.9025, 20.3167, 1, -44.9711, -166.541),
|
|
295
|
+
arc(4.4992, 19.5, 2.5, 13.5083, 256.5107),
|
|
296
|
+
arc(3.6833, 16.0965, 1, 76.541, -45.0289),
|
|
297
|
+
line(4.39, 15.389, 2.707, 13.707),
|
|
298
|
+
arc(4.4139, 12, 2.414, 134.9985, 225.0015),
|
|
299
|
+
line(2.707, 10.293, 4.39, 8.61),
|
|
300
|
+
arc(5.0975, 9.3167, 1, -135.0289, -13.459),
|
|
301
|
+
arc(8.5008, 8.5, 2.5, 166.4917, -76.5107),
|
|
302
|
+
arc(9.3167, 5.0965, 1, 103.459, 225.0289),
|
|
303
|
+
line(8.61, 4.389, 10.293, 2.707),
|
|
304
|
+
arc(12, 4.4139, 2.414, -135.0015, -44.9985),
|
|
305
|
+
line(13.707, 2.707, 15.39, 4.39),
|
|
306
|
+
],
|
|
307
|
+
// without its stem line (Lucide's second piece)
|
|
308
|
+
club: [
|
|
309
|
+
arc(12, 7.51, 5.5, 16.2602, -196.2602),
|
|
310
|
+
arc(7.5026, 14.494, 5.5, -98.1803, -324.8563),
|
|
311
|
+
arc(16.5052, 14.5052, 5.5, 144.9978, -81.9018),
|
|
312
|
+
line(17.28, 9.06, 17.28, 9.05),
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
// ---- making the outlines comparable ----
|
|
316
|
+
const pointOnStroke = (stroke, progress) => strokePiece(stroke, 0, progress)[3];
|
|
317
|
+
const samplesOf = (strokes) => strokes.flatMap((stroke, strokeIndex) => Array.from({ length: SAMPLES_PER_STROKE }, (_, sample) => {
|
|
318
|
+
const progress = sample / SAMPLES_PER_STROKE;
|
|
319
|
+
return { point: pointOnStroke(stroke, progress), strokeIndex, progress };
|
|
320
|
+
}));
|
|
321
|
+
const transformStroke = (stroke, scale, offsetX, offsetY) => {
|
|
322
|
+
const move = ([x, y]) => [x * scale + offsetX, y * scale + offsetY];
|
|
323
|
+
switch (stroke.kind) {
|
|
324
|
+
case 'line': return { ...stroke, from: move(stroke.from), to: move(stroke.to) };
|
|
325
|
+
case 'arc': return { ...stroke, center: move(stroke.center), radius: stroke.radius * scale };
|
|
326
|
+
case 'cubic': return { kind: 'cubic', segment: stroke.segment.map(move) };
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
/** Splits a stroke at `progress` into two strokes of the same kind (arcs stay arcs, so they keep their segment rule). */
|
|
330
|
+
const splitStroke = (stroke, progress) => {
|
|
331
|
+
if (stroke.kind === 'arc') {
|
|
332
|
+
const splitAngle = stroke.startAngle + (stroke.endAngle - stroke.startAngle) * progress;
|
|
333
|
+
return [{ ...stroke, endAngle: splitAngle }, { ...stroke, startAngle: splitAngle }];
|
|
334
|
+
}
|
|
335
|
+
if (stroke.kind === 'line') {
|
|
336
|
+
const splitPoint = pointOnStroke(stroke, progress);
|
|
337
|
+
return [{ ...stroke, to: splitPoint }, { ...stroke, from: splitPoint }];
|
|
338
|
+
}
|
|
339
|
+
return [{ kind: 'cubic', segment: strokePiece(stroke, 0, progress) }, { kind: 'cubic', segment: strokePiece(stroke, progress, 1) }];
|
|
340
|
+
};
|
|
341
|
+
const fitToBox = (strokes) => {
|
|
342
|
+
const points = samplesOf(strokes).map(({ point }) => point);
|
|
343
|
+
const xValues = points.map(([x]) => x), yValues = points.map(([, y]) => y);
|
|
344
|
+
const minX = Math.min(...xValues), maxX = Math.max(...xValues), minY = Math.min(...yValues), maxY = Math.max(...yValues);
|
|
345
|
+
const scale = GRID_SIZE / Math.max(maxX - minX, maxY - minY);
|
|
346
|
+
return strokes.map((stroke) => transformStroke(stroke, scale, GRID_CENTER - ((minX + maxX) / 2) * scale, GRID_CENTER - ((minY + maxY) / 2) * scale));
|
|
347
|
+
};
|
|
348
|
+
/** Positive = clockwise on screen (y points down). */
|
|
349
|
+
const signedArea = (strokes) => {
|
|
350
|
+
const points = samplesOf(strokes).map(({ point }) => point);
|
|
351
|
+
return points.reduce((sum, [x, y], index) => {
|
|
352
|
+
const [nextX, nextY] = points[(index + 1) % points.length];
|
|
353
|
+
return sum + x * nextY - nextX * y;
|
|
354
|
+
}, 0);
|
|
355
|
+
};
|
|
356
|
+
const clockwise = (strokes) => (signedArea(strokes) >= 0 ? strokes : strokes.slice().reverse().map(reversedStroke));
|
|
357
|
+
/** Starts the closed outline where it crosses x = 12 highest up (splitting that stroke). */
|
|
358
|
+
const startAtTopMiddle = (strokes) => {
|
|
359
|
+
const samples = samplesOf(strokes);
|
|
360
|
+
let best = null;
|
|
361
|
+
for (let index = 0; index < samples.length; index++) {
|
|
362
|
+
const sample = samples[index], next = samples[(index + 1) % samples.length];
|
|
363
|
+
const [x, y] = sample.point, [nextX, nextY] = next.point;
|
|
364
|
+
if ((x - GRID_CENTER) * (nextX - GRID_CENTER) > 0 || x === nextX)
|
|
365
|
+
continue;
|
|
366
|
+
const fraction = (GRID_CENTER - x) / (nextX - x);
|
|
367
|
+
const crossingY = y + (nextY - y) * fraction;
|
|
368
|
+
if (best && crossingY >= best.y)
|
|
369
|
+
continue;
|
|
370
|
+
const nextProgress = next.strokeIndex === sample.strokeIndex ? next.progress : 1;
|
|
371
|
+
best = { strokeIndex: sample.strokeIndex, progress: sample.progress + (nextProgress - sample.progress) * fraction, y: crossingY };
|
|
372
|
+
}
|
|
373
|
+
if (!best)
|
|
374
|
+
return strokes;
|
|
375
|
+
const { strokeIndex, progress } = best;
|
|
376
|
+
const before = strokes.slice(0, strokeIndex), after = strokes.slice(strokeIndex + 1);
|
|
377
|
+
if (progress < 1e-6)
|
|
378
|
+
return [...strokes.slice(strokeIndex), ...before];
|
|
379
|
+
if (progress > 1 - 1e-6)
|
|
380
|
+
return [...after, ...before, strokes[strokeIndex]];
|
|
381
|
+
const [firstPart, secondPart] = splitStroke(strokes[strokeIndex], progress);
|
|
382
|
+
return [secondPart, ...after, ...before, firstPart];
|
|
383
|
+
};
|
|
384
|
+
// ---- roundness: 1 = Lucide's own corners, 0 = sharp ----
|
|
385
|
+
/** Lucide's corner arcs are small (radius 0.56 … 2.43); bigger arcs are part of the shape (circle, moon, droplet). */
|
|
386
|
+
const LARGEST_CORNER_RADIUS = 2.5;
|
|
387
|
+
const crossingOfLines = (point, direction, otherPoint, otherDirection) => {
|
|
388
|
+
const determinant = direction[0] * otherDirection[1] - direction[1] * otherDirection[0];
|
|
389
|
+
if (Math.abs(determinant) < 1e-9)
|
|
390
|
+
return null;
|
|
391
|
+
const along = ((otherPoint[0] - point[0]) * otherDirection[1] - (otherPoint[1] - point[1]) * otherDirection[0]) / determinant;
|
|
392
|
+
return [point[0] + direction[0] * along, point[1] + direction[1] * along];
|
|
393
|
+
};
|
|
394
|
+
/** Moves a stroke's start (or end) to `point`; a cubic's neighbouring control point moves along, so its direction there stays. */
|
|
395
|
+
const withMovedEnd = (stroke, which, point) => {
|
|
396
|
+
if (stroke.kind === 'line')
|
|
397
|
+
return which === 'start' ? { ...stroke, from: point } : { ...stroke, to: point };
|
|
398
|
+
if (stroke.kind !== 'cubic')
|
|
399
|
+
return stroke;
|
|
400
|
+
const [start, control1, control2, end] = stroke.segment;
|
|
401
|
+
if (which === 'start') {
|
|
402
|
+
const shift = [point[0] - start[0], point[1] - start[1]];
|
|
403
|
+
return { kind: 'cubic', segment: [point, [control1[0] + shift[0], control1[1] + shift[1]], control2, end] };
|
|
404
|
+
}
|
|
405
|
+
const shift = [point[0] - end[0], point[1] - end[1]];
|
|
406
|
+
return { kind: 'cubic', segment: [start, control1, [control2[0] + shift[0], control2[1] + shift[1]], point] };
|
|
407
|
+
};
|
|
408
|
+
/**
|
|
409
|
+
* Every corner arc (small, between two lines / cubics) shrinks towards the sharp corner where its two neighbours'
|
|
410
|
+
* directions meet: radius × roundness, angles unchanged. At 0 the arc is a point on the corner. Same strokes in the
|
|
411
|
+
* same order at any roundness, so morphs keep working.
|
|
412
|
+
*/
|
|
413
|
+
const withCornerRoundness = (strokes, roundness) => {
|
|
414
|
+
const result = strokes.slice();
|
|
415
|
+
strokes.forEach((stroke, index) => {
|
|
416
|
+
const previousIndex = (index - 1 + strokes.length) % strokes.length, nextIndex = (index + 1) % strokes.length;
|
|
417
|
+
const isEdge = (neighbour) => neighbour.kind === 'line' || neighbour.kind === 'cubic';
|
|
418
|
+
if (stroke.kind !== 'arc' || stroke.radius > LARGEST_CORNER_RADIUS)
|
|
419
|
+
return;
|
|
420
|
+
if (!isEdge(strokes[previousIndex]) || !isEdge(strokes[nextIndex]))
|
|
421
|
+
return;
|
|
422
|
+
const turning = Math.sign(stroke.endAngle - stroke.startAngle);
|
|
423
|
+
const tangent = (angle) => [-Math.sin(angle * DEGREE) * turning, Math.cos(angle * DEGREE) * turning];
|
|
424
|
+
const start = pointOnStroke(stroke, 0), end = pointOnStroke(stroke, 1);
|
|
425
|
+
const corner = crossingOfLines(start, tangent(stroke.startAngle), end, tangent(stroke.endAngle));
|
|
426
|
+
if (!corner)
|
|
427
|
+
return;
|
|
428
|
+
const towardsCorner = ([x, y]) => [corner[0] + (x - corner[0]) * roundness, corner[1] + (y - corner[1]) * roundness];
|
|
429
|
+
result[index] = { ...stroke, center: towardsCorner(stroke.center), radius: stroke.radius * roundness };
|
|
430
|
+
result[previousIndex] = withMovedEnd(result[previousIndex], 'end', towardsCorner(start));
|
|
431
|
+
result[nextIndex] = withMovedEnd(result[nextIndex], 'start', towardsCorner(end));
|
|
432
|
+
});
|
|
433
|
+
return result;
|
|
434
|
+
};
|
|
435
|
+
/** The shape set at a roundness (0 = sharp … 1 = Lucide's own corners); its strokes are built on first use. */
|
|
436
|
+
const shapeSetWithRoundness = (roundness) => {
|
|
437
|
+
let strokes = null;
|
|
438
|
+
return {
|
|
439
|
+
get strokes() {
|
|
440
|
+
strokes ??= Object.fromEntries(Object.entries(lucideShapeOutlines).map(([name, outline]) => [name, startAtTopMiddle(clockwise(fitToBox(withCornerRoundness(outline, roundness))))]));
|
|
441
|
+
return strokes;
|
|
442
|
+
},
|
|
443
|
+
alternativeStrokes: {},
|
|
444
|
+
pairLayouts: {},
|
|
445
|
+
spins: {},
|
|
446
|
+
segmentCount: SEGMENT_COUNT,
|
|
447
|
+
// shapes are filled (clip), not stroked; these only matter for the outlined band's corners
|
|
448
|
+
strokeWidth: 0,
|
|
449
|
+
lineCap: 'butt',
|
|
450
|
+
lineJoin: 'miter',
|
|
451
|
+
withRoundness: shapeSetWithRoundness,
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
/** The shape set of clip(): states `circle`, `ellipse`, `heart` … `puzzle` (see `lucideShapeOutlines`). */
|
|
455
|
+
export const clipShapes = shapeSetWithRoundness(1);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*! morphico v0.5.0 | MIT License, Copyright (c) 2026 Raycho Ivanov (see LICENSE) */
|
|
2
|
+
// Digit set "slim": thin, tall, round digits, drafted from the reference images in icons/slim/
|
|
3
|
+
// (the set format is described in ../types.ts).
|
|
4
|
+
//
|
|
5
|
+
// The images (94 px tall, 6 px stroke, 50 px wide; 5.5 px = 1 grid unit) reveal a strict construction, so the set
|
|
6
|
+
// is written directly in grid units: everything round is an arc of two equal circles of radius 4, stacked and
|
|
7
|
+
// touching at 12,12 — TOP at 12,8 and BOTTOM at 12,16 — inside the box x 8…16, y 4…20.
|
|
8
|
+
// - 8 = both circles; 3 = arcs of both; 2's bowl = TOP; 5's bowl = half of BOTTOM;
|
|
9
|
+
// 6 = arc of TOP + straight left side + a loop (a stadium a bit taller than BOTTOM); 9 = 6 turned 180° around 12,12
|
|
10
|
+
// - 0 = stadium; the 4 is wider and its top point sits lower (y ≈ 5.2), as in the images (its values nudged to the best pixel match)
|
|
11
|
+
// - round ends and corners
|
|
12
|
+
import { arc, line, pointOnCircle, roundedPolyline } from '../geometry.js';
|
|
13
|
+
import { withTextCharacters } from './characters.js';
|
|
14
|
+
const RADIUS = 4;
|
|
15
|
+
const TOP = [12, 8];
|
|
16
|
+
const BOTTOM = [12, 16];
|
|
17
|
+
const topArc = (startAngle, endAngle) => arc(...TOP, RADIUS, startAngle, endAngle);
|
|
18
|
+
const bottomArc = (startAngle, endAngle) => arc(...BOTTOM, RADIUS, startAngle, endAngle);
|
|
19
|
+
/** Where the 2's diagonal leaves its bowl: tangent from the corner at 8,20 (a 3-4-5 triangle, hence the round angle). */
|
|
20
|
+
const TWO_DIAGONAL_ANGLE = 36.8699;
|
|
21
|
+
/** Where the 6's top arc ends on the right (measured: slightly below the circle's right point). */
|
|
22
|
+
const SIX_HOOK_ANGLE = 8;
|
|
23
|
+
const turnedAround = (stroke) => {
|
|
24
|
+
const turn = ([x, y]) => [24 - x, 24 - y];
|
|
25
|
+
switch (stroke.kind) {
|
|
26
|
+
case 'line': return line(...turn(stroke.to), ...turn(stroke.from));
|
|
27
|
+
case 'arc': return arc(...turn(stroke.center), stroke.radius, stroke.endAngle + 180, stroke.startAngle + 180);
|
|
28
|
+
case 'cubic': return { kind: 'cubic', segment: [turn(stroke.segment[3]), turn(stroke.segment[2]), turn(stroke.segment[1]), turn(stroke.segment[0])] };
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/** The 6's loop is taller than the bottom circle: a stadium from this height down to 20 (measured). */
|
|
32
|
+
const SIX_LOOP_TOP = 10.91;
|
|
33
|
+
// the 6: top arc from the right over to the left, down the left side, round the loop and back into the left side
|
|
34
|
+
const SIX = [
|
|
35
|
+
topArc(SIX_HOOK_ANGLE, -180),
|
|
36
|
+
...roundedPolyline([[8, 8], [8, 20], [16, 20], [16, SIX_LOOP_TOP], [8, SIX_LOOP_TOP], [8, SIX_LOOP_TOP + RADIUS]], [0, RADIUS, RADIUS, RADIUS, RADIUS, 0]),
|
|
37
|
+
];
|
|
38
|
+
// '-' '/' '.' ',' ':' are added by withTextCharacters (characters.ts)
|
|
39
|
+
export const digitsSlim = withTextCharacters({
|
|
40
|
+
segmentCount: 16,
|
|
41
|
+
strokeWidth: 6 / 5.5,
|
|
42
|
+
lineCap: 'round',
|
|
43
|
+
lineJoin: 'round',
|
|
44
|
+
strokes: {
|
|
45
|
+
// stadium, from the top middle counter-clockwise
|
|
46
|
+
0: roundedPolyline([[12, 4], [8, 4], [8, 20], [16, 20], [16, 4], [12, 4]], RADIUS),
|
|
47
|
+
// slanted flag up to the top of the stem, stem down
|
|
48
|
+
1: [line(9.64, 9.1, 14.45, 4), line(14.45, 4, 14.45, 20)],
|
|
49
|
+
// bowl over the top, tangent diagonal down to the corner, base
|
|
50
|
+
2: [
|
|
51
|
+
topArc(180, TWO_DIAGONAL_ANGLE + 360),
|
|
52
|
+
line(...pointOnCircle(...TOP, RADIUS, TWO_DIAGONAL_ANGLE), 8, 20),
|
|
53
|
+
line(8, 20, 16, 20),
|
|
54
|
+
],
|
|
55
|
+
// top arc from the left round to the middle, bottom arc from the middle round to the left
|
|
56
|
+
3: [topArc(180, 450), bottomArc(270, 548)],
|
|
57
|
+
// one stroke: crossbar right → left, diagonal up to the top, stem down
|
|
58
|
+
4: [line(16.96, 16.03, 7, 16.03), line(7, 16.03, 14.85, 5.17), line(14.85, 5.17, 14.85, 20)],
|
|
59
|
+
// top bar right → left, left side down, middle bar, bowl (half of the bottom circle), bottom bar
|
|
60
|
+
5: [line(16, 4, 8, 4), line(8, 4, 8, 12), line(8, 12, 12, 12), bottomArc(270, 450), line(12, 20, 8, 20)],
|
|
61
|
+
6: SIX,
|
|
62
|
+
// top bar → slanted stroke down
|
|
63
|
+
7: [line(8.09, 4, 15.89, 4), line(15.89, 4, 12.09, 20)],
|
|
64
|
+
// closed figure-eight: top circle clockwise, bottom circle counter-clockwise, meeting where they touch
|
|
65
|
+
8: [topArc(90, 450), bottomArc(270, -90)],
|
|
66
|
+
// the 6 turned 180°, drawn the other way round
|
|
67
|
+
9: SIX.map(turnedAround).reverse(),
|
|
68
|
+
},
|
|
69
|
+
alternativeStrokes: {
|
|
70
|
+
// the 8 traced like the 0 (as in the round set): from the top counter-clockwise — left half of TOP down to the waist,
|
|
71
|
+
// once around BOTTOM, right half of TOP back up
|
|
72
|
+
'8-like-0': [topArc(270, 90), bottomArc(270, -90), topArc(90, -90)],
|
|
73
|
+
// the 8 as two clockwise loops from the waist (as in the round set): the 3's ends slide along the circles into the waist
|
|
74
|
+
'8-two-loops': [topArc(90, 450), bottomArc(270, 630)],
|
|
75
|
+
// the usual figure-eight starting on the right side of BOTTOM (as in the round set, where the 4's crossbar starts)
|
|
76
|
+
'8-from-right': [bottomArc(0, -90), topArc(90, 450), bottomArc(270, 0)],
|
|
77
|
+
// mirrored figure-eight from the waist (as in the round set): TOP counter-clockwise, BOTTOM clockwise — like the 5
|
|
78
|
+
'8-mirrored': [topArc(90, -270), bottomArc(270, 630)],
|
|
79
|
+
},
|
|
80
|
+
// optimizer fits (tools/check.ts) for the pairs the user rated bad / midd
|
|
81
|
+
pairLayouts: {
|
|
82
|
+
0: {
|
|
83
|
+
1: { startSegment: 6, reversed: true },
|
|
84
|
+
2: { startSegment: 5, reversed: true },
|
|
85
|
+
3: { startSegment: 4, reversed: true },
|
|
86
|
+
4: { startSegment: 10, reversed: true },
|
|
87
|
+
5: { startSegment: 5, reversed: true },
|
|
88
|
+
7: { startSegment: 5, reversed: true },
|
|
89
|
+
9: { startSegment: 5, reversed: true },
|
|
90
|
+
},
|
|
91
|
+
1: {
|
|
92
|
+
0: { breakpoints: [0, 0, 0, 0, 0.1875, 0.5208, 0.7708, 1, 1.2708, 1.4167, 1.5833, 1.75, 1.9375, 1.9375, 1.9375, 1.9375, 2] },
|
|
93
|
+
},
|
|
94
|
+
2: {
|
|
95
|
+
4: { breakpoints: [0, 0, 0.3958, 0.6458, 0.6458, 0.6667, 0.875, 1, 1.1458, 1.1458, 2, 2.875, 2.875, 2.875, 2.875, 2.875, 3] },
|
|
96
|
+
// 2 → 6 spins (see spins): in the turning frame it is 2 → 9 (same layout as 2 → 9)
|
|
97
|
+
6: { breakpoints: [0, 0, 0, 0, 0, 0, 0.2083, 0.4167, 0.625, 0.8333, 1, 1.1458, 1.3333, 1.6042, 1.8125, 2, 3] },
|
|
98
|
+
9: { breakpoints: [0, 0, 0, 0, 0, 0, 0.2083, 0.4167, 0.625, 0.8333, 1, 1.1458, 1.3333, 1.6042, 1.8125, 2, 3] },
|
|
99
|
+
0: { breakpoints: [0, 0, 0, 0.1458, 0.2708, 0.4167, 0.625, 0.8333, 1, 1.1458, 1.3333, 1.5833, 1.8125, 1.8333, 1.8333, 2, 3] },
|
|
100
|
+
},
|
|
101
|
+
3: {
|
|
102
|
+
4: { breakpoints: [0, 0, 0.3333, 0.5208, 0.5208, 0.6042, 0.7708, 0.8333, 1, 1.1667, 1.2083, 1.3958, 1.4792, 1.4792, 1.4792, 1.6875, 2] },
|
|
103
|
+
0: { breakpoints: [0, 0, 0.1042, 0.2292, 0.3333, 0.5, 0.6667, 1, 1.2292, 1.3125, 1.4792, 1.6458, 1.75, 1.8542, 2, 2, 2] },
|
|
104
|
+
},
|
|
105
|
+
4: {
|
|
106
|
+
6: { breakpoints: [0, 0.25, 0.25, 0.25, 0.4375, 1, 1.2917, 1.3542, 1.5208, 1.9583, 2, 2.1875, 2.1875, 2.1875, 2.1875, 2.1875, 3] },
|
|
107
|
+
// as in the round set: drawn backwards, so the stem's end meets the crossbar's start on the 8's right side
|
|
108
|
+
8: { reversed: true },
|
|
109
|
+
3: { breakpoints: [0, 1, 1.9792, 1.9792, 2, 2.1458, 2.2917, 2.4375, 2.4583, 2.5, 2.625, 2.7917, 2.9375, 2.9375, 2.9375, 2.9375, 3] },
|
|
110
|
+
2: { breakpoints: [0, 1, 1.9583, 1.9583, 2, 2.0625, 2.2292, 2.3542, 2.4792, 2.6042, 2.75, 2.875, 3, 3, 3, 3, 3] },
|
|
111
|
+
0: { breakpoints: [0, 0.5417, 0.7083, 0.8333, 1, 1.2083, 1.375, 1.5208, 1.6667, 1.8333, 1.9583, 2, 2.1875, 2.375, 2.5625, 2.7083, 3] },
|
|
112
|
+
},
|
|
113
|
+
5: {
|
|
114
|
+
0: { breakpoints: [0, 1, 1.1875, 1.1875, 1.1875, 1.1875, 2, 3, 3.25, 3.2917, 3.5, 3.75, 4, 4.5208, 4.8958, 5, 5] },
|
|
115
|
+
},
|
|
116
|
+
6: {
|
|
117
|
+
8: { breakpoints: [0, 0.4583, 0.5208, 0.5208, 0.5417, 1, 1.875, 2, 2.5, 3, 3.5, 4, 5, 5.4583, 5.9792, 6, 7] },
|
|
118
|
+
4: { breakpoints: [0, 0, 0, 0, 0, 0.0625, 0.25, 0.5417, 1, 1.3542, 1.5, 2, 3, 4, 5, 6, 7], reversed: true },
|
|
119
|
+
// turned by 180° this is the 9 laid out for the 2 (the 2 ↔ 9 fit, mirrored onto the 6)
|
|
120
|
+
2: { breakpoints: [0, 0.2917, 0.2917, 0.5417, 1, 1.5833, 1.6875, 2, 2.5, 3, 3.5, 4, 4, 4, 5, 6, 7], reversed: true },
|
|
121
|
+
},
|
|
122
|
+
7: {
|
|
123
|
+
0: { breakpoints: [0, 0, 0, 0.0417, 0.25, 0.4792, 1, 1.25, 1.3958, 1.5417, 1.7083, 1.9167, 1.9375, 1.9375, 1.9375, 1.9375, 2] },
|
|
124
|
+
},
|
|
125
|
+
8: {
|
|
126
|
+
// as in round, square and condensed: the 0's left and right points move in to the waist
|
|
127
|
+
0: { tracing: '8-like-0', breakpoints: [0, 0.1667, 0.3333, 0.5208, 0.625, 1, 1.25, 1.3333, 1.4167, 1.5, 1.625, 1.75, 2, 2.375, 2.4792, 2.75, 3] },
|
|
128
|
+
// the user's idea from the round set: the 4's two ends meet on the right side of the bottom circle
|
|
129
|
+
4: { tracing: '8-from-right', breakpoints: [0, 0, 0.1667, 0.5, 1, 1.25, 1.5, 1.7083, 1.9167, 2, 2.125, 2.2292, 2.3333, 2.3542, 2.6667, 2.9792, 3] },
|
|
130
|
+
6: { breakpoints: [0, 0.25, 0.5, 0.5, 0.75, 1, 1.1875, 1.25, 1.375, 1.5, 1.625, 1.75, 1.7917, 1.8958, 2, 2, 2] },
|
|
131
|
+
// the two-loop tracing fits the 9 best (no spin needed)
|
|
132
|
+
9: { tracing: '8-two-loops', breakpoints: [0, 0, 0, 0.1042, 0.2083, 0.25, 0.375, 0.5, 0.625, 0.75, 0.8125, 1, 1.25, 1.375, 1.5, 1.75, 2] },
|
|
133
|
+
// the user's idea from the round set: both ends of the 5 meet at the waist — bars → top loop, bowl → bottom loop
|
|
134
|
+
5: { tracing: '8-mirrored', breakpoints: [0, 0.25, 0.5, 0.5833, 0.625, 0.6875, 0.8125, 0.875, 0.9167, 1, 1.1042, 1.2083, 1.2917, 1.3958, 1.5, 1.75, 2] },
|
|
135
|
+
// the user's idea from the round set: both ends of the 3 slide along the circles into the waist
|
|
136
|
+
3: { tracing: '8-two-loops', breakpoints: [0, 0.25, 0.4583, 0.5208, 0.625, 0.7083, 0.8125, 0.8958, 1, 1.1042, 1.1875, 1.2917, 1.3958, 1.4792, 1.5625, 1.75, 2] },
|
|
137
|
+
},
|
|
138
|
+
9: {
|
|
139
|
+
2: { breakpoints: [0, 1, 2, 3, 3, 3, 3.5, 4, 4.5, 5, 5.3125, 5.4167, 6, 6.4583, 6.7083, 6.7083, 7] },
|
|
140
|
+
0: { breakpoints: [0, 1, 2, 3, 3.7292, 4, 4.5, 5, 5.3333, 5.6667, 6, 6.2292, 6.4792, 6.6458, 6.7917, 7, 7] },
|
|
141
|
+
// turned back by 180° this is exactly the 6 (same segment split, same direction) → a pure spin
|
|
142
|
+
6: { breakpoints: [0, 0.5, 1, 1.5, 2, 3, 3.5, 4, 4.5, 5, 16 / 3, 17 / 3, 6, 6.25, 6.5, 6.75, 7], reversed: true },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
// 9 is exactly 6 turned 180°; 2 → 6 also turns (it is 2 → 9 in the turning frame)
|
|
146
|
+
spins: { 2: { 6: 180 }, 6: { 9: 180, 2: -180 }, 9: { 6: -180 } },
|
|
147
|
+
textLayout: { cellWidth: 9.5, spacing: 4, baselineY: 20, colonDotsY: [8, 16] },
|
|
148
|
+
});
|