material-inspired-component-library 8.1.0 → 9.0.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 (284) hide show
  1. package/README.md +106 -18
  2. package/components/README.md +1 -1
  3. package/components/accordion/README.md +6 -6
  4. package/components/alert/README.md +10 -10
  5. package/components/alert/index.scss +27 -52
  6. package/components/appbar/README.md +19 -6
  7. package/components/appbar/index.scss +40 -44
  8. package/components/badge/README.md +12 -10
  9. package/components/badge/index.scss +21 -25
  10. package/components/bottomsheet/README.md +26 -1
  11. package/components/bottomsheet/index.scss +58 -44
  12. package/components/button/README.md +70 -10
  13. package/components/button/_shared.scss +210 -0
  14. package/components/button/index.scss +75 -389
  15. package/components/card/README.md +97 -85
  16. package/components/card/index.scss +46 -65
  17. package/components/checkbox/README.md +25 -14
  18. package/{tools/shapes/check.mjs → components/checkbox/_label.scss} +37 -42
  19. package/components/checkbox/index.scss +66 -66
  20. package/components/chip/README.md +186 -0
  21. package/components/chip/index.scss +285 -0
  22. package/components/datepicker/README.md +79 -0
  23. package/components/datepicker/index.scss +346 -315
  24. package/components/dialog/README.md +20 -10
  25. package/components/dialog/index.scss +55 -66
  26. package/components/divider/README.md +16 -15
  27. package/components/divider/index.scss +9 -25
  28. package/components/iconbutton/README.md +63 -12
  29. package/components/iconbutton/index.scss +142 -303
  30. package/components/list/README.md +89 -156
  31. package/components/list/index.scss +34 -36
  32. package/components/menu/README.md +81 -155
  33. package/components/menu/index.scss +52 -58
  34. package/components/navigationbar/README.md +108 -0
  35. package/components/navigationbar/index.scss +185 -0
  36. package/components/navigationrail/README.md +62 -37
  37. package/components/navigationrail/_navitem.scss +96 -0
  38. package/components/navigationrail/index.scss +78 -132
  39. package/components/progressindicator/README.md +83 -47
  40. package/components/progressindicator/index.scss +622 -172
  41. package/components/radio/README.md +11 -9
  42. package/components/radio/index.scss +36 -33
  43. package/components/select/README.md +12 -7
  44. package/components/select/index.scss +26 -34
  45. package/components/shapes/README.md +3 -3
  46. package/components/shapes/index.scss +0 -1
  47. package/components/sidesheet/README.md +21 -9
  48. package/components/sidesheet/index.scss +38 -46
  49. package/components/slider/README.md +21 -0
  50. package/components/slider/index.scss +108 -139
  51. package/components/snackbar/README.md +32 -35
  52. package/components/snackbar/index.scss +10 -22
  53. package/components/stepper/README.md +14 -7
  54. package/components/stepper/index.scss +58 -39
  55. package/components/switch/README.md +22 -15
  56. package/components/switch/index.scss +68 -60
  57. package/components/textfield/README.md +48 -5
  58. package/components/textfield/index.scss +192 -248
  59. package/components/timepicker/README.md +53 -23
  60. package/components/timepicker/index.scss +110 -128
  61. package/dist/alert.css +1 -1
  62. package/dist/alert.js +1 -1
  63. package/dist/appbar.css +1 -1
  64. package/dist/appbar.js +1 -1
  65. package/dist/badge.css +1 -1
  66. package/dist/badge.js +1 -1
  67. package/dist/base.css +1 -0
  68. package/dist/base.js +1 -0
  69. package/dist/bottomsheet.css +1 -1
  70. package/dist/bottomsheet.js +1 -1
  71. package/dist/button.css +1 -1
  72. package/dist/button.js +1 -1
  73. package/dist/card.css +1 -1
  74. package/dist/card.js +1 -1
  75. package/dist/checkbox.css +1 -1
  76. package/dist/checkbox.js +1 -1
  77. package/dist/chip.css +1 -0
  78. package/dist/chip.js +1 -0
  79. package/dist/components/button/index.d.ts +1 -1
  80. package/dist/components/iconbutton/index.d.ts +2 -7
  81. package/dist/components/slider/index.d.ts +1 -1
  82. package/dist/components/textfield/index.d.ts +2 -2
  83. package/dist/datepicker.css +1 -1
  84. package/dist/datepicker.js +1 -1
  85. package/dist/dialog.css +1 -1
  86. package/dist/dialog.js +1 -1
  87. package/dist/divider.css +1 -1
  88. package/dist/divider.js +1 -1
  89. package/dist/field.css +1 -0
  90. package/dist/field.js +1 -0
  91. package/dist/foundations/form/index.js +1 -1
  92. package/dist/foundations/runtime.d.ts +15 -0
  93. package/dist/foundations/runtime.js +1 -0
  94. package/dist/iconbutton.css +1 -1
  95. package/dist/iconbutton.js +1 -1
  96. package/dist/layout.css +1 -1
  97. package/dist/layout.js +1 -1
  98. package/dist/list.css +1 -1
  99. package/dist/list.js +1 -1
  100. package/dist/menu.css +1 -1
  101. package/dist/menu.js +1 -1
  102. package/dist/micl.css +1 -1
  103. package/dist/micl.d.ts +14 -5
  104. package/dist/micl.js +1 -1
  105. package/dist/navigationbar.css +1 -0
  106. package/dist/navigationbar.js +1 -0
  107. package/dist/navigationrail.css +1 -1
  108. package/dist/navigationrail.js +1 -1
  109. package/dist/progressindicator.css +1 -1
  110. package/dist/progressindicator.js +1 -1
  111. package/dist/radio.css +1 -1
  112. package/dist/radio.js +1 -1
  113. package/dist/scrollbar.js +1 -1
  114. package/dist/select.css +1 -1
  115. package/dist/select.js +1 -1
  116. package/dist/shapes.css +1 -1
  117. package/dist/shapes.js +1 -1
  118. package/dist/sidesheet.css +1 -1
  119. package/dist/sidesheet.js +1 -1
  120. package/dist/slider.css +1 -1
  121. package/dist/slider.js +1 -1
  122. package/dist/snackbar.css +1 -1
  123. package/dist/snackbar.js +1 -1
  124. package/dist/stepper.css +1 -1
  125. package/dist/stepper.js +1 -1
  126. package/dist/switch.css +1 -1
  127. package/dist/switch.js +1 -1
  128. package/dist/textfield.css +1 -1
  129. package/dist/textfield.js +1 -1
  130. package/dist/timepicker.css +1 -1
  131. package/dist/timepicker.js +1 -1
  132. package/foundations/_index.scss +35 -0
  133. package/foundations/{index.scss → base/index.scss} +21 -56
  134. package/foundations/field/index.scss +37 -0
  135. package/foundations/layout/index.scss +64 -65
  136. package/package.json +23 -9
  137. package/styles/statelayer.scss +20 -5
  138. package/styles.scss +5 -2
  139. package/.claude/settings.local.json +0 -14
  140. package/.editorconfig +0 -12
  141. package/.gitattributes +0 -9
  142. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -35
  143. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  144. package/CLAUDE.md +0 -53
  145. package/CODE_OF_CONDUCT.md +0 -128
  146. package/components/bottomsheet/index.ts +0 -153
  147. package/components/button/index.ts +0 -79
  148. package/components/checkbox/index.ts +0 -148
  149. package/components/datepicker/index.ts +0 -693
  150. package/components/iconbutton/index.ts +0 -77
  151. package/components/list/index.ts +0 -138
  152. package/components/menu/index.ts +0 -145
  153. package/components/navigationrail/index.ts +0 -87
  154. package/components/progressindicator/index.ts +0 -77
  155. package/components/slider/index.ts +0 -149
  156. package/components/snackbar/index.ts +0 -118
  157. package/components/stepper/index.ts +0 -248
  158. package/components/textfield/index.ts +0 -188
  159. package/components/timepicker/index.ts +0 -413
  160. package/dist/components/progressindicator/index.d.ts +0 -6
  161. package/dist/foundations.css +0 -1
  162. package/dist/foundations.js +0 -1
  163. package/docs/accordion.html +0 -344
  164. package/docs/alert.html +0 -177
  165. package/docs/bottomsheet.html +0 -240
  166. package/docs/button.html +0 -290
  167. package/docs/card-awards.webp +0 -0
  168. package/docs/card-cabinet.webp +0 -0
  169. package/docs/card-city.webp +0 -0
  170. package/docs/card-fingerprint.webp +0 -0
  171. package/docs/card-holiday.webp +0 -0
  172. package/docs/card-names.webp +0 -0
  173. package/docs/card.html +0 -159
  174. package/docs/checkbox.html +0 -147
  175. package/docs/datepicker.html +0 -274
  176. package/docs/dialog.html +0 -195
  177. package/docs/divider.html +0 -139
  178. package/docs/docs.css +0 -26
  179. package/docs/docs.js +0 -139
  180. package/docs/iconbutton.html +0 -329
  181. package/docs/index.html +0 -431
  182. package/docs/list.html +0 -310
  183. package/docs/menu.html +0 -574
  184. package/docs/micl.css +0 -1
  185. package/docs/micl.js +0 -1
  186. package/docs/navigationrail.html +0 -138
  187. package/docs/progressindicator.html +0 -288
  188. package/docs/radio.html +0 -123
  189. package/docs/select.html +0 -337
  190. package/docs/shapes.css +0 -1
  191. package/docs/shapes.html +0 -159
  192. package/docs/shapes.js +0 -1
  193. package/docs/sidesheet.html +0 -149
  194. package/docs/slider.html +0 -141
  195. package/docs/snackbar.html +0 -102
  196. package/docs/stepper.html +0 -319
  197. package/docs/switch.html +0 -196
  198. package/docs/textfield.html +0 -208
  199. package/docs/themes/airblue/dark-hc.css +0 -51
  200. package/docs/themes/airblue/dark-mc.css +0 -51
  201. package/docs/themes/airblue/dark.css +0 -51
  202. package/docs/themes/airblue/light-hc.css +0 -51
  203. package/docs/themes/airblue/light-mc.css +0 -51
  204. package/docs/themes/airblue/light.css +0 -51
  205. package/docs/themes/airblue/theme.css +0 -306
  206. package/docs/themes/barnred/dark-hc.css +0 -51
  207. package/docs/themes/barnred/dark-mc.css +0 -51
  208. package/docs/themes/barnred/dark.css +0 -51
  209. package/docs/themes/barnred/light-hc.css +0 -51
  210. package/docs/themes/barnred/light-mc.css +0 -51
  211. package/docs/themes/barnred/light.css +0 -51
  212. package/docs/themes/barnred/theme.css +0 -306
  213. package/docs/themes/citrine/dark-hc.css +0 -51
  214. package/docs/themes/citrine/dark-mc.css +0 -51
  215. package/docs/themes/citrine/dark.css +0 -51
  216. package/docs/themes/citrine/light-hc.css +0 -51
  217. package/docs/themes/citrine/light-mc.css +0 -51
  218. package/docs/themes/citrine/light.css +0 -51
  219. package/docs/themes/citrine/theme.css +0 -306
  220. package/docs/themes/gray/dark-hc.css +0 -51
  221. package/docs/themes/gray/dark-mc.css +0 -51
  222. package/docs/themes/gray/dark.css +0 -51
  223. package/docs/themes/gray/light-hc.css +0 -51
  224. package/docs/themes/gray/light-mc.css +0 -51
  225. package/docs/themes/gray/light.css +0 -51
  226. package/docs/themes/gray/theme.css +0 -306
  227. package/docs/themes/greenery/dark-hc.css +0 -51
  228. package/docs/themes/greenery/dark-mc.css +0 -51
  229. package/docs/themes/greenery/dark.css +0 -51
  230. package/docs/themes/greenery/light-hc.css +0 -51
  231. package/docs/themes/greenery/light-mc.css +0 -51
  232. package/docs/themes/greenery/light.css +0 -51
  233. package/docs/themes/greenery/theme.css +0 -306
  234. package/docs/themes/hermana/dark-hc.css +0 -51
  235. package/docs/themes/hermana/dark-mc.css +0 -51
  236. package/docs/themes/hermana/dark.css +0 -51
  237. package/docs/themes/hermana/light-hc.css +0 -51
  238. package/docs/themes/hermana/light-mc.css +0 -51
  239. package/docs/themes/hermana/light.css +0 -51
  240. package/docs/themes/hermana/theme.css +0 -306
  241. package/docs/themes/illuminating/dark-hc.css +0 -51
  242. package/docs/themes/illuminating/dark-mc.css +0 -51
  243. package/docs/themes/illuminating/dark.css +0 -51
  244. package/docs/themes/illuminating/light-hc.css +0 -51
  245. package/docs/themes/illuminating/light-mc.css +0 -51
  246. package/docs/themes/illuminating/light.css +0 -51
  247. package/docs/themes/illuminating/theme.css +0 -306
  248. package/docs/themes/magenta/dark-hc.css +0 -51
  249. package/docs/themes/magenta/dark-mc.css +0 -51
  250. package/docs/themes/magenta/dark.css +0 -51
  251. package/docs/themes/magenta/light-hc.css +0 -51
  252. package/docs/themes/magenta/light-mc.css +0 -51
  253. package/docs/themes/magenta/light.css +0 -51
  254. package/docs/themes/magenta/theme.css +0 -306
  255. package/docs/themes/mocha/dark-hc.css +0 -51
  256. package/docs/themes/mocha/dark-mc.css +0 -51
  257. package/docs/themes/mocha/dark.css +0 -51
  258. package/docs/themes/mocha/light-hc.css +0 -51
  259. package/docs/themes/mocha/light-mc.css +0 -51
  260. package/docs/themes/mocha/light.css +0 -51
  261. package/docs/themes/mocha/theme.css +0 -306
  262. package/docs/themes/olivegreen/dark-hc.css +0 -51
  263. package/docs/themes/olivegreen/dark-mc.css +0 -51
  264. package/docs/themes/olivegreen/dark.css +0 -51
  265. package/docs/themes/olivegreen/light-hc.css +0 -51
  266. package/docs/themes/olivegreen/light-mc.css +0 -51
  267. package/docs/themes/olivegreen/light.css +0 -51
  268. package/docs/themes/olivegreen/theme.css +0 -306
  269. package/docs/themes/peri/dark-hc.css +0 -51
  270. package/docs/themes/peri/dark-mc.css +0 -51
  271. package/docs/themes/peri/dark.css +0 -51
  272. package/docs/themes/peri/light-hc.css +0 -51
  273. package/docs/themes/peri/light-mc.css +0 -51
  274. package/docs/themes/peri/light.css +0 -51
  275. package/docs/themes/peri/theme.css +0 -306
  276. package/docs/timepicker.html +0 -160
  277. package/foundations/form/index.ts +0 -174
  278. package/foundations/layout/README.md +0 -127
  279. package/intl.d.ts +0 -9
  280. package/micl.ts +0 -221
  281. package/styles/README.md +0 -196
  282. package/tools/shapes/generate.mjs +0 -828
  283. package/tsconfig.json +0 -116
  284. package/webpack.config.js +0 -104
@@ -1,828 +0,0 @@
1
- //
2
- // Copyright © 2025 Hermana AS
3
- //
4
- // Permission is hereby granted, free of charge, to any person obtaining a copy
5
- // of this software and associated documentation files (the "Software"), to deal
6
- // in the Software without restriction, including without limitation the rights
7
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- // copies of the Software, and to permit persons to whom the Software is
9
- // furnished to do so, subject to the following conditions:
10
- //
11
- // The above copyright notice and this permission notice shall be included in all
12
- // copies or substantial portions of the Software.
13
- //
14
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- // SOFTWARE.
21
-
22
- import fs from 'node:fs';
23
- import path from 'node:path';
24
- import { fileURLToPath } from 'node:url';
25
-
26
- //
27
- // Shape-path generator. Run via `npm run gen:shapes`. Writes
28
- // components/shapes/_paths.generated.scss with one map entry per shape; the
29
- // SCSS partial then looks up the path strings without doing any math at
30
- // compile time.
31
- //
32
-
33
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
34
- const targetPath = path.resolve(__dirname, '../../components/shapes/_paths.generated.scss');
35
-
36
- // Sass's default precision is 10 decimal places. Match that, then strip
37
- // trailing zeros so e.g. 50.0000000000 → 50, 3.1400000000 → 3.14.
38
- function toCss(n) {
39
- if (Number.isInteger(n)) return String(n);
40
- let s = n.toFixed(10);
41
- s = s.replace(/(\.\d*?)0+$/, '$1');
42
- s = s.replace(/\.$/, '');
43
- // Sass renders -0 as 0 — match that to avoid spurious diffs.
44
- if (s === '-0') return '0';
45
- return s;
46
- }
47
-
48
- function segmentsLength(segments) {
49
- let t = 0;
50
- for (const s of segments) t += s.len;
51
- return t;
52
- }
53
-
54
- function pointAt(segments, total, t) {
55
- const target = t * total;
56
- let acc = 0;
57
- for (const s of segments) {
58
- if (acc + s.len >= target) {
59
- const local = s.len === 0 ? 0 : (target - acc) / s.len;
60
- if (s.type === 'line') {
61
- return [s.x1 + local * (s.x2 - s.x1), s.y1 + local * (s.y2 - s.y1)];
62
- }
63
- if (s.type === 'arc') {
64
- const a = s.a1 + local * (s.a2 - s.a1);
65
- return [s.cx + s.r * Math.cos(a), s.cy + s.r * Math.sin(a)];
66
- }
67
- if (s.type === 'cubic') {
68
- const u = 1 - local;
69
- const u2 = u * u, u3 = u2 * u;
70
- const t2 = local * local, t3 = t2 * local;
71
- const bx = u3 * s.p0x + 3 * u2 * local * s.p1x + 3 * u * t2 * s.p2x + t3 * s.p3x;
72
- const by = u3 * s.p0y + 3 * u2 * local * s.p1y + 3 * u * t2 * s.p2y + t3 * s.p3y;
73
- return [bx, by];
74
- }
75
- }
76
- acc += s.len;
77
- }
78
- return [0, 0];
79
- }
80
-
81
- // Emits a closed SVG path string from N (x, y) sample points (assumed CW).
82
- // Rotates the array so the path begins at the sample closest to top-centre
83
- // (50, 0). Combined with every shape traversing CW, this keeps point-index k
84
- // mapping to the same relative position across shapes — so `transition: d`
85
- // interpolates smoothly.
86
- function emitAlignedPath(points, n) {
87
- // Find the sample closest to (50, 0). Symmetric shapes (e.g. clover-4)
88
- // have multiple samples at exactly the same distance; ULP-level differences
89
- // in float arithmetic would otherwise let later samples nudge ahead of
90
- // earlier ones non-deterministically. Tolerate ULP-scale differences so
91
- // the first tied sample wins, matching Sass's tie-break behaviour.
92
- const EPS = 1e-9;
93
- let bestI = 0, bestD = 999999;
94
- for (let i = 0; i < n; i++) {
95
- const dx = points[i][0] - 50;
96
- const dy = points[i][1];
97
- const dist = dx * dx + dy * dy;
98
- if (dist < bestD - EPS) { bestD = dist; bestI = i; }
99
- }
100
- let d = '';
101
- for (let j = 0; j < n; j++) {
102
- const idx = (bestI + j) % n;
103
- const p = points[idx];
104
- d += (j === 0 ? 'M' : 'L') + ' ' + toCss(p[0]) + ' ' + toCss(p[1]) + ' ';
105
- }
106
- return d + 'Z';
107
- }
108
-
109
- function buildPath(segments, n) {
110
- const total = segmentsLength(segments);
111
- const points = [];
112
- for (let i = 0; i < n; i++) {
113
- points.push(pointAt(segments, total, i / n));
114
- }
115
- return emitAlignedPath(points, n);
116
- }
117
-
118
- // --- generic M3 rounded polygon --------------------------------------------
119
-
120
- function roundedPolygonPath(n, vertices, normalize = true) {
121
- // Step 0: deduplicate consecutive identical vertices.
122
- let dedup = [];
123
- for (const curr of vertices) {
124
- if (dedup.length > 0) {
125
- const prev = dedup[dedup.length - 1];
126
- const dx = curr[0] - prev[0];
127
- const dy = curr[1] - prev[1];
128
- if (Math.sqrt(dx * dx + dy * dy) < 0.01) continue;
129
- }
130
- dedup.push(curr);
131
- }
132
- if (dedup.length > 1) {
133
- const first = dedup[0];
134
- const last = dedup[dedup.length - 1];
135
- const dx = first[0] - last[0];
136
- const dy = first[1] - last[1];
137
- if (Math.sqrt(dx * dx + dy * dy) < 0.01) {
138
- dedup = dedup.slice(0, -1);
139
- }
140
- }
141
- // Drop collinear vertices.
142
- const straight = [];
143
- const dc = dedup.length;
144
- for (let i = 0; i < dc; i++) {
145
- const prev = dedup[(i - 1 + dc) % dc];
146
- const curr = dedup[i];
147
- const next = dedup[(i + 1) % dc];
148
- const cross = (curr[0] - prev[0]) * (next[1] - curr[1]) -
149
- (curr[1] - prev[1]) * (next[0] - curr[0]);
150
- if (Math.abs(cross) > 0.5) straight.push(curr);
151
- }
152
- vertices = straight;
153
- const count = vertices.length;
154
-
155
- // Step 1: per-vertex flanking-curve geometry (port of M3's
156
- // _RoundedCorner.getCubics from AndroidX).
157
- const corners = [];
158
- for (let i = 0; i < count; i++) {
159
- const vp = vertices[(i - 1 + count) % count];
160
- const vc = vertices[i];
161
- const vn = vertices[(i + 1) % count];
162
-
163
- const px = vp[0], py = vp[1];
164
- const vx = vc[0], vy = vc[1], rBase = vc[2];
165
- const smoothingSpec = vc.length >= 4 ? vc[3] : 0;
166
- const nx = vn[0], ny = vn[1];
167
-
168
- let d1x = px - vx, d1y = py - vy;
169
- const d1Len = Math.sqrt(d1x * d1x + d1y * d1y);
170
- d1x /= d1Len; d1y /= d1Len;
171
-
172
- let d2x = nx - vx, d2y = ny - vy;
173
- const d2Len = Math.sqrt(d2x * d2x + d2y * d2y);
174
- d2x /= d2Len; d2y /= d2Len;
175
-
176
- let cosAngle = d1x * d2x + d1y * d2y;
177
- if (cosAngle > 1) cosAngle = 1;
178
- if (cosAngle < -1) cosAngle = -1;
179
- const sinSq = 1 - cosAngle * cosAngle;
180
- const sinAngle = sinSq <= 0 ? 0 : Math.sqrt(sinSq);
181
-
182
- let expectedRoundCut = 0;
183
- if (sinAngle > 0.001) {
184
- expectedRoundCut = rBase * (1 + cosAngle) / sinAngle;
185
- }
186
- const expectedCut = (1 + smoothingSpec) * expectedRoundCut;
187
-
188
- const allowed0 = d1Len / 2;
189
- const allowed1 = d2Len / 2;
190
- const allowedCut = Math.min(allowed0, allowed1);
191
-
192
- let sm0 = 0;
193
- if (allowed0 > expectedCut) sm0 = smoothingSpec;
194
- else if (allowed0 > expectedRoundCut && expectedCut > expectedRoundCut) {
195
- sm0 = smoothingSpec * (allowed0 - expectedRoundCut) / (expectedCut - expectedRoundCut);
196
- }
197
- let sm1 = 0;
198
- if (allowed1 > expectedCut) sm1 = smoothingSpec;
199
- else if (allowed1 > expectedRoundCut && expectedCut > expectedRoundCut) {
200
- sm1 = smoothingSpec * (allowed1 - expectedRoundCut) / (expectedCut - expectedRoundCut);
201
- }
202
-
203
- const actualRoundCut = Math.min(allowedCut, expectedRoundCut);
204
- const actualR = expectedRoundCut < 0.001 ? 0 : rBase * actualRoundCut / expectedRoundCut;
205
-
206
- let bx = d1x + d2x, by = d1y + d2y;
207
- const bLen = Math.sqrt(bx * bx + by * by);
208
- bx /= bLen; by /= bLen;
209
- const centerDist = Math.sqrt(actualR * actualR + actualRoundCut * actualRoundCut);
210
- const cx = vx + bx * centerDist;
211
- const cy = vy + by * centerDist;
212
-
213
- const t1x = vx + d1x * actualRoundCut, t1y = vy + d1y * actualRoundCut;
214
- const t2x = vx + d2x * actualRoundCut, t2y = vy + d2y * actualRoundCut;
215
- const mx = (t1x + t2x) * 0.5, my = (t1y + t2y) * 0.5;
216
-
217
- // Side 1 (incoming flanking curve)
218
- const cs1x = vx + d1x * actualRoundCut * (1 + sm0);
219
- const cs1y = vy + d1y * actualRoundCut * (1 + sm0);
220
- const p1x = t1x + (mx - t1x) * sm0;
221
- const p1y = t1y + (my - t1y) * sm0;
222
- const dp1x = p1x - cx, dp1y = p1y - cy;
223
- const dp1Len = Math.sqrt(dp1x * dp1x + dp1y * dp1y);
224
- let ce1x = cx, ce1y = cy;
225
- if (dp1Len > 0.0001) {
226
- ce1x = cx + actualR * dp1x / dp1Len;
227
- ce1y = cy + actualR * dp1y / dp1Len;
228
- }
229
-
230
- const tan1x = -(ce1y - cy);
231
- const tan1y = (ce1x - cx);
232
- const det1 = d1x * tan1y - d1y * tan1x;
233
- let ae1x = t1x, ae1y = t1y;
234
- if (Math.abs(det1) > 0.0001) {
235
- const tt1 = ((ce1x - px) * tan1y - (ce1y - py) * tan1x) / det1;
236
- ae1x = px + tt1 * d1x;
237
- ae1y = py + tt1 * d1y;
238
- }
239
- const as1x = (cs1x + 2 * ae1x) / 3;
240
- const as1y = (cs1y + 2 * ae1y) / 3;
241
-
242
- // Side 2 (outgoing flanking curve)
243
- const cs2x = vx + d2x * actualRoundCut * (1 + sm1);
244
- const cs2y = vy + d2y * actualRoundCut * (1 + sm1);
245
- const p2x = t2x + (mx - t2x) * sm1;
246
- const p2y = t2y + (my - t2y) * sm1;
247
- const dp2x = p2x - cx, dp2y = p2y - cy;
248
- const dp2Len = Math.sqrt(dp2x * dp2x + dp2y * dp2y);
249
- let ce2x = cx, ce2y = cy;
250
- if (dp2Len > 0.0001) {
251
- ce2x = cx + actualR * dp2x / dp2Len;
252
- ce2y = cy + actualR * dp2y / dp2Len;
253
- }
254
-
255
- const tan2x = -(ce2y - cy);
256
- const tan2y = (ce2x - cx);
257
- const det2 = d2x * tan2y - d2y * tan2x;
258
- let ae2x = t2x, ae2y = t2y;
259
- if (Math.abs(det2) > 0.0001) {
260
- const tt2 = ((ce2x - nx) * tan2y - (ce2y - ny) * tan2x) / det2;
261
- ae2x = nx + tt2 * d2x;
262
- ae2y = ny + tt2 * d2y;
263
- }
264
- const as2x = (cs2x + 2 * ae2x) / 3;
265
- const as2y = (cs2y + 2 * ae2y) / 3;
266
-
267
- // Arc start/end angles. Convex traverses increasing θ, concave decreasing.
268
- const cross = d1x * d2y - d1y * d2x;
269
- let a1 = Math.atan2(ce1y - cy, ce1x - cx);
270
- let a2 = Math.atan2(ce2y - cy, ce2x - cx);
271
- if (cross < 0) {
272
- if (a2 < a1) a2 += 2 * Math.PI;
273
- } else {
274
- if (a2 > a1) a2 -= 2 * Math.PI;
275
- }
276
-
277
- corners.push({
278
- cs1x, cs1y, as1x, as1y, ae1x, ae1y, ce1x, ce1y,
279
- cx, cy, actualR, a1, a2,
280
- ce2x, ce2y, ae2x, ae2y, as2x, as2y, cs2x, cs2y,
281
- });
282
- }
283
-
284
- // Step 2: emit segments per corner.
285
- const segments = [];
286
- for (let i = 0; i < count; i++) {
287
- const cprev = corners[(i - 1 + count) % count];
288
- const c = corners[i];
289
-
290
- const edgeLen = Math.sqrt((c.cs1x - cprev.cs2x) ** 2 + (c.cs1y - cprev.cs2y) ** 2);
291
- segments.push({ len: edgeLen, type: 'line',
292
- x1: cprev.cs2x, y1: cprev.cs2y, x2: c.cs1x, y2: c.cs1y });
293
-
294
- const c1Len = (
295
- Math.sqrt((c.as1x - c.cs1x) ** 2 + (c.as1y - c.cs1y) ** 2) +
296
- Math.sqrt((c.ae1x - c.as1x) ** 2 + (c.ae1y - c.as1y) ** 2) +
297
- Math.sqrt((c.ce1x - c.ae1x) ** 2 + (c.ce1y - c.ae1y) ** 2) +
298
- Math.sqrt((c.ce1x - c.cs1x) ** 2 + (c.ce1y - c.cs1y) ** 2)
299
- ) / 2;
300
- segments.push({ len: c1Len, type: 'cubic',
301
- p0x: c.cs1x, p0y: c.cs1y, p1x: c.as1x, p1y: c.as1y,
302
- p2x: c.ae1x, p2y: c.ae1y, p3x: c.ce1x, p3y: c.ce1y });
303
-
304
- const arcLen = Math.abs(c.a2 - c.a1) * c.actualR;
305
- segments.push({ len: arcLen, type: 'arc',
306
- cx: c.cx, cy: c.cy, r: c.actualR, a1: c.a1, a2: c.a2 });
307
-
308
- const c2Len = (
309
- Math.sqrt((c.ae2x - c.ce2x) ** 2 + (c.ae2y - c.ce2y) ** 2) +
310
- Math.sqrt((c.as2x - c.ae2x) ** 2 + (c.as2y - c.ae2y) ** 2) +
311
- Math.sqrt((c.cs2x - c.as2x) ** 2 + (c.cs2y - c.as2y) ** 2) +
312
- Math.sqrt((c.cs2x - c.ce2x) ** 2 + (c.cs2y - c.ce2y) ** 2)
313
- ) / 2;
314
- segments.push({ len: c2Len, type: 'cubic',
315
- p0x: c.ce2x, p0y: c.ce2y, p1x: c.ae2x, p1y: c.ae2y,
316
- p2x: c.as2x, p2y: c.as2y, p3x: c.cs2x, p3y: c.cs2y });
317
- }
318
-
319
- // Step 3: optionally normalize bounds to fit 100×100. Collect samples
320
- // during the bounds-finding pass and reuse them for the rescale, so each
321
- // parameter t is only sampled once (was: twice per shape).
322
- if (normalize) {
323
- const total = segmentsLength(segments);
324
- const samples = new Array(n);
325
- let minX = 9999, maxX = -9999, minY = 9999, maxY = -9999;
326
- for (let i = 0; i < n; i++) {
327
- const p = pointAt(segments, total, i / n);
328
- samples[i] = p;
329
- const x = p[0], y = p[1];
330
- if (x < minX) minX = x;
331
- if (x > maxX) maxX = x;
332
- if (y < minY) minY = y;
333
- if (y > maxY) maxY = y;
334
- }
335
- const w = maxX - minX, h = maxY - minY;
336
- const larger = Math.max(w, h);
337
- const scale = 100 / larger;
338
- const offsetX = (100 - w * scale) / 2 - minX * scale;
339
- const offsetY = (100 - h * scale) / 2 - minY * scale;
340
- const points = new Array(n);
341
- for (let i = 0; i < n; i++) {
342
- const [x, y] = samples[i];
343
- points[i] = [x * scale + offsetX, y * scale + offsetY];
344
- }
345
- return emitAlignedPath(points, n);
346
- }
347
- return buildPath(segments, n);
348
- }
349
-
350
- // Faithful port of MaterialShapes._doRepeat. Polar coordinates so the
351
- // rotate/mirror algorithm preserves CW order.
352
- function repeatedPattern(pattern, repeat, mirror = false) {
353
- const cx = 50, cy = 50;
354
- const vertices = [];
355
- const np = pattern.length;
356
-
357
- if (mirror) {
358
- const measures = pattern.map(v => {
359
- const px = v[0] - cx, py = v[1] - cy;
360
- return [Math.atan2(py, px), Math.sqrt(px * px + py * py)];
361
- });
362
- const firstAngle = measures[0][0];
363
- const actualReps = 2 * repeat;
364
- const sectionAng = 2 * Math.PI / actualReps;
365
-
366
- for (let r = 0; r < actualReps; r++) {
367
- const rEven = r % 2 === 0;
368
- for (let idx = 0; idx < np; idx++) {
369
- const i = rEven ? idx : np - 1 - idx;
370
- if (i > 0 || rEven) {
371
- const [mAngle, mDist] = measures[i];
372
- const a = rEven
373
- ? sectionAng * r + mAngle
374
- : sectionAng * r + (sectionAng - mAngle + 2 * firstAngle);
375
- const px = cx + mDist * Math.cos(a);
376
- const py = cy + mDist * Math.sin(a);
377
- const orig = pattern[i];
378
- const rVal = orig[2];
379
- const smoothing = orig.length >= 4 ? orig[3] : 0;
380
- vertices.push([px, py, rVal, smoothing]);
381
- }
382
- }
383
- }
384
- } else {
385
- const angleStep = 2 * Math.PI / repeat;
386
- for (let i = 0; i < np * repeat; i++) {
387
- const idx = i % np;
388
- const repNum = Math.floor(i / np);
389
- const rot = repNum * angleStep;
390
- const cosR = Math.cos(rot);
391
- const sinR = Math.sin(rot);
392
- const v = pattern[idx];
393
- const px = v[0] - cx, py = v[1] - cy;
394
- const rVal = v[2];
395
- const smoothing = v.length >= 4 ? v[3] : 0;
396
- const rx = px * cosR - py * sinR + cx;
397
- const ry = px * sinR + py * cosR + cy;
398
- vertices.push([rx, ry, rVal, smoothing]);
399
- }
400
- }
401
- return vertices;
402
- }
403
-
404
- function verticesOnCircle(numVertices, roundings, rotationDeg = 0) {
405
- const vertices = [];
406
- const rotRad = rotationDeg * Math.PI / 180;
407
- for (let i = 0; i < numVertices; i++) {
408
- const angle = 2 * Math.PI * i / numVertices + rotRad;
409
- const x = 50 + 50 * Math.cos(angle);
410
- const y = 50 + 50 * Math.sin(angle);
411
- const r = roundings[i] * 50;
412
- vertices.push([x, y, r]);
413
- }
414
- return vertices;
415
- }
416
-
417
- function starVertices(points, innerRatio, rounding, rotationDeg = -90) {
418
- const vertices = [];
419
- const outerR = 50;
420
- const innerR = 50 * innerRatio;
421
- const rounding100 = rounding * 50;
422
- const rotRad = rotationDeg * Math.PI / 180;
423
- for (let i = 0; i < points * 2; i++) {
424
- const angle = 2 * Math.PI * i / (points * 2) + rotRad;
425
- const r = i % 2 === 0 ? outerR : innerR;
426
- const x = 50 + r * Math.cos(angle);
427
- const y = 50 + r * Math.sin(angle);
428
- vertices.push([x, y, rounding100]);
429
- }
430
- return vertices;
431
- }
432
-
433
- function circlePath(n) {
434
- const points = [];
435
- for (let i = 0; i < n; i++) {
436
- const angle = 2 * Math.PI * i / n - Math.PI / 2;
437
- points.push([50 + 50 * Math.cos(angle), 50 + 50 * Math.sin(angle)]);
438
- }
439
- return emitAlignedPath(points, n);
440
- }
441
-
442
- // --- shape generators (one per shape) -------------------------------------
443
-
444
- function heartPath(n) {
445
- return roundedPolygonPath(n, repeatedPattern([
446
- [ 50.0, 26.8, 1.6],
447
- [ 79.2, -6.6, 95.8],
448
- [106.4, 27.6, 100.0],
449
- [ 50.1, 94.6, 12.9],
450
- ], 1, true));
451
- }
452
-
453
- function cookie4Path(n) {
454
- return roundedPolygonPath(n, repeatedPattern([
455
- [123.7, 123.6, 50.0],
456
- [ 50.0, 91.8, 35.0],
457
- ], 4));
458
- }
459
-
460
- function cookie6Path(n) {
461
- return roundedPolygonPath(n, repeatedPattern([
462
- [ 72.3, 88.4, 55.0],
463
- [ 50.0, 109.9, 55.0],
464
- ], 6));
465
- }
466
-
467
- function cookie7Path(n) { return roundedPolygonPath(n, starVertices( 7, 0.75, 0.5)); }
468
- function cookie9Path(n) { return roundedPolygonPath(n, starVertices( 9, 0.8, 0.5)); }
469
- function cookie12Path(n) { return roundedPolygonPath(n, starVertices(12, 0.8, 0.5)); }
470
-
471
- function sunnyPath(n) { return roundedPolygonPath(n, starVertices(8, 0.8, 0.15, 0)); }
472
-
473
- function verySunnyPath(n) {
474
- return roundedPolygonPath(n, repeatedPattern([
475
- [50.0, 108.0, 8.5],
476
- [35.8, 84.3, 8.5],
477
- ], 8));
478
- }
479
-
480
- function clover4Path(n) {
481
- const hpi = Math.PI / 2;
482
- const quarter = Math.PI / 4;
483
- const D = 25, L = 25;
484
- const arcLen = L * Math.PI;
485
-
486
- const segments = [];
487
- for (let i = 0; i < 4; i++) {
488
- const theta = i * hpi - hpi + quarter;
489
- const cxL = 50 + D * Math.cos(theta);
490
- const cyL = 50 + D * Math.sin(theta);
491
- segments.push({ len: arcLen, type: 'arc', cx: cxL, cy: cyL, r: L, a1: theta - hpi, a2: theta + hpi });
492
- }
493
- return buildPath(segments, n);
494
- }
495
-
496
- function clover8Path(n) {
497
- const hpi = Math.PI / 2;
498
- const section = Math.PI / 4;
499
- const D = 35;
500
- const L = D * Math.tan(Math.PI / 8);
501
- const arcLen = L * Math.PI;
502
-
503
- const segments = [];
504
- for (let i = 0; i < 8; i++) {
505
- const theta = i * section - hpi;
506
- const cxL = 50 + D * Math.cos(theta);
507
- const cyL = 50 + D * Math.sin(theta);
508
- segments.push({ len: arcLen, type: 'arc', cx: cxL, cy: cyL, r: L, a1: theta - hpi, a2: theta + hpi });
509
- }
510
- return buildPath(segments, n);
511
- }
512
-
513
- function softBurstPath(n) {
514
- return roundedPolygonPath(n, repeatedPattern([
515
- [19.3, 27.7, 5.3],
516
- [17.6, 5.5, 5.3],
517
- ], 10));
518
- }
519
-
520
- function burstPath(n) {
521
- return roundedPolygonPath(n, repeatedPattern([
522
- [50.0, -0.6, 0.6],
523
- [59.2, 15.8, 0.6],
524
- ], 12));
525
- }
526
-
527
- function boomPath(n) {
528
- return roundedPolygonPath(n, repeatedPattern([
529
- [45.7, 29.6, 0.7],
530
- [50.0, -5.1, 0.7],
531
- ], 15));
532
- }
533
-
534
- function softBoomPath(n) {
535
- return roundedPolygonPath(n, repeatedPattern([
536
- [73.3, 45.4, 0.0, 0],
537
- [83.9, 43.7, 53.2, 0],
538
- [94.9, 44.9, 43.9, 1],
539
- [99.8, 47.8, 17.4, 0],
540
- ], 16, true));
541
- }
542
-
543
- function pentagonPath(n) {
544
- return roundedPolygonPath(n, repeatedPattern([
545
- [ 50.0, -0.9, 17.2],
546
- [103.0, 36.5, 16.4],
547
- [ 82.8, 97.0, 16.9],
548
- ], 1, true));
549
- }
550
-
551
- function diamondPath(n) {
552
- return roundedPolygonPath(n, repeatedPattern([
553
- [50.0, 109.6, 15.1, 0.524],
554
- [ 4.0, 50.0, 15.9, 0],
555
- ], 2));
556
- }
557
-
558
- function puffyDiamondPath(n) {
559
- const hpi = Math.PI / 2;
560
- const D = 35, L = 35;
561
- const arcLen = L * Math.PI;
562
-
563
- const segments = [];
564
- for (let i = 0; i < 4; i++) {
565
- const theta = i * hpi - Math.PI / 4;
566
- const cxL = 50 + D * Math.cos(theta);
567
- const cyL = 50 + D * Math.sin(theta);
568
- segments.push({ len: arcLen, type: 'arc', cx: cxL, cy: cyL, r: L, a1: theta - hpi, a2: theta + hpi });
569
- }
570
- return buildPath(segments, n);
571
- }
572
-
573
- function ghostIshPath(n) {
574
- return roundedPolygonPath(n, repeatedPattern([
575
- [ 50.0, 0.0, 100.0],
576
- [100.0, 0.0, 100.0],
577
- [100.0, 114.0, 25.4],
578
- [ 57.5, 90.6, 25.3],
579
- ], 1, true));
580
- }
581
-
582
- function bunPath(n) {
583
- return roundedPolygonPath(n, repeatedPattern([
584
- [79.6, 50.0, 0.0],
585
- [85.3, 51.8, 100.0],
586
- [99.2, 63.1, 100.0],
587
- [96.8, 100.0, 100.0],
588
- ], 2, true));
589
- }
590
-
591
- function clamshellPath(n) {
592
- return roundedPolygonPath(n, repeatedPattern([
593
- [ 17.1, 84.1, 15.9],
594
- [ -2.0, 50.0, 14.0],
595
- [ 17.0, 15.9, 15.9],
596
- ], 2));
597
- }
598
-
599
- function gemPath(n) {
600
- return roundedPolygonPath(n, repeatedPattern([
601
- [49.9, 102.3, 24.1, 0.778],
602
- [-0.5, 79.2, 20.8, 0],
603
- [ 7.3, 25.8, 22.8, 0],
604
- [43.3, 0.0, 49.1, 0],
605
- ], 1, true));
606
- }
607
-
608
- function pillPath(n) {
609
- return roundedPolygonPath(n, [
610
- [90, 75, 50],
611
- [10, 75, 50],
612
- [10, 25, 50],
613
- [90, 25, 50],
614
- ]);
615
- }
616
-
617
- function slantedPath(n) {
618
- return roundedPolygonPath(n, repeatedPattern([
619
- [92.6, 97.0, 18.9, 0.811],
620
- [-2.1, 96.7, 18.7, 0.057],
621
- ], 2));
622
- }
623
-
624
- function flowerPath(n) {
625
- return roundedPolygonPath(n, repeatedPattern([
626
- [37.0, 18.7, 0.0],
627
- [41.6, 4.9, 38.1],
628
- [47.9, 0.1, 9.5],
629
- ], 8, true));
630
- }
631
-
632
- function arrowPath(n) {
633
- return roundedPolygonPath(n, repeatedPattern([
634
- [ 50.0, 89.2, 31.3, 0],
635
- [-21.6, 105.0, 20.7, 0],
636
- [ 49.9, -16.0, 21.5, 1],
637
- [122.5, 106.0, 21.1, 0],
638
- ], 1));
639
- }
640
-
641
- function fanPath(n) {
642
- return roundedPolygonPath(n, repeatedPattern([
643
- [100.4, 100.0, 14.8, 0.417],
644
- [ 0.0, 100.0, 15.1, 0],
645
- [ 0.0, -0.3, 14.8, 0],
646
- [ 97.8, 2.0, 80.3, 0],
647
- ], 1));
648
- }
649
-
650
- function squarePath(n) {
651
- return roundedPolygonPath(n, [
652
- [100, 100, 30], [ 0, 100, 30], [ 0, 0, 30], [100, 0, 30],
653
- ]);
654
- }
655
-
656
- function trianglePath(n) {
657
- return roundedPolygonPath(n, verticesOnCircle(3, [0.2, 0.2, 0.2], -90));
658
- }
659
-
660
- function archPath(n) {
661
- return roundedPolygonPath(n, verticesOnCircle(4, [1, 1, 0.2, 0.2], -135));
662
- }
663
-
664
- function ovalPath(n) {
665
- const a = 50;
666
- const b = 50 * 0.64;
667
- const cos45 = Math.sqrt(2) / 2;
668
- const sin45 = -Math.sqrt(2) / 2;
669
- const points = [];
670
- for (let i = 0; i < n; i++) {
671
- const angle = 2 * Math.PI * i / n - Math.PI / 2;
672
- const x = a * Math.cos(angle);
673
- const y = b * Math.sin(angle);
674
- const rx = x * cos45 - y * sin45;
675
- const ry = x * sin45 + y * cos45;
676
- points.push([50 + rx, 50 + ry]);
677
- }
678
- return emitAlignedPath(points, n);
679
- }
680
-
681
- function pixelCirclePath(n) {
682
- return roundedPolygonPath(n, repeatedPattern([
683
- [ 50.0, 0.0, 0],
684
- [ 70.4, 0.0, 0],
685
- [ 70.4, 6.5, 0],
686
- [ 84.3, 6.5, 0],
687
- [ 84.3, 14.8, 0],
688
- [ 92.6, 14.8, 0],
689
- [ 92.6, 29.6, 0],
690
- [100.0, 29.6, 0],
691
- ], 2, true));
692
- }
693
-
694
- function pixelTrianglePath(n) {
695
- return roundedPolygonPath(n, repeatedPattern([
696
- [ 11.0, 50.0, 0],
697
- [ 11.3, 0.0, 0],
698
- [ 28.7, 0.0, 0],
699
- [ 28.7, 8.7, 0],
700
- [ 42.1, 8.7, 0],
701
- [ 42.1, 17.0, 0],
702
- [ 56.0, 17.0, 0],
703
- [ 56.0, 26.5, 0],
704
- [ 67.4, 26.5, 0],
705
- [ 67.5, 34.4, 0],
706
- [ 78.9, 34.4, 0],
707
- [ 78.9, 43.9, 0],
708
- [ 88.8, 43.9, 0],
709
- ], 1, true));
710
- }
711
-
712
- function puffyPath(n) {
713
- const puffs = 6;
714
- const hpi = Math.PI / 2;
715
- const section = 2 * Math.PI / puffs;
716
- const tanH = Math.tan(Math.PI / puffs);
717
- const D = 50 / (1 + tanH);
718
- const L = D * tanH;
719
- const arcLen = L * Math.PI;
720
-
721
- const segments = [];
722
- for (let i = 0; i < puffs; i++) {
723
- const theta = i * section - hpi;
724
- const cxL = 50 + D * Math.cos(theta);
725
- const cyL = 50 + D * Math.sin(theta);
726
- segments.push({ len: arcLen, type: 'arc', cx: cxL, cy: cyL, r: L, a1: theta - hpi, a2: theta + hpi });
727
- }
728
-
729
- const total = segmentsLength(segments);
730
- const points = [];
731
- for (let i = 0; i < n; i++) {
732
- const [x, y] = pointAt(segments, total, i / n);
733
- points.push([x, 50 + (y - 50) * 0.742]);
734
- }
735
- return emitAlignedPath(points, n);
736
- }
737
-
738
- function semicirclePath(n) {
739
- const hpi = Math.PI / 2;
740
- const r = 16;
741
- const bot = 75 + r / 2;
742
- const cy = bot - r;
743
- const R = 50;
744
- const halfArc = hpi * R;
745
- const arcFillet = hpi * r;
746
- const flat = 100 - 2 * r;
747
-
748
- const segments = [
749
- { len: halfArc, type: 'arc', cx: 50, cy, r: R, a1: -hpi, a2: 0 },
750
- { len: arcFillet, type: 'arc', cx: 100 - r, cy, r, a1: 0, a2: hpi },
751
- { len: flat, type: 'line', x1: 100 - r, y1: bot, x2: r, y2: bot },
752
- { len: arcFillet, type: 'arc', cx: r, cy, r, a1: hpi, a2: Math.PI },
753
- { len: halfArc, type: 'arc', cx: 50, cy, r: R, a1: Math.PI, a2: Math.PI + hpi },
754
- ];
755
- return buildPath(segments, n);
756
- }
757
-
758
- // --- entry point ----------------------------------------------------------
759
-
760
- const SHAPE_GENERATORS = {
761
- 'circle': circlePath,
762
- 'square': squarePath,
763
- 'slanted': slantedPath,
764
- 'arch': archPath,
765
- 'semicircle': semicirclePath,
766
- 'oval': ovalPath,
767
- 'pill': pillPath,
768
- 'triangle': trianglePath,
769
- 'arrow': arrowPath,
770
- 'fan': fanPath,
771
- 'diamond': diamondPath,
772
- 'clamshell': clamshellPath,
773
- 'pentagon': pentagonPath,
774
- 'gem': gemPath,
775
- 'very-sunny': verySunnyPath,
776
- 'sunny': sunnyPath,
777
- 'cookie-4': cookie4Path,
778
- 'cookie-6': cookie6Path,
779
- 'cookie-7': cookie7Path,
780
- 'cookie-9': cookie9Path,
781
- 'cookie-12': cookie12Path,
782
- 'clover-4': clover4Path,
783
- 'clover-8': clover8Path,
784
- 'burst': burstPath,
785
- 'soft-burst': softBurstPath,
786
- 'boom': boomPath,
787
- 'soft-boom': softBoomPath,
788
- 'flower': flowerPath,
789
- 'puffy': puffyPath,
790
- 'puffy-diamond': puffyDiamondPath,
791
- 'ghost-ish': ghostIshPath,
792
- 'pixel-circle': pixelCirclePath,
793
- 'pixel-triangle': pixelTrianglePath,
794
- 'bun': bunPath,
795
- 'heart': heartPath,
796
- };
797
-
798
- const DEFAULT_VERTEX_COUNT = 128;
799
-
800
- export function generatePathsScss(vertexCount = DEFAULT_VERTEX_COUNT) {
801
- const longest = Math.max(...Object.keys(SHAPE_GENERATORS).map(s => s.length));
802
- const lines = [
803
- '//',
804
- '// GENERATED by tools/shapes/generate.mjs — do not edit by hand.',
805
- '// Regenerate with `npm run gen:shapes` after changing the generator',
806
- '// or any shape parameter. CI fails (`npm run check:shapes`) if this',
807
- '// file is out of date.',
808
- '//',
809
- '',
810
- '$paths: (',
811
- ];
812
- for (const [name, fn] of Object.entries(SHAPE_GENERATORS)) {
813
- const d = fn(vertexCount);
814
- const padding = ' '.repeat(longest - name.length);
815
- lines.push(` ${name}:${padding} '${d}',`);
816
- }
817
- lines.push(');', '');
818
- return lines.join('\n');
819
- }
820
-
821
- export const TARGET_PATH = targetPath;
822
-
823
- const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
824
- if (isMain) {
825
- const out = generatePathsScss();
826
- fs.writeFileSync(targetPath, out);
827
- console.log(`Wrote ${Object.keys(SHAPE_GENERATORS).length} shapes to ${path.relative(process.cwd(), targetPath)} (${out.length} bytes).`);
828
- }