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 new file mode 100644 index 0000000..c22fc71 --- /dev/null +++ b/.github/pull_request_template.yml @@ -0,0 +1,30 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes: #(issue no.) + + + +## Type of change + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# Checklist: + + +- [ ] I have made this from my own +- [ ] I have taken help from some online resourses +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings + + +# ATTACH SCREEN-SHOTS / DEPLOYMENT LINK (if applicable) diff --git a/.github/workflows/autocomment-pr.yml b/.github/workflows/autocomment-pr.yml new file mode 100644 index 0000000..8c63718 --- /dev/null +++ b/.github/workflows/autocomment-pr.yml @@ -0,0 +1,36 @@ +name: Auto Comment on PR + +on: + pull_request_target: + types: [opened] + +permissions: + issues: write + pull-requests: write + +jobs: + comment: + runs-on: ubuntu-latest + + steps: + - name: Add Comment to Pull Request + run: | + COMMENT=$(cat < + + + + + 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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..618d2b3 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[Dev-tanay](https://github.com/Dev-tanay)/[Rubik-Cube](https://github.com/Abidsyed25/Rubik-Cube) +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..3a90e92 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,68 @@ +# **Contributing Guidelines** ๐Ÿ“„ + +This documentation contains a set of guidelines to help you during the contribution process. +We are happy to welcome all the contributions from anyone willing to improve/add new scripts to this project. +Thank you for helping out and remember, **no contribution is too small.** +
+Please note we have a [code of conduct](CODE_OF_CONDUCT.md) please follow it in all your interactions with the project. + + + +
+ +## **Need some help regarding the basics?๐Ÿค”** + + +You can refer to the following articles on basics of Git and Github and also contact the Project Mentors, +in case you are stuck: + +- [Forking a Repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) +- [Cloning a Repo](https://help.github.com/en/desktop/contributing-to-projects/creating-an-issue-or-pull-request) +- [How to create a Pull Request](https://opensource.com/article/19/7/create-pull-request-github) +- [Getting started with Git and GitHub](https://towardsdatascience.com/getting-started-with-git-and-github-6fcd0f2d4ac6) +- [Learn GitHub from Scratch](https://docs.github.com/en/get-started/start-your-journey/git-and-github-learning-resources) + +
+ +## **Issue Report Process ๐Ÿ“Œ** + +1. Go to the project's issues. +2. Give proper description for the issues. +3. Don't spam to get the assignment of the issue ๐Ÿ˜€. +4. Wait for till someone is looking into it !. +5. Start working on issue only after you got assigned that issue ๐Ÿš€. + +
+ +## Set-Up and Installation: + +To get started with Rubik Cube, follow these simple steps: + +1. Clone the repository: To get started with contributing to our project, fork the repository and then clone it to your local machine + ```bash + git clone https://github.com/your-username/Rubik-Cube.git + +2. Change the directory to Client and Install required Packages + ```bash + cd client + npm install + ``` +3. Make Your Changes: Make the necessary modifications or additions to the project files. + +4. Commit and Push Once you've made your changes, commit them and push to your fork: + ```bash + git add . + git commit -m "Describe your changes here" + git push + ``` +5. Create a Pull Request: + Navigate back to the original repository and open a pull request from your forked repository. +## **Pull Request Process ๐Ÿš€** +1. Ensure that you have self reviewed your code ๐Ÿ˜€ +2. Make sure you have added the proper description for the functionality of the code +3. I have commented my code, particularly in hard-to-understand areas. +4. Add screenshot it help in review. +5. Submit your PR by giving the necesarry information in PR template and hang tight we will review it really soon ๐Ÿš€ +
+# **Thank you for contributing๐Ÿ’—** +Footer diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d9f23af --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +# Use the official nginx image as the base image +FROM nginx:alpine + +# Copy the HTML, CSS, and JavaScript files into the container +COPY ./ /usr/share/nginx/html + +# Expose port 80 +EXPOSE 80 + +# Start nginx when the container launches +CMD ["nginx", "-g", "daemon off;"] 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 bf3e5d9..7de9b5d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,228 @@ # Rubik-Cube -## Rubik-Cube using HTML , CSS , JS +#

โœจRubik-Cubeโœจ

+ -Link -> https://dev-tanay.github.io/Rubik-Cube/ +

+ + GitHub issues + + + GitHub forks + + + GitHub stars + + + GitHub license + + + Website status + +

+ + +## ๐ŸŒ Link to Game Demo +https://dev-tanay.github.io/Rubik-Cube/ + + + + + + + +## ๐Ÿ“– About the Game +This is a simple and elegant game where you can play Rubik's Cube from your own web browser. + +- Each of the six faces of the Rubik's cube is in one of the six colors - red, green, yellow, blue, white, and orange. +- The visible part of the cube is divided into 26 pieces: + - 6 central pieces (one colored side) + - 12 edge pieces (two colored sides) + - 8 corner pieces (three colored sides) +- The 26 pieces are "scrambled" to have different colors on each face. + +## ๐Ÿ•น๏ธ How to Play +The objective of this game is to get each face of the cube to have a single color. + +This can be done in two ways: + +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. + + +## ๐Ÿงฉ Installation Guide for Rubik-Cube + +Follow the steps below to install and set up the Rubik-Cube project on your local machine. + +### ๐Ÿ“‹ Prerequisites +- Ensure you have `git` installed on your system. If not, download and install it from [here](https://git-scm.com/). +- A modern web browser (e.g., Chrome, Firefox, Edge). + +### ๐Ÿ› ๏ธ Steps + +1. **๐Ÿ”— Clone the Repository** + Open your terminal (or Command Prompt on Windows) and run the following command to clone the repository: + ```sh + git clone https://github.com/Dev-tanay/Rubik-Cube.git + ``` + +2. **๐Ÿ“‚ Navigate to the Cloned Repository** + Change directory to the cloned repository: + ```sh + cd Rubik-Cube + ``` + +3. **๐ŸŒ Open the Index.html File** + Locate the `index.html` file in the repository and open it in your web browser. You can do this by either: + - **Double-clicking the `index.html` file** in your file manager. This will open the file in your default web browser. + - **Running a simple HTTP server** (if you prefer): + + **Using Python (if installed):** + ```sh + python -m http.server + ``` + Open your web browser and go to `http://localhost:8000`. + + **Using Node.js (if installed) with http-server package:** + ```sh + npx http-server + ``` + Open your web browser and go to the provided local server URL (usually `http://127.0.0.1:8080` or similar). + +4. **๐ŸŽฎ Play the Game** + Once the `index.html` file is opened in the web browser, the game screen will be displayed. You can now start playing the Rubik-Cube game. + +### โ„น๏ธ Notes +- Ensure your browser allows running JavaScript, as the game likely depends on it. +- If you encounter any issues, check the browser console for error messages and ensure all assets are properly loaded. + +Enjoy your Rubik-Cube game! ๐Ÿ•น๏ธโœจ + +## ๐Ÿงญ Navigating the Game Screen +- **Home** + +

About the Game

+

This is a simple and elegant game where you can play Rubik's Cube from your own web browser.

+

Each of the six faces of the Rubik's cube is in one of the six colors - red, green, yellow, blue, white, and orange.

+

The visible part of the cube is divided into 26 pieces:

+
    +
  • 6 central pieces (one colored side)
  • +
  • 12 edge pieces (two colored sides)
  • +
  • 8 corner pieces (three colored sides)
  • +
+

The 26 pieces are "scrambled" to have different colors on each face.

+ + +

Link to Game Demo

+https://dev-tanay.github.io/Rubik-Cube/ + + +

How to Play

+

The objective of this game is to get each face of the cube to have a single color.

+

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 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 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.
    • +
    +
+ + +

Technology Used

+ +

+ HTML + CSS + js +

+ + +

Installation

+
    +
  1. Clone the repository
  2. +
    git clone https://github.com/Dev-tanay/Rubik-Cube.git
    +
  3. Navigate to the cloned repository
  4. +
    cd Rubik-Cube
    +
  5. Open the index.html file in a web browser
  6. +
+

There, you've opened the game screen!

+ +

Navigating the Game Screen

+
    +
  • Home
  • + + + - You'll see two icons at the bottom corners of the screen: (1) Leaderboard and (2) Preferences. + - You can start the game by double-clicking on the text "DOUBLE TAP TO START". + +- **Game Start** + + - Once you start the game, a timer will show above the cube. + - Now you'll see only one icon, which is Back. + +- **Preferences** + + - Clicking Preferences from the Home screen will lead you to the settings page. + - Options available for customization: + - Cube size + - Flip Type + - Scramble + - Camera Angle + - Color Scheme + - Two new icons in the bottom corners: (3) Back and (4) Theme. + +- **Theme** + + - Here, you can adjust the hue, saturation, and lightness of the screen. + - New icons in the bottom corners: (5) Back and (6) Reset. + +- **Stats** + + - Displays statistics of your Rubik's Cube game, including: + - Total Number of Solves + - Best time + - Average of 5, 12, 25 + + +

    LICENSE

    +

    This project is licensed under the MIT License - see the LICENSE file for details.

    + + +
    +

    Red Heart Contributors

    +
    +
    + +- This project thanking all the contributors for having your valuable contribution to our project +- Make sure you show some love by giving โญ to our repository + +
    + +
    + + + +
    +
    +

    Back to top

    + +## ๐Ÿ“œ License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + 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/deployment.yaml b/deployment.yaml new file mode 100644 index 0000000..21e723a --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,19 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-web-app +spec: + replicas: 2 + selector: + matchLabels: + app: static-web-app + template: + metadata: + labels: + app: static-web-app + spec: + containers: + - name: static-web-app + image: your-image-name + ports: + - containerPort: 80 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/BulpOff.png b/images/BulpOff.png new file mode 100644 index 0000000..7680a4f Binary files /dev/null and b/images/BulpOff.png differ diff --git a/images/BulpOn.png b/images/BulpOn.png new file mode 100644 index 0000000..e0d98c7 Binary files /dev/null and b/images/BulpOn.png differ diff --git a/images/changeview.png b/images/changeview.png new file mode 100644 index 0000000..f37e611 Binary files /dev/null and b/images/changeview.png differ diff --git a/images/home.png b/images/home.png new file mode 100644 index 0000000..88b16d2 Binary files /dev/null and b/images/home.png differ diff --git a/images/home1.png b/images/home1.png new file mode 100644 index 0000000..3728c0c Binary files /dev/null and b/images/home1.png differ diff --git a/images/preferences.png b/images/preferences.png new file mode 100644 index 0000000..17b080f Binary files /dev/null and b/images/preferences.png differ 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/images/start.png b/images/start.png new file mode 100644 index 0000000..efa56eb Binary files /dev/null and b/images/start.png differ diff --git a/images/stats.png b/images/stats.png new file mode 100644 index 0000000..57bb38f Binary files /dev/null and b/images/stats.png differ diff --git a/images/theme.png b/images/theme.png new file mode 100644 index 0000000..f7212cd Binary files /dev/null and b/images/theme.png differ diff --git a/images/twist1.png b/images/twist1.png new file mode 100644 index 0000000..f3388a5 Binary files /dev/null and b/images/twist1.png differ diff --git a/images/twist2.png b/images/twist2.png new file mode 100644 index 0000000..fce93ee Binary files /dev/null and b/images/twist2.png differ diff --git a/index.html b/index.html index bf11a94..ab78ae8 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,6 @@ - + + Rubik Cube @@ -7,17 +8,109 @@ + + + + + + + + +
    + + + + + + + + +
    + +
    + + + + -
    + +
    + + About Us + +
    + + + +
    +
    + + + +
    +
    + + -
    + -
    + + + + + + +
    + + + + + +

    @@ -27,61 +120,197 @@

    Double tap to start
    -
    - 0:00 +
    Time limit + 05:00 + +
    + + +
    +

    + Let's + Solve! +

    +
    + Double tap to start +
    +
    + +
    +

    + Let's + GO +

    +
    + Click here to start +
    + +
    +

    TIME START NOW

    + + 0:00 +
    +
    + Complete! +
    +
    + + Best Time! +
    + +
    -
    - Complete! + +
    + + + + +
    -
    - - Best Time! + +
    + + +
    -
    - - - - - + + + + +
    -
    - - - -
    +
    + +
    + Cube:3x3x3 +
    +
    + Total solves:- +
    +
    + Best time:- +
    +
    + Worst time:- +
    +
    + Average of 5:- +
    +
    + Average of 12:- +
    +
    + Average of 25:- +
    - Cube:3x3x3 + Cube:3x3x3
    - Total solves:- + Total solves:-
    - Best time:- + Best time:-
    - Worst time:- + Worst time:-
    - Average of 5:- + Average of 5:-
    - Average of 12:- + Average of 12:-
    - Average of 25:- + Average of 25:- +
    + +
    + + + + + +
    + + + + +
    -
    + +
    + + + + + +
    + +
    + + + +
    + +
    +
    + Cube:3x3x3 +
    +
    + Total solves:- +
    +
    + Best time:- +
    +
    + Worst time:- +
    +
    + Average of 5:- +
    +
    + Average of 12:- +
    +
    + Average of 25:- +
    +
    + +
    + + +
    +
    + +
    +
    - - @@ -91,19 +320,250 @@

    --> - - + + +
    +
    + +
    +
    + +
    +
    + + ย ย ย ย 
    + + + + +
    +
    + + +
    +
    + + + + + + + + + + + + + + +
    + +
    + +
    + +
    +

    + 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. +
    +
    + + + + +
    + + + + + + 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 aec3535..de77db0 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,40 @@ 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 ]; + animation =newGame; + + this.ids.splice(index, 1); + delete this.animations[animation.id]; animation = null; } @@ -59,48 +65,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 +116,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 +131,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 +139,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 +237,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 +250,12 @@ function RoundedBoxGeometry( size, radius, radiusSegments ) { vertexPool = [], normalPool = [], indices = [] - ; + ; var lastVertex = rs1 * radiusSegments, cornerVertNumber = rs1 * radiusSegments + 1 - ; + ; doVertices(); doFaces(); @@ -233,77 +267,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 +358,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 +377,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 +403,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 +436,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 +521,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 +549,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 +599,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 +644,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 +683,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 +704,7 @@ function RoundedPlaneGeometry( size, radius, depth ) { class Cube { - constructor( game ) { + constructor(game) { this.game = game; this.size = 3; @@ -686,10 +720,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 +731,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 +783,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 +802,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 +835,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 +845,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 +882,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 +896,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 +940,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 +968,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 +982,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 +990,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 +998,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 +1018,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 +1034,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 +1071,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 +1106,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 +1130,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 +1209,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 +1218,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 +1253,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 +1314,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 +1443,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 +1480,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 +1519,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 +1669,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 +1699,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 +1757,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 +1793,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 +1820,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 +1839,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 +1915,7 @@ class Scrambler { class Transition { - constructor( game ) { + constructor(game) { this.game = game; @@ -1919,14 +1951,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 +1968,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 +2030,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 +2052,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 +2251,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,203 +2328,217 @@ 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); } } class Timer extends Animation { + constructor(game) { + super(false); + this.game = game; + this.reset(); + } + + start(continueGame = false) { + this.startTime = continueGame ? Date.now() - this.deltaTime : Date.now(); + this.isRunning = true; + - constructor( game ) { + constructor(game) { - super( false ); + super(false); this.game = game; this.reset(); - + } - start( continueGame ) { + + start( continueGame = false) { this.startTime = continueGame ? ( Date.now() - this.deltaTime ) : Date.now(); + this.isRunning = true; + this.deltaTime = 0; this.converted = this.convert(); - super.start(); - } reset() { - this.startTime = 0; this.currentTime = 0; this.deltaTime = 0; this.converted = '0:00'; - + this.isRunning = false; + this.setText(); } stop() { - - this.currentTime = Date.now(); - this.deltaTime = this.currentTime - this.startTime; - this.convert(); - - super.stop(); - - return { time: this.converted, millis: this.deltaTime }; - + if (this.isRunning) { + this.currentTime = Date.now(); + this.deltaTime = this.currentTime - this.startTime; + this.convert(); + this.isRunning = false; + super.stop(); + } + } + + resume() { + if (!this.isRunning) { + this.start(true); + } } update() { - const old = this.converted; - this.currentTime = Date.now(); this.deltaTime = this.currentTime - this.startTime; this.convert(); - if ( this.converted != old ) { + if (this.converted !== old) { - localStorage.setItem( 'theCube_time', this.deltaTime ); - this.setText(); + if (this.converted != old) { + localStorage.setItem('theCube_time', this.deltaTime); + this.setText(); } - } convert() { + const seconds = parseInt((this.deltaTime / 1000) % 60); + const minutes = parseInt(this.deltaTime / (1000 * 60)); + this.converted = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; - const seconds = parseInt( ( this.deltaTime / 1000 ) % 60 ); - const minutes = parseInt( ( this.deltaTime / ( 1000 * 60 ) ) ); - this.converted = minutes + ':' + ( seconds < 10 ? '0' : '' ) + seconds; + const seconds = parseInt((this.deltaTime / 1000) % 60); + const minutes = parseInt(this.deltaTime / (1000 * 60)); + this.converted = `${minutes}:${seconds < 10 ? '0' : ''}${seconds}` } setText() { - this.game.dom.texts.timer.innerHTML = this.converted; - } - } + 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 +2548,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 +2565,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 +2643,7 @@ class Range { class Preferences { - constructor( game ) { + constructor(game) { this.game = game; @@ -2607,27 +2653,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 +2681,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 +2693,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 +2705,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 +2764,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 +2826,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 +2839,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 +2854,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 +2864,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 +2894,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 +2905,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 +2917,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 +2930,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 +2980,7 @@ class Particle { class Scores { - constructor( game ) { + constructor(game) { this.game = game; @@ -2967,25 +3013,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 +3042,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 +3087,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 +3115,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 +3147,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 +3173,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 +3187,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 +3201,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 +3226,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 +3251,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 +3272,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 +3286,7 @@ class Storage { class Themes { - constructor( game ) { + constructor(game) { this.game = game; this.theme = null; @@ -3298,32 +3344,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 +3379,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 +3452,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 +3475,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 +3490,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 +3556,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 +3622,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 +3657,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: { @@ -3674,11 +3720,12 @@ const Icons = new IconsConverter( { viewbox: '0 0 448 512', content: '', }, + }, convert: true, -} ); +}); const STATE = { Menu: 0, @@ -3690,12 +3737,17 @@ const STATE = { }; const BUTTONS = { + Menu: [ 'stats', 'prefs' ], - Playing: [ 'back' ], + + Playing: [ 'back' ,"button1",'button3'], + + Playing: [ 'back','button3' ], + Complete: [], - Stats: [], - Prefs: [ 'back', 'theme' ], - Theme: [ 'back', 'reset' ], + Stats: ['back'], + Prefs: ['back', 'theme'], + Theme: ['back', 'reset'], None: [], }; @@ -3706,42 +3758,49 @@ 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' ), + button1: document.querySelector( '.btn--button1' ), + button3: document.querySelector( '.btn--button3' ), + + button3: document.querySelector('.btn--button3'), 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 +3816,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,61 +3832,92 @@ 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 ) { + } + // else if ( this.state === STATE.Stats ) { - this.stats( HIDE ); + // this.stats( HIDE ); - } + // } - }, false ); + }, false); this.controls.onMove = () => { - if ( this.newGame ) { - - this.timer.start( true ); + if (this.newGame) { + + this.timer.start(true); this.newGame = false; } }; + this.dom.buttons.button1.onclick = event => { + // Handle button1 click event + if (this.timer.isRunning) { + this.timer.stop(); + event.target.innerText = 'Play'; + } else { + this.timer.resume(); + event.target.innerText = 'Pause'; + } + }; + + this.dom.buttons.button3.onclick = event => { + // Handle button3 click event + this.timer.stop(); + this.timer.reset(); + this.timer.start(); + this.dom.buttons.button1.innerText = 'Pause'; + }; + + this.dom.buttons.button3.onclick = event => { + // Handle button3 click event + this.timer.stop(); + this.timer.reset(); + this.timer.start(); + this.dom.buttons.button1.innerText = 'Pause'; + }; this.dom.buttons.back.onclick = event => { - if ( this.transition.activeTransitions > 0 ) return; + if (this.transition.activeTransitions > 0) return; + + if (this.state === STATE.Playing) { + + this.game(HIDE); - if ( this.state === STATE.Playing ) { + } else if (this.state === STATE.Prefs) { - this.game( HIDE ); + this.prefs(HIDE); - } else if ( this.state === STATE.Prefs ) { + } else if (this.state === STATE.Stats) { - this.prefs( HIDE ); + this.stats(HIDE); - } else if ( this.state === STATE.Theme ) { + } else if (this.state === STATE.Theme) { - this.theme( HIDE ); + this.theme(HIDE); } @@ -3835,74 +3925,88 @@ 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(); this.newGame = true; + this.transition.buttons(BUTTONS.None, BUTTONS.Playing.concat(BUTTONS.Menu)); + + // Show additional buttons + this.showPlayingButtons(); + this.transition.buttons(BUTTONS.None, BUTTONS.Playing.concat(BUTTONS.Menu)); + + // Show additional buttons + this.showPlayingButtons(); } 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.hidePlayingButtons(); + + + this.transition.zoom(STATE.Menu, 0); + + this.hidePlayingButtons(); 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(); @@ -3911,20 +4015,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 { @@ -3932,102 +4036,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.None ); + 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; @@ -4036,45 +4140,416 @@ 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; } } + + showPlayingButtons() { + if (this.state === STATE.Playing) { + // Show the additional buttons only if the game is in the Playing state + this.dom.buttons.button1.style.display = 'block'; + this.dom.buttons.button2.style.display = 'block'; + this.dom.buttons.button3.style.display = 'block'; + } + } + + hidePlayingButtons() { + // Hide the additional buttons + this.dom.buttons.button1.style.display = 'none'; + this.dom.buttons.button2.style.display = 'none'; + this.dom.buttons.button3.style.display = 'none'; + } + + showPlayingButtons() { + if (this.state === STATE.Playing) { + this.dom.buttons.button3.style.display = 'block'; + } + } + + hidePlayingButtons() { + this.dom.buttons.button3.style.display = 'none'; + } } +// 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 }); +} +//adding interactivity for the image + + let currentImage = "BulpOn.png"; + let imageOn = "BulpOn.png"; + let imageOff = "BulpOff.png"; +function changeImage() { + if (currentImage === imageOn) { + document.getElementById("bulp").src = imageOff; + document.querySelector('.ui__background').style.backgroundColor = "black"; + document.querySelector('.ui').style.color = "white"; + document.querySelector('#b1').style.color = "white"; + document.querySelector('#b2').style.color = "white"; + document.querySelector('#b3').style.color = "white"; + document.querySelector('#b4').style.color = "white"; + document.querySelector('#b5').style.color = "white"; + document.querySelector('.t1').style.color = "white"; + document.querySelector('.t2').style.color = "white"; + document.querySelector('.t3').style.color = "white"; + document.querySelector('.t4').style.color = "white"; + document.querySelector('.t5').style.color = "white"; + document.querySelector('.t6').style.color = "white"; + document.querySelector('.t7').style.color = "white"; + document.querySelector('.range__list').style.color = "white"; + currentImage = imageOff; + } else { + document.getElementById("bulp").src = imageOn; + document.querySelector('.ui__background').style.backgroundColor = "white"; + document.querySelector('.ui').style.color = ""; + document.querySelector('#b1').style.color = ""; + document.querySelector('#b2').style.color = ""; + document.querySelector('#b3').style.color = ""; + document.querySelector('#b4').style.color = ""; + document.querySelector('#b5').style.color = ""; + document.querySelector('.t1').style.color = ""; + document.querySelector('.t2').style.color = ""; + document.querySelector('.t3').style.color = ""; + document.querySelector('.t4').style.color = ""; + document.querySelector('.t5').style.color = ""; + document.querySelector('.t6').style.color = ""; + document.querySelector('.t7').style.color = ""; + document.querySelector('.range__list').style.color = ""; + currentImage = imageOn; + } + } +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 new file mode 100644 index 0000000..790fac8 --- /dev/null +++ b/script1.js @@ -0,0 +1,94 @@ + +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); + + // Display user data or perform your login logic here + document.body.innerHTML = ` +

    Welcome, ${data.name}

    +

    Email: ${data.email}

    + Profile Picture + `; +} + +function jwt_decode(token) //jwt_decode is a helper function to decode the JWT token received from Google to extract user information. +{ + 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.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/service.yaml b/service.yaml new file mode 100644 index 0000000..6cf4235 --- /dev/null +++ b/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: static-web-app-service +spec: + selector: + app: static-web-app + ports: + - protocol: TCP + port: 3000 + targetPort: 80 + type: LoadBalancer diff --git a/style.css b/style.css index aeae420..714af0b 100644 --- a/style.css +++ b/style.css @@ -1,15 +1,428 @@ + +/* 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 { + -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; + } + + /* adding position for th ebulp image */ + #bulp{ + transition: background 500ms ease; + z-index: 1000; + position: fixed; + top: 8px; + right: 10px; + cursor: pointer; + width: 50px; + height: auto; + } + + 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 { + font-family: "BungeeFont", sans-serif; + font-weight: normal; + font-style: normal; + line-height: 1; + cursor: default; + overflow: hidden; + height: 100%; + font-size: 5rem; + } + + .icon { + display: inline-block; + font-size: inherit; + overflow: visible; + vertical-align: -0.125em; + preserveAspectRatio: none; + } + + .range { + position: relative; + width: 14em; + z-index: 1; + opacity: 0; + } + .range:not(:last-child) { + margin-bottom: 2em; + } + .range__label { + position: relative; + font-size: 0.9em; + line-height: 0.75em; + padding-bottom: 0.5em; + z-index: 2; + } + .range__track { + position: relative; + height: 1em; + margin-left: 0.5em; + margin-right: 0.5em; + z-index: 3; + } + .range__track-line { + position: absolute; + background: rgba(255, 0, 0, 0.2); + height: 2px; + top: 50%; + margin-top: -1px; + left: -0.5em; + right: -0.5em; + transform-origin: left center; + } + .range__handle { + position: absolute; + width: 0; + height: 0; + top: 50%; + left: 0; + cursor: pointer; + z-index: 1; + } + .range__handle div { + transition: background 500ms ease; + position: absolute; + left: 0; + top: 0; + width: 0.9em; + height: 0.9em; + border-radius: 0.2em; + margin-left: -0.45em; + margin-top: -0.45em; + 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; + left: 0; + top: 0; + width: 3em; + height: 3em; + margin-left: -1.5em; + margin-top: -1.5em; + } + .range__list { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + position: relative; + padding-top: 0.5em; + font-size: 0.55em; + 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 { + background: currentColor !important; + } + .range--type-color .range__track-line { + background: transparent; + } + .range--type-color .range__track-line:after { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + 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); + } + .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); + } + + .stats { + position: relative; + width: 14em; + z-index: 1; + display: flex; + justify-content: space-between; + opacity: 0; + } + .stats:not(:last-child) { + margin-bottom: 1.5em; + } + .stats > i { + display: block; + color: rgba(0, 0, 0, 0.5); + font-size: 0.9em; + } + .stats > b { + display: block; + font-size: 0.9em; + } + .stats > b > i { + font-size: 0.75em; + } + .stats[name=worst-time] { + display: none; + } + + .text { + position: absolute; + left: 0; + right: 0; + text-align: center; + line-height: 0.75; + perspective: 100rem; + 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; + } + .text--note { + top: 87%; + font-size: 1em; + } + .text--timer { + bottom: 78%; + font-size: 3.5em; + line-height: 1; + } + .text--complete, .text--best-time { + font-size: 1.5em; + top: 83%; + line-height: 1em; + } + + .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: 1.2em; + color: rgba(0, 0, 0, 0.25); + opacity: 0; + } + .btn:after { + position: absolute; + content: ""; + width: 3em; + height: 3em; + left: 50%; + top: 50%; + margin-left: -1.5em; + margin-top: -1.5em; + border-radius: 100%; + } + .btn--bl { + bottom: 0.8em; + left: 0.8em; + } + .btn--br { + bottom: 0.8em; + right: 0.8em; + } + .btn--bc { + bottom: 0.8em; + left: calc(50% - 0.5em); + } + .btn svg { + display: block; + } + .btn--cancel { + display: none !important; + } + + .ui { + pointer-events: none; + color: #070d15; + } + .ui, .ui__background, .ui__game, .ui__texts, .ui__prefs, .ui__theme, .ui__stats, .ui__buttons { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + } + .ui__background { + z-index: 1; + transition: background 500ms ease; + background: #d1d5db; + } + .ui__background:after { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + content: ""; + } + .ui__game { + pointer-events: all; + z-index: 2; + } + .ui__game canvas { + display: block; + width: 100%; + height: 100%; + } + .ui__texts { + z-index: 3; + } + .ui__prefs, .ui__stats, .ui__theme { + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + overflow: hidden; + z-index: 4; + } + .ui__theme { + padding-top: 15em; + } + .ui__buttons { + z-index: 5; + } + font-family: "BungeeFont"; font-weight: normal; font-style: normal; + + src: url("data:font/truetype;charset=utf-8;base64,d09GMgABAAAAACZQABIAAAAAbvQAACXoAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP0ZGVE0cGh4byGIcgW4GYACDGghQCYRlEQgK7wjfAAuBRgABNgIkA4MIBCAFjGMHgw8MgjEb5WQV7NgLbgekMv6v8igENg5QCMa7I6pI6dn/H4+TI6pRucF/LYhNhovYtD4Krf6ybd4KjulSyDi2HjZEfrfyKtpdSpVGjIdn5NaN1SqtTduTgy2PuDLWjMCudfVcZNajE5NFViltnAyHDz5aQY/9KIf/iFKqiMiiy08NsWuNHaGxT3Kteqdfj2SUbAVpQ6QQooGSPMtAr3zE4COgr+M/Jp5/2u9/a8+dM9+c7JVIKJ5JGs0Sv0L9DSmN5KGYlEYlauL9wG/zn5W+IdY1EYl7dRyxAcHqOezADJa6+fdsjIo/de33u7JquDUr3ZpF9eZsy6Vv3n5Ii9EzmqragVH4fHPlZDGbveSSWYJscsdzRCkA6SrTVyPOqPr/Z1K0IGxVdd+bIh+ss5TyEAfykDerbO2vckwXma0Bn+NTRGUWtcrFcmqzqMFDTVWjEHrYb3gKFhFENBhGXzEslLDWl+NC+g9Ut6YZE8IiUOrBXKWTfCLj54mMxydSfp5IeVw+D5fD9XCG5vz30lywhwG8wKUpYPoubR/BR1YbOzNhpsSEfEJNflLibw248HD8cGyhEiolERKENjHEXjQienWD9uYSCvJ/y6bdpdsmDIqHREjeKbpC+eHf3J8ttL7gULha5qZRa0p3URE2QkfJvEiL0X+jRsbI/J+pZvv/LEDO8iJwDgq5OnVXlIpV5Vj5uZz9Oxjszu4ikhICcQxQBC+AgAKouBBgHQCHHIuSVEiQLkbeOaTaXe78VDpVLnPntjR8L9PZXtMr8zOEQr14vCtq2jnjxxJKoAjbWjaJxUBvNlKwlEsT/e9uZL73e/qz/g5HakVSCRIkHVNs638Wx0MvPaORQ4mtZqlXxY/8gwEQAB986zy6ALz74bRJAPiYq9oHAhgAqQAOISZEBeAABBD0WsedGJ5chHTbFHAjmwqA/7v903jc8b5FhvizM1NBcjJoc8vmc0Pa3c+SAToEdBLNK0XblKzqF9ci8YMgiXQAYkDgouIornaSG64d+zuuHAsbikQDE73ZeG77x4w1EMgzBsKRGJRil8/9s/HTqOUaiI3oBnvhKg3OiVQkif1txihAKdcZCpvAOIftLI9wzqBmQRcqp3Gk2Tkz6UcwgagZ3wAoB1tzIFCXwzgCVVzlhWGExap+Hvcn6Y3WyOARkeyCX0SNKR3RzNIpdO4MDGGYv8o4HK0RJa1jUkKZuEkqMjwc3cjjvadoOgA9AkpACQNnzZFlYqOMtqY5HjCKx8s7GD99IsiXAoH04HT3QcrHYvs+qMIx7EBwayBzijQKhmCS2VeV65VesIj40dE4FI5fdFb+GicK0JWRVbprkpwY62tAMTglcgzyNt9a50VMPAK5Esyx3kRf8poh6QutZw0v580DPUeaJqXvV4mWe1g/JTTj2YdePTWK9SLVu5XtXOkas4r2wO84ovmHCrBpzGxceGzRugNHKfO365A80kSZDkdxUAUDcVYlujjJkuoxsCl8mLuy0VzE3omhvBNLjI39alVq5XWkCzTckRI23TYV5nUB8fgCoUgilckJ0rXB6aQCjv8oNFoBJSe4JzPGz5NsbKqauiJYaB8rVntaewdqg4QeiwHmQbl+2SfmEflh4+7xEuK/emKRzdb19dSQ9kpS/in2T1cZHajbxwDXOwihUmpkLBMVNKCm1AEwx+ovKVyePiXZmS2UjUV0cR7WZdxc7FiCaWKWTFNcCMos9bUPxybM9KWqcOcQcqLN4ReGMSlXGF1k5+EAJqENpItZyCts0wN6DFLBFGsgDemm24xkiuR7UdbVCSWyws1V0tFx91YS5gddrf4KBDyIMGMd/vRPMCelPoyOGiwG2JNVSkh+W4/NdQB9AS+H+wLDX4yEE9WoEadwYfarSs7phlTQOrFjz2jLd1O8pHO/AwDZBFnalQEGh2KKTRgof8zs9cmR1viM/XEfZoZEiuknbriBs5A9yrRLooKJsFCXxi/MYMbZ0VCaacIjp83pNoZ+nmWwCpogvbg4sn3Vgg0vEo8zOqkbOlEEmusx5gstLCrYOn8c5twRQVKcERsDUp3kCnmNmYZwWxBVEJCxs1KIH7SQMsUKpMMmGj2XXbvUOgBA3xwC5VdvbT3R/USq/qBdOVmbW/BeDFhwtGrZDMHqh2tJqei+5saAWyOVAXgpCKWKpjEFChUpVqIMwDXiCp/Jarocd5hTmZAZ5dudhdJJtBKxuQBQmpUpuv133V+iXHETJcwkTwyd3H5jMjWc0vJv0jRETyDS4KQAMvSkmujFVwKKBryPAWoJ+9/PMUqkkiyVIEMWI4aJV6RCgio10tXrkq1HjzKrtpQjgGDY7r+LRjN704mmK1f4P0YrbiuWgzfc81kNRCmqCRpteZgZuw27R6rMBykmyoZvxZOlh5cuj5hYkeACuc8qSbb7uv3enENNVQ39DcBlW80t4Wqos4eB9kVpXrD1po+hFzvyz7j/f8C4ORdmoKcA0C/F93OeGgfZr+kLsNinMZVIMVM8vXDBrqNc4879AttmGSyHlbAatlP8zyzARN1vD2ydpbPsv+OzH51LKtOQuEqcJQ4SO4mZxFiy6jsel+9oj9Sn+HC0Omk0fSn3rpOS/leq+n+q+qf6P2QopqW9gQ46DF09fQNDI2Mmi23C4ZqamVtYWlnb2Nrh7usfwOSL/4/Bk8MjWMS3z+Ada/X85tb6BjLqd+Y5pyQd2SSFgnQ0HZ8zcm7uaJl5J5N5Oidzdv3lX8u9JNIO9dM0RWiW1uhJL1FjTnS3StW5Py8brITnNpAAJHVhsl8xvuEArkuHWao1Lr1M+45eoSWVpYfQhL6BSkMMpGtQW9jZELqspRNuNI5dai4ElHJLWLY7+mfunMFp/Tevk2gyMLqrMbzhxPuFp4cwJGZyPIQph01wMKtgBz9PGgBXDUsO28vR3npbEhOoIppmHaF9DFe+NXeOoOd3MAEWdALSmrd5E0XN5cZaZ3zCGLSx+S5jhXgTX4qpOhR65sVB6UlEYZVOKlEbt86tk9LifHkqojFRy2TIb4HPsE/eCJdmyFn0khBZGfv10tTSe4TIkjXv6dYaevcvBvV2fwcBjLDl2Tz3U2F6xyzWK2nXkLHQnPpYCjpYLJzzjPRcJGNBaDXTAXqaU4/etjzttw6bvlkhaDrtC3mv/5JZfeciewtsLlaby+HQQI5BPdU3NgZdF/ERCzaUM/dJwRLvlecJgBuNAc1x4Ny0Sx2BIgOWxiOiAI8EW0DGUhaZYigYFBmglsOrQWhWu+mZN/j8yQqIb0A65tQPxkPyUY/2uNc3RdRoEHdK80WRk8Dn406vUW5nSSPuCDqh8/ms4rW9FL7rrsFTXhDS/HNsMlmdHTWLt4N4bT9dToOZ2SAIAoRzYuPE/GqdclOv43mT0JuN52ZnU9XfmmkHLW8UzHpr/XTnyB2V9sO+0Nm/5+fDhTGC8HmVvN+q4XI26A1U15aimjK7tDITRC9ylR0uVs9hqxj95/PGlKmN5FNlBcVpVlq1BaY+lU+Hd7pei2zm2UgsPxCYCQSKK4Ug/8A8CsSU8bPRFMEQNzWdqiTbJwLgS633quow0QW2LM70EIDFiLATqwUEvro4BMX7MQkPWVqp+M0JIROeBPPXpwS3ovpoKqWoftglC/SSxG2ZtMiLTFp7+SXJTmUJ+y1BME3q+wbqBjaW/IYV2lpDKrdpSYkFsyXN7o2g1GwHIQuG8lx/V0y3p20ZsVXz8UIZyz2P6/BmMXjjV6MVPQxkwU567eTJ6qHZlOKIJgJp2SfTEtoMBpaeFY2nARtJwIWn3rmCToXBBq04x+zSXq+Tq7JLK8X9GxH4I3VG435I8N7wpQif8gsXesqJh55T5V43+FR+/kruPqeT3VQBqesfmN5JwAoqe7FMMucgTqqS0l5IhNt2YiGtuVSdBeNGgAFBxdzIMKa69ByLlCyG0Nji9hhtA265s7Z6ER3uG54E8Ijd8AmJlE5lEdkISpHFg+Apq1OKVKy3QwREBjoAeDsvcAkLPYPfpoOUQQpaGfhSPyD86DmjTvvVye7do0NOvUN5vnofnaNxYkfAqsWTt+dzo+ZKnwJGMGXljqPtSUlmkZbpfZ2OVjJ1dwGcaCpz/0Wlm0lUm85EWcgVa9J8bt9SwPoOU0LWiBm+HP05YkjO5JH1iGGKcCf5ifcKtIX2mznUVOcP2u/n2q5Ofs95VuT6U3Rzz+GwlKMkyEzvq1i6/wDSSTqtUhiMOhy0CYfqZ5fYNTWIHOeoF2zdxeR4m+D4HROCaUezoLESZSadEUwVb/ONbm4rZm3MWzkFIiU/jDbiSp6pYObA4butPoeJfSA5vLpnIzT67+Amz9O15xCRql+uX9ulXiZlEli9JPN9UIO0pkfmHz6u5ouqyEbPkudTin/fiE5OAumLTBxohOpTnssLosVgyHFWmjRlJDRF7kBCagjjZywfbrc6IwnbzruT+3QUaqcOU/x2binJO/qivjYhLqQ6i5fUhk1JJ9P0RBvMOjODL4ai5AfEWXLZpqynA9dAFVs5CaYvueKMrqMdd7b1f5KCP6GaO6GWFtkC9kUumWIpEkgH6SWOnMU53W98azxAyZcwympqZgaeTRudL0yjy3bW0dCL8CdCmXvByiTI7kULRpSOBgaCkmCdQBTRdF7R00NftDLc160cD9i0ff0LmrKR4L23ZCbl6iMusMTAKp5TXh2vcjv1PneX50+h1o09YJENLpmnWAj3zSI3uaQV7SnpamXWnNXVEuoL9Yf+VjVN3o6rzVN3k2A7yN6nuClYDaIZASum4N81GGBUCfvEdNjRk4MOEW/SdoTOy3SkVPK3IeiFBAZi28btUF9Ylg7e0x8sxeNDEVsn/u/AraiLdR8uR0mf4FOO/lx6EWc/GZ3REd3Bp98XEh2S3oawXbdd3Vd6/ygw32PlSnQlieNdSS6kXS3HLBFHeFkakm85tp5vNcQP7FJdDSXwQXXVgpqqPdiAoqqGGgEw2AUIRESugvGk1ALlNxxf9htl+SCzATivVZJliikgfSbUJeKW9tEVaooCAaPfBhQqNGa5D2AFYriYejpfsJE46w6KcJudfBn4Z+EWVMo0TwOR9NO5HbWQTnsCwnV7auc9/Xt18N6aZQZSvbpWbR9Lt1ppqOHQSu/ksQHZbYDRGYBBxhJg8AkwuEOaI5EW+4x1nvf8X/3hlc4TJ/rkOQorFEFAfDQl62L9hen1xbfTKzAfW0oYR8hZRPopVGJptxqKdQ7KCQ30dshpCTv0DIQEFW1vZaHRdidYgU/bpy0Bc8Xp9cJb6fKMj82xHKe4KtjE02/VQ+aGYjkpQvH/rkzCC26J/TX1ezLwA5omWzxjSsZGLg4DBq8ABT3mUaIe8QiTChhcAQz6mfPqdsQFppbBPwwSoFAFGP0uoGAU0T+5MGI+KVrkSAcIlO9yfdl3jfDYWdG/Tz66/AwVgCJ8AwTsmDSOHgdl2pX4qV6ALj9Hw0K+BRRs+8Cdr8CyBQy+YSYDPVMcPzm69yuaARj6z5FQ3w+8B5fzTmKFhsytYpO37N6tOkz1QpH3mokHH2LAjCV5STc7PFtxzqK8hdZIK08+O1MxZEq/+9KUpVud9hww8AwGDP4AAsGegEEKEQOb8d08ZmSxu5bfOdBatnd9eckq0nkSVQKKhj5BCLob9gFx6PF+TG0zr6ewAMSugRjJZsjbe7kPgAEx3BjvXzEat1swzue2lWd8tJqc/GQFBZPWyApMDqR+tJ6aZIFi43Fc2MsYTEo6zgAYTE4aBItongTKjw9NykIyUXuzCBj9DMp+AU4+vKgsw9aAmEwzJyUkUHoHpe6gTgEC7wCjRwyNDQ3MtR/8b72+atBTf1TPBTBwzrkLgOnEuunJq5mLRFPiHDPcwp2V8jB1KnGB+a+NPmfLAwNdJVmeJlsdSLXym+tKznefOnayBDBm3v6LgIE1NzBRbc2ZrGIWVHDrjA5PlBC6dHjAbjPvLSCgSFxW9yY+Vv8FmIpL2wBB/Njpr4TEwZk9feTO5ROjlYDoZeb0ji8/R1e23A/8mue6e7oZyObJ+yqfGzt+ks9qKUp9bDk8+dayOJXVciEHxQeH4/N3UdeHB6/Pi137Vb46YPQqIBVFL/EI2Wnho96sn1C67pjemv6ZDuiPMgFzinQW1ezuCEMcNdlbPLPcTXZk+4n129ov6Gf5sTPdsjw5Wx1IYYq7O2tE3pYF1fyPrNHRz6wo8qKLnagTQyBw0KWlUxeRlsVJl8paRg8EBqegodSU4MADVC8pPvckn9UsTHtkMTz51qI4DWf5vePXhNqEwXvAwP3a+Qo+yufHL0io+PBgDeouVEOUwRsdadCf5HvDds5B1UWXxXJPU0NrUV6OlvGsJJEGckCPd+ti31NCxGy3zng6yINRYr9wL0d7fwGu2uwFYGDAIiJaQaufJxPgBWucOCEPGxLcOtkSBJGw3bri6SAHtETjWUmOdlFuKxHQbrqXaIRbt6iP16JpSTI+tHLoYEOtkq5brH6grmygTmyz2cPdVPIEiQMY3kmhIY9mQdbiLtkWm7Y4jO0v0uU9ecwiPhr7sS3KfKwtNu2oVU3hxbYBYUWJfVQXz5PaSGe9DZN8UAdjQbbHTf19+zCz2yNnHIIrnr57vu4+IbwGC1we4WO9TfsLHMZrI6giRtnJHeZypLIMnqY1vReSR8QjNAYAbSQN1Une0GOjCQhwGb5ASaWIe13k6ph9JHcLjcAm0Lb4VvLc+1WpOj3b1hsTaJl2ukAmp6N58tD8nrMIYOfM8JDE8GYkAN5yaL2BOSMJhiIz+/699vUCPtbghR22l9nP2BfsOrL+uNqH+3prgOqO/BQ+yL22PCxGABNHfmJK2B/Yb5ifjK+6xxnfVThdNTW2XDPMqdV2dsqlCNbhp7vjr8WDInyZpMc31Neyc2Yf9DKudDTFxx6wvcx+wZYYLU3BRtM71YwQt5taRIPWlanAqUMo/TpRoGiGl8b7eKBydvgsuiB5V1waXpr9j6mSi1+BJ7oMWPCDweUHP2H0W9eeF/3CN2krSn1ufXTkqXX8JFabE9GnsuJ7+fZVCt1Ka6tYfHvrulOa/mPfwBxSg66lyV/L0xeQP/eFiMXVQRBSI52yuvFYXNFDCic86iSEJIK47riHwiHUneixiIAVL3Jp5z3EGnzBUDHpS02bwJR7tfnXIGmo8uTRNc2WAYE9vyL29k995wDfUpnx3Wpm+qtV1KbAMa0WpH61np7+YV0JIF4pEghEK5YzBCKF+ILb1M7eGZEaOXdb9ZU/Dzfz3ItRywhIAw+rUdvMpr1Qr7bXU7soJKGLt1PeCyhYct2W11j/svhxyJJAsMQwQj3i+TgOPD483RIZyNr5PEPmlI78EcA0fSiYG+jqUeukOV/zI6Ods5HhPz2nPbYv8x0M3R8X2fRgSkMPxrqFss6axjofmDt61eA1IM3vSa8NALMtKPii7wWDjOWUdBFj8KtNjWUrCD8LdqvgoJXUx03yqce5EQqAo7xrRyTFTy5eTUHWP5KWmaWicNdh58pk5jrHX+qOmqc+LVeBCugvp4ymeiPcXuvsaDULWexD5qqfJiEQ3FcHJ2nMnwGQHNYrGm7xKt0pqCLqnhvh4WdIQAgG/pEeOXdo8uiz2niihtt6j3s27xafFnFx4K6//w2Fz++G9ziqlK2vhOT3RCsQFexnwvKvuKsMgrRZ0M3yqdINkoHbb8g00KQ3nDMIDzf/v5cCWcF2btJK5YzA9/PXPfeuDB16t/Ib6iIVlRUEdckSBN6UnlTGp/EvcAfWdmM3lDLb84YS4RnBQ6C8833YcRXroYKHMCoO3rR9Udso4yKU6BDcyAIUyMOuSuO3xgHX+cmA6eGeMLkxwIAHfRSMnrzPxQ3Cyg3fGqDKcgSBY/THgOkE7Ze9QsjLyStubWrNKabnFFfceACyq6ILuD1ebDHKFLPjxbgF0bIED3DVpUjXkQBD1dbR953U0tI6HRRrCIta2tNzZOuMbXK187FXUhwgxkIBjWGg9xjfWSe7OMq8MyEvN3HJkTCSZPOSgzL1iGxvaCi6/HwUb69k529UlIEbvY1KDoMDouOoGhc1PSbKeYkCCHfKyU1uLjOSbP2OQ7NFIxB7Nq54rI/rJld7Xztl/FATQpfDqDrkBuf8cw+NZLWYVYr8CiHlaaSsB78gNafywMPb4CyHsH8MqX/Sq70KATJf++DnZNzSK9ILyvkGEY/6iO8HXIpVtytPglImTqwOl1GTViLkdw60lXUNBGamNaj3UtEIntQS+FZ8o9q80meISvPfflVulJ9XtrpbNwRg/jOXhxPosbPGC0wNzxJtLT/s4YS0uvah5dleT+7N/J5IJXb2RfjmMc0NoPzu7nbzoTbF4krUwPoxbcH5D1s0jddfUx+IHtOZITYK5sL/7GlZC3UBhGKArCYv1Hqog7oD+wygmIYWVtxq7DR0FWlQTdOONgyl1JSgHkQSw9ONTVGuFX6fIyyqfc3nKiVnNjpxCD4/PmJhM/PKrgaKILQAHa0wXWClzU6yJaxZhRDbyAmp+RDkT/tdTvs48kUPHnX+DJ7zfWWM1bVXbWebz0xr9lcnL+XqVk6st1eZyxtvkKykSS9dBgdvO77TFA7Zs+tPP15qKS/AEwZ+z9voD+L7XvzP/9IMLi+Q43FR3pf3jzPGE95QxtwwKnSxX/4Tpbph50IAt3cmt7F1btakl4Pnu37+8SOeXQ7g+M7mOMmb5cZY4T4MKX44TuBSX5UghHgdjvTNuc0OPJU3Uy9Jsle7zOtK7E1GM4mefJ56nhlEMtNHaRvPLgexw3HyR4tTEnb6yd7xU0/4YP/0ZtHonBLlynT4RRMNSapWdve+MKLpydOXr16/ydftpIqbzDGhvrQzDp2lL6GnzM3lQU1pMWh5q4+JlovONRzcdDj/DF9xR60V5Xbn2Xy3YQVISvp8d8HPRQZUMX2rKdofSiQfGkN3blHNdjJBeBlJ9KdoOfE6yyA8en5mpeY1gNWKR1AEOQ1nvjjxESjqVqsOvtydLPkfO6sZYf0qyumEHGcBwOfco6I+POAxJprGdxPg4jZyB3f41DhTzrbmSn5+ADVIPo+Pm90LU8vFJSknD6SypZl4kSSFWcand8Cvd7Ya2S1f2mPLW3O5zYZO0DY4eNjxkW/yYDmn4fmxTOg2/waWA5/9TjB59pglIgSIzyPbZwEAL2kOsHBe+0YkivIq2JiO2alphxFA5NjQGOp1viVySkVYUtxoz0PsSJI1/LRAdoh6QjMHorS0D1MOc1Aec7DmFNBCBQYmgMeSKps0JGNVwisxzjHZJwTc5QEUblfIOkJkHRRmW+GEYlpuhXK0pNu0Itko/vMaOyxgtacbHRCovIzz1DXNgVkPvVZ9ENUhYhTas4Q8y57Ih0WRJDf1yYQDT9XgK03mpuVCBUaDH1rtG9xxv1vShn/khrACeBOsCbMlbxwWmsSZ3XTs3ICTZpJmcZfa+aFui7QWus0DvDPrQzYw5XDMTniA81jdYSbXGgZf4AjLDhqx0AEHihKwEjLHUVUTZgZKTbFFgwYHi47LAryAzQbhcTzbyccKPrYY1Jit+GpUQsPbTrCpMqUbd2TNnoZx1nlkeRRbZ00kNQ5G2XsIEg50KhisIgkOHwO59qCB0IURxA0stJrFCDlzSzpmRx1bFLY51l2dQXVFI5+aDPMLc+V+E/vmHsINLBoybvUim/GFsIMUzxb19KIsE9Ra3/EIGoMxHuvM5CtzZTUOK0ElcH3+GTNF5Eo6R4DnjRyPKDLHlSGUe9ZQdAzslNWTYGpOfqte3hPpIWQSLcsZ69ECxpHJ6IZ81SZj2Mkaz1egwhEynxaq9Tq/Be7OXt7s2Z1BjltDYRWL8qa4mWKE5BCB8y5XbDneCg8gGM/LC3iMqQUwBVO7Ydl4DmuswhrDyA3c4MNICSVKQFiFF2Kj8YZLPVUVa4HS6cP90uPhuF5iVUbQ7yraHsyG5FkTu75iZlkHK6vkOfPy7oACZo5re2zII8lmxJdpN8HU/sjUYDCDL4yVsm6WTQlh2Qo+M/YspHoYANylETPmmiLjlqt4Yh5HEuNTMOc2Zd2DPOj2QKf7rsM45WeIM4cZZqobGbBGMMZAU14+wfyZSUwkfJFmcxInddQkptGOJ16INgCBvFBNtQzQKuTJdbKux2MyNGPdr1BsiyRm0Vo3bGCQC5qbPOiFhd7u2OO2kWE5yHISm0IeSWCz+8jcTEKUlg1ehJRzR4h4h4N4QR7RVigYM03BVfSJFc6THgl6TxLUC4iUY+2dTAJMJD8FCPUyEt/rRHGc1CdHwxwqrnFUpgGwXCcbYYMCJZT0LLLrXP1CHDaDbJszZW9+ywW0UE4yJi5mli24tZiT+1bM4axKp8liFnPH8ZygOeEEkk9A7cvaB3A/gT61IN1RA2K0GwkMxZfcWVqsMcWe18sybY8F7G5FUtdoIBytR7AxQgECUvEqKHqFDOg5WHMC+dnpurdL0LPX0nsaS9vmOJT+bGLTqDgh1VCBMECGUvYrScG0AWHHGtd/5VQ0C0Y4ccUL0IL0FCMOM1SRR64Gsw5v5RmT4KclK3xtxkWbMJHewK62Shd1wixcKDBGhp0ZF+lZeLmjFalle858FMlhhimgidt2lTDYFLsWSNkEhNMktJoJMye1kg7JkYdCOUfS5qUxo76y9S2C7Xi4tNfyeUT4u5JGiAmXgbd3A1jmwe50Lz9o0n3VkShyZhD0eM7h/Dh6TiQ52dol9rc6C/YGDuwNwzA2MHtPrph54b2mucILh2cg3b5y5p/aB03xhb4PGqfsz2Ld5lQQv5kmmd4hxoVYOvAYUVuQem7NRHbdtWUdWqw7TmyevcijCywrjPReTcl0raR3cdTb97LudaUfippnDwywlvdUp5T3QQgN56vAVfN87oHQDIwue92fBNRd0vtNq/6X1f27AW0CkHoz+VMGvB/Falb3nHpuZcaAlYYCsXdiMuXqV/gdXqu1wBGCMq4E72hVH0Jj6qTe2ZJUwzH0eahCYS1Quu1tkrf+QSwVT15m2wdCxBQLu7SyWVcIwIYYtzAds7emHUeAubV9pgCQ17Aci7R+FSHrSezOruBgAv5U8NU/Du+HDmQbWJvQwoitPwaLoLQuxxnb8hvKOJHhGkOGMig3Y3nZ9wRghavlIdlk4PyBC8MiOtV7dAninvmpOFVrXn34ie7U4KMBPnpyqma334+TUrwYZRpJtKe0yJC7s5G7fEt2s2k8LO3h77xFoq0jGVp4hpL92e7f2ZOeskv28LNz5Uo49PMCNspteUZhH4rxvC+/jXObYPagXAXzfr4bR5OIE3Gik7b3LWfFant7yC7sJ7ZbW+9lDtpWReanCUix9ueIokcI5Po2bVB2ABGADlX5JMQRA52b7RALTX6PpMd+uLGbR4Yf4nw9/P+U4+fuB+hw8LCocfPfHug34f92TNS22MzcvjHoF8o9A/oO/GXcUpW2BzBaDZzzPZYHM9k+4Z/cME9ubNaTgvWps6dE3mkT87tXrb1IqLeF+5vMlW2+0lnrDsdWNW+dLlwqTFfMbta7RWk1Ww+6AYUVH8vD1WNzfC9aH1HqdWeWRu/+Dhs7nNl/dG4t+Ug8rn/kUB1rMRvOzo5XGTTZ+lobBHYWENtQbptGH49uetwoOra48XqbNYJtMYhdW5T4g2azHRi7Xrl7YtSbaVKHLd6RH4P2OUw49yhLPzlyJovfAPf8Q0VVRNUzC4mQFLcUwSY4ZP5z3pw64eq3Jmij2Tx1gO+D0KlvpRv6bLHPzrLptknKThCNcThaJngZrkRF4wXUWtKJhpTz0CrPr+gktRHpfTyixPaEDP4No3N65bUks/8ET5Wu35YAgBh0wpHMwavnQsXgIdTcXkcjOwG05nMtOiUtT3pvICpqF8vgtzGGTfpvJY39cctsfAJcLEwOsM04kLF2Mx12fvjFbf2f6t67LvsF99sOtuKn677bYbMB80NH9/WAiQ9+d5jizUAdWe3rH9rBao8P01F+YHU1XfeH8qPv+fM7EKerEXXGK8VAv6ts9DN8lVkLKUo0OykCLEXSofigrEU9NR9iss92e5id7ICLWT1Z08prQxfgl9tIGnOzC/sv7nAqgT/MHs1n5Zb6jh9cL5oWu3zgMtfLTd5pU70X4LhrIecYF79+kLm+WLay0er6CJmAOWaRs5g3zBsGqLrrm11TTMnw+9kfV9Aea5fG0W//QwPSCILy/zr2jYFBJiEuVsw4xR7KTibpuvON6/ceftGiGk2Rfo8WD2G1olMANnq0qzHCgewynqy1yiEYW4V+lULJuucbm+AhKkUKk4oNP7kLK9q9sHKTqretwbWUrnvWNtFs86AVfSSeZ6t4/JhJzAxPsTak/ZOVoaqj5dJKmrIjybAjuKHJc1lP2ewg0w0TsUBPRLLcZ+4kWmg/k4Tm+1n4uMqNAX4yoRkuU/g41JgxbBJGpLAdQ/1N76gxdUKjTZQWxiZ/33V2zsSfuSrkK4I0g6Sq9bE/hi6YMn1zoRJoZ3pK/I0iOH6FPRLG12FmeBI8dA3to+AsTjUDNeLBjyUzQfcd5b+vgZEkTryEn8vYmxYHOd8935sfu6X0a1uuQqUq1WQ1atWp16BRk2YtWrVp16FTl249evXpN2Dbjl2mqEhNGtKSjvQkkEgGMpJEcRSfhCQmyb3u86CH3O8Btyc5KUkdjoPDHmfd9uR6bdDtqq3tq52yQRppJrEruc71bnCjm9zsFre6LeuLutpJnfBLPS5Wk995OakfBllTcfl54GH4//sLzjTxvMyu3gN01nm+K5Xn8mPraxOxNU9/PHSj2Ki8tIrYxE7GsPJErLKT9+J/6p2pRD1dOcqIUuR6xUg5ChGTRIHvi4OikuACaV0MW4qt0qFRtruOL6bhEKnPh/Ncrdl75c3WmcyPRy/pwf/m6eokRYPktbt3TYHTWmxVAgAAAA==") + 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; box-sizing: border-box; - cursor: inherit; + cursor:pointer; margin: 0; padding: 0; outline: none; @@ -18,9 +431,12 @@ font-weight: inherit; font-style: inherit; text-transform: uppercase; + } + *:focus { outline: none; + cursor:pointer; } html { @@ -35,48 +451,59 @@ html { } body { + font-family: "BungeeFont", sans-serif; font-weight: normal; font-style: normal; line-height: 1; - cursor: default; + cursor: pointer; overflow: hidden; height: 100%; font-size: 5rem; + } .icon { + cursor:pointer; display: inline-block; font-size: inherit; overflow: visible; vertical-align: -0.125em; - preserveAspectRatio: none; + preserveaspectratio: none; } .range { + cursor:pointer; position: relative; width: 14em; z-index: 1; opacity: 0; } + .range:not(:last-child) { margin-bottom: 2em; } + .range__label { + cursor:pointer; position: relative; font-size: 0.9em; line-height: 0.75em; padding-bottom: 0.5em; z-index: 2; } + .range__track { + cursor:pointer; position: relative; height: 1em; margin-left: 0.5em; margin-right: 0.5em; z-index: 3; } + .range__track-line { + cursor:pointer; position: absolute; background: rgba(0, 0, 0, 0.2); height: 2px; @@ -86,7 +513,9 @@ body { right: -0.5em; transform-origin: left center; } + .range__handle { + cursor:pointer; position: absolute; width: 0; height: 0; @@ -95,7 +524,9 @@ body { cursor: pointer; z-index: 1; } + .range__handle div { + cursor:pointer; transition: background 500ms ease; position: absolute; left: 0; @@ -108,9 +539,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,7 +554,9 @@ body { margin-left: -1.5em; margin-top: -1.5em; } + .range__list { + cursor:pointer; display: flex; flex-flow: row nowrap; justify-content: space-between; @@ -131,18 +566,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; @@ -151,63 +592,96 @@ body { height: 100%; content: ""; opacity: 0.5; + cursor:pointer; } + .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); } .stats { + cursor:pointer; position: relative; width: 14em; + cursor:pointer; z-index: 1; display: flex; 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; } .text { + cursor:pointer; position: absolute; left: 0; right: 0; @@ -215,34 +689,45 @@ body { line-height: 0.75; perspective: 100rem; 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; @@ -256,12 +741,25 @@ body { border-radius: 0; border-width: 0; position: absolute; - pointer-events: none; - font-size: 1.2em; + pointer-events:none; + cursor: pointer; + font-size: 1.5em; color: rgba(0, 0, 0, 0.25); opacity: 0; } + +.btn:hover { + color: rgba(203, 18, 18, 0.5); + transition: color 400ms ease-out; + cursor:pointer; + + color: rgb(245, 245, 245); + transition: color 400ms ease-out; + background-color: #541a8be7; +} + .btn:after { + cursor: pointer; position: absolute; content: ""; width: 3em; @@ -272,21 +770,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: 0.8em; - left: 0.8em; + bottom: 1.2em; + left: 1.2em; + height: 60px; + width: 60px; + padding-left: 10px; + border-radius: 8px; + cursor: pointer; + background-color: #541a8b24; } + .btn--br { - bottom: 0.8em; - right: 0.8em; + 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; } @@ -300,11 +872,181 @@ body { pointer-events: all; } + +/*3 buttons*/ +/* .buttons { + position: relative; + top: 5%; + left: 50%; +} + +.btn.btn--tc { + margin: 10px; + padding: 10px 20px; + font-size: 1.2em; + background-color: #41aac8; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.btn.btn--tc:hover { + background-color: #2c7a97; +} */ +.main_B{ + padding-left: 35%; + +} + +.main_B{ + padding-left: 35%; +} + +.sub-main{ + width: 80px; + margin:2px; + float: left; +} + +.btn1, .btn2 , .btn3 { + +.btn3 { + text-align: center; + cursor: pointer; + font-size:24px; + padding: 10px; + +} + + +/* +/Button One/ */ +.btn1 { + padding:20px 60px; + outline: none; + background-color: #27ae60; + border: none; + border-radius:5px; + box-shadow: 0 9px #95a5a6; +} + +.btn1 :hover{ + background-color: #2ecc71; +} + +.btn1 :active { + background-color: #2ecc71; + box-shadow: 0 5px #95a5a6; + transform: translateY(4px); +} + +/* +/Button Two/ */ +.btn2 { + border-radius: 4px; + background-color:#ede9e7; + border: none; + padding: 20px; + width: 200px; + transition: all 0.5s; +} + + +.btn2 span { + cursor: pointer; + display: inline-block; + position: relative; + transition: 0.5s; +} + +.btn2 span:after { + content: 'ยป'; + position: absolute; + opacity: 0; + top: 0; + right: -20px; + transition: 0.5s; +} + +.btn2 :hover span { + padding-right: 25px; +} + +.btn2:hover span:after { + opacity: 1; + right: 0; +} + + +/* /Button Three/ */ +.btn3 { + position: relative; + background-color: #ebe7e0; + border: none; + padding: 20px; + width: 200px; + text-align: center; + -webkit-transition-duration: 0.4s; /* Safari */ + transition-duration: 0.4s; + text-decoration: none; + overflow: hidden; + + position: relative; + background-color: #ebe7e0; + border: none; + padding: 20px; + width: 200px; + text-align: center; + -webkit-transition-duration: 0.4s; /* Safari */ + transition-duration: 0.4s; + text-decoration: none; + overflow: hidden; +} + +.btn3 :hover{ + background:#fff; + box-shadow:0px 2px 10px 5px #97B1BF; + color:#000; +} + +.btn3 :after { + content: ""; + background: #f1c40f; + display: block; + position: absolute; + padding-top: 300%; + padding-left: 350%; + margin-left: -20px !important; + margin-top: -120%; + opacity: 0; + transition: all 0.8s +} + +.btn3 :active:after { + padding: 0; + margin: 0; + opacity: 1; + transition: 0s +} +/* */ +/***/ + + .ui { pointer-events: none; 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; @@ -312,11 +1054,13 @@ body { height: 100%; overflow: hidden; } + .ui__background { z-index: 1; transition: background 500ms ease; background: #d1d5db; } + .ui__background:after { position: absolute; top: 0; @@ -324,21 +1068,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; @@ -346,9 +1101,293 @@ body { overflow: hidden; z-index: 4; } + .ui__theme { padding-top: 15em; } + .ui__buttons { z-index: 5; -} \ No newline at end of file +} + + + +/* 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; +} + +/* styling the navbar issue Back option on leaderboad page #112 */ + +.navbar{ + border: 1px solid white; + width: 80vw; + position: relative; + justify-content: center; + z-index: 15; + align-items: center; + margin: auto; + border-radius: 40px; + padding: 20px 0; + background: linear-gradient(to bottom, #fffffff0, #7ba9b3f0); +} +.navbar ul{ + display: flex; + align-items: center; + justify-content: space-around; + flex-wrap: wrap; +} +.navbar ul li{ + list-style: none; +} +a{ + text-decoration: none; + color: black; + transition-duration: 0.3s; + width: 100%; + font-size: 0.7em; +} + +.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