├── .gitignore
├── screenshot.jpg
├── LICENSE
├── index.html
├── cubetools.css
├── examples.html
├── README.md
├── cube.css
├── stickers.svg
└── cube.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .vscode/launch.json
3 |
--------------------------------------------------------------------------------
/screenshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Adrianotiger/css-rubiks-cube/master/screenshot.jpg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Luka Popijac
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CSS Rubik's Cube
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
29 |
30 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/cubetools.css:
--------------------------------------------------------------------------------
1 | /* Simple rotation for start animation (1s, 360°)*/
2 | @keyframes rotate {
3 | from { transform: rotateX(-20deg) rotateY(-30deg) rotateZ(0deg); }
4 | to { transform: rotateX(-20deg) rotateY(333deg) rotateZ(0deg); }
5 | }
6 | /* Loop rotation for endless demo*/
7 | @keyframes rotate2 {
8 | from { transform: rotateX(-30deg) rotateY(0deg); }
9 | to { transform: rotateX(330deg) rotateY(1800deg); }
10 | }
11 |
12 | .cube {
13 | animation: rotate 1s linear forwards;
14 | }
15 |
16 | .combinations
17 | {
18 | position: absolute;
19 | top: calc(var(--unit) / 5);
20 | right: calc(var(--unit) / 5);
21 | width: calc(var(--unit) * 10);
22 | height: calc(var(--unit) * 2);
23 | border-style: solid;
24 | border-width: 1px;
25 | border-color: #000;
26 | border-radius: calc(var(--unit) / 2);
27 | font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
28 | font-weight: 900;
29 | text-align: center;
30 | line-height: calc(var(--unit) * 2);
31 | font-size: calc(var(--unit) * 1.8);
32 | }
33 |
34 | .click-button
35 | {
36 | position: absolute;
37 | bottom: calc(var(--unit) * 1);
38 | right: calc(var(--unit) * 1);
39 | min-width: calc(var(--unit) * 10);
40 | padding: calc(var(--unit) * 1) calc(var(--unit) * 2);
41 | height: calc(var(--unit) * 3);
42 | background: linear-gradient(to bottom, #0f8, #8fb, #0b8);
43 | cursor: pointer;
44 | border-radius: calc(var(--unit) / 2);
45 | font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
46 | font-weight: 900;
47 | text-align: center;
48 | line-height: calc(var(--unit) * 3);
49 | font-size: calc(var(--unit) * 2.6);
50 | display: none;
51 | }
52 |
53 | .click-button:hover
54 | {
55 | box-shadow: 0px 0px 10px 2px #ff8;
56 | background: linear-gradient(to bottom, #cc5, #ffe, #bba);
57 | }
58 |
59 | .cubeerror
60 | {
61 | position: fixed;
62 | bottom: 0px;
63 | left: 0px;
64 | width: 100%;
65 | height: calc(var(--unit) * 2);
66 | background: linear-gradient(to bottom, #f88, #fdd, #d44);
67 | border-radius: calc(var(--unit) / 4);
68 | font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
69 | text-align: center;
70 | line-height: calc(var(--unit) * 2);
71 | font-size: calc(var(--unit) * 1.6);
72 | }
--------------------------------------------------------------------------------
/examples.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CSS Rubik's Cube
5 |
6 |
7 |
8 |
9 |
10 |
11 |
29 |
30 |
31 |
32 | Auto moves
33 |
34 |
35 |
36 | Demo
37 |
38 |
39 |
40 | Solve F2L (1)
41 |
42 |
43 |
44 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CSS Rubik's Cube
2 |
3 | This is a 3D Rubik's Cube demo made with CSS3.
4 | It is a fork from the luka's project: https://github.com/lukapopijac/css-rubiks-cube/
5 |
6 | The entire animation is made with CSS3. No math libs, no Javascript libs, just CSS.
7 |
8 | This javascript-class will allow you to insert a cube inside your webpage and animate it with some simple moves (no solve functionality).
9 |
10 | **[View Demo](https://adrianotiger.github.io/css-rubiks-cube/)**
11 |
12 | **[Some examples](https://adrianotiger.github.io/css-rubiks-cube/examples.html)**
13 | 
14 |
15 | ## How use this script
16 | ### Install
17 | Copy `cube.js`, `cube.css`, `cubetools.css` and `stickers.svg` in a folder.
18 | Add a new html file `index.html`
19 |
20 | ### Configure
21 | Write this code inside `index.html`:
22 | ```
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 | ```
38 |
39 | You can create multiple cubes on the same page. See [Some examples](https://adrianotiger.github.io/css-rubiks-cube/examples.html).
40 |
41 | ### API usage
42 | Once you created the cube object, you can remove some stickers, let rotate some faces or let blink some cubies.
43 | #### removeStickers(array stickers)
44 | calling `cube.removeStickers(["ufr-r", "dr-d", "rdf-a", ...])` the stickers on the cubies will disappear.
45 | Parameter: array of stickers.
46 | ufr-r => cube: *u*p/*f*ront/*r*ight - sticker: *r*ight
47 | dr-a => cube: *d*own/*r*ight - sticker: *a*ll
48 |
49 | #### blinkStickers(array stickers, int qty)
50 | Same as `removeStickers`, but instead of remove them, they will blink `qty` times.
51 |
52 | #### setMoves(string moves)
53 | Now the main function: this will move the faces with a sequence of moves.
54 | The string is a standard string used to describe the cube moves.
55 | Example: `F B R L U D F' B' R' F2 B2' D2`
56 | Where: *F* = Front, *B* = Back, *R* = Right, *L* = Left, *D* = Down, *U* = Up
57 | And `'` means anticlockwise and `2` means a double turn (single turn is 90°).
58 |
59 | #### scramble(string moves)
60 | Same as `setMoves` but will be executed 10x faster
61 |
62 | #### setAnimation(object animation)
63 | Play a sequence of commands. `animation` is an array of objects.
64 | Every step is an action, like scramble, set moves or wait for an event.
65 | Example code:
66 | ```
67 | let c = new Cube(div);
68 | const animation =
69 | [
70 | { scramble: "F' U' F U", },
71 | { event: { type: "click", title: "PLAY" } },
72 | { blink: { faces: ["f-f", "frd-f"], qty: 6 } },
73 | { blink: { faces: ["frd-d"], qty: 6} },
74 | { solve: "R U R'" },
75 | { event: { type: "sleep", sleep: 2500 } }
76 | ];
77 | c.setAnimation(animation);
78 | ```
79 | I am still playing with this structure... It may change in future.
80 |
81 |
82 | ## Credits
83 | Original project: https://github.com/lukapopijac/
84 |
85 | ## License
86 | This software is released under the MIT license.
87 |
--------------------------------------------------------------------------------
/cube.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --unit: 10px;
3 | }
4 |
5 | .scene {
6 | margin: 0px auto;
7 | display: block;
8 | width: calc(var(--unit) * 50);
9 | height: calc(var(--unit) * 50);
10 | perspective: calc(var(--unit) * 500);
11 | perspective-origin: calc(var(--unit) * 5) calc(var(--unit) * 5);
12 | overflow: hidden;
13 | transition: opacity 0.5s linear 0.2s;
14 | }
15 |
16 | .cube {
17 | position: relative;
18 | left: 40%;
19 | top: 40%;
20 | transform-style: preserve-3d;
21 | transform-origin: calc(var(--unit) * 5) calc(var(--unit) * 5);
22 | transform: rotateY(-30deg) rotateX(-20deg) rotateZ(9deg);
23 | }
24 |
25 | /*
26 | .cube-layer {
27 | transform-style: preserve-3d;
28 | transform-origin: calc(var(--unit) * 5) calc(var(--unit) * 5);
29 | }
30 |
31 | This rule is pushed from the code
32 | .cube-layer.turn {
33 | transition: transform 1.3s cubic-bezier(0.445, 0.05, 0.55, 0.95);
34 | }
35 | */
36 |
37 | .cubie {
38 | transform-style: preserve-3d;
39 | transform-origin: calc(var(--unit) * 5) calc(var(--unit) * 5);
40 | }
41 |
42 | .cubie-corner-position-0 { transform: translate3d(calc(var(--unit) * 10), calc(var(--unit) * -10), calc(var(--unit) * 10)); }
43 | .cubie-corner-position-1 { transform: translate3d(calc(var(--unit) * -10), calc(var(--unit) * -10), calc(var(--unit) * 10)) rotateY(-90deg); }
44 | .cubie-corner-position-2 { transform: translate3d(calc(var(--unit) * 10), calc(var(--unit) * -10), calc(var(--unit) * -10)) rotateY(90deg); }
45 | .cubie-corner-position-3 { transform: translate3d(calc(var(--unit) * -10), calc(var(--unit) * -10), calc(var(--unit) * -10)) rotateY(180deg); }
46 | .cubie-corner-position-4 { transform: translate3d(calc(var(--unit) * 10), calc(var(--unit) * 10), calc(var(--unit) * 10)) rotateX(180deg) rotateY(90deg); }
47 | .cubie-corner-position-5 { transform: translate3d(calc(var(--unit) * -10), calc(var(--unit) * 10), calc(var(--unit) * 10)) rotateX(180deg) rotateY(180deg); }
48 | .cubie-corner-position-6 { transform: translate3d(calc(var(--unit) * 10), calc(var(--unit) * 10), calc(var(--unit) * -10)) rotateX(180deg); }
49 | .cubie-corner-position-7 { transform: translate3d(calc(var(--unit) * -10), calc(var(--unit) * 10), calc(var(--unit) * -10)) rotateX(180deg) rotateY(-90deg); }
50 |
51 | .cubie-corner-orientation-0 {}
52 | .cubie-corner-orientation-1 { transform: rotateY(-90deg) rotateX(90deg); }
53 | .cubie-corner-orientation-2 { transform: rotateY(90deg) rotateZ(-90deg); }
54 |
55 | .cubie-edge-position-0 { transform: translate3d(0px, calc(var(--unit) * -10), calc(var(--unit) * 10)); }
56 | .cubie-edge-position-1 { transform: translate3d(calc(var(--unit) * -10), calc(var(--unit) * -10), 0px) rotateY(-90deg); }
57 | .cubie-edge-position-2 { transform: translate3d(calc(var(--unit) * 10), calc(var(--unit) * -10), 0px) rotateY(90deg); }
58 | .cubie-edge-position-3 { transform: translate3d(0px, calc(var(--unit) * -10), calc(var(--unit) * -10)) rotateY(180deg); }
59 | .cubie-edge-position-4 { transform: translate3d(0px, calc(var(--unit) * 10), calc(var(--unit) * 10)) rotateX(180deg) rotateY(180deg); }
60 | .cubie-edge-position-5 { transform: translate3d(calc(var(--unit) * -10), calc(var(--unit) * 10), 0px) rotateX(180deg) rotateY(-90deg); }
61 | .cubie-edge-position-6 { transform: translate3d(calc(var(--unit) * 10), calc(var(--unit) * 10), 0px) rotateX(180deg) rotateY(90deg); }
62 | .cubie-edge-position-7 { transform: translate3d(0px, calc(var(--unit) * 10), calc(var(--unit) * -10)) rotateX(180deg); }
63 | .cubie-edge-position-8 { transform: translate3d(calc(var(--unit) * 10), 0px, calc(var(--unit) * 10)) rotateX(-90deg) rotateY(90deg); }
64 | .cubie-edge-position-9 { transform: translate3d(calc(var(--unit) * -10), 0px, calc(var(--unit) * 10)) rotateX(-90deg) rotateY(-90deg); }
65 | .cubie-edge-position-10 { transform: translate3d(calc(var(--unit) * 10), 0px, calc(var(--unit) * -10)) rotateX(90deg) rotateY(90deg); }
66 | .cubie-edge-position-11 { transform: translate3d(calc(var(--unit) * -10), 0px, calc(var(--unit) * -10)) rotateX(90deg) rotateY(-90deg); }
67 |
68 | .cubie-edge-orientation-0 {}
69 | .cubie-edge-orientation-1 { transform: rotateX(-90deg) rotateY(180deg); }
70 |
71 | .cubie-middle-r { transform: translate3d(calc(var(--unit) * 10), 0px, 0px); }
72 | .cubie-middle-l { transform: translate3d(calc(var(--unit) * -10), 0px, 0px); }
73 | .cubie-middle-f { transform: translate3d(0px, 0px, calc(var(--unit) * 10)); }
74 | .cubie-middle-b { transform: translate3d(0px, 0px, calc(var(--unit) * -10)); }
75 | .cubie-middle-u { transform: translate3d(0px, calc(var(--unit) * -10), 0px); }
76 | .cubie-middle-d { transform: translate3d(0px, calc(var(--unit) * 10), 0px); }
77 |
78 | .cubie-face {
79 | position: absolute;
80 | width: calc(var(--unit) * 10);
81 | height: calc(var(--unit) * 10);
82 | background-color: rgba(0,30,60,0.4);
83 | box-shadow: inset 0px 0px calc(var(--unit) * 1.5) 0px #000, 0px 0px calc(var(--unit) * 0.2) 0px #000;
84 | border-radius: calc(var(--unit) * 0.2);
85 | transform-style: preserve-3d;
86 | }
87 | .face-u { transform: rotateX(90deg) translateZ(calc(var(--unit) * 5)); }
88 | .face-f { transform: translateZ(calc(var(--unit) * 5)); }
89 | .face-r { transform: rotateY(90deg) translateZ(calc(var(--unit) * 5)); }
90 | .face-l { transform: rotateY(-90deg) translateZ(calc(var(--unit) * 5)); }
91 | .face-b { transform: rotateY(180deg) translateZ(calc(var(--unit) * 5)); }
92 | .face-d { transform: rotateX(-90deg) translateZ(calc(var(--unit) * 5)); }
93 |
94 | .cubie-face:hover
95 | {
96 | background-color: rgba(255,255,200,0.4);
97 | box-shadow: inset 0px 0px calc(var(--unit) * 2) 0px #880, 0px 0px calc(var(--unit) * 1) 1px #ffa;
98 | }
99 |
100 | .cubie-hidden { /*display:none;*/opacity:0.1; }
101 | .cubie-sticker {
102 | background-image: url(stickers.svg);
103 | background-size: calc(var(--unit) * 70) calc(var(--unit) * 10);
104 | transition: all 0.1s linear;
105 | }
106 | .sticker-u { background-position-x: 0; }
107 | .sticker-f { background-position-x: calc(var(--unit) * -10); }
108 | .sticker-r { background-position-x: calc(var(--unit) * -20); }
109 | .sticker-l { background-position-x: calc(var(--unit) * -30); }
110 | .sticker-b { background-position-x: calc(var(--unit) * -40); }
111 | .sticker-d { background-position-x: calc(var(--unit) * -50); }
112 |
113 | .turn-u1, .turn-d3 { transform: rotateY(-90deg); }
114 | .turn-f1, .turn-b3 { transform: rotateZ(90deg); }
115 | .turn-r1, .turn-l3 { transform: rotateX(90deg); }
116 | .turn-l1, .turn-r3 { transform: rotateX(-90deg); }
117 | .turn-b1, .turn-f3 { transform: rotateZ(-90deg); }
118 | .turn-d1, .turn-u3 { transform: rotateY(90deg); }
119 | .turn-u2 { transform: rotateY(-180deg); }
120 | .turn-f2 { transform: rotateZ(180deg); }
121 | .turn-r2 { transform: rotateX(180deg); }
122 | .turn-l2 { transform: rotateX(-180deg); }
123 | .turn-b2 { transform: rotateZ(-180deg); }
124 | .turn-d2 { transform: rotateY(180deg); }
125 |
126 | .hide-sticker { background-position-x: calc(var(--unit) * -60); }
127 | .blink-sticker { background-blend-mode: darken; }
128 |
--------------------------------------------------------------------------------
/stickers.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
16 |
24 |
29 |
35 |
40 |
45 |
51 |
52 |
60 |
65 |
71 |
76 |
81 |
87 |
88 |
96 |
101 |
107 |
112 |
117 |
123 |
124 |
132 |
137 |
143 |
148 |
153 |
159 |
160 |
168 |
173 |
179 |
184 |
189 |
195 |
196 |
204 |
209 |
215 |
220 |
225 |
231 |
232 |
240 |
245 |
251 |
256 |
261 |
267 |
268 |
276 |
281 |
287 |
292 |
297 |
303 |
304 |
312 |
317 |
323 |
328 |
333 |
339 |
340 |
341 |
360 |
364 |
368 |
372 |
376 |
380 |
384 |
388 |
389 |
--------------------------------------------------------------------------------
/cube.js:
--------------------------------------------------------------------------------
1 | /*
2 | corners _.-'-._ edges _.-'-._
3 | _.-'-._3_.-'-._ _.-'-._ _.-'-._
4 | _.-'-._ _.-'-._ _.-'-._ _.-'-._1_.-'-._3_.-'-._
5 | |-._ _.-'-._U_.-'-._ _.-| |-._ _.-'-._U_.-'-._ _.-|
6 | | 1 |-._ _.-'-._ _.-| 2 | | |-._ _.-'-._ _.-| |
7 | |-._| |-._ _.-| |_.-| |-._| 0 |-._ _.-| 2 |_.-|
8 | | |-._| 0 |_.-| | | 9 |-._| | |_.-| 10|
9 | |-._| F |-._|_.-| R |_.-| |-._| F |-._|_.-| R |_.-|
10 | | 5 |-._| | |_.-| 6 | 5--> | |-._| 8| |_.-| | <--7
11 | '-._| |-._|_.-| |_.-' '-._| 4 |-._|_.-| 6 |_.-'
12 | '-._| | |_.-' '-._| | |_.-'
13 | '-._4_.-' '-._|_.-'
14 |
15 | U F R L B D
16 | up front right left back down
17 | */
18 |
19 | function _CN(e,t,r,n=null){var o=document.createElement(e);if("object"==typeof t)for(var a in t)o.setAttribute(a,t[a]);return Array.isArray(r)&&r.forEach(e=>{o.appendChild("string"==typeof e||"number"==typeof e?document.createTextNode(e):e)}),null!==n&&n.appendChild(o),o}
20 |
21 | const layers = {
22 | u: {corners: [0, 1, 3, 2], edges: [0, 1, 3, 2]},
23 | f: {corners: [1, 0, 4, 5], edges: [0, 8, 4, 9]},
24 | r: {corners: [0, 2, 6, 4], edges: [6, 8, 2, 10]},
25 | l: {corners: [3, 1, 5, 7], edges: [1, 9, 5, 11]},
26 | b: {corners: [2, 3, 7, 6], edges: [3, 11, 7, 10]},
27 | d: {corners: [4, 6, 7, 5], edges: [4, 6, 7, 5]}
28 | };
29 |
30 | let cubeInstance = 0;
31 |
32 | class Cube
33 | {
34 | #_ready;
35 | #_moves;
36 | #_scene;
37 | #_cube;
38 | #_css;
39 | #_scrambleSpeed = 0.15;
40 | #_moveSpeed = 1.3;
41 | #_combiDiv = null;
42 | #_clickDiv = null;
43 | #_animations = null;
44 | #_cssCubeLayer = "";
45 |
46 | constructor(element)
47 | {
48 | this.#_cssCubeLayer = "cube-layer" + cubeInstance++;
49 | this.#_scene = _CN("div", {class:"scene", style:"opacity:0.05;"}, null, element);
50 | this.#_ready = false;
51 | this.#_moves = [];
52 | this.#_cube = _CN("div", {class:"cube"}, null, this.#_scene);
53 | this.#_css = new CSSStyleSheet();
54 | this.#_combiDiv = _CN("div", {class:"combinations"}, ["-"], element);
55 | this.#_clickDiv = _CN("div", {class:"click-button"}, ["PLAY"], element);
56 | document.adoptedStyleSheets.push(this.#_css);
57 |
58 | this.#createCube();
59 | this.#initCube();
60 |
61 | window.addEventListener("load", ()=>{
62 | const r = element.getBoundingClientRect();
63 | element.style.setProperty("--unit", parseInt(Math.min(r.width, r.height) / 50) + "px");
64 |
65 | this.#_scene.style.opacity = 1.0;
66 | });
67 | this.#_clickDiv.addEventListener("click", ()=>{
68 | this.#_clickDiv.style.display = "none";
69 | this.#playAnimation("click");
70 | });
71 | }
72 |
73 | setAnimation(a)
74 | {
75 | this.#_animations = a;
76 | this.#_animations.index = 0;
77 | a.forEach(a2=>{
78 | if(a2.solve)
79 | {
80 | this.#_combiDiv.textContent = a2.solve;
81 | }
82 | });
83 | this.#playAnimation();
84 | }
85 |
86 | scramble(s)
87 | {
88 | this.#setReady(false);
89 | this.setMoves(s);
90 | }
91 |
92 | // Example: "F B R L U D F' B' R' F2 B2' D2"
93 | setMoves(s)
94 | {
95 | if(this.#_moves.length > 0) this.#setReady(false);
96 | this.#_combiDiv.textContent = this.#_ready ? s : "...";
97 | s.split(" ").forEach(v=>{
98 | switch(v.length)
99 | {
100 | case 1: this.#addMove(v.toLowerCase() + "1"); break;
101 | case 2:
102 | if(Number.parseInt(v[1]) > 0)
103 | this.#addMove(v.toLowerCase());
104 | else
105 | this.#addMove(v[0].toLowerCase() + "3");
106 | break;
107 | case 3: // MOVE + "2'"
108 | this.#addMove(v[0].toLowerCase() + "3");
109 | this.#addMove(v[0].toLowerCase() + "3");
110 | break;
111 | default:
112 | console.error("Undefined move: " + v);
113 | }
114 | });
115 | this.#nextMove();
116 | }
117 |
118 | // array of stickers: ["ufr-r", "dr-d", "rdf-a", ...]
119 | // set "XXX-a" for all stickers on this cubie
120 | removeStickers(stickers)
121 | {
122 | stickers.forEach(s=>{
123 | const sc = s.split("-")[0].split('');
124 | const sf = s.split("-")[1];
125 | let c = this.#getCubie(sc);
126 | if(c)
127 | {
128 | const ca = c.querySelectorAll(".cubie-face");
129 | ca.forEach(ca2=>{
130 | if(sf=='a' || ca2.classList.contains("sticker-" + sf))
131 | {
132 | ca2.classList.add('hide-sticker');
133 | }
134 | });
135 | }
136 | });
137 | }
138 |
139 | blinkStickers(stickers, qty=6)
140 | {
141 | stickers.forEach(s=>{
142 | const sc = s.split("-")[0].split('');
143 | const sf = s.split("-")[1];
144 | let c = this.#getCubie(sc);
145 | if(c)
146 | {
147 | const ca = c.querySelectorAll(".cubie-face");
148 | ca.forEach(ca2=>{
149 | if((sf=='a' || ca2.classList.contains("sticker-" + sf)) && !ca2.classList.contains("hide-sticker"))
150 | {
151 | this.#setReady(false);
152 | let b = qty;
153 | let i = setInterval(()=>{
154 | b--;
155 | if((b % 2) == 0)
156 | {
157 | ca2.classList.add('blink-sticker');
158 | }
159 | else
160 | {
161 | ca2.classList.remove('blink-sticker');
162 | if(b < 0)
163 | {
164 | clearInterval(i);
165 | this.#setReady(true);
166 | }
167 | }
168 | }, 200);
169 | }
170 | });
171 | }
172 | });
173 | }
174 |
175 | // array of faces
176 | #getCubie(faces)
177 | {
178 | let ret = null;
179 | const cubies = this.#_cube.querySelectorAll('.cubie');
180 | cubies.forEach(c=>{
181 | if(c.childNodes.length < 6) return; // not a cubie
182 | if(c.querySelectorAll(".cubie-sticker").length != faces.length) return; // not the wanted cobie
183 | var valid = true;
184 | faces.forEach(face=>{
185 | if(!c.querySelector(".sticker-" + face)) valid = false; // has not the requested face
186 | });
187 | if(valid) ret = c;;
188 | });
189 | return ret;
190 | }
191 |
192 | #move(turn)
193 | {
194 | const side = turn[0];
195 | const layer = layers[side];
196 | const m = this.#_cube.querySelector('.cubie-middle-' + side);
197 | const cubies = [m.parentNode];
198 | for(var i=0; i
217 | {
218 | let dl = _CN("div", {class:"cubie cubie-corner-orientation-0"});
219 | var j = 0;
220 | Object.keys(layers).forEach( k => {
221 | _CN("div", {class:"cubie-face face-" + k + (c[j] ? " cubie-sticker sticker-" + c[j] : " cubie-hidden")}, null, dl);
222 | j++;
223 | });
224 | _CN("div", {class:this.#_cssCubeLayer}, [_CN("div", {class:"cubie cubie-corner-position-"+ind}, [dl])], this.#_cube);
225 | ind++;
226 | });
227 | ind = 0;
228 | // create 12 edges
229 | ['uf', 'ul', 'ur', 'ub', 'df', 'dl', 'dr', 'db', 'fr', 'fl', 'br', 'bl'].forEach(e=>
230 | {
231 | let dl = _CN("div", {class:"cubie cubie-edge-orientation-0"});
232 | var j = 0;
233 | Object.keys(layers).forEach( k => {
234 | _CN("div", {class:"cubie-face face-" + k + (e[j] ? " cubie-sticker sticker-" + e[j] : " cubie-hidden")}, null, dl);
235 | j++;
236 | });
237 | _CN("div", {class:this.#_cssCubeLayer}, [_CN("div", {class:"cubie cubie-edge-position-"+ind}, [dl])], this.#_cube);
238 | ind++;
239 | });
240 | // create 6 middles
241 | ['0r', '1l', '2f', '3b', '4u', '5d'].forEach(m=>
242 | {
243 | let dl = _CN("div", {class:"cubie cubie-middle-" + m[1]});
244 | Object.keys(layers).forEach( k => {
245 | _CN("div", {class:"cubie-face face-" + k + (m[1]==k ? " cubie-sticker sticker-" + m[1] : " cubie-hidden")}, null, dl);
246 | });
247 | _CN("div", {class:this.#_cssCubeLayer}, [dl], this.#_cube);
248 | });
249 | }
250 |
251 | #getNewCubeCSS(speed)
252 | {
253 | let css = `.${this.#_cssCubeLayer} { transform-style: preserve-3d; transform-origin: calc(var(--unit) * 5) calc(var(--unit) * 5); } `;
254 | if(speed > 0.2)
255 | css += `.${this.#_cssCubeLayer}.turn {transition: transform ${speed}s cubic-bezier(0.445, 0.05, 0.55, 0.95);}`;
256 | else
257 | css += `.${this.#_cssCubeLayer}.turn {transition: transform ${speed}s linear;}`;
258 | return css;
259 | }
260 |
261 | #initCube()
262 | {
263 | const layerDivs = this.#_cube.querySelectorAll(`.${this.#_cssCubeLayer}`);
264 | for(let i=0; i{this.#updateCubie(layerDivs[i])});
267 | layerDivs[i].addEventListener('transitionend', ()=>{this.#nextMove()});
268 | }
269 |
270 | this.#_css.replace(this.#getNewCubeCSS(this.#_moveSpeed));
271 | }
272 |
273 | #updateCubie(el)
274 | {
275 | var match = el.className.match(/turn\-(..)/);
276 | if(!match) return;
277 | el.classList.remove('turn');
278 | el.classList.remove(match[0]);
279 |
280 | const step = +match[1][1];
281 | const side = match[1][0];
282 | const layer = layers[side];
283 | let div = el.children[0];
284 |
285 | let re = /(cubie-corner-position-)(\d+)/;
286 | if(match = div.className.match(re)) {
287 | const idx = layer.corners.indexOf(+match[2]);
288 | var newVal = layer.corners[(idx + step)&3];
289 | div.className = div.className.replace(re, '$1' + newVal);
290 |
291 | div = div.children[0];
292 | re = /(cubie-corner-orientation-)(\d+)/;
293 | match = div.className.match(re);
294 | newVal = (+match[2] + (side!='u' && side!='d') * (step&1) * (1+(idx&1))) % 3;
295 | div.className = div.className.replace(re, '$1' + newVal);
296 | }
297 |
298 | re = /(cubie-edge-position-)(\d+)/;
299 | if(match = div.className.match(re)) {
300 | const idx = layer.edges.indexOf(+match[2]);
301 | var newVal = layer.edges[(idx + step)&3];
302 | div.className = div.className.replace(re, '$1' + newVal);
303 |
304 | div = div.children[0];
305 | re = /(cubie-edge-orientation-)(\d+)/;
306 | match = div.className.match(re);
307 | newVal = +match[2]^(side=='f' || side=='b')&step;
308 | div.className = div.className.replace(re, '$1' + newVal);
309 | }
310 | }
311 |
312 | #nextMove()
313 | {
314 | // Still turning...
315 | if(this.#_cube.querySelector(`.${this.#_cssCubeLayer}.turn`))
316 | return;
317 |
318 | if(this.#_moves.length > 0)
319 | {
320 | console.log("next move...", this.#_moves);
321 | let nMove = this.#_moves.shift();
322 | setTimeout(()=>{this.#move(nMove);}, 10);
323 | }
324 | else
325 | {
326 | if(!this.#_ready) this.#setReady(true);
327 | }
328 | };
329 |
330 | #setReady(ready)
331 | {
332 | this.#_css.replace(this.#getNewCubeCSS(ready ? this.#_moveSpeed : this.#_scrambleSpeed));
333 | this.#_ready = ready;
334 | }
335 |
336 | #addMove(m)
337 | {
338 | this.#_moves.push(m);
339 | }
340 |
341 | #playAnimation(event)
342 | {
343 | if(!this.#_ready)
344 | {
345 | setTimeout(()=>{this.#playAnimation();}, 200);
346 | return;
347 | }
348 | const a = this.#_animations[this.#_animations.index];
349 | this.#_animations.index = (this.#_animations.index + 1) % this.#_animations.length;
350 | if(a.event)
351 | {
352 | if(a.event.type != event)
353 | {
354 | if(a.event.type == "click")
355 | {
356 | this.#_clickDiv.style.display = "block";
357 | return;
358 | }
359 | else if(a.event.type == "sleep")
360 | {
361 | setTimeout(()=>{this.#playAnimation("sleep");}, a.event.sleep);
362 | return;
363 | }
364 | }
365 | }
366 | else if(a.blink) // blink lamps
367 | {
368 | if(a.blink.faces?.length) this.blinkStickers(a.blink.faces, a.blink.qty);
369 | setTimeout(()=>{
370 | this.#playAnimation();
371 | }, a.blink.qty * 200 + 500);
372 | }
373 | else if(a.scramble || a.solve)
374 | {
375 | if(a.scramble)
376 | this.scramble(a.scramble);
377 | else if(a.solve)
378 | this.setMoves(a.solve);
379 |
380 | setTimeout(()=>{
381 | this.#playAnimation();
382 | }, a.scramble ? this.#_scrambleSpeed * a.scramble.length * 1000 : this.#_moveSpeed * a.solve.length * 1000);
383 | }
384 | else
385 | {
386 | console.error("Invalid animation", a);
387 | this.#showError("Inavlid animation");
388 | }
389 | }
390 |
391 | #showError(error)
392 | {
393 | _CN("div", {class:"cubeerror"}, [error], this.#_scene);
394 | console.error("error");
395 | }
396 |
397 | };
398 |
399 |
--------------------------------------------------------------------------------