diff --git a/src/assets/stylesheets/index.scss b/src/assets/stylesheets/index.scss index 014ca0a7a7..84e67fb075 100644 --- a/src/assets/stylesheets/index.scss +++ b/src/assets/stylesheets/index.scss @@ -2,6 +2,8 @@ @import 'hub-create'; @import 'info-dialog'; +$title-width: 350px; + * { box-sizing: border-box; } @@ -53,20 +55,24 @@ body { .header-content { padding: 1.5em 2.5em 1.5em 2.5em; - background-color: rgba(0, 0, 0, 0.85); + background-color: $darkest-transparent; min-height: 90px; height: 90px; display: flex; - border-bottom: 2px solid #242424; + border-bottom: 1px solid $darkest-grey; &__title { - flex: 10; display: flex; + width: $title-width; @media (max-width: 768px) { justify-content: center; } + @media (max-width: 1024px) { + flex: 1 1 350px; + } + &__name { width: 200px; } @@ -77,12 +83,32 @@ body { } } + &__entry-code { + @media (max-width: 1024px) { + display: none; + } + + flex: 10; + text-align: center; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + &__link { + color: white; + text-decoration-color: $light-grey; + } + } + &__experiment { text-align: right; flex: 1 1 350px; + width: 350px; color: $grey-text; font-size: 1.0em; font-weight: lighter; + white-space: nowrap; @media (max-width: 768px) { display: none; @@ -117,6 +143,26 @@ body { } } +.header-subtitle { + @media (min-width: 1024px) { + display: none; + } + + padding: 8px; + background-color: $darkest-transparent; + text-align: center; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + border-bottom: 2px solid $darkest-grey; + + &__link { + color: white; + text-decoration-color: $light-grey; + } +} + .hero-content { flex: 10; min-height: 740px; @@ -208,7 +254,7 @@ body { .footer-content { padding: 1em 2.25em 1em 2.25em; - background-color: rgba(0, 0, 0, 0.85); + background-color: $darkest-transparent; min-height: 80px; display: flex; border-top: 2px solid #242424; diff --git a/src/assets/stylesheets/link.scss b/src/assets/stylesheets/link.scss index 63da92c970..b94325c503 100644 --- a/src/assets/stylesheets/link.scss +++ b/src/assets/stylesheets/link.scss @@ -108,6 +108,7 @@ a { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr 1fr; + text-align: center; } :local(.keypad-button) { @@ -116,8 +117,8 @@ a { font-family: sans-serif; border: 4px $light-grey solid; border-radius: 128px; - min-width: 88px; - min-height: 88px; + min-width: 80px; + min-height: 80px; cursor: pointer; line-height: 68px; margin: 8px; @@ -170,7 +171,7 @@ a { margin: 0; font-size: 64pt; border: 0; - width: 225px; + width: 295px; letter-spacing: 0.08em; text-align: center; } diff --git a/src/assets/stylesheets/shared.scss b/src/assets/stylesheets/shared.scss index 18944a4080..e54447f58f 100644 --- a/src/assets/stylesheets/shared.scss +++ b/src/assets/stylesheets/shared.scss @@ -1,11 +1,12 @@ $dark-transparent: rgba(0, 0, 0, 0.4); $darker-transparent: rgba(0, 0, 0, 0.6); -$darkest-transparent: rgba(0, 0, 0, 0.95); +$darkest-transparent: rgba(0, 0, 0, 0.9); $grey-text: rgba(192, 192, 192, 1.0); $light-text: rgba(240, 240, 240, 1.0); $light-grey: lightgrey; $dark-grey: rgba(128, 128, 128, 1.0); $darker-grey: rgba(64, 64, 64, 1.0); +$darkest-grey: rgba(32, 32, 32, 1.0); %default-font { font-family: 'Zilla Slab', sans-serif; diff --git a/src/assets/translations.data.json b/src/assets/translations.data.json index c6ef31c5c9..0201cf0fca 100644 --- a/src/assets/translations.data.json +++ b/src/assets/translations.data.json @@ -13,6 +13,7 @@ "entry.device-medium": "Device", "entry.device-subtitle-desktop": "Standalone Headset or Phone", "entry.device-subtitle-mobile": "Mobile Headset or PC", + "entry.device-subtitle-vr": "Phone or PC", "entry.cardboard": "Enter on Google Cardboard", "entry.daydream-prefix": "Enter on ", "entry.daydream-medium": "Daydream", @@ -59,10 +60,11 @@ "home.made_with_love": "made with 🦆 by ", "home.environment_author_by": " by ", "home.dialog.close": "CLOSE", + "home.have_entry_code": "Have a link code?", "mailing_list.privacy_label": "I'm okay with Mozilla handling my info as explained in", "mailing_list.privacy_link": "this Privacy Notice", "link.in_your_browser": "In your device's browser, go to:", - "link.enter_code": "Then, enter this code:", + "link.enter_code": "Then, enter this link code:", "link.do_not_close": "Keep this dialog open to use this code.", "link.link_page_header": "Enter your code:", "link.dont_have_a_code": "Don't have a code?", diff --git a/src/hub.js b/src/hub.js index f85a4980f3..8bd8e2a29f 100644 --- a/src/hub.js +++ b/src/hub.js @@ -69,6 +69,7 @@ import UIRoot from "./react-components/ui-root"; import HubChannel from "./utils/hub-channel"; import LinkChannel from "./utils/link-channel"; import { connectToReticulum } from "./utils/phoenix-utils"; +import { disableiOSZoom } from "./utils/disable-ios-zoom"; import "./systems/personal-space-bubble"; import "./systems/app-mode"; @@ -133,6 +134,8 @@ if (!isBotMode) { registerTelemetry(); } +disableiOSZoom(); + AFRAME.registerInputBehaviour("trackpad_dpad4", trackpad_dpad4); AFRAME.registerInputBehaviour("joystick_dpad4", joystick_dpad4); AFRAME.registerInputBehaviour("msft_mr_axis_with_deadzone", msft_mr_axis_with_deadzone); diff --git a/src/link.html b/src/link.html index 8f44654c05..954061491f 100644 --- a/src/link.html +++ b/src/link.html @@ -7,6 +7,7 @@ Enter Code | Hubs by Mozilla + diff --git a/src/react-components/entry-buttons.js b/src/react-components/entry-buttons.js index 90dd40cfd2..9e8302b3c7 100644 --- a/src/react-components/entry-buttons.js +++ b/src/react-components/entry-buttons.js @@ -38,7 +38,8 @@ EntryButton.propTypes = { iconSrc: PropTypes.string, prefixMessageId: PropTypes.string, mediumMessageId: PropTypes.string, - subtitle: PropTypes.string + subtitle: PropTypes.string, + isInHMD: PropTypes.bool }; export const TwoDEntryButton = props => { @@ -91,9 +92,12 @@ export const DeviceEntryButton = props => { ...props, iconSrc: DeviceEntryImg, prefixMessageId: mobiledetect.mobile() ? "entry.device-prefix-mobile" : "entry.device-prefix-desktop", - mediumMessageId: "entry.device-medium", - subtitle: mobiledetect.mobile() ? "entry.device-subtitle-mobile" : "entry.device-subtitle-desktop" + mediumMessageId: "entry.device-medium" }; + entryButtonProps.subtitle = entryButtonProps.isInHMD + ? "entry.device-subtitle-vr" + : mobiledetect.mobile() ? "entry.device-subtitle-mobile" : "entry.device-subtitle-desktop"; + return ; }; diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index df85b42564..7c9a502446 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -99,6 +99,11 @@ class HomeRoot extends Component {
preview
+
+ + + +
@@ -132,6 +137,13 @@ class HomeRoot extends Component {
+
+
+ + + +
+
Medieval Fantasy Book by{" "} diff --git a/src/react-components/link-root.js b/src/react-components/link-root.js index 1ab8aa4782..66ab987b64 100644 --- a/src/react-components/link-root.js +++ b/src/react-components/link-root.js @@ -6,10 +6,12 @@ import en from "react-intl/locale-data/en"; import { lang, messages } from "../utils/i18n"; import classNames from "classnames"; import styles from "../assets/stylesheets/link.scss"; +import { disableiOSZoom } from "../utils/disable-ios-zoom"; const MAX_DIGITS = 4; addLocaleData([...en]); +disableiOSZoom(); class LinkRoot extends Component { static propTypes = { @@ -78,12 +80,17 @@ class LinkRoot extends Component { } }) .catch(e => { - console.error(e); this.setState({ failedAtLeastOnce: true, enteredDigits: "" }); + + if (!(e instanceof Error && (e.message === "in_use" || e.message === "failed"))) { + throw e; + } }); }; render() { + // Note we use type "tel" for the input due to https://bugzilla.mozilla.org/show_bug.cgi?id=1005603 + return (
@@ -106,7 +113,8 @@ class LinkRoot extends Component {
{ this.setState({ enteredDigits: ev.target.value }); diff --git a/src/react-components/ui-root.js b/src/react-components/ui-root.js index 803c28acde..cb69c63436 100644 --- a/src/react-components/ui-root.js +++ b/src/react-components/ui-root.js @@ -612,7 +612,9 @@ class UIRoot extends Component { this.state.entryStep === ENTRY_STEPS.start ? (
- + {this.props.availableVREntryTypes.screen !== VR_DEVICE_AVAILABILITY.no && ( + + )} {this.props.availableVREntryTypes.generic !== VR_DEVICE_AVAILABILITY.no && ( )} @@ -626,7 +628,7 @@ class UIRoot extends Component { } /> )} - + {this.props.availableVREntryTypes.cardboard !== VR_DEVICE_AVAILABILITY.no && (
diff --git a/src/utils/disable-ios-zoom.js b/src/utils/disable-ios-zoom.js new file mode 100644 index 0000000000..c2d17104a6 --- /dev/null +++ b/src/utils/disable-ios-zoom.js @@ -0,0 +1,24 @@ +import MobileDetect from "mobile-detect"; +const mobiledetect = new MobileDetect(navigator.userAgent); + +export function disableiOSZoom() { + if (!mobiledetect.is("iPhone") && !mobiledetect.is("iPad")) return; + + let lastTouchAtMs = 0; + + document.addEventListener("touchmove", ev => { + if (ev.scale === 1) return; + + ev.preventDefault(); + }); + + document.addEventListener("touchend", ev => { + const now = new Date().getTime(); + const isDoubleTouch = now - lastTouchAtMs <= 300; + lastTouchAtMs = now; + + if (isDoubleTouch) { + ev.preventDefault(); + } + }); +} diff --git a/src/utils/vr-caps-detect.js b/src/utils/vr-caps-detect.js index 79c425da26..3ab8f43706 100644 --- a/src/utils/vr-caps-detect.js +++ b/src/utils/vr-caps-detect.js @@ -35,15 +35,19 @@ const GENERIC_ENTRY_TYPE_DEVICE_BLACKLIST = [/cardboard/i]; // Once in a compatible browser, we should assume it will work (if it doesn't, it's because they don't have the headset, // haven't installed the software, our guess about their phone was wrong, etc.) // -// At the time of this writing there are three VR "entry types" that will be validated by this method: +// At the time of this writing there are the following VR "entry types" that will be validated by this method: // +// - screen: Enter "on-screen" in 2D // - generic: Generic WebVR (platform/OS agnostic indicator if a general 'Enter VR' option should be presented.) // - daydream: Google Daydream // - gearvr: Oculus GearVR +// - cardboard: Google Cardboard // +// This function also detects if the user is already in a headset, and returns the isInHMD key to be `true` if so. export async function getAvailableVREntryTypes() { const isSamsungBrowser = browser.name === "chrome" && navigator.userAgent.match(/SamsungBrowser/); const isOculusBrowser = navigator.userAgent.match(/Oculus/); + const isInHMD = isOculusBrowser; // This needs to be kept up-to-date with the latest browsers that can support VR and Hubs. // Checking for navigator.getVRDisplays always passes b/c of polyfill. @@ -51,6 +55,7 @@ export async function getAvailableVREntryTypes() { const isDaydreamCapableBrowser = !!(isWebVRCapableBrowser && browser.name === "chrome" && !isSamsungBrowser); + const screen = isInHMD ? VR_DEVICE_AVAILABILITY.no : VR_DEVICE_AVAILABILITY.yes; let generic = mobiledetect.mobile() ? VR_DEVICE_AVAILABILITY.no : VR_DEVICE_AVAILABILITY.maybe; let cardboard = VR_DEVICE_AVAILABILITY.no; @@ -65,7 +70,8 @@ export async function getAvailableVREntryTypes() { // For daydream detection, we first check if they are on an Android compatible device, and assume they // may support daydream *unless* this browser has WebVR capabilities, in which case we can do better. - let daydream = isMaybeDaydreamCompatibleDevice() ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.no; + let daydream = + isMaybeDaydreamCompatibleDevice() && !isInHMD ? VR_DEVICE_AVAILABILITY.maybe : VR_DEVICE_AVAILABILITY.no; if (isWebVRCapableBrowser) { const displays = await navigator.getVRDisplays(); @@ -94,5 +100,5 @@ export async function getAvailableVREntryTypes() { } } - return { generic, gearvr, daydream, cardboard }; + return { screen, generic, gearvr, daydream, cardboard, isInHMD }; }