diff --git a/src/components/media-views.js b/src/components/media-views.js
index ae72461f1d69bb73af6c29b3a777f2a1d6487681..de356c7491dc644cd46b9a5f14a5ca76d78abf86 100644
--- a/src/components/media-views.js
+++ b/src/components/media-views.js
@@ -231,6 +231,7 @@ AFRAME.registerComponent("media-video", {
 
   init() {
     this.onPauseStateChange = this.onPauseStateChange.bind(this);
+    this.tryUpdateVideoPlaybackState = this.tryUpdateVideoPlaybackState.bind(this);
 
     this._grabStart = this._grabStart.bind(this);
     this._grabEnd = this._grabEnd.bind(this);
@@ -290,7 +291,7 @@ AFRAME.registerComponent("media-video", {
 
   togglePlayingIfOwner() {
     if (this.networkedEl && NAF.utils.isMine(this.networkedEl) && this.video) {
-      this.data.videoPaused ? this.video.play() : this.video.pause();
+      this.tryUpdateVideoPlaybackState(!this.data.videoPaused);
     }
   },
 
@@ -306,6 +307,10 @@ AFRAME.registerComponent("media-video", {
 
   onPauseStateChange() {
     this.el.setAttribute("media-video", "videoPaused", this.video.paused);
+
+    if (this.networkedEl && NAF.utils.isMine(this.networkedEl)) {
+      this.el.emit("owned-video-state-changed");
+    }
   },
 
   async updateTexture(src) {
@@ -353,9 +358,39 @@ AFRAME.registerComponent("media-video", {
   updatePlaybackState(force) {
     if (force || (this.networkedEl && !NAF.utils.isMine(this.networkedEl) && this.video)) {
       if (Math.abs(this.data.time - this.video.currentTime) > this.data.syncTolerance) {
-        this.video.currentTime = this.data.time;
+        this.tryUpdateVideoPlaybackState(this.data.videoPaused, this.data.time);
+      } else {
+        this.tryUpdateVideoPlaybackState(this.data.videoPaused);
+      }
+    }
+  },
+
+  tryUpdateVideoPlaybackState(pause, currentTime) {
+    if (this._playbackStateChangeTimeout) {
+      clearTimeout(this._playbackStateChangeTimeout);
+    }
+
+    if (pause) {
+      this.video.pause();
+
+      if (currentTime) {
+        this.video.currentTime = currentTime;
       }
-      this.data.videoPaused ? this.video.pause() : this.video.play();
+    } else {
+      // Need to deal with the fact play() may fail if user has not interacted with browser yet.
+      this.video
+        .play()
+        .then(() => {
+          if (currentTime) {
+            this.video.currentTime = currentTime;
+          }
+        })
+        .catch(() => {
+          this._playbackStateChangeTimeout = setTimeout(
+            () => this.tryUpdateVideoPlaybackState(pause, currentTime),
+            1000
+          );
+        });
     }
   },
 
diff --git a/src/components/pinnable.js b/src/components/pinnable.js
index 1ab9573bcd06aca7316b974b3c789eacbc063780..e719c033cde63a9f5d8c5ad025dafeeee2e845ae 100644
--- a/src/components/pinnable.js
+++ b/src/components/pinnable.js
@@ -19,6 +19,9 @@ AFRAME.registerComponent("pinnable", {
     // Fire pinned events when page changes so we can persist the page.
     this.el.addEventListener("pager-page-changed", this._fireEvents);
 
+    // Fire pinned events when video state changes so we can persist the page.
+    this.el.addEventListener("owned-video-state-changed", this._fireEvents);
+
     // Hack: need to wait for the initial grabbable and stretchable components
     // to show up from the template before applying.
     this.el.addEventListener("componentinitialized", this._allowApplyOnceComponentsReady);
diff --git a/src/components/position-at-box-shape-border.js b/src/components/position-at-box-shape-border.js
index 561577b5eb1d302f0dfa44d508352e76cad11829..962433b6637c5fed3cf0373a31d1c893f62f87be 100644
--- a/src/components/position-at-box-shape-border.js
+++ b/src/components/position-at-box-shape-border.js
@@ -54,14 +54,27 @@ AFRAME.registerComponent("position-at-box-shape-border", {
     const camWorldPos = new THREE.Vector3();
     const targetPosition = new THREE.Vector3();
     const pointOnBoxFace = new THREE.Vector3();
+    const tempParentWorldScale = new THREE.Vector3();
+
     return function() {
       if (!this.target) {
-        this.target = this.el.querySelector(this.data.target).object3D;
+        this.targetEl = this.el.querySelector(this.data.target);
+        this.target = this.targetEl.object3D;
+
         if (!this.target) return;
+
+        if (this.targetEl.getAttribute("visible") === false) {
+          this.target.scale.setScalar(0.01); // To avoid "pop" of gigantic button first time
+          return;
+        }
       }
+
+      if (this.targetEl.getAttribute("visible") === false) return;
+
       if (!this.el.getObject3D("mesh")) {
         return;
       }
+
       if (!this.halfExtents || this.mesh !== this.el.getObject3D("mesh") || this.shape !== this.el.components.shape) {
         this.mesh = this.el.getObject3D("mesh");
         if (this.el.components.shape) {
@@ -102,7 +115,14 @@ AFRAME.registerComponent("position-at-box-shape-border", {
 
       this.target.position.copy(targetPosition.copy(targetDir).multiplyScalar(targetHalfExtent));
       this.target.rotation.set(0, targetRotation, 0);
-      this.target.scale.setScalar(this.halfExtents[inverseHalfExtents[targetHalfExtentStr]] * 4);
+
+      tempParentWorldScale.setFromMatrixScale(this.target.parent.matrixWorld);
+
+      const distance = Math.sqrt(minSquareDistance);
+      const scale = this.halfExtents[inverseHalfExtents[targetHalfExtentStr]] * distance;
+      const targetScale = Math.min(2.0, Math.max(0.5, scale * tempParentWorldScale.x));
+
+      this.target.scale.setScalar(targetScale / tempParentWorldScale.x);
     };
   })()
 });
diff --git a/src/components/super-networked-interactable.js b/src/components/super-networked-interactable.js
index 3943725e4f85801edf94402dcf13064bc2925d66..8f2d9bee8588c541dc5845a5f047b6e6a67f7cac 100644
--- a/src/components/super-networked-interactable.js
+++ b/src/components/super-networked-interactable.js
@@ -35,7 +35,7 @@ AFRAME.registerComponent("super-networked-interactable", {
 
     NAF.utils.getNetworkedEntity(this.el).then(networkedEl => {
       this.networkedEl = networkedEl;
-      this._syncCounterRegistration(networkedEl);
+      this._syncCounterRegistration();
       if (!NAF.utils.isMine(networkedEl)) {
         this.el.setAttribute("body", { type: "static" });
       }
@@ -69,7 +69,7 @@ AFRAME.registerComponent("super-networked-interactable", {
     if (this.networkedEl && !NAF.utils.isMine(this.networkedEl)) {
       if (NAF.utils.takeOwnership(this.networkedEl)) {
         this.el.setAttribute("body", { type: "dynamic" });
-        this._syncCounterRegistration(this.networkedEl);
+        this._syncCounterRegistration();
       } else {
         this.el.emit("grab-end", { hand: this.hand });
         this.hand = null;
@@ -99,7 +99,7 @@ AFRAME.registerComponent("super-networked-interactable", {
 
   _syncCounterRegistration: function() {
     const el = this.networkedEl;
-    if (!el) return;
+    if (!el || !el.components["networked"]) return;
 
     const isPinned = el.components["pinnable"] && el.components["pinnable"].data.pinned;
 
diff --git a/src/components/visible-while-frozen.js b/src/components/visible-while-frozen.js
index 4acff3d420136fee0393dc84ae32e14c6779553b..c9c50eab3ba640486d42c65873cecf656912adc4 100644
--- a/src/components/visible-while-frozen.js
+++ b/src/components/visible-while-frozen.js
@@ -4,12 +4,51 @@
  * @component visible-while-frozen
  */
 AFRAME.registerComponent("visible-while-frozen", {
+  schema: {
+    withinDistance: { type: "number" }
+  },
+
   init() {
+    this.updateVisibility = this.updateVisibility.bind(this);
+    this.camWorldPos = new THREE.Vector3();
+    this.objWorldPos = new THREE.Vector3();
+    this.cam = this.el.sceneEl.camera.el.object3D;
     this.onStateChange = evt => {
       if (!evt.detail === "frozen") return;
-      this.el.setAttribute("visible", this.el.sceneEl.is("frozen"));
+      this.updateVisibility();
     };
-    this.el.setAttribute("visible", this.el.sceneEl.is("frozen"));
+    this.updateVisibility();
+  },
+
+  tick() {
+    if (!this.data.withinDistance) return;
+
+    const isFrozen = this.el.sceneEl.is("frozen");
+    const isVisible = this.el.getAttribute("visible");
+    if (!isFrozen && !isVisible) return;
+
+    this.updateVisibility();
+  },
+
+  updateVisibility() {
+    const isFrozen = this.el.sceneEl.is("frozen");
+
+    let isWithinDistance = true;
+
+    if (this.data.withinDistance !== undefined) {
+      this.cam.getWorldPosition(this.camWorldPos);
+      this.objWorldPos.copy(this.el.object3D.position);
+      this.el.object3D.localToWorld(this.objWorldPos);
+
+      isWithinDistance =
+        this.camWorldPos.distanceToSquared(this.objWorldPos) < this.data.withinDistance * this.data.withinDistance;
+    }
+
+    const shouldBeVisible = isFrozen && isWithinDistance;
+
+    if (this.el.getAttribute("visible") !== shouldBeVisible) {
+      this.el.setAttribute("visible", shouldBeVisible);
+    }
   },
 
   play() {
diff --git a/src/gltf-component-mappings.js b/src/gltf-component-mappings.js
index 1bca96496e0eb7ee726e66c49af6b4406d4a61e4..15c2f584750e1bbf5a4ea81c6c37699369925876 100644
--- a/src/gltf-component-mappings.js
+++ b/src/gltf-component-mappings.js
@@ -89,4 +89,12 @@ AFRAME.GLTFModelPlus.registerComponent("media", "media", (el, componentName, com
   if (componentData.pageIndex) {
     el.setAttribute("media-pager", { index: componentData.pageIndex });
   }
+
+  if (componentData.paused !== undefined) {
+    el.setAttribute("media-video", { videoPaused: componentData.paused });
+  }
+
+  if (componentData.time) {
+    el.setAttribute("media-video", { time: componentData.time });
+  }
 });
diff --git a/src/hub.html b/src/hub.html
index 28f1bd1ebe8fa6a45953369036e30d411ea92734..9344cbe2c88cac105507a960fd62cb3c3bd7a1ec 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -104,8 +104,8 @@
                         <template data-name="Chest">
                             <a-entity personal-space-invader="radius: 0.2; useMaterial: true;" bone-visibility>
                                 <a-entity billboard>
-                                    <a-entity mixin="rounded-text-button" block-button visible-while-frozen ui-class-while-frozen position="0 0 .35"> </a-entity>
-                                    <a-entity visible-while-frozen text="value:Block; width:2.5; align:center;" position="0 0 0.36"></a-entity>
+                                    <a-entity mixin="rounded-text-button" block-button visible-while-frozen="withinDistance: 3;" ui-class-while-frozen position="0 0 .35"> </a-entity>
+                                    <a-entity visible-while-frozen="withinDistance: 3;" text="value:Block; width:2.5; align:center;" position="0 0 0.36"></a-entity>
                                 </a-entity>
                             </a-entity>
                         </template>
@@ -149,13 +149,13 @@
                     hoverable-visuals="cursorController: #cursor-controller"
                     auto-scale-cannon-physics-body
                     sticky-object="autoLockOnRelease: true; autoLockOnLoad: true;"
-                    position-at-box-shape-border="target:.freeze-menu"
+                    position-at-box-shape-border="target:.freeze-menu;"
                     destroy-at-extreme-distances
                     set-yxz-order
                     pinnable
                 >
                     <a-entity class="interactable-ui" stop-event-propagation__grab-start="event: grab-start" stop-event-propagation__grab-end="event: grab-end">
-                        <a-entity class="freeze-menu" visible-while-frozen>
+                        <a-entity class="freeze-menu" visible-while-frozen="withinDistance: 3;">
                             <a-entity mixin="rounded-text-action-button" pin-networked-object-button="labelSelector:.pin-button-label; hideWhenPinnedSelector:.hide-when-pinned; uiSelector:.interactable-ui" position="0 0.125 0.01"> </a-entity>
                             <a-entity class="pin-button-label" text=" value:pin; width:1.75; align:center;" text-raycast-hack position="0 0.125 0.02"></a-entity>
                             <a-entity mixin="rounded-text-button" class="hide-when-pinned" remove-networked-object-button position="0 -0.125 0.01"> </a-entity>
@@ -185,7 +185,7 @@
                         segments-width="16"
                         segments-height="12"
                     ></a-sphere>
-                    <a-entity class="delete-button" visible-while-frozen>
+                    <a-entity class="delete-button" visible-while-frozen="withinDistance: 3;">
                         <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity>
                         <a-entity text=" value:remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
                     </a-entity>
@@ -207,7 +207,7 @@
                     set-yxz-order
                     auto-scale-cannon-physics-body
                 >
-                    <a-entity class="delete-button" visible-while-frozen>
+                    <a-entity class="delete-button" visible-while-frozen="withinDistance: 3;">
                         <a-entity mixin="rounded-text-button" remove-networked-object-button position="0 0 0"> </a-entity>
                         <a-entity text=" value:remove; width:2.5; align:center;" text-raycast-hack position="0 0 0.01"></a-entity>
                     </a-entity>
diff --git a/src/react-components/scene-ui.js b/src/react-components/scene-ui.js
index 168c6d517c68ee4f3520c2ea7fb770c45417eb1d..0a49bd07bd7b3504957dea05ff318e5a6052863f 100644
--- a/src/react-components/scene-ui.js
+++ b/src/react-components/scene-ui.js
@@ -81,11 +81,12 @@ class SceneUI extends Component {
 
     const toAttributionSpan = a => {
       if (a.url) {
-        const source = a.url.indexOf("sketchfab.com")
-          ? "on Sketchfab"
-          : a.url.indexOf("poly.google.com")
-            ? "on Google Poly"
-            : "";
+        const source =
+          a.url.indexOf("sketchfab.com") >= 0
+            ? "on Sketchfab"
+            : a.url.indexOf("poly.google.com") >= 0
+              ? "on Google Poly"
+              : "";
 
         return (
           <span key={a.url}>
@@ -107,7 +108,7 @@ class SceneUI extends Component {
       if (!this.props.sceneAttributions.extras) {
         attributions = (
           <span>
-            <span>by {this.props.sceneAttributions.creator}</span>&nbsp;
+            <span>{this.props.sceneAttributions.creator ? `by ${this.props.sceneAttributions.creator}` : ""}</span>&nbsp;
             <br />
             {this.props.sceneAttributions.content && this.props.sceneAttributions.content.map(toAttributionSpan)}
           </span>
diff --git a/src/utils/pinned-entity-to-gltf.js b/src/utils/pinned-entity-to-gltf.js
index c820dd786c5d5714c2faf008b49182c08bc63513..092f47d354a920adbff306c1f3ec82628b97b622 100644
--- a/src/utils/pinned-entity-to-gltf.js
+++ b/src/utils/pinned-entity-to-gltf.js
@@ -25,6 +25,11 @@ export default function pinnedEntityToGltf(el) {
     if (components["media-pager"]) {
       gltfComponents.media.pageIndex = components["media-pager"].data.index;
     }
+
+    if (components["media-video"] && components["media-video"].data.videoPaused) {
+      gltfComponents.media.paused = true;
+      gltfComponents.media.time = components["media-video"].data.time;
+    }
   }
 
   gltfComponents.pinnable = { pinned: true };