@wodzik/cubecore 0.1.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 (279) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +118 -0
  3. package/analyze/client.d.ts +18 -0
  4. package/analyze/client.js +42 -0
  5. package/analyze/index.d.ts +114 -0
  6. package/analyze/index.js +151 -0
  7. package/analyze/rotations.d.ts +3 -0
  8. package/analyze/rotations.js +15 -0
  9. package/analyze/roux.d.ts +67 -0
  10. package/analyze/roux.js +95 -0
  11. package/analyze/worker.d.ts +6 -0
  12. package/analyze/worker.js +31 -0
  13. package/analyze/zz.d.ts +47 -0
  14. package/analyze/zz.js +102 -0
  15. package/bld/index.d.ts +14 -0
  16. package/bld/index.js +14 -0
  17. package/bld/memo.d.ts +66 -0
  18. package/bld/memo.js +139 -0
  19. package/bld/scheme.d.ts +30 -0
  20. package/bld/scheme.js +35 -0
  21. package/bld/skin.d.ts +26 -0
  22. package/bld/skin.js +46 -0
  23. package/bld/tracker.d.ts +68 -0
  24. package/bld/tracker.js +145 -0
  25. package/bluetooth/clock.d.ts +22 -0
  26. package/bluetooth/clock.js +37 -0
  27. package/bluetooth/cubeSkins.d.ts +31 -0
  28. package/bluetooth/cubeSkins.js +32 -0
  29. package/bluetooth/gyro.d.ts +31 -0
  30. package/bluetooth/gyro.js +46 -0
  31. package/bluetooth/index.d.ts +5 -0
  32. package/bluetooth/index.js +5 -0
  33. package/bluetooth/session.d.ts +140 -0
  34. package/bluetooth/session.js +201 -0
  35. package/bluetooth/simulated.d.ts +52 -0
  36. package/bluetooth/simulated.js +80 -0
  37. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.d.ts +38 -0
  38. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-definitions.js +51 -0
  39. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.d.ts +35 -0
  40. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-encrypter.js +70 -0
  41. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.d.ts +273 -0
  42. package/bluetooth/vendor/smartcube-web-bluetooth/gan-cube-protocol.js +846 -0
  43. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.d.ts +38 -0
  44. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen1.js +340 -0
  45. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.d.ts +11 -0
  46. package/bluetooth/vendor/smartcube-web-bluetooth/gan-gen234-packet-validate.js +118 -0
  47. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.d.ts +2 -0
  48. package/bluetooth/vendor/smartcube-web-bluetooth/gan-mac-salt.js +15 -0
  49. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.d.ts +18 -0
  50. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-cube.js +152 -0
  51. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.d.ts +74 -0
  52. package/bluetooth/vendor/smartcube-web-bluetooth/gan-smart-timer.js +161 -0
  53. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.d.ts +7 -0
  54. package/bluetooth/vendor/smartcube-web-bluetooth/gan-write-queue.js +11 -0
  55. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.d.ts +2 -0
  56. package/bluetooth/vendor/smartcube-web-bluetooth/gatt-characteristic-write.js +36 -0
  57. package/bluetooth/vendor/smartcube-web-bluetooth/index.d.ts +4 -0
  58. package/bluetooth/vendor/smartcube-web-bluetooth/index.js +4 -0
  59. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.d.ts +12 -0
  60. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/address-hints.js +122 -0
  61. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.d.ts +11 -0
  62. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/build-picker-options.js +55 -0
  63. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.d.ts +21 -0
  64. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/default-optional-services.js +47 -0
  65. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.d.ts +6 -0
  66. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/device-name-scan-filters.js +24 -0
  67. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.d.ts +6 -0
  68. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/gatt-snapshot.js +57 -0
  69. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.d.ts +11 -0
  70. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-candidates.js +67 -0
  71. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.d.ts +8 -0
  72. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-moyu32.js +98 -0
  73. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.d.ts +8 -0
  74. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/mac-probe-qiyi.js +128 -0
  75. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.d.ts +5 -0
  76. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/moyu32-session-crypto.js +60 -0
  77. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.d.ts +2 -0
  78. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/normalize-uuid.js +8 -0
  79. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.d.ts +7 -0
  80. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/packet-sanity.js +142 -0
  81. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.d.ts +6 -0
  82. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/profile-rank.js +21 -0
  83. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.d.ts +32 -0
  84. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/attachment/types.js +1 -0
  85. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.d.ts +6 -0
  86. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/ble-utils.js +68 -0
  87. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.d.ts +3 -0
  88. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/connect.js +178 -0
  89. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.d.ts +16 -0
  90. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/cubie-cube.js +141 -0
  91. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.d.ts +11 -0
  92. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/index.js +10 -0
  93. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.d.ts +26 -0
  94. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocol.js +8 -0
  95. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.d.ts +3 -0
  96. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gan.js +265 -0
  97. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.d.ts +3 -0
  98. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/giiker.js +345 -0
  99. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.d.ts +15 -0
  100. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/gocube.js +382 -0
  101. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.d.ts +3 -0
  102. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-mhc.js +352 -0
  103. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.d.ts +57 -0
  104. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu-v1.js +243 -0
  105. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.d.ts +3 -0
  106. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/moyu32.js +454 -0
  107. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.d.ts +3 -0
  108. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/protocols/qiyi.js +424 -0
  109. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.d.ts +78 -0
  110. package/bluetooth/vendor/smartcube-web-bluetooth/smartcube/types.js +1 -0
  111. package/bluetooth/vendor/smartcube-web-bluetooth/utils.d.ts +45 -0
  112. package/bluetooth/vendor/smartcube-web-bluetooth/utils.js +150 -0
  113. package/cfop/caseData.d.ts +16 -0
  114. package/cfop/caseData.js +88 -0
  115. package/cfop/cases.d.ts +42 -0
  116. package/cfop/cases.js +116 -0
  117. package/cfop/f2l.d.ts +28 -0
  118. package/cfop/f2l.js +88 -0
  119. package/cfop/f2lData.d.ts +15 -0
  120. package/cfop/f2lData.js +49 -0
  121. package/cfop/index.d.ts +41 -0
  122. package/cfop/index.js +110 -0
  123. package/cfop/slots.d.ts +2 -0
  124. package/cfop/slots.js +1 -0
  125. package/core/arrows.d.ts +24 -0
  126. package/core/arrows.js +22 -0
  127. package/core/bytes.d.ts +27 -0
  128. package/core/bytes.js +145 -0
  129. package/core/centers.d.ts +32 -0
  130. package/core/centers.js +83 -0
  131. package/core/checks.d.ts +53 -0
  132. package/core/checks.js +85 -0
  133. package/core/cubies.d.ts +45 -0
  134. package/core/cubies.js +169 -0
  135. package/core/frames.d.ts +61 -0
  136. package/core/frames.js +126 -0
  137. package/core/geometry.d.ts +58 -0
  138. package/core/geometry.js +132 -0
  139. package/core/index.d.ts +19 -0
  140. package/core/index.js +19 -0
  141. package/core/masks.d.ts +32 -0
  142. package/core/masks.js +39 -0
  143. package/core/method.d.ts +35 -0
  144. package/core/method.js +30 -0
  145. package/core/moves.d.ts +48 -0
  146. package/core/moves.js +88 -0
  147. package/core/notation.d.ts +48 -0
  148. package/core/notation.js +226 -0
  149. package/core/physical.d.ts +53 -0
  150. package/core/physical.js +99 -0
  151. package/core/practice.d.ts +62 -0
  152. package/core/practice.js +87 -0
  153. package/core/sequence.d.ts +90 -0
  154. package/core/sequence.js +157 -0
  155. package/core/stages.d.ts +90 -0
  156. package/core/stages.js +21 -0
  157. package/core/state.d.ts +55 -0
  158. package/core/state.js +140 -0
  159. package/core/stateCodec.d.ts +12 -0
  160. package/core/stateCodec.js +51 -0
  161. package/core/stream.d.ts +45 -0
  162. package/core/stream.js +75 -0
  163. package/core/tracker.d.ts +62 -0
  164. package/core/tracker.js +121 -0
  165. package/element/alg.d.ts +45 -0
  166. package/element/alg.js +150 -0
  167. package/element/base.d.ts +2 -0
  168. package/element/base.js +3 -0
  169. package/element/bld.d.ts +82 -0
  170. package/element/bld.js +222 -0
  171. package/element/index.d.ts +8 -0
  172. package/element/index.js +21 -0
  173. package/element/model.d.ts +36 -0
  174. package/element/model.js +64 -0
  175. package/element/player.d.ts +196 -0
  176. package/element/player.js +680 -0
  177. package/element/practice.d.ts +75 -0
  178. package/element/practice.js +150 -0
  179. package/element/scramble.d.ts +50 -0
  180. package/element/scramble.js +107 -0
  181. package/element/sequence.d.ts +126 -0
  182. package/element/sequence.js +254 -0
  183. package/element/styles.d.ts +32 -0
  184. package/element/styles.js +196 -0
  185. package/image/cache.d.ts +21 -0
  186. package/image/cache.js +66 -0
  187. package/image/index.d.ts +2 -0
  188. package/image/index.js +2 -0
  189. package/image/png-node.d.ts +6 -0
  190. package/image/png-node.js +10 -0
  191. package/image/svg.d.ts +36 -0
  192. package/image/svg.js +215 -0
  193. package/lbl/index.d.ts +10 -0
  194. package/lbl/index.js +31 -0
  195. package/methods/index.d.ts +20 -0
  196. package/methods/index.js +33 -0
  197. package/package.json +133 -0
  198. package/petrus/index.d.ts +11 -0
  199. package/petrus/index.js +27 -0
  200. package/react/index.d.ts +176 -0
  201. package/react/index.js +228 -0
  202. package/render/arrows.d.ts +68 -0
  203. package/render/arrows.js +190 -0
  204. package/render/build/attachments.d.ts +35 -0
  205. package/render/build/attachments.js +94 -0
  206. package/render/build/models.d.ts +9 -0
  207. package/render/build/models.js +27 -0
  208. package/render/build/tiles.d.ts +48 -0
  209. package/render/build/tiles.js +194 -0
  210. package/render/gltf.d.ts +10 -0
  211. package/render/gltf.js +106 -0
  212. package/render/index.d.ts +12 -0
  213. package/render/index.js +12 -0
  214. package/render/layers.d.ts +21 -0
  215. package/render/layers.js +24 -0
  216. package/render/pictures.d.ts +46 -0
  217. package/render/pictures.js +80 -0
  218. package/render/pieceModels.d.ts +38 -0
  219. package/render/pieceModels.js +43 -0
  220. package/render/renderer.d.ts +165 -0
  221. package/render/renderer.js +647 -0
  222. package/render/tile.d.ts +114 -0
  223. package/render/tile.js +390 -0
  224. package/render/viewports.d.ts +25 -0
  225. package/render/viewports.js +26 -0
  226. package/roux/cmll.d.ts +35 -0
  227. package/roux/cmll.js +121 -0
  228. package/roux/cmllData.d.ts +12 -0
  229. package/roux/cmllData.js +49 -0
  230. package/roux/index.d.ts +52 -0
  231. package/roux/index.js +175 -0
  232. package/skin/attachments.d.ts +82 -0
  233. package/skin/attachments.js +110 -0
  234. package/skin/index.d.ts +3 -0
  235. package/skin/index.js +3 -0
  236. package/skin/shapes.d.ts +112 -0
  237. package/skin/shapes.js +147 -0
  238. package/skin/skin.d.ts +911 -0
  239. package/skin/skin.js +412 -0
  240. package/solve/client.d.ts +58 -0
  241. package/solve/client.js +56 -0
  242. package/solve/handle.d.ts +6 -0
  243. package/solve/handle.js +38 -0
  244. package/solve/index.d.ts +8 -0
  245. package/solve/index.js +7 -0
  246. package/solve/lse.d.ts +42 -0
  247. package/solve/lse.js +151 -0
  248. package/solve/pieces.d.ts +18 -0
  249. package/solve/pieces.js +24 -0
  250. package/solve/protocol.d.ts +70 -0
  251. package/solve/protocol.js +16 -0
  252. package/solve/scramble.d.ts +76 -0
  253. package/solve/scramble.js +187 -0
  254. package/solve/stage.d.ts +108 -0
  255. package/solve/stage.js +532 -0
  256. package/solve/twophase.d.ts +36 -0
  257. package/solve/twophase.js +229 -0
  258. package/solve/worker.d.ts +6 -0
  259. package/solve/worker.js +15 -0
  260. package/timeline/codec.d.ts +23 -0
  261. package/timeline/codec.js +84 -0
  262. package/timeline/index.d.ts +7 -0
  263. package/timeline/index.js +7 -0
  264. package/timeline/recording.d.ts +19 -0
  265. package/timeline/recording.js +20 -0
  266. package/timeline/replay.d.ts +68 -0
  267. package/timeline/replay.js +145 -0
  268. package/timeline/segments.d.ts +31 -0
  269. package/timeline/segments.js +39 -0
  270. package/timeline/share.d.ts +30 -0
  271. package/timeline/share.js +62 -0
  272. package/timeline/stats.d.ts +34 -0
  273. package/timeline/stats.js +56 -0
  274. package/timeline/timings.d.ts +30 -0
  275. package/timeline/timings.js +54 -0
  276. package/zz/index.d.ts +29 -0
  277. package/zz/index.js +68 -0
  278. package/zz/seeds.d.ts +9 -0
  279. package/zz/seeds.js +29 -0
package/image/svg.js ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Cube pictures as SVG strings — no DOM, no canvas: the same call works in a
3
+ * browser, a worker, or on a server (Node/Bun), and the output is a plain
4
+ * string you can cache, inline, serve or turn into PNG.
5
+ *
6
+ * Looks come from the same `Skin` as the 3D renderer (@cubecore/skin):
7
+ * colours, mask colours, tile shapes per piece kind, custom SVG tile outlines,
8
+ * stickerless tiles and the logo — one skin, one look in 3D and in pictures.
9
+ *
10
+ * Views:
11
+ * "iso" — 3D-looking cube showing U, F and R (like a case card)
12
+ * "top" — last-layer diagram: U face plus the side stickers of the top layer
13
+ * "net" — unfolded cube (U on top, L F R B across, D below)
14
+ *
15
+ * Every tile is drawn in its face's own (a, b) coordinates and then mapped to
16
+ * the picture by an affine projection per view, so shapes, paths and logos
17
+ * work the same way in all three views.
18
+ */
19
+ import { FACELETS, FACE_BASIS, IDENTITY_FRAME, apply, centerUp, colorAt, faceOfNormal, maskStateAt, solvedSpins, stickerTurn, view as frameView, } from "../core/index.js";
20
+ import { SKINS, featureDef, imageUrl, kindOfSticker, roundedOutline, selectedStickers, stickerColor, stickerLayout, overlayOutline, stickerlessOutline, themed, tileSize, tileThickness, } from "../skin/index.js";
21
+ const r3 = (n) => Math.round(n * 1000) / 1000;
22
+ const add = (p, q, k = 1) => [p[0] + q[0] * k, p[1] + q[1] * k, p[2] + q[2] * k];
23
+ const dot = (p, q) => p[0] * q[0] + p[1] * q[1] + p[2] * q[2];
24
+ // ─── projections (affine, per face) ───
25
+ const COS30 = Math.cos(Math.PI / 6);
26
+ const isoProjection = (_face, p) => [(p[0] - p[2]) * COS30, (p[0] + p[2]) * 0.5 - p[1]];
27
+ /** Faces of the net sit apart, so stickerless tiles of neighbouring faces don't run into each other. */
28
+ const NET_GAP = 0.15;
29
+ const NET_CELL = { U: [1, 0], L: [0, 1], F: [1, 1], R: [2, 1], B: [3, 1], D: [1, 2] };
30
+ const NET_ORIGIN = Object.fromEntries(Object.entries(NET_CELL).map(([f, [c, r]]) => [f, [c * (3 + NET_GAP), r * (3 + NET_GAP)]]));
31
+ const netProjection = (face, p) => {
32
+ const { a, b } = FACE_BASIS[face];
33
+ const [ox, oy] = NET_ORIGIN[face];
34
+ return [ox + 1.5 + dot(p, a), oy + 1.5 - dot(p, b)];
35
+ };
36
+ /**
37
+ * Side stickers of the top layer are squashed into strips of this depth
38
+ * around the U face, and spread out as they go down — in perspective, as
39
+ * seen from above (the strips are trapezoids, like the sides of a cube
40
+ * looked at from over its top).
41
+ */
42
+ const STRIP = 0.35;
43
+ const STRIP_GAP = 0.08;
44
+ const topProjection = (face, p) => {
45
+ if (face === "U")
46
+ return [1.5 + p[0], 1.5 + p[2]];
47
+ const n = FACE_BASIS[face].n;
48
+ const out = STRIP_GAP + (1.5 - p[1]) * STRIP;
49
+ const spread = 1 + out / 1.5;
50
+ // Along the edge (the axis the face normal doesn't point along): spread out with the depth.
51
+ const x = n[0] !== 0 ? p[0] : p[0] * spread;
52
+ const z = n[2] !== 0 ? p[2] : p[2] * spread;
53
+ return [1.5 + x + n[0] * out, 1.5 + z + n[2] * out];
54
+ };
55
+ const E = STRIP + STRIP_GAP + 0.1;
56
+ const VIEWS = {
57
+ iso: { project: isoProjection, shows: (f) => f.face === "U" || f.face === "F" || f.face === "R", fullFaces: ["U", "F", "R"], bodies: ["U", "F", "R"], box: [-3 * COS30 - 0.1, -3.1, 6 * COS30 + 0.2, 6.2] },
58
+ net: { project: netProjection, shows: () => true, fullFaces: ["U", "R", "F", "D", "L", "B"], bodies: ["U", "R", "F", "D", "L", "B"], box: [-0.1, -0.1, 12 + 3 * NET_GAP + 0.2, 9 + 2 * NET_GAP + 0.2] },
59
+ top: { project: topProjection, shows: (f) => f.face === "U" || (f.pos[1] === 1 && f.face !== "D"), fullFaces: ["U"], bodies: ["U"], box: [-E, -E, 3 + 2 * E, 3 + 2 * E] },
60
+ };
61
+ // ─── drawing helpers ───
62
+ const polygon = (pts) => `M${pts.map((p) => `${r3(p[0])},${r3(p[1])}`).join("L")}Z`;
63
+ /** Rounded polygon through `pts` (convex, in order) — the cubie body faces. */
64
+ function roundedPolygon(pts, radius) {
65
+ const n = pts.length;
66
+ let d = "";
67
+ for (let i = 0; i < n; i++) {
68
+ const prev = pts[(i + n - 1) % n], cur = pts[i], next = pts[(i + 1) % n];
69
+ const toward = (a, b) => {
70
+ const len = Math.hypot(b[0] - a[0], b[1] - a[1]);
71
+ const k = Math.min(radius, len / 2) / len;
72
+ return [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k];
73
+ };
74
+ const p1 = toward(cur, prev), p2 = toward(cur, next);
75
+ d += `${i === 0 ? "M" : "L"}${r3(p1[0])},${r3(p1[1])}Q${r3(cur[0])},${r3(cur[1])} ${r3(p2[0])},${r3(p2[1])}`;
76
+ }
77
+ return d + "Z";
78
+ }
79
+ /** 3D point of local tile coordinates (x along a, y along b) on facelet `f`. */
80
+ function onFace(f, x, y) {
81
+ const { a, b } = FACE_BASIS[f.face];
82
+ return add(add(add(f.pos, f.normal, 0.5), a, x), b, y);
83
+ }
84
+ /**
85
+ * SVG `matrix(...)` mapping a unit box (0..1, y down — SVG convention) of side
86
+ * `side` onto facelet `f` in the picture: turned `spin` quarters about its
87
+ * own centre, moved by `offset` (tile-local, cubie units), then turned
88
+ * `quarters` CCW with the sticker.
89
+ */
90
+ function boxTransform(f, side, quarters, project, offset = [0, 0], spin = 0, reach) {
91
+ const c = Math.cos((quarters * Math.PI) / 2), s = Math.sin((quarters * Math.PI) / 2);
92
+ const c2 = Math.cos((spin * Math.PI) / 2), s2 = Math.sin((spin * Math.PI) / 2);
93
+ const half = side / 2;
94
+ const at = (px, py) => {
95
+ // Box point → turned by its own `spin` → moved by `offset` (sticker frame) → turned with the sticker.
96
+ // `reach`: stickerless path tiles — the box's outer sides (left / top in the path convention) run out to the cube edge.
97
+ const bx = reach?.left ? -reach.edge + px * (reach.edge + half) : (px - 0.5) * side;
98
+ const by = reach?.top ? reach.edge - py * (reach.edge + half) : (0.5 - py) * side;
99
+ const lx = bx * c2 - by * s2 + offset[0], ly = bx * s2 + by * c2 + offset[1];
100
+ return project(f.face, onFace(f, lx * c - ly * s, lx * s + ly * c));
101
+ };
102
+ const o = at(0, 0), x = at(1, 0), y = at(0, 1);
103
+ return `matrix(${[x[0] - o[0], x[1] - o[1], y[0] - o[0], y[1] - o[1], o[0], o[1]].map((n) => r3(n)).join(" ")})`;
104
+ }
105
+ function tileShape(skin) {
106
+ const r = skin.stickers.radius;
107
+ return skin.stickers.shape ?? { corner: { inner: r, outer: r }, edge: { inner: r, outer: r }, center: r };
108
+ }
109
+ /** A physical centre's spin, re-expressed for the canonical face it plays in `frame`. */
110
+ function spinInFrame(spins, state, centre, frame) {
111
+ const spin = spins[centre];
112
+ if (!frame || frame === IDENTITY_FRAME)
113
+ return spin;
114
+ const f = FACELETS[state.indexOf(centre * 9 + 4)];
115
+ const up = centerUp(f.face, spin);
116
+ // Canonical vectors map to physical ones by `frame.matrix`; go back with its transpose.
117
+ const m = frame.matrix;
118
+ const t = [
119
+ [m[0][0], m[1][0], m[2][0]],
120
+ [m[0][1], m[1][1], m[2][1]],
121
+ [m[0][2], m[1][2], m[2][2]],
122
+ ];
123
+ const face = faceOfNormal(apply(t, f.normal));
124
+ const canonicalUp = apply(t, up);
125
+ for (let k = 0; k < 4; k++)
126
+ if (centerUp(face, k).every((v, i) => v === canonicalUp[i]))
127
+ return k;
128
+ return 0;
129
+ }
130
+ const attr = (v) => v.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
131
+ // ─── render ───
132
+ export function renderSvg(state, options = {}) {
133
+ const viewName = options.view ?? "iso";
134
+ const size = options.size ?? 160;
135
+ const skin = themed(options.skin ?? SKINS.default, options.theme ?? "dark");
136
+ const v = VIEWS[viewName];
137
+ const s = options.frame && options.frame !== IDENTITY_FRAME ? frameView(state, options.frame) : state;
138
+ const sideOf = (kind) => tileSize(skin, kind) * skin.cubieSize;
139
+ const shape = tileShape(skin);
140
+ let out = "";
141
+ if (skin.background)
142
+ out += `<rect x="${r3(v.box[0])}" y="${r3(v.box[1])}" width="${r3(v.box[2])}" height="${r3(v.box[3])}" fill="${skin.background}"/>`;
143
+ // Plastic: one rounded square per drawn face — unless the skin wants the gaps see-through in pictures.
144
+ const plastic = skin.pictureBody === undefined ? skin.body : skin.pictureBody;
145
+ for (const face of plastic === null ? [] : v.bodies) {
146
+ const f = FACELETS.find((x) => x.face === face && x.index % 9 === 4);
147
+ const corners = [[-1, -1], [1, -1], [1, 1], [-1, 1]].map(([u, w]) => v.project(face, onFace(f, u * 1.5, w * 1.5)));
148
+ out += `<path d="${roundedPolygon(corners, skin.cubieRadius * 1.2)}" fill="${plastic}"/>`;
149
+ }
150
+ for (const f of FACELETS) {
151
+ if (!v.shows(f))
152
+ continue;
153
+ const st = options.mask ? maskStateAt(options.mask, s, f.index) : "regular";
154
+ const fill = stickerColor(skin, colorAt(s, f.index), st);
155
+ if (!fill)
156
+ continue;
157
+ const layout = stickerLayout(f.index, shape);
158
+ const path = skin.stickers.paths?.[layout.kind];
159
+ const kindSide = sideOf(layout.kind);
160
+ // Stickers lying on the body (stickered cubes): the sticker's own outline on the plastic.
161
+ const overlay = skin.stickers.overlay;
162
+ if (overlay) {
163
+ out += `<path d="${polygon(overlayOutline(f.index, overlay, 6).map(([x, y]) => v.project(f.face, onFace(f, x, y))))}" fill="${fill}"/>`;
164
+ continue;
165
+ }
166
+ if (path) {
167
+ const reach = skin.stickers.fillOuter && layout.kind !== "center" ? { left: layout.kind === "corner", top: true, edge: 0.506 } : undefined;
168
+ out += `<path d="${attr(path)}" transform="${boxTransform(f, kindSide, layout.pathQuarters, v.project, [0, 0], 0, reach)}" fill="${fill}"/>`;
169
+ continue;
170
+ }
171
+ // Stickerless: outer sides reach the cube edge (a hair past it, so neighbouring faces overlap instead of leaving an anti-aliased seam).
172
+ const outline = skin.stickers.fillOuter ? stickerlessOutline(layout, kindSide, 0.506, 6) : roundedOutline(kindSide, layout.radii, 6);
173
+ out += `<path d="${polygon(outline.map(([x, y]) => v.project(f.face, onFace(f, x, y))))}" fill="${fill}"/>`;
174
+ }
175
+ // Decals and features ride on their sticker, turned with it — as in 3D.
176
+ const spins = options.spins ?? solvedSpins();
177
+ const turnOf = (sticker) => sticker % 9 === 4 ? spinInFrame(spins, state, Math.floor(sticker / 9), options.frame) : stickerTurn(s, sticker);
178
+ const placed = (sticker, regularOnly) => {
179
+ const pos = s.indexOf(sticker);
180
+ const f = FACELETS[pos];
181
+ if (!v.shows(f) || !v.fullFaces.includes(f.face))
182
+ return null; // not on squashed strips
183
+ const st = options.mask ? maskStateAt(options.mask, s, pos) : "regular";
184
+ if (st === "invisible" || (regularOnly && st !== "regular"))
185
+ return null;
186
+ return f;
187
+ };
188
+ for (const use of skin.features ?? []) {
189
+ const def = featureDef(use.type);
190
+ if (!def?.svg)
191
+ continue;
192
+ for (const sticker of selectedStickers(use.select)) {
193
+ const f = placed(sticker, false);
194
+ const kind = kindOfSticker(sticker);
195
+ const markup = def.svg({ side: sideOf(kind), thickness: tileThickness(skin, kind), params: use.params ?? {} });
196
+ if (f)
197
+ out += `<g transform="${boxTransform(f, sideOf(kind), turnOf(sticker), v.project)}">${markup}</g>`;
198
+ }
199
+ }
200
+ for (const decal of skin.decals ?? []) {
201
+ const blend = decal.blend === "multiply" ? ` style="mix-blend-mode:multiply"` : "";
202
+ for (const sticker of selectedStickers(decal.select)) {
203
+ const f = placed(sticker, decal.onlyRegular ?? true);
204
+ if (!f)
205
+ continue;
206
+ const side = sideOf(kindOfSticker(sticker));
207
+ const offset = [((decal.offset?.[0] ?? 0) * side) / 2, ((decal.offset?.[1] ?? 0) * side) / 2];
208
+ const t = boxTransform(f, side * decal.size, turnOf(sticker), v.project, offset, decal.rotate ?? 0);
209
+ out += `<image href="${attr(imageUrl(decal.image))}" width="1" height="1" preserveAspectRatio="xMidYMid meet" transform="${t}"${blend}/>`;
210
+ }
211
+ }
212
+ const [x, y, w, h] = v.box.map(r3);
213
+ const height = Math.round((size * h) / w);
214
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${x} ${y} ${w} ${h}" width="${size}" height="${height}">${out}</svg>`;
215
+ }
package/lbl/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Beginner layer-by-layer: cross, four first-layer corners, four middle-layer
3
+ * edges, last layer orientation in two halves, then corner and edge
4
+ * permutation. Colour and orientation neutral like every cubecore method.
5
+ */
6
+ import { type Frame, type Mask, type MaskRule, type Method } from "../core/index.js";
7
+ export declare const LBL: Method;
8
+ export type LblMask = "cross" | "first-layer" | "second-layer";
9
+ export declare const LBL_MASKS: Record<LblMask, MaskRule>;
10
+ export declare const lblMask: (name: LblMask, frame?: Frame) => Mask;
package/lbl/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Beginner layer-by-layer: cross, four first-layer corners, four middle-layer
3
+ * edges, last layer orientation in two halves, then corner and edge
4
+ * permutation. Colour and orientation neutral like every cubecore method.
5
+ */
6
+ import { IDENTITY_FRAME, buildMask, checks as C, countedStages } from "../core/index.js";
7
+ export const LBL = {
8
+ id: "lbl",
9
+ name: "Layer by layer",
10
+ stages: [
11
+ { id: "cross", label: "Cross", done: C.crossSolved },
12
+ ...countedStages("corner", "Corner", (s) => C.SLOTS.filter((x) => C.slotCornerSolved(s, x)), C.crossSolved),
13
+ ...countedStages("edge", "Edge", (s) => C.SLOTS.filter((x) => C.slotEdgeSolved(s, x)), C.firstLayerSolved),
14
+ {
15
+ id: "oll-1",
16
+ label: "Orient (1st half)",
17
+ done: (s) => C.f2lSolved(s) && (C.topCornersOriented(s) || C.topEdgesOriented(s)),
18
+ detail: (s) => (C.topEdgesOriented(s) ? "edges" : "corners"),
19
+ },
20
+ { id: "oll-2", label: "Orient (2nd half)", done: (s) => C.f2lSolved(s) && C.topOriented(s) },
21
+ { id: "pll-corners", label: "Permute corners", done: (s) => C.f2lSolved(s) && C.topOriented(s) && C.llCornersPermuted(s) },
22
+ { id: "pll", label: "Permute edges", done: C.solvedUpToAuf },
23
+ { id: "auf", label: "AUF", done: C.isSolved },
24
+ ],
25
+ };
26
+ export const LBL_MASKS = {
27
+ cross: (f, c) => (c.kind === "center" || (c.kind === "edge" && c.pos[1] === -1) ? "regular" : "ignored"),
28
+ "first-layer": (f, c) => (c.kind === "center" || c.pos[1] === -1 ? "regular" : "ignored"),
29
+ "second-layer": (f, c) => (c.kind === "center" || c.pos[1] === 0 ? "regular" : c.pos[1] === -1 ? "dim" : "ignored"),
30
+ };
31
+ export const lblMask = (name, frame = IDENTITY_FRAME) => buildMask(LBL_MASKS[name], frame);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * All methods together — for apps that let the user pick a method, or show
3
+ * every analysis side by side. Apps using a single method import its own
4
+ * package (@cubecore/cfop…) and skip the rest.
5
+ */
6
+ import { type Frame, type Mask, type MaskRule, type Method } from "../core/index.js";
7
+ import { CFOP } from "../cfop/index.js";
8
+ import { LBL } from "../lbl/index.js";
9
+ import { PETRUS } from "../petrus/index.js";
10
+ import { ROUX } from "../roux/index.js";
11
+ import { ZZ } from "../zz/index.js";
12
+ export { CFOP, LBL, PETRUS, ROUX, ZZ };
13
+ export declare const METHODS: readonly Method[];
14
+ export declare const methodById: (id: string) => Method | undefined;
15
+ /** Every method's masks, keyed "method:name" (e.g. "cfop:oll", "roux:cmll"). */
16
+ export declare const METHOD_MASKS: Readonly<Record<string, MaskRule>>;
17
+ /** All mask names: the generic presets ("full", "first-layer", "ll") and every "method:name". */
18
+ export declare const MASK_NAMES: readonly string[];
19
+ /** A mask by name from `MASK_NAMES`, for a cube held in `frame`. */
20
+ export declare function maskByName(name: string, frame?: Frame): Mask;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * All methods together — for apps that let the user pick a method, or show
3
+ * every analysis side by side. Apps using a single method import its own
4
+ * package (@cubecore/cfop…) and skip the rest.
5
+ */
6
+ import { IDENTITY_FRAME, MASK_PRESETS, buildMask, presetMask } from "../core/index.js";
7
+ import { CFOP, CFOP_MASKS } from "../cfop/index.js";
8
+ import { LBL, LBL_MASKS } from "../lbl/index.js";
9
+ import { PETRUS, PETRUS_MASKS } from "../petrus/index.js";
10
+ import { ROUX, ROUX_MASKS } from "../roux/index.js";
11
+ import { ZZ, ZZ_MASKS } from "../zz/index.js";
12
+ export { CFOP, LBL, PETRUS, ROUX, ZZ };
13
+ export const METHODS = [CFOP, LBL, ROUX, ZZ, PETRUS];
14
+ export const methodById = (id) => METHODS.find((m) => m.id === id);
15
+ /** Every method's masks, keyed "method:name" (e.g. "cfop:oll", "roux:cmll"). */
16
+ export const METHOD_MASKS = Object.fromEntries([
17
+ ["cfop", CFOP_MASKS],
18
+ ["lbl", LBL_MASKS],
19
+ ["roux", ROUX_MASKS],
20
+ ["zz", ZZ_MASKS],
21
+ ["petrus", PETRUS_MASKS],
22
+ ].flatMap(([method, masks]) => Object.entries(masks).map(([name, rule]) => [`${method}:${name}`, rule])));
23
+ /** All mask names: the generic presets ("full", "first-layer", "ll") and every "method:name". */
24
+ export const MASK_NAMES = [...MASK_PRESETS, ...Object.keys(METHOD_MASKS)];
25
+ /** A mask by name from `MASK_NAMES`, for a cube held in `frame`. */
26
+ export function maskByName(name, frame = IDENTITY_FRAME) {
27
+ const rule = METHOD_MASKS[name];
28
+ if (rule)
29
+ return buildMask(rule, frame);
30
+ if (MASK_PRESETS.includes(name))
31
+ return presetMask(name, frame);
32
+ throw new Error(`Unknown mask "${name}"`);
33
+ }
package/package.json ADDED
@@ -0,0 +1,133 @@
1
+ {
2
+ "name": "@wodzik/cubecore",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.0",
7
+ "description": "A 3×3×3 cube library for the web, built around smart (Bluetooth) cubes: state, notation, methods, solvers and scrambles, a three.js renderer with skins, pictures, web components, React bindings.",
8
+ "license": "MPL-2.0",
9
+ "author": "Tomasz Wodzikowski <wodziszczakow@gmail.com>",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/wodzik/cubecore.git"
13
+ },
14
+ "homepage": "https://github.com/wodzik/cubecore#readme",
15
+ "bugs": "https://github.com/wodzik/cubecore/issues",
16
+ "keywords": [
17
+ "rubiks-cube",
18
+ "speedcubing",
19
+ "cube",
20
+ "smart-cube",
21
+ "bluetooth",
22
+ "solver",
23
+ "scramble",
24
+ "three",
25
+ "web-components",
26
+ "react",
27
+ "cfop",
28
+ "roux",
29
+ "zz"
30
+ ],
31
+ "type": "module",
32
+ "exports": {
33
+ "./skin": {
34
+ "types": "./skin/index.d.ts",
35
+ "import": "./skin/index.js"
36
+ },
37
+ "./methods": {
38
+ "types": "./methods/index.d.ts",
39
+ "import": "./methods/index.js"
40
+ },
41
+ "./solve": {
42
+ "types": "./solve/index.d.ts",
43
+ "import": "./solve/index.js"
44
+ },
45
+ "./core": {
46
+ "types": "./core/index.d.ts",
47
+ "import": "./core/index.js"
48
+ },
49
+ "./bld": {
50
+ "types": "./bld/index.d.ts",
51
+ "import": "./bld/index.js"
52
+ },
53
+ "./lbl": {
54
+ "types": "./lbl/index.d.ts",
55
+ "import": "./lbl/index.js"
56
+ },
57
+ "./element": {
58
+ "types": "./element/index.d.ts",
59
+ "import": "./element/index.js"
60
+ },
61
+ "./render": {
62
+ "types": "./render/index.d.ts",
63
+ "import": "./render/index.js"
64
+ },
65
+ "./image": {
66
+ "types": "./image/index.d.ts",
67
+ "import": "./image/index.js"
68
+ },
69
+ "./petrus": {
70
+ "types": "./petrus/index.d.ts",
71
+ "import": "./petrus/index.js"
72
+ },
73
+ "./zz": {
74
+ "types": "./zz/index.d.ts",
75
+ "import": "./zz/index.js"
76
+ },
77
+ "./bluetooth": {
78
+ "types": "./bluetooth/index.d.ts",
79
+ "import": "./bluetooth/index.js"
80
+ },
81
+ "./analyze": {
82
+ "types": "./analyze/index.d.ts",
83
+ "import": "./analyze/index.js"
84
+ },
85
+ "./cfop": {
86
+ "types": "./cfop/index.d.ts",
87
+ "import": "./cfop/index.js"
88
+ },
89
+ "./timeline": {
90
+ "types": "./timeline/index.d.ts",
91
+ "import": "./timeline/index.js"
92
+ },
93
+ "./react": {
94
+ "types": "./react/index.d.ts",
95
+ "import": "./react/index.js"
96
+ },
97
+ "./roux": {
98
+ "types": "./roux/index.d.ts",
99
+ "import": "./roux/index.js"
100
+ },
101
+ "./image/png-node": {
102
+ "types": "./image/png-node.d.ts",
103
+ "import": "./image/png-node.js"
104
+ },
105
+ "./package.json": "./package.json"
106
+ },
107
+ "sideEffects": [
108
+ "./element/index.js",
109
+ "./react/index.js"
110
+ ],
111
+ "dependencies": {
112
+ "aes-js": "^3.1.2",
113
+ "lz-string": "^1.5.0",
114
+ "rxjs": "^7.8.2",
115
+ "@types/web-bluetooth": "^0.0.21"
116
+ },
117
+ "peerDependencies": {
118
+ "three": ">=0.160",
119
+ "react": ">=18",
120
+ "@resvg/resvg-js": ">=2"
121
+ },
122
+ "peerDependenciesMeta": {
123
+ "three": {
124
+ "optional": true
125
+ },
126
+ "react": {
127
+ "optional": true
128
+ },
129
+ "@resvg/resvg-js": {
130
+ "optional": true
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Petrus — 2×2×2 block (canonical: at D-B-L), extended to 2×2×3 along the
3
+ * left-bottom, EO, F2L, last layer. Checks, method and masks; colour neutral.
4
+ */
5
+ import { type Frame, type Mask, type MaskRule, type Method, type State } from "../core/index.js";
6
+ export declare const block222Solved: (s: State) => boolean;
7
+ export declare const block223Solved: (s: State) => boolean;
8
+ export declare const PETRUS: Method;
9
+ export type PetrusMask = "2x2x2" | "2x2x3";
10
+ export declare const PETRUS_MASKS: Record<PetrusMask, MaskRule>;
11
+ export declare const petrusMask: (name: PetrusMask, frame?: Frame) => Mask;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Petrus — 2×2×2 block (canonical: at D-B-L), extended to 2×2×3 along the
3
+ * left-bottom, EO, F2L, last layer. Checks, method and masks; colour neutral.
4
+ */
5
+ import { IDENTITY_FRAME, LAST_LAYER_STAGES, buildMask, checks as C } from "../core/index.js";
6
+ const inBlock222 = (p) => p[0] <= 0 && p[1] <= 0 && p[2] <= 0;
7
+ const inBlock223 = (p) => p[0] <= 0 && p[1] <= 0;
8
+ const BLOCK_222 = C.cubiesWhere((p) => inBlock222(p) && p.some((v) => v !== 0));
9
+ const BLOCK_223 = C.cubiesWhere((p) => inBlock223(p) && p.some((v) => v !== 0));
10
+ export const block222Solved = (s) => C.allSolved(s, BLOCK_222);
11
+ export const block223Solved = (s) => C.allSolved(s, BLOCK_223);
12
+ export const PETRUS = {
13
+ id: "petrus",
14
+ name: "Petrus",
15
+ stages: [
16
+ { id: "2x2x2", label: "2×2×2 block", done: block222Solved },
17
+ { id: "2x2x3", label: "2×2×3 block", done: block223Solved },
18
+ { id: "eo", label: "EO", done: (s) => block223Solved(s) && C.edgesOrientedFB(s) },
19
+ { id: "f2l", label: "F2L", done: C.f2lSolved },
20
+ ...LAST_LAYER_STAGES,
21
+ ],
22
+ };
23
+ export const PETRUS_MASKS = {
24
+ "2x2x2": (f, c) => (c.kind === "center" || inBlock222(c.pos) ? "regular" : "ignored"),
25
+ "2x2x3": (f, c) => (c.kind === "center" || inBlock223(c.pos) ? "regular" : "ignored"),
26
+ };
27
+ export const petrusMask = (name, frame = IDENTITY_FRAME) => buildMask(PETRUS_MASKS[name], frame);
@@ -0,0 +1,176 @@
1
+ /**
2
+ * React bindings — thin wrappers around the cubecore elements plus hooks for
3
+ * a smart cube and the solver worker. No styling opinions: the elements'
4
+ * CSS variables and ::part() work as usual (className / style pass through).
5
+ *
6
+ * <CubePlayer alg="R U R' U'" progress segmentLabels onEnded={…} ref={player} />
7
+ * const cube = useSmartCube(); // cube.connect() / cube.simulate(); cube.state, cube.session
8
+ * <CubePlayer live={cube.session} autoSkin />
9
+ * <CubeScramble scramble={moves} source={cube.session} editable onComplete={start} />
10
+ * <CubeAlgPractice alg="R U R' U R U2 R'" source={cube.session} reveal="done" onComplete={…} />
11
+ *
12
+ * Works with React 18 and 19: attributes are passed as attributes (booleans
13
+ * only when true), objects are set as properties from effects, events are
14
+ * listened to on the element.
15
+ */
16
+ import { type CSSProperties, type ReactNode } from "react";
17
+ import type { Mask, Method, Move, State } from "../core/index.js";
18
+ import type { BldMessages, CubeBld as CubeBldElement, CubeAlg as CubeAlgElement, CubeAlgPractice as CubeAlgPracticeElement, CubePlayer as CubePlayerElement, CubeScramble as CubeScrambleElement, ArrowTarget, LiveSource, MoveSource, PracticeMessages, ScrambleMessages } from "../element/index.js";
19
+ import type { SmartCubeSession } from "../bluetooth/index.js";
20
+ import type { Skin } from "../skin/index.js";
21
+ import type { SolverClient } from "../solve/index.js";
22
+ import type { Recording, Segment } from "../timeline/index.js";
23
+ import "../element/index.js";
24
+ type Handler<T = void> = (detail: T) => void;
25
+ export interface CubePlayerProps {
26
+ alg?: string;
27
+ setup?: string;
28
+ anchor?: "start" | "end";
29
+ tempo?: number;
30
+ skin?: Skin | string;
31
+ theme?: "light" | "dark" | "auto";
32
+ visualization?: "3d" | "net" | "top" | "iso";
33
+ backView?: "none" | "side-by-side" | "top-right";
34
+ controls?: "default" | "none";
35
+ progress?: boolean;
36
+ markers?: boolean;
37
+ segmentLabels?: boolean;
38
+ tooltips?: boolean;
39
+ maxPause?: number;
40
+ recording?: Recording | null;
41
+ method?: Method | null;
42
+ segments?: Segment[] | null;
43
+ mask?: Mask | null;
44
+ formatSegment?: (s: Segment) => string;
45
+ /** Follow a smart cube live (a SmartCubeSession). */
46
+ live?: LiveSource | null;
47
+ /** With `live`: use the cube's suggested skin. */
48
+ autoSkin?: boolean;
49
+ onTimeUpdate?: Handler<{
50
+ time: number;
51
+ duration: number;
52
+ applied: number;
53
+ }>;
54
+ onPlay?: Handler;
55
+ onPause?: Handler;
56
+ onEnded?: Handler;
57
+ onSegmentChange?: Handler<{
58
+ index: number;
59
+ segment: Segment;
60
+ }>;
61
+ onError?: Handler<{
62
+ message: string;
63
+ }>;
64
+ id?: string;
65
+ className?: string;
66
+ style?: CSSProperties;
67
+ /** e.g. your own controls with slot="controls". */
68
+ children?: ReactNode;
69
+ }
70
+ export declare const CubePlayer: import("react").ForwardRefExoticComponent<CubePlayerProps & import("react").RefAttributes<CubePlayerElement | null>>;
71
+ export interface CubeScrambleProps {
72
+ scramble: string | readonly Move[];
73
+ /** Follow a smart cube (a SmartCubeSession). */
74
+ source?: MoveSource | null;
75
+ /** Arrows on a player's 3D cube for the next turn (with `player`). */
76
+ arrows?: boolean;
77
+ /** The <CubePlayer> ref the arrows go to. */
78
+ player?: {
79
+ current: ArrowTarget | null;
80
+ };
81
+ /** "box" (ribbon along the faces, default) or "circle" (an arc). Colours: --cc-arrow, --cc-arrow-undo, --cc-arrow-wrong-way. */
82
+ arrowShape?: "box" | "circle";
83
+ /** Show an input to paste or type your own scramble (fires onChange). */
84
+ editable?: boolean;
85
+ messages?: Partial<ScrambleMessages>;
86
+ onProgress?: Handler<unknown>;
87
+ onComplete?: Handler<unknown>;
88
+ onChange?: Handler<{
89
+ moves: Move[];
90
+ text: string;
91
+ }>;
92
+ id?: string;
93
+ className?: string;
94
+ style?: CSSProperties;
95
+ }
96
+ export declare const CubeScramble: import("react").ForwardRefExoticComponent<CubeScrambleProps & import("react").RefAttributes<CubeScrambleElement | null>>;
97
+ export interface CubeAlgPracticeProps {
98
+ alg: string | readonly Move[];
99
+ /** Follow a smart cube (a SmartCubeSession); tracking starts from its current state. */
100
+ source?: MoveSource | null;
101
+ /** Arrows on a player's 3D cube for the next turn (with `player`). */
102
+ arrows?: boolean;
103
+ /** The <CubePlayer> ref the arrows go to. */
104
+ player?: {
105
+ current: ArrowTarget | null;
106
+ };
107
+ /** "box" (ribbon along the faces, default) or "circle" (an arc). Colours: --cc-arrow, --cc-arrow-undo, --cc-arrow-wrong-way. */
108
+ arrowShape?: "box" | "circle";
109
+ /** "all" every move shown, "done" dots until done (default), "none" dots only. */
110
+ reveal?: "all" | "done" | "none";
111
+ /** Show the next move after a slip (default true). */
112
+ hintOnMistake?: boolean;
113
+ /** "none" hides the built-in Hint / Show / Restart buttons. */
114
+ controls?: "none";
115
+ /** Hide the whole built-in view (build your own from onProgress). */
116
+ headless?: boolean;
117
+ messages?: Partial<PracticeMessages>;
118
+ onProgress?: Handler<unknown>;
119
+ onMistake?: Handler<unknown>;
120
+ onComplete?: Handler<unknown>;
121
+ id?: string;
122
+ className?: string;
123
+ style?: CSSProperties;
124
+ children?: ReactNode;
125
+ }
126
+ export declare const CubeAlgPractice: import("react").ForwardRefExoticComponent<CubeAlgPracticeProps & import("react").RefAttributes<CubeAlgPracticeElement | null>>;
127
+ export interface CubeBldProps {
128
+ /** Follow a smart cube: memo of it as it is now, then its execution letter by letter. */
129
+ source?: MoveSource | null;
130
+ /** "ruwix" (default) or "speffz". */
131
+ scheme?: "ruwix" | "speffz";
132
+ /** How the cube is held, as rotations (e.g. "x2 y'" = yellow top, orange front). */
133
+ rotation?: string;
134
+ /** Letters still to do: "all" shown (default), "done" hidden until done, "none" hidden. */
135
+ reveal?: "all" | "done" | "none";
136
+ headless?: boolean;
137
+ messages?: Partial<BldMessages>;
138
+ onProgress?: Handler<unknown>;
139
+ onLetter?: Handler<unknown>;
140
+ onWrong?: Handler<unknown>;
141
+ onComplete?: Handler<unknown>;
142
+ id?: string;
143
+ className?: string;
144
+ style?: CSSProperties;
145
+ children?: ReactNode;
146
+ }
147
+ export declare const CubeBld: import("react").ForwardRefExoticComponent<CubeBldProps & import("react").RefAttributes<CubeBldElement | null>>;
148
+ export interface CubeAlgProps {
149
+ /** Id of a <cube-player>, or pass `player`. */
150
+ for?: string;
151
+ player?: {
152
+ current: CubePlayerElement | null;
153
+ };
154
+ alg?: string;
155
+ id?: string;
156
+ className?: string;
157
+ style?: CSSProperties;
158
+ }
159
+ export declare const CubeAlg: import("react").ForwardRefExoticComponent<CubeAlgProps & import("react").RefAttributes<CubeAlgElement | null>>;
160
+ export interface SmartCube {
161
+ session: SmartCubeSession | null;
162
+ state: State | null;
163
+ battery: number | null;
164
+ connected: boolean;
165
+ error: string | null;
166
+ /** Ask the browser for a cube (call from a click). */
167
+ connect(): Promise<void>;
168
+ /** A simulated cube (same events) — for development without hardware. */
169
+ simulate(): Promise<void>;
170
+ disconnect(): Promise<void>;
171
+ }
172
+ /** A smart cube as React state: the session, its state after every move, battery, connection. */
173
+ export declare function useSmartCube(): SmartCube;
174
+ /** The solver worker for this component's lifetime (null until created, e.g. during SSR). */
175
+ export declare function useSolverWorker(url?: string | URL): SolverClient | null;
176
+ export {};