morphico 0.4.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.
Files changed (72) hide show
  1. package/README.md +62 -45
  2. package/dist/esm/all.js +50 -0
  3. package/dist/esm/clip.js +283 -0
  4. package/dist/esm/engine.js +25 -0
  5. package/dist/esm/face.js +153 -0
  6. package/dist/esm/geometry.js +459 -0
  7. package/dist/esm/icon.js +41 -0
  8. package/dist/esm/index.js +50 -0
  9. package/dist/esm/morph.js +107 -0
  10. package/dist/esm/roundness.js +37 -0
  11. package/dist/esm/sets/battery.js +87 -0
  12. package/dist/esm/sets/bell.js +101 -0
  13. package/dist/esm/sets/characters.js +43 -0
  14. package/dist/esm/sets/check-mark.js +74 -0
  15. package/dist/esm/sets/checkbox.js +91 -0
  16. package/dist/esm/sets/chevron.js +70 -0
  17. package/dist/esm/sets/condensed.js +137 -0
  18. package/dist/esm/sets/copy.js +87 -0
  19. package/dist/esm/sets/eye.js +105 -0
  20. package/dist/esm/sets/faces.js +482 -0
  21. package/dist/esm/sets/folder.js +112 -0
  22. package/dist/esm/sets/handwritten.js +136 -0
  23. package/dist/esm/sets/lock.js +75 -0
  24. package/dist/esm/sets/lucide.js +66 -0
  25. package/dist/esm/sets/media.js +109 -0
  26. package/dist/esm/sets/menu.js +84 -0
  27. package/dist/esm/sets/mic.js +102 -0
  28. package/dist/esm/sets/radio.js +84 -0
  29. package/dist/esm/sets/round.js +244 -0
  30. package/dist/esm/sets/shapes.js +455 -0
  31. package/dist/esm/sets/slim.js +148 -0
  32. package/dist/esm/sets/square.js +149 -0
  33. package/dist/esm/sets/theme.js +107 -0
  34. package/dist/esm/sets/toggle.js +73 -0
  35. package/dist/esm/sets/volume.js +89 -0
  36. package/dist/esm/sets/weather.js +691 -0
  37. package/dist/esm/sets/wifi.js +68 -0
  38. package/dist/esm/svg.js +31 -0
  39. package/dist/esm/text.js +88 -0
  40. package/dist/esm/types.js +3 -0
  41. package/dist/morphico.js +3764 -3750
  42. package/dist/types/{iconSets.d.ts → all.d.ts} +0 -2
  43. package/dist/types/clip.d.ts +4 -11
  44. package/dist/types/face.d.ts +2 -6
  45. package/dist/types/icon.d.ts +4 -9
  46. package/dist/types/index.d.ts +1 -3
  47. package/dist/types/roundness.d.ts +8 -0
  48. package/dist/types/sets/battery.d.ts +1 -1
  49. package/dist/types/sets/bell.d.ts +1 -1
  50. package/dist/types/sets/checkbox.d.ts +1 -1
  51. package/dist/types/sets/chevron.d.ts +1 -1
  52. package/dist/types/sets/condensed.d.ts +1 -1
  53. package/dist/types/sets/copy.d.ts +1 -1
  54. package/dist/types/sets/eye.d.ts +1 -1
  55. package/dist/types/sets/folder.d.ts +1 -1
  56. package/dist/types/sets/handwritten.d.ts +1 -1
  57. package/dist/types/sets/lock.d.ts +1 -1
  58. package/dist/types/sets/media.d.ts +1 -1
  59. package/dist/types/sets/menu.d.ts +1 -1
  60. package/dist/types/sets/mic.d.ts +1 -1
  61. package/dist/types/sets/radio.d.ts +1 -1
  62. package/dist/types/sets/round.d.ts +1 -1
  63. package/dist/types/sets/slim.d.ts +1 -1
  64. package/dist/types/sets/square.d.ts +1 -1
  65. package/dist/types/sets/theme.d.ts +1 -1
  66. package/dist/types/sets/toggle.d.ts +1 -1
  67. package/dist/types/sets/volume.d.ts +1 -1
  68. package/dist/types/sets/weather.d.ts +1 -1
  69. package/dist/types/sets/wifi.d.ts +1 -1
  70. package/dist/types/text.d.ts +8 -13
  71. package/package.json +95 -3
  72. package/dist/morphico.mjs +0 -4642
@@ -0,0 +1,149 @@
1
+ /*! morphico v0.5.0 | MIT License, Copyright (c) 2026 Raycho Ivanov (see LICENSE) */
2
+ // Digit set "square": rounded-square "techno" digits, drafted from the reference images in icons/square/
3
+ // (the set format is described in ../types.ts).
4
+ //
5
+ // The images are tiny (every digit 28 px tall, 4 px stroke), so the shapes are written on the images' own pixel
6
+ // grid and converted: x/y are the stroke's middle line in image pixels, measured from the digit's ink box
7
+ // (y 2…26 = top to bottom middle line; x 2 = middle of a stroke on the left edge). 1.5 image px = 1 grid unit.
8
+ // - straight strokes with rounded corners (roundedPolyline), round ends
9
+ // - 3 and 5 share the same right-side bowls (corner radius 5 px); 8 = two stacked rounded rectangles sharing
10
+ // the middle bar; 9 = 6 turned 180° around 12,12
11
+ // - T-junctions (1's foot, 3's middle bar, 4's crossbar) are drawn out and back over themselves
12
+ import { roundedPolyline } from '../geometry.js';
13
+ import { withTextCharacters } from './characters.js';
14
+ const PIXELS_PER_UNIT = 1.5;
15
+ /** The images' stroke is 4 px ≈ 2.67 units; thinner by the user's choice. */
16
+ const STROKE_WIDTH = 1.6;
17
+ /** Image pixels (middle line, measured from the ink box of a digit `inkWidth` px wide) → grid units. */
18
+ const fromImage = (inkWidth) => ([x, y]) => [12 + (x - inkWidth / 2) / PIXELS_PER_UNIT, 4 + (y - 2) / PIXELS_PER_UNIT];
19
+ /** A rounded polyline given in image pixels; radii in image pixels too. */
20
+ const drawn = (inkWidth, points, radii) => roundedPolyline(points.map(fromImage(inkWidth)), Array.isArray(radii) ? radii.map((radius) => radius / PIXELS_PER_UNIT) : radii / PIXELS_PER_UNIT);
21
+ const turnedAround = ([x, y]) => [24 - x, 24 - y];
22
+ // corner radii (image px), each the best pixel match with the images; bowls are exact half-circles
23
+ const ZERO_RADIUS = 8; // half the width: the 0 is a stadium
24
+ const THREE_BOWL_RADIUS = 6; // half of each bowl's height (12 px)
25
+ const FIVE_BOWL_RADIUS = 6.5; // half of the bowl's height (13 px)
26
+ const SIX_RADIUS = 7;
27
+ const EIGHT_RADIUS = 6;
28
+ // the 6: hook from the top right, round the left side, then the closed bottom loop back to the left side
29
+ const SIX_POINTS = [[15, 2], [2, 2], [2, 26], [19, 26], [19, 12], [2, 12]];
30
+ const SIX_RADII = [0, SIX_RADIUS, SIX_RADIUS, SIX_RADIUS, SIX_RADIUS, 0];
31
+ // '-' '/' '.' ',' ':' are added by withTextCharacters (characters.ts)
32
+ export const digitsSquare = withTextCharacters({
33
+ segmentCount: 20,
34
+ strokeWidth: STROKE_WIDTH,
35
+ lineCap: 'round',
36
+ lineJoin: 'round',
37
+ strokes: {
38
+ // closed rounded rectangle, from the top middle counter-clockwise
39
+ 0: drawn(20, [[10, 2], [2, 2], [2, 26], [18, 26], [18, 2], [10, 2]], ZERO_RADIUS),
40
+ // flag → stem down → foot to the left and back across to the right
41
+ 1: drawn(16, [[2, 2], [8, 2], [8, 26], [2, 26], [14, 26]], 0),
42
+ // top bar → right side → diagonal down to the left → left side → base
43
+ 2: drawn(20, [[3, 2], [18, 2], [18, 9.25], [2, 17.25], [2, 26], [18, 26]], [0, 2, 4.5, 4.5, 0, 0]),
44
+ // top bar → upper bowl → middle bar out to the left and back → lower bowl → bottom bar
45
+ 3: drawn(20, [[2, 2], [18, 2], [18, 14], [6, 14], [18, 14], [18, 26], [2, 26]], [0, THREE_BOWL_RADIUS, THREE_BOWL_RADIUS, 0, THREE_BOWL_RADIUS, THREE_BOWL_RADIUS, 0]),
46
+ // left stem down → crossbar to its right end and back to the right stem → down to the bottom and back up to the top
47
+ 4: drawn(24, [[2, 2], [2, 18], [22, 18], [18, 18], [18, 26], [18, 2]], [0, 1, 0, 0, 0, 0]),
48
+ // top bar right → left, down the left side, middle bar → bowl → bottom bar
49
+ 5: drawn(20, [[16, 2], [2, 2], [2, 13], [18, 13], [18, 26], [2, 26]], [0, 0, 0, FIVE_BOWL_RADIUS, FIVE_BOWL_RADIUS, 0]),
50
+ 6: drawn(21, SIX_POINTS, SIX_RADII),
51
+ // top bar → diagonal down to the left
52
+ 7: drawn(20, [[2, 2], [17.5, 2], [3.7, 26]], 0),
53
+ // from the waist: top rectangle (left side up, clockwise), along the middle bar, bottom rectangle (counter-clockwise)
54
+ 8: drawn(20, [[7, 14], [3, 14], [3, 2], [17, 2], [17, 14], [2, 14], [2, 26], [18, 26], [18, 14], [7, 14]], [0, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, 0]),
55
+ // the 6 turned 180°, drawn the other way round (loop first, then the hook)
56
+ 9: roundedPolyline(SIX_POINTS.map(fromImage(21)).map(turnedAround).reverse(), [...SIX_RADII].reverse().map((radius) => radius / PIXELS_PER_UNIT)),
57
+ },
58
+ alternativeStrokes: {
59
+ // the 8 traced like the 0 (from the top, counter-clockwise): left side of the top loop down to the middle of
60
+ // the middle bar, once around the bottom loop back to the middle, right side of the top loop back up
61
+ '8-like-0': drawn(20, [[10, 2], [3, 2], [3, 14], [10, 14], [2, 14], [2, 26], [18, 26], [18, 14], [10, 14], [17, 14], [17, 2], [10, 2]], [0, EIGHT_RADIUS, EIGHT_RADIUS, 0, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, 0, EIGHT_RADIUS, EIGHT_RADIUS, 0]),
62
+ // the 8 traced like the 3, starting and ending at the left part of the middle bar: around the top loop, along
63
+ // the middle bar out to the left and back, around the bottom loop
64
+ '8-like-3': drawn(20, [[9, 14], [3, 14], [3, 2], [17, 2], [17, 14], [8, 14], [18, 14], [18, 26], [2, 26], [2, 14], [8, 14]], [0, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, 0, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, EIGHT_RADIUS, 0]),
65
+ },
66
+ // optimizer fits (tools/check.ts) for the pairs the user rated bad / midd
67
+ pairLayouts: {
68
+ 0: {
69
+ 1: { startSegment: 5, reversed: true },
70
+ 2: { startSegment: 15 },
71
+ 3: { startSegment: 5, reversed: true },
72
+ 5: { startSegment: 5, reversed: true },
73
+ 7: { startSegment: 5, reversed: true },
74
+ 9: { startSegment: 7, reversed: true },
75
+ },
76
+ 1: {
77
+ 9: { breakpoints: [0, 0.2083, 0.2083, 0.4792, 1, 1, 1.25, 1.4583, 1.5417, 1.75, 2, 2, 2.0208, 2.25, 2.25, 2.25, 2.25, 2.25, 2.25, 3, 4] },
78
+ 0: { breakpoints: [0, 0, 0, 0, 0.5, 1, 1.0208, 1.0833, 1.2083, 1.3333, 1.5, 1.6667, 1.7917, 1.9167, 1.9792, 2, 2.5, 3, 3, 3, 4] },
79
+ },
80
+ 2: {
81
+ 4: { breakpoints: [0, 1, 1, 1, 1, 2, 3, 4, 4.2292, 4.2292, 4.2292, 5, 5.2292, 5.5208, 5.5208, 5.5208, 5.5208, 5.5208, 6, 7, 8] },
82
+ 6: { breakpoints: [0, 1, 2, 3, 4, 5, 5.0833, 5.2917, 6, 6.375, 7, 7.4375, 7.6042, 7.7708, 7.7708, 7.7708, 7.7708, 7.7708, 7.7708, 7.7708, 8] },
83
+ 9: { breakpoints: [0, 0.2708, 0.2708, 0.2708, 0.2708, 0.2708, 0.2708, 0.2708, 0.2708, 0.3958, 0.6458, 1, 2, 3, 4, 4.0833, 4.1875, 5, 6, 7, 8] },
84
+ 0: { breakpoints: [0, 1, 2, 2, 2, 3, 4, 4.6667, 4.7917, 5, 5.1042, 5.6667, 6, 7, 7.3333, 7.5, 7.6875, 7.8542, 7.9792, 8, 8] },
85
+ },
86
+ 3: {
87
+ 4: { breakpoints: [0, 1, 1.3958, 1.3958, 1.3958, 1.5833, 2, 2.4167, 2.6042, 2.6042, 2.6042, 3, 4, 5, 5.5625, 6, 6, 7, 7.9792, 8, 8] },
88
+ 6: { breakpoints: [0, 1, 1.1458, 1.1458, 1.1458, 1.1458, 1.1458, 1.1458, 2, 3, 4, 4, 5, 6, 6.2292, 6.2292, 6.2292, 6.2292, 6.2292, 7, 8] },
89
+ 9: { breakpoints: [0, 0.3333, 0.3333, 0.4792, 1, 1.7292, 2, 2.3125, 2.3125, 3, 4, 5, 5.5833, 5.5833, 5.5833, 6, 6.5, 7, 7.3542, 7.5625, 8] },
90
+ 0: { breakpoints: [0, 0, 0.0417, 0.2292, 0.4792, 0.8333, 1, 1.5, 2, 3, 4, 5, 6, 6.5, 7, 7.1667, 7.5208, 7.7708, 7.9583, 8, 8] },
91
+ },
92
+ 4: {
93
+ 7: { breakpoints: [0, 1, 2, 2.3125, 2.3125, 3, 4, 5, 5.7083, 5.7083, 5.7708, 5.8542, 5.9167, 6, 6, 6, 6, 6, 6, 6, 6], reversed: true },
94
+ 9: { breakpoints: [0, 1, 2, 3, 4, 5, 5.5625, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 5.7083, 6], reversed: true },
95
+ 3: { breakpoints: [0, 1, 2, 3, 4, 5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5208, 5.625, 5.75, 5.875, 5.9792, 6, 6, 6, 6], reversed: true },
96
+ 2: { breakpoints: [0, 1, 2, 2.1042, 2.1042, 2.1042, 2.1042, 2.1042, 2.2083, 2.2083, 3, 4, 5, 5.8125, 5.8125, 5.9375, 6, 6, 6, 6, 6], reversed: true },
97
+ },
98
+ 5: {
99
+ 6: { breakpoints: [0, 0.3542, 0.4792, 1, 1.3333, 1.625, 2, 2, 2, 2.0208, 2.2292, 3, 4, 4, 4, 4, 4, 4, 4, 5, 6] },
100
+ 8: { breakpoints: [0, 0.1042, 0.1042, 0.1042, 0.1042, 0.3542, 0.4583, 1, 1.5625, 2, 2.6667, 2.6667, 3, 4, 4.5, 5, 5.3542, 5.875, 5.8958, 5.8958, 6] },
101
+ 9: { breakpoints: [0, 1, 1.4792, 1.4792, 1.4792, 1.4792, 1.4792, 1.4792, 1.4792, 1.4792, 2, 3, 3.3958, 3.7708, 4, 4.3333, 4.6667, 5, 5.3542, 5.5417, 6] },
102
+ 0: { breakpoints: [0, 1, 1.2083, 1.2083, 1.2083, 2, 3, 3.1875, 3.2708, 3.3958, 3.5208, 4, 4.125, 4.5, 4.875, 5, 5.5208, 5.75, 5.9583, 6, 6] },
103
+ },
104
+ 6: {
105
+ 7: { breakpoints: [0, 0.375, 0.375, 0.375, 0.375, 0.375, 0.375, 0.375, 0.375, 0.375, 0.375, 0.375, 1, 2, 2.625, 3, 4, 5, 6, 7, 8] },
106
+ 8: { breakpoints: [0, 0.2292, 0.2292, 0.2292, 0.2292, 1, 2, 2.5833, 2.6458, 3, 3.5417, 4, 5, 5.4583, 6, 7, 7.3542, 7.3542, 7.6875, 7.8333, 8] },
107
+ 5: { breakpoints: [0, 0.5, 1, 1.4792, 1.6875, 2, 2.375, 2.375, 2.375, 2.375, 3, 4, 5, 5.9792, 5.9792, 5.9792, 5.9792, 5.9792, 6, 7, 8] },
108
+ 3: { breakpoints: [0, 0.1458, 0.1458, 0.1458, 0.1458, 0.1458, 0.1458, 0.1458, 0.1458, 1, 2, 3, 4, 5, 5.625, 5.625, 5.625, 5.625, 6, 7, 8] },
109
+ 2: { breakpoints: [0, 0, 0, 0, 1, 2, 2.8125, 2.8125, 3, 3.3125, 3.5208, 4, 5, 5.2292, 5.2292, 5.2292, 5.2292, 5.2292, 6, 7, 8] },
110
+ },
111
+ 7: {
112
+ 6: { breakpoints: [0, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 0.6667, 1, 1.5208, 1.7083, 1.7083, 1.7083, 1.7083, 1.7083, 1.7083, 2] },
113
+ 4: { breakpoints: [0, 0.9167, 0.9167, 0.9167, 0.9167, 0.9167, 0.9167, 1, 1.1042, 1.125, 1.2083, 1.2292, 1.2917, 1.5833, 1.5833, 1.5833, 1.6667, 1.7083, 1.7292, 1.7292, 2] },
114
+ 0: { breakpoints: [0, 0, 0.0208, 0.1458, 0.3125, 0.5208, 0.6875, 1, 1.1667, 1.2292, 1.375, 1.4792, 1.6042, 1.7083, 1.8125, 1.875, 1.875, 1.875, 1.875, 1.875, 2] },
115
+ },
116
+ 8: {
117
+ 5: { startSegment: 11, reversed: true },
118
+ 6: { startSegment: 4 },
119
+ 7: { tracing: '8-like-3', breakpoints: [0, 1, 1.8542, 2, 3, 3.2083, 3.3958, 3.5208, 3.5833, 3.6875, 4, 4.3542, 4.7708, 5, 6, 7, 8, 9, 10, 11, 12] },
120
+ 9: { tracing: '8-like-3', breakpoints: [0, 0, 0, 0, 0.3542, 0.5833, 1, 1.2083, 1.5625, 2, 3, 3.375, 4, 5, 6, 7, 8, 9, 10, 11, 12] },
121
+ 4: { tracing: '8-like-0', breakpoints: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 9, 9, 10, 11, 12, 13, 14, 15] },
122
+ // the user's idea: both ends of the 3 move to the left tip of its middle bar, where the 8's loops close
123
+ 3: {
124
+ tracing: '8-like-3',
125
+ breakpoints: [0, 1, 2, 3, 3.4375, 3.7083, 4, 4.2917, 4.5625, 5, 5.7292, 6, 6.1458, 7, 7.5, 8, 8.5, 9, 10, 11, 12],
126
+ },
127
+ // the user's idea: the 0's left and right sides move in to the middle of the 8's middle bar
128
+ 0: {
129
+ tracing: '8-like-0',
130
+ breakpoints: [0, 1, 1.6042, 2, 3, 4, 5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 10, 11, 12, 13, 13.3958, 14, 15],
131
+ },
132
+ },
133
+ 9: {
134
+ 5: { breakpoints: [0, 1, 2, 2.1875, 2.1875, 2.1875, 2.1875, 2.1875, 2.1875, 2.1875, 3, 4, 5, 5.8333, 6, 6.375, 6.6875, 7, 7.5417, 8, 8] },
135
+ 4: { breakpoints: [0, 1, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5.5, 6, 6.0208, 6.0208, 7, 8, 8] },
136
+ 3: { breakpoints: [0, 1, 2, 3, 4, 4.6667, 5, 5.1875, 5.4375, 5.5, 5.5, 5.5, 5.5, 5.5, 5.6667, 6, 6.5417, 7, 7.4583, 8, 8] },
137
+ 2: { breakpoints: [0, 1, 2, 2.8333, 2.8333, 2.8333, 2.8333, 2.8333, 2.8333, 3, 4, 4.3542, 4.5833, 4.8125, 5, 5.0625, 6, 7, 8, 8, 8] },
138
+ 1: { breakpoints: [0, 1, 2, 3, 3.5, 4, 5, 5.3958, 5.6042, 6, 7, 7.2708, 7.2708, 7.5208, 7.5208, 7.5208, 7.5208, 7.5208, 7.5208, 7.5208, 8] },
139
+ 0: { breakpoints: [0, 1, 1.6667, 2, 2.2083, 2.5, 3, 3.5, 4, 4.5, 4.7917, 5, 5.5, 6, 6.1875, 6.5417, 6.875, 7, 7.875, 8, 8] },
140
+ // turned back by 180° this is exactly the 6 (same segment split, same direction) → a pure spin
141
+ 6: {
142
+ breakpoints: [0, 0.5, 1, 4 / 3, 5 / 3, 2, 7 / 3, 8 / 3, 3, 4, 13 / 3, 14 / 3, 5, 16 / 3, 17 / 3, 6, 19 / 3, 20 / 3, 7, 7.5, 8],
143
+ reversed: true,
144
+ },
145
+ },
146
+ },
147
+ spins: { 6: { 9: 180 }, 9: { 6: -180 } },
148
+ textLayout: { cellWidth: 13.5, spacing: 4, baselineY: 20, colonDotsY: [8, 16] },
149
+ });
@@ -0,0 +1,107 @@
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
+ // Icon set "theme": light (sun), dark (moon) — Lucide 1.61.0 icons "sun", "moon" (ISC, see LICENSE-lucide).
43
+ // Coordinates below are Lucide's; lucideLine / lucideArc / lucideCubic scale them into our icon box (see lucide.ts).
44
+ // Nine pieces:
45
+ // piece 1: the sun's circle | the moon's crescent — the circle grows into the moon and its top right quarter
46
+ // flattens and caves in evenly into the inner curve (the circle starts where the moon's outer arc starts).
47
+ // pieces 2–9: one ray each | nothing — rays run from their outer end inwards; light → dark: they fly outwards and
48
+ // vanish (erased towards their outer tip, PairLayout.draw — the user's sketch) within the first 60 % of the
49
+ // morph; dark → light: they fly back in during the last 60 %. At rest the moon's ray pieces are tiny lines hidden
50
+ // on its outline.
51
+ import { arc, cubic, line } from '../geometry.js';
52
+ import { fromLucide, LUCIDE_STROKE_WIDTH, lucideLength } from './lucide.js';
53
+ const lucideLine = (fromX, fromY, toX, toY) => line(...fromLucide([fromX, fromY]), ...fromLucide([toX, toY]));
54
+ const lucideArc = (centerX, centerY, radius, startAngle, endAngle) => arc(...fromLucide([centerX, centerY]), lucideLength(radius), startAngle, endAngle);
55
+ const lucideCubic = (...coordinates) => {
56
+ const [startX, startY, control1X, control1Y, control2X, control2Y, endX, endY] = coordinates;
57
+ return cubic(...fromLucide([startX, startY]), ...fromLucide([control1X, control1Y]), ...fromLucide([control2X, control2Y]), ...fromLucide([endX, endY]));
58
+ };
59
+ /** Lucide "moon": the outer arc clockwise from its right end, the top tip, the inner arc back, the right tip. */
60
+ const MOON = [
61
+ lucideArc(11.9981, 12.0009, 9, 3.09, 266.904),
62
+ lucideCubic(11.512, 3.014, 11.917, 2.992, 12.129, 3.474, 11.914, 3.817),
63
+ lucideArc(17.002, 6.997, 6, -147.9941, -302.0059),
64
+ lucideCubic(20.182, 12.085, 20.526, 11.87, 21.007, 12.081, 20.985, 12.486),
65
+ ];
66
+ /** Lucide "sun"'s circle, started at the angle where the moon's outer arc starts. */
67
+ const SUN_CIRCLE = [lucideArc(12, 12, 4, 3.09, 363.09)];
68
+ /** The share of the circle under the moon's outer arc (3.09° … 266.904°). */
69
+ const OUTER_SHARE = (266.904 - 3.09) / 360;
70
+ const CIRCLE_FOR_MOON = [
71
+ ...Array.from({ length: 9 }, (_, index) => (index / 8) * OUTER_SHARE),
72
+ ...Array.from({ length: 4 }, (_, index) => OUTER_SHARE + ((index + 1) / 4) * (1 - OUTER_SHARE)),
73
+ ].map((breakpoint) => +breakpoint.toFixed(4));
74
+ /** Lucide "sun"'s rays, each from its outer end inwards (erasing keeps the outer end: the ray flies out). */
75
+ const RAYS = [
76
+ lucideLine(12, 2, 12, 4),
77
+ lucideLine(19.07, 4.93, 17.66, 6.34),
78
+ lucideLine(22, 12, 20, 12),
79
+ lucideLine(19.07, 19.07, 17.66, 17.66),
80
+ lucideLine(12, 22, 12, 20),
81
+ lucideLine(4.93, 19.07, 6.34, 17.66),
82
+ lucideLine(2, 12, 4, 12),
83
+ lucideLine(4.93, 4.93, 6.34, 6.34),
84
+ ];
85
+ /** The moon's ray pieces at rest: a tiny line on the bottom of its outline (it doesn't show). */
86
+ const HIDDEN_RAY = [lucideLine(11.95, 21, 12.05, 21)];
87
+ const rayPiece = (ray) => ({
88
+ strokes: { light: [ray], dark: HIDDEN_RAY },
89
+ alternativeStrokes: {},
90
+ pairLayouts: { dark: { light: { draw: 0.6 } } },
91
+ });
92
+ export const iconsTheme = {
93
+ segmentCount: 12,
94
+ strokeWidth: LUCIDE_STROKE_WIDTH,
95
+ lineCap: 'round',
96
+ lineJoin: 'round',
97
+ strokes: { light: SUN_CIRCLE, dark: MOON },
98
+ alternativeStrokes: {},
99
+ // even, by hand: the circle's part under the outer arc (263.8°) → 8 equal segments of the outer arc; its top right
100
+ // quarter (96.2°) → 4 equal segments: tip, inner curve (2), tip. (The optimizer's fit made a lumpy notch.)
101
+ pairLayouts: {
102
+ light: { dark: { breakpoints: CIRCLE_FOR_MOON } },
103
+ dark: { light: { breakpoints: [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1, 2, 2.5, 3, 4] } },
104
+ },
105
+ morePieces: RAYS.map(rayPiece),
106
+ spins: {},
107
+ };
@@ -0,0 +1,73 @@
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
+ import { LUCIDE_STROKE_WIDTH, lucideLine } from './lucide.js';
43
+ const HORIZONTAL = [lucideLine(5, 12, 19, 12)];
44
+ const first = {
45
+ strokes: {
46
+ plus: HORIZONTAL,
47
+ minus: HORIZONTAL,
48
+ close: [lucideLine(6, 6, 18, 18)],
49
+ check: [lucideLine(4, 12, 9, 17)],
50
+ },
51
+ alternativeStrokes: {},
52
+ pairLayouts: {},
53
+ };
54
+ const second = {
55
+ strokes: {
56
+ plus: [lucideLine(12, 5, 12, 19)],
57
+ minus: HORIZONTAL,
58
+ close: [lucideLine(18, 6, 6, 18)],
59
+ check: [lucideLine(20, 6, 9, 17)],
60
+ },
61
+ alternativeStrokes: {},
62
+ pairLayouts: {},
63
+ };
64
+ export const iconsToggle = {
65
+ segmentCount: 1,
66
+ strokeWidth: LUCIDE_STROKE_WIDTH,
67
+ lineCap: 'round',
68
+ lineJoin: 'round',
69
+ ...first,
70
+ morePieces: [second],
71
+ // plus turned by 45° clockwise is the ✕ (a little smaller): turn while blending
72
+ spins: { plus: { close: 45 }, close: { plus: -45 } },
73
+ };
@@ -0,0 +1,89 @@
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
+ import { LUCIDE_STROKE_WIDTH, lucideArc, lucideLine, lucidePoint } from './lucide.js';
43
+ /** Lucide's speaker, from the top of its front edge counterclockwise around. */
44
+ const SPEAKER = [
45
+ lucideArc(10.295, 4.703, 0.705, -0.0828, -134.9415),
46
+ lucideLine(9.797, 4.204, 6.413, 7.587),
47
+ lucideArc(5.4201, 6.6, 1.4, 44.8292, 90.168),
48
+ lucideLine(5.416, 8, 3, 8),
49
+ lucideArc(3, 9, 1, -90, -180),
50
+ lucideLine(2, 9, 2, 15),
51
+ lucideArc(3, 15, 1, 180, 90),
52
+ lucideLine(3, 16, 5.416, 16),
53
+ lucideArc(5.4201, 17.4, 1.4, -90.168, -44.8292),
54
+ lucideLine(6.413, 16.413, 9.796, 19.797),
55
+ lucideArc(10.295, 19.299, 0.705, 135.0564, -0.0797),
56
+ lucideLine(11, 19.298, 11, 4.702),
57
+ ];
58
+ /** The waves, each from its top end down. */
59
+ const SMALL_WAVE = [lucideArc(12, 12, 5, -36.8699, 36.8699)];
60
+ const BIG_WAVE = [lucideArc(13.0001, 12, 9, -45.0004, 45.0004)];
61
+ /** The ✕, each line from its top end down (so a wave unfolds into it without turning over). */
62
+ const X_SLASH = [lucideLine(21.5, 9.5, 16.5, 14.5)];
63
+ const X_BACKSLASH = [lucideLine(16.5, 9.5, 21.5, 14.5)];
64
+ /** No wave: a point on the middle of the speaker's front edge. */
65
+ const NO_WAVE = lucidePoint(11, 12);
66
+ const speaker = {
67
+ strokes: { muted: SPEAKER, low: SPEAKER, medium: SPEAKER, high: SPEAKER },
68
+ alternativeStrokes: {},
69
+ pairLayouts: {},
70
+ };
71
+ const smallWave = {
72
+ strokes: { muted: X_SLASH, low: NO_WAVE, medium: SMALL_WAVE, high: SMALL_WAVE },
73
+ alternativeStrokes: {},
74
+ pairLayouts: {},
75
+ };
76
+ const bigWave = {
77
+ strokes: { muted: X_BACKSLASH, low: NO_WAVE, medium: SMALL_WAVE, high: BIG_WAVE },
78
+ alternativeStrokes: {},
79
+ pairLayouts: {},
80
+ };
81
+ export const iconsVolume = {
82
+ segmentCount: 16,
83
+ strokeWidth: LUCIDE_STROKE_WIDTH,
84
+ lineCap: 'round',
85
+ lineJoin: 'round',
86
+ ...speaker,
87
+ morePieces: [smallWave, bigWave],
88
+ spins: {},
89
+ };