diff --git a/src/components/gui/gui.jsx b/src/components/gui/gui.jsx
index 354baecdb2758cd26f59c525e042a4a9caba5e85..349e20d5e36b533c2a712f3d55e1b594e512e77e 100644
--- a/src/components/gui/gui.jsx
+++ b/src/components/gui/gui.jsx
@@ -8,9 +8,9 @@ import VM from 'scratch-vm';
 
 import Blocks from '../../containers/blocks.jsx';
 import CostumeTab from '../../containers/costume-tab.jsx';
-import Controls from '../../containers/controls.jsx';
 import TargetPane from '../../containers/target-pane.jsx';
 import SoundTab from '../../containers/sound-tab.jsx';
+import StageHeader from '../../containers/stage-header.jsx';
 import Stage from '../../containers/stage.jsx';
 import {FormattedMessage} from 'react-intl';
 
@@ -112,7 +112,13 @@ const GUIComponent = props => {
 
                     <Box className={styles.stageAndTargetWrapper}>
                         <Box className={styles.stageMenuWrapper}>
-                            <Controls vm={vm} />
+                            <MediaQuery minWidth={layout.fullSizeMinWidth}>{isFullSize => (
+                                <StageHeader
+                                    height={isFullSize ? layout.fullStageHeight : layout.smallerStageHeight}
+                                    vm={vm}
+                                    width={isFullSize ? layout.fullStageWidth : layout.smallerStageWidth}
+                                />
+                            )}</MediaQuery>
                         </Box>
                         <Box className={styles.stageWrapper}>
                             <MediaQuery minWidth={layout.fullSizeMinWidth}>{isFullSize => (
diff --git a/src/components/stage-header/icon--unzoom.svg b/src/components/stage-header/icon--unzoom.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c929966dc735c3c1aeaadc4c10fdfa82306596f6
Binary files /dev/null and b/src/components/stage-header/icon--unzoom.svg differ
diff --git a/src/components/stage-header/icon--zoom.svg b/src/components/stage-header/icon--zoom.svg
new file mode 100644
index 0000000000000000000000000000000000000000..62e008983d41b35d568610d17287c2854e2b7ca7
Binary files /dev/null and b/src/components/stage-header/icon--zoom.svg differ
diff --git a/src/components/stage-header/stage-header.css b/src/components/stage-header/stage-header.css
new file mode 100644
index 0000000000000000000000000000000000000000..c491b82e984982be1d118a92ac8399f1628763ac
--- /dev/null
+++ b/src/components/stage-header/stage-header.css
@@ -0,0 +1,43 @@
+@import "../../css/units.css";
+@import "../../css/colors.css";
+
+.stage-header-wrapper {
+    position: relative;
+}
+
+.stage-header-wrapper-overlay {
+    position: fixed;
+    background-color: rgb(232, 237, 241);
+    top: 0;
+    left: 0;
+    right: 0;
+    z-index: 5000;
+}
+
+.stage-menu-wrapper {
+    display: flex;
+    justify-content: space-between;
+    flex-shrink: 0;
+    align-items: center;
+    height: $stage-menu-height;
+    padding: $space;
+}
+
+.stage-zoom-icon {
+    text-align: right;
+    box-sizing: content-box;
+    width: 1.25rem;
+    height: 1.25rem;
+    padding: 0.375rem;
+    border-radius: 0.25rem;
+    user-select: none;
+    cursor: pointer;
+    transition: 0.2s ease-out;
+}
+
+.stage-zoom-icon:hover {
+    /* Scale icon image by 1.2, but keep background static */
+    width: 1.5rem;
+    height: 1.5rem;
+    padding: 0.25rem;
+}
diff --git a/src/components/stage-header/stage-header.jsx b/src/components/stage-header/stage-header.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..635d161ba1ff092f49efb091446f90cdceaeec47
--- /dev/null
+++ b/src/components/stage-header/stage-header.jsx
@@ -0,0 +1,81 @@
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React from 'react';
+import VM from 'scratch-vm';
+import Box from '../box/box.jsx';
+import Controls from '../../containers/controls.jsx';
+
+import zoomIcon from './icon--zoom.svg';
+import unzoomIcon from './icon--unzoom.svg';
+import styles from './stage-header.css';
+
+const StageHeaderComponent = function (props) {
+    const {
+        className,
+        height,
+        isZoomed,
+        onUnzoom,
+        onZoom,
+        titleZoomIcon,
+        vm,
+        width,
+        ...componentProps
+    } = props;
+    return isZoomed === false ? (
+        <Box className={styles.stageHeaderWrapper}>
+            <Box
+                className={styles.stageMenuWrapper}
+                height={height}
+                width={width}
+            >
+                <Controls vm={vm} />
+                <img
+                    className={classNames(
+                        className,
+                        styles.stageZoomIcon
+                    )}
+                    src={zoomIcon}
+                    title={titleZoomIcon}
+                    onClick={onZoom}
+                    {...componentProps}
+                />
+            </Box>
+        </Box>
+    ) : (
+        <Box className={styles.stageHeaderWrapperOverlay}>
+            <Box
+                className={styles.stageMenuWrapper}
+                height={'100%'}
+                width={'100%'}
+            >
+                <Controls vm={vm} />
+                <img
+                    className={classNames(
+                        className,
+                        styles.stageZoomIcon
+                    )}
+                    src={unzoomIcon}
+                    title={titleZoomIcon}
+                    onClick={onUnzoom}
+                    {...componentProps}
+                />
+            </Box>
+        </Box>
+    );
+};
+StageHeaderComponent.propTypes = {
+    className: PropTypes.string,
+    height: PropTypes.number,
+    isZoomed: PropTypes.bool.isRequired,
+    onUnzoom: PropTypes.func.isRequired,
+    onZoom: PropTypes.func.isRequired,
+    titleZoomIcon: PropTypes.string,
+    vm: PropTypes.instanceOf(VM).isRequired,
+    width: PropTypes.number
+};
+StageHeaderComponent.defaultProps = {
+    width: 480,
+    height: 360,
+    titleZoomIcon: 'Zoom Control'
+};
+export default StageHeaderComponent;
diff --git a/src/components/stage/stage.css b/src/components/stage/stage.css
index 9c784e9560d0ba06f371ea06941c29b627963d53..8188751d1d6112ddf66b5f204ffc75e12a23723f 100644
--- a/src/components/stage/stage.css
+++ b/src/components/stage/stage.css
@@ -36,7 +36,40 @@
     overflow: hidden;
 }
 
-.monitor-wrapper, .color-picker-wrapper {
+.stage-wrapper-overlay {
+    position: fixed;
+    top: $stage-menu-height;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 5000;
+    background-color: rgba(255, 255, 255, 1);
+}
+
+.stage-overlay-content {
+    outline: none;
+    margin: auto;
+    border: 3px solid rgb(126, 133, 151);
+    padding: 0;
+    margin-top: 3px;
+    margin-bottom: 3px;
+    border-radius: $space;
+
+    overflow: hidden;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.stage-overlay-content-border-override {
+    border: none;
+}
+
+.question-wrapper {
+    position: absolute;
+}
+
+.monitor-wrapper, .color-picker-wrapper, .queston-wrapper {
     position: absolute;
     top: 0;
     left: 0;
diff --git a/src/components/stage/stage.jsx b/src/components/stage/stage.jsx
index ae5ff92a6d2d014429dc5f7fdca12102fd20abf1..47981e04ab0f44cbd416212420be64e64aeac819 100644
--- a/src/components/stage/stage.jsx
+++ b/src/components/stage/stage.jsx
@@ -11,28 +11,47 @@ import styles from './stage.css';
 const StageComponent = props => {
     const {
         canvasRef,
-        width,
         height,
+        isColorPicking,
+        isZoomed,
+        width,
         colorInfo,
         onDeactivateColorPicker,
-        isColorPicking,
         question,
         onQuestionAnswered,
         ...boxProps
     } = props;
+
+    let heightCorrectedAspect = height;
+    let widthCorrectedAspect = width;
+    const spacingBorderAdjustment = 9;
+    const stageMenuHeightAdjustment = 40;
+    if (isZoomed) {
+        heightCorrectedAspect = window.innerHeight - stageMenuHeightAdjustment - spacingBorderAdjustment;
+        widthCorrectedAspect = heightCorrectedAspect + (heightCorrectedAspect / 3);
+        if (widthCorrectedAspect > window.innerWidth) {
+            widthCorrectedAspect = window.innerWidth;
+            heightCorrectedAspect = widthCorrectedAspect * .75;
+        }
+    }
     return (
         <div>
             <Box
-                className={classNames(styles.stageWrapper, {
-                    [styles.withColorPicker]: isColorPicking
+                className={classNames({
+                    [styles.stageWrapper]: !isZoomed,
+                    [styles.stageWrapperOverlay]: isZoomed,
+                    [styles.withColorPicker]: !isZoomed && isColorPicking
                 })}
             >
                 <Box
-                    className={styles.stage}
+                    className={classNames(
+                        styles.stage,
+                        {[styles.stageOverlayContent]: isZoomed}
+                    )}
                     componentRef={canvasRef}
                     element="canvas"
-                    height={height}
-                    width={width}
+                    height={heightCorrectedAspect}
+                    width={widthCorrectedAspect}
                     {...boxProps}
                 />
                 <Box className={styles.monitorWrapper}>
@@ -44,10 +63,22 @@ const StageComponent = props => {
                     </Box>
                 ) : null}
                 {question === null ? null : (
-                    <Question
-                        question={question}
-                        onQuestionAnswered={onQuestionAnswered}
-                    />
+                    <div
+                        className={classNames(
+                            styles.stageOverlayContent,
+                            styles.stageOverlayContentBorderOverride
+                        )}
+                    >
+                        <div
+                            className={styles.questionWrapper}
+                            style={{width: widthCorrectedAspect}}
+                        >
+                            <Question
+                                question={question}
+                                onQuestionAnswered={onQuestionAnswered}
+                            />
+                        </div>
+                    </div>
                 )}
             </Box>
             {isColorPicking ? (
@@ -64,6 +95,7 @@ StageComponent.propTypes = {
     colorInfo: Loupe.propTypes.colorInfo,
     height: PropTypes.number,
     isColorPicking: PropTypes.bool,
+    isZoomed: PropTypes.bool.isRequired,
     onDeactivateColorPicker: PropTypes.func,
     onQuestionAnswered: PropTypes.func,
     question: PropTypes.string,
diff --git a/src/containers/stage-header.jsx b/src/containers/stage-header.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..d3acba94ee1501098034a2eda728f49866e9d00b
--- /dev/null
+++ b/src/containers/stage-header.jsx
@@ -0,0 +1,43 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import VM from 'scratch-vm';
+import {setZoomed} from '../reducers/zoom';
+
+import {connect} from 'react-redux';
+
+import StageHeaderComponent from '../components/stage-header/stage-header.jsx';
+
+// eslint-disable-next-line react/prefer-stateless-function
+class StageHeader extends React.Component {
+    render () {
+        const {
+            ...props
+        } = this.props;
+        return (
+            <StageHeaderComponent
+                {...props}
+            />
+        );
+    }
+}
+
+StageHeader.propTypes = {
+    height: PropTypes.number,
+    isZoomed: PropTypes.bool,
+    vm: PropTypes.instanceOf(VM).isRequired,
+    width: PropTypes.number
+};
+
+const mapStateToProps = state => ({
+    isZoomed: state.isZoomed
+});
+
+const mapDispatchToProps = dispatch => ({
+    onZoom: () => dispatch(setZoomed(true)),
+    onUnzoom: () => dispatch(setZoomed(false))
+});
+
+export default connect(
+    mapStateToProps,
+    mapDispatchToProps
+)(StageHeader);
diff --git a/src/containers/stage.jsx b/src/containers/stage.jsx
index fcff811bffef4092b0c912a240dd95466f11272b..72ed404d41db0ff594f8acec8187e583f1895348 100644
--- a/src/containers/stage.jsx
+++ b/src/containers/stage.jsx
@@ -57,6 +57,7 @@ class Stage extends React.Component {
             this.props.height !== nextProps.height ||
             this.props.isColorPicking !== nextProps.isColorPicking ||
             this.state.colorInfo !== nextState.colorInfo ||
+            this.props.isZoomed !== nextProps.isZoomed ||
             this.state.question !== nextState.question;
     }
     componentDidUpdate (prevProps) {
@@ -65,6 +66,8 @@ class Stage extends React.Component {
         } else if (!this.props.isColorPicking && prevProps.isColorPicking) {
             this.stopColorPickingLoop();
         }
+        this.updateRect();
+        this.renderer.resize(this.rect.width, this.rect.height);
     }
     componentWillUnmount () {
         this.detachMouseEvents(this.canvas);
@@ -276,6 +279,7 @@ class Stage extends React.Component {
 Stage.propTypes = {
     height: PropTypes.number,
     isColorPicking: PropTypes.bool,
+    isZoomed: PropTypes.bool,
     onActivateColorPicker: PropTypes.func,
     onDeactivateColorPicker: PropTypes.func,
     vm: PropTypes.instanceOf(VM).isRequired,
@@ -283,7 +287,8 @@ Stage.propTypes = {
 };
 
 const mapStateToProps = state => ({
-    isColorPicking: state.colorPicker.active
+    isColorPicking: state.colorPicker.active,
+    isZoomed: state.isZoomed
 });
 
 const mapDispatchToProps = dispatch => ({
diff --git a/src/reducers/gui.js b/src/reducers/gui.js
index 97e24dfe8d8ef1a02ebe8791e1889193a76bef0c..83ede0203093375c2515d7b613b93d8056240a26 100644
--- a/src/reducers/gui.js
+++ b/src/reducers/gui.js
@@ -8,12 +8,14 @@ import monitorLayoutReducer from './monitor-layout';
 import targetReducer from './targets';
 import toolboxReducer from './toolbox';
 import vmReducer from './vm';
+import zoomReducer from './zoom';
 import {ScratchPaintReducer} from 'scratch-paint';
 
 export default combineReducers({
     colorPicker: colorPickerReducer,
     customProcedures: customProceduresReducer,
     intl: intlReducer,
+    isZoomed: zoomReducer,
     modals: modalReducer,
     monitors: monitorReducer,
     monitorLayout: monitorLayoutReducer,
diff --git a/src/reducers/zoom.js b/src/reducers/zoom.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cd0f5f7fa091fb4ead412bf024f0aa2fcf9c445
--- /dev/null
+++ b/src/reducers/zoom.js
@@ -0,0 +1,23 @@
+const SET_ZOOMED = 'scratch-gui/Zoomed/SET_ZOOMED';
+const defaultZoomed = false;
+const initialState = defaultZoomed;
+
+const reducer = function (state, action) {
+    if (typeof state === 'undefined') state = initialState;
+    switch (action.type) {
+    case SET_ZOOMED:
+        return action.isZoomed;
+    default:
+        return state;
+    }
+};
+const setZoomed = function (isZoomed) {
+    return {
+        type: SET_ZOOMED,
+        isZoomed: isZoomed
+    };
+};
+export {
+    reducer as default,
+    setZoomed
+};
diff --git a/test/integration/test.js b/test/integration/test.js
index bb0e0918408799017ab9904a98e1a6fac1f3fe67..03151ae3872f77f1827bbf3b134dacf76a5bb968 100644
--- a/test/integration/test.js
+++ b/test/integration/test.js
@@ -117,6 +117,30 @@ describe('costumes, sounds and variables', () => {
         await expect(logs).toEqual([]);
     });
 
+    test('Load a project by ID (fullscreen)', async () => {
+        const prevSize = driver.manage()
+            .window()
+            .getSize();
+        await new Promise(resolve => setTimeout(resolve, 2000));
+        driver.manage()
+            .window()
+            .setSize(1920, 1080);
+        const projectId = '96708228';
+        await loadUri(`${uri}#${projectId}`);
+        await new Promise(resolve => setTimeout(resolve, 2000));
+        await clickXpath('//img[@title="Zoom Control"]');
+        await clickXpath('//img[@title="Go"]');
+        await new Promise(resolve => setTimeout(resolve, 2000));
+        await clickXpath('//img[@title="Stop"]');
+        prevSize.then(value => {
+            driver.manage()
+                .window()
+                .setSize(value.width, value.height);
+        });
+        const logs = await getLogs(errorWhitelist);
+        await expect(logs).toEqual([]);
+    });
+
     test('Creating variables', async () => {
         await loadUri(uri);
         await clickText('Blocks');