├── .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 |
17 | 18 | 19 | 22 | 26 | 27 |
20 | MORE EXAMPLES 21 | 23 | GitHub Project 24 | Adriano Petrucci 25 |
28 |
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 | ![Demo cubes](screenshot.jpg) 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 | --------------------------------------------------------------------------------