diff --git a/.github/ISSUE TEMPLATE/Bug_report_template.md b/.github/ISSUE TEMPLATE/Bug_report_template.md deleted file mode 100644 index b41b495..0000000 --- a/.github/ISSUE TEMPLATE/Bug_report_template.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: πŸͺ² Bug Report -about: Create a bug report to help us resolving the bug -title: 'πŸͺ²[Bug]: ' -labels: 'bug' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE TEMPLATE/Feature_request_template.md b/.github/ISSUE TEMPLATE/Feature_request_template.md deleted file mode 100644 index 16775e1..0000000 --- a/.github/ISSUE TEMPLATE/Feature_request_template.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: πŸ’‘ Feature Request -about: Suggest an interesting feature idea for this project -title: 'πŸ’‘[FEATURE]: ' -labels: 'enhancement' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/ISSUE TEMPLATE/docs.md b/.github/ISSUE TEMPLATE/docs.md deleted file mode 100644 index 7f4160a..0000000 --- a/.github/ISSUE TEMPLATE/docs.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: πŸ“ Documentation -about: Propose changes and improvements to Rubik-Cube Docs. -title: 'πŸ“[Docs]: ' -labels: 'enhancement' -assignees: '' ---- - -**What Docs changes are you proposing?** -Provide a clear description of the changes you're proposing for the documentation. Are you suggesting corrections, clarifications, additions, or updates? - -**Why do the Docs need this improvement? What is the motivation for this change? How will this change benefit the community?** -Explain the motivation behind the proposed changes and how they will benefit the community or users of the documentation. - -**Describe the solution you'd like** -Provide a clear and concise description of the changes you'd like to see implemented in the documentation. - -**Describe alternatives you've considered** -If you've thought about alternative approaches or solutions, briefly describe them here. - -**Additional context** -Add any additional context, examples, or screenshots related to the proposed documentation changes. diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..2bc3c61 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,45 @@ +name: "Bug β€‹πŸž" +description: "Report an issue to help us improve the project." +title: "[BUG] " +labels: ["bug", "goal: fix"] + +body: + - type: textarea + attributes: + label: Description + description: A brief description of the issue or bug you are facing, also include what you tried and what didn't work. + validations: + required: false + - type: textarea + attributes: + label: Screenshots + description: Please add screenshots if applicable + validations: + required: false + - type: textarea + attributes: + label: Any additional information? + description: Any additional information or Is there anything we should know about this bug? + validations: + required: false + - type: dropdown + attributes: + label: What browser are you seeing the problem on? + multiple: true + options: + - Firefox + - Chrome + - Safari + - Microsoft Edge + - type: checkboxes + id: terms_checklist_docs + attributes: + label: "Checklist" + description: "By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CODE_OF_CONDUCT.md)" + options: + - label: "I have checked the existing [issues](https://github.com/Dev-tanay/Rubik-Cube/issues)" + required: true + - label: "I have read the [Contributing Guidelines](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CONTRIBUTING.md)" + required: true + - label: "I am willing to work on this issue (optional)" + required: false diff --git a/.github/ISSUE_TEMPLATE/document.yml b/.github/ISSUE_TEMPLATE/document.yml new file mode 100644 index 0000000..8bb45a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/document.yml @@ -0,0 +1,38 @@ +name: "Documentation πŸ“‹" +description: "Suggestions on how to improve our docs" +title: "[DOCS] " +labels: ["documenation", "goal: enhancement"] + +body: + - type: textarea + id: docs_description + attributes: + label: "Issue Description" + description: "Please provide a brief summary of the documentation issue you would like to address." + validations: + required: true + + - type: textarea + id: screenshots_examples_docs + attributes: + label: "Screenshots or Examples (if applicable)" + description: "Include relevant screenshots or examples to help illustrate the problem." + + - type: textarea + id: proposed_solution_docs + attributes: + label: "Proposed Solution (optional)" + description: "If you have a proposed solution for the documentation issue, please provide it here." + - type: checkboxes + id: terms_checklist_docs + attributes: + label: "Checklist" + description: "By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CODE_OF_CONDUCT.md)" + options: + - label: "I have checked the existing [issues](https://github.com/Dev-tanay/Rubik-Cube/issues)" + required: true + - label: "I have read the [Contributing Guidelines](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CONTRIBUTING.md)" + required: true + - label: "I am willing to work on this issue (optional)" + required: false + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..89eef57 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +name: Feature Request πŸ’‘ +description: Have any new idea or new feature for RubikCube? Do suggest! +title: "[Feature] " +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: Description + description: A clear and concise description of the feature you'd like to see implemented. + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: Please add screenshots if applicable + validations: + required: false + - type: textarea + id: solution + attributes: + label: "Proposed Solution (optional)" + description: "If you have a proposed solution for the documentation issue, please provide it here." + - type: checkboxes + id: terms_checklist_docs + attributes: + label: "Checklist" + description: "By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CODE_OF_CONDUCT.md)" + options: + - label: "I have checked the existing [issues](https://github.com/Dev-tanay/Rubik-Cube/issues)" + required: true + - label: "I have read the [Contributing Guidelines](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CONTRIBUTING.md)" + required: true + - label: "I am willing to work on this issue (optional)" + required: false + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/other.yml b/.github/ISSUE_TEMPLATE/other.yml new file mode 100644 index 0000000..ca6ff5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other.yml @@ -0,0 +1,25 @@ +name: Other +description: Use this for any other question or issue. Please do not create blank issues. +title: "[OTHER]" + +body: + - type: textarea + id: issuedescription + attributes: + label: What would you like to share or ask? + description: Provide a clear and concise explanation of your issue. + validations: + required: true + - type: checkboxes + id: terms_checklist_docs + attributes: + label: "Checklist" + description: "By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CODE_OF_CONDUCT.md)" + options: + - label: "I have checked the existing [issues](https://github.com/Dev-tanay/Rubik-Cube/issues)" + required: true + - label: "I have read the [Contributing Guidelines](https://github.com/Dev-tanay/Rubik-Cube/blob/main/CONTRIBUTING.md)" + required: true + - label: "I am willing to work on this issue (optional)" + required: false + \ No newline at end of file diff --git a/.github/Pull-request_template.yml b/.github/pull_request_template.yml similarity index 100% rename from .github/Pull-request_template.yml rename to .github/pull_request_template.yml diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6b665aa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} diff --git a/AboutUs/About.css b/AboutUs/About.css new file mode 100644 index 0000000..709fb18 --- /dev/null +++ b/AboutUs/About.css @@ -0,0 +1,327 @@ + +::-webkit-scrollbar-track +{ + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); + background-color: #F5F5F5; +} + +::-webkit-scrollbar +{ + width: 10px; + background-color: #F5F5F5; +} + +::-webkit-scrollbar-thumb +{ + background-color:#FF9900; + background-image: -webkit-linear-gradient(45deg, + rgba(255, 255, 255, .2) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, .2) 50%, + rgba(255, 255, 255, .2) 75%, + transparent 75%, + transparent) +} + + + +html { + -webkit-tap-highlight-color: transparent; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + overflow: hidden; + height: 100%; +} + +body { + margin: 0; + padding: 0; + height: 100%; + font-family: "BungeeFont"; + font-weight: normal; + font-style: normal; + background-color: #fff; + cursor: default; + overflow: scroll; + overflow-x: hidden; +} + +.header { + padding: 10px; +} + +.content { + text-align: center; + padding: 20px; +} + +@font-face { + font-family: "BungeeFont"; + font-weight: normal; + font-style: normal; + src: url("data:font/truetype;charset=utf-8;base64,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") + format("woff2"), + url("data:font/truetype;charset=utf-8;base64,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") + format("woff"); +} +*, +*:before, +*:after { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + box-sizing: border-box; + cursor: inherit; + margin: 0; + padding: 0; + outline: none; + font-size: inherit; + font-family: inherit; + font-weight: inherit; + font-style: inherit; + text-transform: uppercase; +} +*:focus { + outline: none; +} + +/*banner*/ +.banner { + width: 80%; + height: 200px; + margin: 0 auto 20px; /* Center the banner and add margin-bottom */ + background-image: url("./AboutUs_images/AboutUsBanner.png"); /* Path to your banner image */ + background-size: cover; + background-position: center; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.text--title { + margin-top: 20px; + font-size: 2em; + color: #333; +} + +.text { + margin: 0 auto; + max-width: 800px; + text-align: left; +} + +.about-text--content p, +.about-text--content h1, +.about-text--content h2, +.about-text--content ul { + margin-bottom: 20px; + line-height: 1.6; +} + +.subheading { + font-size: 1.5em; + color: #444; +} + +.subtopic { + margin-left: 20px; +} + +.formula h3 { + margin-top: 10px; + font-size: 1.2em; + color: #555; +} + +.formula p { + font-family: monospace; + color: #333; +} + +.footer { + text-align: center; +} + +.text--footer { + margin: 0; + font-size: 0.9em; + color: #666; +} + +#contributors { + list-style-type: none; + padding: 0; + display: flex; + flex-wrap: wrap; +} +.contributor-item { + margin-right: 10px; + margin-bottom: 10px; + transition: transform 0.3s ease; +} +.contributor-item:hover { + transform: translateY(-5px); +} +.contributor-item img { + width: 50px; + height: 50px; + border-radius: 50%; + cursor: pointer; + transition: transform 0.3s ease; +} +.contributor-item img:hover { + transform: scale(1.1); + + /*For Btn */ +} +.btn { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent; + border-radius: 0; + border-width: 0; + position: absolute; + pointer-events: none; + font-size: 2em; /* Increased font size */ + color: rgba(0, 0, 0, 0.25); + opacity: 0; +} + +.btn:hover { + color: rgb(245, 245, 245); + transition: color 400ms ease-out; + background-color: #541a8be7; +} + +.btn:after { + cursor: pointer; + position: absolute; + content: ""; + width: 3.5em; + height: 3.5em; + left: 50%; + top: 50%; + margin-left: 1.75em; + margin-top: 1.75em; + border-radius: 100%; +} + +.btn--bg { + top: 1.2em; + left: 1.2em; + opacity: 1; + pointer-events: auto; + height: 60px; + width: 60px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; + padding-left: 11px; + padding-top: 9px; +} + +.btn--gh { + top: 1.2em; + right: 1.2em; + opacity: 1; + pointer-events: auto; + height: 60px; + width: 60px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; + padding-left: 10px; + padding-top: 9px; +} + +@media screen and (max-width: 768px) { + .banner { + width: 100%; + } + + .text--title { + font-size: 1.5em; + } + + .text { + padding: 0 10px; + } + + .btn { + font-size: 1.5em; /* Adjust font size */ + } + + .btn:after { + width: 3em; + height: 3em; + margin-left: -1.5em; + margin-top: -1.5em; + } + + .btn--bg { + position: fixed; + top: 0.5em; + left: 1em; + } + + .btn--gh { + position: fixed; + top: 0.5em; + right: 1em; + } +} + +.icons { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + gap: 25px; +} + +.icons .icon i { + margin-top: 15px; + font-size: 30px; + transition: all 0.9s ease; +} + +.icons .icon:hover i { + transform: scale(1.2); +} + +.icons .icon:hover .fab.fa-facebook { + color: #3b5998; +} + +.icons .icon:hover .fab.fa-twitter { + color: #090e11; +} + +.icons .icon:hover .fab.fa-instagram { + background: radial-gradient( + circle at 30% 107%, + #fdf497 0%, + #fdf497 5%, + #fd5949 45%, + #d6249f 60%, + #285aeb 90% + ); + background-clip: text; + border-radius: 20%; + transform: scale(1.5); + color: transparent; +} + +.icons .icon:hover .fab.fa-youtube { + color: #c31a1e; +} +.icons .icon:hover .fab.fa-github { + color: #333; +} +.icons .icon:hover .fab.fa-linkedin { + color: #0077b5; +} diff --git a/AboutUs/About.html b/AboutUs/About.html new file mode 100644 index 0000000..6b6d01b --- /dev/null +++ b/AboutUs/About.html @@ -0,0 +1,131 @@ + + + + + + About The Game - Rubik Cube + + + + + + +
+ + +
+ +
+ + About The Game + +
+ +
+
+
+

Welcome to our website dedicated to providing you with the best Rubik's Cube solving experience! Our team is passionate about puzzles and games, and we strive to create tools and resources that enhance your enjoyment and skill development.

+

Whether you're a beginner or an experienced solver, we hope you find our website helpful and enjoyable. Feel free to explore the various features and settings of our Rubik's Cube solver and let us know if you have any feedback or suggestions!

+

Our project is open source, and we are proud to collaborate with a diverse group of contributors. Here is the list of our amazing contributors who have made this project possible:

+

Contributors

+
    + +
+ +

History

+

The Rubik's Cube, invented by ErnΕ‘ Rubik in 1974, is a 3D combination puzzle that has fascinated millions of people worldwide. Originally called the "Magic Cube," it gained immense popularity after being licensed to the Ideal Toy Corporation in 1980 and rebranded as the "Rubik's Cube."

+ +

Basic Concepts

+

A standard Rubik's Cube consists of six faces, each covered by nine stickers of one of six solid colors: white, red, blue, orange, green, and yellow. The goal is to solve the cube by rotating the faces until each face is a solid color.

+ +

Solving Methods

+

There are several methods for solving Rubik's Cube, including:

+
    +
  • Fridrich Method (CFOP)
  • +
  • BEGINNER Method
  • +
  • Roux Method
  • +
  • ZB Method
  • +
+ +

Advanced Techniques

+

Advanced cubers often utilize techniques such as:

+
    +
  • OLL (Orientation of Last Layer)
  • +
  • PLL (Permutation of Last Layer)
  • +
  • F2L (First Two Layers)
  • +
  • X-Cross
  • +
  • OLLCP (OLL plus Corner Permutation)
  • +
+
+ +

Formulas and Algorithms

+
+
+

The Cross

+

Formula: F R U R' U' F'

+
+
+

First Layer Corners

+

Formula: R U R' U R U2 R'

+
+
+

Second Layer Edges (F2L)

+

Formula: R U R' U' R U' R'

+
+
+

Orientation of Last Layer (OLL)

+

Formula: F R U R' U' F'

+
+
+

Permutation of Last Layer (PLL)

+

Formula: R U2 R' U' R U R' U' R U' R'

+
+
+
+ + + +
+ +
+ + + + + + \ No newline at end of file diff --git a/AboutUs/About.js b/AboutUs/About.js new file mode 100644 index 0000000..9d556e0 --- /dev/null +++ b/AboutUs/About.js @@ -0,0 +1,22 @@ +async function fetchContributors() { + const repoOwner = 'Dev-tanay'; + const repoName = 'Rubik-Cube'; + const response = await fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/contributors`); + const contributors = await response.json(); + + const contributorsList = document.getElementById('contributors'); + contributorsList.innerHTML = ''; + + contributors.forEach(contributor => { + const listItem = document.createElement('li'); + listItem.classList.add('contributor-item'); + const profilePic = document.createElement('img'); + profilePic.src = contributor.avatar_url; + profilePic.alt = contributor.login; + profilePic.onclick = () => window.open(contributor.html_url, '_blank'); + listItem.appendChild(profilePic); + contributorsList.appendChild(listItem); + }); + } + + fetchContributors(); \ No newline at end of file diff --git a/AboutUs/AboutUs_images/AboutUsBanner.png b/AboutUs/AboutUs_images/AboutUsBanner.png new file mode 100644 index 0000000..30f325b Binary files /dev/null and b/AboutUs/AboutUs_images/AboutUsBanner.png differ diff --git a/Music/8 Bit Space Groove! Chill Pretty Chiptune Game Music by HeatleyBros.mp3 b/Music/8 Bit Space Groove! Chill Pretty Chiptune Game Music by HeatleyBros.mp3 new file mode 100644 index 0000000..47865c7 Binary files /dev/null and b/Music/8 Bit Space Groove! Chill Pretty Chiptune Game Music by HeatleyBros.mp3 differ diff --git a/README.md b/README.md index cfa3af9..550645d 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,21 @@

This can be done in two ways:

  1. Twisting the segments
  2. -

    This can be done by dragging the cursor over the segment you want to rotate.

    +

    This can be done by dragging the cursor over the segment you want to rotate or letters(r l u b d f) for the move(Hold Shift for prime/anticlockwise moves) +

  3. Changing the view of the cube
  4. -

    This can be done by dragging the cursor in an arrow around the cube.

    +

    This can be done by dragging the cursor in an arrow or arrowkeys (up,down,right,left) around the cube.

    +
  5. Using keyboard shortcuts
  6. +

    You can also use keyboard shortcuts to rotate the cube:

    +
      +
    • Press w or Arrow Up to rotate the cube upwards.
    • +
    • Press s or Arrow Down to rotate the cube downwards.
    • +
    • Press a or Arrow Left to rotate the cube to the left.
    • +
    • Press d or Arrow Right to rotate the cube to the right.
    • +
diff --git a/changeview.png b/changeview.png new file mode 100644 index 0000000..f37e611 Binary files /dev/null and b/changeview.png differ diff --git a/check-progress.html b/check-progress.html new file mode 100644 index 0000000..46362ef --- /dev/null +++ b/check-progress.html @@ -0,0 +1,78 @@ + + + + + Check Progress + + + + +
+ About Us + +
+ +
+ +

Your Rubik's Cube Progress

+

+
+
+

Total Solves

+

0

+
+
+

Best Time

+

NA

+
+
+

Average Time

+

NA

+
+
+

Types of Cubes Solved

+

3x3

+
+
+

Latest Solves

+
    + +
+
+ +

Your Rubik's Cube Progress

+
+
+
+
Total Solves: --
+
Best Time: --
+
Average Time: --
+
+ Types of Cubes Solved: 3x3, 4x4, 5x5 +
+
Latest Solves:
+
    + +
+
+
+ + + + + diff --git a/edit-profile.html b/edit-profile.html new file mode 100644 index 0000000..aa00e52 --- /dev/null +++ b/edit-profile.html @@ -0,0 +1,50 @@ + + + + + Profile Dropdown and Edit Profile + + + + + +
+ About Us + +
+ + +
+

Edit Profile

+

You are playing as a guest now, add details to identify you.

+
+ + + + + + + + + + + + + +
+
+ + + + diff --git a/images/profilepic.jpg b/images/profilepic.jpg new file mode 100644 index 0000000..1d6602b Binary files /dev/null and b/images/profilepic.jpg differ diff --git a/index.html b/index.html index 9ed694f..88087d0 100644 --- a/index.html +++ b/index.html @@ -8,12 +8,36 @@ + + + + + + + +
+ + About Us + +
+ + + +
+
+
@@ -30,6 +54,7 @@
+ + +
+

Let's @@ -54,6 +82,19 @@

Double tap to start

+ +
+

+ Let's + GO +

+
+ Click here to start +
+ +
+

TIME START NOW

+ 0:00
@@ -63,6 +104,7 @@

Best Time!

+
@@ -123,11 +165,234 @@

+ + + + +
+ +
+ + + + + +
+ +
+ + + +
+ +
+
+ Cube:3x3x3 +
+
+ Total solves:- +
+
+ Best time:- +
+
+ Worst time:- +
+
+ Average of 5:- +
+
+ Average of 12:- +
+
+ Average of 25:- +
+
+ +
+ + +
+
+ +
+ + + + + + + + + + + + +
+ +
+ +
+ +
+

+ Let's + Solve! +

+
+ Double tap to start +
+
+ 0:00 +
+
+ Complete! +
+
+ + Best Time! +
+
+ +
+ + + + + +
+ +
+ + + +
+ +
+
+ Cube:3x3x3 +
+
+ Total solves:- +
+
+ Best time:- +
+
+ Worst time:- +
+
+ Average of 5:- +
+
+ Average of 12:- +
+
+ Average of 25:- +
+
+ +
+ + + + + + + +
+ + +
+ + + + + + + + + +
+ + +
+

1. Twisting the segments

+
+ This can be done by dragging the cursor over the segment you want to rotate. +
+
+ +

2. Changing the view of the cube

+
+ This can be done by dragging the cursor in an arrow around the cube. +
+
+ + + + +
+
+ + + - \ No newline at end of file + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..da8fa65 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Rubik-Cube", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/profile.css b/profile.css new file mode 100644 index 0000000..0a17ceb --- /dev/null +++ b/profile.css @@ -0,0 +1,181 @@ +/* Your existing CSS styles */ +@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap"); + +body { + font-family: "Space Grotesk", sans-serif; + background-image: url("https://png.pngtree.com/thumb_back/fh260/background/20210728/pngtree-purple-rubiks-cube-holographic-floating-geometric-abstract-background-image_752595.jpg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + margin: 0; + padding: 0; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + color: #333; + z-index: 10; + background-color: rgba(194, 190, 190, 0.4); + width: 100%; + height: 60px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + position: fixed; + top: 0; + left: 0; +} + +/* Button style for About Us link */ +.header a { + display: inline-block; + padding: 10px 20px; + background: linear-gradient(rgb(231, 231, 246), rgb(180, 180, 249)); + color: rgb(7, 6, 6); + text-decoration: none; + border: 2px solid #000000; + border-radius: 10px; + font-weight: bold; + transition: background-color 0.3s, transform 0.3s, box-shadow 0.3s; + box-shadow: 3px 3px 0px 0px #b3aaf7; +} + +.header a:hover { + background-color: #2b74bc; + transform: translateY(-2px); + box-shadow: 5px 5px 0px 0px #090915; +} + +.header a:active { + transform: translateY(1px); + box-shadow: 2px 2px 0px 0px #e99f4c; +} + +.profile-menu { + position: relative; + display: inline-block; + z-index: 11; +} + +.profile-btn { + background-color: transparent; + border: none; + color: rgb(10, 10, 10); + padding: 10px; + border-radius: 50%; + cursor: pointer; + outline: none; + width: 4rem; + height: 4rem; + margin-right: 80px; +} + +.profile-pic { + width: 100%; + height: 100%; + border-radius: 50%; +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background-color: transparent; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 12; + border-radius: 5px; + margin-right: 30px; +} + +.dropdown-content a { + color: black; + padding: 10px 30px; + text-decoration: none; + display: block; +} + +.dropdown-content a:hover { + + background-color: #2f302d; + + background-color: #f1f1f1; + +} + +.profile-menu:hover .dropdown-content { + display: block; +} + +/* Styles for the edit profile form */ + +#edit-profile-form { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + opacity: 0.6; + background-image: url("https://t4.ftcdn.net/jpg/03/30/27/41/360_F_330274196_C6mjMdPaTggBr4Jg45faWAwViexh7DoA.jpg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + border-radius: 5px; + max-width: 400px; + margin-top: 180px; /* Adjust margin-top to create space below the header */ + border: 2px solid #0a0b0b; + border-radius: 20px; + box-shadow: 10px 8px 0px 2px #0a0808; + backdrop-filter: blur(10px); +} + +#edit-profile-form label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: #000000; +} + +#edit-profile-form input[type="text"], +#edit-profile-form input[type="file"] { + width: 85%; + padding: 8px; + background: #ebedf0; + margin-bottom: 10px; + border: 1.5px solid #000000; + transform: translateY(4px); + box-shadow: 1px 2px 0px 0px #080604; + color: #000000; + border-radius: 3px; +} + +#edit-profile-form button { + + padding: 10px 20px; + background-color: #007bff; + color: rgb(75, 231, 8); + border: none; + border-radius: 3px; + cursor: pointer; + + padding: 10px 20px; + background-color: #2d8ef6; + border: 1.5px solid #000000; + color: rgb(13, 13, 13); + border-radius: 10px; + cursor: pointer; + margin-top: 15px; + font-weight: 800; + box-shadow: 3px 3px 0px 0px #0f0b07; +} + +#edit-profile-form button:hover { + background-color: rgba(4, 4, 4, 0.8); + color: white; + border: 1.5px solid #000000; + box-shadow: 6px 6px 0px 0px #0f0b05; + +} diff --git a/profile.js b/profile.js new file mode 100644 index 0000000..d22e56e --- /dev/null +++ b/profile.js @@ -0,0 +1,220 @@ +// Function to handle user sign-in +function handleCredentialResponse(response) { + const data = jwt_decode(response.credential); + const profileContainer = document.querySelector(".profile-container"); + const profileName = document.querySelector(".profile-name"); + const profileImage = document.querySelector(".profile-container img"); + + profileName.textContent = data.name; + profileImage.src = data.picture; + + profileContainer.style.opacity = 1; + profileContainer.style.pointerEvents = "auto"; + } + + // Function to handle user sign-out + function signOut() { + const profileContainer = document.querySelector(".profile-container"); + + profileContainer.style.opacity = 0; + profileContainer.style.pointerEvents = "none"; + } + + // Add sign-out button event listener + document.querySelector(".sign-out-btn").addEventListener("click", signOut); + + function handleCredentialResponse(response) { + const responsePayload = parseJwt(response.credential); + document.getElementById("user-name").innerText = `Hello, ${responsePayload.name}`; + document.getElementById("user-image").src = responsePayload.picture; + document.getElementById("user-image").style.display = "block"; + document.getElementById("sign-out").style.display = "block"; + + // Save user profile in local storage + localStorage.setItem("userProfile", JSON.stringify(responsePayload)); + + // Load user progress + loadUserProgress(); + } + + function parseJwt(token) { + const base64Url = token.split(".")[1]; + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + const jsonPayload = decodeURIComponent( + atob(base64) + .split("") + .map(function (c) { + return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join("") + ); + + return JSON.parse(jsonPayload); + } + + document.getElementById("sign-out").addEventListener("click", function () { + document.getElementById("user-name").innerText = ""; + document.getElementById("user-image").style.display = "none"; + document.getElementById("sign-out").style.display = "none"; + localStorage.removeItem("userProfile"); + localStorage.removeItem("userProgress"); + location.reload(); + }); + + function loadUserProfile() { + const userProfile = JSON.parse(localStorage.getItem("userProfile")); + if (userProfile) { + document.getElementById("user-name").innerText = `Hello, ${userProfile.name}`; + document.getElementById("user-image").src = userProfile.picture; + document.getElementById("user-image").style.display = "block"; + document.getElementById("sign-out").style.display = "block"; + + // Load additional profile info + document.getElementById("user-email").innerText = `Email: ${userProfile.email}`; + document.getElementById("user-additional-info").innerText = `Info: ${userProfile.additionalInfo}`; + + loadUserProgress(); + } + } + + function saveUserProgress(stats) { + localStorage.setItem("userProgress", JSON.stringify(stats)); + } + + function loadUserProgress() { + const userProgress = JSON.parse(localStorage.getItem("userProgress")); + if (userProgress) { + document.querySelector('.stats[name="cube-size"] b').innerText = userProgress.cubeSize; + document.querySelector('.stats[name="total-solves"] b').innerText = userProgress.totalSolves; + document.querySelector('.stats[name="best-time"] b').innerText = userProgress.bestTime; + document.querySelector('.stats[name="worst-time"] b').innerText = userProgress.worstTime; + document.querySelector('.stats[name="average-5"] b').innerText = userProgress.average5; + document.querySelector('.stats[name="average-12"] b').innerText = userProgress.average12; + document.querySelector('.stats[name="average-25"] b').innerText = userProgress.average25; + } + } + + // Call loadUserProfile on page load + window.onload = function () { + loadUserProfile(); + }; + + function changeProfilePic() { + // Implement functionality to change profile picture + alert("Change Profile Picture"); + } + + function editName() { + document.getElementById("profile-name").style.display = "none"; + document.getElementById("name-input").style.display = "inline"; + document.getElementById("save-name-btn").style.display = "inline"; + document.getElementById("name-input").value = document.getElementById("profile-name").textContent; + } + + function saveName() { + const newName = document.getElementById("name-input").value; + document.getElementById("profile-name").textContent = newName; + document.getElementById("profile-name").style.display = "inline"; + document.getElementById("name-input").style.display = "none"; + document.getElementById("save-name-btn").style.display = "none"; + + // Optionally, save the new name to local storage or server + } + + document.getElementById("sign-out-btn").addEventListener("click", function () { + // Sign out logic here + console.log("User signed out"); + // Hide profile elements + document.getElementById("profile-container").style.opacity = 0; + document.getElementById("profile-container").style.pointerEvents = "none"; + // Clear user data from localStorage or state + }); + + function toggleProfileOptions() { + const options = document.getElementById("profile-options"); + options.classList.toggle("hidden"); // Toggle visibility + } + + function editProfileName() { + const newName = prompt("Enter new name:"); + if (newName) { + document.getElementById("user-name").textContent = newName; + // Update this in your local storage or server as needed + } + } + + function viewProgress() { + alert("Viewing progress..."); // Replace with actual progress viewing logic + } + + // Update profile info + function updateProfile(event) { + event.preventDefault(); + + const name = document.getElementById("name").value.trim(); + const email = document.getElementById("email").value.trim(); + const additionalInfo = document.getElementById("additional-info").value.trim(); + const profilePic = document.getElementById("profile-pic").files[0]; + + if (name === "" || email === "" || additionalInfo === "") { + Swal.fire({ + icon: "error", + title: "Oops...", + text: "Please complete all fields before submitting.", + customClass: { + popup: "swal-popup", + title: "swal-title", + content: "swal-content", + confirmButton: "swal-confirm-button", + }, + }); + return; + } + + if (!profilePic) { + Swal.fire({ + icon: "error", + title: "Oops...", + text: "Please select a profile picture before submitting.", + customClass: { + popup: "swal-popup", + title: "swal-title", + content: "swal-content", + confirmButton: "swal-confirm-button", + }, + }); + return; + } + + const reader = new FileReader(); + reader.onload = function (e) { + const userProfile = { + name: name, + email: email, + additionalInfo: additionalInfo, + picture: e.target.result, + }; + localStorage.setItem("userProfile", JSON.stringify(userProfile)); + + Swal.fire({ + icon: "success", + title: "Success!", + text: "Your profile has been updated.", + customClass: { + popup: "swal-popup", + title: "swal-title", + content: "swal-content", + confirmButton: "swal-confirm-button", + }, + }).then((result) => { + if (result.isConfirmed || result.isDismissed) { + document.getElementById("edit-profile").reset(); + document.getElementById("main-profile-pic").src = userProfile.picture; + } + }); + }; + reader.readAsDataURL(profilePic); + } + + document.getElementById("edit-profile").addEventListener("submit", updateProfile); + diff --git a/progress.css b/progress.css new file mode 100644 index 0000000..8ad21fb --- /dev/null +++ b/progress.css @@ -0,0 +1,257 @@ +/* Import Google Fonts */ +@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap"); + +/* Body styling */ +body { + + font-family: 'Arial', sans-serif; + /* Use a clean, modern font */ + font-size: 30px; + /* Base font size */ + color: #f7f4f4; + /* Dark gray text for readability */ + background-color: #f4f4f4; + background-image:url('https://i2.pickpik.com/photos/502/642/517/rubik-s-cube-game-cube-strategy-13845d21bb0c925716567a8a3ee7a0fd.jpg'); + background-size: cover; + background-repeat: no-repeat; + background-position: center center; + /* Light gray background */ + padding: 20px; + line-height: 1.6; +} + + +/* Header styles */ + + + font-family: "Space Grotesk", sans-serif; + background-image: url("https://png.pngtree.com/thumb_back/fh260/background/20210728/pngtree-purple-rubiks-cube-holographic-floating-geometric-abstract-background-image_752595.jpg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + margin: 0; + padding: 0; + height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +/* Header styling */ + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + color: #333; + z-index: 10; + background-color: rgba(194, 190, 190, 0.4); + width: 100%; + height: 60px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4); + position: fixed; + top: 0; + left: 0; +} + +/* Button style for About Us link */ +.header a { + display: inline-block; + padding: 10px 20px; + background: linear-gradient(rgb(231, 231, 246), rgb(180, 180, 249)); + color: rgb(7, 6, 6); + text-decoration: none; + border: 2px solid #000000; + border-radius: 10px; + font-weight: bold; + transition: background-color 0.3s, transform 0.3s, box-shadow 0.3s; + box-shadow: 3px 3px 0px 0px #b3aaf7; +} + +.header a:hover { + background-color: #2b74bc; + transform: translateY(-2px); + box-shadow: 5px 5px 0px 0px #090915; +} + +.header a:active { + transform: translateY(1px); + box-shadow: 2px 2px 0px 0px #e99f4c; +} + +/* Profile menu styling */ +.profile-menu { + position: relative; + display: inline-block; + z-index: 11; +} + +.profile-btn { + background-color: transparent; + border: none; + color: rgb(10, 10, 10); + padding: 10px; + border-radius: 50%; + cursor: pointer; + outline: none; + width: 4rem; + height: 4rem; + margin-right: 80px; +} + +.profile-pic { + width: 100%; + height: 100%; + border-radius: 50%; +} + +.dropdown-content { + display: none; + position: absolute; + right: 0; + background-color: transparent; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 12; + border-radius: 5px; + margin-right: 30px; +} + +.dropdown-content a { + color: black; + padding: 10px 30px; + text-decoration: none; + display: block; +} + +.dropdown-content a:hover { + background-color: #f1f1f1; +} + +.profile-menu:hover .dropdown-content { + display: block; +} + + + +/* Progress container styles */ + + + +.progress-container { + + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-top: 20px; + max-width: 900px; /* Set a maximum width */ + height: 500px; + display: flex; + flex-direction: column; + justify-content: center; + padding: 20px; + +} + +.progress-stats { + display: grid; + grid-template-columns: repeat(3, 2fr); + gap: 20px; + margin: 0; + +} + +.progress-box { + background-color: #f0e5e5; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin:0; + +} + +.progress-box h3 { + font-size: 20px; + margin-bottom: 8px; + text-align: center; + color: #333; +} + +.progress-box p { + font-size: 16px; + color: #666; + text-align: center; +} + +ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +/* Additional styles for the layout of the two boxes below */ +.progress-stats .bottom-box { + grid-column: span 2; /* Span across two columns */ +} + + +#no-progress{ + color: red; + +/* Progress container styling */ +.progress-container { + opacity: 0.6; + background-image: url("https://t4.ftcdn.net/jpg/03/30/27/41/360_F_330274196_C6mjMdPaTggBr4Jg45faWAwViexh7DoA.jpg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + margin-top: 150px; + border-radius: 10px; + padding: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.7); + width: 50%; + max-width: 800px; + text-align: center; +} + +.progress-container:hover { + opacity: 0.9; + transform: translateY(-10px); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.9); +} + +.progress-container h1 { + font-size: 2em; + margin-bottom: 20px; + color: #0b0a0a; +} + +.progress-stats { + font-size: 1.2em; + color: #080606; +} + +#recent-solves li { + background: white; + margin-top: 5px; + padding: 10px; + border-radius: 4px; + +.progress-stats div { + margin-bottom: 10px; + +} + +.progress-stats ul { + list-style-type: none; + padding: 0; +} + +.progress-stats ul li { + background: rgba(231, 231, 246, 0.8); + border: 1px solid #ccc; + border-radius: 5px; + padding: 10px; + margin-bottom: 5px; + box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.1); +} diff --git a/progress.js b/progress.js new file mode 100644 index 0000000..b92e623 --- /dev/null +++ b/progress.js @@ -0,0 +1,47 @@ +document.addEventListener('DOMContentLoaded', function() { + // Example function to fetch data + fetchProgressData(); + + function fetchProgressData() { + // This function should fetch data from your server or local storage + // Here we simulate data fetching with a static example + const progressData = { + totalSolves: 0, + bestTime: 'NA', + averageTime: 'NA', + cubeTypes: '3x3', + recentSolves: [ + // { time: '1:30', cubeType: '3x3' }, + // { time: '2:15', cubeType: '4x4' } + ] + }; + + displayProgressData(progressData); + } + // Store data in localStorage if it doesn't already exist + if (!localStorage.getItem('progressData')) { + localStorage.setItem('progressData', JSON.stringify(initialData)); + } + + // Retrieve data from localStorage + const progressData = JSON.parse(localStorage.getItem('progressData')); + + + function displayProgressData(data) { + document.getElementById('total-solves').textContent = data.totalSolves; + document.getElementById('best-time').textContent = data.bestTime; + document.getElementById('average-time').textContent = data.averageTime; + document.getElementById('cube-types').textContent = data.cubeTypes; + + if(data.totalSolves == 0) document.getElementById('no-progress').textContent = "Please attempt atleast once to display the progress!!" + const recentSolvesList = document.getElementById('recent-solves'); + recentSolvesList.innerHTML = ''; // Clear previous entries + data.recentSolves.forEach(solve => { + const listItem = document.createElement('li'); + listItem.textContent = `${solve.time} - ${solve.cubeType}`; + recentSolvesList.appendChild(listItem); + }); + } + // Fetch and display data on page load + fetchProgressData(); +}); \ No newline at end of file diff --git a/script.js b/script.js index f122628..4db09dc 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,4 @@ -const animationEngine = ( () => { +const animationEngine = (() => { let uniqueID = 0; @@ -8,12 +8,13 @@ const animationEngine = ( () => { this.ids = []; this.animations = {}; - this.update = this.update.bind( this ); + this.update = this.update.bind(this); this.raf = 0; this.time = 0; } + update() { const now = performance.now(); @@ -22,35 +23,35 @@ const animationEngine = ( () => { let i = this.ids.length; - this.raf = i ? requestAnimationFrame( this.update ) : 0; + this.raf = i ? requestAnimationFrame(this.update) : 0; - while ( i-- ) - this.animations[ this.ids[ i ] ] && this.animations[ this.ids[ i ] ].update( delta ); + while (i--) + this.animations[this.ids[i]] && this.animations[this.ids[i]].update(delta); } - add( animation ) { + add(animation) { - animation.id = uniqueID ++; + animation.id = uniqueID++; - this.ids.push( animation.id ); - this.animations[ animation.id ] = animation; + this.ids.push(animation.id); + this.animations[animation.id] = animation; - if ( this.raf !== 0 ) return; + if (this.raf !== 0) return; this.time = performance.now(); - this.raf = requestAnimationFrame( this.update ); + this.raf = requestAnimationFrame(this.update); } - remove( animation ) { + remove(animation) { - const index = this.ids.indexOf( animation.id ); + const index = this.ids.indexOf(animation.id); - if ( index < 0 ) return; + if (index < 0) return; - this.ids.splice( index, 1 ); - delete this.animations[ animation.id ]; + this.ids.splice(index, 1); + delete this.animations[animation.id]; animation = null; } @@ -59,48 +60,48 @@ const animationEngine = ( () => { return new AnimationEngine(); -} )(); +})(); class Animation { - constructor( start ) { + constructor(start) { - if ( start === true ) this.start(); + if (start === true) this.start(); } start() { - animationEngine.add( this ); + animationEngine.add(this); } stop() { - animationEngine.remove( this ); + animationEngine.remove(this); } - update( delta ) {} + update(delta) { } } class World extends Animation { - constructor( game ) { + constructor(game) { - super( true ); + super(true); this.game = game; this.container = this.game.dom.game; this.scene = new THREE.Scene(); - this.renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } ); - this.renderer.setPixelRatio( window.devicePixelRatio ); - this.container.appendChild( this.renderer.domElement ); + this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.container.appendChild(this.renderer.domElement); - this.camera = new THREE.PerspectiveCamera( 2, 1, 0.1, 10000 ); + this.camera = new THREE.PerspectiveCamera(2, 1, 0.1, 10000); this.stage = { width: 2, height: 3 }; this.fov = 10; @@ -110,13 +111,13 @@ class World extends Animation { this.onResize = []; this.resize(); - window.addEventListener( 'resize', () => this.resize(), false ); + window.addEventListener('resize', () => this.resize(), false); } update() { - this.renderer.render( this.scene, this.camera ); + this.renderer.render(this.scene, this.camera); } @@ -125,7 +126,7 @@ class World extends Animation { this.width = this.container.offsetWidth; this.height = this.container.offsetHeight; - this.renderer.setSize( this.width, this.height ); + this.renderer.setSize(this.width, this.height); this.camera.fov = this.fov; this.camera.aspect = this.width / this.height; @@ -133,62 +134,90 @@ class World extends Animation { const aspect = this.stage.width / this.stage.height; const fovRad = this.fov * THREE.Math.DEG2RAD; - let distance = ( aspect < this.camera.aspect ) - ? ( this.stage.height / 2 ) / Math.tan( fovRad / 2 ) - : ( this.stage.width / this.camera.aspect ) / ( 2 * Math.tan( fovRad / 2 ) ); + let distance = (aspect < this.camera.aspect) + ? (this.stage.height / 2) / Math.tan(fovRad / 2) + : (this.stage.width / this.camera.aspect) / (2 * Math.tan(fovRad / 2)); distance *= 0.5; - this.camera.position.set( distance, distance, distance); - this.camera.lookAt( this.scene.position ); + this.camera.position.set(distance, distance, distance); + this.camera.lookAt(this.scene.position); this.camera.updateProjectionMatrix(); - const docFontSize = ( aspect < this.camera.aspect ) - ? ( this.height / 100 ) * aspect + const docFontSize = (aspect < this.camera.aspect) + ? (this.height / 100) * aspect : this.width / 100; document.documentElement.style.fontSize = docFontSize + 'px'; - if ( this.onResize ) this.onResize.forEach( cb => cb() ); + if (this.onResize) this.onResize.forEach(cb => cb()); } createLights() { this.lights = { - holder: new THREE.Object3D, - ambient: new THREE.AmbientLight( 0xffffff, 0.69 ), - front: new THREE.DirectionalLight( 0xffffff, 0.36 ), - back: new THREE.DirectionalLight( 0xffffff, 0.19 ), + holder: new THREE.Object3D, + ambient: new THREE.AmbientLight(0xffffff, 0.69), + front: new THREE.DirectionalLight(0xffffff, 0.36), + back: new THREE.DirectionalLight(0xffffff, 0.19), }; - this.lights.front.position.set( 1.5, 5, 3 ); - this.lights.back.position.set( -1.5, -5, -3 ); + this.lights.front.position.set(1.5, 5, 3); + this.lights.back.position.set(-1.5, -5, -3); - this.lights.holder.add( this.lights.ambient ); - this.lights.holder.add( this.lights.front ); - this.lights.holder.add( this.lights.back ); + this.lights.holder.add(this.lights.ambient); + this.lights.holder.add(this.lights.front); + this.lights.holder.add(this.lights.back); - this.scene.add( this.lights.holder ); + this.scene.add(this.lights.holder); } } -function RoundedBoxGeometry( size, radius, radiusSegments ) { - THREE.BufferGeometry.call( this ); +// script.js + +document.addEventListener('DOMContentLoaded', () => { + const themeSelector = document.getElementById('theme'); + + // Load saved theme from localStorage if it exists + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + document.body.classList.add(`${savedTheme}-theme`); + themeSelector.value = savedTheme; + } else { + document.body.classList.add('default-theme'); + } + + // Change theme when the user selects a different option + themeSelector.addEventListener('change', (event) => { + document.body.className = ''; // Remove all existing classes + const selectedTheme = event.target.value; + document.body.classList.add(`${selectedTheme}-theme`); + + // Save selected theme to localStorage + localStorage.setItem('theme', selectedTheme); + }); +}); + + +function RoundedBoxGeometry(size, radius, radiusSegments) { + + + THREE.BufferGeometry.call(this); this.type = 'RoundedBoxGeometry'; - radiusSegments = ! isNaN( radiusSegments ) ? Math.max( 1, Math.floor( radiusSegments ) ) : 1; + radiusSegments = !isNaN(radiusSegments) ? Math.max(1, Math.floor(radiusSegments)) : 1; var width, height, depth; width = height = depth = size; radius = size * radius; - radius = Math.min( radius, Math.min( width, Math.min( height, Math.min( depth ) ) ) / 2 ); + radius = Math.min(radius, Math.min(width, Math.min(height, Math.min(depth))) / 2); var edgeHalfWidth = width / 2 - radius; var edgeHalfHeight = height / 2 - radius; @@ -203,10 +232,10 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { }; var rs1 = radiusSegments + 1; - var totalVertexCount = ( rs1 * radiusSegments + 1 ) << 3; + var totalVertexCount = (rs1 * radiusSegments + 1) << 3; - var positions = new THREE.BufferAttribute( new Float32Array( totalVertexCount * 3 ), 3 ); - var normals = new THREE.BufferAttribute( new Float32Array( totalVertexCount * 3 ), 3 ); + var positions = new THREE.BufferAttribute(new Float32Array(totalVertexCount * 3), 3); + var normals = new THREE.BufferAttribute(new Float32Array(totalVertexCount * 3), 3); var cornerVerts = [], @@ -216,12 +245,12 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { vertexPool = [], normalPool = [], indices = [] - ; + ; var lastVertex = rs1 * radiusSegments, cornerVertNumber = rs1 * radiusSegments + 1 - ; + ; doVertices(); doFaces(); @@ -233,77 +262,77 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { function doVertices() { var cornerLayout = [ - new THREE.Vector3( 1, 1, 1 ), - new THREE.Vector3( 1, 1, - 1 ), - new THREE.Vector3( - 1, 1, - 1 ), - new THREE.Vector3( - 1, 1, 1 ), - new THREE.Vector3( 1, - 1, 1 ), - new THREE.Vector3( 1, - 1, - 1 ), - new THREE.Vector3( - 1, - 1, - 1 ), - new THREE.Vector3( - 1, - 1, 1 ) + new THREE.Vector3(1, 1, 1), + new THREE.Vector3(1, 1, - 1), + new THREE.Vector3(- 1, 1, - 1), + new THREE.Vector3(- 1, 1, 1), + new THREE.Vector3(1, - 1, 1), + new THREE.Vector3(1, - 1, - 1), + new THREE.Vector3(- 1, - 1, - 1), + new THREE.Vector3(- 1, - 1, 1) ]; - for ( var j = 0; j < 8; j ++ ) { + for (var j = 0; j < 8; j++) { - cornerVerts.push( [] ); - cornerNormals.push( [] ); + cornerVerts.push([]); + cornerNormals.push([]); } var PIhalf = Math.PI / 2; - var cornerOffset = new THREE.Vector3( edgeHalfWidth, edgeHalfHeight, edgeHalfDepth ); + var cornerOffset = new THREE.Vector3(edgeHalfWidth, edgeHalfHeight, edgeHalfDepth); - for ( var y = 0; y <= radiusSegments; y ++ ) { + for (var y = 0; y <= radiusSegments; y++) { var v = y / radiusSegments; var va = v * PIhalf; - var cosVa = Math.cos( va ); - var sinVa = Math.sin( va ); + var cosVa = Math.cos(va); + var sinVa = Math.sin(va); - if ( y == radiusSegments ) { + if (y == radiusSegments) { - vertex.set( 0, 1, 0 ); - var vert = vertex.clone().multiplyScalar( radius ).add( cornerOffset ); - cornerVerts[ 0 ].push( vert ); - vertexPool.push( vert ); + vertex.set(0, 1, 0); + var vert = vertex.clone().multiplyScalar(radius).add(cornerOffset); + cornerVerts[0].push(vert); + vertexPool.push(vert); var norm = vertex.clone(); - cornerNormals[ 0 ].push( norm ); - normalPool.push( norm ); + cornerNormals[0].push(norm); + normalPool.push(norm); continue; } - for ( var x = 0; x <= radiusSegments; x ++ ) { + for (var x = 0; x <= radiusSegments; x++) { var u = x / radiusSegments; var ha = u * PIhalf; - vertex.x = cosVa * Math.cos( ha ); + vertex.x = cosVa * Math.cos(ha); vertex.y = sinVa; - vertex.z = cosVa * Math.sin( ha ); + vertex.z = cosVa * Math.sin(ha); - var vert = vertex.clone().multiplyScalar( radius ).add( cornerOffset ); - cornerVerts[ 0 ].push( vert ); - vertexPool.push( vert ); + var vert = vertex.clone().multiplyScalar(radius).add(cornerOffset); + cornerVerts[0].push(vert); + vertexPool.push(vert); var norm = vertex.clone().normalize(); - cornerNormals[ 0 ].push( norm ); - normalPool.push( norm ); + cornerNormals[0].push(norm); + normalPool.push(norm); } } - for ( var i = 1; i < 8; i ++ ) { + for (var i = 1; i < 8; i++) { - for ( var j = 0; j < cornerVerts[ 0 ].length; j ++ ) { + for (var j = 0; j < cornerVerts[0].length; j++) { - var vert = cornerVerts[ 0 ][ j ].clone().multiply( cornerLayout[ i ] ); - cornerVerts[ i ].push( vert ); - vertexPool.push( vert ); + var vert = cornerVerts[0][j].clone().multiply(cornerLayout[i]); + cornerVerts[i].push(vert); + vertexPool.push(vert); - var norm = cornerNormals[ 0 ][ j ].clone().multiply( cornerLayout[ i ] ); - cornerNormals[ i ].push( norm ); - normalPool.push( norm ); + var norm = cornerNormals[0][j].clone().multiply(cornerLayout[i]); + cornerNormals[i].push(norm); + normalPool.push(norm); } @@ -324,18 +353,18 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { true ]; - var lastRowOffset = rs1 * ( radiusSegments - 1 ); + var lastRowOffset = rs1 * (radiusSegments - 1); - for ( var i = 0; i < 8; i ++ ) { + for (var i = 0; i < 8; i++) { var cornerOffset = cornerVertNumber * i; - for ( var v = 0; v < radiusSegments - 1; v ++ ) { + for (var v = 0; v < radiusSegments - 1; v++) { var r1 = v * rs1; - var r2 = ( v + 1 ) * rs1; + var r2 = (v + 1) * rs1; - for ( var u = 0; u < radiusSegments; u ++ ) { + for (var u = 0; u < radiusSegments; u++) { var u1 = u + 1; var a = cornerOffset + r1 + u; @@ -343,25 +372,25 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { var c = cornerOffset + r2 + u; var d = cornerOffset + r2 + u1; - if ( ! flips[ i ] ) { + if (!flips[i]) { - indices.push( a ); - indices.push( b ); - indices.push( c ); + indices.push(a); + indices.push(b); + indices.push(c); - indices.push( b ); - indices.push( d ); - indices.push( c ); + indices.push(b); + indices.push(d); + indices.push(c); } else { - indices.push( a ); - indices.push( c ); - indices.push( b ); + indices.push(a); + indices.push(c); + indices.push(b); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(b); + indices.push(c); + indices.push(d); } @@ -369,23 +398,23 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { } - for ( var u = 0; u < radiusSegments; u ++ ) { + for (var u = 0; u < radiusSegments; u++) { var a = cornerOffset + lastRowOffset + u; var b = cornerOffset + lastRowOffset + u + 1; var c = cornerOffset + lastVertex; - if ( ! flips[ i ] ) { + if (!flips[i]) { - indices.push( a ); - indices.push( b ); - indices.push( c ); + indices.push(a); + indices.push(b); + indices.push(c); } else { - indices.push( a ); - indices.push( c ); - indices.push( b ); + indices.push(a); + indices.push(c); + indices.push(b); } @@ -402,84 +431,84 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { var c = lastVertex + cornerVertNumber * 2; var d = lastVertex + cornerVertNumber * 3; - indices.push( a ); - indices.push( b ); - indices.push( c ); - indices.push( a ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(a); + indices.push(c); + indices.push(d); a = lastVertex + cornerVertNumber * 4; b = lastVertex + cornerVertNumber * 5; c = lastVertex + cornerVertNumber * 6; d = lastVertex + cornerVertNumber * 7; - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( a ); - indices.push( d ); - indices.push( c ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(a); + indices.push(d); + indices.push(c); a = 0; b = cornerVertNumber; c = cornerVertNumber * 4; d = cornerVertNumber * 5; - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); a = cornerVertNumber * 2; b = cornerVertNumber * 3; c = cornerVertNumber * 6; d = cornerVertNumber * 7; - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); a = radiusSegments; b = radiusSegments + cornerVertNumber * 3; c = radiusSegments + cornerVertNumber * 4; d = radiusSegments + cornerVertNumber * 7; - indices.push( a ); - indices.push( b ); - indices.push( c ); - indices.push( b ); - indices.push( d ); - indices.push( c ); + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); a = radiusSegments + cornerVertNumber; b = radiusSegments + cornerVertNumber * 2; c = radiusSegments + cornerVertNumber * 5; d = radiusSegments + cornerVertNumber * 6; - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); } function doHeightEdges() { - for ( var i = 0; i < 4; i ++ ) { + for (var i = 0; i < 4; i++) { var cOffset = i * cornerVertNumber; var cRowOffset = 4 * cornerVertNumber + cOffset; var needsFlip = i & 1 === 1; - for ( var u = 0; u < radiusSegments; u ++ ) { + for (var u = 0; u < radiusSegments; u++) { var u1 = u + 1; var a = cOffset + u; @@ -487,23 +516,23 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { var c = cRowOffset + u; var d = cRowOffset + u1; - if ( ! needsFlip ) { + if (!needsFlip) { - indices.push( a ); - indices.push( b ); - indices.push( c ); - indices.push( b ); - indices.push( d ); - indices.push( c ); + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); } else { - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); } @@ -515,43 +544,43 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { function doDepthEdges() { - var cStarts = [ 0, 2, 4, 6 ]; - var cEnds = [ 1, 3, 5, 7 ]; + var cStarts = [0, 2, 4, 6]; + var cEnds = [1, 3, 5, 7]; - for ( var i = 0; i < 4; i ++ ) { + for (var i = 0; i < 4; i++) { - var cStart = cornerVertNumber * cStarts[ i ]; - var cEnd = cornerVertNumber * cEnds[ i ]; + var cStart = cornerVertNumber * cStarts[i]; + var cEnd = cornerVertNumber * cEnds[i]; var needsFlip = 1 >= i; - for ( var u = 0; u < radiusSegments; u ++ ) { + for (var u = 0; u < radiusSegments; u++) { var urs1 = u * rs1; - var u1rs1 = ( u + 1 ) * rs1; + var u1rs1 = (u + 1) * rs1; var a = cStart + urs1; var b = cStart + u1rs1; var c = cEnd + urs1; var d = cEnd + u1rs1; - if ( needsFlip ) { + if (needsFlip) { - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); } else { - indices.push( a ); - indices.push( b ); - indices.push( c ); - indices.push( b ); - indices.push( d ); - indices.push( c ); + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); } @@ -565,40 +594,40 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { var end = radiusSegments - 1; - var cStarts = [ 0, 1, 4, 5 ]; - var cEnds = [ 3, 2, 7, 6 ]; - var needsFlip = [ 0, 1, 1, 0 ]; + var cStarts = [0, 1, 4, 5]; + var cEnds = [3, 2, 7, 6]; + var needsFlip = [0, 1, 1, 0]; - for ( var i = 0; i < 4; i ++ ) { + for (var i = 0; i < 4; i++) { - var cStart = cStarts[ i ] * cornerVertNumber; - var cEnd = cEnds[ i ] * cornerVertNumber; + var cStart = cStarts[i] * cornerVertNumber; + var cEnd = cEnds[i] * cornerVertNumber; - for ( var u = 0; u <= end; u ++ ) { + for (var u = 0; u <= end; u++) { var a = cStart + radiusSegments + u * rs1; - var b = cStart + ( u != end ? radiusSegments + ( u + 1 ) * rs1 : cornerVertNumber - 1 ); + var b = cStart + (u != end ? radiusSegments + (u + 1) * rs1 : cornerVertNumber - 1); var c = cEnd + radiusSegments + u * rs1; - var d = cEnd + ( u != end ? radiusSegments + ( u + 1 ) * rs1 : cornerVertNumber - 1 ); + var d = cEnd + (u != end ? radiusSegments + (u + 1) * rs1 : cornerVertNumber - 1); - if ( ! needsFlip[ i ] ) { + if (!needsFlip[i]) { - indices.push( a ); - indices.push( b ); - indices.push( c ); - indices.push( b ); - indices.push( d ); - indices.push( c ); + indices.push(a); + indices.push(b); + indices.push(c); + indices.push(b); + indices.push(d); + indices.push(c); } else { - indices.push( a ); - indices.push( c ); - indices.push( b ); - indices.push( b ); - indices.push( c ); - indices.push( d ); + indices.push(a); + indices.push(c); + indices.push(b); + indices.push(b); + indices.push(c); + indices.push(d); } @@ -610,36 +639,36 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { var index = 0; - for ( var i = 0; i < vertexPool.length; i ++ ) { + for (var i = 0; i < vertexPool.length; i++) { positions.setXYZ( index, - vertexPool[ i ].x, - vertexPool[ i ].y, - vertexPool[ i ].z + vertexPool[i].x, + vertexPool[i].y, + vertexPool[i].z ); normals.setXYZ( index, - normalPool[ i ].x, - normalPool[ i ].y, - normalPool[ i ].z + normalPool[i].x, + normalPool[i].y, + normalPool[i].z ); - index ++; + index++; } - this.setIndex( new THREE.BufferAttribute( new Uint16Array( indices ), 1 ) ); - this.addAttribute( 'position', positions ); - this.addAttribute( 'normal', normals ); + this.setIndex(new THREE.BufferAttribute(new Uint16Array(indices), 1)); + this.addAttribute('position', positions); + this.addAttribute('normal', normals); } -RoundedBoxGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); +RoundedBoxGeometry.prototype = Object.create(THREE.BufferGeometry.prototype); RoundedBoxGeometry.constructor = RoundedBoxGeometry; -function RoundedPlaneGeometry( size, radius, depth ) { +function RoundedPlaneGeometry(size, radius, depth) { var x, y, width, height; @@ -649,15 +678,15 @@ function RoundedPlaneGeometry( size, radius, depth ) { const shape = new THREE.Shape(); - shape.moveTo( x, y + radius ); - shape.lineTo( x, y + height - radius ); - shape.quadraticCurveTo( x, y + height, x + radius, y + height ); - shape.lineTo( x + width - radius, y + height ); - shape.quadraticCurveTo( x + width, y + height, x + width, y + height - radius ); - shape.lineTo( x + width, y + radius ); - shape.quadraticCurveTo( x + width, y, x + width - radius, y ); - shape.lineTo( x + radius, y ); - shape.quadraticCurveTo( x, y, x, y + radius ); + shape.moveTo(x, y + radius); + shape.lineTo(x, y + height - radius); + shape.quadraticCurveTo(x, y + height, x + radius, y + height); + shape.lineTo(x + width - radius, y + height); + shape.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); + shape.lineTo(x + width, y + radius); + shape.quadraticCurveTo(x + width, y, x + width - radius, y); + shape.lineTo(x + radius, y); + shape.quadraticCurveTo(x, y, x, y + radius); const geometry = new THREE.ExtrudeBufferGeometry( shape, @@ -670,7 +699,7 @@ function RoundedPlaneGeometry( size, radius, depth ) { class Cube { - constructor( game ) { + constructor(game) { this.game = game; this.size = 3; @@ -686,10 +715,10 @@ class Cube { this.object = new THREE.Object3D(); this.animator = new THREE.Object3D(); - this.holder.add( this.animator ); - this.animator.add( this.object ); + this.holder.add(this.animator); + this.animator.add(this.object); - this.game.world.scene.add( this.holder ); + this.game.world.scene.add(this.holder); } @@ -697,42 +726,42 @@ class Cube { this.cubes = []; this.object.children = []; - this.object.add( this.game.controls.group ); + this.object.add(this.game.controls.group); - if ( this.size === 2 ) this.scale = 1.25; - else if ( this.size === 3 ) this.scale = 1; - else if ( this.size > 3 ) this.scale = 3 / this.size; + if (this.size === 2) this.scale = 1.25; + else if (this.size === 3) this.scale = 1; + else if (this.size > 3) this.scale = 3 / this.size; - this.object.scale.set( this.scale, this.scale, this.scale ); + this.object.scale.set(this.scale, this.scale, this.scale); const controlsScale = this.size === 2 ? 0.825 : 1; - this.game.controls.edges.scale.set( controlsScale, controlsScale, controlsScale ); - + this.game.controls.edges.scale.set(controlsScale, controlsScale, controlsScale); + this.generatePositions(); this.generateModel(); - this.pieces.forEach( piece => { + this.pieces.forEach(piece => { - this.cubes.push( piece.userData.cube ); - this.object.add( piece ); + this.cubes.push(piece.userData.cube); + this.object.add(piece); - } ); + }); - this.holder.traverse( node => { + this.holder.traverse(node => { - if ( node.frustumCulled ) node.frustumCulled = false; + if (node.frustumCulled) node.frustumCulled = false; - } ); + }); - this.updateColors( this.game.themes.getColors() ); + this.updateColors(this.game.themes.getColors()); this.sizeGenerated = this.size; } - resize( force = false ) { + resize(force = false) { - if ( this.size !== this.sizeGenerated || force ) { + if (this.size !== this.sizeGenerated || force) { this.size = this.game.preferences.ranges.size.value; @@ -749,11 +778,11 @@ class Cube { reset() { - this.game.controls.edges.rotation.set( 0, 0, 0 ); + this.game.controls.edges.rotation.set(0, 0, 0); - this.holder.rotation.set( 0, 0, 0 ); - this.object.rotation.set( 0, 0, 0 ); - this.animator.rotation.set( 0, 0, 0 ); + this.holder.rotation.set(0, 0, 0); + this.object.rotation.set(0, 0, 0); + this.animator.rotation.set(0, 0, 0); } @@ -768,22 +797,22 @@ class Cube { this.positions = []; - for ( x = 0; x < this.size; x ++ ) { - for ( y = 0; y < this.size; y ++ ) { - for ( z = 0; z < this.size; z ++ ) { + for (x = 0; x < this.size; x++) { + for (y = 0; y < this.size; y++) { + for (z = 0; z < this.size; z++) { let position = new THREE.Vector3(first + x, first + y, first + z); let edges = []; - if ( x == 0 ) edges.push(0); - if ( x == m ) edges.push(1); - if ( y == 0 ) edges.push(2); - if ( y == m ) edges.push(3); - if ( z == 0 ) edges.push(4); - if ( z == m ) edges.push(5); + if (x == 0) edges.push(0); + if (x == m) edges.push(1); + if (y == 0) edges.push(2); + if (y == m) edges.push(3); + if (z == 0) edges.push(4); + if (z == m) edges.push(5); position.edges = edges; - this.positions.push( position ); + this.positions.push(position); } } @@ -801,7 +830,7 @@ class Cube { const mainMaterial = new THREE.MeshLambertMaterial(); const pieceMesh = new THREE.Mesh( - new RoundedBoxGeometry( pieceSize, this.geometry.pieceCornerRadius, 3 ), + new RoundedBoxGeometry(pieceSize, this.geometry.pieceCornerRadius, 3), mainMaterial.clone() ); @@ -811,32 +840,32 @@ class Cube { this.geometry.edgeDepth ); - this.positions.forEach( ( position, index ) => { + this.positions.forEach((position, index) => { const piece = new THREE.Object3D(); const pieceCube = pieceMesh.clone(); const pieceEdges = []; - piece.position.copy( position.clone().divideScalar( 3 ) ); - piece.add( pieceCube ); + piece.position.copy(position.clone().divideScalar(3)); + piece.add(pieceCube); piece.name = index; piece.edgesName = ''; - position.edges.forEach( position => { + position.edges.forEach(position => { - const edge = new THREE.Mesh( edgeGeometry, mainMaterial.clone() ); - const name = [ 'L', 'R', 'D', 'U', 'B', 'F' ][ position ]; + const edge = new THREE.Mesh(edgeGeometry, mainMaterial.clone()); + const name = ['L', 'R', 'D', 'U', 'B', 'F'][position]; const distance = pieceSize / 2; edge.position.set( - distance * [ - 1, 1, 0, 0, 0, 0 ][ position ], - distance * [ 0, 0, - 1, 1, 0, 0 ][ position ], - distance * [ 0, 0, 0, 0, - 1, 1 ][ position ] + distance * [- 1, 1, 0, 0, 0, 0][position], + distance * [0, 0, - 1, 1, 0, 0][position], + distance * [0, 0, 0, 0, - 1, 1][position] ); edge.rotation.set( - Math.PI / 2 * [ 0, 0, 1, - 1, 0, 0 ][ position ], - Math.PI / 2 * [ - 1, 1, 0, 0, 2, 0 ][ position ], + Math.PI / 2 * [0, 0, 1, - 1, 0, 0][position], + Math.PI / 2 * [- 1, 1, 0, 0, 2, 0][position], 0 ); @@ -848,11 +877,11 @@ class Cube { edge.name = name; - piece.add( edge ); - pieceEdges.push( name ); - this.edges.push( edge ); + piece.add(edge); + pieceEdges.push(name); + this.edges.push(edge); - } ); + }); piece.userData.edges = pieceEdges; piece.userData.cube = pieceCube; @@ -862,39 +891,39 @@ class Cube { rotation: piece.rotation.clone(), }; - this.pieces.push( piece ); + this.pieces.push(piece); - } ); + }); } - updateColors( colors ) { + updateColors(colors) { - if ( typeof this.pieces !== 'object' && typeof this.edges !== 'object' ) return; + if (typeof this.pieces !== 'object' && typeof this.edges !== 'object') return; - this.pieces.forEach( piece => piece.userData.cube.material.color.setHex( colors.P ) ); - this.edges.forEach( edge => edge.material.color.setHex( colors[ edge.name ] ) ); + this.pieces.forEach(piece => piece.userData.cube.material.color.setHex(colors.P)); + this.edges.forEach(edge => edge.material.color.setHex(colors[edge.name])); } - loadFromData( data ) { + loadFromData(data) { this.size = data.size; this.reset(); this.init(); - this.pieces.forEach( piece => { + this.pieces.forEach(piece => { - const index = data.names.indexOf( piece.name ); + const index = data.names.indexOf(piece.name); const position = data.positions[index]; const rotation = data.rotations[index]; - piece.position.set( position.x, position.y, position.z ); - piece.rotation.set( rotation.x, rotation.y, rotation.z ); + piece.position.set(position.x, position.y, position.z); + piece.rotation.set(rotation.x, rotation.y, rotation.z); - } ); + }); } @@ -906,27 +935,27 @@ const Easing = { In: power => { - power = Math.round( power || 1 ); + power = Math.round(power || 1); - return t => Math.pow( t, power ); + return t => Math.pow(t, power); }, Out: power => { - power = Math.round( power || 1 ); + power = Math.round(power || 1); - return t => 1 - Math.abs( Math.pow( t - 1, power ) ); + return t => 1 - Math.abs(Math.pow(t - 1, power)); }, InOut: power => { - power = Math.round( power || 1 ); + power = Math.round(power || 1); - return t => ( t < 0.5 ) - ? Math.pow( t * 2, power ) / 2 - : ( 1 - Math.abs( Math.pow( ( t * 2 - 1 ) - 1, power ) ) ) / 2 + 0.5; + return t => (t < 0.5) + ? Math.pow(t * 2, power) / 2 + : (1 - Math.abs(Math.pow((t * 2 - 1) - 1, power))) / 2 + 0.5; }, @@ -934,11 +963,11 @@ const Easing = { Sine: { - In: () => t => 1 + Math.sin( Math.PI / 2 * t - Math.PI / 2 ), + In: () => t => 1 + Math.sin(Math.PI / 2 * t - Math.PI / 2), - Out: () => t => Math.sin( Math.PI / 2 * t ), + Out: () => t => Math.sin(Math.PI / 2 * t), - InOut: () => t => ( 1 + Math.sin( Math.PI * t - Math.PI / 2 ) ) / 2, + InOut: () => t => (1 + Math.sin(Math.PI * t - Math.PI / 2)) / 2, }, @@ -948,7 +977,7 @@ const Easing = { s = s || 1.70158; - return t => { return ( t -= 1 ) * t * ( ( s + 1 ) * t + s ) + 1; }; + return t => { return (t -= 1) * t * ((s + 1) * t + s) + 1; }; }, @@ -956,7 +985,7 @@ const Easing = { s = s || 1.70158; - return t => { return t * t * ( ( s + 1 ) * t - s ); }; + return t => { return t * t * ((s + 1) * t - s); }; } @@ -964,17 +993,17 @@ const Easing = { Elastic: { - Out: ( amplitude, period ) => { + Out: (amplitude, period) => { let PI2 = Math.PI * 2; - let p1 = ( amplitude >= 1 ) ? amplitude : 1; - let p2 = ( period || 0.3 ) / ( amplitude < 1 ? amplitude : 1 ); - let p3 = p2 / PI2 * ( Math.asin( 1 / p1 ) || 0 ); + let p1 = (amplitude >= 1) ? amplitude : 1; + let p2 = (period || 0.3) / (amplitude < 1 ? amplitude : 1); + let p3 = p2 / PI2 * (Math.asin(1 / p1) || 0); p2 = PI2 / p2; - return t => { return p1 * Math.pow( 2, -10 * t ) * Math.sin( ( t - p3 ) * p2 ) + 1 } + return t => { return p1 * Math.pow(2, -10 * t) * Math.sin((t - p3) * p2) + 1 } }, @@ -984,14 +1013,14 @@ const Easing = { class Tween extends Animation { - constructor( options ) { + constructor(options) { - super( false ); + super(false); this.duration = options.duration || 500; - this.easing = options.easing || ( t => t ); - this.onUpdate = options.onUpdate || ( () => {} ); - this.onComplete = options.onComplete || ( () => {} ); + this.easing = options.easing || (t => t); + this.onUpdate = options.onUpdate || (() => { }); + this.onComplete = options.onComplete || (() => { }); this.delay = options.delay || false; this.yoyo = options.yoyo ? false : null; @@ -1000,36 +1029,36 @@ class Tween extends Animation { this.value = 0; this.delta = 0; - this.getFromTo( options ); + this.getFromTo(options); - if ( this.delay ) setTimeout( () => super.start(), this.delay ); + if (this.delay) setTimeout(() => super.start(), this.delay); else super.start(); - this.onUpdate( this ); + this.onUpdate(this); } - update( delta ) { + update(delta) { const old = this.value * 1; - const direction = ( this.yoyo === true ) ? - 1 : 1; + const direction = (this.yoyo === true) ? - 1 : 1; - this.progress += ( delta / this.duration ) * direction; + this.progress += (delta / this.duration) * direction; - this.value = this.easing( this.progress ); + this.value = this.easing(this.progress); this.delta = this.value - old; - if ( this.values !== null ) this.updateFromTo(); + if (this.values !== null) this.updateFromTo(); - if ( this.yoyo !== null ) this.updateYoyo(); - else if ( this.progress <= 1 ) this.onUpdate( this ); + if (this.yoyo !== null) this.updateYoyo(); + else if (this.progress <= 1) this.onUpdate(this); else { this.progress = 1; this.value = 1; - this.onUpdate( this ); - this.onComplete( this ); - super.stop(); + this.onUpdate(this); + this.onComplete(this); + super.stop(); } @@ -1037,30 +1066,30 @@ class Tween extends Animation { updateYoyo() { - if ( this.progress > 1 || this.progress < 0 ) { + if (this.progress > 1 || this.progress < 0) { - this.value = this.progress = ( this.progress > 1 ) ? 1 : 0; - this.yoyo = ! this.yoyo; + this.value = this.progress = (this.progress > 1) ? 1 : 0; + this.yoyo = !this.yoyo; } - this.onUpdate( this ); + this.onUpdate(this); } updateFromTo() { - this.values.forEach( key => { + this.values.forEach(key => { - this.target[ key ] = this.from[ key ] + ( this.to[ key ] - this.from[ key ] ) * this.value; + this.target[key] = this.from[key] + (this.to[key] - this.from[key]) * this.value; - } ); + }); } - getFromTo( options ) { + getFromTo(options) { - if ( ! options.target || ! options.to ) { + if (!options.target || !options.to) { this.values = null; return; @@ -1072,21 +1101,21 @@ class Tween extends Animation { this.to = options.to || null; this.values = []; - if ( Object.keys( this.from ).length < 1 ) - Object.keys( this.to ).forEach( key => { this.from[ key ] = this.target[ key ]; } ); + if (Object.keys(this.from).length < 1) + Object.keys(this.to).forEach(key => { this.from[key] = this.target[key]; }); - Object.keys( this.to ).forEach( key => { this.values.push( key ); } ); + Object.keys(this.to).forEach(key => { this.values.push(key); }); } } -window.addEventListener( 'touchmove', () => {} ); -document.addEventListener( 'touchmove', event => { event.preventDefault(); }, { passive: false } ); +window.addEventListener('touchmove', () => { }); +document.addEventListener('touchmove', event => { event.preventDefault(); }, { passive: false }); class Draggable { - constructor( element, options ) { + constructor(element, options) { this.position = { current: new THREE.Vector2(), @@ -1096,76 +1125,76 @@ class Draggable { drag: new THREE.Vector2(), }; - this.options = Object.assign( { + this.options = Object.assign({ calcDelta: false, - }, options || {} ); + }, options || {}); this.element = element; this.touch = null; this.drag = { - start: ( event ) => { + start: (event) => { - if ( event.type == 'mousedown' && event.which != 1 ) return; - if ( event.type == 'touchstart' && event.touches.length > 1 ) return; + if (event.type == 'mousedown' && !(event.which == 1 || event.which == 3)) return; + if (event.type == 'touchstart' && event.touches.length > 1) return; - this.getPositionCurrent( event ); + this.getPositionCurrent(event); - if ( this.options.calcDelta ) { + if (this.options.calcDelta) { this.position.start = this.position.current.clone(); - this.position.delta.set( 0, 0 ); - this.position.drag.set( 0, 0 ); + this.position.delta.set(0, 0); + this.position.drag.set(0, 0); } - this.touch = ( event.type == 'touchstart' ); - - this.onDragStart( this.position ); + this.touch = (event.type == 'touchstart'); - window.addEventListener( ( this.touch ) ? 'touchmove' : 'mousemove', this.drag.move, false ); - window.addEventListener( ( this.touch ) ? 'touchend' : 'mouseup', this.drag.end, false ); + this.onDragStart(this.position); + window.addEventListener((this.touch) ? 'touchmove' : 'mousemove', this.drag.move, false); + window.addEventListener((this.touch) ? 'touchend' : 'mouseup', this.drag.end, false); + window.addEventListener("contextmenu", (event) => { event.preventDefault() }); }, - move: ( event ) => { + move: (event) => { - if ( this.options.calcDelta ) { + if (this.options.calcDelta) { this.position.old = this.position.current.clone(); } - this.getPositionCurrent( event ); + this.getPositionCurrent(event); - if ( this.options.calcDelta ) { + if (this.options.calcDelta) { - this.position.delta = this.position.current.clone().sub( this.position.old ); - this.position.drag = this.position.current.clone().sub( this.position.start ); + this.position.delta = this.position.current.clone().sub(this.position.old); + this.position.drag = this.position.current.clone().sub(this.position.start); } - this.onDragMove( this.position ); + this.onDragMove(this.position); }, - end: ( event ) => { + end: (event) => { - this.getPositionCurrent( event ); + this.getPositionCurrent(event); - this.onDragEnd( this.position ); + this.onDragEnd(this.position); - window.removeEventListener( ( this.touch ) ? 'touchmove' : 'mousemove', this.drag.move, false ); - window.removeEventListener( ( this.touch ) ? 'touchend' : 'mouseup', this.drag.end, false ); + window.removeEventListener((this.touch) ? 'touchmove' : 'mousemove', this.drag.move, false); + window.removeEventListener((this.touch) ? 'touchend' : 'mouseup', this.drag.end, false); }, }; - this.onDragStart = () => {}; - this.onDragMove = () => {}; - this.onDragEnd = () => {}; + this.onDragStart = () => { }; + this.onDragMove = () => { }; + this.onDragEnd = () => { }; this.enable(); @@ -1175,8 +1204,8 @@ class Draggable { enable() { - this.element.addEventListener( 'touchstart', this.drag.start, false ); - this.element.addEventListener( 'mousedown', this.drag.start, false ); + this.element.addEventListener('touchstart', this.drag.start, false); + this.element.addEventListener('mousedown', this.drag.start, false); return this; @@ -1184,27 +1213,27 @@ class Draggable { disable() { - this.element.removeEventListener( 'touchstart', this.drag.start, false ); - this.element.removeEventListener( 'mousedown', this.drag.start, false ); + this.element.removeEventListener('touchstart', this.drag.start, false); + this.element.removeEventListener('mousedown', this.drag.start, false); return this; } - getPositionCurrent( event ) { + getPositionCurrent(event) { const dragEvent = event.touches - ? ( event.touches[ 0 ] || event.changedTouches[ 0 ] ) + ? (event.touches[0] || event.changedTouches[0]) : event; - this.position.current.set( dragEvent.pageX, dragEvent.pageY ); + this.position.current.set(dragEvent.pageX, dragEvent.pageY); } - convertPosition( position ) { + convertPosition(position) { - position.x = ( position.x / this.element.offsetWidth ) * 2 - 1; - position.y = - ( ( position.y / this.element.offsetHeight ) * 2 - 1 ); + position.x = (position.x / this.element.offsetWidth) * 2 - 1; + position.y = - ((position.y / this.element.offsetHeight) * 2 - 1); return position; @@ -1219,40 +1248,40 @@ const ANIMATING = 3; class Controls { - constructor( game ) { + constructor(game) { this.game = game; this.flipConfig = 0; - this.flipEasings = [ Easing.Power.Out( 3 ), Easing.Sine.Out(), Easing.Back.Out( 1.5 ) ]; - this.flipSpeeds = [ 125, 200, 300 ]; + this.flipEasings = [Easing.Power.Out(3), Easing.Sine.Out(), Easing.Back.Out(1.5)]; + this.flipSpeeds = [125, 200, 300]; this.raycaster = new THREE.Raycaster(); - const helperMaterial = new THREE.MeshBasicMaterial( { depthWrite: false, transparent: true, opacity: 0, color: 0x0033ff } ); + const helperMaterial = new THREE.MeshBasicMaterial({ depthWrite: false, transparent: true, opacity: 0, color: 0x0033ff }); this.group = new THREE.Object3D(); this.group.name = 'controls'; - this.game.cube.object.add( this.group ); + this.game.cube.object.add(this.group); this.helper = new THREE.Mesh( - new THREE.PlaneBufferGeometry( 200, 200 ), + new THREE.PlaneBufferGeometry(200, 200), helperMaterial.clone() ); - this.helper.rotation.set( 0, Math.PI / 4, 0 ); - this.game.world.scene.add( this.helper ); + this.helper.rotation.set(0, Math.PI / 4, 0); + this.game.world.scene.add(this.helper); this.edges = new THREE.Mesh( - new THREE.BoxBufferGeometry( 1, 1, 1 ), + new THREE.BoxBufferGeometry(1, 1, 1), helperMaterial.clone(), ); - this.game.world.scene.add( this.edges ); + this.game.world.scene.add(this.edges); - this.onSolved = () => {}; - this.onMove = () => {}; + this.onSolved = () => { }; + this.onMove = () => { }; this.momentum = []; @@ -1280,128 +1309,126 @@ class Controls { initDraggable() { - this.draggable = new Draggable( this.game.dom.game ); + this.draggable = new Draggable(this.game.dom.game); this.draggable.onDragStart = position => { - if ( this.scramble !== null ) return; - if ( this.state === PREPARING || this.state === ROTATING ) return; + if (this.scramble !== null) return; + if (this.state === PREPARING || this.state === ROTATING) return; this.gettingDrag = this.state === ANIMATING; - const edgeIntersect = this.getIntersect( position.current, this.edges, false ); + const edgeIntersect = this.getIntersect(position.current, this.edges, false); - if ( edgeIntersect !== false ) { + if (edgeIntersect !== false) { - this.dragIntersect = this.getIntersect( position.current, this.game.cube.cubes, true ); + this.dragIntersect = this.getIntersect(position.current, this.game.cube.cubes, true); } - if ( edgeIntersect !== false && this.dragIntersect !== false ) { + if (edgeIntersect !== false && this.dragIntersect !== false && event.which != 3) { this.dragNormal = edgeIntersect.face.normal.round(); this.flipType = 'layer'; - this.attach( this.helper, this.edges ); + this.attach(this.helper, this.edges); - this.helper.rotation.set( 0, 0, 0 ); - this.helper.position.set( 0, 0, 0 ); - this.helper.lookAt( this.dragNormal ); - this.helper.translateZ( 0.5 ); + this.helper.rotation.set(0, 0, 0); + this.helper.position.set(0, 0, 0); + this.helper.lookAt(this.dragNormal); + this.helper.translateZ(0.5); this.helper.updateMatrixWorld(); - this.detach( this.helper, this.edges ); + this.detach(this.helper, this.edges); } else { - this.dragNormal = new THREE.Vector3( 0, 0, 1 ); + this.dragNormal = new THREE.Vector3(0, 0, 1); this.flipType = 'cube'; - this.helper.position.set( 0, 0, 0 ); - this.helper.rotation.set( 0, Math.PI / 4, 0 ); + this.helper.position.set(0, 0, 0); + this.helper.rotation.set(0, Math.PI / 4, 0); this.helper.updateMatrixWorld(); } - let planeIntersect = this.getIntersect( position.current, this.helper, false ); - if ( planeIntersect === false ) return; + let planeIntersect = this.getIntersect(position.current, this.helper, false); + if (planeIntersect === false) return; - this.dragCurrent = this.helper.worldToLocal( planeIntersect.point ); + this.dragCurrent = this.helper.worldToLocal(planeIntersect.point); this.dragTotal = new THREE.Vector3(); - this.state = ( this.state === STILL ) ? PREPARING : this.state; + this.state = (this.state === STILL) ? PREPARING : this.state; }; this.draggable.onDragMove = position => { - if ( this.scramble !== null ) return; - if ( this.state === STILL || ( this.state === ANIMATING && this.gettingDrag === false ) ) return; + if (this.scramble !== null) return; + if (this.state === STILL || (this.state === ANIMATING && this.gettingDrag === false)) return; - const planeIntersect = this.getIntersect( position.current, this.helper, false ); - if ( planeIntersect === false ) return; + const planeIntersect = this.getIntersect(position.current, this.helper, false); + if (planeIntersect === false) return; - const point = this.helper.worldToLocal( planeIntersect.point.clone() ); + const point = this.helper.worldToLocal(planeIntersect.point.clone()); - this.dragDelta = point.clone().sub( this.dragCurrent ).setZ( 0 ); - this.dragTotal.add( this.dragDelta ); + this.dragDelta = point.clone().sub(this.dragCurrent).setZ(0); + this.dragTotal.add(this.dragDelta); this.dragCurrent = point; - this.addMomentumPoint( this.dragDelta ); + this.addMomentumPoint(this.dragDelta); - if ( this.state === PREPARING && this.dragTotal.length() > 0.05 ) { + if (this.state === PREPARING && this.dragTotal.length() > 0.05) { - this.dragDirection = this.getMainAxis( this.dragTotal ); + this.dragDirection = this.getMainAxis(this.dragTotal); - if ( this.flipType === 'layer' ) { + if (this.flipType === 'layer') { const direction = new THREE.Vector3(); - direction[ this.dragDirection ] = 1; + direction[this.dragDirection] = 1; - const worldDirection = this.helper.localToWorld( direction ).sub( this.helper.position ); - const objectDirection = this.edges.worldToLocal( worldDirection ).round(); + const worldDirection = this.helper.localToWorld(direction).sub(this.helper.position); + const objectDirection = this.edges.worldToLocal(worldDirection).round(); - this.flipAxis = objectDirection.cross( this.dragNormal ).negate(); + this.flipAxis = objectDirection.cross(this.dragNormal).negate(); - this.selectLayer( this.getLayer( false ) ); + this.selectLayer(this.getLayer(false)); } else { - const axis = ( this.dragDirection != 'x' ) - ? ( ( this.dragDirection == 'y' && position.current.x > this.game.world.width / 2 ) ? 'z' : 'x' ) + const axis = (this.dragDirection != 'x') + ? ((this.dragDirection == 'y' && position.current.x > this.game.world.width / 2) ? 'z' : 'x') : 'y'; this.flipAxis = new THREE.Vector3(); - this.flipAxis[ axis ] = 1 * ( ( axis == 'x' ) ? - 1 : 1 ); + this.flipAxis[axis] = 1 * ((axis == 'x') ? - 1 : 1); } this.flipAngle = 0; this.state = ROTATING; - } else if ( this.state === ROTATING ) { + } else if (this.state === ROTATING) { - const rotation = this.dragDelta[ this.dragDirection ]; + const rotation = this.dragDelta[this.dragDirection]; - if ( this.flipType === 'layer' ) { + if (this.flipType === 'layer') { - this.group.rotateOnAxis( this.flipAxis, rotation ); + this.group.rotateOnAxis(this.flipAxis, rotation); this.flipAngle += rotation; } else { - this.edges.rotateOnWorldAxis( this.flipAxis, rotation ); - this.game.cube.object.rotation.copy( this.edges.rotation ); + this.edges.rotateOnWorldAxis(this.flipAxis, rotation); + this.game.cube.object.rotation.copy(this.edges.rotation); this.flipAngle += rotation; - } - } }; this.draggable.onDragEnd = position => { - if ( this.scramble !== null ) return; - if ( this.state !== ROTATING ) { + if (this.scramble !== null) return; + if (this.state !== ROTATING) { this.gettingDrag = false; this.state = STILL; @@ -1411,36 +1438,36 @@ class Controls { this.state = ANIMATING; - const momentum = this.getMomentum()[ this.dragDirection ]; - const flip = ( Math.abs( momentum ) > 0.05 && Math.abs( this.flipAngle ) < Math.PI / 2 ); + const momentum = this.getMomentum()[this.dragDirection]; + const flip = (Math.abs(momentum) > 0.05 && Math.abs(this.flipAngle) < Math.PI / 2); const angle = flip - ? this.roundAngle( this.flipAngle + Math.sign( this.flipAngle ) * ( Math.PI / 4 ) ) - : this.roundAngle( this.flipAngle ); + ? this.roundAngle(this.flipAngle + Math.sign(this.flipAngle) * (Math.PI / 4)) + : this.roundAngle(this.flipAngle); const delta = angle - this.flipAngle; - if ( this.flipType === 'layer' ) { + if (this.flipType === 'layer') { - this.rotateLayer( delta, false, layer => { + this.rotateLayer(delta, false, layer => { this.game.storage.saveGame(); - + this.state = this.gettingDrag ? PREPARING : STILL; this.gettingDrag = false; this.checkIsSolved(); - } ); + }); } else { - this.rotateCube( delta, () => { + this.rotateCube(delta, () => { this.state = this.gettingDrag ? PREPARING : STILL; this.gettingDrag = false; - } ); + }); } @@ -1448,38 +1475,38 @@ class Controls { } - rotateLayer( rotation, scramble, callback ) { + rotateLayer(rotation, scramble, callback) { const config = scramble ? 0 : this.flipConfig; - const easing = this.flipEasings[ config ]; - const duration = this.flipSpeeds[ config ]; - const bounce = ( config == 2 ) ? this.bounceCube() : ( () => {} ); + const easing = this.flipEasings[config]; + const duration = this.flipSpeeds[config]; + const bounce = (config == 2) ? this.bounceCube() : (() => { }); - this.rotationTween = new Tween( { + this.rotationTween = new Tween({ easing: easing, duration: duration, onUpdate: tween => { let deltaAngle = tween.delta * rotation; - this.group.rotateOnAxis( this.flipAxis, deltaAngle ); - bounce( tween.value, deltaAngle, rotation ); + this.group.rotateOnAxis(this.flipAxis, deltaAngle); + bounce(tween.value, deltaAngle, rotation); }, onComplete: () => { - if ( ! scramble ) this.onMove(); + if (!scramble) this.onMove(); - const layer = this.flipLayer.slice( 0 ); + const layer = this.flipLayer.slice(0); - this.game.cube.object.rotation.setFromVector3( this.snapRotation( this.game.cube.object.rotation.toVector3() ) ); - this.group.rotation.setFromVector3( this.snapRotation( this.group.rotation.toVector3() ) ); - this.deselectLayer( this.flipLayer ); + this.game.cube.object.rotation.setFromVector3(this.snapRotation(this.game.cube.object.rotation.toVector3())); + this.group.rotation.setFromVector3(this.snapRotation(this.group.rotation.toVector3())); + this.deselectLayer(this.flipLayer); - callback( layer ); + callback(layer); }, - } ); + }); } @@ -1487,149 +1514,149 @@ class Controls { let fixDelta = true; - return ( progress, delta, rotation ) => { + return (progress, delta, rotation) => { - if ( progress >= 1 ) { + if (progress >= 1) { - if ( fixDelta ) { + if (fixDelta) { - delta = ( progress - 1 ) * rotation; - fixDelta = false; + delta = (progress - 1) * rotation; + fixDelta = false; - } + } - this.game.cube.object.rotateOnAxis( this.flipAxis, delta ); + this.game.cube.object.rotateOnAxis(this.flipAxis, delta); - } + } } } - rotateCube( rotation, callback ) { + rotateCube(rotation, callback) { const config = this.flipConfig; - const easing = [ Easing.Power.Out( 4 ), Easing.Sine.Out(), Easing.Back.Out( 2 ) ][ config ]; - const duration = [ 100, 150, 350 ][ config ]; + const easing = [Easing.Power.Out(4), Easing.Sine.Out(), Easing.Back.Out(2)][config]; + const duration = [100, 150, 350][config]; - this.rotationTween = new Tween( { + this.rotationTween = new Tween({ easing: easing, duration: duration, onUpdate: tween => { - this.edges.rotateOnWorldAxis( this.flipAxis, tween.delta * rotation ); - this.game.cube.object.rotation.copy( this.edges.rotation ); + this.edges.rotateOnWorldAxis(this.flipAxis, tween.delta * rotation); + this.game.cube.object.rotation.copy(this.edges.rotation); }, onComplete: () => { - this.edges.rotation.setFromVector3( this.snapRotation( this.edges.rotation.toVector3() ) ); - this.game.cube.object.rotation.copy( this.edges.rotation ); + this.edges.rotation.setFromVector3(this.snapRotation(this.edges.rotation.toVector3())); + this.game.cube.object.rotation.copy(this.edges.rotation); callback(); }, - } ); + }); } - selectLayer( layer ) { + selectLayer(layer) { - this.group.rotation.set( 0, 0, 0 ); - this.movePieces( layer, this.game.cube.object, this.group ); + this.group.rotation.set(0, 0, 0); + this.movePieces(layer, this.game.cube.object, this.group); this.flipLayer = layer; } - deselectLayer( layer ) { + deselectLayer(layer) { - this.movePieces( layer, this.group, this.game.cube.object ); + this.movePieces(layer, this.group, this.game.cube.object); this.flipLayer = null; } - movePieces( layer, from, to ) { + movePieces(layer, from, to) { from.updateMatrixWorld(); to.updateMatrixWorld(); - layer.forEach( index => { + layer.forEach(index => { - const piece = this.game.cube.pieces[ index ]; + const piece = this.game.cube.pieces[index]; - piece.applyMatrix( from.matrixWorld ); - from.remove( piece ); - piece.applyMatrix( new THREE.Matrix4().getInverse( to.matrixWorld ) ); - to.add( piece ); + piece.applyMatrix(from.matrixWorld); + from.remove(piece); + piece.applyMatrix(new THREE.Matrix4().getInverse(to.matrixWorld)); + to.add(piece); - } ); + }); } - getLayer( position ) { + getLayer(position) { - const scalar = { 2: 6, 3: 3, 4: 4, 5: 3 }[ this.game.cube.size ]; + const scalar = { 2: 6, 3: 3, 4: 4, 5: 3 }[this.game.cube.size]; const layer = []; let axis; - if ( position === false ) { + if (position === false) { const piece = this.dragIntersect.object.parent; - axis = this.getMainAxis( this.flipAxis ); - position = piece.position.clone() .multiplyScalar( scalar ) .round(); + axis = this.getMainAxis(this.flipAxis); + position = piece.position.clone().multiplyScalar(scalar).round(); } else { - axis = this.getMainAxis( position ); + axis = this.getMainAxis(position); } - this.game.cube.pieces.forEach( piece => { + this.game.cube.pieces.forEach(piece => { - const piecePosition = piece.position.clone().multiplyScalar( scalar ).round(); + const piecePosition = piece.position.clone().multiplyScalar(scalar).round(); - if ( piecePosition[ axis ] == position[ axis ] ) layer.push( piece.name ); + if (piecePosition[axis] == position[axis]) layer.push(piece.name); - } ); + }); return layer; } - keyboardMove( type, move, callback ) { + keyboardMove(type, move, callback) { - if ( this.state !== STILL ) return; - if ( this.enabled !== true ) return; + if (this.state !== STILL) return; + if (this.enabled !== true) return; - if ( type === 'LAYER' ) { + if (type === 'LAYER') { - const layer = this.getLayer( move.position ); + const layer = this.getLayer(move.position); this.flipAxis = new THREE.Vector3(); - this.flipAxis[ move.axis ] = 1; + this.flipAxis[move.axis] = 1; this.state = ROTATING; - this.selectLayer( layer ); - this.rotateLayer( move.angle, false, layer => { + this.selectLayer(layer); + this.rotateLayer(move.angle, false, layer => { this.game.storage.saveGame(); this.state = STILL; this.checkIsSolved(); - } ); + }); - } else if ( type === 'CUBE' ) { + } else if (type === 'CUBE') { this.flipAxis = new THREE.Vector3(); - this.flipAxis[ move.axis ] = 1; + this.flipAxis[move.axis] = 1; this.state = ROTATING; - this.rotateCube( move.angle, () => { + this.rotateCube(move.angle, () => { this.state = STILL; - } ); + }); } @@ -1637,26 +1664,26 @@ class Controls { scrambleCube() { - if ( this.scramble == null ) { + if (this.scramble == null) { this.scramble = this.game.scrambler; - this.scramble.callback = ( typeof callback !== 'function' ) ? () => {} : callback; + this.scramble.callback = (typeof callback !== 'function') ? () => { } : callback; } const converted = this.scramble.converted; - const move = converted[ 0 ]; - const layer = this.getLayer( move.position ); + const move = converted[0]; + const layer = this.getLayer(move.position); this.flipAxis = new THREE.Vector3(); - this.flipAxis[ move.axis ] = 1; + this.flipAxis[move.axis] = 1; - this.selectLayer( layer ); - this.rotateLayer( move.angle, true, () => { + this.selectLayer(layer); + this.rotateLayer(move.angle, true, () => { converted.shift(); - if ( converted.length > 0 ) { + if (converted.length > 0) { this.scrambleCube(); @@ -1667,56 +1694,56 @@ class Controls { } - } ); + }); } - getIntersect( position, object, multiple ) { + getIntersect(position, object, multiple) { this.raycaster.setFromCamera( - this.draggable.convertPosition( position.clone() ), + this.draggable.convertPosition(position.clone()), this.game.world.camera ); - const intersect = ( multiple ) - ? this.raycaster.intersectObjects( object ) - : this.raycaster.intersectObject( object ); + const intersect = (multiple) + ? this.raycaster.intersectObjects(object) + : this.raycaster.intersectObject(object); - return ( intersect.length > 0 ) ? intersect[ 0 ] : false; + return (intersect.length > 0) ? intersect[0] : false; } - getMainAxis( vector ) { + getMainAxis(vector) { - return Object.keys( vector ).reduce( - ( a, b ) => Math.abs( vector[ a ] ) > Math.abs( vector[ b ] ) ? a : b + return Object.keys(vector).reduce( + (a, b) => Math.abs(vector[a]) > Math.abs(vector[b]) ? a : b ); } - detach( child, parent ) { + detach(child, parent) { - child.applyMatrix( parent.matrixWorld ); - parent.remove( child ); - this.game.world.scene.add( child ); + child.applyMatrix(parent.matrixWorld); + parent.remove(child); + this.game.world.scene.add(child); } - attach( child, parent ) { + attach(child, parent) { - child.applyMatrix( new THREE.Matrix4().getInverse( parent.matrixWorld ) ); - this.game.world.scene.remove( child ); - parent.add( child ); + child.applyMatrix(new THREE.Matrix4().getInverse(parent.matrixWorld)); + this.game.world.scene.remove(child); + parent.add(child); } - addMomentumPoint( delta ) { + addMomentumPoint(delta) { const time = Date.now(); - this.momentum = this.momentum.filter( moment => time - moment.time < 500 ); + this.momentum = this.momentum.filter(moment => time - moment.time < 500); - if ( delta !== false ) this.momentum.push( { delta, time } ); + if (delta !== false) this.momentum.push({ delta, time }); } @@ -1725,31 +1752,31 @@ class Controls { const points = this.momentum.length; const momentum = new THREE.Vector2(); - this.addMomentumPoint( false ); + this.addMomentumPoint(false); - this.momentum.forEach( ( point, index ) => { + this.momentum.forEach((point, index) => { - momentum.add( point.delta.multiplyScalar( index / points ) ); + momentum.add(point.delta.multiplyScalar(index / points)); - } ); + }); return momentum; } - roundAngle( angle ) { + roundAngle(angle) { const round = Math.PI / 2; - return Math.sign( angle ) * Math.round( Math.abs( angle) / round ) * round; + return Math.sign(angle) * Math.round(Math.abs(angle) / round) * round; } - snapRotation( angle ) { + snapRotation(angle) { return angle.set( - this.roundAngle( angle.x ), - this.roundAngle( angle.y ), - this.roundAngle( angle.z ) + this.roundAngle(angle.x), + this.roundAngle(angle.y), + this.roundAngle(angle.z) ); } @@ -1761,26 +1788,26 @@ class Controls { let solved = true; const sides = { 'x-': [], 'x+': [], 'y-': [], 'y+': [], 'z-': [], 'z+': [] }; - this.game.cube.edges.forEach( edge => { + this.game.cube.edges.forEach(edge => { const position = edge.parent - .localToWorld( edge.position.clone() ) - .sub( this.game.cube.object.position ); + .localToWorld(edge.position.clone()) + .sub(this.game.cube.object.position); - const mainAxis = this.getMainAxis( position ); - const mainSign = position.multiplyScalar( 2 ).round()[ mainAxis ] < 1 ? '-' : '+'; + const mainAxis = this.getMainAxis(position); + const mainSign = position.multiplyScalar(2).round()[mainAxis] < 1 ? '-' : '+'; - sides[ mainAxis + mainSign ].push( edge.name ); + sides[mainAxis + mainSign].push(edge.name); - } ); + }); - Object.keys( sides ).forEach( side => { + Object.keys(sides).forEach(side => { - if ( ! sides[ side ].every( value => value === sides[ side ][ 0 ] ) ) solved = false; + if (!sides[side].every(value => value === sides[side][0])) solved = false; - } ); + }); - if ( solved ) this.onSolved(); + if (solved) this.onSolved(); } @@ -1788,17 +1815,17 @@ class Controls { class Scrambler { - constructor( game ) { + constructor(game) { this.game = game; this.dificulty = 0; this.scrambleLength = { - 2: [ 7, 9, 11 ], - 3: [ 20, 25, 30 ], - 4: [ 30, 40, 50 ], - 5: [ 40, 60, 80 ], + 2: [7, 9, 11], + 3: [20, 25, 30], + 4: [30, 40, 50], + 5: [40, 60, 80], }; this.moves = []; @@ -1807,73 +1834,73 @@ class Scrambler { } - scramble( scramble ) { + scramble(scramble) { let count = 0; - this.moves = ( typeof scramble !== 'undefined' ) ? scramble.split( ' ' ) : []; + this.moves = (typeof scramble !== 'undefined') ? scramble.split(' ') : []; - if ( this.moves.length < 1 ) { + if (this.moves.length < 1) { - const scrambleLength = this.scrambleLength[ this.game.cube.size ][ this.dificulty ]; + const scrambleLength = this.scrambleLength[this.game.cube.size][this.dificulty]; const faces = this.game.cube.size < 4 ? 'UDLRFB' : 'UuDdLlRrFfBb'; - const modifiers = [ "", "'", "2" ]; - const total = ( typeof scramble === 'undefined' ) ? scrambleLength : scramble; + const modifiers = ["", "'", "2"]; + const total = (typeof scramble === 'undefined') ? scrambleLength : scramble; - while ( count < total ) { + while (count < total) { const move = - faces[ Math.floor( Math.random() * faces.length ) ] + - modifiers[ Math.floor( Math.random() * 3 ) ]; + faces[Math.floor(Math.random() * faces.length)] + + modifiers[Math.floor(Math.random() * 3)]; - if ( count > 0 && move.charAt( 0 ) == this.moves[ count - 1 ].charAt( 0 ) ) continue; - if ( count > 1 && move.charAt( 0 ) == this.moves[ count - 2 ].charAt( 0 ) ) continue; + if (count > 0 && move.charAt(0) == this.moves[count - 1].charAt(0)) continue; + if (count > 1 && move.charAt(0) == this.moves[count - 2].charAt(0)) continue; - this.moves.push( move ); - count ++; + this.moves.push(move); + count++; } } - this.callback = () => {}; + this.callback = () => { }; this.convert(); - this.print = this.moves.join( ' ' ); + this.print = this.moves.join(' '); return this; } - convert( moves ) { + convert(moves) { this.converted = []; - this.moves.forEach( move => { + this.moves.forEach(move => { - const convertedMove = this.convertMove( move ); - const modifier = move.charAt( 1 ); + const convertedMove = this.convertMove(move); + const modifier = move.charAt(1); - this.converted.push( convertedMove ); - if ( modifier == "2" ) this.converted.push( convertedMove ); + this.converted.push(convertedMove); + if (modifier == "2") this.converted.push(convertedMove); - } ); + }); } - convertMove( move ) { + convertMove(move) { - const face = move.charAt( 0 ); - const modifier = move.charAt( 1 ); + const face = move.charAt(0); + const modifier = move.charAt(1); - const axis = { D: 'y', U: 'y', L: 'x', R: 'x', F: 'z', B: 'z' }[ face.toUpperCase() ]; - let row = { D: -1, U: 1, L: -1, R: 1, F: 1, B: -1 }[ face.toUpperCase() ]; + const axis = { D: 'y', U: 'y', L: 'x', R: 'x', F: 'z', B: 'z' }[face.toUpperCase()]; + let row = { D: -1, U: 1, L: -1, R: 1, F: 1, B: -1 }[face.toUpperCase()]; - if ( this.game.cube.size > 3 && face !== face.toLowerCase() ) row = row * 2; + if (this.game.cube.size > 3 && face !== face.toLowerCase()) row = row * 2; const position = new THREE.Vector3(); - position[ { D: 'y', U: 'y', L: 'x', R: 'x', F: 'z', B: 'z' }[ face.toUpperCase() ] ] = row; + position[{ D: 'y', U: 'y', L: 'x', R: 'x', F: 'z', B: 'z' }[face.toUpperCase()]] = row; - const angle = ( Math.PI / 2 ) * - row * ( ( modifier == "'" ) ? - 1 : 1 ); + const angle = (Math.PI / 2) * - row * ((modifier == "'") ? - 1 : 1); return { position, axis, angle, name: move }; @@ -1883,7 +1910,7 @@ class Scrambler { class Transition { - constructor( game ) { + constructor(game) { this.game = game; @@ -1919,14 +1946,14 @@ class Transition { } - buttons( show, hide ) { + buttons(show, hide) { - const buttonTween = ( button, show ) => { + const buttonTween = (button, show) => { - return new Tween( { + return new Tween({ target: button.style, duration: 300, - easing: show ? Easing.Power.Out( 2 ) : Easing.Power.In( 3 ), + easing: show ? Easing.Power.Out(2) : Easing.Power.In(3), from: { opacity: show ? 0 : 1 }, to: { opacity: show ? 1 : 0 }, onUpdate: tween => { @@ -1936,61 +1963,61 @@ class Transition { }, onComplete: () => button.style.pointerEvents = show ? 'all' : 'none' - } ); + }); }; - hide.forEach( button => - this.tweens.buttons[ button ] = buttonTween( this.game.dom.buttons[ button ], false ) + hide.forEach(button => + this.tweens.buttons[button] = buttonTween(this.game.dom.buttons[button], false) ); - setTimeout( () => show.forEach( button => { + setTimeout(() => show.forEach(button => { - this.tweens.buttons[ button ] = buttonTween( this.game.dom.buttons[ button ], true ); + this.tweens.buttons[button] = buttonTween(this.game.dom.buttons[button], true); - } ), hide ? 500 : 0 ); + }), hide ? 500 : 0); } - cube( show, theming = false ) { + cube(show, theming = false) { this.activeTransitions++; - try { this.tweens.cube.stop(); } catch(e) {} + try { this.tweens.cube.stop(); } catch (e) { } const currentY = this.game.cube.animator.position.y; const currentRotation = this.game.cube.animator.rotation.x; - this.tweens.cube = new Tween( { + this.tweens.cube = new Tween({ duration: show ? 3000 : 1250, - easing: show ? Easing.Elastic.Out( 0.8, 0.6 ) : Easing.Back.In( 1 ), + easing: show ? Easing.Elastic.Out(0.8, 0.6) : Easing.Back.In(1), onUpdate: tween => { this.game.cube.animator.position.y = show - ? ( theming ? 0.9 + ( 1 - tween.value ) * 3.5 : ( 1 - tween.value ) * 4 ) + ? (theming ? 0.9 + (1 - tween.value) * 3.5 : (1 - tween.value) * 4) : currentY + tween.value * 4; this.game.cube.animator.rotation.x = show - ? ( 1 - tween.value ) * Math.PI / 3 + ? (1 - tween.value) * Math.PI / 3 : currentRotation + tween.value * - Math.PI / 3; }, - } ); + }); - if ( theming ) { + if (theming) { - if ( show ) { + if (show) { this.game.world.camera.zoom = 0.75; this.game.world.camera.updateProjectionMatrix(); } else { - setTimeout( () => { + setTimeout(() => { this.game.world.camera.zoom = this.data.cameraZoom; this.game.world.camera.updateProjectionMatrix(); - }, 1500 ); + }, 1500); } @@ -1998,20 +2025,20 @@ class Transition { this.durations.cube = show ? 1500 : 1500; - setTimeout( () => this.activeTransitions--, this.durations.cube ); + setTimeout(() => this.activeTransitions--, this.durations.cube); } float() { - try { this.tweens.float.stop(); } catch(e) {} - this.tweens.float = new Tween( { + try { this.tweens.float.stop(); } catch (e) { } + this.tweens.float = new Tween({ duration: 1500, easing: Easing.Sine.InOut(), yoyo: true, onUpdate: tween => { - this.game.cube.holder.position.y = (- 0.02 + tween.value * 0.04); + this.game.cube.holder.position.y = (- 0.02 + tween.value * 0.04); this.game.cube.holder.rotation.x = 0.005 - tween.value * 0.01; this.game.cube.holder.rotation.z = - this.game.cube.holder.rotation.x; this.game.cube.holder.rotation.y = this.game.cube.holder.rotation.x; @@ -2020,198 +2047,198 @@ class Transition { this.game.cube.holder.position.y + this.game.cube.object.position.y; }, - } ); + }); } - zoom( play, time ) { + zoom(play, time) { this.activeTransitions++; - const zoom = ( play ) ? 1 : this.data.cameraZoom; - const duration = ( time > 0 ) ? Math.max( time, 1500 ) : 1500; - const rotations = ( time > 0 ) ? Math.round( duration / 1500 ) : 1; - const easing = Easing.Power.InOut( ( time > 0 ) ? 2 : 3 ); + const zoom = (play) ? 1 : this.data.cameraZoom; + const duration = (time > 0) ? Math.max(time, 1500) : 1500; + const rotations = (time > 0) ? Math.round(duration / 1500) : 1; + const easing = Easing.Power.InOut((time > 0) ? 2 : 3); - this.tweens.zoom = new Tween( { + this.tweens.zoom = new Tween({ target: this.game.world.camera, duration: duration, easing: easing, to: { zoom: zoom }, onUpdate: () => { this.game.world.camera.updateProjectionMatrix(); }, - } ); + }); - this.tweens.rotate = new Tween( { + this.tweens.rotate = new Tween({ target: this.game.cube.animator.rotation, duration: duration, easing: easing, to: { y: - Math.PI * 2 * rotations }, onComplete: () => { this.game.cube.animator.rotation.y = 0; }, - } ); + }); this.durations.zoom = duration; - setTimeout( () => this.activeTransitions--, this.durations.zoom ); + setTimeout(() => this.activeTransitions--, this.durations.zoom); } - elevate( complete ) { + elevate(complete) { this.activeTransitions++; - const cubeY = + const cubeY = - this.tweens.elevate = new Tween( { - target: this.game.cube.object.position, - duration: complete ? 1500 : 0, - easing: Easing.Power.InOut( 3 ), - to: { y: complete ? -0.05 : this.data.cubeY } - } ); + this.tweens.elevate = new Tween({ + target: this.game.cube.object.position, + duration: complete ? 1500 : 0, + easing: Easing.Power.InOut(3), + to: { y: complete ? -0.05 : this.data.cubeY } + }); this.durations.elevate = 1500; - setTimeout( () => this.activeTransitions--, this.durations.elevate ); + setTimeout(() => this.activeTransitions--, this.durations.elevate); } - complete( show, best ) { + complete(show, best) { this.activeTransitions++; const text = best ? this.game.dom.texts.best : this.game.dom.texts.complete; - if ( text.querySelector( 'span i' ) === null ) - text.querySelectorAll( 'span' ).forEach( span => this.splitLetters( span ) ); + if (text.querySelector('span i') === null) + text.querySelectorAll('span').forEach(span => this.splitLetters(span)); - const letters = text.querySelectorAll( '.icon, i' ); + const letters = text.querySelectorAll('.icon, i'); - this.flipLetters( best ? 'best' : 'complete', letters, show ); + this.flipLetters(best ? 'best' : 'complete', letters, show); text.style.opacity = 1; - const duration = this.durations[ best ? 'best' : 'complete' ]; + const duration = this.durations[best ? 'best' : 'complete']; - if ( ! show ) setTimeout( () => this.game.dom.texts.timer.style.transform = '', duration ); + if (!show) setTimeout(() => this.game.dom.texts.timer.style.transform = '', duration); - setTimeout( () => this.activeTransitions--, duration ); + setTimeout(() => this.activeTransitions--, duration); - } + } - stats( show ) { + stats(show) { - if ( show ) this.game.scores.calcStats(); + if (show) this.game.scores.calcStats(); this.activeTransitions++; - this.tweens.stats.forEach( tween => { tween.stop(); tween = null; } ); + this.tweens.stats.forEach(tween => { tween.stop(); tween = null; }); let tweenId = -1; - const stats = this.game.dom.stats.querySelectorAll( '.stats' ); - const easing = show ? Easing.Power.Out( 2 ) : Easing.Power.In( 3 ); + const stats = this.game.dom.stats.querySelectorAll('.stats'); + const easing = show ? Easing.Power.Out(2) : Easing.Power.In(3); - stats.forEach( ( stat, index ) => { + stats.forEach((stat, index) => { - const delay = index * ( show ? 80 : 60 ); + const delay = index * (show ? 80 : 60); - this.tweens.stats[ tweenId++ ] = new Tween( { + this.tweens.stats[tweenId++] = new Tween({ delay: delay, duration: 400, easing: easing, onUpdate: tween => { - const translate = show ? ( 1 - tween.value ) * 2 : tween.value; - const opacity = show ? tween.value : ( 1 - tween.value ); + const translate = show ? (1 - tween.value) * 2 : tween.value; + const opacity = show ? tween.value : (1 - tween.value); stat.style.transform = `translate3d(0, ${translate}em, 0)`; stat.style.opacity = opacity; } - } ); + }); - } ); + }); this.durations.stats = 0; - setTimeout( () => this.activeTransitions--, this.durations.stats ); + setTimeout(() => this.activeTransitions--, this.durations.stats); } - preferences( show ) { + preferences(show) { - this.ranges( this.game.dom.prefs.querySelectorAll( '.range' ), 'prefs', show ); + this.ranges(this.game.dom.prefs.querySelectorAll('.range'), 'prefs', show); } - theming( show ) { + theming(show) { - this.ranges( this.game.dom.theme.querySelectorAll( '.range' ), 'prefs', show ); + this.ranges(this.game.dom.theme.querySelectorAll('.range'), 'prefs', show); } - ranges( ranges, type, show ) { + ranges(ranges, type, show) { this.activeTransitions++; - this.tweens[ type ].forEach( tween => { tween.stop(); tween = null; } ); + this.tweens[type].forEach(tween => { tween.stop(); tween = null; }); const easing = show ? Easing.Power.Out(2) : Easing.Power.In(3); let tweenId = -1; let listMax = 0; - ranges.forEach( ( range, rangeIndex ) => { - - const label = range.querySelector( '.range__label' ); - const track = range.querySelector( '.range__track-line' ); - const handle = range.querySelector( '.range__handle' ); - const list = range.querySelectorAll( '.range__list div' ); + ranges.forEach((range, rangeIndex) => { - const delay = rangeIndex * ( show ? 120 : 100 ); + const label = range.querySelector('.range__label'); + const track = range.querySelector('.range__track-line'); + const handle = range.querySelector('.range__handle'); + const list = range.querySelectorAll('.range__list div'); + + const delay = rangeIndex * (show ? 120 : 100); label.style.opacity = show ? 0 : 1; track.style.opacity = show ? 0 : 1; handle.style.opacity = show ? 0 : 1; handle.style.pointerEvents = show ? 'all' : 'none'; - this.tweens[ type ][ tweenId++ ] = new Tween( { + this.tweens[type][tweenId++] = new Tween({ delay: show ? delay : delay, duration: 400, easing: easing, onUpdate: tween => { - const translate = show ? ( 1 - tween.value ) : tween.value; - const opacity = show ? tween.value : ( 1 - tween.value ); + const translate = show ? (1 - tween.value) : tween.value; + const opacity = show ? tween.value : (1 - tween.value); label.style.transform = `translate3d(0, ${translate}em, 0)`; label.style.opacity = opacity; } - } ); + }); - this.tweens[ type ][ tweenId++ ] = new Tween( { + this.tweens[type][tweenId++] = new Tween({ delay: show ? delay + 100 : delay, duration: 400, easing: easing, onUpdate: tween => { - const translate = show ? ( 1 - tween.value ) : tween.value; - const scale = show ? tween.value : ( 1 - tween.value ); + const translate = show ? (1 - tween.value) : tween.value; + const scale = show ? tween.value : (1 - tween.value); const opacity = scale; track.style.transform = `translate3d(0, ${translate}em, 0) scale3d(${scale}, 1, 1)`; track.style.opacity = opacity; } - } ); + }); - this.tweens[ type ][ tweenId++ ] = new Tween( { + this.tweens[type][tweenId++] = new Tween({ delay: show ? delay + 100 : delay, duration: 400, easing: easing, onUpdate: tween => { - const translate = show ? ( 1 - tween.value ) : tween.value; + const translate = show ? (1 - tween.value) : tween.value; const opacity = 1 - translate; const scale = 0.5 + opacity * 0.5; @@ -2219,74 +2246,74 @@ class Transition { handle.style.opacity = opacity; } - } ); + }); - list.forEach( ( listItem, labelIndex ) => { + list.forEach((listItem, labelIndex) => { listItem.style.opacity = show ? 0 : 1; - this.tweens[ type ][ tweenId++ ] = new Tween( { + this.tweens[type][tweenId++] = new Tween({ delay: show ? delay + 200 + labelIndex * 50 : delay, duration: 400, easing: easing, onUpdate: tween => { - const translate = show ? ( 1 - tween.value ) : tween.value; - const opacity = show ? tween.value : ( 1 - tween.value ); + const translate = show ? (1 - tween.value) : tween.value; + const opacity = show ? tween.value : (1 - tween.value); listItem.style.transform = `translate3d(0, ${translate}em, 0)`; listItem.style.opacity = opacity; } - } ); + }); - } ); + }); listMax = list.length > listMax ? list.length - 1 : listMax; range.style.opacity = 1; - } ); + }); - this.durations[ type ] = show - ? ( ( ranges.length - 1 ) * 100 ) + 200 + listMax * 50 + 400 - : ( ( ranges.length - 1 ) * 100 ) + 400; + this.durations[type] = show + ? ((ranges.length - 1) * 100) + 200 + listMax * 50 + 400 + : ((ranges.length - 1) * 100) + 400; - setTimeout( () => this.activeTransitions--, this.durations[ type ] ); + setTimeout(() => this.activeTransitions--, this.durations[type]); } - title( show ) { + title(show) { this.activeTransitions++; const title = this.game.dom.texts.title; - if ( title.querySelector( 'span i' ) === null ) - title.querySelectorAll( 'span' ).forEach( span => this.splitLetters( span ) ); + if (title.querySelector('span i') === null) + title.querySelectorAll('span').forEach(span => this.splitLetters(span)); - const letters = title.querySelectorAll( 'i' ); + const letters = title.querySelectorAll('i'); - this.flipLetters( 'title', letters, show ); + this.flipLetters('title', letters, show); title.style.opacity = 1; const note = this.game.dom.texts.note; - this.tweens.title[ letters.length ] = new Tween( { + this.tweens.title[letters.length] = new Tween({ target: note.style, easing: Easing.Sine.InOut(), duration: show ? 800 : 400, yoyo: show ? true : null, - from: { opacity: show ? 0 : ( parseFloat( getComputedStyle( note ).opacity ) ) }, + from: { opacity: show ? 0 : (parseFloat(getComputedStyle(note).opacity)) }, to: { opacity: show ? 1 : 0 }, - } ); + }); - setTimeout( () => this.activeTransitions--, this.durations.title ); + setTimeout(() => this.activeTransitions--, this.durations.title); } - timer( show ) { + timer(show) { this.activeTransitions++; @@ -2296,58 +2323,58 @@ class Transition { this.game.timer.convert(); this.game.timer.setText(); - this.splitLetters( timer ); - const letters = timer.querySelectorAll( 'i' ); - this.flipLetters( 'timer', letters, show ); + this.splitLetters(timer); + const letters = timer.querySelectorAll('i'); + this.flipLetters('timer', letters, show); timer.style.opacity = 1; - setTimeout( () => this.activeTransitions--, this.durations.timer ); + setTimeout(() => this.activeTransitions--, this.durations.timer); } - splitLetters( element ) { + splitLetters(element) { const text = element.innerHTML; element.innerHTML = ''; - text.split( '' ).forEach( letter => { + text.split('').forEach(letter => { - const i = document.createElement( 'i' ); + const i = document.createElement('i'); i.innerHTML = letter; - element.appendChild( i ); + element.appendChild(i); - } ); + }); } - flipLetters( type, letters, show ) { + flipLetters(type, letters, show) { - try { this.tweens[ type ].forEach( tween => tween.stop() ); } catch(e) {} - letters.forEach( ( letter, index ) => { + try { this.tweens[type].forEach(tween => tween.stop()); } catch (e) { } + letters.forEach((letter, index) => { letter.style.opacity = show ? 0 : 1; - this.tweens[ type ][ index ] = new Tween( { + this.tweens[type][index] = new Tween({ easing: Easing.Sine.Out(), duration: show ? 800 : 400, delay: index * 50, onUpdate: tween => { - const rotation = show ? ( 1 - tween.value ) * -80 : tween.value * 80; + const rotation = show ? (1 - tween.value) * -80 : tween.value * 80; letter.style.transform = `rotate3d(0, 1, 0, ${rotation}deg)`; - letter.style.opacity = show ? tween.value : ( 1 - tween.value ); + letter.style.opacity = show ? tween.value : (1 - tween.value); }, - } ); + }); - } ); + }); - this.durations[ type ] = ( letters.length - 1 ) * 50 + ( show ? 800 : 400 ); + this.durations[type] = (letters.length - 1) * 50 + (show ? 800 : 400); } @@ -2355,18 +2382,18 @@ class Transition { class Timer extends Animation { - constructor( game ) { + constructor(game) { - super( false ); + super(false); this.game = game; this.reset(); - + } - start( continueGame ) { + start(continueGame) { - this.startTime = continueGame ? ( Date.now() - this.deltaTime ) : Date.now(); + this.startTime = continueGame ? (Date.now() - this.deltaTime) : Date.now(); this.deltaTime = 0; this.converted = this.convert(); @@ -2403,9 +2430,9 @@ class Timer extends Animation { this.deltaTime = this.currentTime - this.startTime; this.convert(); - if ( this.converted != old ) { + if (this.converted != old) { - localStorage.setItem( 'theCube_time', this.deltaTime ); + localStorage.setItem('theCube_time', this.deltaTime); this.setText(); } @@ -2414,10 +2441,10 @@ class Timer extends Animation { convert() { - const seconds = parseInt( ( this.deltaTime / 1000 ) % 60 ); - const minutes = parseInt( ( this.deltaTime / ( 1000 * 60 ) ) ); + const seconds = parseInt((this.deltaTime / 1000) % 60); + const minutes = parseInt((this.deltaTime / (1000 * 60))); - this.converted = minutes + ':' + ( seconds < 10 ? '0' : '' ) + seconds; + this.converted = minutes + ':' + (seconds < 10 ? '0' : '') + seconds; } @@ -2432,67 +2459,67 @@ class Timer extends Animation { const RangeHTML = [ '
', - '
', - '
', - '
', - '
', - '
', - '
', + '
', + '
', + '
', + '
', + '
', + '
', '
', -].join( '\n' ); +].join('\n'); -document.querySelectorAll( 'range' ).forEach( el => { +document.querySelectorAll('range').forEach(el => { - const temp = document.createElement( 'div' ); + const temp = document.createElement('div'); temp.innerHTML = RangeHTML; - const range = temp.querySelector( '.range' ); - const rangeLabel = range.querySelector( '.range__label' ); - const rangeList = range.querySelector( '.range__list' ); + const range = temp.querySelector('.range'); + const rangeLabel = range.querySelector('.range__label'); + const rangeList = range.querySelector('.range__list'); - range.setAttribute( 'name', el.getAttribute( 'name' ) ); - rangeLabel.innerHTML = el.getAttribute( 'title' ); + range.setAttribute('name', el.getAttribute('name')); + rangeLabel.innerHTML = el.getAttribute('title'); - if ( el.hasAttribute( 'color' ) ) { + if (el.hasAttribute('color')) { - range.classList.add( 'range--type-color' ); - range.classList.add( 'range--color-' + el.getAttribute( 'name' ) ); + range.classList.add('range--type-color'); + range.classList.add('range--color-' + el.getAttribute('name')); } - if ( el.hasAttribute( 'list' ) ) { + if (el.hasAttribute('list')) { - el.getAttribute( 'list' ).split( ',' ).forEach( listItemText => { + el.getAttribute('list').split(',').forEach(listItemText => { - const listItem = document.createElement( 'div' ); + const listItem = document.createElement('div'); listItem.innerHTML = listItemText; - rangeList.appendChild( listItem ); + rangeList.appendChild(listItem); - } ); + }); } - el.parentNode.replaceChild( range, el ); + el.parentNode.replaceChild(range, el); -} ); +}); class Range { - constructor( name, options ) { + constructor(name, options) { - options = Object.assign( { - range: [ 0, 1 ], + options = Object.assign({ + range: [0, 1], value: 0, step: 0, - onUpdate: () => {}, - onComplete: () => {}, - }, options || {} ); + onUpdate: () => { }, + onComplete: () => { }, + }, options || {}); - this.element = document.querySelector( '.range[name="' + name + '"]' ); - this.track = this.element.querySelector( '.range__track' ); - this.handle = this.element.querySelector( '.range__handle' ); - this.list = [].slice.call( this.element.querySelectorAll( '.range__list div' ) ); + this.element = document.querySelector('.range[name="' + name + '"]'); + this.track = this.element.querySelector('.range__track'); + this.handle = this.element.querySelector('.range__handle'); + this.list = [].slice.call(this.element.querySelectorAll('.range__list div')); this.value = options.value; this.min = options.range[0]; @@ -2502,15 +2529,15 @@ class Range { this.onUpdate = options.onUpdate; this.onComplete = options.onComplete; - this.setValue( this.value ); + this.setValue(this.value); this.initDraggable(); } - setValue( value ) { + setValue(value) { - this.value = this.round( this.limitValue( value ) ); + this.value = this.round(this.limitValue(value)); this.setHandlePosition(); } @@ -2519,77 +2546,77 @@ class Range { let current; - this.draggable = new Draggable( this.handle, { calcDelta: true } ); + this.draggable = new Draggable(this.handle, { calcDelta: true }); this.draggable.onDragStart = position => { - current = this.positionFromValue( this.value ); + current = this.positionFromValue(this.value); this.handle.style.left = current + 'px'; }; this.draggable.onDragMove = position => { - current = this.limitPosition( current + position.delta.x ); - this.value = this.round( this.valueFromPosition( current ) ); + current = this.limitPosition(current + position.delta.x); + this.value = this.round(this.valueFromPosition(current)); this.setHandlePosition(); - - this.onUpdate( this.value ); + + this.onUpdate(this.value); }; this.draggable.onDragEnd = position => { - this.onComplete( this.value ); + this.onComplete(this.value); }; } - round( value ) { + round(value) { - if ( this.step < 1 ) return value; + if (this.step < 1) return value; - return Math.round( ( value - this.min ) / this.step ) * this.step + this.min; + return Math.round((value - this.min) / this.step) * this.step + this.min; } - limitValue( value ) { + limitValue(value) { - const max = Math.max( this.max, this.min ); - const min = Math.min( this.max, this.min ); + const max = Math.max(this.max, this.min); + const min = Math.min(this.max, this.min); - return Math.min( Math.max( value, min ), max ); + return Math.min(Math.max(value, min), max); } - limitPosition( position ) { + limitPosition(position) { - return Math.min( Math.max( position, 0 ), this.track.offsetWidth ); + return Math.min(Math.max(position, 0), this.track.offsetWidth); } - percentsFromValue( value ) { + percentsFromValue(value) { - return ( value - this.min ) / ( this.max - this.min ); + return (value - this.min) / (this.max - this.min); } - valueFromPosition( position ) { + valueFromPosition(position) { - return this.min + ( this.max - this.min ) * ( position / this.track.offsetWidth ); + return this.min + (this.max - this.min) * (position / this.track.offsetWidth); } - positionFromValue( value ) { + positionFromValue(value) { - return this.percentsFromValue( value ) * this.track.offsetWidth; + return this.percentsFromValue(value) * this.track.offsetWidth; } setHandlePosition() { - this.handle.style.left = this.percentsFromValue( this.value ) * 100 + '%'; + this.handle.style.left = this.percentsFromValue(this.value) * 100 + '%'; } @@ -2597,7 +2624,7 @@ class Range { class Preferences { - constructor( game ) { + constructor(game) { this.game = game; @@ -2607,27 +2634,27 @@ class Preferences { this.ranges = { - size: new Range( 'size', { + size: new Range('size', { value: this.game.cube.size, - range: [ 2, 5 ], + range: [2, 5], step: 1, onUpdate: value => { this.game.cube.size = value; - this.game.preferences.ranges.scramble.list.forEach( ( item, i ) => { + this.game.preferences.ranges.scramble.list.forEach((item, i) => { - item.innerHTML = this.game.scrambler.scrambleLength[ this.game.cube.size ][ i ]; + item.innerHTML = this.game.scrambler.scrambleLength[this.game.cube.size][i]; - } ); + }); }, onComplete: () => this.game.storage.savePreferences(), - } ), + }), - flip: new Range( 'flip', { + flip: new Range('flip', { value: this.game.controls.flipConfig, - range: [ 0, 2 ], + range: [0, 2], step: 1, onUpdate: value => { @@ -2635,11 +2662,11 @@ class Preferences { }, onComplete: () => this.game.storage.savePreferences(), - } ), + }), - scramble: new Range( 'scramble', { + scramble: new Range('scramble', { value: this.game.scrambler.dificulty, - range: [ 0, 2 ], + range: [0, 2], step: 1, onUpdate: value => { @@ -2647,11 +2674,11 @@ class Preferences { }, onComplete: () => this.game.storage.savePreferences() - } ), + }), - fov: new Range( 'fov', { + fov: new Range('fov', { value: this.game.world.fov, - range: [ 2, 45 ], + range: [2, 45], onUpdate: value => { this.game.world.fov = value; @@ -2659,57 +2686,57 @@ class Preferences { }, onComplete: () => this.game.storage.savePreferences() - } ), + }), - theme: new Range( 'theme', { - value: { cube: 0, erno: 1, dust: 2, camo: 3, rain: 4 }[ this.game.themes.theme ], - range: [ 0, 4 ], + theme: new Range('theme', { + value: { cube: 0, erno: 1, dust: 2, camo: 3, rain: 4 }[this.game.themes.theme], + range: [0, 4], step: 1, onUpdate: value => { - const theme = [ 'cube', 'erno', 'dust', 'camo', 'rain' ][ value ]; - this.game.themes.setTheme( theme ); + const theme = ['cube', 'erno', 'dust', 'camo', 'rain'][value]; + this.game.themes.setTheme(theme); }, onComplete: () => this.game.storage.savePreferences() - } ), + }), - hue: new Range( 'hue', { + hue: new Range('hue', { value: 0, - range: [ 0, 360 ], + range: [0, 360], onUpdate: value => this.game.themeEditor.updateHSL(), onComplete: () => this.game.storage.savePreferences(), - } ), + }), - saturation: new Range( 'saturation', { + saturation: new Range('saturation', { value: 100, - range: [ 0, 100 ], + range: [0, 100], onUpdate: value => this.game.themeEditor.updateHSL(), onComplete: () => this.game.storage.savePreferences(), - } ), + }), - lightness: new Range( 'lightness', { + lightness: new Range('lightness', { value: 50, - range: [ 0, 100 ], + range: [0, 100], onUpdate: value => this.game.themeEditor.updateHSL(), onComplete: () => this.game.storage.savePreferences(), - } ), + }), }; - this.ranges.scramble.list.forEach( ( item, i ) => { + this.ranges.scramble.list.forEach((item, i) => { - item.innerHTML = this.game.scrambler.scrambleLength[ this.game.cube.size ][ i ]; + item.innerHTML = this.game.scrambler.scrambleLength[this.game.cube.size][i]; + + }); - } ); - } } class Confetti { - constructor( game ) { + constructor(game) { this.game = game; this.started = 0; @@ -2718,61 +2745,61 @@ class Confetti { speed: { min: 0.0011, max: 0.0022 }, revolution: { min: 0.01, max: 0.05 }, size: { min: 0.1, max: 0.15 }, - colors: [ 0x41aac8, 0x82ca38, 0xffef48, 0xef3923, 0xff8c0a ], + colors: [0x41aac8, 0x82ca38, 0xffef48, 0xef3923, 0xff8c0a], }; - this.geometry = new THREE.PlaneGeometry( 1, 1 ); - this.material = new THREE.MeshLambertMaterial( { side: THREE.DoubleSide } ); + this.geometry = new THREE.PlaneGeometry(1, 1); + this.material = new THREE.MeshLambertMaterial({ side: THREE.DoubleSide }); this.holders = [ - new ConfettiStage( this.game, this, 1, 20 ), - new ConfettiStage( this.game, this, -1, 30 ), + new ConfettiStage(this.game, this, 1, 20), + new ConfettiStage(this.game, this, -1, 30), ]; } start() { - if ( this.started > 0 ) return; + if (this.started > 0) return; - this.holders.forEach( holder => { + this.holders.forEach(holder => { - this.game.world.scene.add( holder.holder ); + this.game.world.scene.add(holder.holder); holder.start(); - this.started ++; + this.started++; - } ); + }); } stop() { - if ( this.started == 0 ) return; + if (this.started == 0) return; - this.holders.forEach( holder => { + this.holders.forEach(holder => { - holder.stop( () => { + holder.stop(() => { - this.game.world.scene.remove( holder.holder ); - this.started --; + this.game.world.scene.remove(holder.holder); + this.started--; - } ); + }); - } ); + }); } - updateColors( colors ) { + updateColors(colors) { - this.holders.forEach( holder => { + this.holders.forEach(holder => { - holder.options.colors.forEach( ( color, index ) => { + holder.options.colors.forEach((color, index) => { - holder.options.colors[ index ] = colors[ [ 'D', 'F', 'R', 'B', 'L' ][ index ] ]; + holder.options.colors[index] = colors[['D', 'F', 'R', 'B', 'L'][index]]; - } ); + }); - } ); + }); } @@ -2780,9 +2807,9 @@ class Confetti { class ConfettiStage extends Animation { - constructor( game, parent, distance, count ) { + constructor(game, parent, distance, count) { - super( false ); + super(false); this.game = game; this.parent = parent; @@ -2793,14 +2820,14 @@ class ConfettiStage extends Animation { this.particles = []; this.holder = new THREE.Object3D(); - this.holder.rotation.copy( this.game.world.camera.rotation ); + this.holder.rotation.copy(this.game.world.camera.rotation); this.object = new THREE.Object3D(); - this.holder.add( this.object ); + this.holder.add(this.object); - this.resizeViewport = this.resizeViewport.bind( this ); - this.game.world.onResize.push( this.resizeViewport ); - this.resizeViewport(); + this.resizeViewport = this.resizeViewport.bind(this); + this.game.world.onResize.push(this.resizeViewport); + this.resizeViewport(); this.geometry = this.parent.geometry; this.material = this.parent.material; @@ -2808,7 +2835,7 @@ class ConfettiStage extends Animation { this.options = this.parent.options; let i = this.count; - while ( i-- ) this.particles.push( new Particle( this ) ); + while (i--) this.particles.push(new Particle(this)); } @@ -2818,13 +2845,13 @@ class ConfettiStage extends Animation { this.playing = true; let i = this.count; - while ( i-- ) this.particles[ i ].reset(); + while (i--) this.particles[i].reset(); super.start(); } - stop( callback ) { + stop(callback) { this.playing = false; this.completed = 0; @@ -2848,10 +2875,10 @@ class ConfettiStage extends Animation { let i = this.count; - while ( i-- ) - if ( ! this.particles[ i ].completed ) this.particles[ i ].update( delta ); + while (i--) + if (!this.particles[i].completed) this.particles[i].update(delta); - if ( ! this.playing && this.completed == this.count ) this.reset(); + if (!this.playing && this.completed == this.count) this.reset(); } @@ -2859,7 +2886,7 @@ class ConfettiStage extends Animation { const fovRad = this.game.world.camera.fov * THREE.Math.DEG2RAD; - this.height = 2 * Math.tan( fovRad / 2 ) * ( this.game.world.camera.position.length() - this.distanceFromCube ); + this.height = 2 * Math.tan(fovRad / 2) * (this.game.world.camera.position.length() - this.distanceFromCube); this.width = this.height * this.game.world.camera.aspect; const scale = 1 / this.game.transition.data.cameraZoom; @@ -2871,12 +2898,12 @@ class ConfettiStage extends Animation { this.object.position.y = this.height / 2; } - + } class Particle { - constructor( confetti ) { + constructor(confetti) { this.confetti = confetti; this.options = this.confetti.options; @@ -2884,49 +2911,49 @@ class Particle { this.velocity = new THREE.Vector3(); this.force = new THREE.Vector3(); - this.mesh = new THREE.Mesh( this.confetti.geometry, this.confetti.material.clone() ); - this.confetti.object.add( this.mesh ); + this.mesh = new THREE.Mesh(this.confetti.geometry, this.confetti.material.clone()); + this.confetti.object.add(this.mesh); - this.size = THREE.Math.randFloat( this.options.size.min, this.options.size.max ); - this.mesh.scale.set( this.size, this.size, this.size ); + this.size = THREE.Math.randFloat(this.options.size.min, this.options.size.max); + this.mesh.scale.set(this.size, this.size, this.size); return this; } - reset( randomHeight = true ) { + reset(randomHeight = true) { this.completed = false; - this.color = new THREE.Color( this.options.colors[ Math.floor( Math.random() * this.options.colors.length ) ] ); - this.mesh.material.color.set( this.color ); + this.color = new THREE.Color(this.options.colors[Math.floor(Math.random() * this.options.colors.length)]); + this.mesh.material.color.set(this.color); - this.speed = THREE.Math.randFloat( this.options.speed.min, this.options.speed.max ) * - 1; - this.mesh.position.x = THREE.Math.randFloat( - this.confetti.width / 2, this.confetti.width / 2 ); - this.mesh.position.y = ( randomHeight ) - ? THREE.Math.randFloat( this.size, this.confetti.height + this.size ) + this.speed = THREE.Math.randFloat(this.options.speed.min, this.options.speed.max) * - 1; + this.mesh.position.x = THREE.Math.randFloat(- this.confetti.width / 2, this.confetti.width / 2); + this.mesh.position.y = (randomHeight) + ? THREE.Math.randFloat(this.size, this.confetti.height + this.size) : this.size; - this.revolutionSpeed = THREE.Math.randFloat( this.options.revolution.min, this.options.revolution.max ); - this.revolutionAxis = [ 'x', 'y', 'z' ][ Math.floor( Math.random() * 3 ) ]; - this.mesh.rotation.set( Math.random() * Math.PI / 3, Math.random() * Math.PI / 3, Math.random() * Math.PI / 3 ); + this.revolutionSpeed = THREE.Math.randFloat(this.options.revolution.min, this.options.revolution.max); + this.revolutionAxis = ['x', 'y', 'z'][Math.floor(Math.random() * 3)]; + this.mesh.rotation.set(Math.random() * Math.PI / 3, Math.random() * Math.PI / 3, Math.random() * Math.PI / 3); } stop() { this.completed = true; - this.confetti.completed ++; + this.confetti.completed++; } - update( delta ) { + update(delta) { this.mesh.position.y += this.speed * delta; - this.mesh.rotation[ this.revolutionAxis ] += this.revolutionSpeed; + this.mesh.rotation[this.revolutionAxis] += this.revolutionSpeed; - if ( this.mesh.position.y < - this.confetti.height - this.size ) - ( this.confetti.playing ) ? this.reset( false ) : this.stop(); + if (this.mesh.position.y < - this.confetti.height - this.size) + (this.confetti.playing) ? this.reset(false) : this.stop(); } @@ -2934,7 +2961,7 @@ class Particle { class Scores { - constructor( game ) { + constructor(game) { this.game = game; @@ -2967,25 +2994,25 @@ class Scores { } - addScore( time ) { + addScore(time) { - const data = this.data[ this.game.cube.sizeGenerated ]; + const data = this.data[this.game.cube.sizeGenerated]; - data.scores.push( time ); + data.scores.push(time); data.solves++; - if ( data.scores.lenght > 100 ) data.scores.shift(); + if (data.scores.lenght > 100) data.scores.shift(); - let bestTime = false; + let bestTime = false; - if ( time < data.best || data.best === 0 ) { + if (time < data.best || data.best === 0) { data.best = time; bestTime = true; } - if ( time > data.worst ) data.worst = time; + if (time > data.worst) data.worst = time; this.game.storage.saveScores(); @@ -2996,44 +3023,44 @@ class Scores { calcStats() { const s = this.game.cube.sizeGenerated; - const data = this.data[ s ]; + const data = this.data[s]; - this.setStat( 'cube-size', `${s}x${s}x${s}` ); - this.setStat( 'total-solves', data.solves ); - this.setStat( 'best-time', this.convertTime( data.best ) ); - this.setStat( 'worst-time', this.convertTime( data.worst ) ); - this.setStat( 'average-5', this.getAverage( 5 ) ); - this.setStat( 'average-12', this.getAverage( 12 ) ); - this.setStat( 'average-25', this.getAverage( 25 ) ); + this.setStat('cube-size', `${s}x${s}x${s}`); + this.setStat('total-solves', data.solves); + this.setStat('best-time', this.convertTime(data.best)); + this.setStat('worst-time', this.convertTime(data.worst)); + this.setStat('average-5', this.getAverage(5)); + this.setStat('average-12', this.getAverage(12)); + this.setStat('average-25', this.getAverage(25)); } - setStat( name, value ) { + setStat(name, value) { - if ( value === 0 ) value = '-'; + if (value === 0) value = '-'; - this.game.dom.stats.querySelector( `.stats[name="${name}"] b` ).innerHTML = value; + this.game.dom.stats.querySelector(`.stats[name="${name}"] b`).innerHTML = value; } - getAverage( count ) { + getAverage(count) { - const data = this.data[ this.game.cube.sizeGenerated ]; + const data = this.data[this.game.cube.sizeGenerated]; - if ( data.scores.length < count ) return 0; + if (data.scores.length < count) return 0; - return this.convertTime( data.scores.slice( -count ).reduce( ( a, b ) => a + b, 0 ) / count ); + return this.convertTime(data.scores.slice(-count).reduce((a, b) => a + b, 0) / count); } - convertTime( time ) { + convertTime(time) { - if ( time <= 0 ) return 0; + if (time <= 0) return 0; - const seconds = parseInt( ( time / 1000 ) % 60 ); - const minutes = parseInt( ( time / ( 1000 * 60 ) ) ); + const seconds = parseInt((time / 1000) % 60); + const minutes = parseInt((time / (1000 * 60))); - return minutes + ':' + ( seconds < 10 ? '0' : '' ) + seconds; + return minutes + ':' + (seconds < 10 ? '0' : '') + seconds; } @@ -3041,18 +3068,18 @@ class Scores { class Storage { - constructor( game ) { + constructor(game) { this.game = game; - const userVersion = localStorage.getItem( 'theCube_version' ); + const userVersion = localStorage.getItem('theCube_version'); - if ( ! userVersion || userVersion !== window.gameVersion ) { + if (!userVersion || userVersion !== window.gameVersion) { this.clearGame(); this.clearPreferences(); this.migrateScores(); - localStorage.setItem( 'theCube_version', window.gameVersion ); + localStorage.setItem('theCube_version', window.gameVersion); } @@ -3069,23 +3096,23 @@ class Storage { try { - const gameInProgress = localStorage.getItem( 'theCube_playing' ) === 'true'; + const gameInProgress = localStorage.getItem('theCube_playing') === 'true'; - if ( ! gameInProgress ) throw new Error(); + if (!gameInProgress) throw new Error(); - const gameCubeData = JSON.parse( localStorage.getItem( 'theCube_savedState' ) ); - const gameTime = parseInt( localStorage.getItem( 'theCube_time' ) ); + const gameCubeData = JSON.parse(localStorage.getItem('theCube_savedState')); + const gameTime = parseInt(localStorage.getItem('theCube_time')); - if ( ! gameCubeData || gameTime === null ) throw new Error(); - if ( gameCubeData.size !== this.game.cube.sizeGenerated ) throw new Error(); + if (!gameCubeData || gameTime === null) throw new Error(); + if (gameCubeData.size !== this.game.cube.sizeGenerated) throw new Error(); - this.game.cube.loadFromData( gameCubeData ); + this.game.cube.loadFromData(gameCubeData); this.game.timer.deltaTime = gameTime; this.game.saved = true; - } catch( e ) { + } catch (e) { this.game.saved = false; @@ -3101,25 +3128,25 @@ class Storage { gameCubeData.size = this.game.cube.sizeGenerated; - this.game.cube.pieces.forEach( piece => { + this.game.cube.pieces.forEach(piece => { - gameCubeData.names.push( piece.name ); - gameCubeData.positions.push( piece.position ); - gameCubeData.rotations.push( piece.rotation.toVector3() ); + gameCubeData.names.push(piece.name); + gameCubeData.positions.push(piece.position); + gameCubeData.rotations.push(piece.rotation.toVector3()); - } ); + }); - localStorage.setItem( 'theCube_playing', gameInProgress ); - localStorage.setItem( 'theCube_savedState', JSON.stringify( gameCubeData ) ); - localStorage.setItem( 'theCube_time', gameTime ); + localStorage.setItem('theCube_playing', gameInProgress); + localStorage.setItem('theCube_savedState', JSON.stringify(gameCubeData)); + localStorage.setItem('theCube_time', gameTime); } clearGame() { - localStorage.removeItem( 'theCube_playing' ); - localStorage.removeItem( 'theCube_savedState' ); - localStorage.removeItem( 'theCube_time' ); + localStorage.removeItem('theCube_playing'); + localStorage.removeItem('theCube_savedState'); + localStorage.removeItem('theCube_time'); } @@ -3127,13 +3154,13 @@ class Storage { try { - const scoresData = JSON.parse( localStorage.getItem( 'theCube_scores' ) ); + const scoresData = JSON.parse(localStorage.getItem('theCube_scores')); - if ( ! scoresData ) throw new Error(); + if (!scoresData) throw new Error(); this.game.scores.data = scoresData; - } catch( e ) {} + } catch (e) { } } @@ -3141,13 +3168,13 @@ class Storage { const scoresData = this.game.scores.data; - localStorage.setItem( 'theCube_scores', JSON.stringify( scoresData ) ); + localStorage.setItem('theCube_scores', JSON.stringify(scoresData)); } clearScores() { - localStorage.removeItem( 'theCube_scores' ); + localStorage.removeItem('theCube_scores'); } @@ -3155,24 +3182,24 @@ class Storage { try { - const scoresData = JSON.parse( localStorage.getItem( 'theCube_scoresData' ) ); - const scoresBest = parseInt( localStorage.getItem( 'theCube_scoresBest' ) ); - const scoresWorst = parseInt( localStorage.getItem( 'theCube_scoresWorst' ) ); - const scoresSolves = parseInt( localStorage.getItem( 'theCube_scoresSolves' ) ); + const scoresData = JSON.parse(localStorage.getItem('theCube_scoresData')); + const scoresBest = parseInt(localStorage.getItem('theCube_scoresBest')); + const scoresWorst = parseInt(localStorage.getItem('theCube_scoresWorst')); + const scoresSolves = parseInt(localStorage.getItem('theCube_scoresSolves')); - if ( ! scoresData || ! scoresBest || ! scoresSolves || ! scoresWorst ) return false; + if (!scoresData || !scoresBest || !scoresSolves || !scoresWorst) return false; - this.game.scores.data[ 3 ].scores = scoresData; - this.game.scores.data[ 3 ].best = scoresBest; - this.game.scores.data[ 3 ].solves = scoresSolves; - this.game.scores.data[ 3 ].worst = scoresWorst; + this.game.scores.data[3].scores = scoresData; + this.game.scores.data[3].best = scoresBest; + this.game.scores.data[3].solves = scoresSolves; + this.game.scores.data[3].worst = scoresWorst; - localStorage.removeItem( 'theCube_scoresData' ); - localStorage.removeItem( 'theCube_scoresBest' ); - localStorage.removeItem( 'theCube_scoresWorst' ); - localStorage.removeItem( 'theCube_scoresSolves' ); + localStorage.removeItem('theCube_scoresData'); + localStorage.removeItem('theCube_scoresBest'); + localStorage.removeItem('theCube_scoresWorst'); + localStorage.removeItem('theCube_scoresSolves'); - } catch( e ) {} + } catch (e) { } } @@ -3180,19 +3207,19 @@ class Storage { try { - const preferences = JSON.parse( localStorage.getItem( 'theCube_preferences' ) ); + const preferences = JSON.parse(localStorage.getItem('theCube_preferences')); - if ( ! preferences ) throw new Error(); + if (!preferences) throw new Error(); - this.game.cube.size = parseInt( preferences.cubeSize ); - this.game.controls.flipConfig = parseInt( preferences.flipConfig ); - this.game.scrambler.dificulty = parseInt( preferences.dificulty ); + this.game.cube.size = parseInt(preferences.cubeSize); + this.game.controls.flipConfig = parseInt(preferences.flipConfig); + this.game.scrambler.dificulty = parseInt(preferences.dificulty); - this.game.world.fov = parseFloat( preferences.fov ); + this.game.world.fov = parseFloat(preferences.fov); this.game.world.resize(); this.game.themes.colors = preferences.colors; - this.game.themes.setTheme( preferences.theme ); + this.game.themes.setTheme(preferences.theme); return true; @@ -3205,7 +3232,7 @@ class Storage { this.game.world.fov = 10; this.game.world.resize(); - this.game.themes.setTheme( 'cube' ); + this.game.themes.setTheme('cube'); this.savePreferences(); @@ -3226,13 +3253,13 @@ class Storage { colors: this.game.themes.colors, }; - localStorage.setItem( 'theCube_preferences', JSON.stringify( preferences ) ); + localStorage.setItem('theCube_preferences', JSON.stringify(preferences)); } clearPreferences() { - localStorage.removeItem( 'theCube_preferences' ); + localStorage.removeItem('theCube_preferences'); } @@ -3240,7 +3267,7 @@ class Storage { class Themes { - constructor( game ) { + constructor(game) { this.game = game; this.theme = null; @@ -3298,32 +3325,32 @@ class Themes { }, }; - this.colors = JSON.parse( JSON.stringify( this.defaults ) ); + this.colors = JSON.parse(JSON.stringify(this.defaults)); } getColors() { - return this.colors[ this.theme ]; + return this.colors[this.theme]; } - setTheme( theme = false, force = false ) { + setTheme(theme = false, force = false) { - if ( theme === this.theme && force === false ) return; - if ( theme !== false ) this.theme = theme; + if (theme === this.theme && force === false) return; + if (theme !== false) this.theme = theme; const colors = this.getColors(); - this.game.dom.prefs.querySelectorAll( '.range__handle div' ).forEach( range => { + this.game.dom.prefs.querySelectorAll('.range__handle div').forEach(range => { range.style.background = '#' + colors.R.toString(16).padStart(6, '0'); - } ); + }); - this.game.cube.updateColors( colors ); + this.game.cube.updateColors(colors); - this.game.confetti.updateColors( colors ); + this.game.confetti.updateColors(colors); this.game.dom.back.style.background = '#' + colors.G.toString(16).padStart(6, '0'); @@ -3333,67 +3360,67 @@ class Themes { class ThemeEditor { - constructor( game ) { + constructor(game) { this.game = game; this.editColor = 'R'; - this.getPieceColor = this.getPieceColor.bind( this ); + this.getPieceColor = this.getPieceColor.bind(this); } - colorFromHSL( h, s, l ) { + colorFromHSL(h, s, l) { - h = Math.round( h ); - s = Math.round( s ); - l = Math.round( l ); + h = Math.round(h); + s = Math.round(s); + l = Math.round(l); - return new THREE.Color( `hsl(${h}, ${s}%, ${l}%)` ); + return new THREE.Color(`hsl(${h}, ${s}%, ${l}%)`); } - setHSL( color = null, animate = false ) { + setHSL(color = null, animate = false) { - this.editColor = ( color === null) ? 'R' : color; + this.editColor = (color === null) ? 'R' : color; - const hsl = new THREE.Color( this.game.themes.getColors()[ this.editColor ] ); + const hsl = new THREE.Color(this.game.themes.getColors()[this.editColor]); - const { h, s, l } = hsl.getHSL( hsl ); + const { h, s, l } = hsl.getHSL(hsl); const { hue, saturation, lightness } = this.game.preferences.ranges; - if ( animate ) { + if (animate) { const ho = hue.value / 360; const so = saturation.value / 100; const lo = lightness.value / 100; - const colorOld = this.colorFromHSL( hue.value, saturation.value, lightness.value ); + const colorOld = this.colorFromHSL(hue.value, saturation.value, lightness.value); - if ( this.tweenHSL ) this.tweenHSL.stop(); + if (this.tweenHSL) this.tweenHSL.stop(); - this.tweenHSL = new Tween( { + this.tweenHSL = new Tween({ duration: 200, easing: Easing.Sine.Out(), onUpdate: tween => { - hue.setValue( ( ho + ( h - ho ) * tween.value ) * 360 ); - saturation.setValue( ( so + ( s - so ) * tween.value ) * 100 ); - lightness.setValue( ( lo + ( l - lo ) * tween.value ) * 100 ); + hue.setValue((ho + (h - ho) * tween.value) * 360); + saturation.setValue((so + (s - so) * tween.value) * 100); + lightness.setValue((lo + (l - lo) * tween.value) * 100); - const colorTween = colorOld.clone().lerp( hsl, tween.value ); + const colorTween = colorOld.clone().lerp(hsl, tween.value); const colorTweenStyle = colorTween.getStyle(); - const colorTweenHex = colorTween.getHSL( colorTween ); + const colorTweenHex = colorTween.getHSL(colorTween); hue.handle.style.color = colorTweenStyle; saturation.handle.style.color = colorTweenStyle; lightness.handle.style.color = colorTweenStyle; saturation.track.style.color = - this.colorFromHSL( colorTweenHex.h * 360, 100, 50 ).getStyle(); + this.colorFromHSL(colorTweenHex.h * 360, 100, 50).getStyle(); lightness.track.style.color = - this.colorFromHSL( colorTweenHex.h * 360, colorTweenHex.s * 100, 50 ).getStyle(); + this.colorFromHSL(colorTweenHex.h * 360, colorTweenHex.s * 100, 50).getStyle(); this.game.dom.theme.style.display = 'none'; this.game.dom.theme.offsetHeight; @@ -3406,13 +3433,13 @@ class ThemeEditor { this.game.storage.savePreferences(); }, - } ); + }); } else { - hue.setValue( h * 360 ); - saturation.setValue( s * 100 ); - lightness.setValue( l * 100 ); + hue.setValue(h * 360); + saturation.setValue(s * 100); + lightness.setValue(l * 100); this.updateHSL(); this.game.storage.savePreferences(); @@ -3429,14 +3456,14 @@ class ThemeEditor { const s = saturation.value; const l = lightness.value; - const color = this.colorFromHSL( h, s, l ).getStyle(); + const color = this.colorFromHSL(h, s, l).getStyle(); hue.handle.style.color = color; saturation.handle.style.color = color; lightness.handle.style.color = color; - saturation.track.style.color = this.colorFromHSL( h, 100, 50 ).getStyle(); - lightness.track.style.color = this.colorFromHSL( h, s, 50 ).getStyle(); + saturation.track.style.color = this.colorFromHSL(h, 100, 50).getStyle(); + lightness.track.style.color = this.colorFromHSL(h, s, 50).getStyle(); this.game.dom.theme.style.display = 'none'; this.game.dom.theme.offsetHeight; @@ -3444,64 +3471,64 @@ class ThemeEditor { const theme = this.game.themes.theme; - this.game.themes.colors[ theme ][ this.editColor ] = this.colorFromHSL( h, s, l ).getHex(); + this.game.themes.colors[theme][this.editColor] = this.colorFromHSL(h, s, l).getHex(); this.game.themes.setTheme(); } - colorPicker( enable ) { + colorPicker(enable) { - if ( enable ) { + if (enable) { - this.game.dom.game.addEventListener( 'click', this.getPieceColor, false ); + this.game.dom.game.addEventListener('click', this.getPieceColor, false); } else { - this.game.dom.game.removeEventListener( 'click', this.getPieceColor, false ); + this.game.dom.game.removeEventListener('click', this.getPieceColor, false); } } - getPieceColor( event ) { + getPieceColor(event) { const clickEvent = event.touches - ? ( event.touches[ 0 ] || event.changedTouches[ 0 ] ) + ? (event.touches[0] || event.changedTouches[0]) : event; - const clickPosition = new THREE.Vector2( clickEvent.pageX, clickEvent.pageY ); + const clickPosition = new THREE.Vector2(clickEvent.pageX, clickEvent.pageY); - let edgeIntersect = this.game.controls.getIntersect( clickPosition, this.game.cube.edges, true ); - let pieceIntersect = this.game.controls.getIntersect( clickPosition, this.game.cube.cubes, true ); + let edgeIntersect = this.game.controls.getIntersect(clickPosition, this.game.cube.edges, true); + let pieceIntersect = this.game.controls.getIntersect(clickPosition, this.game.cube.cubes, true); - if ( edgeIntersect !== false ) { + if (edgeIntersect !== false) { const edge = edgeIntersect.object; const position = edge.parent - .localToWorld( edge.position.clone() ) - .sub( this.game.cube.object.position ) - .sub( this.game.cube.animator.position ); + .localToWorld(edge.position.clone()) + .sub(this.game.cube.object.position) + .sub(this.game.cube.animator.position); - const mainAxis = this.game.controls.getMainAxis( position ); - if ( position.multiplyScalar( 2 ).round()[ mainAxis ] < 1 ) edgeIntersect = false; + const mainAxis = this.game.controls.getMainAxis(position); + if (position.multiplyScalar(2).round()[mainAxis] < 1) edgeIntersect = false; } const name = edgeIntersect ? edgeIntersect.object.name : pieceIntersect ? 'P' : 'G'; - this.setHSL( name, true ); + this.setHSL(name, true); } resetTheme() { - this.game.themes.colors[ this.game.themes.theme ] = - JSON.parse( JSON.stringify( this.game.themes.defaults[ this.game.themes.theme ] ) ); + this.game.themes.colors[this.game.themes.theme] = + JSON.parse(JSON.stringify(this.game.themes.defaults[this.game.themes.theme])); this.game.themes.setTheme(); - this.setHSL( this.editColor, true ); + this.setHSL(this.editColor, true); } @@ -3510,35 +3537,35 @@ class ThemeEditor { const States = { 3: { checkerboard: { - names: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 ], + names: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26], positions: [ - { "x": 1/3, "y": -1/3, "z": 1/3 }, - { "x": -1/3, "y": 1/3, "z": 0 }, - { "x": 1/3, "y": -1/3, "z": -1/3 }, - { "x": -1/3, "y": 0, "z": -1/3 }, - { "x": 1/3, "y": 0, "z": 0 }, - { "x": -1/3, "y": 0, "z": 1/3 }, - { "x": 1/3, "y": 1/3, "z": 1/3 }, - { "x": -1/3, "y": -1/3, "z": 0 }, - { "x": 1/3, "y": 1/3, "z": -1/3 }, - { "x": 0, "y": 1/3, "z": -1/3 }, - { "x": 0, "y": -1/3, "z": 0 }, - { "x": 0, "y": 1/3, "z": 1/3 }, - { "x": 0, "y": 0, "z": 1/3 }, + { "x": 1 / 3, "y": -1 / 3, "z": 1 / 3 }, + { "x": -1 / 3, "y": 1 / 3, "z": 0 }, + { "x": 1 / 3, "y": -1 / 3, "z": -1 / 3 }, + { "x": -1 / 3, "y": 0, "z": -1 / 3 }, + { "x": 1 / 3, "y": 0, "z": 0 }, + { "x": -1 / 3, "y": 0, "z": 1 / 3 }, + { "x": 1 / 3, "y": 1 / 3, "z": 1 / 3 }, + { "x": -1 / 3, "y": -1 / 3, "z": 0 }, + { "x": 1 / 3, "y": 1 / 3, "z": -1 / 3 }, + { "x": 0, "y": 1 / 3, "z": -1 / 3 }, + { "x": 0, "y": -1 / 3, "z": 0 }, + { "x": 0, "y": 1 / 3, "z": 1 / 3 }, + { "x": 0, "y": 0, "z": 1 / 3 }, { "x": 0, "y": 0, "z": 0 }, - { "x": 0, "y": 0, "z": -1/3 }, - { "x": 0, "y": -1/3, "z": -1/3 }, - { "x": 0, "y": 1/3, "z": 0 }, - { "x": 0, "y": -1/3, "z": 1/3 }, - { "x": -1/3, "y": -1/3, "z": 1/3 }, - { "x": 1/3, "y": 1/3, "z": 0 }, - { "x": -1/3, "y": -1/3, "z": -1/3 }, - { "x": 1/3, "y": 0, "z": -1/3 }, - { "x": -1/3, "y": 0, "z": 0 }, - { "x": 1/3, "y": 0, "z": 1/3 }, - { "x": -1/3, "y": 1/3, "z": 1/3 }, - { "x": 1/3, "y": -1/3, "z": 0 }, - { "x": -1/3, "y": 1/3, "z": -1/3 } + { "x": 0, "y": 0, "z": -1 / 3 }, + { "x": 0, "y": -1 / 3, "z": -1 / 3 }, + { "x": 0, "y": 1 / 3, "z": 0 }, + { "x": 0, "y": -1 / 3, "z": 1 / 3 }, + { "x": -1 / 3, "y": -1 / 3, "z": 1 / 3 }, + { "x": 1 / 3, "y": 1 / 3, "z": 0 }, + { "x": -1 / 3, "y": -1 / 3, "z": -1 / 3 }, + { "x": 1 / 3, "y": 0, "z": -1 / 3 }, + { "x": -1 / 3, "y": 0, "z": 0 }, + { "x": 1 / 3, "y": 0, "z": 1 / 3 }, + { "x": -1 / 3, "y": 1 / 3, "z": 1 / 3 }, + { "x": 1 / 3, "y": -1 / 3, "z": 0 }, + { "x": -1 / 3, "y": 1 / 3, "z": -1 / 3 } ], rotations: [ { "x": -Math.PI, "y": 0, "z": Math.PI, }, @@ -3576,32 +3603,32 @@ const States = { class IconsConverter { - constructor( options ) { + constructor(options) { - options = Object.assign( { + options = Object.assign({ tagName: 'icon', className: 'icon', styles: false, icons: {}, observe: false, convert: false, - }, options || {} ); + }, options || {}); this.tagName = options.tagName; this.className = options.className; this.icons = options.icons; - this.svgTag = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ); - this.svgTag.setAttribute( 'class', this.className ); + this.svgTag = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this.svgTag.setAttribute('class', this.className); - if ( options.styles ) this.addStyles(); - if ( options.convert ) this.convertAllIcons(); + if (options.styles) this.addStyles(); + if (options.convert) this.convertAllIcons(); - if ( options.observe ) { + if (options.observe) { const MutationObserver = window.MutationObserver || window.WebKitMutationObserver; - this.observer = new MutationObserver( mutations => { this.convertAllIcons(); } ); - this.observer.observe( document.documentElement, { childList: true, subtree: true } ); + this.observer = new MutationObserver(mutations => { this.convertAllIcons(); }); + this.observer.observe(document.documentElement, { childList: true, subtree: true }); } @@ -3611,39 +3638,39 @@ class IconsConverter { convertAllIcons() { - document.querySelectorAll( this.tagName ).forEach( icon => { this.convertIcon( icon ); } ); + document.querySelectorAll(this.tagName).forEach(icon => { this.convertIcon(icon); }); } - convertIcon( icon ) { + convertIcon(icon) { - const svgData = this.icons[ icon.attributes[0].localName ]; + const svgData = this.icons[icon.attributes[0].localName]; - if ( typeof svgData === 'undefined' ) return; + if (typeof svgData === 'undefined') return; - const svg = this.svgTag.cloneNode( true ); - const viewBox = svgData.viewbox.split( ' ' ); + const svg = this.svgTag.cloneNode(true); + const viewBox = svgData.viewbox.split(' '); - svg.setAttributeNS( null, 'viewBox', svgData.viewbox ); + svg.setAttributeNS(null, 'viewBox', svgData.viewbox); svg.style.width = viewBox[2] / viewBox[3] + 'em'; svg.style.height = '1em'; svg.innerHTML = svgData.content; - icon.parentNode.replaceChild( svg, icon ); + icon.parentNode.replaceChild(svg, icon); } addStyles() { - const style = document.createElement( 'style' ); + const style = document.createElement('style'); style.innerHTML = `.${this.className} { display: inline-block; font-size: inherit; overflow: visible; vertical-align: -0.125em; preserveAspectRatio: none; }`; - document.head.appendChild( style ); + document.head.appendChild(style); } } -const Icons = new IconsConverter( { +const Icons = new IconsConverter({ icons: { settings: { @@ -3678,7 +3705,7 @@ const Icons = new IconsConverter( { convert: true, -} ); +}); const STATE = { Menu: 0, @@ -3690,12 +3717,12 @@ const STATE = { }; const BUTTONS = { - Menu: [ 'stats', 'prefs' ], - Playing: [ 'back' ], + Menu: ['stats', 'prefs'], + Playing: ['back'], Complete: [], - Stats: [ 'back' ], - Prefs: [ 'back', 'theme' ], - Theme: [ 'back', 'reset' ], + Stats: ['back'], + Prefs: ['back', 'theme'], + Theme: ['back', 'reset'], None: [], }; @@ -3706,42 +3733,43 @@ class Game { constructor() { + this.audio = document.getElementById('background-music'); this.dom = { - ui: document.querySelector( '.ui' ), - game: document.querySelector( '.ui__game' ), - back: document.querySelector( '.ui__background' ), - prefs: document.querySelector( '.ui__prefs' ), - theme: document.querySelector( '.ui__theme' ), - stats: document.querySelector( '.ui__stats' ), + ui: document.querySelector('.ui'), + game: document.querySelector('.ui__game'), + back: document.querySelector('.ui__background'), + prefs: document.querySelector('.ui__prefs'), + theme: document.querySelector('.ui__theme'), + stats: document.querySelector('.ui__stats'), texts: { - title: document.querySelector( '.text--title' ), - note: document.querySelector( '.text--note' ), - timer: document.querySelector( '.text--timer' ), - complete: document.querySelector( '.text--complete' ), - best: document.querySelector( '.text--best-time' ), - theme: document.querySelector( '.text--theme' ), + title: document.querySelector('.text--title'), + note: document.querySelector('.text--note'), + timer: document.querySelector('.text--timer'), + complete: document.querySelector('.text--complete'), + best: document.querySelector('.text--best-time'), + theme: document.querySelector('.text--theme'), }, buttons: { - prefs: document.querySelector( '.btn--prefs' ), - back: document.querySelector( '.btn--back' ), - stats: document.querySelector( '.btn--stats' ), - reset: document.querySelector( '.btn--reset' ), - theme: document.querySelector( '.btn--theme' ), + prefs: document.querySelector('.btn--prefs'), + back: document.querySelector('.btn--back'), + stats: document.querySelector('.btn--stats'), + reset: document.querySelector('.btn--reset'), + theme: document.querySelector('.btn--theme'), }, }; - this.world = new World( this ); - this.cube = new Cube( this ); - this.controls = new Controls( this ); - this.scrambler = new Scrambler( this ); - this.transition = new Transition( this ); - this.timer = new Timer( this ); - this.preferences = new Preferences( this ); - this.scores = new Scores( this ); - this.storage = new Storage( this ); - this.confetti = new Confetti( this ); - this.themes = new Themes( this ); - this.themeEditor = new ThemeEditor( this ); + this.world = new World(this); + this.cube = new Cube(this); + this.controls = new Controls(this); + this.scrambler = new Scrambler(this); + this.transition = new Transition(this); + this.timer = new Timer(this); + this.preferences = new Preferences(this); + this.scores = new Scores(this); + this.storage = new Storage(this); + this.confetti = new Confetti(this); + this.themes = new Themes(this); + this.themeEditor = new ThemeEditor(this); this.initActions(); @@ -3757,15 +3785,15 @@ class Game { this.storage.loadGame(); this.scores.calcStats(); - setTimeout( () => { + setTimeout(() => { this.transition.float(); - this.transition.cube( SHOW ); + this.transition.cube(SHOW); - setTimeout( () => this.transition.title( SHOW ), 700 ); - setTimeout( () => this.transition.buttons( BUTTONS.Menu, BUTTONS.None ), 1000 ); + setTimeout(() => this.transition.title(SHOW), 700); + setTimeout(() => this.transition.buttons(BUTTONS.Menu, BUTTONS.None), 1000); - }, 500 ); + }, 500); } @@ -3773,26 +3801,26 @@ class Game { let tappedTwice = false; - this.dom.game.addEventListener( 'click', event => { + this.dom.game.addEventListener('click', event => { - if ( this.transition.activeTransitions > 0 ) return; - if ( this.state === STATE.Playing ) return; + if (this.transition.activeTransitions > 0) return; + if (this.state === STATE.Playing) return; - if ( this.state === STATE.Menu ) { + if (this.state === STATE.Menu) { - if ( ! tappedTwice ) { + if (!tappedTwice) { tappedTwice = true; - setTimeout( () => tappedTwice = false, 300 ); + setTimeout(() => tappedTwice = false, 300); return false; } - this.game( SHOW ); + this.game(SHOW); - } else if ( this.state === STATE.Complete ) { + } else if (this.state === STATE.Complete) { - this.complete( HIDE ); + this.complete(HIDE); } // else if ( this.state === STATE.Stats ) { @@ -3801,13 +3829,13 @@ class Game { // } - }, false ); + }, false); this.controls.onMove = () => { - if ( this.newGame ) { - - this.timer.start( true ); + if (this.newGame) { + + this.timer.start(true); this.newGame = false; } @@ -3816,23 +3844,23 @@ class Game { this.dom.buttons.back.onclick = event => { - if ( this.transition.activeTransitions > 0 ) return; + if (this.transition.activeTransitions > 0) return; - if ( this.state === STATE.Playing ) { + if (this.state === STATE.Playing) { - this.game( HIDE ); + this.game(HIDE); - } else if ( this.state === STATE.Prefs ) { + } else if (this.state === STATE.Prefs) { - this.prefs( HIDE ); + this.prefs(HIDE); - } else if ( this.state === STATE.Stats ) { + } else if (this.state === STATE.Stats) { - this.stats( HIDE ); + this.stats(HIDE); - } else if ( this.state === STATE.Theme ) { + } else if (this.state === STATE.Theme) { - this.theme( HIDE ); + this.theme(HIDE); } @@ -3840,29 +3868,29 @@ class Game { this.dom.buttons.reset.onclick = event => { - if ( this.state === STATE.Theme ) { + if (this.state === STATE.Theme) { this.themeEditor.resetTheme(); } - + }; - this.dom.buttons.prefs.onclick = event => this.prefs( SHOW ); + this.dom.buttons.prefs.onclick = event => this.prefs(SHOW); - this.dom.buttons.theme.onclick = event => this.theme( SHOW ); + this.dom.buttons.theme.onclick = event => this.theme(SHOW); - this.dom.buttons.stats.onclick = event => this.stats( SHOW ); + this.dom.buttons.stats.onclick = event => this.stats(SHOW); - this.controls.onSolved = () => this.complete( SHOW ); + this.controls.onSolved = () => this.complete(SHOW); } - game( show ) { + game(show) { - if ( show ) { + if (show) { - if ( ! this.saved ) { + if (!this.saved) { this.scrambler.scramble(); this.controls.scrambleCube(); @@ -3871,43 +3899,43 @@ class Game { } const duration = this.saved ? 0 : - this.scrambler.converted.length * ( this.controls.flipSpeeds[0] + 10 ); + this.scrambler.converted.length * (this.controls.flipSpeeds[0] + 10); this.state = STATE.Playing; this.saved = true; - this.transition.buttons( BUTTONS.None, BUTTONS.Menu ); + this.transition.buttons(BUTTONS.None, BUTTONS.Menu); - this.transition.zoom( STATE.Playing, duration ); - this.transition.title( HIDE ); + this.transition.zoom(STATE.Playing, duration); + this.transition.title(HIDE); - setTimeout( () => { + setTimeout(() => { - this.transition.timer( SHOW ); - this.transition.buttons( BUTTONS.Playing, BUTTONS.None ); + this.transition.timer(SHOW); + this.transition.buttons(BUTTONS.Playing, BUTTONS.None); - }, this.transition.durations.zoom - 1000 ); + }, this.transition.durations.zoom - 1000); - setTimeout( () => { + setTimeout(() => { this.controls.enable(); - if ( ! this.newGame ) this.timer.start( true ); + if (!this.newGame) this.timer.start(true); - }, this.transition.durations.zoom ); + }, this.transition.durations.zoom); } else { this.state = STATE.Menu; - this.transition.buttons( BUTTONS.Menu, BUTTONS.Playing ); + this.transition.buttons(BUTTONS.Menu, BUTTONS.Playing); - this.transition.zoom( STATE.Menu, 0 ); + this.transition.zoom(STATE.Menu, 0); this.controls.disable(); - if ( ! this.newGame ) this.timer.stop(); - this.transition.timer( HIDE ); + if (!this.newGame) this.timer.stop(); + this.transition.timer(HIDE); - setTimeout( () => this.transition.title( SHOW ), this.transition.durations.zoom - 1000 ); + setTimeout(() => this.transition.title(SHOW), this.transition.durations.zoom - 1000); this.playing = false; this.controls.disable(); @@ -3916,20 +3944,20 @@ class Game { } - prefs( show ) { + prefs(show) { - if ( show ) { + if (show) { - if ( this.transition.activeTransitions > 0 ) return; + if (this.transition.activeTransitions > 0) return; this.state = STATE.Prefs; - this.transition.buttons( BUTTONS.Prefs, BUTTONS.Menu ); + this.transition.buttons(BUTTONS.Prefs, BUTTONS.Menu); - this.transition.title( HIDE ); - this.transition.cube( HIDE ); + this.transition.title(HIDE); + this.transition.cube(HIDE); - setTimeout( () => this.transition.preferences( SHOW ), 1000 ); + setTimeout(() => this.transition.preferences(SHOW), 1000); } else { @@ -3937,102 +3965,102 @@ class Game { this.state = STATE.Menu; - this.transition.buttons( BUTTONS.Menu, BUTTONS.Prefs ); + this.transition.buttons(BUTTONS.Menu, BUTTONS.Prefs); - this.transition.preferences( HIDE ); + this.transition.preferences(HIDE); - setTimeout( () => this.transition.cube( SHOW ), 500 ); - setTimeout( () => this.transition.title( SHOW ), 1200 ); + setTimeout(() => this.transition.cube(SHOW), 500); + setTimeout(() => this.transition.title(SHOW), 1200); } } - theme( show ) { + theme(show) { - this.themeEditor.colorPicker( show ); - - if ( show ) { + this.themeEditor.colorPicker(show); - if ( this.transition.activeTransitions > 0 ) return; + if (show) { - this.cube.loadFromData( States[ '3' ][ 'checkerboard' ] ); + if (this.transition.activeTransitions > 0) return; - this.themeEditor.setHSL( null, false ); + this.cube.loadFromData(States['3']['checkerboard']); + + this.themeEditor.setHSL(null, false); this.state = STATE.Theme; - this.transition.buttons( BUTTONS.Theme, BUTTONS.Prefs ); + this.transition.buttons(BUTTONS.Theme, BUTTONS.Prefs); - this.transition.preferences( HIDE ); + this.transition.preferences(HIDE); - setTimeout( () => this.transition.cube( SHOW, true ), 500 ); - setTimeout( () => this.transition.theming( SHOW ), 1000 ); + setTimeout(() => this.transition.cube(SHOW, true), 500); + setTimeout(() => this.transition.theming(SHOW), 1000); } else { this.state = STATE.Prefs; - this.transition.buttons( BUTTONS.Prefs, BUTTONS.Theme ); + this.transition.buttons(BUTTONS.Prefs, BUTTONS.Theme); - this.transition.cube( HIDE, true ); - this.transition.theming( HIDE ); + this.transition.cube(HIDE, true); + this.transition.theming(HIDE); - setTimeout( () => this.transition.preferences( SHOW ), 1000 ); - setTimeout( () => { + setTimeout(() => this.transition.preferences(SHOW), 1000); + setTimeout(() => { - const gameCubeData = JSON.parse( localStorage.getItem( 'theCube_savedState' ) ); + const gameCubeData = JSON.parse(localStorage.getItem('theCube_savedState')); - if ( !gameCubeData ) { + if (!gameCubeData) { - this.cube.resize( true ); + this.cube.resize(true); return; } - this.cube.loadFromData( gameCubeData ); + this.cube.loadFromData(gameCubeData); - }, 1500 ); + }, 1500); } } - stats( show ) { + stats(show) { - if ( show ) { + if (show) { - if ( this.transition.activeTransitions > 0 ) return; + if (this.transition.activeTransitions > 0) return; this.state = STATE.Stats; - this.transition.buttons( BUTTONS.Stats, BUTTONS.Menu ); + this.transition.buttons(BUTTONS.Stats, BUTTONS.Menu); - this.transition.title( HIDE ); - this.transition.cube( HIDE ); + this.transition.title(HIDE); + this.transition.cube(HIDE); - setTimeout( () => this.transition.stats( SHOW ), 1000 ); + setTimeout(() => this.transition.stats(SHOW), 1000); } else { this.state = STATE.Menu; - this.transition.buttons( BUTTONS.Menu, BUTTONS.Stats ); + this.transition.buttons(BUTTONS.Menu, BUTTONS.Stats); - this.transition.stats( HIDE ); + this.transition.stats(HIDE); - setTimeout( () => this.transition.cube( SHOW ), 500 ); - setTimeout( () => this.transition.title( SHOW ), 1200 ); + setTimeout(() => this.transition.cube(SHOW), 500); + setTimeout(() => this.transition.title(SHOW), 1200); } } - complete( show ) { + complete(show) { - if ( show ) { + if (show) { - this.transition.buttons( BUTTONS.Complete, BUTTONS.Playing ); + this.transition.buttons(BUTTONS.Complete, BUTTONS.Playing); this.state = STATE.Complete; this.saved = false; @@ -4041,37 +4069,38 @@ class Game { this.timer.stop(); this.storage.clearGame(); - this.bestTime = this.scores.addScore( this.timer.deltaTime ); + this.bestTime = this.scores.addScore(this.timer.deltaTime); - this.transition.zoom( STATE.Menu, 0 ); - this.transition.elevate( SHOW ); + this.transition.zoom(STATE.Menu, 0); + this.transition.elevate(SHOW); - setTimeout( () => { + setTimeout(() => { - this.transition.complete( SHOW, this.bestTime ); + this.transition.complete(SHOW, this.bestTime); this.confetti.start(); - }, 1000 ); + }, 1000); } else { this.state = STATE.Stats; + this.transition.buttons(BUTTONS.Stats, BUTTONS.Complete); this.saved = false; - this.transition.timer( HIDE ); - this.transition.complete( HIDE, this.bestTime ); - this.transition.cube( HIDE ); + this.transition.timer(HIDE); + this.transition.complete(HIDE, this.bestTime); + this.transition.cube(HIDE); this.timer.reset(); - setTimeout( () => { + setTimeout(() => { this.cube.reset(); this.confetti.stop(); - this.transition.stats( SHOW ); - this.transition.elevate( 0 ); + this.transition.stats(SHOW); + this.transition.elevate(0); - }, 1000 ); + }, 1000); return false; @@ -4080,6 +4109,306 @@ class Game { } } +// Volume button functionality +const musicButton = document.getElementById('play-btn'); +const music = document.getElementById('background-music'); + +musicButton.addEventListener('click', () => { + if (music.paused) { + music.play(); + musicButton.classList.remove('fa-volume-xmark'); + musicButton.classList.add('fa-volume-high'); + } else { + music.pause(); + musicButton.classList.remove('fa-volume-high'); + musicButton.classList.add('fa-volume-xmark'); + } +}); + + feature/shortcut-keys +////Add keyboard shortcuts +function rotateView(direction) { + const move = { + 'up': { axis: 'x', angle: -Math.PI / 2 }, + 'down': { axis: 'x', angle: Math.PI / 2 }, + 'left': { axis: 'y', angle: Math.PI / 2 }, + 'right': { axis: 'y', angle: -Math.PI / 2 } + +//rotation functions +function rotateU() { + const move = { axis: 'y', angle: -Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, 1, 0), axis: move.axis, angle: move.angle }); +} + +function rotateUPrime() { + const move = { axis: 'y', angle: Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, 1, 0), axis: move.axis, angle: move.angle }); +} + +function rotateR() { + const move = { axis: 'x', angle: -Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(1, 0, 0), axis: move.axis, angle: move.angle }); +} + +function rotateRPrime() { + const move = { axis: 'x', angle: Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(1, 0, 0), axis: move.axis, angle: move.angle }); +} + +function rotateF() { + const move = { axis: 'z', angle: -Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, 0, 1), axis: move.axis, angle: move.angle }); +} + +function rotateFPrime() { + const move = { axis: 'z', angle: Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, 0, 1), axis: move.axis, angle: move.angle }); +} + +function rotateL() { + const move = { axis: 'x', angle: Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(-1, 0, 0), axis: move.axis, angle: move.angle }); +} + +function rotateLPrime() { + const move = { axis: 'x', angle: -Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(-1, 0, 0), axis: move.axis, angle: move.angle }); +} + +function rotateD() { + const move = { axis: 'y', angle: Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, -1, 0), axis: move.axis, angle: move.angle }); +} + +function rotateDPrime() { + const move = { axis: 'y', angle: -Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, -1, 0), axis: move.axis, angle: move.angle }); +} + +function rotateB() { + const move = { axis: 'z', angle: Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, 0, -1), axis: move.axis, angle: move.angle }); +} + +function rotateBPrime() { + const move = { axis: 'z', angle: -Math.PI / 2 }; + game.controls.keyboardMove('LAYER', { position: new THREE.Vector3(0, 0, -1), axis: move.axis, angle: move.angle }); +} + +// Function to handle view rotation based on direction +function rotateView(direction, isPrime = false) { + const move = { + 'up': { axis: 'x', angle: isPrime ? Math.PI / 2 : -Math.PI / 2 }, + 'down': { axis: 'x', angle: isPrime ? -Math.PI / 2 : Math.PI / 2 }, + 'left': { axis: 'y', angle: isPrime ? -Math.PI / 2 : Math.PI / 2 }, + 'right': { axis: 'y', angle: isPrime ? Math.PI / 2 : -Math.PI / 2 } + + }[direction]; + if (move) { + game.controls.keyboardMove('CUBE', move); + } +} + + feature/shortcut-keys +document.addEventListener('keydown', function(event) { + const key = event.key.toLowerCase(); + switch (key) { + case 'w': + case 'arrowup': + rotateView('up'); + break; + case 's': + case 'arrowdown': + rotateView('down'); + break; + case 'a': + case 'arrowleft': + rotateView('left'); + break; + case 'd': + case 'arrowright': + rotateView('right'); + +let isPaused = false; + +function togglePause() { + if (isPaused) { + game.timer.start(true); + game.controls.enable(); + console.log('Game resumed'); + } else { + game.timer.stop(); + game.controls.disable(); + console.log('Game paused'); + } + isPaused = !isPaused; +} + +// Add event listener for keydown +document.addEventListener('keydown', function (event) { + const isPrime = event.shiftKey; + const key = event.key.toLowerCase(); + + if (event.key === 'p') { + togglePause(); + return; + } + + if (isPaused) return; + + switch (key) { + case 'arrowup': + rotateView('up', isPrime); + break; + case 'arrowdown': + rotateView('down', isPrime); + break; + case 'arrowleft': + rotateView('left', isPrime); + break; + case 'arrowright': + rotateView('right', isPrime); + break; + case 'u': + isPrime ? rotateUPrime() : rotateU(); + break; + case 'r': + isPrime ? rotateRPrime() : rotateR(); + break; + case 'f': + isPrime ? rotateFPrime() : rotateF(); + break; + case 'l': + isPrime ? rotateLPrime() : rotateL(); + break; + case 'd': + isPrime ? rotateDPrime() : rotateD(); + break; + case 'b': + isPrime ? rotateBPrime() : rotateB(); + main + break; + default: + break; + } +}); + + feature/shortcut-keys + +document.addEventListener('mousemove', function () { + if (isPaused) { + togglePause(); + } +}); + +let cube; // Global cube variable + +function handleKeyPress(event) { + console.log('Key pressed:', event.key); // Log the pressed key + switch (event.key) { + case 'ArrowLeft': + console.log('Left Arrow pressed'); + rotateLeft(); + break; + case 'ArrowUp': + console.log('Up Arrow pressed'); + rotateUp(); + break; + case 'ArrowRight': + console.log('Right Arrow pressed'); + rotateRight(); + break; + case 'ArrowDown': + console.log('Down Arrow pressed'); + rotateDown(); + break; + default: + console.log('Other key pressed:', event.key); + break; + } +} + +function rotateUp() { + console.log('Rotate up function called'); + if (cube) { + var axis = new THREE.Vector3(1, 0, 0); // Rotate around the x-axis + var angle = Math.PI / 2; // Rotate by 90 degrees + cube.rotateOnAxis(axis, angle); + } +} +function rotateDown() { + console.log('Rotate down function called'); + if (cube) { + var axis = new THREE.Vector3(1, 0, 0); // Rotate around the x-axis + var angle = -Math.PI / 2; // Rotate by -90 degrees + cube.rotateOnAxis(axis, angle); + } +} + +function rotateLeft() { + console.log('Rotate left function called'); + if (cube) { + var axis = new THREE.Vector3(0, 1, 0); // Rotate around the y-axis + var angle = Math.PI / 2; // Rotate by 90 degrees + cube.rotateOnAxis(axis, angle); + } +} + +function rotateRight() { + console.log('Rotate right function called'); + if (cube) { + var axis = new THREE.Vector3(0, 1, 0); // Rotate around the y-axis + var angle = -Math.PI / 2; // Rotate by -90 degrees + cube.rotateOnAxis(axis, angle); + } +} + +function init() { + console.log('Initializing Three.js'); + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); + const renderer = new THREE.WebGLRenderer(); + renderer.setSize(window.innerWidth, window.innerHeight); + document.body.appendChild(renderer.domElement); + + const geometry = new THREE.BoxGeometry(); + const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); + cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + camera.position.z = 5; + + const animate = function () { + requestAnimationFrame(animate); + renderer.render(scene, camera); + }; + + animate(); +} + +function handleCookieBlocking() { + console.log('Checking for third-party cookie blocking'); + // Implement your logic to check and handle third-party cookie blocking +} + +// Adding CSS dynamically +const head = document.head || document.getElementsByTagName('head')[0]; +if (head) { + const style = document.createElement('style'); + style.textContent = ` + /* Your CSS rules here */ + `; + head.appendChild(style); +} else { + console.error('Head element not found'); +} + +function initThreeJS() { + console.log('Initializing Three.js'); + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window) +} + window.version = '0.99.2'; -window.game = new Game(); \ No newline at end of file +window.game = new Game(); diff --git a/script1.js b/script1.js index f2ba21d..790fac8 100644 --- a/script1.js +++ b/script1.js @@ -1,5 +1,6 @@ + function handleCredentialResponse(response)//is the callback function that handles the ID token received from Google. - { +{ const data = jwt_decode(response.credential); console.log('User data:', data); @@ -15,8 +16,79 @@ function jwt_decode(token) //jwt_decode is a helper function to decode the JWT t { const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) { + const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); return JSON.parse(jsonPayload); } + +document.addEventListener('DOMContentLoaded', () => { + console.log('DOM fully loaded and parsed'); + // Initialize Three.js + console.log('Initializing Three.js'); + // Your Three.js initialization code here + + // Check for and handle third-party cookie blocking issues + console.log('Checking for third-party cookie blocking'); + // Your code to handle third-party cookie blocking + + // Check for and handle any failed resource loading issues + console.log('Checking for failed resource loading'); + // Your code to handle failed resource loading +}); + +document.addEventListener('DOMContentLoaded', (event) => { + console.log('DOM fully loaded and parsed'); + + // Check for third-party cookie blocking + checkThirdPartyCookies(); + + // Initialize Three.js + initThreeJS(); + + // Append custom style + appendCustomStyle(); +}); + +function checkThirdPartyCookies() { + console.log('Checking for third-party cookie blocking'); + // Implement the check for third-party cookies here +} + +function appendCustomStyle() { + console.log('Appending custom style'); + var head = document.head || document.getElementsByTagName('head')[0]; + if (head) { + var style = document.createElement('style'); + style.textContent = ` + /* Your CSS rules here */ + `; + head.appendChild(style); + } else { + console.error('Head element not found'); + } +} + +function initThreeJS() { + console.log('Initializing Three.js'); + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); + const renderer = new THREE.WebGLRenderer(); + renderer.setSize(window.innerWidth, window.innerHeight); + document.body.appendChild(renderer.domElement); + + const geometry = new THREE.BoxGeometry(); + const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + camera.position.z = 5; + + const animate = function () { + requestAnimationFrame(animate); + renderer.render(scene, camera); + }; + + animate(); +} + diff --git a/style.css b/style.css index c1ee3c1..4326119 100644 --- a/style.css +++ b/style.css @@ -1,10 +1,67 @@ + +/* styles.css */ + +/* Default theme */ +body.default-theme { + background-color: white; + color: black; +} + +/* Dark theme */ +body.dark-theme { + background-color: #333; + color: white; +} + +/* Light theme */ +body.light-theme { + background-color: #f9f9f9; + color: #333; +} + +/* Colorful theme */ +body.colorful-theme { + background: linear-gradient(45deg, #ff6f69, #ffcc5c, #ffeead, #88d8b0, #96ceb4); + color: #333; +} + +/* Style for the theme selector */ +.theme-selector { + position: fixed; + top: 10px; + right: 10px; + z-index: 1000; +} + +#play-btn { + position: fixed; + top: 85px; + right: 50px; /* Adjust as necessary to avoid overlap */ + z-index: 10001; /* Higher than the theme selector */ +} + @font-face { font-family: "BungeeFont"; font-weight: normal; font-style: normal; + + src: url("data:font/truetype;charset=utf-8;base64,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") + format("woff2"), + url("data:font/truetype;charset=utf-8;base64,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") + format("woff"); +} +*, +*:before, +*:after { + src: url("data:font/truetype;charset=utf-8;base64,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") format("woff2"), url("data:font/truetype;charset=utf-8;base64,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") format("woff"); } -*, *:before, *:after { + +*, +*:before, +*:after { + + -webkit-user-select: none; -moz-user-select: none; user-select: none; @@ -18,7 +75,9 @@ font-weight: inherit; font-style: inherit; text-transform: uppercase; + } + *:focus { outline: none; } @@ -35,6 +94,7 @@ html { } body { + font-family: "BungeeFont", sans-serif; font-weight: normal; font-style: normal; @@ -43,6 +103,7 @@ body { overflow: hidden; height: 100%; font-size: 5rem; + } .icon { @@ -50,7 +111,7 @@ body { font-size: inherit; overflow: visible; vertical-align: -0.125em; - preserveAspectRatio: none; + preserveaspectratio: none; } .range { @@ -59,9 +120,11 @@ body { z-index: 1; opacity: 0; } + .range:not(:last-child) { margin-bottom: 2em; } + .range__label { position: relative; font-size: 0.9em; @@ -69,6 +132,7 @@ body { padding-bottom: 0.5em; z-index: 2; } + .range__track { position: relative; height: 1em; @@ -76,6 +140,7 @@ body { margin-right: 0.5em; z-index: 3; } + .range__track-line { position: absolute; background: rgba(0, 0, 0, 0.2); @@ -86,6 +151,7 @@ body { right: -0.5em; transform-origin: left center; } + .range__handle { position: absolute; width: 0; @@ -95,6 +161,7 @@ body { cursor: pointer; z-index: 1; } + .range__handle div { transition: background 500ms ease; position: absolute; @@ -108,9 +175,11 @@ body { background: #41aac8; border-bottom: 2px solid rgba(0, 0, 0, 0.2); } + .range.is-active .range__handle div { transform: scale(1.25); } + .range__handle:after { content: ""; position: absolute; @@ -121,6 +190,7 @@ body { margin-left: -1.5em; margin-top: -1.5em; } + .range__list { display: flex; flex-flow: row nowrap; @@ -131,18 +201,24 @@ body { color: rgba(0, 0, 0, 0.5); z-index: 1; } + .range--type-color:not(:last-child) { margin-bottom: 1em; } + .range--type-color .range__list { display: none; } -.range--type-color .range__handle > div { + +.range--type-color .range__handle>div { background: currentColor !important; + } + .range--type-color .range__track-line { background: transparent; } + .range--type-color .range__track-line:after { position: absolute; top: 0; @@ -152,30 +228,48 @@ body { content: ""; opacity: 0.5; } + .range--color-hue .range__handle { color: red; } + .range--color-hue .range__track { color: red; } + .range--color-hue .range__track-line:after { - background: linear-gradient(to right, red, yellow, lime, cyan, blue, magenta, red); + background: linear-gradient( + to right, + red, + yellow, + lime, + cyan, + blue, + magenta, + red + ); } + .range--color-saturation .range__handle { color: red; } + .range--color-saturation .range__track { color: red; } + .range--color-saturation .range__track-line:after { background: linear-gradient(to right, gray, currentColor); } + .range--color-lightness .range__handle { color: red; } + .range--color-lightness .range__track { color: red; } + .range--color-lightness .range__track-line:after { background: linear-gradient(to right, black, currentColor, white); } @@ -188,22 +282,33 @@ body { justify-content: space-between; opacity: 0; } + .stats:not(:last-child) { margin-bottom: 1.5em; } -.stats > i { + +.stats>i { + display: block; color: rgba(0, 0, 0, 0.5); font-size: 0.9em; } -.stats > b { + +.stats>b { display: block; font-size: 0.9em; } -.stats > b > i { + +.stats>b>i { font-size: 0.75em; + } + +.stats[name="worst-time"] { + + .stats[name=worst-time] { + display: none; } @@ -217,34 +322,42 @@ body { opacity: 0; } + .text i { display: inline-block; opacity: 0; white-space: pre-wrap; } + .text--title { bottom: 75%; font-size: 4.4em; height: 1.2em; } + .text--title span { display: block; } + .text--title span:first-child { font-size: 0.5em; margin-bottom: 0.2em; margin-top: 28px; } + .text--note { top: 87%; font-size: 1em; } + .text--timer { bottom: 78%; font-size: 3.5em; line-height: 1; } -.text--complete, .text--best-time { + +.text--complete, +.text--best-time { font-size: 1.5em; top: 83%; line-height: 1em; @@ -263,10 +376,13 @@ body { color: rgba(0, 0, 0, 0.25); opacity: 0; } + .btn:hover { - color: rgba(0, 0, 0, 0.5); + color: rgb(245, 245, 245); transition: color 400ms ease-out; + background-color: #541a8be7; } + .btn:after { cursor: pointer; position: absolute; @@ -279,25 +395,95 @@ body { margin-top: -1.5em; border-radius: 100%; } + + +/*The back button was changed to bottom because it interrupts the info button.*/ + .btn--tl { top: 1.2em; left: 1.2em; + + height: 60px; + width: 60px; + padding-left: 12px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; + + left: 1.2em; + } + .btn--bl { bottom: 1.2em; left: 1.2em; + height: 60px; + width: 60px; + padding-left: 10px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; } + .btn--br { bottom: 1.2em; right: 1.2em; + height: 60px; + width: 60px; + padding-left: 11px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; } + .btn--bc { bottom: 0.8em; left: calc(50% - 0.5em); } + + +/* For volume button */ + +.btn--tr { + top: 1.2em; + right: 1.2em; + opacity: 1; + pointer-events: auto; + height: 60px; + width: 60px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; +} + + +/* For info button */ + +.btn--in { + top: 1.2em; + left: 1.2em; + opacity: 1; + pointer-events: auto; + height: 60px; + width: 60px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 5px 5px; + color: #333; + z-index: 10; +} + .btn svg { display: block; } + .btn--cancel { display: none !important; } @@ -307,7 +493,15 @@ body { color: #070d15; } -.ui, .ui__background, .ui__game, .ui__texts, .ui__prefs, .ui__theme, .ui__stats, .ui__buttons { + +.ui, +.ui__background, +.ui__game, +.ui__texts, +.ui__prefs, +.ui__theme, +.ui__stats, +.ui__buttons { position: absolute; top: 0; left: 0; @@ -315,11 +509,13 @@ body { height: 100%; overflow: hidden; } + .ui__background { z-index: 1; transition: background 500ms ease; background: #d1d5db; } + .ui__background:after { position: absolute; top: 0; @@ -327,22 +523,32 @@ body { width: 100%; height: 100%; content: ""; - background-image: linear-gradient(to bottom, white 50%, rgba(255, 255, 255, 0) 100%); + background-image: linear-gradient( + to bottom, + white 50%, + rgba(255, 255, 255, 0) 100% + ); } + .ui__game { pointer-events: all; z-index: 2; background: linear-gradient(to bottom, #fffffff0, #7dadb7f0); } + .ui__game canvas { display: block; width: 100%; height: 100%; } + .ui__texts { z-index: 3; } -.ui__prefs, .ui__stats, .ui__theme { + +.ui__prefs, +.ui__stats, +.ui__theme { display: flex; flex-flow: column nowrap; justify-content: center; @@ -350,16 +556,66 @@ body { overflow: hidden; z-index: 4; } + .ui__theme { padding-top: 15em; } + .ui__buttons { z-index: 5; } + + +/* Replace -ms-high-constrast styling with the new Forced Colors Mode standard */ + +/* Original styling */ + + +/* Updated styling for Forced Colors Mode */ +@media (forced-colors: active) { + /* Apply styling for Forced Colors Mode */ + @media (forced-colors: active) { + + /* Update styles accordingly */ + *, + *:before, + *:after { + color: ButtonText; + background-color: ButtonFace; + /* Add other properties as needed */ + } + } + /* Apply styling for high contrast mode in Microsoft Edge */ + @media screen and (-ms-high-constrast: active), + (-ms-high-constrast: none) { + + /* Update styles accordingly */ + *, + *:before, + *:after { + -webkit-text-fill-color: WindowText; + -webkit-background-color: Window; + color: WindowText; + background-color: Window; + /* Add other properties as needed */ + } + } + + /* Styling for Google Sign-in button */ + .g_id_signin { + margin-top: 20px; + } + + /* Styles for Forced Colors Mode */ + /* Update styles accordingly */ +} + + /* styling element for the goggle login option to be placed at the top right corner of the webpage. */ + .navbar .g_id_signin { position: relative; z-index: 160; @@ -394,4 +650,199 @@ a{ transition-duration: 0.3s; width: 100%; font-size: 0.7em; -} \ No newline at end of file +} + +.g_id_signin { + margin-top: 20px; +} + + + +/* HOW TO PLAY CSS */ + + +/* Body styling */ +body { + line-height: 1.2; + color: #ffffff; + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + padding-right: 1300px; + padding-bottom: 500px; +} + +/* Center section styling */ +.sec-center { + position: relative; + max-width: 100%; + text-align: center; + z-index: 200; +} + +/* Hide checkboxes */ +[type="checkbox"] { + position: absolute; + left: -9999px; + opacity: 0; + pointer-events: none; +} + +/* Dropdown button styling */ +.dropdown:checked + label, +.dropdown:not(:checked) + label { + position: relative; + line-height: 2; + height: 50px; + transition: all 200ms linear; + border-radius: 4px; + width: 220px; + letter-spacing: 1px; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; + border: none; + background-color: #a188eac4; + cursor: pointer; + color: #102770; + box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset; +} + + +/* Dropdown content styling */ +.section-dropdown { + position: absolute; + padding: 5px; + background-color: #5a29edd8; + top: 70px; + left: 0; + width: 400%; + border-radius: 10px; + display: block; + box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset; + z-index: 2; + opacity: 0; + pointer-events: none; + transform: translateY(20px); + transition: all 200ms linear; +} + +.dropdown:checked ~ .section-dropdown { + opacity: 1; + pointer-events: auto; + transform: translateY(0); +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 20px; + color: #333; + +.profile-menu { + position: relative; + display: inline-block; + z-index: 11; + margin-left: auto; + top: 40px; /* Move lower by 10px */ +} + +.profile-btn { + /* Blue background for the button */ + border: none; + color: white; + padding: 10px; + border-radius: 50%; + /* Make the button circular */ + cursor: pointer; + outline: none; + /* Optional: add a border */ + width: 10rem; + /* Set the width of the button */ + height: 10rem; + /* Set the height of the button */ + background-image: url('images/profilepic.jpg'); + background-size: cover; + /* Cover the entire button */ + background-position: center; + /* Center the background image */ + border-radius: 50%; + /* Makes the button circular */ + cursor: pointer; + margin-right: 25rem; + margin-top: 7rem; +} + +.profile-pic { + width: 40px; + height: 40px; + border-radius: 50%; + /* Ensure the profile picture is circular */ + + z-index: 10; + width: 100%; + height: 100%; + border-radius: 50%; + /* Ensures the image inside is circular */ +} + + +.btn--about { + position: relative; + z-index: 10; /* Ensure it's on top */ +} + + +.dropdown-content { + display: none; + position: absolute; + right: 0; + top: 100%; + background-color: #f9f9f9; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 12; + border-radius: 3px; +} + +.dropdown-content a { + color: black; + padding: 12px 16px; + text-decoration: none; + display: block; +} + +.dropdown-content a:hover { + background-color: #f1f1f1; +} + +.profile-menu:hover .dropdown-content { + display: block; +} + +@media screen and (max-width: 430px) { + #ui_texts { + font-size: 3.5rem; + } + + #theme_selector_id { + font-size: 3rem; + } + + .profile-btn { + margin-right: 14rem; + margin-top: 12rem; + width: 7rem; + height: 7rem; + } + + .btn--tr { + top: 1.8em; + right: 0.5em; + opacity: 1; + pointer-events: auto; + } +} + diff --git a/twist1.png b/twist1.png new file mode 100644 index 0000000..f3388a5 Binary files /dev/null and b/twist1.png differ diff --git a/twist2.png b/twist2.png new file mode 100644 index 0000000..fce93ee Binary files /dev/null and b/twist2.png differ