diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000000000000000000000000000000000000..e24a7694c4f59404174dfa940e4be583ff0daa44 --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,33 @@ +# Privacy Notice for Hubs by Mozilla + +Version 1.0, Effective April 26, 2018 + +## At Mozilla (that’s us), we believe that privacy is fundamental to a healthy internet. + +In this Privacy Notice, we explain what data may be accessible to Mozilla or others when you use [Hubs by Mozilla](https://hubs.mozilla.com). We also adhere to the practices outlined in the Mozilla [privacy policy](https://www.mozilla.org/en-US/privacy/) for how we receive, handle, and share information we collect from Hubs. + +## Things you should know: + +<details open> + <summary> + <strong>Your presence and communications are sent to Mozilla and other Room participants.</strong> + </summary> + +- **Avatar data**: We receive and send to others in the Room the name of your Avatar, its position in the Room, and your interactions with objects in the Room. Mozilla does not record or store this data. You can optionally store information about your Avatar in your browser’s local storage. +- **Room data**: Rooms are publicly accessible to anyone with the URL. Mozilla receives data about the virtual objects and Avatars in a Room and shares that data with others in the Room. +- **Voice data**: If your microphone is on, Mozilla receives and sends audio to other users in the Room. Mozilla does not record or store the audio. *Be aware that once you agree to let Hubs use your microphone, it will stay on as long as you remain in a Hubs room, unless you turn it off.* +- You can learn more by looking at the code itself. [Janus SFU](https://github.com/mozilla/janus-plugin-sfu), [Reticulum](https://github.com/mozilla/reticulum), [Hubs](https://github.com/mozilla/hubs) +</details> + +<p/> + +<details open> + <summary> + <strong>Mozilla receives technical and interaction data to improve performance and stability.</strong> + </summary> + +- **Technical data**: We receive and store data about Room URLs and names; the type of device you use to interact with Hubs, as well as its operating system, language, the name and version of browser; and other data to load and operate the Room. +- **Interaction data**: We receive data about your interactions with the Hubs service itself such as the number of Rooms created, the maximum number of users in a particular room at one same time, the start and end time of a user’s interaction with Hubs, the amount of time a user interacts with Hubs through Virtual Reality, the first time in a particular month or day that a user begins to use Hubs. Mozilla uses third party services to store and analyze these operational messages. +- **Error Data**: In order to diagnose problems, Hubs sends Mozilla logs of error messages (which include the Room URL, response time for requests, the page you were on when you encountered the error, your operating system, browser information, and may include your IP address). +- You can learn more by looking at the code itself. [Janus SFU](https://github.com/mozilla/janus-plugin-sfu), [Reticulum](https://github.com/mozilla/reticulum), [Hubs](https://github.com/mozilla/hubs) +</details> diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000000000000000000000000000000000000..97ff9349caf521eb0f751c64991289e891bdfca2 --- /dev/null +++ b/TERMS.md @@ -0,0 +1,61 @@ +# Terms of Service for Hubs by Mozilla + +Version 1.0, Effective April 26, 2018 + +[Hubs by Mozilla](https://hubs.mozilla.com) is a real-time communications platform for Virtual Reality, Augmented Reality, Desktop, Laptop, Mobile, or however else you browse the internet. These Terms of Service explain your rights and responsibilities when you use Hubs. + +### 1. Privacy Policy +The Hubs [Privacy Notice](https://github.com/mozilla/hubs/blob/master/PRIVACY.md) explains what information we collect when you use Hubs by Mozilla and how that information is handled and shared. + +### 2. Communications and Content +Hubs allows users to send information (such as audio) to other users. By using Hubs, you agree to give Mozilla all rights necessary to operate Hubs by Mozilla. This includes, but is not limited to, a license and permission to transmit and display the information you send through Hubs and to gather and share information as described in the [Privacy Notice](https://github.com/mozilla/hubs/blob/master/PRIVACY.md) for Hubs by Mozilla. + +You are solely responsible for the information you send using Hubs and the consequences of sending that information. + +### 3. Conditions of Use +By using Mozilla Hubs, you agree that your use will comply with Mozilla’s [Conditions of Use](https://www.mozilla.org/en-US/about/legal/acceptable-use/). Mozilla reserves the right to remove any content, suspend any users, and shut down any room it reasonably believes has violated these conditions. + +Please also be aware of [Mozilla’s Community Participation Guidelines](https://www.mozilla.org/en-US/about/governance/policies/participation/), which address participation in Mozilla communities. + +### 4. Mozilla's Rights +Mozilla does not grant you any intellectual property rights in Hubs unless these Terms specifically say otherwise. For example, these Terms do not provide the right to use any of Mozilla’s copyrights, trade names, trademarks, service marks, logos, domain names, or other distinctive brand features. + +Mozilla distributes the Hubs software under an open source license. To learn more, you can read the [license itself](https://github.com/mozilla/hubs/blob/master/LICENSE). + +### 5. Services Interruption; Term; Termination +We are continuing to develop Hubs. As a result, we plan to upgrade and change Hubs over time. To do this, we might have to temporarily suspend Hubs and it is not always possible for us to give notice. You will not be entitled to claim expenses or damages for such suspension or limitation of the use of Hubs. + +These Terms apply to your use of Hubs and will continue to apply until ended by either you or upon notice from Mozilla. You can choose to end them at any time for any reason by discontinuing your use of Hubs. + +We may cut off your access to Hubs, either temporarily or permanently at any time for any reason. This includes, but is not limited to, situations where we reasonably believe: (i) you have violated these Terms (ii) you create risk or possible legal exposure for Mozilla; or (iii) providing and operating Hubs is no longer commercially viable. If possible, we will make reasonable efforts to notify you through Hubs. + +In all such cases, these Terms shall terminate, including, without limitation, your license to use Hubs, except that the sections with the following titles shall continue to apply: Indemnification, Disclaimer; Limitation of Liability and Miscellaneous. + +### 6. Indemnification +You agree to defend, indemnify and hold harmless Mozilla, and its respective parent and affiliate companies, contractors, contributors, licensors, partners, directors, officers, employees and agents ("Indemnified Parties") from and against any and all third party claims and expenses, including attorneys' fees, arising out of or related to your use of Hubs. This includes, but is not limited to, claims and expenses from any content you transmit using Hubs. + +### 7. Disclaimer; Limitation of Liability +THE SERVICES ARE PROVIDED "AS IS" WITH ALL FAULTS. TO THE EXTENT PERMITTED BY LAW, THE INDEMNIFIED PARTIES, HEREBY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES THAT THE SERVICES ARE FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, AND NON-INFRINGING. + +YOU BEAR THE ENTIRE RISK AS TO SELECTING THE SERVICES FOR YOUR PURPOSES AND AS TO THE QUALITY AND PERFORMANCE OF THE SERVICES, INCLUDING WITHOUT LIMITATION THE RISK THAT YOUR CONTENT IS DELETED OR CORRUPTED. + +THIS LIMITATION WILL APPLY NOTWITHSTANDING THE FAILURE OF ESSENTIAL PURPOSE OF ANY REMEDY. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF IMPLIED WARRANTIES, SO THIS DISCLAIMER MAY NOT APPLY TO YOU. + +EXCEPT AS REQUIRED BY LAW, THE INDEMNIFIED PARTIES, WILL NOT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR EXEMPLARY DAMAGES ARISING OUT OF OR IN ANY WAY RELATING TO THESE TERMS OR THE USE OF OR INABILITY TO USE THE SERVICES, INCLUDING WITHOUT LIMITATION DIRECT AND INDIRECT DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, LOST PROFITS, LOSS OF DATA, AND COMPUTER FAILURE OR MALFUNCTION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES AND REGARDLESS OF THE THEORY (CONTRACT, TORT, OR OTHERWISE) UPON WHICH SUCH CLAIM IS BASED. THE COLLECTIVE LIABILITY OF THE INDEMNIFIED PARTIES, UNDER THIS AGREEMENT WILL NOT EXCEED $500 (FIVE HUNDRED DOLLARS). SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF INCIDENTAL, CONSEQUENTIAL, OR SPECIAL DAMAGES, SO THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +### 8. Modifications to These Terms +Mozilla may update these Terms from time to time. We will post the update Terms online. If the changes are substantive, we will announce the update through Mozilla's usual channels for such announcements such as blog posts, forums, or in the particular service itself, in this case: Hubs by Mozilla. + +Your continued use of Hubs after we post the new Terms constitutes your acceptance of the new Terms. To make your review more convenient, we will post an effective date at the top of this page. + +### 9. Miscellaneous +These Terms make up the entire agreement between you and Mozilla concerning Hubs. The laws of the state of California, U.S.A (excluding its conflict of law provisions) govern this agreement. + +If any portion of these Terms is held to be invalid or unenforceable, the remaining portions remain in full force and effect. If there is a conflict or ambiguity between a translated version of these terms and the English language version, the English language version applies. + +### 10. Contact Us +For support, to provide feedback, or to report abuse of Hubs or violations of the Conditions of Use, you can email us at [hubs@mozilla.com](mailto:hubs@mozilla.com). + +To report a claim of copyright or trademark infringement, see [our policy](https://www.mozilla.org/en-US/about/legal/report-infringement/). + +For other notices, you may email us at [legal-notices@mozilla.com](mailto:legal-notices@mozilla.com) or write to us at Mozilla Corporation (Attn: Mozilla Legal Notices, 2 Harrison Street, San Francisco CA 94105). diff --git a/package.json b/package.json index 0d9ed6b57e38dc448a3da828e3acb5cc24a33035..b406d749c1d6186e875021204be636304c187427 100644 --- a/package.json +++ b/package.json @@ -29,16 +29,17 @@ "aframe-teleport-controls": "^0.3.1", "aframe-xr": "github:brianpeiris/aframe-xr#3162aed", "classnames": "^2.2.5", + "copy-to-clipboard": "^3.0.8", + "copy-webpack-plugin": "^4.5.1", "detect-browser": "^2.1.0", "event-target-shim": "^3.0.1", "form-urlencoded": "^2.0.4", "jsonschema": "^1.2.2", - "minijanus": "^0.5.0", "mobile-detect": "^1.4.1", "moment": "^2.22.0", "moment-timezone": "^0.5.14", "moving-average": "^1.0.0", - "naf-janus-adapter": "^0.5.2", + "naf-janus-adapter": "^0.6.0", "networked-aframe": "https://github.com/mozillareality/networked-aframe#mr-social-client/master", "nipplejs": "^0.6.7", "phoenix": "^1.3.0", diff --git a/src/assets/images/favicon.ico b/src/assets/images/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..0d18366e63c47b9351670b83df9ada3a6aa383fc Binary files /dev/null and b/src/assets/images/favicon.ico differ diff --git a/src/assets/images/help-hud.png b/src/assets/images/help-hud.png new file mode 100644 index 0000000000000000000000000000000000000000..43956c0d39fe0f08cf600e8cc56fd23c2ff85b1a Binary files /dev/null and b/src/assets/images/help-hud.png differ diff --git a/src/assets/images/help-hud@2x.png b/src/assets/images/help-hud@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..512ad316d0d2a8d8e3ac19350f81c96da4c50dfb Binary files /dev/null and b/src/assets/images/help-hud@2x.png differ diff --git a/src/assets/images/logo-narrow.svg b/src/assets/images/logo-narrow.svg new file mode 100755 index 0000000000000000000000000000000000000000..c9a5b10857a769cf7c35556bc4725e70b29bf96a --- /dev/null +++ b/src/assets/images/logo-narrow.svg @@ -0,0 +1,76 @@ +<svg width="329" height="190" viewBox="0 0 329 190" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g data-name="Canvas" fill="none"> +<g data-name="logo-narrow"> +<g data-name="Text"> +<g data-name="Hubs"> +<path d="M 105.225 140L 71.6285 140L 71.6285 132.452L 84.2085 132.452L 84.2085 94.564L 27.5245 94.564L 27.5245 132.452L 40.1045 132.452L 40.1045 140L 6.50853 140L 6.50853 132.452L 19.0885 132.452L 19.0885 51.348L 6.50853 51.348L 6.50853 43.8L 40.1045 43.8L 40.1045 51.348L 27.5245 51.348L 27.5245 87.312L 84.2085 87.312L 84.2085 51.348L 71.6285 51.348L 71.6285 43.8L 105.225 43.8L 105.225 51.348L 92.7925 51.348L 92.7925 132.452L 105.225 132.452L 105.225 140ZM 188.315 140L 167.891 140L 167.891 126.976C 165.819 131.317 162.76 134.82 158.715 137.484C 154.67 140.148 149.983 141.48 144.655 141.48C 138.143 141.48 132.716 139.457 128.375 135.412C 124.034 131.367 121.863 124.855 121.863 115.876L 121.863 81.244L 109.727 81.244L 109.727 74.288L 130.003 74.288L 130.003 113.952C 130.003 121.451 131.582 126.729 134.739 129.788C 137.995 132.748 142.09 134.228 147.023 134.228C 152.943 134.228 157.827 131.909 161.675 127.272C 165.622 122.635 167.694 117.948 167.891 113.212L 167.891 81.244L 153.387 81.244L 153.387 74.288L 176.031 74.288L 176.031 133.192L 188.315 133.192L 188.315 140ZM 262.713 105.516C 262.713 111.14 261.775 116.715 259.901 122.24C 258.026 127.765 254.918 132.353 250.577 136.004C 246.334 139.655 240.71 141.48 233.705 141.48C 228.574 141.48 223.887 140.395 219.645 138.224C 215.501 136.053 212.343 132.896 210.173 128.752L 208.841 140L 200.701 140C 201.095 137.237 201.49 134.228 201.885 130.972C 202.378 127.617 202.625 124.361 202.625 121.204L 202.625 44.984L 190.637 44.984L 190.637 38.176L 210.765 38.176L 210.765 88.2C 212.837 84.1547 215.698 80.6027 219.349 77.544C 223.098 74.4853 228.179 72.956 234.593 72.956C 242.782 72.956 249.491 75.768 254.721 81.392C 260.049 87.016 262.713 95.0573 262.713 105.516ZM 254.129 106.256C 254.129 97.5733 252.057 91.0613 247.913 86.72C 243.769 82.3787 238.786 80.208 232.965 80.208C 226.354 80.208 221.026 82.6253 216.981 87.46C 213.034 92.2947 210.962 97.1787 210.765 102.112L 210.765 114.248C 210.765 119.773 212.886 124.509 217.129 128.456C 221.371 132.304 226.403 134.228 232.225 134.228C 240.217 134.228 245.841 131.268 249.097 125.348C 252.451 119.428 254.129 113.064 254.129 106.256ZM 326.545 121.352C 326.545 127.469 323.93 132.353 318.701 136.004C 313.57 139.655 307.206 141.48 299.609 141.48C 294.971 141.48 290.383 140.839 285.845 139.556C 281.405 138.175 278.001 136.645 275.633 134.968L 277.409 121.056L 284.069 121.648L 283.477 130.528C 288.114 132.995 293.393 134.228 299.313 134.228C 304.147 134.228 308.489 133.291 312.337 131.416C 316.283 129.443 318.257 126.384 318.257 122.24C 318.257 118.293 316.826 115.481 313.965 113.804C 311.202 112.127 307.749 110.943 303.605 110.252C 299.461 109.463 295.317 108.624 291.173 107.736C 287.127 106.848 283.674 105.22 280.813 102.852C 278.05 100.484 276.669 96.784 276.669 91.752C 276.669 85.0427 279.086 80.2573 283.921 77.396C 288.755 74.436 294.034 72.956 299.757 72.956C 304.295 72.956 308.538 73.5973 312.485 74.88C 316.53 76.1627 319.786 77.544 322.253 79.024L 323.733 94.12L 316.925 95.008L 315.149 83.908C 313.077 82.8227 310.906 81.9347 308.637 81.244C 306.367 80.5533 303.753 80.208 300.793 80.208C 296.155 80.208 292.406 81.0467 289.545 82.724C 286.683 84.4013 285.253 87.1147 285.253 90.864C 285.253 94.6133 286.634 97.2773 289.397 98.856C 292.159 100.336 295.613 101.421 299.757 102.112C 303.901 102.803 307.995 103.641 312.041 104.628C 316.185 105.615 319.638 107.341 322.401 109.808C 325.163 112.275 326.545 116.123 326.545 121.352Z" transform="translate(1 -38)" fill="white"/> +</g> +</g> +<g data-name="duck"> +<g data-name="Group 2"> +<g data-name="noun_12039_cc"> +<g data-name="Subtract"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M 65.9802 25.5275C 66.8167 24.1531 67.3038 23.3527 68.1282 23.9791C 76.1992 30.1084 78.8308 45.0471 72.9722 56.835C 71.6601 59.4753 70.0015 61.7731 68.066 63.7626C 65.5201 62.4236 63.5402 60.4234 62.5366 58.0578C 60.6794 62.435 55.4794 65.5615 49.5366 65.5615C 43.5937 65.5615 38.3937 62.435 36.5366 58.0578C 34.6794 62.435 29.4794 65.5615 23.5366 65.5615C 17.5937 65.5615 12.3937 62.435 10.5366 58.0578C 9.56506 60.3476 7.67883 62.295 5.25006 63.6318C 2.79633 60.0866 1.36328 55.825 1.36328 51.0474C 1.36328 41.481 7.07397 33.2552 15.2622 29.5916C 7.39661 26.6014 0 21.6447 0 16.6679C 0 11.4169 4.85236 12.4405 10.9141 13.7192L 11.2712 13.7945C 12.9096 5.91818 19.8732 0 28.2216 0C 37.7862 0 45.5384 7.76956 45.5384 17.3529C 45.5384 23.4162 42.4309 28.7462 37.728 31.8492C 42.0263 33.296 47.1101 34.3864 53.1051 33.6837C 61.6244 32.6852 64.4144 28.1006 65.9802 25.5275Z" transform="translate(15.4634 124)" fill="url(#paint0_linear)"/> +</g> +</g> +<g data-name="Ellipse"> +<ellipse cx="4.64306" cy="4.68961" rx="4.64306" ry="4.68961" transform="matrix(0.998718 0.0506159 -0.0488663 0.998805 32.2697 134.867)" fill="black"/> +</g> +<g data-name="Mask Group"> +<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="5" y="131" width="27" height="59"> +<g data-name="Vector"> +<path d="M 6.93762 20.2166C 5.85085 22.3398 5.10886 24.5133 4.64228 26.6927L 11.0257 56.9833C 11.5736 58.0248 11.7322 58.3512 11.1899 57.7622L 11.0257 56.9833C 9.02442 53.1786 1.82962 39.8301 4.64228 26.6927L 0 4.66426L 16.7412 0C 25.5019 6.79027 25.5689 9.96318 24.7864 21.6411C 14.632 24.2817 10.5626 13.1346 6.93762 20.2166Z" transform="translate(5.92407 131.947)" fill="#C4C4C4"/> +</g> +</mask> +<g mask="url(#mask0)"> +<g data-name="Vector"> +<path d="M 68.1282 23.9791C 65.7603 22.1799 66.1749 32.1518 53.1051 33.6837C 47.1101 34.3864 42.0263 33.296 37.7279 31.8492C 42.4309 28.7462 45.5384 23.4162 45.5384 17.3529C 45.5384 7.76957 37.7862 2.4899e-08 28.2216 2.4899e-08C 19.8732 2.4899e-08 12.9096 5.91817 11.2712 13.7945C 5.04398 12.4829 -5.48121e-09 11.3142 -5.48121e-09 16.6679C -5.48121e-09 21.6447 7.39661 26.6014 15.2622 29.5916C 7.07399 33.2553 1.3633 41.4809 1.3633 51.0474C 1.3633 64.021 11.931 73.1904 24.8076 74.5389C 43.0915 76.454 64.7817 73.3164 72.9722 56.835C 78.8308 45.0471 76.1992 30.1084 68.1282 23.9791Z" transform="translate(15.4634 124)" fill="#FF8A00"/> +</g> +</g> +</g> +<g data-name="Ellipse"> +<ellipse cx="1.31646" cy="1.32745" rx="1.31646" ry="1.32745" transform="translate(36.8608 137.274)" fill="white"/> +</g> +</g> +<g data-name="Group 4.1"> +<g data-name="Group 3"> +<g data-name="Subtract"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M 0 0L 0 6.46024C 1.85712 10.8374 7.05713 13.9639 13 13.9639C 18.9429 13.9639 24.1429 10.8374 26 6.46024C 27.8571 10.8374 33.0571 13.9639 39 13.9639C 44.9429 13.9639 50.1429 10.8374 52 6.46024C 53.8571 10.8374 59.0571 13.9639 65 13.9639C 70.9429 13.9639 76.1429 10.8374 78 6.46024C 79.8571 10.8374 85.0571 13.9639 91 13.9639C 96.9429 13.9639 102.143 10.8374 104 6.46024L 104 0C 102.143 4.37717 96.9429 7.50369 91 7.50369C 85.0571 7.50369 79.8571 4.37717 78 0C 76.1429 4.37717 70.9429 7.50369 65 7.50369C 59.0571 7.50369 53.8571 4.37717 52 0C 50.1429 4.37717 44.9429 7.50369 39 7.50369C 33.0571 7.50369 27.8571 4.37717 26 0C 24.1429 4.37717 18.9429 7.50369 13 7.50369C 7.05713 7.50369 1.85712 4.37717 0 0Z" transform="translate(0 175.598)" fill="url(#paint1_radial)"/> +</g> +</g> +</g> +</g> +<g data-name="bottom"> +<g data-name="Group"> +<g data-name="moz logo"> +<g data-name="Rectangle 2.1"> +<rect width="172" height="49" transform="translate(157 140)" fill="white"/> +</g> +<g data-name="moz://a"> +<path d="M 41.472 45L 32.4 45L 32.4 32.808C 32.4 30.952 32.08 29.624 31.44 28.824C 30.832 28.024 29.92 27.624 28.704 27.624C 27.232 27.624 26.16 28.104 25.488 29.064C 24.848 30.024 24.528 31.256 24.528 32.76L 24.528 40.344L 27.408 40.344L 27.408 45L 18.336 45L 18.336 32.808C 18.336 30.952 18.016 29.624 17.376 28.824C 16.768 28.024 15.856 27.624 14.64 27.624C 13.136 27.624 12.064 28.104 11.424 29.064C 10.784 30.024 10.464 31.256 10.464 32.76L 10.464 40.344L 14.592 40.344L 14.592 45L 1.392 45L 1.392 40.344L 4.272 40.344L 4.272 28.056L 1.44 28.056L 1.44 23.352L 10.464 23.352L 10.464 26.616C 11.84 24.152 14.032 22.92 17.04 22.92C 18.608 22.92 20.048 23.304 21.36 24.072C 22.672 24.84 23.584 26.008 24.096 27.576C 25.376 24.472 27.728 22.92 31.152 22.92C 33.104 22.92 34.832 23.528 36.336 24.744C 37.84 25.96 38.592 27.848 38.592 30.408L 38.592 40.344L 41.472 40.344L 41.472 45ZM 66.1909 34.2C 66.1909 37.624 65.0709 40.36 62.8309 42.408C 60.5909 44.456 57.7429 45.48 54.2869 45.48C 50.9589 45.48 48.2389 44.52 46.1269 42.6C 44.0469 40.648 43.0069 37.928 43.0069 34.44C 43.0069 31.272 43.9669 28.552 45.8869 26.28C 47.8069 24.008 50.7189 22.872 54.6229 22.872C 58.5269 22.872 61.4229 24.008 63.3109 26.28C 65.2309 28.52 66.1909 31.16 66.1909 34.2ZM 59.8069 34.008C 59.8069 31.8 59.2949 30.184 58.2709 29.16C 57.2469 28.136 55.9989 27.624 54.5269 27.624C 52.9269 27.624 51.6629 28.2 50.7349 29.352C 49.8389 30.472 49.3909 32.056 49.3909 34.104C 49.3909 35.96 49.8069 37.528 50.6389 38.808C 51.5029 40.088 52.7829 40.728 54.4789 40.728C 56.0789 40.728 57.3589 40.136 58.3189 38.952C 59.3109 37.736 59.8069 36.088 59.8069 34.008ZM 87.8119 45L 68.8999 45L 68.2759 41.736L 79.8919 28.008L 73.4119 28.008L 72.4519 31.32L 67.9879 30.936L 68.7559 23.352L 87.7639 23.352L 88.2439 26.616L 76.2919 40.344L 83.2519 40.344L 84.2599 36.936L 89.1559 37.416L 87.8119 45ZM 92.8789 31.128L 92.8789 23.352L 99.3109 23.352L 99.3109 31.128L 92.8789 31.128ZM 92.8789 45L 92.8789 37.224L 99.3109 37.224L 99.3109 45L 92.8789 45ZM 119.281 11.976L 109.921 45L 103.825 45L 113.185 11.976L 119.281 11.976ZM 131.562 11.976L 122.202 45L 116.106 45L 125.466 11.976L 131.562 11.976ZM 133.858 24.888C 135.746 24.12 137.314 23.608 138.562 23.352C 139.842 23.064 141.362 22.92 143.122 22.92C 145.458 22.92 147.474 23.528 149.17 24.744C 150.866 25.96 151.714 27.8 151.714 30.264L 151.714 39.672C 151.714 40.792 152.21 41.352 153.202 41.352C 153.49 41.352 153.794 41.304 154.114 41.208L 154.162 44.472C 152.914 45.144 151.618 45.48 150.274 45.48C 147.426 45.48 145.842 44.056 145.522 41.208L 145.522 41.16C 144.914 42.248 144.034 43.24 142.882 44.136C 141.762 45.032 140.322 45.48 138.562 45.48C 136.994 45.48 135.49 45.016 134.05 44.088C 132.61 43.16 131.89 41.56 131.89 39.288C 131.89 36.632 132.946 34.872 135.058 34.008C 137.202 33.144 139.506 32.712 141.97 32.712C 143.346 32.712 144.53 32.776 145.522 32.904L 145.522 32.184C 145.522 31.064 145.362 29.976 145.042 28.92C 144.754 27.864 143.73 27.336 141.97 27.336C 141.33 27.336 140.738 27.384 140.194 27.48C 139.682 27.544 139.17 27.704 138.658 27.96L 137.842 31.032L 132.898 30.504L 133.858 24.888ZM 145.522 36.168L 145.522 35.88C 145.01 35.816 144.482 35.752 143.938 35.688C 143.426 35.624 142.914 35.592 142.402 35.592C 141.314 35.592 140.354 35.8 139.522 36.216C 138.69 36.6 138.274 37.384 138.274 38.568C 138.274 39.272 138.466 39.864 138.85 40.344C 139.234 40.824 139.89 41.064 140.818 41.064C 141.906 41.064 142.914 40.68 143.842 39.912C 144.802 39.112 145.362 37.864 145.522 36.168Z" transform="translate(167 134)" fill="black"/> +</g> +</g> +<g data-name="by"> +<path d="M 14.61 21.01C 14.61 22.15 14.42 23.28 14.04 24.4C 13.66 25.52 13.03 26.45 12.15 27.19C 11.29 27.93 10.15 28.3 8.73 28.3C 7.69 28.3 6.74 28.08 5.88 27.64C 5.04 27.2 4.4 26.56 3.96 25.72L 3.69 28L 2.04 28C 2.12 27.44 2.2 26.83 2.28 26.17C 2.38 25.49 2.43 24.83 2.43 24.19L 2.43 8.74L 0 8.74L 0 7.36L 4.08 7.36L 4.08 17.5C 4.5 16.68 5.08 15.96 5.82 15.34C 6.58 14.72 7.61 14.41 8.91 14.41C 10.57 14.41 11.93 14.98 12.99 16.12C 14.07 17.26 14.61 18.89 14.61 21.01ZM 12.87 21.16C 12.87 19.4 12.45 18.08 11.61 17.2C 10.77 16.32 9.76 15.88 8.58 15.88C 7.24 15.88 6.16 16.37 5.34 17.35C 4.54 18.33 4.12 19.32 4.08 20.32L 4.08 22.78C 4.08 23.9 4.51 24.86 5.37 25.66C 6.23 26.44 7.25 26.83 8.43 26.83C 10.05 26.83 11.19 26.23 11.85 25.03C 12.53 23.83 12.87 22.54 12.87 21.16ZM 30.3495 16.09L 28.6695 16.09L 23.8695 28L 21.9195 33.01L 24.3495 33.01L 24.3495 34.39L 17.7495 34.39L 17.7495 33.01L 20.3295 33.01L 22.2495 27.91L 17.3595 16.09L 15.7095 16.09L 15.7095 14.68L 21.7095 14.68L 21.7095 16.09L 19.0995 16.09L 23.1195 25.78L 23.2995 25.78L 26.9595 16.09L 24.5295 16.09L 24.5295 14.68L 30.3495 14.68L 30.3495 16.09Z" transform="translate(113 134)" fill="white"/> +</g> +</g> +</g> +</g> +</g> +<defs> + +<linearGradient id="paint0_linear" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 75.037 -76.1429 0 76.1429 0)"> +<stop stop-color="#FFC000"/> +<stop offset="1" stop-color="#FFD600"/> +</linearGradient> +<radialGradient id="paint1_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-6.58228 -212.392 147.186 -4.63796 -17.6435 186.344)"> +<stop stop-color="#2F80ED" stop-opacity="0"/> +<stop offset="0.154696" stop-color="#2F80ED"/> +<stop offset="0.508287" stop-color="#2F80ED"/> +<stop offset="0.883978" stop-color="#2F80ED"/> +<stop offset="0.983425" stop-color="#2F80ED" stop-opacity="0.51"/> +</radialGradient> + +</defs> +</svg> + diff --git a/src/assets/images/logo.svg b/src/assets/images/logo.svg index 43d6250ba602598561cd23813f5b63b9898f2df7..08b13a316bb4eff0a60ae174c9a1490fed605b24 100755 --- a/src/assets/images/logo.svg +++ b/src/assets/images/logo.svg @@ -1,73 +1,61 @@ -<svg width="1101" height="150" viewBox="0 0 1101 150" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> -<title>logo</title> -<desc>Created using Figma</desc> -<g id="Canvas" transform="matrix(3 0 0 3 1473 588)"> -<g id="logo"> -<g id="Rectangle 2.1"> -<use xlink:href="#path0_fill" transform="translate(-491 -196)" fill="#FFFFFF"/> +<svg width="509" height="102" viewBox="0 0 509 102" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<g data-name="Canvas" fill="none"> +<g data-name="logo"> +<g data-name="ducky"> +<g data-name="Group 2"> +<g data-name="noun_12039_cc"> +<g data-name="Subtract"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M 54.7397 21.2729C 55.4336 20.1276 55.8378 19.4605 56.5217 19.9826C 63.2177 25.0903 65.401 37.5392 60.5405 47.3625C 59.2541 49.9626 57.5664 52.1641 55.5724 54.014C 54.2682 53.9409 53.0217 53.6718 51.8827 53.2424L 51.8827 48.3815C 51.3302 49.6896 50.4211 50.8636 49.2547 51.8315C 47.9809 50.8988 46.9925 49.7175 46.421 48.3815L 46.421 53.554C 44.8222 54.2442 43.006 54.6346 41.0974 54.6346C 36.167 54.6346 31.8529 52.0292 30.3121 48.3815C 28.7714 52.0292 24.4572 54.6346 19.5268 54.6346C 14.5964 54.6346 10.2823 52.0292 8.74155 48.3815C 7.93555 50.2896 6.3707 51.9125 4.35568 53.0265C 2.31998 50.0722 1.13104 46.5208 1.13104 42.5395C 1.13104 34.5675 5.86884 27.7127 12.6621 24.6597C 6.13651 22.1679 0 18.0372 0 13.89C 0 9.51408 4.02563 10.3671 9.05475 11.4327L 9.35098 11.4954C 10.7103 4.93181 16.4876 0 23.4137 0C 31.3488 0 37.7803 6.47464 37.7803 14.4608C 37.7803 19.5135 35.2023 23.9551 31.3005 26.541C 34.8666 27.7466 39.0843 28.6553 44.058 28.0698C 51.1259 27.2376 53.4406 23.4172 54.7397 21.2729Z" transform="translate(429.829 9)" fill="url(#paint0_linear)"/> </g> -<g id="moz://a"> -<use xlink:href="#path1_fill" transform="translate(-481 -202)"/> </g> -<g id="Group 5.1"> -<g id="duck"> -<use xlink:href="#path2_fill" transform="translate(-311 -199)" fill="#FFFFFF"/> +<g data-name="Ellipse"> +<ellipse cx="3.8521" cy="3.90797" rx="3.8521" ry="3.90797" transform="matrix(0.998707 0.0508408 -0.0486501 0.998816 443.772 18.0556)" fill="black"/> </g> -<g id="Group 4"> -<g id="Group 2"> -<g id="noun_12039_cc"> -<g id="Subtract"> -<use xlink:href="#path3_fill" transform="translate(-191.254 -196)" fill="url(#paint3_linear)"/> +<g data-name="Mask Group"> +<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="421" y="15" width="22" height="49"> +<g data-name="Vector"> +<path d="M 5.75571 16.8472C 4.85408 18.6165 4.23851 20.4278 3.85141 22.2439L 9.14736 47.4861C 9.60188 48.354 9.73351 48.626 9.28354 48.1352L 9.14736 47.4861C 7.487 44.3155 1.51792 33.1918 3.85141 22.2439L 0 3.88688L 13.8892 0C 21.1574 5.65856 21.2129 8.30265 20.5637 18.0342C 12.1392 20.2348 8.76311 10.9455 5.75571 16.8472Z" transform="translate(421.915 15.6221)" fill="#C4C4C4"/> </g> +</mask> +<g mask="url(#mask0)"> +<g data-name="Vector"> +<path d="M 56.5217 19.9826C 54.5572 18.4832 54.9011 26.7932 44.058 28.0698C 39.0843 28.6553 34.8666 27.7466 31.3005 26.541C 35.2023 23.9551 37.7803 19.5135 37.7803 14.4608C 37.7803 6.47464 31.3488 2.07491e-08 23.4137 2.07491e-08C 16.4876 2.07491e-08 10.7103 4.93181 9.35099 11.4954C 4.18468 10.4024 -4.54742e-09 9.42848 -4.54742e-09 13.8899C -4.54742e-09 18.0372 6.13651 22.1679 12.6621 24.6597C 5.86885 27.7127 1.13105 34.5675 1.13105 42.5395C 1.13105 53.3508 9.8984 60.992 20.5813 62.1157C 35.7503 63.7117 53.7453 61.097 60.5405 47.3625C 65.401 37.5392 63.2177 25.0903 56.5217 19.9826Z" transform="translate(429.829 9)" fill="#FF8A00"/> </g> -<g id="Ellipse"> -<use xlink:href="#path4_fill" transform="matrix(0.998739 0.0501978 -0.0492734 0.998785 -178.487 -187.814)"/> </g> -<g id="Mask Group"> -<mask id="mask0_alpha" mask-type="alpha"> -<g id="Vector"> -<use xlink:href="#path5_fill" transform="translate(-198.5 -190.014)" fill="#C4C4C4"/> </g> -</mask> -<g id="Vector" mask="url(#mask0_alpha)"> -<use xlink:href="#path6_fill" transform="translate(-191.254 -196)" fill="#FF8A00"/> +<g data-name="Ellipse"> +<ellipse cx="1.09218" cy="1.10621" rx="1.09218" ry="1.10621" transform="translate(447.581 20.0621)" fill="white"/> +</g> </g> </g> -<g id="Ellipse"> -<use xlink:href="#path7_fill" transform="translate(-175 -186)" fill="#FFFFFF"/> +<g data-name="duck border"> +<path d="M 0 0L 75.5 0" stroke-width="2" transform="translate(428 51)" stroke="black"/> </g> +<g data-name="Text"> +<g data-name="moz logo"> +<g data-name="Rectangle 2.1"> +<rect width="172" height="49" transform="translate(337 52)" fill="white"/> </g> -<g id="Group 4.1"> -<g id="Group 3"> -<g id="Subtract"> -<use xlink:href="#path8_fill" transform="translate(-203 -157.13)" fill="url(#paint8_radial)"/> +<g data-name="moz://a"> +<path d="M 41.472 45L 32.4 45L 32.4 32.808C 32.4 30.952 32.08 29.624 31.44 28.824C 30.832 28.024 29.92 27.624 28.704 27.624C 27.232 27.624 26.16 28.104 25.488 29.064C 24.848 30.024 24.528 31.256 24.528 32.76L 24.528 40.344L 27.408 40.344L 27.408 45L 18.336 45L 18.336 32.808C 18.336 30.952 18.016 29.624 17.376 28.824C 16.768 28.024 15.856 27.624 14.64 27.624C 13.136 27.624 12.064 28.104 11.424 29.064C 10.784 30.024 10.464 31.256 10.464 32.76L 10.464 40.344L 14.592 40.344L 14.592 45L 1.392 45L 1.392 40.344L 4.272 40.344L 4.272 28.056L 1.44 28.056L 1.44 23.352L 10.464 23.352L 10.464 26.616C 11.84 24.152 14.032 22.92 17.04 22.92C 18.608 22.92 20.048 23.304 21.36 24.072C 22.672 24.84 23.584 26.008 24.096 27.576C 25.376 24.472 27.728 22.92 31.152 22.92C 33.104 22.92 34.832 23.528 36.336 24.744C 37.84 25.96 38.592 27.848 38.592 30.408L 38.592 40.344L 41.472 40.344L 41.472 45ZM 66.1909 34.2C 66.1909 37.624 65.0709 40.36 62.8309 42.408C 60.5909 44.456 57.7429 45.48 54.2869 45.48C 50.9589 45.48 48.2389 44.52 46.1269 42.6C 44.0469 40.648 43.0069 37.928 43.0069 34.44C 43.0069 31.272 43.9669 28.552 45.8869 26.28C 47.8069 24.008 50.7189 22.872 54.6229 22.872C 58.5269 22.872 61.4229 24.008 63.3109 26.28C 65.2309 28.52 66.1909 31.16 66.1909 34.2ZM 59.8069 34.008C 59.8069 31.8 59.2949 30.184 58.2709 29.16C 57.2469 28.136 55.9989 27.624 54.5269 27.624C 52.9269 27.624 51.6629 28.2 50.7349 29.352C 49.8389 30.472 49.3909 32.056 49.3909 34.104C 49.3909 35.96 49.8069 37.528 50.6389 38.808C 51.5029 40.088 52.7829 40.728 54.4789 40.728C 56.0789 40.728 57.3589 40.136 58.3189 38.952C 59.3109 37.736 59.8069 36.088 59.8069 34.008ZM 87.8119 45L 68.8999 45L 68.2759 41.736L 79.8919 28.008L 73.4119 28.008L 72.4519 31.32L 67.9879 30.936L 68.7559 23.352L 87.7639 23.352L 88.2439 26.616L 76.2919 40.344L 83.2519 40.344L 84.2599 36.936L 89.1559 37.416L 87.8119 45ZM 92.8789 31.128L 92.8789 23.352L 99.3109 23.352L 99.3109 31.128L 92.8789 31.128ZM 92.8789 45L 92.8789 37.224L 99.3109 37.224L 99.3109 45L 92.8789 45ZM 119.281 11.976L 109.921 45L 103.825 45L 113.185 11.976L 119.281 11.976ZM 131.562 11.976L 122.202 45L 116.106 45L 125.466 11.976L 131.562 11.976ZM 133.858 24.888C 135.746 24.12 137.314 23.608 138.562 23.352C 139.842 23.064 141.362 22.92 143.122 22.92C 145.458 22.92 147.474 23.528 149.17 24.744C 150.866 25.96 151.714 27.8 151.714 30.264L 151.714 39.672C 151.714 40.792 152.21 41.352 153.202 41.352C 153.49 41.352 153.794 41.304 154.114 41.208L 154.162 44.472C 152.914 45.144 151.618 45.48 150.274 45.48C 147.426 45.48 145.842 44.056 145.522 41.208L 145.522 41.16C 144.914 42.248 144.034 43.24 142.882 44.136C 141.762 45.032 140.322 45.48 138.562 45.48C 136.994 45.48 135.49 45.016 134.05 44.088C 132.61 43.16 131.89 41.56 131.89 39.288C 131.89 36.632 132.946 34.872 135.058 34.008C 137.202 33.144 139.506 32.712 141.97 32.712C 143.346 32.712 144.53 32.776 145.522 32.904L 145.522 32.184C 145.522 31.064 145.362 29.976 145.042 28.92C 144.754 27.864 143.73 27.336 141.97 27.336C 141.33 27.336 140.738 27.384 140.194 27.48C 139.682 27.544 139.17 27.704 138.658 27.96L 137.842 31.032L 132.898 30.504L 133.858 24.888ZM 145.522 36.168L 145.522 35.88C 145.01 35.816 144.482 35.752 143.938 35.688C 143.426 35.624 142.914 35.592 142.402 35.592C 141.314 35.592 140.354 35.8 139.522 36.216C 138.69 36.6 138.274 37.384 138.274 38.568C 138.274 39.272 138.466 39.864 138.85 40.344C 139.234 40.824 139.89 41.064 140.818 41.064C 141.906 41.064 142.914 40.68 143.842 39.912C 144.802 39.112 145.362 37.864 145.522 36.168Z" transform="translate(347 46)" fill="black"/> </g> </g> +<g data-name="by"> +<path d="M 14.61 21.01C 14.61 22.15 14.42 23.28 14.04 24.4C 13.66 25.52 13.03 26.45 12.15 27.19C 11.29 27.93 10.15 28.3 8.73 28.3C 7.69 28.3 6.74 28.08 5.88 27.64C 5.04 27.2 4.4 26.56 3.96 25.72L 3.69 28L 2.04 28C 2.12 27.44 2.2 26.83 2.28 26.17C 2.38 25.49 2.43 24.83 2.43 24.19L 2.43 8.74L 0 8.74L 0 7.36L 4.08 7.36L 4.08 17.5C 4.5 16.68 5.08 15.96 5.82 15.34C 6.58 14.72 7.61 14.41 8.91 14.41C 10.57 14.41 11.93 14.98 12.99 16.12C 14.07 17.26 14.61 18.89 14.61 21.01ZM 12.87 21.16C 12.87 19.4 12.45 18.08 11.61 17.2C 10.77 16.32 9.76 15.88 8.58 15.88C 7.24 15.88 6.16 16.37 5.34 17.35C 4.54 18.33 4.12 19.32 4.08 20.32L 4.08 22.78C 4.08 23.9 4.51 24.86 5.37 25.66C 6.23 26.44 7.25 26.83 8.43 26.83C 10.05 26.83 11.19 26.23 11.85 25.03C 12.53 23.83 12.87 22.54 12.87 21.16ZM 30.3495 16.09L 28.6695 16.09L 23.8695 28L 21.9195 33.01L 24.3495 33.01L 24.3495 34.39L 17.7495 34.39L 17.7495 33.01L 20.3295 33.01L 22.2495 27.91L 17.3595 16.09L 15.7095 16.09L 15.7095 14.68L 21.7095 14.68L 21.7095 16.09L 19.0995 16.09L 23.1195 25.78L 23.2995 25.78L 26.9595 16.09L 24.5295 16.09L 24.5295 14.68L 30.3495 14.68L 30.3495 16.09Z" transform="translate(337 12)" fill="white"/> </g> +<g data-name="Hubs"> +<path d="M 102.24 136L 69.552 136L 69.552 128.656L 81.792 128.656L 81.792 91.792L 26.64 91.792L 26.64 128.656L 38.88 128.656L 38.88 136L 6.192 136L 6.192 128.656L 18.432 128.656L 18.432 49.744L 6.192 49.744L 6.192 42.4L 38.88 42.4L 38.88 49.744L 26.64 49.744L 26.64 84.736L 81.792 84.736L 81.792 49.744L 69.552 49.744L 69.552 42.4L 102.24 42.4L 102.24 49.744L 90.144 49.744L 90.144 128.656L 102.24 128.656L 102.24 136ZM 183.085 136L 163.213 136L 163.213 123.328C 161.197 127.552 158.221 130.96 154.285 133.552C 150.349 136.144 145.789 137.44 140.605 137.44C 134.269 137.44 128.989 135.472 124.765 131.536C 120.541 127.6 118.429 121.264 118.429 112.528L 118.429 78.832L 106.621 78.832L 106.621 72.064L 126.349 72.064L 126.349 110.656C 126.349 117.952 127.885 123.088 130.957 126.064C 134.125 128.944 138.109 130.384 142.909 130.384C 148.669 130.384 153.421 128.128 157.165 123.616C 161.005 119.104 163.021 114.544 163.213 109.936L 163.213 78.832L 149.101 78.832L 149.101 72.064L 171.133 72.064L 171.133 129.376L 183.085 129.376L 183.085 136ZM 255.472 102.448C 255.472 107.92 254.56 113.344 252.736 118.72C 250.912 124.096 247.888 128.56 243.664 132.112C 239.536 135.664 234.064 137.44 227.248 137.44C 222.256 137.44 217.696 136.384 213.568 134.272C 209.536 132.16 206.464 129.088 204.352 125.056L 203.056 136L 195.136 136C 195.52 133.312 195.904 130.384 196.288 127.216C 196.768 123.952 197.008 120.784 197.008 117.712L 197.008 43.552L 185.344 43.552L 185.344 36.928L 204.928 36.928L 204.928 85.6C 206.944 81.664 209.728 78.208 213.28 75.232C 216.928 72.256 221.872 70.768 228.112 70.768C 236.08 70.768 242.608 73.504 247.696 78.976C 252.88 84.448 255.472 92.272 255.472 102.448ZM 247.12 103.168C 247.12 94.72 245.104 88.384 241.072 84.16C 237.04 79.936 232.192 77.824 226.528 77.824C 220.096 77.824 214.912 80.176 210.976 84.88C 207.136 89.584 205.12 94.336 204.928 99.136L 204.928 110.944C 204.928 116.32 206.992 120.928 211.12 124.768C 215.248 128.512 220.144 130.384 225.808 130.384C 233.584 130.384 239.056 127.504 242.224 121.744C 245.488 115.984 247.12 109.792 247.12 103.168ZM 317.578 117.856C 317.578 123.808 315.034 128.56 309.946 132.112C 304.955 135.664 298.763 137.44 291.371 137.44C 286.858 137.44 282.394 136.816 277.979 135.568C 273.658 134.224 270.346 132.736 268.043 131.104L 269.771 117.568L 276.25 118.144L 275.674 126.784C 280.186 129.184 285.322 130.384 291.082 130.384C 295.787 130.384 300.011 129.472 303.755 127.648C 307.595 125.728 309.515 122.752 309.515 118.72C 309.515 114.88 308.122 112.144 305.338 110.512C 302.65 108.88 299.29 107.728 295.258 107.056C 291.227 106.288 287.195 105.472 283.163 104.608C 279.227 103.744 275.866 102.16 273.082 99.856C 270.395 97.552 269.051 93.952 269.051 89.056C 269.051 82.528 271.403 77.872 276.107 75.088C 280.811 72.208 285.947 70.768 291.515 70.768C 295.931 70.768 300.059 71.392 303.898 72.64C 307.835 73.888 311.003 75.232 313.403 76.672L 314.843 91.36L 308.219 92.224L 306.491 81.424C 304.475 80.368 302.363 79.504 300.155 78.832C 297.947 78.16 295.403 77.824 292.523 77.824C 288.01 77.824 284.362 78.64 281.578 80.272C 278.794 81.904 277.402 84.544 277.402 88.192C 277.402 91.84 278.746 94.432 281.434 95.968C 284.122 97.408 287.482 98.464 291.515 99.136C 295.547 99.808 299.531 100.624 303.467 101.584C 307.499 102.544 310.859 104.224 313.547 106.624C 316.234 109.024 317.578 112.768 317.578 117.856Z" transform="translate(-6 -36)" fill="white"/> </g> </g> </g> </g> <defs> -<linearGradient id="paint3_linear" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.54163e-15 56.5272 -57.8393 3.46129e-15 57.8393 1.67757e-22)"> -<stop offset="0" stop-color="#FFC000"/> + +<linearGradient id="paint0_linear" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 62.5308 -63.171 0 63.171 0)"> +<stop stop-color="#FFC000"/> <stop offset="1" stop-color="#FFD600"/> </linearGradient> -<radialGradient id="paint8_radial" cx="0.5" cy="0.5" r="0.5" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-5 -160 111.805 -3.49389 -13.4023 140.377)"> -<stop offset="0" stop-color="#2F80ED" stop-opacity="0"/> -<stop offset="0.154696" stop-color="#2F80ED"/> -<stop offset="0.508287" stop-color="#2F80ED"/> -<stop offset="0.883978" stop-color="#2F80ED"/> -<stop offset="0.983425" stop-color="#2F80ED" stop-opacity="0.51"/> -</radialGradient> -<path id="path0_fill" d="M 0 0L 172 0L 172 49L 0 49L 0 0Z"/> -<path id="path1_fill" d="M 41.472 45L 32.4 45L 32.4 32.808C 32.4 30.952 32.08 29.624 31.44 28.824C 30.832 28.024 29.92 27.624 28.704 27.624C 27.232 27.624 26.16 28.104 25.488 29.064C 24.848 30.024 24.528 31.256 24.528 32.76L 24.528 40.344L 27.408 40.344L 27.408 45L 18.336 45L 18.336 32.808C 18.336 30.952 18.016 29.624 17.376 28.824C 16.768 28.024 15.856 27.624 14.64 27.624C 13.136 27.624 12.064 28.104 11.424 29.064C 10.784 30.024 10.464 31.256 10.464 32.76L 10.464 40.344L 14.592 40.344L 14.592 45L 1.392 45L 1.392 40.344L 4.272 40.344L 4.272 28.056L 1.44 28.056L 1.44 23.352L 10.464 23.352L 10.464 26.616C 11.84 24.152 14.032 22.92 17.04 22.92C 18.608 22.92 20.048 23.304 21.36 24.072C 22.672 24.84 23.584 26.008 24.096 27.576C 25.376 24.472 27.728 22.92 31.152 22.92C 33.104 22.92 34.832 23.528 36.336 24.744C 37.84 25.96 38.592 27.848 38.592 30.408L 38.592 40.344L 41.472 40.344L 41.472 45ZM 66.1909 34.2C 66.1909 37.624 65.0709 40.36 62.8309 42.408C 60.5909 44.456 57.7429 45.48 54.2869 45.48C 50.9589 45.48 48.2389 44.52 46.1269 42.6C 44.0469 40.648 43.0069 37.928 43.0069 34.44C 43.0069 31.272 43.9669 28.552 45.8869 26.28C 47.8069 24.008 50.7189 22.872 54.6229 22.872C 58.5269 22.872 61.4229 24.008 63.3109 26.28C 65.2309 28.52 66.1909 31.16 66.1909 34.2ZM 59.8069 34.008C 59.8069 31.8 59.2949 30.184 58.2709 29.16C 57.2469 28.136 55.9989 27.624 54.5269 27.624C 52.9269 27.624 51.6629 28.2 50.7349 29.352C 49.8389 30.472 49.3909 32.056 49.3909 34.104C 49.3909 35.96 49.8069 37.528 50.6389 38.808C 51.5029 40.088 52.7829 40.728 54.4789 40.728C 56.0789 40.728 57.3589 40.136 58.3189 38.952C 59.3109 37.736 59.8069 36.088 59.8069 34.008ZM 87.8119 45L 68.8999 45L 68.2759 41.736L 79.8919 28.008L 73.4119 28.008L 72.4519 31.32L 67.9879 30.936L 68.7559 23.352L 87.7639 23.352L 88.2439 26.616L 76.2919 40.344L 83.2519 40.344L 84.2599 36.936L 89.1559 37.416L 87.8119 45ZM 92.8789 31.128L 92.8789 23.352L 99.3109 23.352L 99.3109 31.128L 92.8789 31.128ZM 92.8789 45L 92.8789 37.224L 99.3109 37.224L 99.3109 45L 92.8789 45ZM 119.281 11.976L 109.921 45L 103.825 45L 113.185 11.976L 119.281 11.976ZM 131.562 11.976L 122.202 45L 116.106 45L 125.466 11.976L 131.562 11.976ZM 133.858 24.888C 135.746 24.12 137.314 23.608 138.562 23.352C 139.842 23.064 141.362 22.92 143.122 22.92C 145.458 22.92 147.474 23.528 149.17 24.744C 150.866 25.96 151.714 27.8 151.714 30.264L 151.714 39.672C 151.714 40.792 152.21 41.352 153.202 41.352C 153.49 41.352 153.794 41.304 154.114 41.208L 154.162 44.472C 152.914 45.144 151.618 45.48 150.274 45.48C 147.426 45.48 145.842 44.056 145.522 41.208L 145.522 41.16C 144.914 42.248 144.034 43.24 142.882 44.136C 141.762 45.032 140.322 45.48 138.562 45.48C 136.994 45.48 135.49 45.016 134.05 44.088C 132.61 43.16 131.89 41.56 131.89 39.288C 131.89 36.632 132.946 34.872 135.058 34.008C 137.202 33.144 139.506 32.712 141.97 32.712C 143.346 32.712 144.53 32.776 145.522 32.904L 145.522 32.184C 145.522 31.064 145.362 29.976 145.042 28.92C 144.754 27.864 143.73 27.336 141.97 27.336C 141.33 27.336 140.738 27.384 140.194 27.48C 139.682 27.544 139.17 27.704 138.658 27.96L 137.842 31.032L 132.898 30.504L 133.858 24.888ZM 145.522 36.168L 145.522 35.88C 145.01 35.816 144.482 35.752 143.938 35.688C 143.426 35.624 142.914 35.592 142.402 35.592C 141.314 35.592 140.354 35.8 139.522 36.216C 138.69 36.6 138.274 37.384 138.274 38.568C 138.274 39.272 138.466 39.864 138.85 40.344C 139.234 40.824 139.89 41.064 140.818 41.064C 141.906 41.064 142.914 40.68 143.842 39.912C 144.802 39.112 145.362 37.864 145.522 36.168Z"/> -<path id="path2_fill" d="M 23.32 42L 17.292 42L 17.292 37.952C 16.6467 39.1253 15.752 40.1667 14.608 41.076C 13.4933 41.9853 12.0413 42.44 10.252 42.44C 7.75867 42.44 5.73467 41.5893 4.18 39.888C 2.62533 38.1867 1.848 35.8253 1.848 32.804C 1.848 31.1027 2.12667 29.4307 2.684 27.788C 3.27067 26.1453 4.20933 24.7813 5.5 23.696C 6.79067 22.6107 8.52133 22.068 10.692 22.068C 12.0707 22.068 13.3467 22.376 14.52 22.992C 15.6933 23.608 16.6173 24.5027 17.292 25.676L 17.292 13.752L 12.364 13.752L 12.364 11.728L 19.712 11.728L 19.712 39.976L 23.32 39.976L 23.32 42ZM 17.292 33.684L 17.292 29.856C 17.2333 28.2427 16.588 26.908 15.356 25.852C 14.1533 24.7667 12.7307 24.224 11.088 24.224C 8.68267 24.224 6.96667 25.104 5.94 26.864C 4.91333 28.624 4.4 30.5453 4.4 32.628C 4.4 35.1213 5.00133 37.028 6.204 38.348C 7.40667 39.6387 8.91733 40.284 10.736 40.284C 12.5547 40.284 14.08 39.5507 15.312 38.084C 16.5733 36.6173 17.2333 35.1507 17.292 33.684ZM 47.6496 42L 41.5776 42L 41.5776 38.128C 40.9616 39.4187 40.0523 40.46 38.8496 41.252C 37.6469 42.044 36.2536 42.44 34.6696 42.44C 32.7336 42.44 31.1203 41.8387 29.8296 40.636C 28.5389 39.4333 27.8936 37.4973 27.8936 34.828L 27.8936 24.532L 24.2856 24.532L 24.2856 22.464L 30.3136 22.464L 30.3136 34.256C 30.3136 36.4853 30.7829 38.0547 31.7216 38.964C 32.6896 39.844 33.9069 40.284 35.3736 40.284C 37.1336 40.284 38.5856 39.5947 39.7296 38.216C 40.9029 36.8373 41.5189 35.444 41.5776 34.036L 41.5776 24.532L 37.2656 24.532L 37.2656 22.464L 43.9976 22.464L 43.9976 39.976L 47.6496 39.976L 47.6496 42ZM 66.8638 35.62C 66.5705 37.3213 65.7932 38.8907 64.5318 40.328C 63.2998 41.736 61.4078 42.44 58.8558 42.44C 56.1865 42.44 54.0745 41.56 52.5198 39.8C 50.9652 38.04 50.1878 35.62 50.1878 32.54C 50.1878 30.868 50.4958 29.2253 51.1118 27.612C 51.7278 25.9987 52.6812 24.6787 53.9718 23.652C 55.2918 22.596 56.9785 22.068 59.0318 22.068C 61.7012 22.068 63.8865 22.772 65.5878 24.18L 66.2918 28.536L 64.2678 28.8L 63.5198 25.5C 62.3172 24.6493 60.8212 24.224 59.0318 24.224C 56.7145 24.224 55.0865 25.06 54.1478 26.732C 53.2385 28.3747 52.7838 30.252 52.7838 32.364C 52.7838 34.8573 53.3265 36.808 54.4118 38.216C 55.4972 39.5947 57.0665 40.284 59.1198 40.284C 62.2292 40.284 64.1212 38.568 64.7958 35.136L 66.8638 35.62ZM 90.5792 42L 81.5592 42L 81.5592 39.976L 84.8152 39.976L 78.9632 32.584L 75.3112 32.584L 75.3112 39.976L 78.4352 39.976L 78.4352 42L 69.2832 42L 69.2832 39.976L 72.8912 39.976L 72.8912 13.752L 69.2392 13.752L 69.2392 11.728L 75.3112 11.728L 75.3112 30.648L 78.9192 30.648L 83.8032 24.532L 80.5912 24.532L 80.5912 22.464L 89.5232 22.464L 89.5232 24.532L 86.3552 24.532L 80.8112 31.396L 87.5432 39.976L 90.5792 39.976L 90.5792 42Z"/> -<path id="path3_fill" fill-rule="evenodd" d="M 50.1196 19.2305C 50.7549 18.1952 51.125 17.5921 51.7513 18.064C 57.8821 22.6814 59.8811 33.9351 55.4308 42.8152C 54.4341 44.8042 53.1743 46.5352 51.704 48.0339C 49.7701 47.0253 48.2661 45.5184 47.5038 43.7364C 46.093 47.0338 42.143 49.3891 37.6288 49.3891C 33.1145 49.3891 29.1645 47.0338 27.7538 43.7364C 26.343 47.0338 22.393 49.3891 17.8788 49.3891C 13.3645 49.3891 9.41447 47.0338 8.00375 43.7364C 7.26579 45.4613 5.83299 46.9284 3.98805 47.9354C 2.12418 45.2647 1.03558 42.0543 1.03558 38.4553C 1.03558 31.2486 5.3735 25.052 11.5934 22.2921C 5.61858 20.0395 0 16.3055 0 12.5564C 0 8.60062 3.68587 9.3717 8.2905 10.335C 8.38057 10.3539 8.47099 10.3728 8.56175 10.3917C 9.80632 4.4583 15.096 0 21.4376 0C 28.703 0 34.5916 5.85301 34.5916 13.0724C 34.5916 17.64 32.2312 21.6552 28.6587 23.9928C 31.9238 25.0827 35.7856 25.9041 40.3394 25.3748C 46.8108 24.6225 48.9302 21.1689 50.1196 19.2305Z"/> -<path id="path4_fill" d="M 7.05374 3.53287C 7.05374 5.48402 5.4747 7.06574 3.52687 7.06574C 1.57903 7.06574 0 5.48402 0 3.53287C 0 1.58172 1.57903 0 3.52687 0C 5.4747 0 7.05374 1.58172 7.05374 3.53287Z"/> -<path id="path5_fill" d="M 5.26992 15.2297C 4.44439 16.8291 3.88077 18.4665 3.52634 20.1083L 8.37531 42.9269C 8.79147 43.7115 8.912 43.9574 8.5 43.5137L 8.37531 42.9269C 6.85509 40.0607 1.3898 30.005 3.52634 20.1083L 0 3.5137L 12.7169 0C 19.3717 5.11528 19.4225 7.50551 18.8281 16.3028C 11.1147 18.292 8.0235 9.89461 5.26992 15.2297Z"/> -<path id="path6_fill" d="M 51.7513 18.064C 49.9525 16.7087 50.2674 24.2207 40.3395 25.3748C 35.7856 25.9041 31.9238 25.0827 28.6587 23.9928C 32.2312 21.6552 34.5916 17.64 34.5916 13.0724C 34.5916 5.85301 28.703 1.8757e-08 21.4376 1.8757e-08C 15.096 1.8757e-08 9.80632 4.4583 8.56176 10.3917C 3.83149 9.40368 -4.16361e-09 8.52324 -4.16361e-09 12.5564C -4.16361e-09 16.3055 5.61858 20.0395 11.5934 22.2921C 5.37351 25.052 1.03558 31.2486 1.03558 38.4553C 1.03558 48.2286 9.06296 55.1362 18.8443 56.152C 32.7329 57.5947 49.2091 55.231 55.4308 42.8152C 59.8811 33.9351 57.8821 22.6814 51.7513 18.064Z"/> -<path id="path7_fill" d="M 2 1C 2 1.55228 1.55228 2 1 2C 0.447715 2 0 1.55228 0 1C 0 0.447715 0.447715 0 1 0C 1.55228 0 2 0.447715 2 1Z"/> -<path id="path8_fill" fill-rule="evenodd" d="M 0 1.52588e-05L 0 4.86665C 1.41072 8.16408 5.36072 10.5194 9.875 10.5194C 14.3893 10.5194 18.3393 8.16408 19.75 4.86665C 21.1607 8.16408 25.1107 10.5194 29.625 10.5194C 34.1393 10.5194 38.0893 8.16408 39.5 4.86665C 40.9107 8.16408 44.8607 10.5194 49.375 10.5194C 53.8893 10.5194 57.8393 8.16408 59.25 4.86665C 60.6607 8.16408 64.6107 10.5194 69.125 10.5194C 73.6393 10.5194 77.5893 8.16408 79 4.86665L 79 0C 77.5893 3.29741 73.6393 5.65273 69.125 5.65273C 64.6107 5.65273 60.6607 3.29741 59.25 0C 57.8393 3.29741 53.8893 5.65273 49.375 5.65273C 44.8607 5.65273 40.9107 3.29741 39.5 0C 38.0893 3.29741 34.1393 5.65273 29.625 5.65273C 25.1107 5.65273 21.1607 3.29741 19.75 0C 18.3393 3.29742 14.3893 5.65273 9.875 5.65273C 5.36072 5.65273 1.41072 3.29742 0 1.52588e-05Z"/> + </defs> </svg> + diff --git a/src/assets/stylesheets/2d-hud.css b/src/assets/stylesheets/2d-hud.css index a50436114181d18997248dce77a3cc1d9500363f..024daffb0b6463f58a70d5e7d2ef9565d840de91 100644 --- a/src/assets/stylesheets/2d-hud.css +++ b/src/assets/stylesheets/2d-hud.css @@ -38,7 +38,7 @@ display: flex; align-items: center; justify-content: center; - z-index: 10; + z-index: 1; } :local(.panel) { diff --git a/src/assets/stylesheets/avatar-selector.scss b/src/assets/stylesheets/avatar-selector.scss index b3d20bea02eb7e17cebbbf220794aa5b3c305338..3485ea3de016e7bc7f895a4232fa9136dd1e04f1 100644 --- a/src/assets/stylesheets/avatar-selector.scss +++ b/src/assets/stylesheets/avatar-selector.scss @@ -1,8 +1,14 @@ @import 'shared'; +@import 'loader'; #selector-root { height: 100%; } + +canvas { + border-radius: 8px; +} + .avatar-selector { overflow: hidden; height: 100%; diff --git a/src/assets/stylesheets/exited.scss b/src/assets/stylesheets/exited.scss index 693d6d38798705979478930f0b175ea57aa6e183..716d9e88dbc9f674aef5dea3f4aa1d8c1d49650c 100644 --- a/src/assets/stylesheets/exited.scss +++ b/src/assets/stylesheets/exited.scss @@ -9,12 +9,18 @@ align-items: center; flex-direction: column; - &__title { - font-size: 1.2em; + &__logo { + width: 150px; } &__subtitle { - font-size: 0.8em; + @extend %default-font; + padding: 12px; + text-align: center; + + a { + color: $light-text; + } } } diff --git a/src/assets/stylesheets/footer.scss b/src/assets/stylesheets/footer.scss new file mode 100644 index 0000000000000000000000000000000000000000..843c7f534ce4874a8cf98fa3b941796180d7c9d0 --- /dev/null +++ b/src/assets/stylesheets/footer.scss @@ -0,0 +1,121 @@ +@import 'shared'; + +:local(.container) { + position: absolute; + width: 100%; + bottom: 0; + font-size: 1.3em; + display: flex; + flex-direction: column; + pointer-events: auto; + // Position above virtual gamepad controls on mobile + z-index: 1; + +} +:local(.floatingButton) { + display: flex; + justify-content: center; +} +:local(.header), :local(.menu-header) { + display: flex; +} +:local(.header) { + border-bottom: 1px solid rgba(32, 32, 32, 0.65); + + @media (max-width: 768px) { + border-bottom: none; + } +} +:local(.menu-header) { + background-color: transparent; + border-bottom: 1px solid rgba(32, 32, 32, 0.65); + + @media (min-width: 768px) , (max-height: 380px) { + display: none; + } +} +:local(.header) { + background-color: rgba(0, 0, 0, 0.65); + + @media (max-width: 768px) { + background-color: transparent; + } + + :local(.hub-info) { + @media (max-width: 768px) { + display: none; + } + } + + :local(.hub-stats) { + @media (max-width: 768px) { + display: none; + } + } +} + +:local(.hub-info) { + flex: 1; + margin: 16px 24px; + display: flex; + align-items: center; + @media (max-width: 768px) { + margin: 16px 8px; + margin-left: 24px; + font-size: 0.9em; + } +} +:local(.hub-stats) { + text-align: right; + margin: 16px 24px; + display: flex; + align-items: center; + justify-content: flex-end; + @media (min-width: 768px) { + flex: 1; + } + @media (max-width: 768px) { + margin: 16px 8px; + } + :local(.hub-participant-count) { + margin: 0 12px; + } +} + +:local(.menu) { + padding: 5px 0; + background-color: rgba(0, 0, 0, 0.85); + display: flex; + flex-direction: column; +} +:local(.menu-buttons) { + margin: 0 auto; +} +:local(.menu-button) { + @extend %fa-icon-button; + + @media (max-width: 768px) { + flex: 1; + align-self: center; + } + + :local(.menu-button__icon) { + @extend %fa-icon-button-icon; + } + + :local(.menu-button__text) { + @extend %fa-icon-button-text; + } + + :local(.menu-button__narrow-close-icon) { + @media (max-width: 767px) { + display: none; + } + } + + :local(.menu-button__wide-close-icon) { + @media (min-width: 768px) { + display: none; + } + } +} diff --git a/src/assets/stylesheets/hub.scss b/src/assets/stylesheets/hub.scss index c10db8dd196ca6b3dce999605e002893cacd05e6..b7d6a928ceb38e36282373ca184bbef631705025 100644 --- a/src/assets/stylesheets/hub.scss +++ b/src/assets/stylesheets/hub.scss @@ -6,20 +6,16 @@ @import 'profile'; @import 'entry'; @import 'audio'; +@import 'info-dialog'; .a-enter-vr { display: none; } -.rs-base { - top: auto; - bottom: 20px; -} - .a-canvas.a-grab-cursor:hover { - cursor: none; + cursor: none; } .a-canvas.a-grab-cursor:active { - cursor: none; + cursor: none; } diff --git a/src/assets/stylesheets/index.scss b/src/assets/stylesheets/index.scss index b09114616dd142d6272899ea88ca70564511f741..02260eddba851303ebae5c7dd9bc1ac15c6d9b83 100644 --- a/src/assets/stylesheets/index.scss +++ b/src/assets/stylesheets/index.scss @@ -1,5 +1,6 @@ @import 'shared'; @import 'hub-create'; +@import 'info-dialog'; * { box-sizing: border-box; @@ -66,7 +67,7 @@ body { } &__name { - width: 300px; + width: 200px; } &__preview { @@ -226,110 +227,3 @@ body { } } -.overlay { - width: 100%; - height: 100%; - top: 0; - left: 0; - position: absolute; - pointer-events: none; - color: white; - z-index: 2; -} - -.mailing-list-form { - display: flex; - height: 100%; - flex-direction: column; - justify-content: center; - text-align: center; - margin: 0; - - &__first { - width: 100%; - } - - &__email_field { - @extend %rounded-border; - @extend %default-font; - color: $light-text; - font-size: 1.2em; - background-color: transparent; - line-height: 2.0em; - padding-left: 1.25em; - padding-right: 1.25em; - margin: 0.5em 0; - width: 100%; - } - - &__submit { - @extend %bottom-button; - border: 0; - margin-top: 16px; - } - - &__privacy { - margin-top: 10px; - font-size: 0.7em; - } -} - -.dialog { - display: grid; - grid-template-columns: 1fr 20px minmax(200px,500px) 20px 1fr; - grid-template-rows: 1fr 20px 275px 20px 1fr; - width: 100%; - height: 100%; - background-color: rgba(0,0,0,.6); - - &__box { - grid-column: 3; - grid-row: 3; - position: relative; - pointer-events: auto; - - &__contents { - background-color: rgba(0,0,0,0.8); - border-radius: 8px; - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - position: relative; - - &__title { - @extend %top-title; - margin-top: 20px; - } - - &__body { - margin: 40px; - font-size: 1.1em; - margin-top: 20px; - color: $grey-text; - display: flex; - flex-direction: column; - - a { color: white } - } - - &__close { - position: absolute; - left: 12px; - top: 6px; - color: white; - font-size: 1.4em; - - background: none; - cursor: pointer; - border: none; - } - } - } -} - - - diff --git a/src/assets/stylesheets/info-dialog.scss b/src/assets/stylesheets/info-dialog.scss new file mode 100644 index 0000000000000000000000000000000000000000..a54ef8e9d6720f9d1c6ce01b03e64c8c168bba38 --- /dev/null +++ b/src/assets/stylesheets/info-dialog.scss @@ -0,0 +1,172 @@ +.dialog-overlay { + width: 100%; + height: 100%; + top: 0; + left: 0; + position: absolute; + pointer-events: none; + color: white; + z-index: 2; +} + +.dialog { + display: grid; + grid-template-columns: 1fr 20px minmax(200px,525px) 20px 1fr; + grid-template-rows: 1fr 20px 275px 20px 1fr; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,.6); + + &--tall { + grid-template-rows: 1fr 20px 675px 20px 1fr; + } + + &__box { + grid-column: 3; + grid-row: 3; + position: relative; + pointer-events: auto; + + &__contents { + background-color: rgba(0,0,0,0.8); + border-radius: 8px; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + position: relative; + + &__title { + @extend %top-title; + } + + &__body { + margin: 40px; + margin-bottom: 0px; + font-size: 1.1em; + margin-top: 20px; + color: $grey-text; + display: flex; + flex-direction: column; + + a { color: white } + } + + &__close { + position: absolute; + left: 12px; + top: 6px; + color: white; + font-size: 1.4em; + + background: none; + cursor: pointer; + border: none; + } + } + } +} + +.invite-form { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + margin: 0; + + &__buttons { + display: flex; + flex-direction: row; + align-items: center; + } + + &__link { + display: flex; + flex-direction: row; + } + + &__link_field { + @extend %rounded-border; + @extend %default-font; + color: $light-text; + font-size: 1.2em; + background-color: transparent; + line-height: 2.0em; + padding-left: 1.25em; + padding-right: 1.25em; + margin: 0.5em 0; + width: 100%; + } + + &__action-button { + @extend %bottom-button; + margin-left: 6px; + margin-right: 6px; + appearance: none; + width: 128px; + text-align: center; + -moz-appearance: none; + -webkit-appearance: none; + } +} + +.mailing-list-form { + display: flex; + height: 100%; + flex-direction: column; + justify-content: center; + text-align: center; + margin: 0; + + &__first { + width: 100%; + } + + &__email_field { + @extend %rounded-border; + @extend %default-font; + color: $light-text; + font-size: 1.2em; + background-color: transparent; + line-height: 2.0em; + padding-left: 1.25em; + padding-right: 1.25em; + margin: 0.5em 0; + width: 100%; + } + + &__submit { + @extend %bottom-button; + border: 0; + margin-top: 16px; + } + + &__privacy { + margin-top: 10px; + font-size: 0.7em; + } + + &__privacy_checkbox { + @extend %checkbox; + vertical-align: middle; + } + + &__privacy_checkbox:checked { + @extend %checkbox-checked; + vertical-align: middle; + } +} + + +.info-dialog { + &__help { + text-align: left; + + &__hud { + margin: 12px; + } + } +} diff --git a/src/assets/stylesheets/loader.scss b/src/assets/stylesheets/loader.scss index a770b54d46a7a4126c69017b89257e343d66c4ce..5b96c811f484ae517086dc707947018a25f52a81 100644 --- a/src/assets/stylesheets/loader.scss +++ b/src/assets/stylesheets/loader.scss @@ -20,9 +20,10 @@ flex-direction: column; } -.loading-panel__title { - @extend %default-font; - font-size: 1.2em; +.loading-panel__logo { + width: 150px; + height: 86px; + margin-top: 40px; } .loader-center, diff --git a/src/assets/stylesheets/profile.scss b/src/assets/stylesheets/profile.scss index 95f2caa2629d34e3f99fbab242f5cbf28b498038..4947cacbe84027d2170ced33976a29b02a7f41d9 100644 --- a/src/assets/stylesheets/profile.scss +++ b/src/assets/stylesheets/profile.scss @@ -16,6 +16,10 @@ margin: 1em 0; } + &__form { + height: 100%; + } + &__box { border-radius: 8px; display: flex; @@ -27,7 +31,21 @@ width: 60vw; min-width: 300px; max-width: 700px; - height: 500px; + min-height: 300px; + max-height: 1000px; + height: 90%; + + &__links { + display: flex; + justify-content: center; + width: 100%; + margin: 16px; + + a { + margin: 0px 12px; + color: $light-text; + } + } &--darkened { background-color: $darkest-transparent; @@ -58,31 +76,6 @@ margin: 0.5em 0; } - &__terms { - margin-bottom: 16px; - - &__checkbox { - @extend %checkbox; - vertical-align: unset; - } - &__checkbox:checked { - @extend %checkbox-checked; - } - - &__text { - display: inline-block; - max-width: 20em; - } - - &__link { - color: white; - } - - &__link:visited { - color: grey; - } - } - &__form-submit { @extend %bottom-button; margin: 0; @@ -93,6 +86,23 @@ display: flex; flex: 1 1 40px; width: 100%; + justify-content: space-between; + min-height: 80px; + + &__menu-buttons { + display: flex; + margin: 0 20px; + + &__menu-button { + @extend %fa-icon-button; + margin-left: 16px; + + &__icon { + @extend %fa-icon-button-icon; + background: transparent; + } + } + } &__icon { cursor: pointer; @@ -102,13 +112,15 @@ } &__profile_display_name { + margin-left: 16px; cursor: pointer; - flex: 6 1 auto; font-size: 1.2em; line-height: 50px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; + display: flex; + align-items: center; } &__app_name { diff --git a/src/assets/stylesheets/shared.scss b/src/assets/stylesheets/shared.scss index f959943585bbfacf37791586f5485add3e524cd1..945e127566fc6efadc6afb59196cae074a133b41 100644 --- a/src/assets/stylesheets/shared.scss +++ b/src/assets/stylesheets/shared.scss @@ -18,6 +18,7 @@ $darker-grey: rgba(64, 64, 64, 1.0); %bottom-button { @extend %default-font; + outline-style: none; font-size: 1em; font-weight: bold; margin-top: auto; @@ -28,6 +29,10 @@ $darker-grey: rgba(64, 64, 64, 1.0); padding: 12px; background: none; color: white; + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; } %top-title { @@ -47,6 +52,7 @@ $darker-grey: rgba(64, 64, 64, 1.0); color: white; border: none; font-size: 64pt; + outline-style: none; } %checkbox { @@ -66,3 +72,35 @@ $darker-grey: rgba(64, 64, 64, 1.0); outline: 9px solid white; outline-offset: -18px; } + +%fa-icon-button { + @extend %default-font; + margin: 16px 0; + padding: 0; + display: block; + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 0.8em; + outline-style: none; +} + +%fa-icon-button-icon { + background: black; + width: 40px !important; + height: 40px; + border: 3px solid white; + border-radius: 40px; + display: inline-block; + font-size: 22px; + vertical-align: sub; + line-height: 42px; + svg { + margin-left: 0px; + } +} + +%fa-icon-button-text { + margin-left: 16px; +} diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index 2ad5c1cf95deb131b7b8b4aa02add9f63c11cc7f..9ae0b27c44672c2e3b2e7519c1ab4dd503a653b9 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -13,15 +13,11 @@ "entry.daydream-via-chrome": "Using Google Chrome", "entry.enable-screen-sharing": "Share my desktop", "profile.save": "SAVE", - "profile.display_name.label": "Display name:", "profile.display_name.validation_warning": "Alphanumerics and hyphens. At least 3 characters, no more than 32", - "profile.header": "Your identity", - "profile.terms.prefix": "I confirm that I am over the age of 13 and agree to the", - "profile.terms.privacy": "privacy policy", - "profile.terms.conjunction": "and", - "profile.terms.tou": "terms of use", - "profile.terms.suffix": ".", + "profile.header": "Your display name:", "profile.avatar-selector.loading": "Loading Avatars...", + "profile.terms_of_use": "Terms of Use", + "profile.privacy_notice": "Privacy Notice", "audio.title": "Test your audio", "audio.subtitle-desktop": "Confirm HMD speaker output", "audio.subtitle-mobile": "Earphones are recommended", @@ -32,7 +28,10 @@ "audio.granted-title": "Mic permissions granted", "audio.granted-subtitle": "You can still mute yourself in-game", "audio.granted-next": "NEXT", - "exit.subtitle": "Your session has ended. Refresh your browser to start a new one.", + "exit.subtitle.exited": "Your session has ended. Refresh your browser to start a new one.", + "exit.subtitle.closed": "This room is no longer available.", + "exit.subtitle.full": "This room is full, please try again later.", + "exit.subtitle.connect_error": "Unable to connect to this room, please try again later.", "autoexit.title": "Auto-ending session in ", "autoexit.title_units": " seconds", "autoexit.subtitle": "You have started another session.", @@ -45,7 +44,9 @@ "home.webvr_disclaimer_mr_team": "Mozilla Mixed Reality", "home.view_source": "View Source", "home.join_us": "Join the Conversation", - "home.report_issue": "Report an Issue", + "home.report_issue": "Report Issues", + "home.privacy_notice": "Privacy Notice", + "home.terms_of_use": "Terms of Use", "home.get_updates": "Get Updates", "home.hero_title": "A new way to get together online.", "home.hero_subtitle": "Laugh, play, get stuff done, or just hang out.", diff --git a/src/components/stats-plus.css b/src/components/stats-plus.css index fc4417bf506b5a5916c286b72aeaf86189790732..9fc5a1908dbc72abadc6a67c0479e3cfe8d01db3 100644 --- a/src/components/stats-plus.css +++ b/src/components/stats-plus.css @@ -11,16 +11,18 @@ } :global(.rs-fps-counter) { + font-family: monospace; cursor: pointer; position: absolute; - bottom: 0; - left: 0; - padding: 8px; + top: 0; + right: 0; + padding: 8px 12px; color: #aaa; font-size: 10px; } -:global(.rs-mobile) { - bottom: auto; - top: 0; -} \ No newline at end of file +:global(.rs-base) { + right: 10px; + left: auto; + top: 10px; +} diff --git a/src/components/stats-plus.js b/src/components/stats-plus.js index c64100eeafa0e069a10ff2b005181a2dc446435e..2c4c148001c65cb2523bfa564024615a402392a8 100644 --- a/src/components/stats-plus.js +++ b/src/components/stats-plus.js @@ -53,6 +53,7 @@ AFRAME.registerComponent("stats-plus", { this.fpsEl.classList.add("rs-fps-counter"); document.body.appendChild(this.fpsEl); this.lastFpsUpdate = performance.now(); + this.lastFps = 0; this.frameCount = 0; if (scene.isMobile) { @@ -88,8 +89,11 @@ AFRAME.registerComponent("stats-plus", { // Update the fps counter text once a second if (now >= this.lastFpsUpdate + 1000) { - const fps = this.frameCount / ((now - this.lastFpsUpdate) / 1000); - this.fpsEl.innerHTML = Math.round(fps) + " FPS"; + const fps = Math.round(this.frameCount / ((now - this.lastFpsUpdate) / 1000)); + if (fps !== this.lastFps) { + this.fpsEl.innerHTML = Math.round(fps) + " FPS"; + this.lastFps = fps; + } this.lastFpsUpdate = now; this.frameCount = 0; } diff --git a/src/hub.html b/src/hub.html index edb7395d4641f10c27994b3399dc580e72f6952b..6877a63d9b1ad11d71902e7caf5f00060ca36ed1 100644 --- a/src/hub.html +++ b/src/hub.html @@ -5,7 +5,8 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta http-equiv="origin-trial" data-feature="WebVR (For Chrome M62+)" data-expires="<%= ORIGIN_TRIAL_EXPIRES %>" content="<%= ORIGIN_TRIAL_TOKEN %>"> - <title>moz://a duck</title> + <link rel="shortcut icon" type="image/png" href="/favicon.ico"/> + <title>Hubs | A new way to get together online</title> <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> <% if(NODE_ENV === "production") { %> diff --git a/src/hub.js b/src/hub.js index f57b614c42578d0d942148b26d8da4cb917198b9..5a5dfc3d34e435437ae0793cd22eb65a1252667a 100644 --- a/src/hub.js +++ b/src/hub.js @@ -112,121 +112,15 @@ AFRAME.registerInputMappings(inputConfig, true); const store = new Store(); const concurrentLoadDetector = new ConcurrentLoadDetector(); -const hubChannel = new HubChannel(store); concurrentLoadDetector.start(); // Always layer in any new default profile bits -store.update({ profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } }); +store.update({ activity: {}, settings: {}, profile: { ...generateDefaultProfile(), ...(store.state.profile || {}) } }); // Regenerate name to encourage users to change it. -if (!store.state.profile.has_changed_name) { - store.update({ profile: { display_name: generateRandomName() } }); -} - -async function exitScene() { - if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { - NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); - } - hubChannel.disconnect(); - const scene = document.querySelector("a-scene"); - scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this - document.body.removeChild(scene); -} - -function applyProfileFromStore(playerRig) { - const displayName = store.state.profile.display_name; - playerRig.setAttribute("player-info", { - displayName, - avatarSrc: "#" + (store.state.profile.avatar_id || "botdefault") - }); - document.querySelector("a-scene").emit("username-changed", { username: displayName }); -} - -async function enterScene(mediaStream, enterInVR, janusRoomId) { - const scene = document.querySelector("a-scene"); - const playerRig = document.querySelector("#player-rig"); - document.querySelector("a-scene canvas").classList.remove("blurred"); - scene.render(); - - scene.setAttribute("stats-plus", false); - - if (enterInVR) { - scene.enterVR(); - } - - AFRAME.registerInputActions(inGameActions, "default"); - - document.querySelector("#player-camera").setAttribute("look-controls", ""); - - scene.setAttribute("networked-scene", { - room: janusRoomId, - serverURL: process.env.JANUS_SERVER - }); - - if (isMobile || qsTruthy("mobile")) { - playerRig.setAttribute("virtual-gamepad-controls", {}); - } - - const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig); - applyProfileOnPlayerRig(); - store.addEventListener("statechanged", applyProfileOnPlayerRig); - - const avatarScale = parseInt(qs.avatar_scale, 10); - - if (avatarScale) { - playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); - } - - const videoTracks = mediaStream.getVideoTracks(); - let sharingScreen = videoTracks.length > 0; - - const screenEntityId = `${NAF.clientId}-screen`; - let screenEntity = document.getElementById(screenEntityId); - - scene.addEventListener("action_share_screen", () => { - sharingScreen = !sharingScreen; - if (sharingScreen) { - for (const track of videoTracks) { - mediaStream.addTrack(track); - } - } else { - for (const track of mediaStream.getVideoTracks()) { - mediaStream.removeTrack(track); - } - } - NAF.connection.adapter.setLocalMediaStream(mediaStream); - screenEntity.setAttribute("visible", sharingScreen); - }); - - if (!qsTruthy("offline")) { - document.body.addEventListener("connected", () => { - hubChannel.sendEntryEvent().then(() => { - store.update({ lastEnteredAt: moment().toJSON() }); - }); - }); - - scene.components["networked-scene"].connect(); - - if (mediaStream) { - NAF.connection.adapter.setLocalMediaStream(mediaStream); - - if (screenEntity) { - screenEntity.setAttribute("visible", sharingScreen); - } else if (sharingScreen) { - const sceneEl = document.querySelector("a-scene"); - screenEntity = document.createElement("a-entity"); - screenEntity.id = screenEntityId; - screenEntity.setAttribute("offset-relative-to", { - target: "#player-camera", - offset: "0 0 -2", - on: "action_share_screen" - }); - screenEntity.setAttribute("networked", { template: "#video-template" }); - sceneEl.appendChild(screenEntity); - } - } - } +if (!store.state.activity.hasChangedName) { + store.update({ profile: { displayName: generateRandomName() } }); } function mountUI(scene, props = {}) { @@ -234,14 +128,12 @@ function mountUI(scene, props = {}) { const forcedVREntryType = qs.vr_entry_type || null; const enableScreenSharing = qsTruthy("enable_screen_sharing"); const htmlPrefix = document.body.dataset.htmlPrefix || ""; - const showProfileEntry = !store.state.profile.has_changed_name; + const showProfileEntry = !store.state.activity.hasChangedName; ReactDOM.render( <UIRoot {...{ scene, - enterScene, - exitScene, concurrentLoadDetector, disableAutoExitOnConcurrentLoad, forcedVREntryType, @@ -258,19 +150,149 @@ function mountUI(scene, props = {}) { const onReady = async () => { const scene = document.querySelector("a-scene"); + const hubChannel = new HubChannel(store); + document.querySelector("a-scene canvas").classList.add("blurred"); window.APP.scene = scene; registerNetworkSchemas(); + let uiProps = {}; + mountUI(scene); - let modifiedProps = {}; const remountUI = props => { - modifiedProps = { ...modifiedProps, ...props }; - mountUI(scene, modifiedProps); + uiProps = { ...uiProps, ...props }; + mountUI(scene, uiProps); }; + const applyProfileFromStore = playerRig => { + const displayName = store.state.profile.displayName; + playerRig.setAttribute("player-info", { + displayName, + avatarSrc: "#" + (store.state.profile.avatarId || "botdefault") + }); + document.querySelector("a-scene").emit("username-changed", { username: displayName }); + }; + + const exitScene = () => { + if (NAF.connection.adapter && NAF.connection.adapter.localMediaStream) { + NAF.connection.adapter.localMediaStream.getTracks().forEach(t => t.stop()); + } + hubChannel.disconnect(); + const scene = document.querySelector("a-scene"); + scene.renderer.animate(null); // Stop animation loop, TODO A-Frame should do this + document.body.removeChild(scene); + }; + + const enterScene = async (mediaStream, enterInVR, janusRoomId) => { + const scene = document.querySelector("a-scene"); + const playerRig = document.querySelector("#player-rig"); + document.querySelector("a-scene canvas").classList.remove("blurred"); + scene.render(); + + if (enterInVR) { + scene.enterVR(); + } + + AFRAME.registerInputActions(inGameActions, "default"); + + document.querySelector("#player-camera").setAttribute("look-controls", ""); + + scene.setAttribute("networked-scene", { + room: janusRoomId, + serverURL: process.env.JANUS_SERVER + }); + + scene.setAttribute("stats-plus", false); + + if (isMobile || qsTruthy("mobile")) { + playerRig.setAttribute("virtual-gamepad-controls", {}); + } + + const applyProfileOnPlayerRig = applyProfileFromStore.bind(null, playerRig); + applyProfileOnPlayerRig(); + store.addEventListener("statechanged", applyProfileOnPlayerRig); + + const avatarScale = parseInt(qs.avatar_scale, 10); + + if (avatarScale) { + playerRig.setAttribute("scale", { x: avatarScale, y: avatarScale, z: avatarScale }); + } + + const videoTracks = mediaStream.getVideoTracks(); + let sharingScreen = videoTracks.length > 0; + + const screenEntityId = `${NAF.clientId}-screen`; + let screenEntity = document.getElementById(screenEntityId); + + scene.addEventListener("action_share_screen", () => { + sharingScreen = !sharingScreen; + if (sharingScreen) { + for (const track of videoTracks) { + mediaStream.addTrack(track); + } + } else { + for (const track of mediaStream.getVideoTracks()) { + mediaStream.removeTrack(track); + } + } + NAF.connection.adapter.setLocalMediaStream(mediaStream); + screenEntity.setAttribute("visible", sharingScreen); + }); + + if (!qsTruthy("offline")) { + document.body.addEventListener("connected", () => { + hubChannel.sendEntryEvent().then(() => { + store.update({ activity: { lastEnteredAt: moment().toJSON() } }); + }); + remountUI({ occupantCount: NAF.connection.adapter.publisher.initialOccupants.length + 1 }); + }); + + document.body.addEventListener("clientConnected", () => { + remountUI({ + occupantCount: Object.keys(NAF.connection.adapter.occupants).length + 1 + }); + }); + + document.body.addEventListener("clientDisconnected", () => { + remountUI({ + occupantCount: Object.keys(NAF.connection.adapter.occupants).length + 1 + }); + }); + + scene.components["networked-scene"].connect().catch(connectError => { + // hacky until we get return codes + const isFull = connectError.error && connectError.error.msg.match(/\bfull\b/i); + remountUI({ roomUnavailableReason: isFull ? "full" : "connect_error" }); + exitScene(); + + return; + }); + + if (mediaStream) { + NAF.connection.adapter.setLocalMediaStream(mediaStream); + + if (screenEntity) { + screenEntity.setAttribute("visible", sharingScreen); + } else if (sharingScreen) { + const sceneEl = document.querySelector("a-scene"); + screenEntity = document.createElement("a-entity"); + screenEntity.id = screenEntityId; + screenEntity.setAttribute("offset-relative-to", { + target: "#player-camera", + offset: "0 0 -2", + on: "action_share_screen" + }); + screenEntity.setAttribute("networked", { template: "#video-template" }); + sceneEl.appendChild(screenEntity); + } + } + } + }; + + remountUI({ enterScene, exitScene }); + getAvailableVREntryTypes().then(availableVREntryTypes => { remountUI({ availableVREntryTypes }); }); @@ -302,10 +324,10 @@ const onReady = async () => { console.log(`Hub ID: ${hubId}`); const socketProtocol = document.location.protocol === "https:" ? "wss:" : "ws:"; - const socketPort = qs.phx_port || (process.env.NODE_ENV === "production" ? document.location.port : 443); - const socketHost = - qs.phx_host || - (process.env.NODE_ENV === "production" ? document.location.hostname : process.env.DEV_RETICULUM_SERVER); + const [retHost, retPort] = (process.env.DEV_RETICULUM_SERVER || "").split(":"); + const isProd = process.env.NODE_ENV === "production"; + const socketPort = qs.phx_port || (isProd ? document.location.port : retPort) || "443"; + const socketHost = qs.phx_host || (isProd ? document.location.hostname : retHost) || ""; const socketUrl = `${socketProtocol}//${socketHost}${socketPort ? `:${socketPort}` : ""}/socket`; console.log(`Phoenix Channel URL: ${socketUrl}`); @@ -320,11 +342,18 @@ const onReady = async () => { const hub = data.hubs[0]; const defaultSpaceTopic = hub.topics[0]; const gltfBundleUrl = defaultSpaceTopic.assets.find(a => a.asset_type === "gltf_bundle").src; - remountUI({ janusRoomId: defaultSpaceTopic.janus_room_id }); + remountUI({ janusRoomId: defaultSpaceTopic.janus_room_id, hubName: hub.name }); initialEnvironmentEl.setAttribute("gltf-bundle", `src: ${gltfBundleUrl}`); hubChannel.setPhoenixChannel(channel); }) - .receive("error", res => console.error(res)); + .receive("error", res => { + if (res.reason === "closed") { + exitScene(); + remountUI({ roomUnavailableReason: "closed" }); + } + + console.error(res); + }); }; document.addEventListener("DOMContentLoaded", onReady); diff --git a/src/index.html b/src/index.html index 6e0d03c001627862dcba3b8e3db3e991afc29a4e..89b279bdaebe6de010cc7a17fcbc8a5b941f4ba7 100644 --- a/src/index.html +++ b/src/index.html @@ -4,7 +4,8 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>moz://a duck</title> + <link rel="shortcut icon" type="image/png" href="/favicon.ico"/> + <title>Hubs | A new way to get together online</title> <link href="https://fonts.googleapis.com/css?family=Zilla+Slab:300,300i,400,400i,700" rel="stylesheet"> </head> diff --git a/src/react-components/avatar-selector.js b/src/react-components/avatar-selector.js index 6ae6ab6ac91110ae8cc65b9d5b5f2db8d075f911..266278013074732ae19c8d3ba4bbe3681c3a12ed 100644 --- a/src/react-components/avatar-selector.js +++ b/src/react-components/avatar-selector.js @@ -1,6 +1,6 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; -import { injectIntl, FormattedMessage } from "react-intl"; +import { injectIntl } from "react-intl"; import FontAwesomeIcon from "@fortawesome/react-fontawesome"; import faAngleLeft from "@fortawesome/fontawesome-free-solid/faAngleLeft"; import faAngleRight from "@fortawesome/fontawesome-free-solid/faAngleRight"; @@ -66,9 +66,10 @@ class AvatarSelector extends Component { <template data-selector=".RootScene"> <a-entity animation-mixer /> </template> + <a-animation attribute="rotation" - dur="2000" + dur="12000" to={`0 ${this.getAvatarIndex() === i ? 360 : 0} 0`} repeat="indefinite" /> @@ -78,9 +79,13 @@ class AvatarSelector extends Component { return ( <div className="avatar-selector"> - <span className="avatar-selector__loading"> - <FormattedMessage id="profile.avatar-selector.loading" /> - </span> + <div className="loading-panel"> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> + </div> + </div> <a-scene vr-mode-ui="enabled: false" ref={sce => (this.scene = sce)}> <a-assets> {avatarAssets} @@ -91,7 +96,7 @@ class AvatarSelector extends Component { <a-animation ref={anm => (this.animation = anm)} attribute="rotation" - dur="1000" + dur="2000" easing="ease-out" to={`0 ${(360 * this.getAvatarIndex() / this.props.avatars.length + 180) % 360} 0`} /> diff --git a/src/react-components/footer.js b/src/react-components/footer.js new file mode 100644 index 0000000000000000000000000000000000000000..e155bea128a151fd2013251ada9b94a5c67e6794 --- /dev/null +++ b/src/react-components/footer.js @@ -0,0 +1,96 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import FontAwesomeIcon from "@fortawesome/react-fontawesome"; +import faUsers from "@fortawesome/fontawesome-free-solid/faUsers"; +import faBars from "@fortawesome/fontawesome-free-solid/faBars"; +import faShareAlt from "@fortawesome/fontawesome-free-solid/faShareAlt"; +import faExclamation from "@fortawesome/fontawesome-free-solid/faExclamation"; +import faTimes from "@fortawesome/fontawesome-free-solid/faTimes"; +import faArrowDown from "@fortawesome/fontawesome-free-solid/faArrowDown"; +import faQuestion from "@fortawesome/fontawesome-free-solid/faQuestion"; +import faNewspaper from "@fortawesome/fontawesome-free-solid/faNewspaper"; + +import styles from "../assets/stylesheets/footer.scss"; + +export default class Footer extends Component { + static propTypes = { + hubName: PropTypes.string, + occupantCount: PropTypes.number, + onClickInvite: PropTypes.func, + onClickReport: PropTypes.func, + onClickUpdates: PropTypes.func, + onClickHelp: PropTypes.func + }; + state = { + menuVisible: false + }; + render() { + const menuVisible = this.state.menuVisible; + return ( + <div className={styles.container}> + <div className={styles.header}> + <div className={styles.hubInfo}> + <span>{this.props.hubName}</span> + </div> + <button className={styles.menuButton} onClick={() => this.setState({ menuVisible: !menuVisible })}> + <i className={styles.menuButtonIcon}> + <FontAwesomeIcon className={styles.menuButtonNarrowCloseIcon} icon={menuVisible ? faArrowDown : faBars} /> + <FontAwesomeIcon className={styles.menuButtonWideCloseIcon} icon={menuVisible ? faTimes : faBars} /> + </i> + </button> + <div className={styles.hubStats}> + <FontAwesomeIcon icon={faUsers} /> + <span className={styles.hubParticipantCount}>{this.props.occupantCount || "-"}</span> + </div> + </div> + {menuVisible && ( + <div className={styles.menu}> + <div className={styles.menuHeader}> + <div className={styles.hubInfo}> + <span>{this.props.hubName}</span> + </div> + <div className={styles.hubStats}> + <FontAwesomeIcon icon={faUsers} /> + <span className={styles.hubParticipantCount}>{this.props.occupantCount || "-"}</span> + </div> + </div> + <div className={styles.menuButtons}> + <button className={styles.menuButton} onClick={this.props.onClickInvite}> + <i className={styles.menuButtonIcon}> + <FontAwesomeIcon icon={faShareAlt} /> + </i> + <span className={styles.menuButtonText}> + <strong>Invite</strong> Friends + </span> + </button> + <button className={styles.menuButton} onClick={this.props.onClickHelp}> + <i className={styles.menuButtonIcon}> + <FontAwesomeIcon icon={faQuestion} /> + </i> + <span className={styles.menuButtonText}> + <strong>Learn</strong> the Basics + </span> + </button> + <button className={styles.menuButton} onClick={this.props.onClickUpdates}> + <i className={styles.menuButtonIcon}> + <FontAwesomeIcon icon={faNewspaper} /> + </i> + <span className={styles.menuButtonText}> + <strong>Sign Up</strong> for Updates + </span> + </button> + <button className={styles.menuButton} onClick={this.props.onClickReport}> + <i className={styles.menuButtonIcon}> + <FontAwesomeIcon icon={faExclamation} /> + </i> + <span className={styles.menuButtonText}> + <strong>Report</strong> an Issue + </span> + </button> + </div> + </div> + )} + </div> + ); + } +} diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index 88d697a2d15ffc1ebfb4528f0e38f6de7134656e..8c3bb3d1313ab787807b2b9c1c27a989e7023613 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -4,9 +4,9 @@ import { IntlProvider, FormattedMessage, addLocaleData } from "react-intl"; import en from "react-intl/locale-data/en"; import homeVideo from "../assets/video/home.webm"; import classNames from "classnames"; -import formurlencoded from "form-urlencoded"; import HubCreatePanel from "./hub-create-panel.js"; +import InfoDialog from "./info-dialog.js"; const navigatorLang = (navigator.languages && navigator.languages[0]) || navigator.language || navigator.userLanguage; @@ -50,32 +50,6 @@ class HomeRoot extends Component { }; }; - closeDialog = () => { - this.setState({ dialogType: null }); - }; - - signUpForMailingList = async e => { - e.preventDefault(); - e.stopPropagation(); - if (!this.state.mailingListPrivacy) return; - - const url = "https://www.mozilla.org/en-US/newsletter/"; - - const payload = { - email: this.state.mailingListEmail, - newsletters: "mixed-reality", - privacy: true, - fmt: "H", - source_url: document.location.href - }; - - await fetch(url, { - body: formurlencoded(payload), - method: "POST", - headers: { "content-type": "application/x-www-form-urlencoded" } - }).then(() => this.setState({ dialogType: "email_submitted" })); - }; - loadEnvironments = () => { const environments = []; @@ -92,93 +66,11 @@ class HomeRoot extends Component { }; render() { - let dialogTitle = null; - let dialogBody = null; - - switch (this.state.dialogType) { - // TODO i18n, FormattedMessage doesn't play nicely with links - case "slack": - dialogTitle = "Get in Touch"; - dialogBody = ( - <span> - Want to join the conversation? - <p /> - Join us on the{" "} - <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> - WebVR Slack - </a>{" "} - in the #social channel.<br />VR meetups every Friday at noon PST! - <p /> Or, tweet at{" "} - <a href="https://twitter.com/mozillareality" target="_blank" rel="noopener noreferrer"> - @mozillareality - </a>{" "} - on Twitter. - </span> - ); - break; - case "email_submitted": - dialogTitle = ""; - dialogBody = "Great! Please check your e-mail to confirm your subscription."; - break; - case "updates": - dialogTitle = ""; - dialogBody = ( - <span> - Sign up to get release notes about new features. - <p /> - <form onSubmit={this.signUpForMailingList}> - <div className="mailing-list-form"> - <input - type="email" - value={this.state.mailingListEmail} - onChange={e => this.setState({ mailingListEmail: e.target.value })} - className="mailing-list-form__email_field" - required - placeholder="Your email here" - /> - <label className="mailing-list-form__privacy"> - <input - className="mailing-list-form__privacy_checkbox" - type="checkbox" - required - value={this.state.mailingListPrivacy} - onChange={e => this.setState({ mailingListPrivacy: e.target.checked })} - /> - <span className="mailing-list-form__privacy_label"> - <FormattedMessage id="mailing_list.privacy_label" />{" "} - <a target="_blank" rel="noopener noreferrer" href="https://www.mozilla.org/en-US/privacy/"> - <FormattedMessage id="mailing_list.privacy_link" /> - </a> - </span> - </label> - <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" /> - </div> - </form> - </span> - ); - break; - case "report": - dialogTitle = "Report an Issue"; - dialogBody = ( - <span> - Need to report a problem? - <p /> - You can file a{" "} - <a href="https://github.com/mozilla/mr-social-client/issues" target="_blank" rel="noopener noreferrer"> - Github Issue - </a>{" "} - or e-mail us for support at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. - <p /> - You can also find us in #social on the{" "} - <a href="http://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> - WebVR Slack - </a>. - </span> - ); - break; - } - - const mainContentClassNames = classNames({ "main-content": true, "main-content--noninteractive": !!dialogTitle }); + const mainContentClassNames = classNames({ + "main-content": true, + "main-content--noninteractive": !!this.state.dialogType + }); + const dialogTypes = InfoDialog.dialogTypes; return ( <IntlProvider locale={lang} messages={messages}> @@ -242,7 +134,7 @@ class HomeRoot extends Component { className="footer-content__links__link" rel="noopener noreferrer" href="#" - onClick={this.showDialog("slack")} + onClick={this.showDialog(dialogTypes.slack)} > <FormattedMessage id="home.join_us" /> </a> @@ -250,7 +142,7 @@ class HomeRoot extends Component { className="footer-content__links__link" rel="noopener noreferrer" href="#" - onClick={this.showDialog("updates")} + onClick={this.showDialog(dialogTypes.updates)} > <FormattedMessage id="home.get_updates" /> </a> @@ -258,10 +150,26 @@ class HomeRoot extends Component { className="footer-content__links__link" rel="noopener noreferrer" href="#" - onClick={this.showDialog("report")} + onClick={this.showDialog(dialogTypes.report)} > <FormattedMessage id="home.report_issue" /> </a> + <a + className="footer-content__links__link" + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/TERMS.md" + > + <FormattedMessage id="home.terms_of_use" /> + </a> + <a + className="footer-content__links__link" + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + > + <FormattedMessage id="home.privacy_notice" /> + </a> </div> <div className="footer-content__links__bottom"> <FormattedMessage id="home.made_with_love" /> @@ -284,20 +192,11 @@ class HomeRoot extends Component { <source src={homeVideo} type="video/webm" /> </video> {this.state.dialogType && ( - <div className="overlay"> - <div className="dialog"> - <div className="dialog__box"> - <div className="dialog__box__contents"> - <button className="dialog__box__contents__close" onClick={this.closeDialog}> - <span>🗙</span> - </button> - <div className="dialog__box__contents__title">{dialogTitle}</div> - <div className="dialog__box__contents__body">{dialogBody}</div> - <div className="dialog__box__contents__button-container" /> - </div> - </div> - </div> - </div> + <InfoDialog + dialogType={this.state.dialogType} + onCloseDialog={() => this.setState({ dialogType: null })} + onSubmittedEmail={() => this.setState({ dialogType: dialogTypes.email_submitted })} + /> )} </div> </IntlProvider> diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js index 6e689653bcc4129547ec780cacbe220cb9f954da..0b74d55b2698d51f6688e23e02e871177ecea436 100644 --- a/src/react-components/hub-create-panel.js +++ b/src/react-components/hub-create-panel.js @@ -7,7 +7,7 @@ import faAngleLeft from "@fortawesome/fontawesome-free-solid/faAngleLeft"; import faAngleRight from "@fortawesome/fontawesome-free-solid/faAngleRight"; import FontAwesomeIcon from "@fortawesome/react-fontawesome"; -import deafault_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; +import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; const HUB_NAME_PATTERN = "^[A-Za-z0-9-'\":!@#$%^&*(),.?~ ]{4,64}$"; @@ -21,15 +21,34 @@ class HubCreatePanel extends Component { super(props); this.state = { + ready: false, name: generateHubName(), environmentIndex: Math.floor(Math.random() * props.environments.length), - // HACK: expand on small screens by default to ensure scene selection possible. // Eventually this could/should be done via media queries. expanded: window.innerWidth < 420 }; + + // Optimisticly preload all environment thumbnails + (async () => { + const environmentThumbnails = props.environments.map((_, i) => this._getEnvironmentThumbnail(i)); + await Promise.all( + environmentThumbnails.map(environmentThumbnail => this._preloadImage(environmentThumbnail.srcset)) + ); + this.setState({ ready: true }); + })(); } + _getEnvironmentThumbnail = environmentIndex => { + const environment = this.props.environments[environmentIndex]; + const meta = environment.meta || {}; + return ( + (meta.images || []).find(i => i.type === "preview-thumbnail") || { + srcset: default_scene_preview_thumbnail + } + ); + }; + createHub = async e => { e.preventDefault(); const environment = this.props.environments[this.state.environmentIndex]; @@ -64,21 +83,28 @@ class HubCreatePanel extends Component { return new RegExp(HUB_NAME_PATTERN).test(this.state.name) && new RegExp(hubAlphaPattern).test(this.state.name); }; - setToEnvironmentOffset = offset => { + _preloadImage = async srcset => { + const img = new Image(); + const imgLoad = new Promise(resolve => img.addEventListener("load", resolve)); + img.srcset = srcset; + await imgLoad; + }; + + setToEnvironmentOffset = async offset => { const numEnvs = this.props.environments.length; - this.setState(state => ({ - environmentIndex: ((state.environmentIndex + offset) % this.props.environments.length + numEnvs) % numEnvs - })); + const environmentIndex = ((this.state.environmentIndex + offset) % numEnvs + numEnvs) % numEnvs; + const environmentThumbnail = this._getEnvironmentThumbnail(environmentIndex); + await this._preloadImage(environmentThumbnail.srcset); + + this.setState({ environmentIndex }); }; - setToNextEnvironment = e => { - e.preventDefault(); + setToNextEnvironment = () => { this.setToEnvironmentOffset(1); }; - setToPreviousEnvironment = e => { - e.preventDefault(); + setToPreviousEnvironment = () => { this.setToEnvironmentOffset(-1); }; @@ -90,6 +116,7 @@ class HubCreatePanel extends Component { }; render() { + if (!this.state.ready) return null; const { formatMessage } = this.props.intl; if (this.props.environments.length == 0) { @@ -101,9 +128,7 @@ class HubCreatePanel extends Component { const environmentTitle = meta.title || environment.name; const environmentAuthor = (meta.authors || [])[0]; - const environmentThumbnail = (meta.images || []).find(i => i.type === "preview-thumbnail") || { - srcset: deafault_scene_preview_thumbnail - }; + const environmentThumbnail = this._getEnvironmentThumbnail(this.state.environmentIndex); const formNameClassNames = classNames("create-panel__form__name", { "create-panel__form__name--expanded": this.state.expanded @@ -120,17 +145,16 @@ class HubCreatePanel extends Component { <div className="create-panel__form"> <div className="create-panel__form__left-container" - onClick={e => { - e.preventDefault(); - + onClick={async () => { if (this.state.expanded) { this.shuffle(); } else { + await this._preloadImage(this._getEnvironmentThumbnail(this.state.environmentIndex).srcset); this.setState({ expanded: true }); } }} > - <button className="create-panel__form__rotate-button"> + <button type="button" tabIndex="3" className="create-panel__form__rotate-button"> {this.state.expanded ? ( <img src="../assets/images/dice_icon.svg" /> ) : ( @@ -138,8 +162,8 @@ class HubCreatePanel extends Component { )} </button> </div> - <div className="create-panel__form__right-container" onClick={this.createHub}> - <button className="create-panel__form__submit-button"> + <div className="create-panel__form__right-container"> + <button type="submit" tabIndex="5" className="create-panel__form__submit-button"> {this.isHubNameValid() ? ( <img src="../assets/images/hub_create_button_enabled.svg" /> ) : ( @@ -184,6 +208,8 @@ class HubCreatePanel extends Component { <div className="create-panel__form__environment__picker__controls"> <button className="create-panel__form__environment__picker__controls__prev" + type="button" + tabIndex="1" onClick={this.setToPreviousEnvironment} > <FontAwesomeIcon icon={faAngleLeft} /> @@ -191,6 +217,8 @@ class HubCreatePanel extends Component { <button className="create-panel__form__environment__picker__controls__next" + type="button" + tabIndex="2" onClick={this.setToNextEnvironment} > <FontAwesomeIcon icon={faAngleRight} /> @@ -200,6 +228,7 @@ class HubCreatePanel extends Component { </div> )} <input + tabIndex="4" className={formNameClassNames} value={this.state.name} onChange={e => this.setState({ name: e.target.value })} diff --git a/src/react-components/info-dialog.js b/src/react-components/info-dialog.js new file mode 100644 index 0000000000000000000000000000000000000000..32f971952b473583bd2f32e7a569022e7ed71d3c --- /dev/null +++ b/src/react-components/info-dialog.js @@ -0,0 +1,243 @@ +import React, { Component } from "react"; +import copy from "copy-to-clipboard"; +import classNames from "classnames"; +import PropTypes from "prop-types"; +import { FormattedMessage } from "react-intl"; +import formurlencoded from "form-urlencoded"; + +// TODO i18n + +class InfoDialog extends Component { + static dialogTypes = { + slack: Symbol("slack"), + email_submitted: Symbol("email_submitted"), + invite: Symbol("invite"), + updates: Symbol("updates"), + report: Symbol("report"), + help: Symbol("help") + }; + static propTypes = { + dialogType: PropTypes.oneOf(Object.values(InfoDialog.dialogTypes)), + onCloseDialog: PropTypes.func, + onSubmittedEmail: PropTypes.func + }; + + constructor(props) { + super(props); + + const loc = document.location; + this.shareLink = `${loc.protocol}//${loc.host}${loc.pathname}`; + } + + shareLinkClicked = () => { + navigator.share({ + title: document.title, + url: this.shareLink + }); + }; + + copyLinkClicked = () => { + copy(this.shareLink); + this.setState({ copyLinkButtonText: "Copied!" }); + }; + + state = { + mailingListEmail: "", + mailingListPrivacy: false, + copyLinkButtonText: "Copy" + }; + + signUpForMailingList = async e => { + e.preventDefault(); + e.stopPropagation(); + if (!this.state.mailingListPrivacy) return; + + const url = "https://www.mozilla.org/en-US/newsletter/"; + + const payload = { + email: this.state.mailingListEmail, + newsletters: "mixed-reality", + privacy: true, + fmt: "H", + source_url: document.location.href + }; + + await fetch(url, { + body: formurlencoded(payload), + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" } + }).then(this.props.onSubmittedEmail); + }; + + render() { + if (!this.props.dialogType) { + return <div />; + } + + let dialogTitle = null; + let dialogBody = null; + + switch (this.props.dialogType) { + // TODO i18n, FormattedMessage doesn't play nicely with links + case InfoDialog.dialogTypes.slack: + dialogTitle = "Get in Touch"; + dialogBody = ( + <span> + Want to join the conversation? + <p /> + Join us on the{" "} + <a href="https://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> + WebVR Slack + </a>{" "} + in the #social channel.<br />VR meetups every Friday at noon PST! + <p /> Or, tweet at{" "} + <a href="https://twitter.com/mozillareality" target="_blank" rel="noopener noreferrer"> + @mozillareality + </a>{" "} + on Twitter. + </span> + ); + break; + case InfoDialog.dialogTypes.email_submitted: + dialogTitle = ""; + dialogBody = "Great! Please check your e-mail to confirm your subscription."; + break; + case InfoDialog.dialogTypes.invite: + dialogTitle = "Invite Friends"; + dialogBody = ( + <div> + <div>Just share the link to have others join you.</div> + <div className="invite-form"> + <input + type="text" + readOnly + onFocus={e => e.target.select()} + value={this.shareLink} + className="invite-form__link_field" + /> + <div className="invite-form__buttons"> + {navigator.share && ( + <button className="invite-form__action-button" onClick={this.shareLinkClicked}> + <span>Share</span> + </button> + )} + <button className="invite-form__action-button" onClick={this.copyLinkClicked}> + <span>{this.state.copyLinkButtonText}</span> + </button> + </div> + </div> + </div> + ); + break; + case InfoDialog.dialogTypes.updates: + dialogTitle = ""; + dialogBody = ( + <span> + Sign up to get updates about new features in Hubs. + <p /> + <form onSubmit={this.signUpForMailingList}> + <div className="mailing-list-form"> + <input + type="email" + value={this.state.mailingListEmail} + onChange={e => this.setState({ mailingListEmail: e.target.value })} + className="mailing-list-form__email_field" + required + placeholder="Your email here" + /> + <label className="mailing-list-form__privacy"> + <input + className="mailing-list-form__privacy_checkbox" + type="checkbox" + required + value={this.state.mailingListPrivacy} + onChange={e => this.setState({ mailingListPrivacy: e.target.checked })} + /> + <span className="mailing-list-form__privacy_label"> + <FormattedMessage id="mailing_list.privacy_label" />{" "} + <a target="_blank" rel="noopener noreferrer" href="https://www.mozilla.org/en-US/privacy/"> + <FormattedMessage id="mailing_list.privacy_link" /> + </a> + </span> + </label> + <input className="mailing-list-form__submit" type="submit" value="Sign Up Now" /> + </div> + </form> + </span> + ); + break; + case InfoDialog.dialogTypes.report: + dialogTitle = "Report an Issue"; + dialogBody = ( + <span> + Need to report a problem? + <p /> + You can file a{" "} + <a href="https://github.com/mozilla/mr-social-client/issues" target="_blank" rel="noopener noreferrer"> + Github Issue + </a>{" "} + or e-mail us for support at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. + <p /> + You can also find us in #social on the{" "} + <a href="http://webvr-slack.herokuapp.com/" target="_blank" rel="noopener noreferrer"> + WebVR Slack + </a>. + </span> + ); + break; + case InfoDialog.dialogTypes.help: + dialogTitle = "Getting Started"; + dialogBody = ( + <div className="info-dialog__help"> + When in a room, other avatars can see and hear you. + <p /> + Use your controller's action button to teleport from place to place. If it has a trigger, use it to + pick up objects. + <p style={{ textAlign: "center" }}> + In VR, <b>look up</b> to find your menu: + <img + className="info-dialog__help__hud" + src="../assets/images/help-hud.png" + srcSet="../assets/images/help-hud@2x.png 2x" + /> + </p> + <p> + The <b>Mic Toggle</b> mutes your mic. + </p> + <p> + The <b>Pause/Resume Toggle</b> pauses all other avatars. You can then block them from having further + interactions with you. + </p> + <p> + The <b>Bubble Toggle</b> hides avatars that enter your personal space. + </p> + </div> + ); + break; + } + + const dialogClasses = classNames({ + dialog: true, + "dialog--tall": this.props.dialogType === InfoDialog.dialogTypes.help + }); + + return ( + <div className="dialog-overlay"> + <div className={dialogClasses}> + <div className="dialog__box"> + <div className="dialog__box__contents"> + <button className="dialog__box__contents__close" onClick={this.props.onCloseDialog}> + <span>×</span> + </button> + <div className="dialog__box__contents__title">{dialogTitle}</div> + <div className="dialog__box__contents__body">{dialogBody}</div> + <div className="dialog__box__contents__button-container" /> + </div> + </div> + </div> + </div> + ); + } +} + +export default InfoDialog; diff --git a/src/react-components/profile-entry-panel.js b/src/react-components/profile-entry-panel.js index 2732f3ca9bd173cc3390bd1af05316b8f6614747..e10f9bc6c6f7e42b112f43dfe5d7dcb83b1f5f50 100644 --- a/src/react-components/profile-entry-panel.js +++ b/src/react-components/profile-entry-panel.js @@ -14,26 +14,28 @@ class ProfileEntryPanel extends Component { constructor(props) { super(props); - const { display_name, avatar_id } = this.props.store.state.profile; - this.state = { display_name, avatar_id }; + const { displayName, avatarId } = this.props.store.state.profile; + this.state = { displayName, avatarId }; this.props.store.addEventListener("statechanged", this.storeUpdated); } storeUpdated = () => { - const { avatar_id, display_name } = this.props.store.state.profile; - this.setState({ avatar_id, display_name }); + const { avatarId, displayName } = this.props.store.state.profile; + this.setState({ avatarId, displayName }); }; saveStateAndFinish = e => { e.preventDefault(); - const has_agreed_to_terms = this.props.store.state.profile.has_agreed_to_terms || this.state.has_agreed_to_terms; - if (!has_agreed_to_terms) return; - const { has_changed_name, display_name } = this.props.store.state.profile; - const hasChangedName = has_changed_name || this.state.display_name !== display_name; + + const { displayName } = this.props.store.state.profile; + const { hasChangedName } = this.props.store.state.activity; + + const hasChangedNowOrPreviously = hasChangedName || this.state.displayName !== displayName; this.props.store.update({ + activity: { + hasChangedName: hasChangedNowOrPreviously + }, profile: { - has_agreed_to_terms: true, - has_changed_name: hasChangedName, ...this.state } }); @@ -48,7 +50,7 @@ class ProfileEntryPanel extends Component { if (e.source !== this.avatarSelector.contentWindow) { return; } - this.setState({ avatar_id: e.data.avatarId }); + this.setState({ avatarId: e.data.avatarId }); }; componentDidMount() { @@ -72,53 +74,42 @@ class ProfileEntryPanel extends Component { return ( <div className="profile-entry"> - <form onSubmit={this.saveStateAndFinish}> + <form onSubmit={this.saveStateAndFinish} className="profile-entry__form"> <div className="profile-entry__box profile-entry__box--darkened"> - <div className="profile-entry__subtitle"> + <label htmlFor="#profile-entry-display-name" className="profile-entry__subtitle"> <FormattedMessage id="profile.header" /> - </div> + </label> <label> - <span className="profile-entry__display-name-label"> - <FormattedMessage id="profile.display_name.label" /> - </span> <input + id="profile-entry-display-name" className="profile-entry__form-field-text" - value={this.state.display_name} - onChange={e => this.setState({ display_name: e.target.value })} + value={this.state.displayName} + onFocus={e => e.target.select()} + onChange={e => this.setState({ displayName: e.target.value })} required - pattern={SCHEMA.definitions.profile.properties.display_name.pattern} + pattern={SCHEMA.definitions.profile.properties.displayName.pattern} title={formatMessage({ id: "profile.display_name.validation_warning" })} ref={inp => (this.nameInput = inp)} /> </label> <iframe className="profile-entry__avatar-selector" - src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatar_id}`} + src={`/${this.props.htmlPrefix}avatar-selector.html#avatar_id=${this.state.avatarId}`} ref={ifr => (this.avatarSelector = ifr)} /> - {!this.props.store.state.profile.has_agreed_to_terms && ( - <label className="profile-entry__terms"> - <input - className="profile-entry__terms__checkbox" - type="checkbox" - required - value={this.state.has_agreed_to_terms} - onChange={e => this.setState({ has_agreed_to_terms: e.target.checked })} - /> - <span className="profile-entry__terms__text"> - <FormattedMessage id="profile.terms.prefix" />{" "} - <a className="profile-entry__terms__link" target="_blank" href="/privacy"> - <FormattedMessage id="profile.terms.privacy" /> - </a>{" "} - <FormattedMessage id="profile.terms.conjunction" />{" "} - <a className="profile-entry__terms__link" target="_blank" href="/terms"> - <FormattedMessage id="profile.terms.tou" /> - </a> - <FormattedMessage id="profile.terms.suffix" /> - </span> - </label> - )} <input className="profile-entry__form-submit" type="submit" value={formatMessage({ id: "profile.save" })} /> + <div className="profile-entry__box__links"> + <a target="_blank" rel="noopener noreferrer" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> + <FormattedMessage id="profile.terms_of_use" /> + </a> + <a + target="_blank" + rel="noopener noreferrer" + href="https://github.com/mozilla/hubs/blob/master/PRIVACY.md" + > + <FormattedMessage id="profile.privacy_notice" /> + </a> + </div> </div> </form> </div> diff --git a/src/react-components/profile-info-header.js b/src/react-components/profile-info-header.js index ca7a3b891c3c99cc3aca753244ba4289abe525b4..f5c23dd6b0a7050aee50bcd44ec66f263ab9b827 100644 --- a/src/react-components/profile-info-header.js +++ b/src/react-components/profile-info-header.js @@ -1,19 +1,35 @@ import React from "react"; import PropTypes from "prop-types"; +import FontAwesomeIcon from "@fortawesome/react-fontawesome"; +import faQuestion from "@fortawesome/fontawesome-free-solid/faQuestion"; +import faShareAlt from "@fortawesome/fontawesome-free-solid/faShareAlt"; export const ProfileInfoHeader = props => ( <div className="profile-info-header"> - <img src="../assets/images/account.svg" onClick={props.onClick} className="profile-info-header__icon" /> - <div className="profile-info-header__profile_display_name" onClick={props.onClick} title={props.name}> - {props.name} + <div className="profile-info-header__profile_display_name"> + <img src="../assets/images/account.svg" onClick={props.onClickName} className="profile-info-header__icon" /> + <div onClick={props.onClickName} title={props.name}> + {props.name} + </div> </div> - <div className="profile-info-header__app_name"> - <b>moz://a</b> duck + <div className="profile-info-header__menu-buttons"> + <button className="profile-info-header__menu-buttons__menu-button" onClick={props.onClickInvite}> + <i className="profile-info-header__menu-buttons__menu-button__icon"> + <FontAwesomeIcon icon={faShareAlt} /> + </i> + </button> + <button className="profile-info-header__menu-buttons__menu-button" onClick={props.onClickHelp}> + <i className="profile-info-header__menu-buttons__menu-button__icon"> + <FontAwesomeIcon icon={faQuestion} /> + </i> + </button> </div> </div> ); ProfileInfoHeader.propTypes = { - onClick: PropTypes.func, + onClickName: PropTypes.func, + onClickInvite: PropTypes.func, + onClickHelp: PropTypes.func, name: PropTypes.string }; diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 2883d23f066498e941d561b6722b9fc5b434590c..ca9eca0d14b0df4a1bc27083f21c7452d8ce78a9 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -12,7 +12,9 @@ import AutoExitWarning from "./auto-exit-warning"; import { TwoDEntryButton, GenericEntryButton, GearVREntryButton, DaydreamEntryButton } from "./entry-buttons.js"; import { ProfileInfoHeader } from "./profile-info-header.js"; import ProfileEntryPanel from "./profile-entry-panel"; +import InfoDialog from "./info-dialog.js"; import TwoDHUD from "./2d-hud"; +import Footer from "./footer"; const mobiledetect = new MobileDetect(navigator.userAgent); @@ -64,12 +66,16 @@ class UIRoot extends Component { showProfileEntry: PropTypes.bool, availableVREntryTypes: PropTypes.object, initialEnvironmentLoaded: PropTypes.bool, - janusRoomId: PropTypes.number + janusRoomId: PropTypes.number, + roomUnavailableReason: PropTypes.string, + hubName: PropTypes.string, + occupantCount: PropTypes.number }; state = { entryStep: ENTRY_STEPS.start, enterInVR: false, + infoDialogType: null, shareScreen: false, requestedScreen: false, @@ -309,7 +315,7 @@ class UIRoot extends Component { setMediaStreamToDefault = async () => { let hasAudio = false; - const { lastUsedMicDeviceId } = this.props.store.state; + const { lastUsedMicDeviceId } = this.props.store.state.settings; // Try to fetch last used mic, if there was one. if (lastUsedMicDeviceId) { @@ -404,7 +410,7 @@ class UIRoot extends Component { const micDeviceId = this.micDeviceIdForMicLabel(this.micLabelForMediaStream(mediaStream)); if (micDeviceId) { - this.props.store.update({ lastUsedMicDeviceId: micDeviceId }); + this.props.store.update({ settings: { lastUsedMicDeviceId: micDeviceId } }); } this.setState({ micLevelAudioContext, micUpdateInterval }); @@ -505,33 +511,51 @@ class UIRoot extends Component { }; render() { - if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.janusRoomId) { + if (this.state.exited || this.props.roomUnavailableReason) { + let subtitle = null; + if (this.props.roomUnavailableReason !== "closed") { + const exitSubtitleId = `exit.subtitle.${this.state.exited ? "exited" : this.props.roomUnavailableReason}`; + subtitle = <FormattedMessage id={exitSubtitleId} />; + } else { + // TODO i18n, due to links and markup + subtitle = ( + <div> + Sorry, this room is no longer available. + <p /> + A room may be closed if we receive reports that it violates our{" "} + <a target="_blank" rel="noreferrer noopener" href="https://github.com/mozilla/hubs/blob/master/TERMS.md"> + Terms of Use + </a>. + <br /> + If you have questions, contact us at <a href="mailto:hubs@mozilla.com">hubs@mozilla.com</a>. + <p /> + If you'd like to run your own server, Hubs's source code is available on{" "} + <a href="https://github.com/mozilla/hubs">Github</a>. + </div> + ); + } + return ( <IntlProvider locale={lang} messages={messages}> - <div className="loading-panel"> - <div className="loader-wrap"> - <div className="loader"> - <div className="loader-center" /> - </div> - </div> - <div className="loading-panel__title"> - <b>moz://a</b> duck - </div> + <div className="exited-panel"> + <img className="exited-panel__logo" src="../assets/images/logo.svg" /> + <div className="exited-panel__subtitle">{subtitle}</div> </div> </IntlProvider> ); } - if (this.state.exited) { + if (!this.props.initialEnvironmentLoaded || !this.props.availableVREntryTypes || !this.props.janusRoomId) { return ( <IntlProvider locale={lang} messages={messages}> - <div className="exited-panel"> - <div className="loading-panel__title"> - <b>moz://a</b> duck - </div> - <div className="loading-panel__subtitle"> - <FormattedMessage id="exit.subtitle" /> + <div className="loading-panel"> + <div className="loader-wrap"> + <div className="loader"> + <div className="loader-center" /> + </div> </div> + + <img className="loading-panel__logo" src="../assets/images/logo-narrow.svg" /> </div> </IntlProvider> ); @@ -725,8 +749,10 @@ class UIRoot extends Component { ) : ( <div className="entry-dialog"> <ProfileInfoHeader - name={this.props.store.state.profile.display_name} - onClick={() => this.setState({ showProfileEntry: true })} + name={this.props.store.state.profile.displayName} + onClickName={() => this.setState({ showProfileEntry: true })} + onClickInvite={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.invite })} + onClickHelp={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.help })} /> {entryPanel} {micPanel} @@ -738,7 +764,7 @@ class UIRoot extends Component { "ui-dialog--darkened": this.state.entryStep !== ENTRY_STEPS.finished }); - const dialogBoxClassNames = classNames("ui-interactive", "ui-dialog-box"); + const dialogBoxClassNames = classNames({ "ui-interactive": !this.state.infoDialogType, "ui-dialog-box": true }); const dialogBoxContentsClassNames = classNames({ "ui-dialog-box-contents": true, @@ -748,6 +774,12 @@ class UIRoot extends Component { return ( <IntlProvider locale={lang} messages={messages}> <div className="ui"> + <InfoDialog + dialogType={this.state.infoDialogType} + onSubmittedEmail={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.email_submitted })} + onCloseDialog={() => this.setState({ infoDialogType: null })} + /> + <div className={dialogClassNames}> {(this.state.entryStep !== ENTRY_STEPS.finished || this.isWaitingForAutoExit()) && ( <div className={dialogBoxClassNames}> @@ -764,7 +796,17 @@ class UIRoot extends Component { )} </div> {this.state.entryStep === ENTRY_STEPS.finished ? ( - <TwoDHUD muted={this.state.muted} onToggleMute={this.toggleMute} /> + <div> + <TwoDHUD muted={this.state.muted} onToggleMute={this.toggleMute} /> + <Footer + hubName={this.props.hubName} + occupantCount={this.props.occupantCount} + onClickInvite={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.invite })} + onClickReport={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.report })} + onClickHelp={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.help })} + onClickUpdates={() => this.setState({ infoDialogType: InfoDialog.dialogTypes.updates })} + /> + </div> ) : null} </div> </IntlProvider> diff --git a/src/storage/store.js b/src/storage/store.js index 4351ebeda99e9f9665fcbbf395858296cd2e56a4..b648e0bfa3dea3c35fdc9103ed3396dc623bb05d 100644 --- a/src/storage/store.js +++ b/src/storage/store.js @@ -1,8 +1,7 @@ -import uuid from "uuid/v4"; import { Validator } from "jsonschema"; import { merge } from "lodash"; -const LOCAL_STORE_KEY = "___mozilla_duck"; +const LOCAL_STORE_KEY = "___hubs_store"; const STORE_STATE_CACHE_KEY = Symbol(); const validator = new Validator(); import { EventTarget } from "event-target-shim"; @@ -10,17 +9,32 @@ import { EventTarget } from "event-target-shim"; // Durable (via local-storage) schema-enforced state that is meant to be consumed via forward data flow. // (Think flux but with way less incidental complexity, at least for now :)) export const SCHEMA = { - id: "/MozillaDuckStore", + id: "/HubsStore", definitions: { profile: { type: "object", additionalProperties: false, properties: { - has_agreed_to_terms: { type: "boolean" }, - has_changed_name: { type: "boolean" }, - display_name: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" }, - avatar_id: { type: "string" } + displayName: { type: "string", pattern: "^[A-Za-z0-9-]{3,32}$" }, + avatarId: { type: "string" } + } + }, + + activity: { + type: "object", + additionalProperties: false, + properties: { + hasChangedName: { type: "boolean" }, + lastEnteredAt: { type: "string" } + } + }, + + settings: { + type: "object", + additionalProperties: false, + properties: { + lastUsedMicDeviceId: { type: "string" } } } }, @@ -28,10 +42,9 @@ export const SCHEMA = { type: "object", properties: { - id: { type: "string", pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" }, profile: { $ref: "#/definitions/profile" }, - lastUsedMicDeviceId: { type: "string" }, - lastEnteredAt: { type: "string" } + activity: { $ref: "#/definitions/activity" }, + settings: { $ref: "#/definitions/settings" } }, additionalProperties: false @@ -42,7 +55,7 @@ export default class Store extends EventTarget { super(); if (localStorage.getItem(LOCAL_STORE_KEY) === null) { - localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify({ id: uuid() })); + localStorage.setItem(LOCAL_STORE_KEY, JSON.stringify({})); } } @@ -55,10 +68,6 @@ export default class Store extends EventTarget { } update(newState) { - if (newState.id) { - throw new Error("Store id is immutable."); - } - const finalState = merge(this.state, newState); const isValid = validator.validate(finalState, SCHEMA).valid; diff --git a/src/systems/exit-on-blur.js b/src/systems/exit-on-blur.js index e6263a101f5a2fd1f71cd4b122a12431104e2457..ef1ca38fe442bb803d2a16730ce4641e6bb070e0 100644 --- a/src/systems/exit-on-blur.js +++ b/src/systems/exit-on-blur.js @@ -13,7 +13,7 @@ AFRAME.registerSystem("exit-on-blur", { if (this.el.isMobile) { this.exitTimeout = setTimeout(() => { this.el.dispatchEvent(new CustomEvent("exit")); - }, 10 * 1000); + }, 60 * 1000); } }, diff --git a/src/utils/hub-channel.js b/src/utils/hub-channel.js index 13e51b21b1e1f3702432a7a04c6804949c332b88..aad95ec4e7550ff29181789b9e8a8c299f8961db 100644 --- a/src/utils/hub-channel.js +++ b/src/utils/hub-channel.js @@ -46,12 +46,13 @@ export default class HubChannel { getEntryTimingFlags = () => { const entryTimingFlags = { isNewDaily: true, isNewMonthly: true, isNewDayWindow: true, isNewMonthWindow: true }; + const storedLastEnteredAt = this.store.state.activity.lastEnteredAt; - if (!this.store.state.lastEnteredAt) { + if (!storedLastEnteredAt) { return entryTimingFlags; } - const lastEntered = moment(this.store.state.lastEnteredAt); + const lastEntered = moment(storedLastEnteredAt); const lastEnteredPst = moment(lastEntered).tz("America/Los_Angeles"); const nowPst = moment().tz("America/Los_Angeles"); const dayWindowAgo = moment().subtract(1, "day"); diff --git a/src/utils/identity.js b/src/utils/identity.js index db78b027e3e851aa254532f264438e362062576c..2aaabfd899fde063857b2e60d359abfc2bb74348 100644 --- a/src/utils/identity.js +++ b/src/utils/identity.js @@ -101,8 +101,6 @@ export const avatarIds = avatars.map(av => av.id); export function generateDefaultProfile() { return { - has_agreed_to_terms: false, - has_changed_name: false, - avatar_id: selectRandom(avatarIds) + avatarId: selectRandom(avatarIds) }; } diff --git a/webpack.config.js b/webpack.config.js index 55bae82132e8d8611ba8b01ffcddf1ac49109c4b..8c4e0ed6d1f33fc621f574ecfe6d8ea52578ed59 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,6 +7,7 @@ const selfsigned = require("selfsigned"); const webpack = require("webpack"); const HTMLWebpackPlugin = require("html-webpack-plugin"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); +const CopyWebpackPlugin = require("copy-webpack-plugin"); const _ = require("lodash"); const SMOKE_PREFIX = "smoke-"; @@ -141,7 +142,9 @@ const config = { loader: "css-loader", options: { name: "[path][name]-[hash].[ext]", - minimize: process.env.NODE_ENV === "production" + minimize: process.env.NODE_ENV === "production", + localIdentName: "[name]__[local]__[hash:base64:5]", + camelCase: true } }, "sass-loader" @@ -156,7 +159,9 @@ const config = { loader: "css-loader", options: { name: "[path][name]-[hash].[ext]", - minimize: process.env.NODE_ENV === "production" + minimize: process.env.NODE_ENV === "production", + localIdentName: "[name]__[local]__[hash:base64:5]", + camelCase: true } } }) @@ -195,6 +200,12 @@ const config = { chunks: ["avatar-selector"], inject: "head" }), + new CopyWebpackPlugin([ + { + from: "src/assets/images/favicon.ico", + to: "favicon.ico" + } + ]), // Extract required css and add a content hash. new ExtractTextPlugin({ filename: "assets/stylesheets/[name]-[contenthash].css", diff --git a/yarn.lock b/yarn.lock index 6ae1ce2d8e2b6fb237724d975e27a2145a189ca7..1b58fdb1c3518400c38f068f2f3c2284a1623079 100644 --- a/yarn.lock +++ b/yarn.lock @@ -118,7 +118,7 @@ accepts@1.3.3: mime-types "~2.1.11" negotiator "0.6.1" -accepts@~1.3.4: +accepts@~1.3.4, accepts@~1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" dependencies: @@ -392,7 +392,7 @@ arraybuffer.slice@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" -arrify@^1.0.0: +arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" @@ -1635,7 +1635,7 @@ bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" -cacache@^10.0.1: +cacache@^10.0.1, cacache@^10.0.4: version "10.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" dependencies: @@ -2007,14 +2007,18 @@ colormin@^1.0.5: css-color-names "0.0.4" has "^1.0.1" -colors@*, colors@^1.1.2, colors@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +colors@*: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" colors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" +colors@^1.1.2, colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + combine-source-map@~0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" @@ -2166,6 +2170,25 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" +copy-to-clipboard@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9" + dependencies: + toggle-selection "^1.0.3" + +copy-webpack-plugin@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-4.5.1.tgz#fc4f68f4add837cc5e13d111b20715793225d29c" + dependencies: + cacache "^10.0.4" + find-cache-dir "^1.0.0" + globby "^7.1.1" + is-glob "^4.0.0" + loader-utils "^1.1.0" + minimatch "^3.0.4" + p-limit "^1.0.0" + serialize-javascript "^1.4.0" + core-js@^1.0.0: version "1.2.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" @@ -2579,6 +2602,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +dir-glob@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034" + dependencies: + arrify "^1.0.1" + path-type "^3.0.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -3062,7 +3092,42 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -express@^4.10.7, express@^4.16.2: +express@^4.10.7: + version "4.16.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.3" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +express@^4.16.2: version "4.16.2" resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: @@ -3268,6 +3333,18 @@ finalhandler@1.1.0: statuses "~1.3.1" unpipe "~1.0.0" +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + find-cache-dir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" @@ -3650,6 +3727,17 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680" + dependencies: + array-union "^1.0.1" + dir-glob "^2.0.0" + glob "^7.1.2" + ignore "^3.3.5" + pify "^3.0.0" + slash "^1.0.0" + globule@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" @@ -4041,7 +4129,7 @@ iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" -ignore@^3.3.3: +ignore@^3.3.3, ignore@^3.3.5: version "3.3.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.7.tgz#612289bfb3c220e186a58118618d5be8c1bab021" @@ -5121,9 +5209,9 @@ min-document@^2.19.0: dependencies: dom-walk "^0.1.0" -minijanus@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/minijanus/-/minijanus-0.5.0.tgz#78e1429bb5d83cb3957a538335d2ae901bf614fa" +minijanus@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/minijanus/-/minijanus-0.6.0.tgz#70d068b4e943a5e2724d0ef5703c9099a1c99bbc" minimalistic-assert@^1.0.0: version "1.0.0" @@ -5308,12 +5396,12 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -naf-janus-adapter@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/naf-janus-adapter/-/naf-janus-adapter-0.5.2.tgz#f4a9522c4e0b38fcbfe7c71b668afed67d5e133e" +naf-janus-adapter@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/naf-janus-adapter/-/naf-janus-adapter-0.6.0.tgz#6ca6f3765f5941b8bfa72eb5370f9b51bc314e1d" dependencies: debug "^3.1.0" - minijanus "^0.5.0" + minijanus "0.6.0" nan@^2.3.0, nan@^2.3.2: version "2.9.1" @@ -5356,7 +5444,7 @@ neo-async@^2.5.0: "networked-aframe@https://github.com/mozillareality/networked-aframe#mr-social-client/master": version "0.6.1" - resolved "https://github.com/mozillareality/networked-aframe#69be0e7e5f66070526c8240cb795b9e88da971a9" + resolved "https://github.com/mozillareality/networked-aframe#74c00c8c4568c8ac5ffa6ef93f342ce0138684ce" dependencies: easyrtc "1.1.0" express "^4.10.7" @@ -5745,7 +5833,7 @@ p-lazy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-lazy/-/p-lazy-1.0.0.tgz#ec53c802f2ee3ac28f166cc82d0b2b02de27a835" -p-limit@^1.1.0: +p-limit@^1.0.0, p-limit@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" dependencies: @@ -5936,6 +6024,12 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + dependencies: + pify "^3.0.0" + pbkdf2@^3.0.3: version "3.0.14" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" @@ -6334,7 +6428,7 @@ prop-types@^15.5.4, prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" -proxy-addr@~2.0.2: +proxy-addr@~2.0.2, proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" dependencies: @@ -7102,7 +7196,7 @@ serve-static@1.13.1: parseurl "~1.3.2" send "0.16.1" -serve-static@^1.10.0, serve-static@^1.8.0: +serve-static@1.13.2, serve-static@^1.10.0, serve-static@^1.8.0: version "1.13.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" dependencies: @@ -7862,6 +7956,10 @@ to-regex@^3.0.1: extend-shallow "^2.0.1" regex-not "^1.0.0" +toggle-selection@^1.0.3: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + toposort@^1.0.0: version "1.0.6" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.6.tgz#c31748e55d210effc00fdcdc7d6e68d7d7bb9cec" @@ -7914,7 +8012,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-is@~1.6.15: +type-is@~1.6.15, type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" dependencies: