diff --git a/package.json b/package.json
index b4b134061f6a0f1f025ef917eb064e811a8bdc80..7485a7a3afd28083f4aedca06cda0b9cd145cd12 100644
--- a/package.json
+++ b/package.json
@@ -71,7 +71,8 @@
     "minilog": "3.1.0",
     "mkdirp": "^0.5.1",
     "postcss-import": "^11.0.0",
-    "postcss-loader": "^2.1.4",
+    "postcss-import": "^12.0.0",
+    "postcss-loader": "^3.0.0",
     "postcss-simple-vars": "^4.0.0",
     "prop-types": "^15.5.10",
     "raf": "^3.4.0",
@@ -96,16 +97,16 @@
     "redux-throttle": "0.1.1",
     "rimraf": "^2.6.1",
     "scratch-audio": "0.1.0-prerelease.20180625202813",
-    "scratch-blocks": "0.1.0-prerelease.1532446271",
-    "scratch-l10n": "3.0.20180719145856",
-    "scratch-paint": "0.2.0-prerelease.20180718183615",
-    "scratch-render": "0.1.0-prerelease.20180724152606",
+    "scratch-blocks": "0.1.0-prerelease.1533835159",
+    "scratch-l10n": "3.0.20180803171042",
+    "scratch-paint": "0.2.0-prerelease.20180809150043",
+    "scratch-render": "0.1.0-prerelease.20180808184135",
     "scratch-storage": "0.5.1",
     "scratch-svg-renderer": "0.2.0-prerelease.20180712223402",
-    "scratch-vm": "0.2.0-prerelease.20180724192502",
+    "scratch-vm": "^0.2.0-prerelease.20180808205317",
     "selenium-webdriver": "3.6.0",
     "startaudiocontext": "1.2.1",
-    "style-loader": "^0.21.0",
+    "style-loader": "^0.22.1",
     "svg-to-image": "1.1.3",
     "text-encoding": "0.6.4",
     "to-style": "1.3.3",
diff --git a/src/components/action-menu/action-menu.css b/src/components/action-menu/action-menu.css
index 8f39946c128d445c5dbd98acbade5ac10553f1c2..e82103ca10a2d62e78912a13100355a75ed90cc3 100644
--- a/src/components/action-menu/action-menu.css
+++ b/src/components/action-menu/action-menu.css
@@ -1,4 +1,5 @@
 @import "../../css/colors.css";
+@import "../../css/z-index.css";
 
 $main-button-size: 2.75rem;
 $more-button-size: 2.25rem;
@@ -44,7 +45,7 @@ button::-moz-focus-inner {
     width: $main-button-size;
     height: $main-button-size;
     box-shadow: 0 0 0 4px $motion-transparent;
-    z-index: 20; /* TODO reorder layout to prevent z-index need */
+    z-index: $z-index-add-button;
     transition: transform, box-shadow 0.5s;
 }
 
@@ -58,6 +59,10 @@ button::-moz-focus-inner {
     height: calc($main-button-size - 1rem);
 }
 
+[dir="rtl"] .main-icon {
+    transform: scaleX(-1);
+}
+
 .more-buttons-outer {
     /*
         Need to use two divs to set different overflow x/y
@@ -71,6 +76,7 @@ button::-moz-focus-inner {
     border-top-right-radius: $more-button-size;
     width: $more-button-size;
     margin-left: calc(($main-button-size - $more-button-size) / 2);
+    margin-right: calc(($main-button-size - $more-button-size) / 2);
 
     position: absolute;
     bottom: calc($main-button-size);
@@ -150,7 +156,7 @@ button::-moz-focus-inner {
     border-radius: .25rem !important;
     box-shadow: 0 0 .5rem hsla(0, 0%, 0%, .25) !important;
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
-    z-index: 100 !important;
+    z-index: $z-index-tooltip !important;
 }
 
 $arrow-size: 0.5rem;
diff --git a/src/components/action-menu/action-menu.jsx b/src/components/action-menu/action-menu.jsx
index 2725c502b421007f85c7ec4d8621ff213f35b162..90555dfd8c1fde5ed886b81daa3f716d25d3d053 100644
--- a/src/components/action-menu/action-menu.jsx
+++ b/src/components/action-menu/action-menu.jsx
@@ -101,6 +101,7 @@ class ActionMenu extends React.Component {
             img: mainImg,
             title: mainTitle,
             moreButtons,
+            tooltipPlace,
             onClick
         } = this.props;
 
@@ -134,7 +135,7 @@ class ActionMenu extends React.Component {
                     className={styles.tooltip}
                     effect="solid"
                     id={mainTooltipId}
-                    place="left"
+                    place={tooltipPlace || 'left'}
                 />
                 <div className={styles.moreButtonsOuter}>
                     <div className={styles.moreButtons}>
@@ -142,7 +143,7 @@ class ActionMenu extends React.Component {
                             fileAccept, fileChange, fileInput}, keyId) => {
                             const isComingSoon = !handleClick;
                             const hasFileInput = fileInput;
-                            const tooltipId = title;
+                            const tooltipId = `${mainTooltipId}-${title}`;
                             return (
                                 <div key={`${tooltipId}-${keyId}`}>
                                     <button
@@ -174,7 +175,7 @@ class ActionMenu extends React.Component {
                                         })}
                                         effect="solid"
                                         id={tooltipId}
-                                        place="left"
+                                        place={tooltipPlace || 'left'}
                                     />
                                 </div>
                             );
@@ -198,7 +199,8 @@ ActionMenu.propTypes = {
         fileInput: PropTypes.func // Optional, only for file upload
     })),
     onClick: PropTypes.func.isRequired,
-    title: PropTypes.node.isRequired
+    title: PropTypes.node.isRequired,
+    tooltipPlace: PropTypes.string
 };
 
 export default ActionMenu;
diff --git a/src/components/asset-panel/selector.css b/src/components/asset-panel/selector.css
index 132f7d71dd68ec3883f9e2d6dafc2b8679148c21..535c38b0be684e8ac55d3cec8446cf758cd00833 100644
--- a/src/components/asset-panel/selector.css
+++ b/src/components/asset-panel/selector.css
@@ -32,6 +32,7 @@ $fade-out-distance: 100px;
     position: absolute;
     bottom: 0;
     left: 0;
+    right:0;
     background: linear-gradient(rgba(232,237,241, 0),rgba(232,237,241, 1));
     height: $fade-out-distance;
     width: 100%;
diff --git a/src/components/backpack/backpack.jsx b/src/components/backpack/backpack.jsx
index 0f7b0b89f7ea02a0f0e4fd2b6472debc75f82784..04cdc293798d3d2217f9eaed7299505db951c3a1 100644
--- a/src/components/backpack/backpack.jsx
+++ b/src/components/backpack/backpack.jsx
@@ -17,7 +17,7 @@ const dragTypeMap = {
     sprite: DragConstants.BACKPACK_SPRITE
 };
 
-const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, onToggle, onDelete}) => (
+const Backpack = ({containerRef, contents, dragOver, error, expanded, loading, onToggle, onDelete}) => (
     <div className={styles.backpackContainer}>
         <div
             className={styles.backpackHeader}
@@ -45,7 +45,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on
         {expanded ? (
             <div
                 className={styles.backpackList}
-                ref={dropAreaRef}
+                ref={containerRef}
             >
                 {error ? (
                     <div className={styles.statusMessage}>
@@ -104,6 +104,7 @@ const Backpack = ({contents, dragOver, dropAreaRef, error, expanded, loading, on
 );
 
 Backpack.propTypes = {
+    containerRef: PropTypes.func,
     contents: PropTypes.arrayOf(PropTypes.shape({
         id: PropTypes.string,
         thumbnailUrl: PropTypes.string,
@@ -111,7 +112,6 @@ Backpack.propTypes = {
         name: PropTypes.string
     })),
     dragOver: PropTypes.bool,
-    dropAreaRef: PropTypes.func,
     error: PropTypes.bool,
     expanded: PropTypes.bool,
     loading: PropTypes.bool,
diff --git a/src/components/browser-modal/browser-modal.jsx b/src/components/browser-modal/browser-modal.jsx
index bb5c82fcffab08ea5c860ea746f8ebc9d0a6c71e..457105727df41413ee3767d0d02cada6cdcaf1c3 100644
--- a/src/components/browser-modal/browser-modal.jsx
+++ b/src/components/browser-modal/browser-modal.jsx
@@ -60,12 +60,12 @@ const BrowserModal = ({intl, ...props}) => (
                         previewFaqLink: (
                             <a
                                 className={styles.faqLink}
-                                href="//scratch.mit.edu/preview-faq"
+                                href="//scratch.mit.edu/3faq"
                             >
                                 <FormattedMessage
-                                    defaultMessage="Preview FAQ"
-                                    description="link to Scratch 3.0 preview FAQ page"
-                                    id="gui.unsupportedBrowser.previewfaqlink"
+                                    defaultMessage="FAQ"
+                                    description="link to Scratch 3.0 FAQ page"
+                                    id="gui.unsupportedBrowser.previewfaqlinktext"
                                 />
                             </a>
                         )
diff --git a/src/components/button/button.css b/src/components/button/button.css
index 9ce746c52140b73be1ba889fae9c028ab7cfe2bb..9ba2fbd93b4b48dd489f6249e2cafd8abcfc2eec 100644
--- a/src/components/button/button.css
+++ b/src/components/button/button.css
@@ -11,10 +11,17 @@
 }
 
 .icon {
-    margin-right: .5rem;
     height: 1.5rem;
 }
 
+[dir="ltr"] .icon {
+    margin-right: .5rem;
+}
+
+[dir="rtl"] .icon {
+    margin-left: .5rem;
+}
+
 .content {
     white-space: nowrap;
 }
diff --git a/src/components/camera-modal/camera-modal.css b/src/components/camera-modal/camera-modal.css
index 4ea962fbafd68bc7ecdf3736d96930070a46f0de..7522fbb2ec286f0491d62d1fd6a67a7ebb5b2ed4 100644
--- a/src/components/camera-modal/camera-modal.css
+++ b/src/components/camera-modal/camera-modal.css
@@ -135,6 +135,10 @@ $main-button-size: 2.75rem;
     color: $ui-white;
 }
 
+[dir="rtl"] .retake-button img {
+    transform: scaleX(-1);
+}
+
 @keyframes flash {
     0% { opacity: 1; }
     100% { opacity: 0; }
diff --git a/src/components/camera-modal/camera-modal.jsx b/src/components/camera-modal/camera-modal.jsx
index f115a6f8a75f1c77286f15365137509313a8783f..8c499c3624922e7d84ac1b00e75cf8f9205c29c0 100644
--- a/src/components/camera-modal/camera-modal.jsx
+++ b/src/components/camera-modal/camera-modal.jsx
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import {defineMessages, injectIntl, intlShape} from 'react-intl';
 import Box from '../box/box.jsx';
-import Modal from '../modal/modal.jsx';
+import Modal from '../../containers/modal.jsx';
 import styles from './camera-modal.css';
 import backIcon from './icon--back.svg';
 import cameraIcon from '../action-menu/icon--camera.svg';
@@ -79,7 +79,7 @@ const CameraModal = ({intl, ...props}) => (
             {props.capture ?
                 <Box className={styles.buttonRow}>
                     <button
-                        className={styles.cancelButton}
+                        className={styles.retakeButton}
                         key="retake-button"
                         onClick={props.onBack}
                     >
diff --git a/src/components/coming-soon/coming-soon.css b/src/components/coming-soon/coming-soon.css
index bdd7f87eb4f031a9155aec4a0fd3f6affda1ad0d..091d2dd344c4bba864447041cd0c08b5ee07f5d6 100644
--- a/src/components/coming-soon/coming-soon.css
+++ b/src/components/coming-soon/coming-soon.css
@@ -16,7 +16,7 @@
     font-family: "Helvetica Neue", Helvetica, Arial, sans-serif !important;
     font-size: 1rem !important;
     line-height: 1.25rem !important;
-    z-index: $z-index-coming-soon !important;
+    z-index: $z-index-tooltip !important;
 }
 
 .coming-soon:after {
diff --git a/src/components/connection-modal/connection-modal.css b/src/components/connection-modal/connection-modal.css
index b1e99f37a3cf5222a7a2da7b2cfd2d0730186611..aec4ce630d51e7bf1bcc352d0b13855366d25b15 100644
--- a/src/components/connection-modal/connection-modal.css
+++ b/src/components/connection-modal/connection-modal.css
@@ -51,10 +51,14 @@
     align-items: center;
 }
 
-.device-tile-image {
+[dir="ltr"] .device-tile-image {
     margin-right: 0.5rem;
 }
 
+[dir="rtl"] .device-tile-image {
+    margin-left: 0.5rem;
+}
+
 .device-tile-name-wrapper {
     display: flex;
     flex-direction: column;
@@ -89,9 +93,16 @@
     align-items: flex-end;
     width: 22px;
     height: 16px;
+}
+
+[dir="ltr"] .signal-strength-meter {
     margin-right: 1rem;
 }
 
+[dir="rtl"] .signal-strength-meter {
+    margin-left: 1rem;
+}
+
 .signal-bar {
     width: 4px;
     border-radius: 4px;
@@ -122,6 +133,14 @@
     animation: spin 4s linear infinite;
 }
 
+[dir="ltr"] .radar {
+    margin-right: .5rem;
+}
+
+[dir="rtl"] .radar {
+    margin-left: .5rem;
+}
+
 @keyframes spin {
     100% {
         transform: rotate(360deg);
@@ -145,6 +164,7 @@
     position: absolute;
     top: -5px;
     right: -15px;
+    left: -15px;
     padding: 5px 5px;
     background-color: $motion-primary;
     border-radius: 100%;
@@ -215,14 +235,26 @@
     margin-left: 3rem;
 }
 
+[dir="ltr"] .scratch-link-help-step {
+    margin-left: 3rem;
+}
+
+[dir="rtl"] .scratch-link-help-step {
+    margin-right: 3rem;
+}
+
 .scratch-link-icon {
     max-width: 50px;
 }
 
-.help-step-image {
+[dir="ltr"] .help-step-image {
     margin-right: 0.5rem;
 }
 
+[dir="rtl"] .help-step-image {
+    margin-left: 0.5rem;
+}
+
 .help-step-number {
     background: $pen-primary;
     border-radius: 100%;
@@ -231,11 +263,18 @@
     align-items: center;
     color: $ui-white;
     font-weight: bold;
-    margin-right: 0.5rem;
     min-width: 2rem;
     height: 2rem;
 }
 
+[dir="ltr"] .help-step-number {
+    margin-right: 0.5rem;
+}
+
+[dir="rtl"] .help-step-number {
+    margin-left: 0.5rem;
+}
+
 .button-row {
     font-weight: bolder;
     text-align: center;
@@ -281,14 +320,26 @@
     border-bottom-left-radius: 0;
 }
 
-.button-icon-right {
+[dir="ltr"] .button-icon-right {
     margin-left: 0.5rem;
 }
+[dir="rtl"] .button-icon-right {
+    margin-right: 0.5rem;
+}
 
-.button-icon-left {
+[dir="ltr"] .button-icon-left {
     margin-right: 0.5rem;
 }
 
+[dir="rtl"] .button-icon-left {
+    margin-left: 0.5rem;
+}
+
+/* reverse back arrow icon for RTL, don't reverse other connection icons */
+[dir="rtl"] .button-icon-back {
+    transform: scaleX(-1);
+}
+
 .red-button {
     background: $red-primary;
 }
diff --git a/src/components/connection-modal/connection-modal.jsx b/src/components/connection-modal/connection-modal.jsx
index 577bcd2f71b2969a08883b8c7e8f33f73eb4ff64..6fb9cd4ff98c924e05d0ea5f35ebbf3aa7f1f348 100644
--- a/src/components/connection-modal/connection-modal.jsx
+++ b/src/components/connection-modal/connection-modal.jsx
@@ -3,7 +3,7 @@ import React from 'react';
 import keyMirror from 'keymirror';
 
 import Box from '../box/box.jsx';
-import Modal from '../modal/modal.jsx';
+import Modal from '../../containers/modal.jsx';
 
 import ScanningStep from '../../containers/scanning-step.jsx';
 import AutoScanningStep from '../../containers/auto-scanning-step.jsx';
diff --git a/src/components/connection-modal/error-step.jsx b/src/components/connection-modal/error-step.jsx
index 194e95678fed2fae311e583c35f0a676acc58aa8..a5a5ce1b70085d66bfd351599673da58da171821 100644
--- a/src/components/connection-modal/error-step.jsx
+++ b/src/components/connection-modal/error-step.jsx
@@ -1,5 +1,6 @@
 import {FormattedMessage} from 'react-intl';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import React from 'react';
 
 import Box from '../box/box.jsx';
@@ -39,7 +40,7 @@ const ErrorStep = props => (
                     onClick={props.onScanning}
                 >
                     <img
-                        className={styles.buttonIconLeft}
+                        className={classNames(styles.buttonIconLeft, styles.buttonIconBack)}
                         src={backIcon}
                     />
                     <FormattedMessage
diff --git a/src/components/connection-modal/unavailable-step.jsx b/src/components/connection-modal/unavailable-step.jsx
index 975a66debe7d0b14b25a87c632c04a494775ac4d..ae89404a1952d8ccded43103dc0b1b6af50cf036 100644
--- a/src/components/connection-modal/unavailable-step.jsx
+++ b/src/components/connection-modal/unavailable-step.jsx
@@ -1,5 +1,6 @@
 import {FormattedMessage} from 'react-intl';
 import PropTypes from 'prop-types';
+import classNames from 'classnames';
 import React from 'react';
 
 import Box from '../box/box.jsx';
@@ -64,7 +65,7 @@ const UnavailableStep = props => (
                     onClick={props.onScanning}
                 >
                     <img
-                        className={styles.buttonIconLeft}
+                        className={classNames(styles.buttonIconLeft, styles.buttonIconBack)}
                         src={backIcon}
                     />
                     <FormattedMessage
diff --git a/src/components/custom-procedures/custom-procedures.css b/src/components/custom-procedures/custom-procedures.css
index 6c8967a12d513023f337c3b58d5070281a761170..1716baab306a5b5b4a877831aaff8ee8dfbe5dda 100644
--- a/src/components/custom-procedures/custom-procedures.css
+++ b/src/components/custom-procedures/custom-procedures.css
@@ -91,6 +91,10 @@
     color: white;
 }
 
-.button-row button + button {
+[dir="ltr"] .button-row button + button {
     margin-left: 0.5rem;
 }
+
+[dir="rtl"] .button-row button + button {
+    margin-right: 0.5rem;
+}
diff --git a/src/components/custom-procedures/custom-procedures.jsx b/src/components/custom-procedures/custom-procedures.jsx
index 81756e762ee9e8ffd64492a35b59b2849aada0a7..7d2f3e1a5352afb855d6f5e59bae83164e37ff6a 100644
--- a/src/components/custom-procedures/custom-procedures.jsx
+++ b/src/components/custom-procedures/custom-procedures.jsx
@@ -1,6 +1,6 @@
 import PropTypes from 'prop-types';
 import React from 'react';
-import Modal from '../modal/modal.jsx';
+import Modal from '../../containers/modal.jsx';
 import Box from '../box/box.jsx';
 import {defineMessages, injectIntl, intlShape, FormattedMessage} from 'react-intl';
 
diff --git a/src/components/custom-procedures/icon--label.svg b/src/components/custom-procedures/icon--label.svg
index 2c2d52a66da350abe5947ffdc139a1eacb2dbbf8..c46c2f00cc8c247ff412ecfa41d0af0e4298465d 100644
Binary files a/src/components/custom-procedures/icon--label.svg and b/src/components/custom-procedures/icon--label.svg differ
diff --git a/src/components/direction-picker/dial.css b/src/components/direction-picker/dial.css
new file mode 100644
index 0000000000000000000000000000000000000000..ce9b7daea87f1f91f59437877c0504255b758666
--- /dev/null
+++ b/src/components/direction-picker/dial.css
@@ -0,0 +1,41 @@
+@import "../../css/colors.css";
+
+.container {
+    padding: 1rem;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    user-select: none;
+}
+
+.dial-container {
+    position: relative;
+}
+
+.dial-face, .dial-handle, .gauge {
+    position: absolute;
+    top: 0;
+    left: 0;
+    overflow: visible;
+}
+
+.dial-face {
+    width: 100%;
+}
+
+$dial-size: 40px;
+
+.dial-handle {
+    cursor: pointer;
+    width: $dial-size;
+    height: $dial-size;
+    /* Use margin to make positioning via top/left easier */
+    margin-left: calc($dial-size / -2);
+    margin-top: calc($dial-size / -2);
+}
+
+.gauge-path {
+    fill: $motion-transparent;
+    stroke: $motion-primary;
+    stroke-width: 1px;
+}
diff --git a/src/components/direction-picker/dial.jsx b/src/components/direction-picker/dial.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..df97fda7fa4cc03cdf61f2b4fc52f6dfcd05c814
--- /dev/null
+++ b/src/components/direction-picker/dial.jsx
@@ -0,0 +1,156 @@
+import PropTypes from 'prop-types';
+import bindAll from 'lodash.bindall';
+import React from 'react';
+import {getEventXY} from '../../lib/touch-utils';
+
+import styles from './dial.css';
+
+import dialFace from './icon--dial.svg';
+import dialHandle from './icon--handle.svg';
+
+class Dial extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'handleMouseDown',
+            'handleMouseMove',
+            'containerRef',
+            'handleRef',
+            'unbindMouseEvents'
+        ]);
+    }
+
+    componentDidMount () {
+        // Manually add touch/mouse handlers so that preventDefault can be used
+        // to prevent scrolling on touch.
+        // Tracked as a react issue https://github.com/facebook/react/issues/6436
+        this.handleElement.addEventListener('mousedown', this.handleMouseDown);
+        this.handleElement.addEventListener('touchstart', this.handleMouseDown);
+    }
+
+    componentWillUnmount () {
+        this.unbindMouseEvents();
+        this.handleElement.removeEventListener('mousedown', this.handleMouseDown);
+        this.handleElement.removeEventListener('touchstart', this.handleMouseDown);
+    }
+
+    /**
+     * Get direction from dial center to mouse move event.
+     * @param {Event} e - Mouse move event.
+     * @returns {number} Direction in degrees, clockwise, 90=horizontal.
+     */
+    directionToMouseEvent (e) {
+        const {x: mx, y: my} = getEventXY(e);
+        const bbox = this.containerElement.getBoundingClientRect();
+        const cy = bbox.top + (bbox.height / 2);
+        const cx = bbox.left + (bbox.width / 2);
+        const angle = Math.atan2(my - cy, mx - cx);
+        const degrees = angle * (180 / Math.PI);
+        return degrees + 90; // To correspond with scratch coordinate system
+    }
+
+    /**
+     * Create SVG path data string for the dial "gauge", the overlaid arc slice.
+     * @param {number} radius - The radius of the dial.
+     * @param {number} direction - Direction in degrees, clockwise, 90=horizontal.
+     * @returns {string} Path data string for the gauge.
+     */
+    gaugePath (radius, direction) {
+        const rads = (direction) * (Math.PI / 180);
+        const path = [];
+        path.push(`M ${radius} 0`);
+        path.push(`L ${radius} ${radius}`);
+        path.push(`L ${radius + (radius * Math.sin(rads))} ${radius - (radius * Math.cos(rads))}`);
+        path.push(`A ${radius} ${radius} 0 0 ${direction < 0 ? 1 : 0} ${radius} 0`);
+        path.push(`Z`);
+        return path.join(' ');
+    }
+
+    handleMouseMove (e) {
+        this.props.onChange(this.directionToMouseEvent(e) + this.directionOffset);
+        e.preventDefault();
+    }
+
+    unbindMouseEvents () {
+        window.removeEventListener('mousemove', this.handleMouseMove);
+        window.removeEventListener('mouseup', this.unbindMouseEvents);
+        window.removeEventListener('touchmove', this.handleMouseMove);
+        window.removeEventListener('touchend', this.unbindMouseEvents);
+    }
+
+    handleMouseDown (e) {
+        // Because the drag handle is not a single point, there is some initial
+        // difference between the current sprite direction and the direction to the mouse
+        // Store this offset to prevent jumping when the mouse is moved.
+        this.directionOffset = this.props.direction - this.directionToMouseEvent(e);
+        window.addEventListener('mousemove', this.handleMouseMove);
+        window.addEventListener('mouseup', this.unbindMouseEvents);
+        window.addEventListener('touchmove', this.handleMouseMove);
+        window.addEventListener('touchend', this.unbindMouseEvents);
+        e.preventDefault();
+    }
+
+    containerRef (el) {
+        this.containerElement = el;
+    }
+
+    handleRef (el) {
+        this.handleElement = el;
+    }
+
+    render () {
+        const {direction, radius} = this.props;
+        return (
+            <div className={styles.container}>
+                <div
+                    className={styles.dialContainer}
+                    ref={this.containerRef}
+                    style={{
+                        width: `${radius * 2}px`,
+                        height: `${radius * 2}px`
+                    }}
+                >
+                    <img
+                        className={styles.dialFace}
+                        draggable={false}
+                        src={dialFace}
+                    />
+                    <svg
+                        className={styles.gauge}
+                        height={radius * 2}
+                        width={radius * 2}
+                    >
+                        <path
+                            className={styles.gaugePath}
+                            d={this.gaugePath(radius, direction)}
+                        />
+                    </svg>
+                    <img
+                        className={styles.dialHandle}
+                        draggable={false}
+                        ref={this.handleRef}
+                        src={dialHandle}
+                        style={{
+                            top: `${radius - (radius * Math.cos(direction * (Math.PI / 180)))}px`,
+                            left: `${radius + (radius * Math.sin(direction * (Math.PI / 180)))}px`,
+                            transform: `rotate(${direction}deg)`
+                        }}
+                    />
+                </div>
+            </div>
+        );
+    }
+}
+
+Dial.propTypes = {
+    direction: PropTypes.number,
+    onChange: PropTypes.func.isRequired,
+    radius: PropTypes.number
+};
+
+Dial.defaultProps = {
+    direction: 90, // degrees
+    radius: 56 // px
+};
+
+export default Dial;
diff --git a/src/components/direction-picker/direction-picker.css b/src/components/direction-picker/direction-picker.css
new file mode 100644
index 0000000000000000000000000000000000000000..501ff02171425a3a229e56b789823a748b2d4554
--- /dev/null
+++ b/src/components/direction-picker/direction-picker.css
@@ -0,0 +1,32 @@
+@import "../../css/colors.css";
+
+.button-row {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+
+}
+
+.icon-button {
+    margin: 0.25rem;
+    border: none;
+    background: none;
+    outline: none;
+    cursor: pointer;
+    user-select: none;
+}
+
+.icon-button:active > img {
+    width: 20px;
+    height: 20px;
+    transform: scale(1.15);
+}
+
+.icon-button > img {
+    transition: transform 0.1s;
+    filter: grayscale(100%);
+}
+
+.icon-button.active > img {
+    filter: none;
+}
diff --git a/src/components/direction-picker/direction-picker.jsx b/src/components/direction-picker/direction-picker.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..626957381abf5d42680e4580ce49d557cde06a49
--- /dev/null
+++ b/src/components/direction-picker/direction-picker.jsx
@@ -0,0 +1,142 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import classNames from 'classnames';
+import Popover from 'react-popover';
+import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
+
+import Label from '../forms/label.jsx';
+import Input from '../forms/input.jsx';
+import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
+import Dial from './dial.jsx';
+
+import styles from './direction-picker.css';
+
+import allAroundIcon from './icon--all-around.svg';
+import leftRightIcon from './icon--left-right.svg';
+import dontRotateIcon from './icon--dont-rotate.svg';
+
+const BufferedInput = BufferedInputHOC(Input);
+
+const directionLabel = (
+    <FormattedMessage
+        defaultMessage="Direction"
+        description="Sprite info direction label"
+        id="gui.SpriteInfo.direction"
+    />
+);
+
+const RotationStyles = {
+    ALL_AROUND: 'all around',
+    LEFT_RIGHT: 'left-right',
+    DONT_ROTATE: "don't rotate"
+};
+
+const messages = defineMessages({
+    allAround: {
+        id: 'gui.directionPicker.rotationStyles.allAround',
+        description: 'Button to change to the all around rotation style',
+        defaultMessage: 'All Around'
+    },
+    leftRight: {
+        id: 'gui.directionPicker.rotationStyles.leftRight',
+        description: 'Button to change to the left-right rotation style',
+        defaultMessage: 'Left/Right'
+    },
+    dontRotate: {
+        id: 'gui.directionPicker.rotationStyles.dontRotate',
+        description: 'Button to change to the dont rotate rotation style',
+        defaultMessage: 'Do not rotate'
+    }
+});
+
+const DirectionPicker = props => (
+    <Label
+        secondary
+        text={directionLabel}
+    >
+        <Popover
+            body={
+                <div>
+                    <Dial
+                        direction={props.direction}
+                        onChange={props.onChangeDirection}
+                    />
+                    <div className={styles.buttonRow}>
+                        <button
+                            className={classNames(styles.iconButton, {
+                                [styles.active]: props.rotationStyle === RotationStyles.ALL_AROUND
+                            })}
+                            title={props.intl.formatMessage(messages.allAround)}
+                            onClick={props.onClickAllAround}
+                        >
+                            <img
+                                draggable={false}
+                                src={allAroundIcon}
+                            />
+                        </button>
+                        <button
+                            className={classNames(styles.iconButton, {
+                                [styles.active]: props.rotationStyle === RotationStyles.LEFT_RIGHT
+                            })}
+                            title={props.intl.formatMessage(messages.leftRight)}
+                            onClick={props.onClickLeftRight}
+                        >
+                            <img
+                                draggable={false}
+                                src={leftRightIcon}
+                            />
+                        </button>
+                        <button
+                            className={classNames(styles.iconButton, {
+                                [styles.active]: props.rotationStyle === RotationStyles.DONT_ROTATE
+                            })}
+                            title={props.intl.formatMessage(messages.dontRotate)}
+                            onClick={props.onClickDontRotate}
+                        >
+                            <img
+                                draggable={false}
+                                src={dontRotateIcon}
+                            />
+                        </button>
+                    </div>
+                </div>
+            }
+            isOpen={props.popoverOpen}
+            preferPlace="above"
+            onOuterAction={props.onClosePopover}
+        >
+            <BufferedInput
+                small
+                disabled={props.disabled}
+                label={directionLabel}
+                tabIndex="0"
+                type="text"
+                value={props.disabled ? '' : props.direction}
+                onFocus={props.onOpenPopover}
+                onSubmit={props.onChangeDirection}
+            />
+        </Popover>
+    </Label>
+
+);
+
+DirectionPicker.propTypes = {
+    direction: PropTypes.number,
+    disabled: PropTypes.bool.isRequired,
+    intl: intlShape,
+    onChangeDirection: PropTypes.func.isRequired,
+    onClickAllAround: PropTypes.func.isRequired,
+    onClickDontRotate: PropTypes.func.isRequired,
+    onClickLeftRight: PropTypes.func.isRequired,
+    onClosePopover: PropTypes.func.isRequired,
+    onOpenPopover: PropTypes.func.isRequired,
+    popoverOpen: PropTypes.bool.isRequired,
+    rotationStyle: PropTypes.string
+};
+
+const WrappedDirectionPicker = injectIntl(DirectionPicker);
+
+export {
+    WrappedDirectionPicker as default,
+    RotationStyles
+};
diff --git a/src/components/direction-picker/icon--all-around.svg b/src/components/direction-picker/icon--all-around.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2412c0b2327c3db488418d978ad382fa72335702
Binary files /dev/null and b/src/components/direction-picker/icon--all-around.svg differ
diff --git a/src/components/direction-picker/icon--dial.svg b/src/components/direction-picker/icon--dial.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d4aa8ed8205a7d51ee4c78086e409c2ef3248ff5
Binary files /dev/null and b/src/components/direction-picker/icon--dial.svg differ
diff --git a/src/components/direction-picker/icon--dont-rotate.svg b/src/components/direction-picker/icon--dont-rotate.svg
new file mode 100644
index 0000000000000000000000000000000000000000..4796c03af2562424f84487a4d8522ea16f20537c
Binary files /dev/null and b/src/components/direction-picker/icon--dont-rotate.svg differ
diff --git a/src/components/direction-picker/icon--handle.svg b/src/components/direction-picker/icon--handle.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8e5fee6e0ba3c7edd111b8a52e8fd58cb6239ddd
Binary files /dev/null and b/src/components/direction-picker/icon--handle.svg differ
diff --git a/src/components/direction-picker/icon--left-right.svg b/src/components/direction-picker/icon--left-right.svg
new file mode 100644
index 0000000000000000000000000000000000000000..4525bd6e917188b4f348b4215d4f00a15955f78d
Binary files /dev/null and b/src/components/direction-picker/icon--left-right.svg differ
diff --git a/src/components/filter/filter.css b/src/components/filter/filter.css
index b67e60795c29a100c7e67982b8dafeb8bb46c28b..fffef4812eca008d8d3e3dc30c9a540e75192eef 100644
--- a/src/components/filter/filter.css
+++ b/src/components/filter/filter.css
@@ -18,13 +18,22 @@
 .filter-icon {
     position: absolute;
     top: 0;
-    left: 0;
 
     height: 1rem;
     width: 1rem;
+}
+
+[dir="ltr"] .filter-icon {
+    left: 0;
     margin: 0.75rem 0.75rem 0.75rem 1rem;
 }
 
+[dir="rtl"] .filter-icon {
+    right: 0;
+    margin: 0.75rem 1rem 0.75rem 0.75rem;
+    transform: scaleX(-1);
+}
+
 .filter:focus-within {
     box-shadow: 0 0 0 .25rem $motion-transparent;
 }
@@ -36,7 +45,6 @@
     opacity: 0;
     position: absolute;
     top: 0;
-    right: 0;
 
     display: flex;
     justify-content: center;
@@ -53,6 +61,14 @@
     transition: opacity 0.05s linear;
 }
 
+[dir="ltr"] .x-icon-wrapper {
+    right: 0;
+}
+
+[dir="rtl"] .x-icon-wrapper {
+    left: 0;
+}
+
 /*
     Shown state
 */
@@ -96,13 +112,27 @@
     font-size: 0.75rem;
     letter-spacing: 0.15px;
     cursor: text;
+}
+
+[dir="ltr"] .filter-input {
     padding: .625rem 2rem .625rem 3rem;
 }
 
+[dir="rtl"] .filter-input {
+    padding: .625rem 3rem .625rem 2rem;
+}
+
 .filter-input::placeholder {
     opacity: .5;
-    padding: 0 0 0 0.25rem;
     color: $text-primary;
     font-size: 0.875rem;
     letter-spacing: 0.15px;
 }
+
+[dir="ltr"] .filter-input::placeholder {
+    padding: 0 0 0 0.25rem;
+}
+
+[dir="rtl"] .filter-input::placeholder {
+    padding: 0 0.25rem 0 0;
+}
diff --git a/src/components/forms/label.css b/src/components/forms/label.css
index 759a731d37efca6effb443534f24ed17e30bec5c..14691f1056a672bb91b5b1912c47661440b36566 100644
--- a/src/components/forms/label.css
+++ b/src/components/forms/label.css
@@ -9,13 +9,20 @@
 
 .input-label, .input-label-secondary {
     font-size: 0.625rem;
-    margin-right: .5rem;
     user-select: none;
     cursor: default;
 
     white-space: nowrap;
 }
 
+[dir="ltr"] .input-label, .input-label-secondary {
+    margin-right: .5rem;
+}
+
+[dir="rtl"] .input-label, .input-label-secondary {
+    margin-left: .5rem;
+}
+
 .input-label {
     font-weight: bold;
 }
diff --git a/src/components/gui/gui.css b/src/components/gui/gui.css
index e40b025735171530f8006dbbfd589e48879a82e3..1924bd8c4080c2ce3208de1c2fb211492a0fbd79 100644
--- a/src/components/gui/gui.css
+++ b/src/components/gui/gui.css
@@ -63,7 +63,6 @@
     flex-grow: 1;
     height: 80%;
     margin-bottom: 0;
-    margin-left: -0.5rem;
 
     border-radius: 1rem 1rem 0 0;
     border: 1px solid $ui-black-transparent;
@@ -82,9 +81,24 @@
     white-space: nowrap;
 }
 
+[dir="ltr"] .tab {
+    margin-left: -0.5rem;
+}
+
+[dir="rtl"] .tab {
+    margin-right: -0.5rem;
+}
+
+[dir="ltr"] .tab:nth-of-type(1) {
+    margin-left: 0;
+}
+
+[dir="rtl"] .tab:nth-of-type(1) {
+    margin-right: 0;
+}
+
 /* Use z-indices to force left-on-top for tabs */
 .tab:nth-of-type(1) {
-    margin-left: 0;
     z-index: 3;
 }
 .tab:nth-of-type(2) {
@@ -106,11 +120,19 @@
 }
 
 .tab img {
-    margin-right: 0.125rem;
     width: 1.375rem;
     filter: grayscale(100%);
 }
 
+[dir="ltr"] .tab img {
+    margin-right: 0.125rem;
+}
+
+[dir="rtl"] .tab img {
+    margin-left: 0.125rem;
+    transform: scaleX(-1);
+}
+
 .tab.is-selected img {
     filter: none;
 }
@@ -191,7 +213,7 @@
     padding-right: $space;
 
     min-height: 0; /* this makes it work in Firefox */
-    
+
     /*
         For making the sprite-selector a scrollable pane
         @todo: Not working in Safari
@@ -206,6 +228,7 @@
     position: absolute;
     bottom: 0;
     left: 0;
+    right: 0;
     z-index: $z-index-extension-button;
     background: $motion-primary;
 
@@ -240,6 +263,10 @@ $fade-out-distance: 15px;
     height: 1.75rem;
 }
 
+[dir="rtl"] .extension-button-icon {
+    transform: scaleX(-1);
+}
+
 .extension-button > div {
     margin-top: 0;
 }
diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 8d251bd14e17538e5f38da679d8ddd3eb752757d..c3b7cff716b6b07fd874c64abfa68035a593fca5 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -65,6 +65,7 @@ const GUIComponent = props => {
         importInfoVisible,
         intl,
         isPlayerOnly,
+        isRtl,
         loading,
         onExtensionButtonClick,
         onActivateCostumesTab,
@@ -110,6 +111,7 @@ const GUIComponent = props => {
         ) : (
             <Box
                 className={styles.pageWrapper}
+                dir={isRtl ? 'rtl' : 'ltr'}
                 {...componentProps}
             >
                 {previewInfoVisible ? (
@@ -282,6 +284,7 @@ GUIComponent.propTypes = {
     importInfoVisible: PropTypes.bool,
     intl: intlShape.isRequired,
     isPlayerOnly: PropTypes.bool,
+    isRtl: PropTypes.bool,
     loading: PropTypes.bool,
     onActivateCostumesTab: PropTypes.func,
     onActivateSoundsTab: PropTypes.func,
diff --git a/src/components/import-modal/import-modal.jsx b/src/components/import-modal/import-modal.jsx
index ad42929e7d6c24afced4c2febf2bda976ea27f2b..0fa3aac864978b182c294cc4ce0de911ef652e65 100644
--- a/src/components/import-modal/import-modal.jsx
+++ b/src/components/import-modal/import-modal.jsx
@@ -16,6 +16,12 @@ const messages = defineMessages({
         description: 'Scratch 2.0 import modal label - for accessibility'
     },
     formDescription: {
+        defaultMessage:
+            'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Beta will not be saved.',
+        description: 'Import project message',
+        id: 'gui.importInfo.betamessage'
+    },
+    previewFormDescription: {
         defaultMessage:
             'Enter a link to one of your shared Scratch projects. Changes made in this 3.0 Preview will not be saved.',
         description: 'Import project message',
@@ -123,12 +129,12 @@ const ImportModal = ({intl, ...props}) => (
                         previewFaqLink: (
                             <a
                                 className={styles.faqLink}
-                                href="//scratch.mit.edu/preview-faq"
+                                href="//scratch.mit.edu/3faq"
                             >
                                 <FormattedMessage
-                                    defaultMessage="Preview FAQ"
-                                    description="link to Scratch 3.0 preview FAQ page"
-                                    id="gui.importInfo.previewfaqlink"
+                                    defaultMessage="FAQ"
+                                    description="link to Scratch 3.0 FAQ page"
+                                    id="gui.importInfo.previewfaqlinktext"
                                 />
                             </a>
                         )
diff --git a/src/components/menu-bar/menu-bar.jsx b/src/components/menu-bar/menu-bar.jsx
index 08bd6b5561446df26a4968c64d559ce1d0d3f2bc..24117372b46f1524a3bcf46c4189b34e3a020d33 100644
--- a/src/components/menu-bar/menu-bar.jsx
+++ b/src/components/menu-bar/menu-bar.jsx
@@ -14,6 +14,7 @@ import ProjectLoader from '../../containers/project-loader.jsx';
 import Menu from '../../containers/menu.jsx';
 import {MenuItem, MenuSection} from '../menu/menu.jsx';
 import ProjectSaver from '../../containers/project-saver.jsx';
+import TurboMode from '../../containers/turbo-mode.jsx';
 
 import {openTipsLibrary} from '../../reducers/modals';
 import {setPlayer} from '../../reducers/mode';
@@ -297,15 +298,23 @@ class MenuBar extends React.Component {
                                     </MenuItem>
                                 </MenuItemTooltip>
                                 <MenuSection>
-                                    <MenuItemTooltip id="turbo">
-                                        <MenuItem>
-                                            <FormattedMessage
-                                                defaultMessage="Turbo mode"
-                                                description="Menu bar item for toggling turbo mode"
-                                                id="gui.menuBar.turboMode"
-                                            />
+                                    <TurboMode>{(toggleTurboMode, {turboMode}) => (
+                                        <MenuItem onClick={toggleTurboMode}>
+                                            {turboMode ? (
+                                                <FormattedMessage
+                                                    defaultMessage="Turn off Turbo Mode"
+                                                    description="Menu bar item for turning off turbo mode"
+                                                    id="gui.menuBar.turboModeOff"
+                                                />
+                                            ) : (
+                                                <FormattedMessage
+                                                    defaultMessage="Turn on Turbo Mode"
+                                                    description="Menu bar item for turning on turbo mode"
+                                                    id="gui.menuBar.turboModeOn"
+                                                />
+                                            )}
                                         </MenuItem>
-                                    </MenuItemTooltip>
+                                    )}</TurboMode>
                                 </MenuSection>
                             </MenuBarMenu>
                         </div>
@@ -376,7 +385,7 @@ class MenuBar extends React.Component {
                 <div className={classNames(styles.menuBarItem, styles.feedbackButtonWrapper)}>
                     <a
                         className={styles.feedbackLink}
-                        href="https://scratch.mit.edu/discuss/topic/299791/"
+                        href="https://scratch.mit.edu/discuss/57/"
                         rel="noopener noreferrer"
                         target="_blank"
                     >
diff --git a/src/components/modal/modal.css b/src/components/modal/modal.css
index 3b318f40ddd7b0697e33d90aa31b505c519a0394..1bb4f196343e94658a56c140447ee91ce4a739aa 100644
--- a/src/components/modal/modal.css
+++ b/src/components/modal/modal.css
@@ -100,13 +100,24 @@ $sides: 20rem;
     user-select: none;
     letter-spacing: 0.4px;
     cursor: default;
+}
+
+[dir="ltr"] .header-item-title {
     margin: 0 -$sides 0 0;
 }
 
-.full-screen .header-item-title {
+[dir="rtl"] .header-item-title {
+    margin: 0 0 0 -$sides;
+}
+
+.full-screen [dir="ltr"] .header-item-title {
     margin: 0 0 0 -$sides;
 }
 
+.full-screen [dir="rtl"] .header-item-title {
+    margin: 0 -$sides 0 0;
+}
+
 .header-item-close {
     flex-basis: $sides;
     justify-content: flex-end;
@@ -120,17 +131,36 @@ $sides: 20rem;
 
 .back-button {
     font-weight: normal;
+    padding-right: 0;
     padding-left: 0;
 }
 
+[dir="rtl"] .back-button img {
+    transform: scaleX(-1);
+}
+
 .header-item-help {
     padding: 0;
-    margin-right: -4.75rem;
     z-index: 1;
 }
 
+[dir="ltr"] .header-item-help {
+    margin-right: -4.75rem;
+}
+
+[dir="rtl"] .header-item-help {
+    margin-left: -4.75rem;
+}
+
 .help-button {
     font-weight: normal;
-    padding-right: 0;
     font-size: 0.75rem;
 }
+
+[dir="ltr"] .help-button {
+    padding-right: 0;
+}
+
+[dir="rtl"] .help-button {
+    padding-left: 0;
+}
diff --git a/src/components/modal/modal.jsx b/src/components/modal/modal.jsx
index acce21993d86b23137104f5f7bddc1215c1d26bf..32fe0502854d4676a2b617f5ccb04f46660a00d0 100644
--- a/src/components/modal/modal.jsx
+++ b/src/components/modal/modal.jsx
@@ -24,6 +24,7 @@ const ModalComponent = props => (
         onRequestClose={props.onRequestClose}
     >
         <Box
+            dir={props.isRtl ? 'rtl' : 'ltr'}
             direction="column"
             grow={1}
         >
@@ -103,6 +104,7 @@ ModalComponent.propTypes = {
     fullScreen: PropTypes.bool,
     headerClassName: PropTypes.string,
     headerImage: PropTypes.string,
+    isRtl: PropTypes.bool,
     onHelp: PropTypes.func,
     onRequestClose: PropTypes.func
 };
diff --git a/src/components/preview-modal/preview-modal.jsx b/src/components/preview-modal/preview-modal.jsx
index 5772b518539a7541f30859a3aef6540eef030878..b9c803b767782939b86193725d4ff6dd09b919d1 100644
--- a/src/components/preview-modal/preview-modal.jsx
+++ b/src/components/preview-modal/preview-modal.jsx
@@ -12,6 +12,11 @@ const messages = defineMessages({
         id: 'gui.previewInfo.label',
         defaultMessage: 'Try Scratch 3.0',
         description: 'Scratch 3.0 modal label - for accessibility'
+    },
+    previewWelcome: {
+        defaultMessage: 'Welcome to the Scratch 3.0 Beta',
+        description: 'Header for Preview Info Modal',
+        id: 'gui.previewInfo.welcome'
     }
 });
 
@@ -28,15 +33,15 @@ const PreviewModal = ({intl, ...props}) => (
         <Box className={styles.body}>
             <h2>
                 <FormattedMessage
-                    defaultMessage="Welcome to the Scratch 3.0 Preview"
-                    description="Header for Preview Info Modal"
-                    id="gui.previewInfo.welcome"
+                    defaultMessage="Welcome to the Scratch 3.0 Beta"
+                    description="Header for Beta Info Modal"
+                    id="gui.previewInfo.betawelcome"
                 />
             </h2>
             <p>
                 <FormattedMessage
                     defaultMessage="We're working on the next generation of Scratch. We're excited for you to try it!"
-                    description="Invitation to try 3.0 preview"
+                    description="Invitation to try 3.0 Beta"
                     id="gui.previewInfo.invitation"
                 />
             </p>
@@ -48,7 +53,7 @@ const PreviewModal = ({intl, ...props}) => (
                 >
                     <FormattedMessage
                         defaultMessage="Not Now"
-                        description="Label for button to back out of trying Scratch 3.0 preview"
+                        description="Label for button to back out of trying Scratch 3.0 Beta"
                         id="gui.previewInfo.notnow"
                     />
                 </button>
@@ -59,7 +64,7 @@ const PreviewModal = ({intl, ...props}) => (
                 >
                     <FormattedMessage
                         defaultMessage="Try It! {caticon}"
-                        description="Label for button to try Scratch 3.0 preview"
+                        description="Label for button to try Scratch 3.0 Beta"
                         id="gui.previewModal.tryit"
                         values={{
                             caticon: (
@@ -86,18 +91,18 @@ const PreviewModal = ({intl, ...props}) => (
             <Box className={styles.faqLinkText}>
                 <FormattedMessage
                     defaultMessage="To learn more, go to the {previewFaqLink}."
-                    description="Invitation to try 3.0 preview"
+                    description="Invitation to try 3.0 Beta"
                     id="gui.previewInfo.previewfaq"
                     values={{
                         previewFaqLink: (
                             <a
                                 className={styles.faqLink}
-                                href="//scratch.mit.edu/preview-faq"
+                                href="//scratch.mit.edu/3faq"
                             >
                                 <FormattedMessage
-                                    defaultMessage="Preview FAQ"
-                                    description="link to Scratch 3.0 preview FAQ page"
-                                    id="gui.previewInfo.previewfaqlink"
+                                    defaultMessage="FAQ"
+                                    description="link to Scratch 3.0 FAQ page"
+                                    id="gui.previewInfo.previewfaqlinktext"
                                 />
                             </a>
                         )
diff --git a/src/components/preview-modal/welcome.png b/src/components/preview-modal/welcome.png
index 520b619919b1b9dfabcef138fc16fc64aebee9e6..1172df932b425ae538bcfe2d23430373310e325e 100644
Binary files a/src/components/preview-modal/welcome.png and b/src/components/preview-modal/welcome.png differ
diff --git a/src/components/prompt/prompt.css b/src/components/prompt/prompt.css
index 78e3ecb27c6b3d2583528ed1dc6b1df6f36dca6e..ae523e188013ad9c79433ed908f4623665551a50 100644
--- a/src/components/prompt/prompt.css
+++ b/src/components/prompt/prompt.css
@@ -60,10 +60,14 @@
     color: white;
 }
 
-.button-row button + button {
+[dir="ltr"] .button-row button + button {
     margin-left: 0.5rem;
 }
 
+[dir="rtl"] .button-row button + button {
+    margin-right: 0.5rem;
+}
+
 .more-options {
     border-top: 1px dashed hsla(0, 0%, 0%, .25);
     overflow: visible;
diff --git a/src/components/prompt/prompt.jsx b/src/components/prompt/prompt.jsx
index 3e5ec7950fc1be25a4e5cc89297a9828237fd649..42de1599939801b8896374f032525be7c77982a9 100644
--- a/src/components/prompt/prompt.jsx
+++ b/src/components/prompt/prompt.jsx
@@ -4,7 +4,7 @@ import React from 'react';
 
 import Box from '../box/box.jsx';
 import {ComingSoonTooltip} from '../coming-soon/coming-soon.jsx';
-import Modal from '../modal/modal.jsx';
+import Modal from '../../containers/modal.jsx';
 
 import styles from './prompt.css';
 
diff --git a/src/components/record-modal/playback-step.jsx b/src/components/record-modal/playback-step.jsx
index 40167172deac8f7167c88d2046b89a92d5162886..8f76b506f33e8e38c00869c0a2d06bf6f6fcc471 100644
--- a/src/components/record-modal/playback-step.jsx
+++ b/src/components/record-modal/playback-step.jsx
@@ -87,7 +87,7 @@ const PlaybackStep = props => (
         </Box>
         <Box className={styles.buttonRow}>
             <button
-                className={styles.cancelButton}
+                className={styles.rerecordButton}
                 onClick={props.onBack}
             >
                 <img
diff --git a/src/components/record-modal/record-modal.css b/src/components/record-modal/record-modal.css
index 79ce9c606a995d69642eabc76b064ba06cc50aa1..ae9d8afcc0d0b2d8b5235b7c86b30c5fa3bb15b6 100644
--- a/src/components/record-modal/record-modal.css
+++ b/src/components/record-modal/record-modal.css
@@ -118,3 +118,7 @@
     opacity: 0.2;
     transition: 0.1s;
 }
+
+[dir="rtl"] .rerecord-button img {
+    transform: scaleX(-1);
+}
diff --git a/src/components/record-modal/record-modal.jsx b/src/components/record-modal/record-modal.jsx
index 2adc9c14774b9fcf8992c78a53a531c26328429d..5f4086c1c60a35f7a46e011d49248f3849d93de6 100644
--- a/src/components/record-modal/record-modal.jsx
+++ b/src/components/record-modal/record-modal.jsx
@@ -4,7 +4,7 @@ import Box from '../box/box.jsx';
 import {defineMessages, injectIntl, intlShape} from 'react-intl';
 import RecordingStep from '../../containers/recording-step.jsx';
 import PlaybackStep from '../../containers/playback-step.jsx';
-import Modal from '../modal/modal.jsx';
+import Modal from '../../containers/modal.jsx';
 import styles from './record-modal.css';
 
 const messages = defineMessages({
diff --git a/src/components/sound-editor/sound-editor.css b/src/components/sound-editor/sound-editor.css
index 7703802e307b871ecfb073f12458126d3b8b43f6..c6e620001af95c4762242a6f1d0f914c02d0d561 100644
--- a/src/components/sound-editor/sound-editor.css
+++ b/src/components/sound-editor/sound-editor.css
@@ -14,6 +14,10 @@
     align-items: center;
 }
 
+[dir="rtl"] .row-reverse {
+    flex-direction: row-reverse;
+}
+
 .row + .row {
     margin-top: calc(2 * $space);
 }
@@ -23,12 +27,28 @@
     flex-direction: row;
 }
 
-.input-group + .input-group {
+[dir="ltr"] .input-group + .input-group {
     margin-left: calc(2 * $space);
 }
 
-.input-group {
+[dir="rtl"] .input-group + .input-group {
+    margin-right: calc(2 * $space);
+}
+
+[dir="ltr"] .input-group {
+    padding-right: calc(2 * $space);
+    border-right: 1px dashed $ui-black-transparent;
+}
+
+[dir="rtl"] .input-group {
+    padding-left: calc(2 * $space);
+    border-left: 1px dashed $ui-black-transparent;
+}
+
+[dir="rtl"] .row-reverse > .input-group {
+    padding-left: 0;
     padding-right: calc(2 * $space);
+    border-left: none;
     border-right: 1px dashed $ui-black-transparent;
 }
 
@@ -87,26 +107,31 @@ $border-radius: 0.25rem;
     /*min-width: 1.5rem;*/
 }
 
+[dir="rtl"] .undo-icon, [dir="rtl"] .redo-icon {
+    transform: scaleX(-1);
+}
+
 .trim-button {
     display: flex;
     align-items: center;
     color: $text-primary;
     font-size: 0.625rem;
-    margin-left: 1rem;
     user-select: none;
 }
 
+[dir="ltr"] .trim-button {
+    margin-left: 1rem;
+}
+
+[dir="rtl"] .trim-button {
+    margin-right: 1rem;
+}
+
 .trim-button > img {
     width: 1.25rem;
     margin-bottom: -0.375rem;
 }
 
-.input-group-right {
-    flex-grow: 1;
-    display: flex;
-    flex-direction: row-reverse;
-}
-
 .effect-button {
     flex-basis: 60px;
     color: $text-primary;
@@ -124,26 +149,47 @@ $border-radius: 0.25rem;
     margin-bottom: -0.375rem;
 }
 
-.button-group {
+[dir="ltr"] .button-group {
     margin-left: 1rem;
 }
 
+[dir="rtl"] .button-group {
+    margin-right: 1rem;
+}
+
 .button-group .button {
     border-radius: 0;
+}
+
+[dir="ltr"] .button-group .button {
     border-left: none;
 }
+[dir="rtl"] .button-group .button {
+    border-right: none;
+}
 
-.button-group .button:last-of-type {
+[dir="ltr"] .button-group .button:last-of-type {
     border-top-right-radius: $border-radius;
     border-bottom-right-radius: $border-radius;
 }
 
-.button-group .button:first-of-type {
+[dir="ltr"] .button-group .button:first-of-type {
     border-left: 1px solid $ui-black-transparent;
     border-top-left-radius: $border-radius;
     border-bottom-left-radius: $border-radius;
 }
 
+[dir="rtl"] .button-group .button:last-of-type {
+    border-top-left-radius: $border-radius;
+    border-bottom-left-radius: $border-radius;
+}
+
+[dir="rtl"] .button-group .button:first-of-type {
+    border-right: 1px solid $ui-black-transparent;
+    border-top-right-radius: $border-radius;
+    border-bottom-right-radius: $border-radius;
+}
+
 .button:disabled > img {
     opacity: 0.25;
 }
diff --git a/src/components/sound-editor/sound-editor.jsx b/src/components/sound-editor/sound-editor.jsx
index a4aafcd9155968b46d30d8022092a3da11cc58ae..f97338e06a55bf75e0bceda2ddc33d8a9be1070b 100644
--- a/src/components/sound-editor/sound-editor.jsx
+++ b/src/components/sound-editor/sound-editor.jsx
@@ -122,6 +122,7 @@ const SoundEditor = props => (
                         onClick={props.onUndo}
                     >
                         <img
+                            className={styles.undoIcon}
                             draggable={false}
                             src={undoIcon}
                         />
@@ -133,6 +134,7 @@ const SoundEditor = props => (
                         onClick={props.onRedo}
                     >
                         <img
+                            className={styles.redoIcon}
                             draggable={false}
                             src={redoIcon}
                         />
@@ -168,7 +170,7 @@ const SoundEditor = props => (
                 />
             </div>
         </div>
-        <div className={styles.row}>
+        <div className={classNames(styles.row, styles.rowReverse)}>
             <div className={styles.inputGroup}>
                 {props.playhead ? (
                     <button
diff --git a/src/components/sprite-info/sprite-info.css b/src/components/sprite-info/sprite-info.css
index c835843994ce4ffb477a742fdd48c6461c288992..179b13965c5268ee367aa6fed7d2ec7abad98e3f 100644
--- a/src/components/sprite-info/sprite-info.css
+++ b/src/components/sprite-info/sprite-info.css
@@ -59,27 +59,45 @@
     cursor: default;
 }
 
-.radio-left {
+.radio-first {
     border: 1px solid $ui-black-transparent;
+}
+
+[dir="ltr"] .radio-first {
     border-top-left-radius: $form-radius;
     border-bottom-left-radius: $form-radius;
 }
 
-.radio-left:focus {
+[dir="rtl"] .radio-first {
+    border-top-right-radius: $form-radius;
+    border-bottom-right-radius: $form-radius;
+}
+
+.radio-first:focus {
     border-color: $motion-primary;
     box-shadow: inset 0 0 0 -2px $ui-black-transparent;
 }
 
-.radio-right {
+.radio-last {
     border-bottom: 1px solid $ui-black-transparent;
     border-top: 1px solid $ui-black-transparent;
+}
+
+[dir="ltr"] .radio-last {
     border-right: 1px solid $ui-black-transparent;
     border-left: 1px solid $ui-black-transparent;
     border-top-right-radius: $form-radius;
     border-bottom-right-radius: $form-radius;
 }
 
-.radio-right:focus {
+[dir="rtl"] .radio-last {
+    border-left: 1px solid $ui-black-transparent;
+    border-right: 1px solid $ui-black-transparent;
+    border-top-left-radius: $form-radius;
+    border-bottom-left-radius: $form-radius;
+}
+
+.radio-last:focus {
     border-color: $motion-primary;
     box-shadow: inset 0 0 0 -2px $ui-black-transparent;
 }
diff --git a/src/components/sprite-info/sprite-info.jsx b/src/components/sprite-info/sprite-info.jsx
index 37e18735a8c6b2bddbba1443f356b820f50ca321..f4e3773b96ff002f25092bd74426cc5238527625 100644
--- a/src/components/sprite-info/sprite-info.jsx
+++ b/src/components/sprite-info/sprite-info.jsx
@@ -6,6 +6,8 @@ import Box from '../box/box.jsx';
 import Label from '../forms/label.jsx';
 import Input from '../forms/input.jsx';
 import BufferedInputHOC from '../forms/buffered-input-hoc.jsx';
+import DirectionPicker from '../../containers/direction-picker.jsx';
+
 import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'react-intl';
 
 import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants.js';
@@ -29,6 +31,7 @@ const messages = defineMessages({
 class SpriteInfo extends React.Component {
     shouldComponentUpdate (nextProps) {
         return (
+            this.props.rotationStyle !== nextProps.rotationStyle ||
             this.props.direction !== nextProps.direction ||
             this.props.disabled !== nextProps.disabled ||
             this.props.name !== nextProps.name ||
@@ -65,13 +68,6 @@ class SpriteInfo extends React.Component {
                 id="gui.SpriteInfo.size"
             />
         );
-        const directionLabel = (
-            <FormattedMessage
-                defaultMessage="Direction"
-                description="Sprite info direction label"
-                id="gui.SpriteInfo.direction"
-            />
-        );
 
         const spriteNameInput = (
             <BufferedInput
@@ -180,7 +176,7 @@ class SpriteInfo extends React.Component {
                             <div
                                 className={classNames(
                                     styles.radio,
-                                    styles.radioLeft,
+                                    styles.radioFirst,
                                     styles.iconWrapper,
                                     {
                                         [styles.isActive]: this.props.visible && !this.props.disabled,
@@ -199,7 +195,7 @@ class SpriteInfo extends React.Component {
                             <div
                                 className={classNames(
                                     styles.radio,
-                                    styles.radioRight,
+                                    styles.radioLast,
                                     styles.iconWrapper,
                                     {
                                         [styles.isActive]: !this.props.visible && !this.props.disabled,
@@ -234,20 +230,13 @@ class SpriteInfo extends React.Component {
                         </Label>
                     </div>
                     <div className={classNames(styles.group, styles.largerInput)}>
-                        <Label
-                            secondary
-                            text={directionLabel}
-                        >
-                            <BufferedInput
-                                small
-                                disabled={this.props.disabled}
-                                label={directionLabel}
-                                tabIndex="0"
-                                type="text"
-                                value={this.props.disabled ? '' : this.props.direction}
-                                onSubmit={this.props.onChangeDirection}
-                            />
-                        </Label>
+                        <DirectionPicker
+                            direction={this.props.direction}
+                            disabled={this.props.disabled}
+                            rotationStyle={this.props.rotationStyle}
+                            onChangeDirection={this.props.onChangeDirection}
+                            onChangeRotationStyle={this.props.onChangeRotationStyle}
+                        />
                     </div>
                 </div>
             </Box>
@@ -265,6 +254,7 @@ SpriteInfo.propTypes = {
     name: PropTypes.string,
     onChangeDirection: PropTypes.func,
     onChangeName: PropTypes.func,
+    onChangeRotationStyle: PropTypes.func,
     onChangeSize: PropTypes.func,
     onChangeX: PropTypes.func,
     onChangeY: PropTypes.func,
@@ -272,6 +262,7 @@ SpriteInfo.propTypes = {
     onClickVisible: PropTypes.func,
     onPressNotVisible: PropTypes.func,
     onPressVisible: PropTypes.func,
+    rotationStyle: PropTypes.string,
     size: PropTypes.oneOfType([
         PropTypes.string,
         PropTypes.number
diff --git a/src/components/sprite-selector-item/sprite-selector-item.css b/src/components/sprite-selector-item/sprite-selector-item.css
index 24bf17395a71fec24c218446c88762d8ad3cb1a9..d29d15c82c0777cf8eecec14ae027972715cc506 100644
--- a/src/components/sprite-selector-item/sprite-selector-item.css
+++ b/src/components/sprite-selector-item/sprite-selector-item.css
@@ -80,15 +80,29 @@
 .delete-button {
     position: absolute;
     top: 0.125rem;
-    right: 0.125rem;
     z-index: 1;
 }
 
+[dir="ltr"] .delete-button {
+    right: 0.125rem;
+}
+
+[dir="rtl"] .delete-button {
+    left: 0.125rem;
+}
+
 .number {
     position: absolute;
     top: 0.15rem;
-    left: 0.15rem;
     font-size: 0.625rem;
     font-weight: bold;
     z-index: 2;
 }
+
+[dir="ltr"] .number {
+    left: 0.15rem;
+}
+
+[dir="rtl"] .number {
+    right: 0.15rem;
+}
diff --git a/src/components/sprite-selector/sprite-list.jsx b/src/components/sprite-selector/sprite-list.jsx
index a29912b79385183e8c49e0ac819ba5288ec3e761..df42c6c28e94024d15c58e2d388ea4c3ce608fbf 100644
--- a/src/components/sprite-selector/sprite-list.jsx
+++ b/src/components/sprite-selector/sprite-list.jsx
@@ -53,8 +53,11 @@ const SpriteList = function (props) {
                 // Note the absence of the self-sharing check: a sprite can share assets with itself.
                 // This is a quirk of 2.0, but seems worth leaving possible, it
                 // allows quick (albeit unusual) duplication of assets.
-                isRaised = isRaised || draggingType === DragConstants.COSTUME ||
-                    draggingType === DragConstants.SOUND;
+                isRaised = isRaised || [
+                    DragConstants.COSTUME,
+                    DragConstants.SOUND,
+                    DragConstants.BACKPACK_COSTUME,
+                    DragConstants.BACKPACK_SOUND].includes(draggingType);
 
                 return (
                     <SortableAsset
diff --git a/src/components/sprite-selector/sprite-selector.css b/src/components/sprite-selector/sprite-selector.css
index 70895211757bba3a752569a9969aaf21c783cc8e..da89efab0433d86fe091e452f56c06f3fb496e9e 100644
--- a/src/components/sprite-selector/sprite-selector.css
+++ b/src/components/sprite-selector/sprite-selector.css
@@ -68,8 +68,14 @@
 .add-button {
     position: absolute;
     bottom: 0.75rem;
+}
+
+[dir="ltr"] .add-button {
     right: 1rem;
-    z-index: $z-index-add-button;
+}
+
+[dir="rtl"] .add-button {
+    left: 1rem;
 }
 
 .raised {
diff --git a/src/components/sprite-selector/sprite-selector.jsx b/src/components/sprite-selector/sprite-selector.jsx
index e6948c5b7d85c08720a24625425e560c345811aa..5b55c200998b4076e14da558e2504ce0b2c4323a 100644
--- a/src/components/sprite-selector/sprite-selector.jsx
+++ b/src/components/sprite-selector/sprite-selector.jsx
@@ -2,12 +2,12 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import {defineMessages, injectIntl, intlShape} from 'react-intl';
 
-
 import Box from '../box/box.jsx';
 import SpriteInfo from '../../containers/sprite-info.jsx';
 import SpriteList from './sprite-list.jsx';
 import ActionMenu from '../action-menu/action-menu.jsx';
 import {STAGE_DISPLAY_SIZES} from '../../lib/layout-constants';
+import RtlLocales from '../../lib/rtl-locales';
 
 import styles from './sprite-selector.css';
 
@@ -47,6 +47,7 @@ const SpriteSelectorComponent = function (props) {
         intl,
         onChangeSpriteDirection,
         onChangeSpriteName,
+        onChangeSpriteRotationStyle,
         onChangeSpriteSize,
         onChangeSpriteVisibility,
         onChangeSpriteX,
@@ -84,6 +85,7 @@ const SpriteSelectorComponent = function (props) {
                 direction={selectedSprite.direction}
                 disabled={spriteInfoDisabled}
                 name={selectedSprite.name}
+                rotationStyle={selectedSprite.rotationStyle}
                 size={selectedSprite.size}
                 stageSize={stageSize}
                 visible={selectedSprite.visible}
@@ -91,6 +93,7 @@ const SpriteSelectorComponent = function (props) {
                 y={selectedSprite.y}
                 onChangeDirection={onChangeSpriteDirection}
                 onChangeName={onChangeSpriteName}
+                onChangeRotationStyle={onChangeSpriteRotationStyle}
                 onChangeSize={onChangeSpriteSize}
                 onChangeVisibility={onChangeSpriteVisibility}
                 onChangeX={onChangeSpriteX}
@@ -137,6 +140,7 @@ const SpriteSelectorComponent = function (props) {
                     }
                 ]}
                 title={intl.formatMessage(messages.addSpriteFromLibrary)}
+                tooltipPlace={RtlLocales.indexOf(intl.locale) === -1 ? 'left' : 'right'}
                 onClick={onNewSpriteClick}
             />
         </Box>
@@ -152,6 +156,7 @@ SpriteSelectorComponent.propTypes = {
     intl: intlShape.isRequired,
     onChangeSpriteDirection: PropTypes.func,
     onChangeSpriteName: PropTypes.func,
+    onChangeSpriteRotationStyle: PropTypes.func,
     onChangeSpriteSize: PropTypes.func,
     onChangeSpriteVisibility: PropTypes.func,
     onChangeSpriteX: PropTypes.func,
diff --git a/src/components/stage-header/stage-header.css b/src/components/stage-header/stage-header.css
index 93ef5b4408a1d701fc624a0f41b9ad994844dd58..397c679da34811e8170a422d6f24dc336a619db4 100644
--- a/src/components/stage-header/stage-header.css
+++ b/src/components/stage-header/stage-header.css
@@ -31,9 +31,16 @@
 
 .stage-size-toggle-group {
     display: flex;
+}
+
+[dir="ltr"] .stage-size-toggle-group {
     margin-right: .2rem;
 }
 
+[dir="rtl"] .stage-size-toggle-group {
+    margin-left: .2rem;
+}
+
 .stage-button {
     display: block;
     border: 1px solid $ui-black-transparent;
@@ -51,13 +58,24 @@
     height: 100%;
 }
 
-.stage-button-right {
+[dir="ltr"] .stage-button-first {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+[dir="ltr"] .stage-button-last {
     border-left: none;
     border-top-left-radius: 0;
     border-bottom-left-radius: 0;
 }
 
-.stage-button-left {
+[dir="rtl"] .stage-button-first {
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+}
+
+[dir="rtl"] .stage-button-last {
+    border-right: none;
     border-top-right-radius: 0;
     border-bottom-right-radius: 0;
 }
diff --git a/src/components/stage-header/stage-header.jsx b/src/components/stage-header/stage-header.jsx
index 21a156a95a6add7ff7caa60b21aa23c28b04b6d7..43414179b7b4d4f26454b0ef92d8ed16e6af28be 100644
--- a/src/components/stage-header/stage-header.jsx
+++ b/src/components/stage-header/stage-header.jsx
@@ -96,7 +96,7 @@ const StageHeaderComponent = function (props) {
                         <Button
                             className={classNames(
                                 styles.stageButton,
-                                styles.stageButtonLeft,
+                                styles.stageButtonFirst,
                                 (stageSizeMode === STAGE_SIZE_MODES.small) ? null : styles.stageButtonToggledOff
                             )}
                             onClick={onSetStageSmall}
@@ -113,7 +113,7 @@ const StageHeaderComponent = function (props) {
                         <Button
                             className={classNames(
                                 styles.stageButton,
-                                styles.stageButtonRight,
+                                styles.stageButtonLast,
                                 (stageSizeMode === STAGE_SIZE_MODES.large) ? null : styles.stageButtonToggledOff
                             )}
                             onClick={onSetStageLarge}
diff --git a/src/components/stage-selector/stage-selector.css b/src/components/stage-selector/stage-selector.css
index a168759901196783d90a0714fbb578941e028d9d..2669a6de576a4487686e466cfdae7d0ba87349f9 100644
--- a/src/components/stage-selector/stage-selector.css
+++ b/src/components/stage-selector/stage-selector.css
@@ -1,6 +1,5 @@
 @import "../../css/units.css";
 @import "../../css/colors.css";
-@import "../../css/z-index.css";
 
 $header-height: calc($stage-menu-height - 2px);
 
@@ -96,7 +95,6 @@ $header-height: calc($stage-menu-height - 2px);
 .add-button {
     position: absolute;
     bottom: 0.75rem;
-    z-index: $z-index-add-button
 }
 
 .raised, .raised .header {
diff --git a/src/components/stage-selector/stage-selector.jsx b/src/components/stage-selector/stage-selector.jsx
index f5de8b7a89b1c0b2b4fb784b51f5fb66251eee2a..c40620be07c57b5c465605ff64b6b6ff51e4e374 100644
--- a/src/components/stage-selector/stage-selector.jsx
+++ b/src/components/stage-selector/stage-selector.jsx
@@ -39,6 +39,8 @@ const messages = defineMessages({
 const StageSelector = props => {
     const {
         backdropCount,
+        containerRef,
+        dragOver,
         fileInputRef,
         intl,
         selected,
@@ -59,9 +61,10 @@ const StageSelector = props => {
         <Box
             className={classNames(styles.stageSelector, {
                 [styles.isSelected]: selected,
-                [styles.raised]: raised,
+                [styles.raised]: raised || dragOver,
                 [styles.receivedBlocks]: receivedBlocks
             })}
+            componentRef={containerRef}
             onClick={onClick}
             onMouseEnter={onMouseEnter}
             onMouseLeave={onMouseLeave}
@@ -125,6 +128,8 @@ const StageSelector = props => {
 
 StageSelector.propTypes = {
     backdropCount: PropTypes.number.isRequired,
+    containerRef: PropTypes.func,
+    dragOver: PropTypes.bool,
     fileInputRef: PropTypes.func,
     intl: intlShape.isRequired,
     onBackdropFileUpload: PropTypes.func,
diff --git a/src/components/target-pane/target-pane.jsx b/src/components/target-pane/target-pane.jsx
index c620c8504c13a1dbd2034a9a2d3a72f80e3d8b85..565c9881cf405e6cb3856eb38671e69995a2ac2f 100644
--- a/src/components/target-pane/target-pane.jsx
+++ b/src/components/target-pane/target-pane.jsx
@@ -23,6 +23,7 @@ const TargetPane = ({
     spriteLibraryVisible,
     onChangeSpriteDirection,
     onChangeSpriteName,
+    onChangeSpriteRotationStyle,
     onChangeSpriteSize,
     onChangeSpriteVisibility,
     onChangeSpriteX,
@@ -60,6 +61,7 @@ const TargetPane = ({
             stageSize={stageSize}
             onChangeSpriteDirection={onChangeSpriteDirection}
             onChangeSpriteName={onChangeSpriteName}
+            onChangeSpriteRotationStyle={onChangeSpriteRotationStyle}
             onChangeSpriteSize={onChangeSpriteSize}
             onChangeSpriteVisibility={onChangeSpriteVisibility}
             onChangeSpriteX={onChangeSpriteX}
@@ -128,6 +130,7 @@ TargetPane.propTypes = {
     }),
     onChangeSpriteDirection: PropTypes.func,
     onChangeSpriteName: PropTypes.func,
+    onChangeSpriteRotationStyle: PropTypes.func,
     onChangeSpriteSize: PropTypes.func,
     onChangeSpriteVisibility: PropTypes.func,
     onChangeSpriteX: PropTypes.func,
diff --git a/src/components/webgl-modal/webgl-modal.jsx b/src/components/webgl-modal/webgl-modal.jsx
index 0a7b753aae47d4ed746ac1da18fc8fe2ad3e0ebf..f0c3c6c27529eb3ac8ab782131898698634459b7 100644
--- a/src/components/webgl-modal/webgl-modal.jsx
+++ b/src/components/webgl-modal/webgl-modal.jsx
@@ -74,12 +74,12 @@ const WebGlModal = ({intl, ...props}) => (
                         previewFaqLink: (
                             <a
                                 className={styles.faqLink}
-                                href="//scratch.mit.edu/preview-faq"
+                                href="//scratch.mit.edu/3faq"
                             >
                                 <FormattedMessage
-                                    defaultMessage="preview FAQ"
+                                    defaultMessage="FAQ"
                                     description="link to Scratch 3.0 FAQ page"
-                                    id="gui.webglModal.previewfaqlink"
+                                    id="gui.webglModal.previewfaqlinktext"
                                 />
                             </a>
                         )
diff --git a/src/containers/backpack.jsx b/src/containers/backpack.jsx
index aedf2f35da1c84187386aae3e5132d7fbfad9372..e7303c145fb969ff5e226f68ff51b768f3b2da5b 100644
--- a/src/containers/backpack.jsx
+++ b/src/containers/backpack.jsx
@@ -11,11 +11,15 @@ import {
     spritePayload
 } from '../lib/backpack-api';
 import DragConstants from '../lib/drag-constants';
+import DropAreaHOC from '../lib/drop-area-hoc.jsx';
 
 import {connect} from 'react-redux';
 import storage from '../lib/storage';
 import VM from 'scratch-vm';
 
+const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND, DragConstants.SPRITE];
+const DroppableBackpack = DropAreaHOC(dragTypes)(BackpackComponent);
+
 class Backpack extends React.Component {
     constructor (props) {
         super(props);
@@ -23,8 +27,7 @@ class Backpack extends React.Component {
             'handleDrop',
             'handleToggle',
             'handleDelete',
-            'refreshContents',
-            'setRef'
+            'refreshContents'
         ]);
         this.state = {
             dragOver: false,
@@ -46,29 +49,6 @@ class Backpack extends React.Component {
             storage._hasAddedBackpackSource = true;
         }
     }
-    componentWillReceiveProps (newProps) {
-        const dragTypes = [DragConstants.COSTUME, DragConstants.SOUND, DragConstants.SPRITE];
-        // If `dragging` becomes true, record the drop area rectangle
-        if (newProps.dragInfo.dragging && !this.props.dragInfo.dragging) {
-            this.dropAreaRect = this.ref && this.ref.getBoundingClientRect();
-        // If `dragging` becomes false, call the drop handler
-        } else if (!newProps.dragInfo.dragging && this.props.dragInfo.dragging && this.state.dragOver) {
-            this.handleDrop(this.props.dragInfo);
-            this.setState({dragOver: false});
-        }
-
-        // If a drag is in progress (currentOffset) and it matches the relevant drag types,
-        // test if the drag is within the drop area rect and set the state accordingly.
-        if (this.dropAreaRect && newProps.dragInfo.currentOffset && dragTypes.includes(newProps.dragInfo.dragType)) {
-            const {x, y} = newProps.dragInfo.currentOffset;
-            const {top, right, bottom, left} = this.dropAreaRect;
-            if (x > left && x < right && y > top && y < bottom) {
-                this.setState({dragOver: true});
-            } else {
-                this.setState({dragOver: false});
-            }
-        }
-    }
     handleToggle () {
         const newState = !this.state.expanded;
         this.setState({expanded: newState, offset: 0});
@@ -126,19 +106,15 @@ class Backpack extends React.Component {
                 });
         }
     }
-    setRef (ref) {
-        this.ref = ref;
-    }
     render () {
         return (
-            <BackpackComponent
+            <DroppableBackpack
                 contents={this.state.contents}
-                dragOver={this.state.dragOver}
-                dropAreaRef={this.setRef}
                 error={this.state.error}
                 expanded={this.state.expanded}
                 loading={this.state.loading}
                 onDelete={this.handleDelete}
+                onDrop={this.handleDrop}
                 onToggle={this.props.host ? this.handleToggle : null}
             />
         );
@@ -146,15 +122,6 @@ class Backpack extends React.Component {
 }
 
 Backpack.propTypes = {
-    dragInfo: PropTypes.shape({
-        currentOffset: PropTypes.shape({
-            x: PropTypes.number,
-            y: PropTypes.number
-        }),
-        dragType: PropTypes.string,
-        dragging: PropTypes.bool,
-        index: PropTypes.number
-    }),
     host: PropTypes.string,
     token: PropTypes.string,
     username: PropTypes.string,
@@ -181,7 +148,6 @@ const getTokenAndUsername = state => {
 
 const mapStateToProps = state => Object.assign(
     {
-        dragInfo: state.scratchGui.assetDrag,
         vm: state.scratchGui.vm
     },
     getTokenAndUsername(state)
diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx
index d5dcc5400f3a0d0a0aa8c9562a7f90478c671a84..8a6bec5fd7578beff79912dea9b6dd5cc52595ed 100644
--- a/src/containers/blocks.jsx
+++ b/src/containers/blocks.jsx
@@ -77,7 +77,7 @@ class Blocks extends React.Component {
         const workspaceConfig = defaultsDeep({},
             Blocks.defaultOptions,
             this.props.options,
-            {toolbox: this.props.toolboxXML}
+            {rtl: this.props.isRtl, toolbox: this.props.toolboxXML}
         );
         this.workspace = this.ScratchBlocks.inject(this.blocks, workspaceConfig);
 
@@ -405,6 +405,7 @@ class Blocks extends React.Component {
             options,
             stageSize,
             vm,
+            isRtl,
             isVisible,
             onActivateColorPicker,
             updateToolboxState,
@@ -464,6 +465,7 @@ Blocks.propTypes = {
     anyModalVisible: PropTypes.bool,
     customProceduresVisible: PropTypes.bool,
     extensionLibraryVisible: PropTypes.bool,
+    isRtl: PropTypes.bool,
     isVisible: PropTypes.bool,
     locale: PropTypes.string,
     messages: PropTypes.objectOf(PropTypes.string),
@@ -538,6 +540,7 @@ const mapStateToProps = state => ({
         state.scratchGui.mode.isFullScreen
     ),
     extensionLibraryVisible: state.scratchGui.modals.extensionLibrary,
+    isRtl: state.locales.isRtl,
     locale: state.locales.locale,
     messages: state.locales.messages,
     toolboxXML: state.scratchGui.toolbox.toolboxXML,
diff --git a/src/containers/connection-modal.jsx b/src/containers/connection-modal.jsx
index 90017c047dc3031219139ef655f3d5a5a45667b2..276d4f72a886e6df3b5f150d1cdd443e5eee9956 100644
--- a/src/containers/connection-modal.jsx
+++ b/src/containers/connection-modal.jsx
@@ -3,6 +3,7 @@ import React from 'react';
 import bindAll from 'lodash.bindall';
 import ConnectionModalComponent, {PHASES} from '../components/connection-modal/connection-modal.jsx';
 import VM from 'scratch-vm';
+import analytics from '../lib/analytics';
 
 class ConnectionModal extends React.Component {
     constructor (props) {
@@ -16,18 +17,13 @@ class ConnectionModal extends React.Component {
             'handleHelp'
         ]);
         this.state = {
-            phase: PHASES.scanning
+            phase: props.vm.getPeripheralIsConnected(props.extensionId) ?
+                PHASES.connected : PHASES.scanning
         };
     }
     componentDidMount () {
         this.props.vm.on('PERIPHERAL_CONNECTED', this.handleConnected);
         this.props.vm.on('PERIPHERAL_ERROR', this.handleError);
-
-        // Check if we're already connected
-        if (this.props.vm.getPeripheralIsConnected(this.props.extensionId)) {
-            this.handleConnected();
-        }
-
     }
     componentWillUnmount () {
         this.props.vm.removeListener('PERIPHERAL_CONNECTED', this.handleConnected);
@@ -43,6 +39,11 @@ class ConnectionModal extends React.Component {
         this.setState({
             phase: PHASES.connecting
         });
+        analytics.event({
+            category: 'extensions',
+            action: 'connecting',
+            label: this.props.extensionId
+        });
     }
     handleDisconnect () {
         this.props.onStatusButtonUpdate(this.props.extensionId, 'not ready');
@@ -68,6 +69,11 @@ class ConnectionModal extends React.Component {
             this.setState({
                 phase: PHASES.error
             });
+            analytics.event({
+                category: 'extensions',
+                action: 'connecting error',
+                label: this.props.extensionId
+            });
         }
     }
     handleConnected () {
@@ -75,9 +81,19 @@ class ConnectionModal extends React.Component {
         this.setState({
             phase: PHASES.connected
         });
+        analytics.event({
+            category: 'extensions',
+            action: 'connected',
+            label: this.props.extensionId
+        });
     }
     handleHelp () {
         window.open(this.props.helpLink, '_blank');
+        analytics.event({
+            category: 'extensions',
+            action: 'help',
+            label: this.props.extensionId
+        });
     }
     render () {
         return (
diff --git a/src/containers/controls.jsx b/src/containers/controls.jsx
index 1b4637d9900f3b929eb0f9a810d6dd830b5bbdfc..7cc52870fea7dad1e58b168d406019a4a4549162 100644
--- a/src/containers/controls.jsx
+++ b/src/containers/controls.jsx
@@ -2,6 +2,7 @@ import bindAll from 'lodash.bindall';
 import PropTypes from 'prop-types';
 import React from 'react';
 import VM from 'scratch-vm';
+import {connect} from 'react-redux';
 
 import analytics from '../lib/analytics';
 import ControlsComponent from '../components/controls/controls.jsx';
@@ -11,34 +12,13 @@ class Controls extends React.Component {
         super(props);
         bindAll(this, [
             'handleGreenFlagClick',
-            'handleStopAllClick',
-            'onProjectRunStart',
-            'onProjectRunStop'
+            'handleStopAllClick'
         ]);
-        this.state = {
-            projectRunning: false,
-            turbo: false
-        };
-    }
-    componentDidMount () {
-        this.props.vm.addListener('PROJECT_RUN_START', this.onProjectRunStart);
-        this.props.vm.addListener('PROJECT_RUN_STOP', this.onProjectRunStop);
-    }
-    componentWillUnmount () {
-        this.props.vm.removeListener('PROJECT_RUN_START', this.onProjectRunStart);
-        this.props.vm.removeListener('PROJECT_RUN_STOP', this.onProjectRunStop);
-    }
-    onProjectRunStart () {
-        this.setState({projectRunning: true});
-    }
-    onProjectRunStop () {
-        this.setState({projectRunning: false});
     }
     handleGreenFlagClick (e) {
         e.preventDefault();
         if (e.shiftKey) {
-            this.setState({turbo: !this.state.turbo});
-            this.props.vm.setTurboMode(!this.state.turbo);
+            this.props.vm.setTurboMode(!this.props.turbo);
         } else {
             this.props.vm.greenFlag();
             analytics.event({
@@ -58,13 +38,15 @@ class Controls extends React.Component {
     render () {
         const {
             vm, // eslint-disable-line no-unused-vars
+            projectRunning,
+            turbo,
             ...props
         } = this.props;
         return (
             <ControlsComponent
                 {...props}
-                active={this.state.projectRunning}
-                turbo={this.state.turbo}
+                active={projectRunning}
+                turbo={turbo}
                 onGreenFlagClick={this.handleGreenFlagClick}
                 onStopAllClick={this.handleStopAllClick}
             />
@@ -73,7 +55,14 @@ class Controls extends React.Component {
 }
 
 Controls.propTypes = {
+    projectRunning: PropTypes.bool.isRequired,
+    turbo: PropTypes.bool.isRequired,
     vm: PropTypes.instanceOf(VM)
 };
 
-export default Controls;
+const mapStateToProps = state => ({
+    projectRunning: state.scratchGui.vmStatus.running,
+    turbo: state.scratchGui.vmStatus.turbo
+});
+
+export default connect(mapStateToProps)(Controls);
diff --git a/src/containers/custom-procedures.jsx b/src/containers/custom-procedures.jsx
index 879800aae880313299ab53601e6a1f9f0f667d54..525422720a8324b69d47ccad362943f7f8a6e988 100644
--- a/src/containers/custom-procedures.jsx
+++ b/src/containers/custom-procedures.jsx
@@ -32,7 +32,8 @@ class CustomProcedures extends React.Component {
         this.blocks = blocksRef;
         const workspaceConfig = defaultsDeep({},
             CustomProcedures.defaultOptions,
-            this.props.options
+            this.props.options,
+            {rtl: this.props.isRtl}
         );
 
         // @todo This is a hack to make there be no toolbox.
@@ -117,6 +118,7 @@ class CustomProcedures extends React.Component {
 }
 
 CustomProcedures.propTypes = {
+    isRtl: PropTypes.bool,
     mutator: PropTypes.instanceOf(Element),
     onRequestClose: PropTypes.func.isRequired,
     options: PropTypes.shape({
@@ -147,6 +149,7 @@ CustomProcedures.defaultProps = {
 };
 
 const mapStateToProps = state => ({
+    isRtl: state.locales.isRtl,
     mutator: state.scratchGui.customProcedures.mutator
 });
 
diff --git a/src/containers/direction-picker.jsx b/src/containers/direction-picker.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..a0643d706c8aaaa9600a08aea4ecd2c986ba5659
--- /dev/null
+++ b/src/containers/direction-picker.jsx
@@ -0,0 +1,62 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import DirectionComponent, {RotationStyles} from '../components/direction-picker/direction-picker.jsx';
+
+class DirectionPicker extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'handleOpenPopover',
+            'handleClosePopover',
+            'handleClickLeftRight',
+            'handleClickDontRotate',
+            'handleClickAllAround'
+        ]);
+        this.state = {
+            popoverOpen: false
+        };
+    }
+    handleOpenPopover () {
+        this.setState({popoverOpen: true});
+    }
+    handleClosePopover () {
+        this.setState({popoverOpen: false});
+    }
+    handleClickAllAround () {
+        this.props.onChangeRotationStyle(RotationStyles.ALL_AROUND);
+    }
+    handleClickLeftRight () {
+        this.props.onChangeRotationStyle(RotationStyles.LEFT_RIGHT);
+    }
+    handleClickDontRotate () {
+        this.props.onChangeRotationStyle(RotationStyles.DONT_ROTATE);
+    }
+    render () {
+        return (
+            <DirectionComponent
+                direction={this.props.direction}
+                disabled={this.props.disabled}
+                popoverOpen={this.state.popoverOpen && !this.props.disabled}
+                rotationStyle={this.props.rotationStyle}
+                onChangeDirection={this.props.onChangeDirection}
+                onClickAllAround={this.handleClickAllAround}
+                onClickDontRotate={this.handleClickDontRotate}
+                onClickLeftRight={this.handleClickLeftRight}
+                onClosePopover={this.handleClosePopover}
+                onOpenPopover={this.handleOpenPopover}
+            />
+        );
+    }
+}
+
+DirectionPicker.propTypes = {
+    direction: PropTypes.number,
+    disabled: PropTypes.bool,
+    onChangeDirection: PropTypes.func,
+    onChangeRotationStyle: PropTypes.func,
+    rotationStyle: PropTypes.string
+};
+
+export default DirectionPicker;
diff --git a/src/containers/gui.jsx b/src/containers/gui.jsx
index 8db598c85fd28f2f1c6e66fdbae55468399123c4..ffa099496fc5c3d10587fdb66f3f547e6622f127 100644
--- a/src/containers/gui.jsx
+++ b/src/containers/gui.jsx
@@ -113,6 +113,7 @@ const mapStateToProps = state => ({
     costumesTabVisible: state.scratchGui.editorTab.activeTabIndex === COSTUMES_TAB_INDEX,
     importInfoVisible: state.scratchGui.modals.importInfo,
     isPlayerOnly: state.scratchGui.mode.isPlayerOnly,
+    isRtl: state.locales.isRtl,
     loadingStateVisible: state.scratchGui.modals.loadingProject,
     previewInfoVisible: state.scratchGui.modals.previewInfo,
     targetIsStage: (
diff --git a/src/containers/modal.jsx b/src/containers/modal.jsx
index 67b14c9ff4494b1e5f354f7befb0b6b0faa9946b..30dc4277b56a8a7aabcf2dd204d3be9c14c5b072 100644
--- a/src/containers/modal.jsx
+++ b/src/containers/modal.jsx
@@ -1,6 +1,7 @@
 import bindAll from 'lodash.bindall';
 import PropTypes from 'prop-types';
 import React from 'react';
+import {connect} from 'react-redux';
 
 import ModalComponent from '../components/modal/modal.jsx';
 
@@ -47,8 +48,15 @@ class Modal extends React.Component {
 
 Modal.propTypes = {
     id: PropTypes.string.isRequired,
+    isRtl: PropTypes.bool,
     onRequestClose: PropTypes.func,
     onRequestOpen: PropTypes.func
 };
 
-export default Modal;
+const mapStateToProps = state => ({
+    isRtl: state.locales.isRtl
+});
+
+export default connect(
+    mapStateToProps
+)(Modal);
diff --git a/src/containers/stage-selector.jsx b/src/containers/stage-selector.jsx
index 99c233c7d8e7ea7b8a28f497274fe85da966948d..e5a86fcc067417198d55724646f85f1ebecebfa9 100644
--- a/src/containers/stage-selector.jsx
+++ b/src/containers/stage-selector.jsx
@@ -7,6 +7,8 @@ import {connect} from 'react-redux';
 import {openBackdropLibrary} from '../reducers/modals';
 import {activateTab, COSTUMES_TAB_INDEX} from '../reducers/editor-tab';
 import {setHoveredSprite} from '../reducers/hovered-target';
+import DragConstants from '../lib/drag-constants';
+import DropAreaHOC from '../lib/drop-area-hoc.jsx';
 
 import StageSelectorComponent from '../components/stage-selector/stage-selector.jsx';
 
@@ -14,6 +16,15 @@ import backdropLibraryContent from '../lib/libraries/backdrops.json';
 import costumeLibraryContent from '../lib/libraries/costumes.json';
 import {handleFileUpload, costumeUpload} from '../lib/file-uploader.js';
 
+const dragTypes = [
+    DragConstants.COSTUME,
+    DragConstants.SOUND,
+    DragConstants.BACKPACK_COSTUME,
+    DragConstants.BACKPACK_SOUND
+];
+
+const DroppableStage = DropAreaHOC(dragTypes)(StageSelectorComponent);
+
 class StageSelector extends React.Component {
     constructor (props) {
         super(props);
@@ -27,6 +38,7 @@ class StageSelector extends React.Component {
             'handleBackdropUpload',
             'handleMouseEnter',
             'handleMouseLeave',
+            'handleDrop',
             'setFileInput'
         ]);
     }
@@ -75,6 +87,22 @@ class StageSelector extends React.Component {
     handleMouseLeave () {
         this.props.dispatchSetHoveredSprite(null);
     }
+    handleDrop (dragInfo) {
+        if (dragInfo.dragType === DragConstants.COSTUME) {
+            this.props.vm.shareCostumeToTarget(dragInfo.index, this.props.id);
+        } else if (dragInfo.dragType === DragConstants.SOUND) {
+            this.props.vm.shareSoundToTarget(dragInfo.index, this.props.id);
+        } else if (dragInfo.dragType === DragConstants.BACKPACK_COSTUME) {
+            this.props.vm.addCostume(dragInfo.payload.body, {
+                name: dragInfo.payload.name
+            }, this.props.id);
+        } else if (dragInfo.dragType === DragConstants.BACKPACK_SOUND) {
+            this.props.vm.addSound({
+                md5: dragInfo.payload.body,
+                name: dragInfo.payload.name
+            }, this.props.id);
+        }
+    }
     setFileInput (input) {
         this.fileInput = input;
     }
@@ -82,16 +110,16 @@ class StageSelector extends React.Component {
         const componentProps = omit(this.props, [
             'assetId', 'dispatchSetHoveredSprite', 'id', 'onActivateTab', 'onSelect']);
         return (
-            <StageSelectorComponent
+            <DroppableStage
                 fileInputRef={this.setFileInput}
                 onBackdropFileUpload={this.handleBackdropUpload}
                 onBackdropFileUploadClick={this.handleFileUploadClick}
                 onClick={this.handleClick}
+                onDrop={this.handleDrop}
                 onEmptyBackdropClick={this.handleEmptyBackdrop}
                 onMouseEnter={this.handleMouseEnter}
                 onMouseLeave={this.handleMouseLeave}
                 onSurpriseBackdropClick={this.handleSurpriseBackdrop}
-
                 {...componentProps}
             />
         );
diff --git a/src/containers/target-pane.jsx b/src/containers/target-pane.jsx
index a116b3c0788127d6743e7555ecb1cdb34016d97d..870b9716440211dab3249f4393a25095dbdb58fa 100644
--- a/src/containers/target-pane.jsx
+++ b/src/containers/target-pane.jsx
@@ -20,6 +20,7 @@ class TargetPane extends React.Component {
         super(props);
         bindAll(this, [
             'handleBlockDragEnd',
+            'handleChangeSpriteRotationStyle',
             'handleChangeSpriteDirection',
             'handleChangeSpriteName',
             'handleChangeSpriteSize',
@@ -48,6 +49,9 @@ class TargetPane extends React.Component {
     handleChangeSpriteDirection (direction) {
         this.props.vm.postSpriteInfo({direction});
     }
+    handleChangeSpriteRotationStyle (rotationStyle) {
+        this.props.vm.postSpriteInfo({rotationStyle});
+    }
     handleChangeSpriteName (name) {
         this.props.vm.renameSprite(this.props.editingTarget, name);
     }
@@ -152,6 +156,18 @@ class TargetPane extends React.Component {
                 this.props.vm.shareCostumeToTarget(dragInfo.index, targetId);
             } else if (targetId && dragInfo.dragType === DragConstants.SOUND) {
                 this.props.vm.shareSoundToTarget(dragInfo.index, targetId);
+            } else if (dragInfo.dragType === DragConstants.BACKPACK_COSTUME) {
+                // In scratch 2, this only creates a new sprite from the costume.
+                // We may be able to handle both kinds of drops, depending on where
+                // the drop happens. For now, just add the costume.
+                this.props.vm.addCostume(dragInfo.payload.body, {
+                    name: dragInfo.payload.name
+                }, targetId);
+            } else if (dragInfo.dragType === DragConstants.BACKPACK_SOUND) {
+                this.props.vm.addSound({
+                    md5: dragInfo.payload.body,
+                    name: dragInfo.payload.name
+                }, targetId);
             }
         }
     }
@@ -167,6 +183,7 @@ class TargetPane extends React.Component {
                 fileInputRef={this.setFileInput}
                 onChangeSpriteDirection={this.handleChangeSpriteDirection}
                 onChangeSpriteName={this.handleChangeSpriteName}
+                onChangeSpriteRotationStyle={this.handleChangeSpriteRotationStyle}
                 onChangeSpriteSize={this.handleChangeSpriteSize}
                 onChangeSpriteVisibility={this.handleChangeSpriteVisibility}
                 onChangeSpriteX={this.handleChangeSpriteX}
diff --git a/src/containers/turbo-mode.jsx b/src/containers/turbo-mode.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f7ee5878e956c4e6cbec6e255a0a447562832962
--- /dev/null
+++ b/src/containers/turbo-mode.jsx
@@ -0,0 +1,60 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+import {connect} from 'react-redux';
+
+/**
+ * Turbo Mode component passes toggleTurboMode function to its child.
+ * It also includes `turboMode` in the props passed to the children.
+ * It expects this child to be a function with the signature
+ *     function (toggleTurboMode, {turboMode, ...props}) {}
+ * The component can then be used to attach turbo mode setting functionality
+ * to any other component:
+ *
+ * <TurboMode>{(toggleTurboMode, props) => (
+ *     <MyCoolComponent
+ *         turboEnabled={props.turboMode}
+ *         onClick={toggleTurboMode}
+ *         {...props}
+ *     />
+ * )}</TurboMode>
+ */
+class TurboMode extends React.Component {
+    constructor (props) {
+        super(props);
+        bindAll(this, [
+            'toggleTurboMode'
+        ]);
+    }
+    toggleTurboMode () {
+        this.props.vm.setTurboMode(!this.props.turboMode);
+    }
+    render () {
+        const {
+            /* eslint-disable no-unused-vars */
+            children,
+            vm,
+            /* eslint-enable no-unused-vars */
+            ...props
+        } = this.props;
+        return this.props.children(this.toggleTurboMode, props);
+    }
+}
+
+TurboMode.propTypes = {
+    children: PropTypes.func,
+    turboMode: PropTypes.bool,
+    vm: PropTypes.shape({
+        setTurboMode: PropTypes.func
+    })
+};
+
+const mapStateToProps = state => ({
+    vm: state.scratchGui.vm,
+    turboMode: state.scratchGui.vmStatus.turbo
+});
+
+export default connect(
+    mapStateToProps,
+    () => ({}) // omit dispatch prop
+)(TurboMode);
diff --git a/src/css/z-index.css b/src/css/z-index.css
index b3368fc2bd638ef4763bf495171b41a7f41d7c53..26893f08fa5147930328992359d709777bef0ec7 100644
--- a/src/css/z-index.css
+++ b/src/css/z-index.css
@@ -8,8 +8,8 @@ $z-index-extension-button: 50; /* Force extension button above the ScratchBlocks
 $z-index-menu-bar: 50; /* blocklyToolboxDiv is 40 */
 
 $z-index-monitor: 100;
-$z-index-coming-soon: 110;
 $z-index-add-button: 120;
+$z-index-tooltip: 130; /* tooltips should go over add buttons if they overlap */
 
 $z-index-card: 490;
 $z-index-loader: 500;
diff --git a/src/lib/drop-area-hoc.jsx b/src/lib/drop-area-hoc.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9f44293e88cdaab98dcd80b0a8cdd4fde915b646
--- /dev/null
+++ b/src/lib/drop-area-hoc.jsx
@@ -0,0 +1,88 @@
+import bindAll from 'lodash.bindall';
+import PropTypes from 'prop-types';
+import React from 'react';
+import omit from 'lodash.omit';
+import {connect} from 'react-redux';
+
+const DropAreaHOC = function (dragTypes) {
+    return function (WrappedComponent) {
+        class DropAreaWrapper extends React.Component {
+            constructor (props) {
+                super(props);
+                bindAll(this, [
+                    'setRef'
+                ]);
+
+                this.state = {
+                    dragOver: false
+                };
+
+                this.ref = null;
+                this.containerBox = null;
+            }
+
+            componentWillReceiveProps (newProps) {
+                // If `dragging` becomes true, record the drop area rectangle
+                if (newProps.dragInfo.dragging && !this.props.dragInfo.dragging) {
+                    this.dropAreaRect = this.ref && this.ref.getBoundingClientRect();
+                // If `dragging` becomes false, call the drop handler
+                } else if (!newProps.dragInfo.dragging && this.props.dragInfo.dragging && this.state.dragOver) {
+                    this.props.onDrop(this.props.dragInfo);
+                    this.setState({dragOver: false});
+                }
+
+                // If a drag is in progress (currentOffset) and it matches the relevant drag types,
+                // test if the drag is within the drop area rect and set the state accordingly.
+                if (this.dropAreaRect && newProps.dragInfo.currentOffset &&
+                    dragTypes.includes(newProps.dragInfo.dragType)) {
+                    const {x, y} = newProps.dragInfo.currentOffset;
+                    const {top, right, bottom, left} = this.dropAreaRect;
+                    if (x > left && x < right && y > top && y < bottom) {
+                        this.setState({dragOver: true});
+                    } else {
+                        this.setState({dragOver: false});
+                    }
+                }
+            }
+            setRef (el) {
+                this.ref = el;
+            }
+            render () {
+                const componentProps = omit(this.props, ['onDrop', 'dragInfo']);
+                return (
+                    <WrappedComponent
+                        containerRef={this.setRef}
+                        dragOver={this.state.dragOver}
+                        {...componentProps}
+                    />
+                );
+            }
+        }
+
+        DropAreaWrapper.propTypes = {
+            dragInfo: PropTypes.shape({
+                currentOffset: PropTypes.shape({
+                    x: PropTypes.number,
+                    y: PropTypes.number
+                }),
+                dragType: PropTypes.string,
+                dragging: PropTypes.bool,
+                index: PropTypes.number
+            }),
+            onDrop: PropTypes.func
+        };
+
+        const mapStateToProps = state => ({
+            dragInfo: state.scratchGui.assetDrag
+        });
+
+        const mapDispatchToProps = () => ({});
+
+        return connect(
+            mapStateToProps,
+            mapDispatchToProps
+        )(DropAreaWrapper);
+    };
+};
+
+export default DropAreaHOC;
diff --git a/src/lib/rtl-locales.js b/src/lib/rtl-locales.js
new file mode 100644
index 0000000000000000000000000000000000000000..b48ff75997f906abc0a11530e74e2a4ecc4116a6
--- /dev/null
+++ b/src/lib/rtl-locales.js
@@ -0,0 +1,3 @@
+// TODO: this probably should be coming from scratch-l10n
+// Tracking in https://github.com/LLK/scratch-l10n/issues/32
+export default ['he'];
diff --git a/src/lib/vm-listener-hoc.jsx b/src/lib/vm-listener-hoc.jsx
index 28f578b371b89bb717cf8dffa01c4a752530d464..b2256b82a4ace77d8d2bec3a426bb02852cd3464 100644
--- a/src/lib/vm-listener-hoc.jsx
+++ b/src/lib/vm-listener-hoc.jsx
@@ -8,6 +8,7 @@ import {connect} from 'react-redux';
 import {updateTargets} from '../reducers/targets';
 import {updateBlockDrag} from '../reducers/block-drag';
 import {updateMonitors} from '../reducers/monitors';
+import {setRunningState, setTurboState} from '../reducers/vm-status';
 
 /*
  * Higher Order Component to manage events emitted by the VM
@@ -31,6 +32,10 @@ const vmListenerHOC = function (WrappedComponent) {
             this.props.vm.on('targetsUpdate', this.props.onTargetsUpdate);
             this.props.vm.on('MONITORS_UPDATE', this.props.onMonitorsUpdate);
             this.props.vm.on('BLOCK_DRAG_UPDATE', this.props.onBlockDragUpdate);
+            this.props.vm.on('TURBO_MODE_ON', this.props.onTurboModeOn);
+            this.props.vm.on('TURBO_MODE_OFF', this.props.onTurboModeOff);
+            this.props.vm.on('PROJECT_RUN_START', this.props.onProjectRunStart);
+            this.props.vm.on('PROJECT_RUN_STOP', this.props.onProjectRunStop);
         }
         componentDidMount () {
             if (this.props.attachKeyboardEvents) {
@@ -96,7 +101,11 @@ const vmListenerHOC = function (WrappedComponent) {
         onKeyDown: PropTypes.func,
         onKeyUp: PropTypes.func,
         onMonitorsUpdate: PropTypes.func.isRequired,
+        onProjectRunStart: PropTypes.func.isRequired,
+        onProjectRunStop: PropTypes.func.isRequired,
         onTargetsUpdate: PropTypes.func.isRequired,
+        onTurboModeOff: PropTypes.func.isRequired,
+        onTurboModeOn: PropTypes.func.isRequired,
         username: PropTypes.string,
         vm: PropTypes.instanceOf(VM).isRequired
     };
@@ -117,7 +126,11 @@ const vmListenerHOC = function (WrappedComponent) {
         },
         onBlockDragUpdate: areBlocksOverGui => {
             dispatch(updateBlockDrag(areBlocksOverGui));
-        }
+        },
+        onProjectRunStart: () => dispatch(setRunningState(true)),
+        onProjectRunStop: () => dispatch(setRunningState(false)),
+        onTurboModeOn: () => dispatch(setTurboState(true)),
+        onTurboModeOff: () => dispatch(setTurboState(false))
     });
     return connect(
         mapStateToProps,
diff --git a/src/playground/player.jsx b/src/playground/player.jsx
index a0894fe39dea782401e1b2806f3ddf55b9d267e9..6c2833f1b8108d08022e9d6c85bac9357f1f497f 100644
--- a/src/playground/player.jsx
+++ b/src/playground/player.jsx
@@ -18,7 +18,7 @@ if (process.env.NODE_ENV === 'production' && typeof window === 'object') {
 
 import styles from './player.css';
 
-const Player = ({isPlayerOnly, onSeeInside}) => (
+const Player = ({isPlayerOnly, onSeeInside, projectId}) => (
     <Box
         className={classNames({
             [styles.stageOnly]: isPlayerOnly
@@ -28,13 +28,15 @@ const Player = ({isPlayerOnly, onSeeInside}) => (
         <GUI
             enableCommunity
             isPlayerOnly={isPlayerOnly}
+            projectId={projectId}
         />
     </Box>
 );
 
 Player.propTypes = {
     isPlayerOnly: PropTypes.bool,
-    onSeeInside: PropTypes.func
+    onSeeInside: PropTypes.func,
+    projectId: PropTypes.string
 };
 
 const mapStateToProps = state => ({
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index 7d98ae2f3f74a757bc101910bbabe6fd491fc192..2c5642416a52b1e65a4324fee3ad0019ffb52e8b 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -15,6 +15,7 @@ import stageSizeReducer, {stageSizeInitialState} from './stage-size';
 import targetReducer, {targetsInitialState} from './targets';
 import toolboxReducer, {toolboxInitialState} from './toolbox';
 import vmReducer, {vmInitialState} from './vm';
+import vmStatusReducer, {vmStatusInitialState} from './vm-status';
 import throttle from 'redux-throttle';
 
 const guiMiddleware = compose(applyMiddleware(throttle(300, {leading: true, trailing: true})));
@@ -35,7 +36,8 @@ const guiInitialState = {
     monitorLayout: monitorLayoutInitialState,
     targets: targetsInitialState,
     toolbox: toolboxInitialState,
-    vm: vmInitialState
+    vm: vmInitialState,
+    vmStatus: vmStatusInitialState
 };
 
 const initPlayer = function (currentState) {
@@ -75,7 +77,8 @@ const guiReducer = combineReducers({
     monitorLayout: monitorLayoutReducer,
     targets: targetReducer,
     toolbox: toolboxReducer,
-    vm: vmReducer
+    vm: vmReducer,
+    vmStatus: vmStatusReducer
 });
 
 export {
diff --git a/src/reducers/locales.js b/src/reducers/locales.js
index fda8f9dc36d14dac09f72fad62f704617b87e0cf..14ca3bd37b17971432645eeafc684cc244a63270 100644
--- a/src/reducers/locales.js
+++ b/src/reducers/locales.js
@@ -2,6 +2,7 @@ import {addLocaleData} from 'react-intl';
 
 import {localeData} from 'scratch-l10n';
 import editorMessages from 'scratch-l10n/locales/editor-msgs';
+import RtlLocales from '../lib/rtl-locales';
 
 addLocaleData(localeData);
 
@@ -9,6 +10,7 @@ const UPDATE_LOCALES = 'scratch-gui/locales/UPDATE_LOCALES';
 const SELECT_LOCALE = 'scratch-gui/locales/SELECT_LOCALE';
 
 const initialState = {
+    isRtl: false,
     locale: 'en',
     messagesByLocale: editorMessages,
     messages: editorMessages.en
@@ -19,12 +21,14 @@ const reducer = function (state, action) {
     switch (action.type) {
     case SELECT_LOCALE:
         return Object.assign({}, state, {
+            isRtl: RtlLocales.indexOf(action.locale) !== -1,
             locale: action.locale,
             messagesByLocale: state.messagesByLocale,
             messages: state.messagesByLocale[action.locale]
         });
     case UPDATE_LOCALES:
         return Object.assign({}, state, {
+            isRtl: state.isRtl,
             locale: state.locale,
             messagesByLocale: action.messagesByLocale,
             messages: action.messagesByLocale[state.locale]
@@ -53,6 +57,7 @@ const initLocale = function (currentState, locale) {
             {},
             currentState,
             {
+                isRtl: RtlLocales.indexOf(locale) !== -1,
                 locale: locale,
                 messagesByLocale: currentState.messagesByLocale,
                 messages: currentState.messagesByLocale[locale]
diff --git a/src/reducers/vm-status.js b/src/reducers/vm-status.js
new file mode 100644
index 0000000000000000000000000000000000000000..8e965199232d322d386ce53c3f57fb243be6151c
--- /dev/null
+++ b/src/reducers/vm-status.js
@@ -0,0 +1,44 @@
+const SET_RUNNING_STATE = 'scratch-gui/vm-status/SET_RUNNING_STATE';
+const SET_TURBO_STATE = 'scratch-gui/vm-status/SET_TURBO_STATE';
+
+const initialState = {
+    running: false,
+    turbo: false
+};
+
+const reducer = function (state, action) {
+    if (typeof state === 'undefined') state = initialState;
+    switch (action.type) {
+    case SET_RUNNING_STATE:
+        return Object.assign({}, state, {
+            running: action.running
+        });
+    case SET_TURBO_STATE:
+        return Object.assign({}, state, {
+            turbo: action.turbo
+        });
+    default:
+        return state;
+    }
+};
+
+const setRunningState = function (running) {
+    return {
+        type: SET_RUNNING_STATE,
+        running: running
+    };
+};
+
+const setTurboState = function (turbo) {
+    return {
+        type: SET_TURBO_STATE,
+        turbo: turbo
+    };
+};
+
+export {
+    reducer as default,
+    initialState as vmStatusInitialState,
+    setRunningState,
+    setTurboState
+};
diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js
index ce2d6a533e35a43b07ae7557b64b0d96ebadccf5..2d979e4486221ed08b770c2a572572d4f2e8f498 100644
--- a/test/helpers/selenium-helper.js
+++ b/test/helpers/selenium-helper.js
@@ -44,6 +44,9 @@ class SeleniumHelper {
             args.push('--headless');
         }
         chromeCapabilities.set('chromeOptions', {args});
+        chromeCapabilities.setLoggingPrefs({
+            performance: 'ALL'
+        });
         this.driver = new webdriver.Builder()
             .forBrowser('chrome')
             .withCapabilities(chromeCapabilities)
diff --git a/test/integration/examples.test.js b/test/integration/examples.test.js
index f745dc24d2c0101582e5901d581d7dd901f717d7..ecaeb56f2fa36f9bd83095563395f72a7759426f 100644
--- a/test/integration/examples.test.js
+++ b/test/integration/examples.test.js
@@ -35,6 +35,14 @@ describe('player example', () => {
         await clickXpath('//img[@title="Stop"]');
         const logs = await getLogs();
         await expect(logs).toEqual([]);
+        const projectRequests = await driver.manage().logs()
+            .get('performance')
+            .then(pLogs => pLogs.map(log => JSON.parse(log.message).message)
+                .filter(m => m.method === 'Network.requestWillBeSent')
+                .map(m => m.params.request.url)
+                .filter(url => url === 'https://projects.scratch.mit.edu/internalapi/project/96708228/get/')
+            );
+        await expect(projectRequests).toEqual(['https://projects.scratch.mit.edu/internalapi/project/96708228/get/']);
     });
 });
 
@@ -58,6 +66,14 @@ describe('blocks example', () => {
         await clickXpath('//img[@title="Stop"]');
         const logs = await getLogs();
         await expect(logs).toEqual([]);
+        const projectRequests = await driver.manage().logs()
+            .get('performance')
+            .then(pLogs => pLogs.map(log => JSON.parse(log.message).message)
+                .filter(m => m.method === 'Network.requestWillBeSent')
+                .map(m => m.params.request.url)
+                .filter(url => url === 'https://projects.scratch.mit.edu/internalapi/project/96708228/get/')
+            );
+        await expect(projectRequests).toEqual(['https://projects.scratch.mit.edu/internalapi/project/96708228/get/']);
     });
 
     test('Change categories', async () => {
diff --git a/test/smoke/browser.test.js b/test/smoke/browser.test.js
index aa894a05578bcebb74f335469ed5e6a2d85710f8..941b3eef3bd7e34b0165c2002898d4b170fc4b73 100644
--- a/test/smoke/browser.test.js
+++ b/test/smoke/browser.test.js
@@ -8,7 +8,7 @@ const {
 // Make the default timeout longer, Sauce tests take ~30s
 jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000; // eslint-disable-line
 
-const SUPPORTED_MESSAGE = 'Welcome to the Scratch 3.0 Preview';
+const SUPPORTED_MESSAGE = 'Welcome to the Scratch 3.0 Beta';
 const UNSUPPORTED_MESSAGE = 'Scratch 3.0 does not support Internet Explorer';
 
 // Driver configs can be generated with the Sauce Platform Configurator
diff --git a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap
index 0279a418b36f6ede1d60b9f936eeebf03ecf5aaa..93ebc2cf68c794ad31bbf93e6f61b6ec5ac440b5 100644
--- a/test/unit/components/__snapshots__/sound-editor.test.jsx.snap
+++ b/test/unit/components/__snapshots__/sound-editor.test.jsx.snap
@@ -39,6 +39,7 @@ exports[`Sound Editor Component matches snapshot 1`] = `
           title="Undo"
         >
           <img
+            className={undefined}
             draggable={false}
             src="test-file-stub"
           />
@@ -50,6 +51,7 @@ exports[`Sound Editor Component matches snapshot 1`] = `
           title="Redo"
         >
           <img
+            className={undefined}
             draggable={false}
             src="test-file-stub"
           />
@@ -334,7 +336,7 @@ exports[`Sound Editor Component matches snapshot 1`] = `
     </div>
   </div>
   <div
-    className={undefined}
+    className=""
   >
     <div
       className={undefined}