diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml
index 59fa66af0b..0b34a8f9ac 100644
--- a/interface/resources/qml/hifi/Card.qml
+++ b/interface/resources/qml/hifi/Card.qml
@@ -17,7 +17,7 @@ import QtGraphicalEffects 1.0
 import "toolbars"
 import "../styles-uit"
 
-Rectangle {
+Item {
     id: root;
     property string userName: "";
     property string placeName: "";
@@ -45,7 +45,7 @@ Rectangle {
     property int textSizeSmall: 18;
     property int stackShadowNarrowing: 5;
     property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif");
-    property int shadowHeight: 20;
+    property int shadowHeight: 10;
     HifiConstants { id: hifi }
 
     function pastTime(timestamp) { // Answer a descriptive string
@@ -70,6 +70,40 @@ Rectangle {
     }
 
     property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4);
+
+    DropShadow {
+        visible: isStacked;
+        anchors.fill: shadow1;
+        source: shadow1;
+        verticalOffset: 2;
+        radius: 4;
+        samples: 9;
+        color: hifi.colors.baseGrayShadow;
+    }
+    Rectangle {
+        id: shadow1;
+        visible: isStacked;
+        width: parent.width - stackShadowNarrowing;
+        height: shadowHeight;
+        anchors {
+            top: parent.bottom;
+            horizontalCenter: parent.horizontalCenter;
+        }
+    }
+    DropShadow {
+        anchors.fill: base;
+        source: base;
+        verticalOffset: 2;
+        radius: 4;
+        samples: 9;
+        color: hifi.colors.baseGrayShadow;
+    }
+    Rectangle {
+        id: base;
+        color: "white";
+        anchors.fill: parent;
+    }
+
     AnimatedImage {
         id: animation;
         // Always visible, to drive loading, but initially covered up by lobby during load.
@@ -96,34 +130,6 @@ Rectangle {
             }
         }
     }
-    Rectangle {
-        id: shadow1;
-        visible: isStacked;
-        width: parent.width - stackShadowNarrowing;
-        height: shadowHeight / 2;
-        anchors {
-            top: parent.bottom;
-            horizontalCenter: parent.horizontalCenter;
-        }
-        gradient: Gradient {
-            GradientStop { position: 0.0; color: "gray" }
-            GradientStop { position: 1.0; color: "white" }
-        }
-    }
-    Rectangle {
-        id: shadow2;
-        visible: isStacked;
-        width: shadow1.width - stackShadowNarrowing;
-        height: shadowHeight / 2;
-        anchors {
-            top: shadow1.bottom;
-            horizontalCenter: parent.horizontalCenter;
-        }
-        gradient: Gradient {
-            GradientStop { position: 0.0; color: "gray" }
-            GradientStop { position: 1.0; color: "white" }
-        }
-    }
     property int dropHorizontalOffset: 0;
     property int dropVerticalOffset: 1;
     property int dropRadius: 2;
@@ -168,7 +174,7 @@ Rectangle {
             source: "../../images/snap-icon.svg"
             width: 40;
             height: 40;
-            visible: action === 'snapshot';
+            visible: (action === 'snapshot') && (messageHeight >= 40);
         }
         RalewayRegular {
             id: message;
@@ -209,7 +215,7 @@ Rectangle {
     StateImage {
         id: actionIcon;
         imageURL: "../../images/info-icon-2-state.svg";
-        size: 32;
+        size: 30;
         buttonState: messageArea.containsMouse ? 1 : 0;
         anchors {
             bottom: parent.bottom;
diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml
index d95518b891..b03144644a 100644
--- a/interface/resources/qml/hifi/Feed.qml
+++ b/interface/resources/qml/hifi/Feed.qml
@@ -23,7 +23,15 @@ Column {
 
     property int cardWidth: 212;
     property int cardHeight: 152;
-    property int stackedCardShadowHeight: 10;
+    property int textPadding: 10;
+    property int smallMargin: 4;
+    property int messageHeight: 40;
+    property int textSize: 24;
+    property int textSizeSmall: 18;
+    property int stackShadowNarrowing: 5;
+    property int stackedCardShadowHeight: 4;
+    property int labelSize: 20;
+
     property string metaverseServerUrl: '';
     property string actions: 'snapshot';
     onActionsChanged: fillDestinations();
@@ -118,7 +126,7 @@ Column {
     }
     function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches
         var words = filter.toUpperCase().split(/\s+/).filter(identity);
-        function suggestable(story) { // fixme add to makeFilteredStoryProcessor
+        function suggestable(story) {
             if (story.action === 'snapshot') {
                 return true;
             }
@@ -161,25 +169,26 @@ Column {
         root.visible = !!suggestions.count;
     }
 
-    RalewayLight {
+    RalewayBold {
         id: label;
         text: labelText;
-        color: hifi.colors.white;
-        size: 28;
+        color: hifi.colors.blueAccent;
+        size: labelSize;
     }
     ListView {
         id: scroll;
-        clip: true;
         model: suggestions;
         orientation: ListView.Horizontal;
         highlightMoveDuration: -1;
         highlightMoveVelocity: -1;
-        highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; }
+        highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight; z: 1; }
+        currentIndex: -1;
 
-        spacing: 14;
+        spacing: 12;
         width: parent.width;
         height: cardHeight + stackedCardShadowHeight;
         delegate: Card {
+            id: card;
             width: cardWidth;
             height: cardHeight;
             goFunction: root.goFunction;
@@ -193,7 +202,15 @@ Column {
             onlineUsers: model.online_users;
             storyId: model.metaverseId;
             drillDownToPlace: model.drillDownToPlace;
-            shadowHeight: stackedCardShadowHeight;
+
+            textPadding: root.textPadding;
+            smallMargin: root.smallMargin;
+            messageHeight: root.messageHeight;
+            textSize: root.textSize;
+            textSizeSmall: root.textSizeSmall;
+            stackShadowNarrowing: root.stackShadowNarrowing;
+            shadowHeight: root.stackedCardShadowHeight;
+
             hoverThunk: function () { scroll.currentIndex = index; }
             unhoverThunk: function () { scroll.currentIndex = -1; }
         }
diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
index 5578b94168..9689583649 100644
--- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
+++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml
@@ -154,7 +154,7 @@ StackView {
                 left: parent.left;
             }
 
-            HifiStyles.RalewayLight {
+            HifiStyles.RalewayRegular {
                 id: notice;
                 font.pixelSize: hifi.fonts.pixelSize * 0.7;
                 anchors {
@@ -224,63 +224,73 @@ StackView {
 
         Rectangle {
             id: bgMain;
-            color: hifiStyleConstants.colors.faintGray50;
             anchors {
                 top: addressBar.bottom;
                 bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom;
                 left: parent.left;
                 right: parent.right;
             }
-            ScrollView {
-                anchors.fill: bgMain;
-                horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff;
-                verticalScrollBarPolicy: Qt.ScrollBarAsNeeded;
-                Rectangle { // Column margins require QtQuick 2.7, which we don't use yet.
-                    id: column;
-                    property real pad: 10;
-                    width: bgMain.width - column.pad;
-                    height: stack.height;
-                    color: "transparent";
-                    anchors {
-                        left: parent.left;
-                        leftMargin: column.pad;
-                        topMargin: column.pad;
+            Rectangle {
+                id: addressShadow;
+                width: parent.width;
+                height: 42 - 33;
+                gradient: Gradient {
+                    GradientStop { position: 0.0; color: "gray" }
+                    GradientStop { position: 1.0; color: "white" }
+                }
+            }
+            Rectangle { // Column margins require QtQuick 2.7, which we don't use yet.
+                id: column;
+                property real pad: 10;
+                width: bgMain.width - column.pad;
+                height: stack.height;
+                color: "transparent";
+                anchors {
+                    left: parent.left;
+                    leftMargin: column.pad;
+                    top: addressShadow.bottom;
+                    topMargin: column.pad;
+                }
+                Column {
+                    id: stack;
+                    width: column.width;
+                    spacing: 33 - places.labelSize;
+                    Feed {
+                        id: happeningNow;
+                        width: parent.width;
+                        cardWidth: 312 + (2 * 4);
+                        cardHeight: 163 + (2 * 4);
+                        metaverseServerUrl: addressBarDialog.metaverseServerUrl;
+                        labelText: 'HAPPENING NOW';
+                        //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing.
+                        actions: 'announcement';
+                        filter: addressLine.text;
+                        goFunction: goCard;
                     }
-                    Column {
-                        id: stack;
-                        width: column.width;
-                        spacing: column.pad;
-                        Feed {
-                            id: happeningNow;
-                            width: parent.width;
-                            property real cardScale: 1.5;
-                            cardWidth: places.cardWidth * happeningNow.cardScale;
-                            cardHeight: places.cardHeight * happeningNow.cardScale;
-                            metaverseServerUrl: addressBarDialog.metaverseServerUrl;
-                            labelText: 'Happening Now';
-                            //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing.
-                            actions: 'announcement';
-                            filter: addressLine.text;
-                            goFunction: goCard;
-                        }
-                        Feed {
-                            id: places;
-                            width: parent.width;
-                            metaverseServerUrl: addressBarDialog.metaverseServerUrl;
-                            labelText: 'Places';
-                            actions: 'concurrency';
-                            filter: addressLine.text;
-                            goFunction: goCard;
-                        }
-                        Feed {
-                            id: snapshots;
-                            width: parent.width;
-                            metaverseServerUrl: addressBarDialog.metaverseServerUrl;
-                            labelText: 'Recent Activity';
-                            actions: 'snapshot';
-                            filter: addressLine.text;
-                            goFunction: goCard;
-                        }
+                    Feed {
+                        id: places;
+                        width: parent.width;
+                        cardWidth: 210;
+                        cardHeight: 110 + messageHeight;
+                        messageHeight: 44;
+                        metaverseServerUrl: addressBarDialog.metaverseServerUrl;
+                        labelText: 'PLACES';
+                        actions: 'concurrency';
+                        filter: addressLine.text;
+                        goFunction: goCard;
+                    }
+                    Feed {
+                        id: snapshots;
+                        width: parent.width;
+                        cardWidth: 143 + (2 * 4);
+                        cardHeight: 75 + messageHeight + 4;
+                        messageHeight: 32;
+                        textPadding: 6;
+                        metaverseServerUrl: addressBarDialog.metaverseServerUrl;
+                        labelText: 'RECENT SNAPS';
+                        actions: 'snapshot';
+                        filter: addressLine.text;
+                        goFunction: goCard;
                     }
                 }
             }
@@ -369,8 +379,8 @@ StackView {
             notice.text = "Go To a place, @user, path, or network address:";
             notice.color = hifiStyleConstants.colors.baseGrayHighlight;
         } else {
-            notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected";
-            notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.baseGrayHighlight : hifiStyleConstants.colors.redHighlight;
+            notice.text = AddressManager.isConnected ? "YOUR LOCATION" : "NOT CONNECTED";
+            notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.blueHighlight : hifiStyleConstants.colors.redHighlight;
             // Display hostname, which includes ip address, localhost, and other non-placenames.
             location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '');
         }
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 9e437e5d26..e8fe082d10 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -535,6 +535,7 @@ bool setupEssentials(int& argc, char** argv) {
     DependencyManager::set<OctreeStatsProvider>(nullptr, qApp->getOcteeSceneStats());
     DependencyManager::set<AvatarBookmarks>();
     DependencyManager::set<LocationBookmarks>();
+    DependencyManager::set<Snapshot>();
 
     return previousSessionCrashed;
 }
@@ -2052,6 +2053,7 @@ void Application::initializeUi() {
     rootContext->setContextProperty("Scene", DependencyManager::get<SceneScriptingInterface>().data());
     rootContext->setContextProperty("Render", _renderEngine->getConfiguration().get());
     rootContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface());
+    rootContext->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
 
     rootContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
 
@@ -5517,6 +5519,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
     scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
     scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
     scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
+    scriptEngine->registerGlobalObject("Snapshot", DependencyManager::get<Snapshot>().data());
     scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
     scriptEngine->registerGlobalObject("AudioStats", DependencyManager::get<AudioClient>()->getStats().data());
     scriptEngine->registerGlobalObject("AudioScope", DependencyManager::get<AudioScope>().data());
@@ -6461,7 +6464,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
         // Get a screenshot and save it
         QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio));
         // If we're not doing an animated snapshot as well...
-        if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) {
+        if (!includeAnimated) {
             // Tell the dependency manager that the capture of the still snapshot has taken place.
             emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
         } else {
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index 2df825d643..1e14c24da3 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -168,6 +168,28 @@ void WindowScriptingInterface::ensureReticleVisible() const {
     }
 }
 
+/// Display a "browse to directory" dialog.  If `directory` is an invalid file or directory the browser will start at the current
+/// working directory.
+/// \param const QString& title title of the window
+/// \param const QString& directory directory to start the file browser at
+/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog`
+/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue`
+QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) {
+    ensureReticleVisible();
+    QString path = directory;
+    if (path.isEmpty()) {
+        path = getPreviousBrowseLocation();
+    }
+#ifndef Q_OS_WIN
+    path = fixupPathForMac(directory);
+#endif
+    QString result = OffscreenUi::getExistingDirectory(nullptr, title, path);
+    if (!result.isEmpty()) {
+        setPreviousBrowseLocation(QFileInfo(result).absolutePath());
+    }
+    return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result);
+}
+
 /// Display an open file dialog.  If `directory` is an invalid file or directory the browser will start at the current
 /// working directory.
 /// \param const QString& title title of the window
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index eb7dc02627..2b1e48d918 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -51,6 +51,7 @@ public slots:
     QScriptValue confirm(const QString& message = "");
     QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
     CustomPromptResult customPrompt(const QVariant& config);
+    QScriptValue browseDir(const QString& title = "", const QString& directory = "");
     QScriptValue browse(const QString& title = "", const QString& directory = "",  const QString& nameFilter = "");
     QScriptValue save(const QString& title = "", const QString& directory = "",  const QString& nameFilter = "");
     QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
@@ -74,7 +75,7 @@ signals:
     void svoImportRequested(const QString& url);
     void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo);
     void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify);
-    void snapshotShared(const QString& error);
+    void snapshotShared(bool isError, const QString& reply);
     void processingGifStarted(const QString& pathStillSnapshot);
     void processingGifCompleted(const QString& pathAnimatedSnapshot);
 
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index a12d9020ae..617ac1ed1c 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -116,11 +116,6 @@ void setupPreferences() {
         auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter);
         preferences->addPreference(preference);
     }
-    {
-        auto getter = []()->bool { return SnapshotAnimated::alsoTakeAnimatedSnapshot.get(); };
-        auto setter = [](bool value) { SnapshotAnimated::alsoTakeAnimatedSnapshot.set(value); };
-        preferences->addPreference(new CheckPreference(SNAPSHOTS, "Take Animated GIF Snapshot", getter, setter));
-    }
     {
         auto getter = []()->float { return SnapshotAnimated::snapshotAnimatedDuration.get(); };
         auto setter = [](float value) { SnapshotAnimated::snapshotAnimatedDuration.set(value); };
diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp
index f75190530c..59ecce5bc7 100644
--- a/interface/src/ui/Snapshot.cpp
+++ b/interface/src/ui/Snapshot.cpp
@@ -194,3 +194,10 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) {
                                 multiPart);
 }
 
+QString Snapshot::getSnapshotsLocation() {
+    return snapshotsLocation.get("");
+}
+
+void Snapshot::setSnapshotsLocation(const QString& location) {
+    snapshotsLocation.set(location);
+}
diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h
index 14e1bc2e9f..93ffbbc7bb 100644
--- a/interface/src/ui/Snapshot.h
+++ b/interface/src/ui/Snapshot.h
@@ -18,6 +18,7 @@
 #include <QStandardPaths>
 
 #include <SettingHandle.h>
+#include <DependencyManager.h>
 
 class QFile;
 class QTemporaryFile;
@@ -32,7 +33,9 @@ private:
     QUrl _URL;
 };
 
-class Snapshot {
+class Snapshot : public QObject, public Dependency {
+    Q_OBJECT
+    SINGLETON_DEPENDENCY
 public:
     static QString saveSnapshot(QImage image);
     static QTemporaryFile* saveTempSnapshot(QImage image);
@@ -40,6 +43,10 @@ public:
 
     static Setting::Handle<QString> snapshotsLocation;
     static void uploadSnapshot(const QString& filename, const QUrl& href = QUrl(""));
+
+public slots:
+    Q_INVOKABLE QString getSnapshotsLocation();
+    Q_INVOKABLE void setSnapshotsLocation(const QString& location);
 private:
     static QFile* savedFileForSnapshot(QImage & image, bool isTemporary);
 };
diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp
index 411e892de5..aa37608476 100644
--- a/interface/src/ui/SnapshotUploader.cpp
+++ b/interface/src/ui/SnapshotUploader.cpp
@@ -49,6 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
         userStoryObject.insert("place_name", placeName);
         userStoryObject.insert("path", currentPath);
         userStoryObject.insert("action", "snapshot");
+        userStoryObject.insert("audience", "for_url");
         rootObject.insert("user_story", userStoryObject);
 
         auto accountManager = DependencyManager::get<AccountManager>();
@@ -61,7 +62,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) {
             QJsonDocument(rootObject).toJson());
 
     } else {
-        emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(contents);
+        emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, contents);
         delete this;
     }
 }
@@ -72,12 +73,13 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) {
     if (replyString.size() == 0) {
         replyString = reply.errorString();
     }
-    emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(replyString); // maybe someday include _inWorldLocation, _filename?
+    emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename?
     delete this;
 }
 
 void SnapshotUploader::createStorySuccess(QNetworkReply& reply) {
-    emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(QString());
+    QString replyString = reply.readAll();
+    emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(false, replyString);
     delete this;
 }
 
@@ -87,7 +89,7 @@ void SnapshotUploader::createStoryFailure(QNetworkReply& reply) {
     if (replyString.size() == 0) {
         replyString = reply.errorString();
     }
-    emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(replyString);
+    emit DependencyManager::get<WindowScriptingInterface>()->snapshotShared(true, replyString);
     delete this;
 }
 
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index 24b12dacf0..fedead5aa5 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -51,6 +51,7 @@
 #include "ui/AvatarInputs.h"
 #include "avatar/AvatarManager.h"
 #include "scripting/GlobalServicesScriptingInterface.h"
+#include "ui/Snapshot.h"
 
 static const float DPI = 30.47f;
 static const float INCHES_TO_METERS = 1.0f / 39.3701f;
@@ -177,6 +178,7 @@ void Web3DOverlay::loadSourceURL() {
         _webSurface->getRootContext()->setContextProperty("Quat", new Quat());
         _webSurface->getRootContext()->setContextProperty("MyAvatar", DependencyManager::get<AvatarManager>()->getMyAvatar().get());
         _webSurface->getRootContext()->setContextProperty("Entities", DependencyManager::get<EntityScriptingInterface>().data());
+        _webSurface->getRootContext()->setContextProperty("Snapshot", DependencyManager::get<Snapshot>().data());
 
         if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
             auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp
index b36abebb1b..6edd969568 100644
--- a/libraries/animation/src/AnimInverseKinematics.cpp
+++ b/libraries/animation/src/AnimInverseKinematics.cpp
@@ -568,8 +568,10 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector<IKTarget>& targe
                 newHipsOffset += targetPosition - actualPosition;
 
                 // Add downward pressure on the hips
-                newHipsOffset *= 0.95f;
-                newHipsOffset -= 1.0f;
+                const float PRESSURE_SCALE_FACTOR = 0.95f;
+                const float PRESSURE_TRANSLATION_OFFSET = 1.0f;
+                newHipsOffset *= PRESSURE_SCALE_FACTOR;
+                newHipsOffset -= PRESSURE_TRANSLATION_OFFSET;
             }
         } else if (target.getType() == IKTarget::Type::RotationAndPosition) {
             glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans();
@@ -627,7 +629,7 @@ static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float l
     const int NUM_SUBDIVISIONS = 8;
     std::vector<float> minDots;
     minDots.reserve(NUM_SUBDIVISIONS);
-    float dTheta = (2.0f * PI) / NUM_SUBDIVISIONS;
+    float dTheta = TWO_PI / NUM_SUBDIVISIONS;
     float theta = 0.0f;
     for (int i = 0; i < NUM_SUBDIVISIONS; i++) {
         minDots.push_back(cosf(glm::length(glm::vec2(anteriorSwingTheta * cosf(theta), lateralSwingTheta * sinf(theta)))));
@@ -829,7 +831,9 @@ void AnimInverseKinematics::initConstraints() {
             stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
 
             // limit lateral swings more then forward-backward swings
-            setEllipticalSwingLimits(stConstraint, PI / 30.0f, PI / 20.0f);
+            const float MAX_SPINE_LATERAL_SWING = PI / 30.0f;
+            const float MAX_SPINE_ANTERIOR_SWING = PI / 20.0f;
+            setEllipticalSwingLimits(stConstraint, MAX_SPINE_LATERAL_SWING, MAX_SPINE_ANTERIOR_SWING);
 
             if (0 == baseName.compare("Spine1", Qt::CaseSensitive)
                     || 0 == baseName.compare("Spine", Qt::CaseSensitive)) {
@@ -844,7 +848,10 @@ void AnimInverseKinematics::initConstraints() {
             const float MAX_NECK_TWIST = PI / 10.0f;
             stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST);
 
-            setEllipticalSwingLimits(stConstraint, PI / 10.0f, PI / 8.0f);
+            // limit lateral swings more then forward-backward swings
+            const float MAX_NECK_LATERAL_SWING = PI / 10.0f;
+            const float MAX_NECK_ANTERIOR_SWING = PI / 8.0f;
+            setEllipticalSwingLimits(stConstraint, MAX_NECK_LATERAL_SWING, MAX_NECK_ANTERIOR_SWING);
 
             constraint = static_cast<RotationConstraint*>(stConstraint);
         } else if (0 == baseName.compare("Head", Qt::CaseSensitive)) {
diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html
index 145cfb16a9..fb40c04d05 100644
--- a/scripts/system/html/SnapshotReview.html
+++ b/scripts/system/html/SnapshotReview.html
@@ -1,15 +1,17 @@
 <html>
 <head>
     <title>Share</title>
-    <link rel="stylesheet" type="text/css" href="css/edit-style.css">
+    <link rel="stylesheet" type="text/css" href="css/hifi-style.css">
     <link rel="stylesheet" type="text/css" href="css/SnapshotReview.css">
     <script type="text/javascript" src="js/eventBridgeLoader.js"></script>
     <script type="text/javascript" src="js/SnapshotReview.js"></script>
 </head>
 
 <body>
-    <div class="snapsection title">
-        <label>Snap</label>
+    <div class="title">
+        <label>Snapshots</label>
+        <label id="settingsLabel" for="snapshotSettings">Settings</label>
+        <input type="button" class="hifi-glyph naked" id="snapshotSettings" value="@" onclick="snapshotSettings()" />
     </div>
     <hr />
     <div id="snapshot-pane">
@@ -17,30 +19,16 @@
         </div>
     </div>
     <div id="snapshot-controls">
-        <div class="snapsection" id="snap-buttons">
-            <div id="sharing">
-                <div class="button">
-                    <span class="compound-button">
-                        <input type="button" class="blue" id="share" value="Share in Feed" onclick="shareSelected()" />
-                        <span class="glyph"></span>
-                    </span>
-                </div>
-            </div>
-            <div class="button">
-                <input type="button" class="black" id="close" value="Don't Share" onclick="doNotShare()" />
-            </div>
-        </div>
-        <hr />
-        <div class="snapsection" id="snap-settings">
-            <span class="setting">
-                <input type="button" class="glyph naked" id="snapshotSettings" value="@" onclick="snapshotSettings()" />
-                <label for="snapshotSettings">Settings</label>
-            </span>
-            <span class="setting checkbox">
-                <input id="openFeed" type="checkbox" checked />
-                <label for="openFeed">Open feed after</label>
-            </span>
+        <div id="snap-settings">
+            <label>CAMERA CAPTURES</label><br />
+            <form action="">
+                <input type="radio" name="cameraCaptures" id="stillAndGif" value="stillAndGif" /><label for="stillAndGif"><span><span></span></span>Still + GIF</label>
+                <br />
+                <input type="radio" name="cameraCaptures" id="stillOnly" value="stillOnly" /><label for="stillOnly"><span><span></span></span>Still Only</label>
+            </form>
         </div>
+        <input type="button" id="snap-button" onclick="takeSnapshot()" />
+        <div id="snap-settings-right"></div>
     </div>
 </body>
 </html>
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css
index 34b690a021..12b91d372b 100644
--- a/scripts/system/html/css/SnapshotReview.css
+++ b/scripts/system/html/css/SnapshotReview.css
@@ -8,142 +8,280 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 */
 
-body {
-    padding-top: 0;
-    padding-bottom: 14px;
-}
+/*
+// START styling of top bar and its contents
+*/
 
-.snapsection {
-    padding-top: 14px;
-    text-align: center;
-}
-
-.snapsection.title {
-    padding-top: 0;
+.title {
+    padding: 6px 10px;
     text-align: left;
+    height: 26px;
+    line-height: 26px;
+    clear: both;
 }
 
 .title label {
-    font-size: 18px;
     position: relative;
-    top: 12px;
+    font-size: 18px;
+    float: left;
 }
 
+#snapshotSettings {
+    position: relative;
+    float: right;
+}
+#settingsLabel {
+    position: relative;
+    float: right;
+    font-family: Raleway-SemiBold;
+    font-size: 14px;
+}
+.hifi-glyph {
+    font-size: 30px;
+    top: -4px;
+}
+input[type=button].naked {
+    color: #afafaf;
+    background: none;
+}
+input[type=button].naked:hover {
+    color: #ffffff;
+}
+input[type=button].naked:active {
+    color: #afafaf;
+}
+/*
+// END styling of top bar and its contents
+*/
+
+/*
+// START styling of snapshot instructions panel
+*/
+.snapshotInstructions {
+    font-family: Raleway-Regular;
+    margin: 0 20px;
+    width: 100%;
+    height: 50%;
+}
+/*
+// END styling of snapshot instructions panel
+*/
+
+/*
+// START styling of snapshot pane and its contents
+*/
 #snapshot-pane {
     width: 100%;
-    height: 100%;
-    position: absolute;
-    top: 0;
-    left: 0;
-    box-sizing: border-box;
-    padding-top: 56px;
-    padding-bottom: 175px;
+    height: 560px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
 }
 
 #snapshot-images {
-    height: 100%;
     width: 100%;
-    position: relative;
-}
-
-#snapshot-images > div {
-    position: relative;
-    text-align: center;
 }
 
 #snapshot-images img {
     max-width: 100%;
     max-height: 100%;
+}
+
+.gifLabel {
+    position:absolute;
+    left: 15px;
+    top: 10px;
+    font-family: Raleway-SemiBold;
+    font-size: 18px;
+    color: white;
+    text-shadow: 2px 2px 3px #000000;
+}
+/*
+// END styling of snapshot pane and its contents
+*/
+
+/*
+// START styling of share bar
+*/
+.shareControls {
+    display: flex;
+    justify-content: space-between;
+    flex-direction: row;
+    align-items: center;
+    height: 50px;
+    line-height: 60px;
+    width: calc(100% - 8px);
     position: absolute;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    vertical-align: middle;
+    bottom: 4px;
+    left: 4px;
+    right: 4px;
 }
-
-#snapshot-images div.property {
-    margin-top: 0;
+.shareButtons {
+    display: flex;
+    align-items: center;
+    margin-left: 30px;
+    height: 100%;
+    width: 80%;
+}
+.blastToConnections {
+    text-align: left;
+    margin-right: 25px;
+    height: 29px;
+}
+.shareWithEveryone {
+    background: #DDDDDD url(../img/shareToFeed.png) no-repeat scroll center;
+    border-width: 0px;
+    text-align: left;
+    margin-right: 8px;
+    height: 29px;
+    width: 30px;
+    border-radius: 3px;
+}
+.facebookButton {
+    background-image: url(../img/fb_logo.png);
+    width: 29px;
+    height: 29px;
+    display: inline-block;
+    margin-right: 8px;
+}
+.twitterButton {
+    background-image: url(../img/twitter_logo.png);
+    width: 29px;
+    height: 29px;
+    display: inline-block;
+    margin-right: 8px;
+    border-radius: 3px;
+}
+.showShareButtonsButtonDiv {
+    display: inline-flex;
+    align-items: center;
+    font-family: Raleway-SemiBold;
+    font-size: 14px;
+    color: white;
+    text-shadow: 2px 2px 3px #000000;
+    height: 100%;
+    margin-right: 10px;
+    width: 20%;
+}
+.showShareButton {
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    border-width: 0;
+    margin-left: 5px;
+    outline: none;
+}
+.showShareButton.active {
+    border-color: #00b4ef;
+    border-width: 3px;
+    background-color: white;
+}
+.showShareButton.active:hover {
+    background-color: #afafaf;
+}
+.showShareButton.active:active {
+    background-color: white;
+}
+.showShareButton.inactive {
+    border-width: 0;
+    background-color: white;
+}
+.showShareButton.inactive:hover {
+    background-color: #afafaf;
+}
+.showShareButton.inactive:active {
+    background-color: white;
+}
+.showShareButtonDots {
+    display: flex;
+    width: 32px;
+    height: 40px;
     position: absolute;
-    top: 50%;
-    left: 7px;
-    transform: translate(0%, -50%);
+    top: 5px;
+    right: 14px;
+    pointer-events: none;
 }
-
-#snapshot-images img {
-    box-sizing: border-box;
-    padding: 0 7px 0 7px;
-}
-
-#snapshot-images img.multiple {
-    padding-left: 28px;
+.showShareButtonDots > span {
+    width: 10px;
+    height: 10px;
+    margin: auto;
+    background-color: #0093C5;
+    border-radius: 50%;
+    border-width: 0;
+    display: inline;
 }
+/*
+// END styling of share overlay
+*/
 
+/*
+// START styling of snapshot controls (bottom panel) and its contents
+*/
 #snapshot-controls {
     width: 100%;
     position: absolute;
     left: 0;
-    bottom: 14px;
+    overflow: hidden;
+    display: flex;
+    justify-content: center;
+}
+#snap-settings {
+    display: inline;
+    width: 150px;
+    margin: 2px auto 0 auto;
+}
+#snap-settings form input {
+    margin-bottom: 5px;
 }
 
-.prompt {
-    font-family: Raleway-SemiBold;
-    font-size: 14px;
-}
-
-div.button {
-    padding-top: 21px;
-}
-
-.compound-button {
-    position: relative;
-    height: auto;
-}
-
-.compound-button input {
-    padding-left: 40px;
-}
-
-.compound-button .glyph {
-    display: inline-block;
-    position: absolute;
-    left: 12px;
-    top: 16px;
-    width: 23px;
-    height: 23px;
-    background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaGVpZ2h0PSI0MCIKICAgd2lkdGg9IjQwIgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgdmlld0JveD0iMCAwIDQwIDQwIgogICB5PSIwcHgiCiAgIHg9IjBweCIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGEzNCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48ZGM6dGl0bGU+PC9kYzp0aXRsZT48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczMyIiAvPjxzdHlsZQogICAgIGlkPSJzdHlsZTQiCiAgICAgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM0MTQwNDI7fQoJLnN0MXtmaWxsOiNDQ0NDQ0M7fQoJLnN0MntmaWxsOiMxMzk4QkI7fQoJLnN0M3tmaWxsOiMzMUQ4RkY7fQo8L3N0eWxlPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMSI+PGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJjaXJjbGUxMyIKICAgICAgIHI9IjQuNDQwMDAwMSIKICAgICAgIGN5PSIxMjYuMTciCiAgICAgICBjeD0iMjAuNTQwMDAxIgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTUiCiAgICAgICBkPSJtIDI4Ljg3LDEzOS4yNiBjIDAuMDEsLTAuMDEgMC4wMiwtMC4wMiAwLjAzLC0wLjAzIGwgMCwtMS44NiBjIDAsLTIuNjggLTIuMzMsLTQuNzcgLTUsLTQuNzcgbCAtNi40MiwwIGMgLTIuNjgsMCAtNC44NSwyLjA5IC00Ljg1LDQuNzcgbCAwLDEuODggMTYuMjQsMCB6IgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTciCiAgICAgICBkPSJtIDM4LjE3LDEyMy40MiBjIDAsLTMuOTcgLTMuMjIsLTcuMTkgLTcuMTksLTcuMTkgbCAtMjAuMzEsMCBjIC0zLjk3LDAgLTcuMTksMy4yMiAtNy4xOSw3LjE5IGwgMCwxNC4xOCBjIDAsMy45NyAzLjIyLDcuMTkgNy4xOSw3LjE5IGwgMjAuMzEsMCBjIDMuOTcsMCA3LjE5LC0zLjIyIDcuMTksLTcuMTkgbCAwLC0xNC4xOCB6IG0gLTEuNzgsMTQuMjcgYyAwLDMuMDMgLTIuNDYsNS40OSAtNS40OSw1LjQ5IGwgLTIwLjMyLDAgYyAtMy4wMywwIC01LjQ5LC0yLjQ2IC01LjQ5LC01LjQ5IGwgMCwtMTQuMTkgYyAwLC0zLjAzIDIuNDYsLTUuNDkgNS40OSwtNS40OSBsIDIwLjMzLDAgYyAzLjAzLDAgNS40OSwyLjQ2IDUuNDksNS40OSBsIDAsMTQuMTkgeiIKICAgICAgIGNsYXNzPSJzdDEiIC8+PC9nPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMiIgLz48L3N2Zz4=);
-    background-repeat: no-repeat;
-    background-size: 23px 23px;
-}
-
-.setting {
-    display: inline-table;
-    height: 28px;
-}
-
-.setting label {
-    display: table-cell;
-    vertical-align: middle;
-    font-family: Raleway-SemiBold;
-    font-size: 14px;
-}
-
-.setting + .setting {
-    margin-left: 18px;
-}
-
-input[type=button].naked {
-    font-size: 40px;
-    line-height: 40px;
-    width: 30px;
+#snap-button {
+    width: 72px;
+    height: 72px;
     padding: 0;
-    margin: 0 0 -6px 0;
-    position: relative;
-    top: -6px;
-    left: -8px;
-    background: none;
+    border-radius: 50%;
+    background: #EA4C5F;
+    border: 3px solid white;
+    margin: 2px auto 0 auto;
+    box-sizing: content-box;
+    display: inline;
+    outline:none;
 }
+#snap-button:disabled {
+    background: gray;
+}
+#snap-button:hover:enabled {
+    background: #C62147;
+}
+#snap-button:active:enabled {
+    background: #EA4C5F;
+}
+#snap-settings-right {
+    display: inline;
+    width: 150px;
+    margin: auto;
+}
+/*
+// END styling of snapshot controls (bottom panel) and its contents
+*/
 
-input[type=button].naked:hover {
-    color: #00b4ef;
-    background: none;
+/*
+// START misc styling
+*/
+body {
+    padding: 0;
+    margin: 0;
+    overflow: hidden;
 }
+p {
+    margin: 2px 0;
+}
+h4 {
+    margin: 14px 0 0 0;
+}
+.centeredImage {
+    margin: 0 auto;
+    display: block;
+}
+/*
+// END misc styling
+*/
diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css
new file mode 100644
index 0000000000..f1ace02eb0
--- /dev/null
+++ b/scripts/system/html/css/hifi-style.css
@@ -0,0 +1,170 @@
+/*
+//  hifi-style.css
+//
+//  Created by Zach Fox on 2017-04-18
+//  Copyright 2017 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+*/
+
+@font-face {
+    font-family: Raleway-Regular;
+    src: url(../../../../resources/fonts/Raleway-Regular.ttf),  /* Windows production */
+         url(../../../../fonts/Raleway-Regular.ttf),  /* OSX production */
+         url(../../../../interface/resources/fonts/Raleway-Regular.ttf);  /* Development, running script in /HiFi/examples */
+}
+
+@font-face {
+    font-family: Raleway-Light;
+    src: url(../../../../resources/fonts/Raleway-Light.ttf),
+         url(../../../../fonts/Raleway-Light.ttf),
+         url(../../../../interface/resources/fonts/Raleway-Light.ttf);
+}
+
+@font-face {
+    font-family: Raleway-Bold;
+    src: url(../../../../resources/fonts/Raleway-Bold.ttf),
+         url(../../../../fonts/Raleway-Bold.ttf),
+         url(../../../../interface/resources/fonts/Raleway-Bold.ttf);
+}
+
+@font-face {
+    font-family: Raleway-SemiBold;
+    src: url(../../../../resources/fonts/Raleway-SemiBold.ttf),
+         url(../../../../fonts/Raleway-SemiBold.ttf),
+         url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf);
+}
+
+@font-face {
+    font-family: FiraSans-SemiBold;
+    src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf),
+         url(../../../../fonts/FiraSans-SemiBold.ttf),
+         url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf);
+}
+
+@font-face {
+    font-family: AnonymousPro-Regular;
+    src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf),
+         url(../../../../fonts/AnonymousPro-Regular.ttf),
+         url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf);
+}
+
+@font-face {
+    font-family: HiFi-Glyphs;
+    src: url(../../../../resources/fonts/hifi-glyphs.ttf),
+         url(../../../../fonts/hifi-glyphs.ttf),
+         url(../../../../interface/resources/fonts/hifi-glyphs.ttf);
+}
+
+body {
+    color: #afafaf;
+    background-color: #404040;
+    font-family: Raleway-Regular;
+    font-size: 15px;
+
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+
+    overflow-x: hidden;
+    overflow-y: auto;
+}
+
+hr {
+    border: none;
+    background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left;
+    padding: 1px;
+    -webkit-margin-before: 0;
+    -webkit-margin-after: 0;
+    -webkit-margin-start: 0;
+    -webkit-margin-end: 0;
+    width: 100%;
+    position: absolute;
+}
+
+.hifi-glyph {
+    font-family: HiFi-Glyphs;
+    border: none;
+    //margin: -10px;
+    padding: 0;
+}
+
+input[type=radio] {
+  width: 2em;
+  margin: 0;
+  padding: 0;
+  font-size: 1em;
+  opacity: 0;
+}
+input[type=radio] + label{
+  display: inline-block;
+  margin-left: -2em;
+  line-height: 2em;
+}
+input[type=radio] + label > span{
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  margin: 5px;
+  border-radius: 50%;
+  background: #6B6A6B;
+  background-image: linear-gradient(#7D7D7D, #6B6A6B);
+  vertical-align: bottom;
+}
+input[type=radio]:checked + label > span{
+  background-image: linear-gradient(#7D7D7D, #6B6A6B);
+}
+input[type=radio]:active + label > span,
+input[type=radio]:hover + label > span{
+  background-image: linear-gradient(#FFFFFF, #AFAFAF);
+}
+input[type=radio]:checked + label > span > span,
+input[type=radio]:active + label > span > span{
+  display: block;
+  width: 10px;
+  height: 10px;
+  margin: 3px;
+  border: 2px solid #36CDFF;
+  border-radius: 50%;
+  background: #00B4EF;
+}
+
+.grayButton {
+    font-family: FiraSans-SemiBold;
+    color: white;
+    padding: 0px 10px;
+    border-width: 0px;
+    background-image: linear-gradient(#FFFFFF, #AFAFAF);
+}
+.grayButton:hover {
+    background-image: linear-gradient(#FFFFFF, #FFFFFF);
+}
+.grayButton:active {
+    background-image: linear-gradient(#AFAFAF, #AFAFAF);
+}
+.grayButton:disabled {
+    background-image: linear-gradient(#FFFFFF, ##AFAFAF);
+}
+.blueButton {
+    font-family: FiraSans-SemiBold;
+    color: white;
+    padding: 0px 10px;
+    border-radius: 3px;
+    border-width: 0px;
+    background-image: linear-gradient(#00B4EF, #1080B8);
+    min-height: 30px;
+
+}
+.blueButton:hover {
+    background-image: linear-gradient(#00B4EF, #00B4EF);
+}
+.blueButton:active {
+    background-image: linear-gradient(#1080B8, #1080B8);
+}
+.blueButton:disabled {
+    background-image: linear-gradient(#FFFFFF, #AFAFAF);
+}
\ No newline at end of file
diff --git a/scripts/system/html/img/fb_logo.png b/scripts/system/html/img/fb_logo.png
new file mode 100644
index 0000000000..1de20bacd8
Binary files /dev/null and b/scripts/system/html/img/fb_logo.png differ
diff --git a/scripts/system/html/img/shareIcon.png b/scripts/system/html/img/shareIcon.png
new file mode 100644
index 0000000000..0486ac9202
Binary files /dev/null and b/scripts/system/html/img/shareIcon.png differ
diff --git a/scripts/system/html/img/shareToFeed.png b/scripts/system/html/img/shareToFeed.png
new file mode 100644
index 0000000000..f681c49d8f
Binary files /dev/null and b/scripts/system/html/img/shareToFeed.png differ
diff --git a/scripts/system/html/img/snapshotIcon.png b/scripts/system/html/img/snapshotIcon.png
new file mode 100644
index 0000000000..5cb2742a32
Binary files /dev/null and b/scripts/system/html/img/snapshotIcon.png differ
diff --git a/scripts/system/html/img/twitter_logo.png b/scripts/system/html/img/twitter_logo.png
new file mode 100644
index 0000000000..59fd027c2a
Binary files /dev/null and b/scripts/system/html/img/twitter_logo.png differ
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js
index f140c54e09..53f4d17930 100644
--- a/scripts/system/html/js/SnapshotReview.js
+++ b/scripts/system/html/js/SnapshotReview.js
@@ -10,117 +10,325 @@
 //  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
 
-var paths = [], idCounter = 0, imageCount;
-function addImage(data) {
-    if (!data.localPath) {
+var paths = [];
+var idCounter = 0;
+var imageCount = 0;
+function showSetupInstructions() {
+    var snapshotImagesDiv = document.getElementById("snapshot-images");
+    snapshotImagesDiv.className = "snapshotInstructions";
+    snapshotImagesDiv.innerHTML = '<img class="centeredImage" src="./img/snapshotIcon.png" alt="Snapshot Instructions" width="64" height="64"/>' +
+    '<br/>' +
+    '<p>This app lets you take and share snaps and GIFs with your connections in High Fidelity.</p>' +
+    "<h4>Setup Instructions</h4>" +
+    "<p>Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:</p>" +
+    '<br/>' +
+    '<div style="text-align:center;">' +
+        '<input class="blueButton" style="margin-left:auto;margin-right:auto;width:130px;" type="button" value="CHOOSE" onclick="chooseSnapshotLocation()" />' +
+    '</div>';
+    document.getElementById("snap-button").disabled = true;
+}
+function showSetupComplete() {
+    var snapshotImagesDiv = document.getElementById("snapshot-images");
+    snapshotImagesDiv.className = "snapshotInstructions";
+    snapshotImagesDiv.innerHTML = '<img class="centeredImage" src="./img/snapshotIcon.png" alt="Snapshot Instructions" width="64" height="64"/>' +
+    '<br/>' +
+    "<h4>You're all set!</h4>" +
+    '<p>Try taking a snapshot by pressing the red button below.</p>';
+}
+function chooseSnapshotLocation() {
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "snapshot",
+        action: "chooseSnapshotLocation"
+    }));
+}
+function clearImages() {
+    document.getElementById("snap-button").disabled = false;
+    var snapshotImagesDiv = document.getElementById("snapshot-images");
+    snapshotImagesDiv.classList.remove("snapshotInstructions");
+    while (snapshotImagesDiv.hasChildNodes()) {
+        snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild);
+    }
+    paths = [];
+    imageCount = 0;
+    idCounter = 0;
+}
+function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages, hifiShareButtonsDisabled) {
+    if (!image_data.localPath) {
         return;
     }
-    var div = document.createElement("DIV"),
-        input = document.createElement("INPUT"),
-        label = document.createElement("LABEL"),
-        img = document.createElement("IMG"),
-        div2 = document.createElement("DIV"),
-        id = "p" + idCounter++;
-        img.id = id + "img";
-    function toggle() { data.share = input.checked; }
-    div.style.height = "" + Math.floor(100 / imageCount) + "%";
+    var id = "p" + idCounter++;
+    // imageContainer setup
+    var imageContainer = document.createElement("DIV");
+    imageContainer.id = id;
+    imageContainer.style.width = "100%";
+    imageContainer.style.height = "251px";
+    imageContainer.style.display = "flex";
+    imageContainer.style.justifyContent = "center";
+    imageContainer.style.alignItems = "center";
+    imageContainer.style.position = "relative";
+    // img setup
+    var img = document.createElement("IMG");
+    img.id = id + "img";
     if (imageCount > 1) {
         img.setAttribute("class", "multiple");
     }
-    img.src = data.localPath;
-    div.appendChild(img);
-    if (imageCount > 1) { // I'd rather use css, but the included stylesheet is quite particular.
-        // Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state.
-        label.setAttribute('for', id); // cannot do label.for =
-        input.id = id;
-        input.type = "checkbox";
-        input.checked = false;
-        data.share = input.checked;
-        input.addEventListener('change', toggle);
-        div2.setAttribute("class", "property checkbox");
-        div2.appendChild(input);
-        div2.appendChild(label);
-        div.appendChild(div2);
-    } else {
-        data.share = true;
+    img.src = image_data.localPath;
+    imageContainer.appendChild(img);
+    document.getElementById("snapshot-images").appendChild(imageContainer);
+    paths.push(image_data.localPath);
+    var isGif = img.src.split('.').pop().toLowerCase() === "gif";
+    if (isGif) {
+        imageContainer.innerHTML += '<span class="gifLabel">GIF</span>';
+    }
+    if (!isGifLoading && !isShowingPreviousImages) {
+        shareForUrl(id);
+    } else if (isShowingPreviousImages && canSharePreviousImages) {
+        appendShareBar(id, image_data.story_id, isGif, hifiShareButtonsDisabled)
     }
-    document.getElementById("snapshot-images").appendChild(div);
-    paths.push(data);
 }
-function handleShareButtons(messageOptions) {
-    var openFeed = document.getElementById('openFeed');
-    openFeed.checked = messageOptions.openFeedAfterShare;
-    openFeed.onchange = function () {
+function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) {
+    var story_url = "https://highfidelity.com/user_stories/" + story_id;
+    var parentDiv = document.getElementById(divID);
+    parentDiv.setAttribute('data-story-id', story_id);
+    document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled));
+    if (divID === "p0") {
+        selectImageToShare(divID, true);
+    }
+}
+function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) {
+    var shareBar = document.createElement("div");
+    shareBar.id = parentID + "shareBar";
+    shareBar.className = "shareControls";
+    var shareButtonsDivID = parentID + "shareButtonsDiv";
+    var showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv";
+    var showShareButtonsButtonID = parentID + "showShareButtonsButton";
+    var showShareButtonsLabelID = parentID + "showShareButtonsLabel";
+    var blastToConnectionsButtonID = parentID + "blastToConnectionsButton";
+    var shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton";
+    var facebookButtonID = parentID + "facebookButton";
+    var twitterButtonID = parentID + "twitterButton";
+    shareBar.innerHTML += '' +
+        '<div class="shareButtons" id="' + shareButtonsDivID + '" style="visibility:hidden">' +
+            '<input type="button"' + (hifiShareButtonsDisabled ? ' disabled' : '') + ' class="blastToConnections blueButton" id="' + blastToConnectionsButtonID + '" value="BLAST TO MY CONNECTIONS" onclick="blastToConnections(' + parentID + ', ' + isGif + ')" />' +
+            '<input type="button"' + (hifiShareButtonsDisabled ? ' disabled' : '') + ' class="shareWithEveryone" id="' + shareWithEveryoneButtonID + '" onclick="shareWithEveryone(' + parentID + ', ' + isGif + ')" />' +
+            '<a class="facebookButton" id="' + facebookButtonID + '" onclick="shareButtonClicked(' + parentID + ')" target="_blank" href="https://www.facebook.com/dialog/feed?app_id=1585088821786423&link=' + shareURL + '"></a>' +
+            '<a class="twitterButton" id="' + twitterButtonID + '" onclick="shareButtonClicked(' + parentID + ')" target="_blank" href="https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelity&hashtags=VR,HiFi"></a>' +
+        '</div>' +
+        '<div class="showShareButtonsButtonDiv" id="' + showShareButtonsButtonDivID + '">' +
+            '<label id="' + showShareButtonsLabelID + '" for="' + showShareButtonsButtonID + '">SHARE</label>' +
+            '<input type="button" class="showShareButton inactive" id="' + showShareButtonsButtonID + '" onclick="selectImageToShare(' + parentID + ', true)" />' +
+            '<div class="showShareButtonDots">' +
+                '<span></span><span></span><span></span>' +
+            '</div>' +
+        '</div>';
+
+    // Add onclick handler to parent DIV's img to toggle share buttons
+    document.getElementById(parentID + 'img').onclick = function () { selectImageToShare(parentID, true) };
+
+    return shareBar;
+}
+function selectImageToShare(selectedID, isSelected) {
+    if (selectedID.id) {
+        selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID
+    }
+    var imageContainer = document.getElementById(selectedID);
+    var image = document.getElementById(selectedID + 'img');
+    var shareBar = document.getElementById(selectedID + "shareBar");
+    var shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv");
+    var showShareButtonsButton = document.getElementById(selectedID + "showShareButtonsButton");
+
+    if (isSelected) {
+        showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, false) };
+        showShareButtonsButton.classList.remove("inactive");
+        showShareButtonsButton.classList.add("active");
+
+        image.onclick = function () { selectImageToShare(selectedID, false) };
+        imageContainer.style.outline = "4px solid #00b4ef";
+        imageContainer.style.outlineOffset = "-4px";
+
+        shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.5)";
+
+        shareButtonsDiv.style.visibility = "visible";
+    } else {
+        showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, true) };
+        showShareButtonsButton.classList.remove("active");
+        showShareButtonsButton.classList.add("inactive");
+
+        image.onclick = function () { selectImageToShare(selectedID, true) };
+        imageContainer.style.outline = "none";
+
+        shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)";
+
+        shareButtonsDiv.style.visibility = "hidden";
+    }
+}
+function shareForUrl(selectedID) {
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "snapshot",
+        action: "shareSnapshotForUrl",
+        data: paths[parseInt(selectedID.substring(1))]
+    }));
+}
+function blastToConnections(selectedID, isGif) {
+    selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
+
+    document.getElementById(selectedID + "blastToConnectionsButton").disabled = true;
+    document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true;
+
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "snapshot",
+        action: "blastToConnections",
+        story_id: document.getElementById(selectedID).getAttribute("data-story-id"),
+        isGif: isGif
+    }));
+}
+function shareWithEveryone(selectedID, isGif) {
+    selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
+
+    document.getElementById(selectedID + "blastToConnectionsButton").disabled = true;
+    document.getElementById(selectedID + "shareWithEveryoneButton").disabled = true;
+
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "snapshot",
+        action: "shareSnapshotWithEveryone",
+        story_id: document.getElementById(selectedID).getAttribute("data-story-id"),
+        isGif: isGif
+    }));
+}
+function shareButtonClicked(selectedID) {
+    selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "snapshot",
+        action: "shareButtonClicked",
+        story_id: document.getElementById(selectedID).getAttribute("data-story-id")
+    }));
+}
+function cancelSharing(selectedID) {
+    selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID
+    var shareBar = document.getElementById(selectedID + "shareBar");
+
+    shareBar.style.display = "inline";
+}
+
+function handleCaptureSetting(setting) {
+    var stillAndGif = document.getElementById('stillAndGif');
+    var stillOnly = document.getElementById('stillOnly');
+    stillAndGif.checked = setting;
+    stillOnly.checked = !setting;
+
+    stillAndGif.onclick = function () {
         EventBridge.emitWebEvent(JSON.stringify({
             type: "snapshot",
-            action: (openFeed.checked ? "setOpenFeedTrue" : "setOpenFeedFalse")
+            action: "captureStillAndGif"
         }));
-    };
-
-    if (!messageOptions.canShare) {
-        // this means you may or may not be logged in, but can't share
-        // because you are not in a public place.
-        document.getElementById("sharing").innerHTML = "<p class='prompt'>Snapshots can be shared when they're taken in shareable places.";
     }
+    stillOnly.onclick = function () {
+        EventBridge.emitWebEvent(JSON.stringify({
+            type: "snapshot",
+            action: "captureStillOnly"
+        }));
+    }
+
 }
 window.onload = function () {
-    // Something like the following will allow testing in a browser.
-    //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'});
-    //addImage({ localPath: 'http://lorempixel.com/1512/1680' });
+    // Uncomment the line below to test functionality in a browser.
+    // See definition of "testInBrowser()" to modify tests.
+    //testInBrowser(true);
     openEventBridge(function () {
         // Set up a handler for receiving the data, and tell the .js we are ready to receive it.
         EventBridge.scriptEventReceived.connect(function (message) {
+
             message = JSON.parse(message);
+
             if (message.type !== "snapshot") {
                 return;
             }
+            
+            switch (message.action) {
+                case 'showSetupInstructions':
+                    showSetupInstructions();
+                    break;
+                case 'snapshotLocationChosen':
+                    clearImages();
+                    showSetupComplete();
+                    break;
+                case 'clearPreviousImages':
+                    clearImages();
+                    break;
+                case 'showPreviousImages':
+                    clearImages();
+                    var messageOptions = message.options;
+                    imageCount = message.image_data.length;
+                    message.image_data.forEach(function (element, idx, array) {
+                        addImage(element, true, true, message.canShare, message.image_data[idx].buttonDisabled);
+                    });
+                    break;
+                case 'addImages':
+                    // The last element of the message contents list contains a bunch of options,
+                    // including whether or not we can share stuff
+                    // The other elements of the list contain image paths.
+                    var messageOptions = message.options;
 
-            // The last element of the message contents list contains a bunch of options,
-            // including whether or not we can share stuff
-            // The other elements of the list contain image paths.
-            var messageOptions = message.action.pop();
-            handleShareButtons(messageOptions);
+                    if (messageOptions.containsGif) {
+                        if (messageOptions.processingGif) {
+                            imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon
+                            message.image_data.unshift({ localPath: messageOptions.loadingGifPath });
+                            message.image_data.forEach(function (element, idx, array) {
+                                addImage(element, idx === 0, false, false);
+                            });
+                        } else {
+                            var gifPath = message.image_data[0].localPath;
+                            var p0img = document.getElementById('p0img');
+                            p0img.src = gifPath;
 
-            if (messageOptions.containsGif) {
-                if (messageOptions.processingGif) {
-                    imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon
-                    message.action.unshift({ localPath: messageOptions.loadingGifPath });
-                    message.action.forEach(addImage);
-                    document.getElementById('p0').disabled = true;
-                } else {
-                    var gifPath = message.action[0].localPath;
-                    document.getElementById('p0').disabled = false;
-                    document.getElementById('p0img').src = gifPath;
-                    paths[0].localPath = gifPath;
-                }
-            } else {
-                imageCount = message.action.length;
-                message.action.forEach(addImage);
+                            paths[0] = gifPath;
+                            shareForUrl("p0");
+                        }
+                    } else {
+                        imageCount = message.image_data.length;
+                        message.image_data.forEach(function (element, idx, array) {
+                            addImage(element, false, false, false);
+                        });
+                    }
+                    break;
+                case 'captureSettings':
+                    handleCaptureSetting(message.setting);
+                    break;
+                case 'snapshotUploadComplete':
+                    var isGif = message.image_url.split('.').pop().toLowerCase() === "gif";
+                    appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif);
+                    break;
+                default:
+                    console.log("Unknown message action received in SnapshotReview.js.");
+                    break;
             }
         });
+
         EventBridge.emitWebEvent(JSON.stringify({
             type: "snapshot",
             action: "ready"
         }));
-    });
-
+    });;
 };
-// beware of bug: Cannot send objects at top level. (Nested in arrays is fine.)
-function shareSelected() {
-    EventBridge.emitWebEvent(JSON.stringify({
-        type: "snapshot",
-        action: paths
-    }));
-}
-function doNotShare() {
-    EventBridge.emitWebEvent(JSON.stringify({
-        type: "snapshot",
-        action: []
-    }));
-}
 function snapshotSettings() {
     EventBridge.emitWebEvent(JSON.stringify({
         type: "snapshot",
         action: "openSettings"
     }));
 }
+function takeSnapshot() {
+    EventBridge.emitWebEvent(JSON.stringify({
+        type: "snapshot",
+        action: "takeSnapshot"
+    }));
+}
+
+function testInBrowser(isTestingSetupInstructions) {
+    if (isTestingSetupInstructions) {
+        showSetupInstructions();
+    } else {
+        imageCount = 1;
+        //addImage({ localPath: 'http://lorempixel.com/553/255' });
+        addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false);
+    }
+}
diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js
index 6039bc09c1..1cc24b8265 100644
--- a/scripts/system/snapshot.js
+++ b/scripts/system/snapshot.js
@@ -7,7 +7,7 @@
 // Distributed under the Apache License, Version 2.0
 // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
 //
-/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar */
+/* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */
 /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
 
 (function() { // BEGIN LOCAL_SCOPE
@@ -24,28 +24,79 @@ var buttonConnected = false;
 var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
 var button = tablet.addButton({
     icon: "icons/tablet-icons/snap-i.svg",
+    activeIcon: "icons/tablet-icons/snap-a.svg",
     text: buttonName,
     sortOrder: 5
 });
 
-function shouldOpenFeedAfterShare() {
-    var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false"
-    return persisted && (persisted !== 'false');
+var snapshotOptions;
+var imageData = [];
+var storyIDsToMaybeDelete = [];
+var shareAfterLogin = false;
+var snapshotToShareAfterLogin;
+var METAVERSE_BASE = location.metaverseServerUrl;
+
+// It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story,
+// POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS
+function request(options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request.
+    var httpRequest = new XMLHttpRequest(), key;
+    // QT bug: apparently doesn't handle onload. Workaround using readyState.
+    httpRequest.onreadystatechange = function () {
+        var READY_STATE_DONE = 4;
+        var HTTP_OK = 200;
+        if (httpRequest.readyState >= READY_STATE_DONE) {
+            var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText,
+                response = !error && httpRequest.responseText,
+                contentType = !error && httpRequest.getResponseHeader('content-type');
+            if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc.
+                try {
+                    response = JSON.parse(response);
+                } catch (e) {
+                    error = e;
+                }
+            }
+            callback(error, response);
+        }
+    };
+    if (typeof options === 'string') {
+        options = { uri: options };
+    }
+    if (options.url) {
+        options.uri = options.url;
+    }
+    if (!options.method) {
+        options.method = 'GET';
+    }
+    if (options.body && (options.method === 'GET')) { // add query parameters
+        var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&';
+        for (key in options.body) {
+            params.push(key + '=' + options.body[key]);
+        }
+        options.uri += appender + params.join('&');
+        delete options.body;
+    }
+    if (options.json) {
+        options.headers = options.headers || {};
+        options.headers["Content-type"] = "application/json";
+        options.body = JSON.stringify(options.body);
+    }
+    for (key in options.headers || {}) {
+        httpRequest.setRequestHeader(key, options.headers[key]);
+    }
+    httpRequest.open(options.method, options.uri, true);
+    httpRequest.send(options.body);
 }
-function showFeedWindow() {
-    if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar"))
-            || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) {
-        tablet.loadQMLSource("TabletAddressDialog.qml");
+
+function openLoginWindow() {
+    if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false))
+        || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
+        Menu.triggerOption("Login / Sign Up");
     } else {
-         tablet.initialScreen("TabletAddressDialog.qml");
+        tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml");
         HMD.openTablet();
     }
 }
 
-var outstanding;
-var readyData;
-var shareAfterLogin = false;
-var snapshotToShareAfterLogin;     
 function onMessage(message) {
     // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following:
     // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.)
@@ -58,91 +109,257 @@ function onMessage(message) {
     }
 
     var isLoggedIn;
-    var needsLogin = false;
     switch (message.action) {
-        case 'ready':  // Send it.
+        case 'ready': // DOM is ready and page has loaded
             tablet.emitScriptEvent(JSON.stringify({
                 type: "snapshot",
-                action: readyData
+                action: "captureSettings",
+                setting: Settings.getValue("alsoTakeAnimatedSnapshot", true)
             }));
-            outstanding = 0;
+            if (Snapshot.getSnapshotsLocation() !== "") {
+                tablet.emitScriptEvent(JSON.stringify({
+                    type: "snapshot",
+                    action: "showPreviousImages",
+                    options: snapshotOptions,
+                    image_data: imageData,
+                    canShare: !isDomainOpen(Settings.getValue("previousSnapshotDomainID"))
+                }));
+            } else {
+                tablet.emitScriptEvent(JSON.stringify({
+                    type: "snapshot",
+                    action: "showSetupInstructions"
+                }));
+                Settings.setValue("previousStillSnapPath", "");
+                Settings.setValue("previousStillSnapStoryID", "");
+                Settings.setValue("previousStillSnapSharingDisabled", false);
+                Settings.setValue("previousAnimatedSnapPath", "");
+                Settings.setValue("previousAnimatedSnapStoryID", "");
+                Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+            }
+            break;
+        case 'chooseSnapshotLocation':
+            var snapshotPath = Window.browseDir("Choose Snapshots Directory", "", "");
+
+            if (snapshotPath) { // not cancelled
+                Snapshot.setSnapshotsLocation(snapshotPath);
+                tablet.emitScriptEvent(JSON.stringify({
+                    type: "snapshot",
+                    action: "snapshotLocationChosen"
+                }));
+            }
             break;
         case 'openSettings':
-            if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar"))
-                    || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) {
+            if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false))
+                || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) {
                 Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "General Preferences");
             } else {
                 tablet.loadQMLOnTop("TabletGeneralPreferences.qml");
             }
             break;
-        case 'setOpenFeedFalse':
-            Settings.setValue('openFeedAfterShare', false);
+        case 'captureStillAndGif':
+            print("Changing Snapshot Capture Settings to Capture Still + GIF");
+            Settings.setValue("alsoTakeAnimatedSnapshot", true);
             break;
-        case 'setOpenFeedTrue':
-            Settings.setValue('openFeedAfterShare', true);
+        case 'captureStillOnly':
+            print("Changing Snapshot Capture Settings to Capture Still Only");
+            Settings.setValue("alsoTakeAnimatedSnapshot", false);
+            break;
+        case 'takeSnapshot':
+            takeSnapshot();
+            break;
+        case 'shareSnapshotForUrl':
+            isLoggedIn = Account.isLoggedIn();
+            if (isLoggedIn) {
+                print('Sharing snapshot with audience "for_url":', message.data);
+                Window.shareSnapshot(message.data, message.href || href);
+            } else {
+                // TODO
+            }
+            break;
+        case 'blastToConnections':
+            isLoggedIn = Account.isLoggedIn();
+            storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
+            if (message.isGif) {
+                Settings.setValue("previousAnimatedSnapSharingDisabled", true);
+            } else {
+                Settings.setValue("previousStillSnapSharingDisabled", true);
+            }
+
+            if (isLoggedIn) {
+                print('Uploading new story for announcement!');
+
+                request({
+                    uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id,
+                    method: 'GET'
+                }, function (error, response) {
+                    if (error || (response.status !== 'success')) {
+                        print("ERROR getting details about existing snapshot story:", error || response.status);
+                        return;
+                    } else {
+                        var requestBody = {
+                            user_story: {
+                                audience: "for_connections",
+                                action: "announcement",
+                                path: response.user_story.path,
+                                place_name: response.user_story.place_name,
+                                thumbnail_url: response.user_story.thumbnail_url,
+                                // For historical reasons, the server doesn't take nested JSON objects.
+                                // Thus, I'm required to STRINGIFY what should be a nested object.
+                                details: JSON.stringify({
+                                    shareable_url: response.user_story.details.shareable_url,
+                                    image_url: response.user_story.details.image_url
+                                })
+                            }
+                        }
+                        request({
+                            uri: METAVERSE_BASE + '/api/v1/user_stories',
+                            method: 'POST',
+                            json: true,
+                            body: requestBody
+                        }, function (error, response) {
+                            if (error || (response.status !== 'success')) {
+                                print("ERROR uploading announcement story: ", error || response.status);
+                                if (message.isGif) {
+                                    Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+                                } else {
+                                    Settings.setValue("previousStillSnapSharingDisabled", false);
+                                }
+                                return;
+                            } else {
+                                print("SUCCESS uploading announcement story! Story ID:", response.user_story.id);
+                            }
+                        });
+                    }
+                });
+
+            } else {
+                openLoginWindow();
+            }
+            break;
+        case 'shareSnapshotWithEveryone':
+            isLoggedIn = Account.isLoggedIn();
+            storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
+            if (message.isGif) {
+                Settings.setValue("previousAnimatedSnapSharingDisabled", true);
+            } else {
+                Settings.setValue("previousStillSnapSharingDisabled", true);
+            }
+
+            if (isLoggedIn) {
+                print('Modifying audience of story ID', message.story_id, "to 'for_feed'");
+                var requestBody = {
+                    audience: "for_feed"
+                }
+
+                if (message.isAnnouncement) {
+                    requestBody.action = "announcement";
+                    print('...Also announcing!');
+                }
+                request({
+                    uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id,
+                    method: 'PUT',
+                    json: true,
+                    body: requestBody
+                }, function (error, response) {
+                    if (error || (response.status !== 'success')) {
+                        print("ERROR changing audience: ", error || response.status);
+                        if (message.isGif) {
+                            Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+                        } else {
+                            Settings.setValue("previousStillSnapSharingDisabled", false);
+                        }
+                        return;
+                    } else {
+                        print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!"));
+                    }
+                });
+            } else {
+                openLoginWindow();
+                shareAfterLogin = true;
+                snapshotToShareAfterLogin = { path: message.data, href: message.href || href };
+            }
+            break;
+        case 'shareButtonClicked':
+            print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].');
+            storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1);
+            print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete));
             break;
         default:
-            //tablet.webEventReceived.disconnect(onMessage);  // <<< It's probably this that's missing?!
-            HMD.closeTablet();
-            isLoggedIn = Account.isLoggedIn();
-            message.action.forEach(function (submessage) {
-                if (submessage.share && !isLoggedIn) {
-                    needsLogin = true;
-                    submessage.share = false;
-                    shareAfterLogin = true;
-                    snapshotToShareAfterLogin = {path: submessage.localPath, href: submessage.href || href};
-                }
-                if (submessage.share) {
-                    print('sharing', submessage.localPath);
-                    outstanding = true;
-                    Window.shareSnapshot(submessage.localPath, submessage.href || href);
-                } else {
-                    print('not sharing', submessage.localPath);
-                }
-                
-            });
-            if (outstanding && shouldOpenFeedAfterShare()) {
-                showFeedWindow();
-                outstanding = false;
-            }
-            if (needsLogin) { // after the possible feed, so that the login is on top
-                var isLoggedIn = Account.isLoggedIn();
-
-                if (!isLoggedIn) {
-                    if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar"))
-                        || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) {
-                        Menu.triggerOption("Login / Sign Up");
-                    } else {
-                        tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml");
-                        HMD.openTablet();
-                    }
-                }
-            }
+            print('Unknown message action received by snapshot.js!');
+            break;
     }
 }
 
 var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html");
 var isInSnapshotReview = false;
-function confirmShare(data) {
-    tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL);
-    readyData = data;
-    tablet.webEventReceived.connect(onMessage);
-    HMD.openTablet();
-    isInSnapshotReview = true;
+var shouldActivateButton = false;
+function onButtonClicked() {
+    if (isInSnapshotReview){
+        // for toolbar-mode: go back to home screen, this will close the window.
+        tablet.gotoHomeScreen();
+    } else {
+        shouldActivateButton = true;
+        var previousStillSnapPath = Settings.getValue("previousStillSnapPath");
+        var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID");
+        var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled");
+        var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath");
+        var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID");
+        var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled");
+        snapshotOptions = {
+            containsGif: previousAnimatedSnapPath !== "",
+            processingGif: false,
+            shouldUpload: false
+        }
+        imageData = [];
+        if (previousAnimatedSnapPath !== "") {
+            imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled });
+        }
+        if (previousStillSnapPath !== "") {
+            imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled });
+        }
+        tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL);
+        tablet.webEventReceived.connect(onMessage);
+        HMD.openTablet();
+        isInSnapshotReview = true;
+    }
 }
 
-function snapshotShared(errorMessage) {
-    if (!errorMessage) {
-        print('snapshot uploaded and shared');
+function snapshotUploaded(isError, reply) {
+    if (!isError) {
+        var replyJson = JSON.parse(reply);
+        var storyID = replyJson.user_story.id;
+        storyIDsToMaybeDelete.push(storyID);
+        var imageURL = replyJson.user_story.details.image_url;
+        var isGif = imageURL.split('.').pop().toLowerCase() === "gif";
+        print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID);
+        tablet.emitScriptEvent(JSON.stringify({
+            type: "snapshot",
+            action: "snapshotUploadComplete",
+            story_id: storyID,
+            image_url: imageURL,
+        }));
+        if (isGif) {
+            Settings.setValue("previousAnimatedSnapStoryID", storyID);
+        } else {
+            Settings.setValue("previousStillSnapStoryID", storyID);
+        }
     } else {
-        print(errorMessage);
-    }
-    if ((--outstanding <= 0) && shouldOpenFeedAfterShare()) {
-        showFeedWindow();
+        print(reply);
     }
 }
 var href, domainId;
-function onClicked() {
+function takeSnapshot() {
+    tablet.emitScriptEvent(JSON.stringify({
+        type: "snapshot",
+        action: "clearPreviousImages"
+    }));
+    Settings.setValue("previousStillSnapPath", "");
+    Settings.setValue("previousStillSnapStoryID", "");
+    Settings.setValue("previousStillSnapSharingDisabled", false);
+    Settings.setValue("previousAnimatedSnapPath", "");
+    Settings.setValue("previousAnimatedSnapStoryID", "");
+    Settings.setValue("previousAnimatedSnapSharingDisabled", false);
+
     // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving.
     // Turn it off now, before we start futzing with things (and possibly moving).
     clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy.
@@ -152,14 +369,25 @@ function onClicked() {
     // Even the domainId could change (e.g., if the user falls into a teleporter while recording).
     href = location.href;
     domainId = location.domainId;
+    Settings.setValue("previousSnapshotDomainID", domainId);
+
+    maybeDeleteSnapshotStories();
 
     // update button states
-    resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicke.
+    resetOverlays = Menu.isOptionChecked("Overlays"); // For completeness. Certainly true if the button is visible to be clicked.
     reticleVisible = Reticle.visible;
     Reticle.visible = false;
-    Window.stillSnapshotTaken.connect(stillSnapshotTaken);
-    Window.processingGifStarted.connect(processingGifStarted);
-    Window.processingGifCompleted.connect(processingGifCompleted);
+    
+    var includeAnimated = Settings.getValue("alsoTakeAnimatedSnapshot", true);
+    if (includeAnimated) {
+        Window.processingGifStarted.connect(processingGifStarted);
+    } else {
+        Window.stillSnapshotTaken.connect(stillSnapshotTaken);
+    }
+    if (buttonConnected) {
+        button.clicked.disconnect(onButtonClicked);
+        buttonConnected = false;
+    }
 
     // hide overlays if they are on
     if (resetOverlays) {
@@ -170,13 +398,17 @@ function onClicked() {
     Script.setTimeout(function () {
         HMD.closeTablet();
         Script.setTimeout(function () {
-            Window.takeSnapshot(false, true, 1.91);
+            Window.takeSnapshot(false, includeAnimated, 1.91);
         }, SNAPSHOT_DELAY);
     }, FINISH_SOUND_DELAY);
 }
 
 function isDomainOpen(id) {
-    var request = new XMLHttpRequest();
+    print("Checking open status of domain with ID:", id);
+    if (!id) {
+        return false;
+    }
+
     var options = [
         'now=' + new Date().toISOString(),
         'include_actions=concurrency',
@@ -184,15 +416,19 @@ function isDomainOpen(id) {
         'restriction=open,hifi' // If we're sharing, we're logged in
         // If we're here, protocol matches, and it is online
     ];
-    var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&');
-    request.open("GET", url, false);
-    request.send();
-    if (request.status !== 200) {
-        return false;
-    }
-    var response = JSON.parse(request.response); // Not parsed for us.
-    return (response.status === 'success') &&
-        response.total_entries;
+    var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&');
+
+    return request({
+        uri: url,
+        method: 'GET'
+    }, function (error, response) {
+        if (error || (response.status !== 'success')) {
+            print("ERROR getting open status of domain: ", error || response.status);
+            return false;
+        } else {
+            return response.total_entries;
+        }
+    });
 }
 
 function stillSnapshotTaken(pathStillSnapshot, notify) {
@@ -203,20 +439,30 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
         Menu.setIsOptionChecked("Overlays", true);
     }
     Window.stillSnapshotTaken.disconnect(stillSnapshotTaken);
+    if (!buttonConnected) {
+        button.clicked.connect(onButtonClicked);
+        buttonConnected = true;
+    }
 
     // A Snapshot Review dialog might be left open indefinitely after taking the picture,
     // during which time the user may have moved. So stash that info in the dialog so that
     // it records the correct href. (We can also stash in .jpegs, but not .gifs.)
     // last element in data array tells dialog whether we can share or not
-    var confirmShareContents = [
-        { localPath: pathStillSnapshot, href: href },
-        {
-            containsGif: false,
-            processingGif: false,
-            canShare: !!isDomainOpen(domainId),
-            openFeedAfterShare: shouldOpenFeedAfterShare()
-        }];
-    confirmShare(confirmShareContents);
+    snapshotOptions = {
+        containsGif: false,
+        processingGif: false,
+        canShare: !isDomainOpen(domainId)
+    };
+    imageData = [{ localPath: pathStillSnapshot, href: href }];
+    Settings.setValue("previousStillSnapPath", pathStillSnapshot);
+
+    tablet.emitScriptEvent(JSON.stringify({
+        type: "snapshot",
+        action: "addImages",
+        options: snapshotOptions,
+        image_data: imageData
+    }));
+
     if (clearOverlayWhenMoving) {
         MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
     }
@@ -225,8 +471,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) {
 
 function processingGifStarted(pathStillSnapshot) {
     Window.processingGifStarted.disconnect(processingGifStarted);
-    button.clicked.disconnect(onClicked);
-    buttonConnected = false;
+    Window.processingGifCompleted.connect(processingGifCompleted);
     // show hud
     Reticle.visible = reticleVisible;
     // show overlays if they were on
@@ -234,16 +479,22 @@ function processingGifStarted(pathStillSnapshot) {
         Menu.setIsOptionChecked("Overlays", true);
     }
 
-    var confirmShareContents = [
-        { localPath: pathStillSnapshot, href: href },
-        {
-            containsGif: true,
-            processingGif: true,
-            loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'),
-            canShare: !!isDomainOpen(domainId),
-            openFeedAfterShare: shouldOpenFeedAfterShare()
-        }];
-    confirmShare(confirmShareContents);
+    snapshotOptions = {
+        containsGif: true,
+        processingGif: true,
+        loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'),
+        canShare: !isDomainOpen(domainId)
+    };
+    imageData = [{ localPath: pathStillSnapshot, href: href }];
+    Settings.setValue("previousStillSnapPath", pathStillSnapshot);
+
+    tablet.emitScriptEvent(JSON.stringify({
+        type: "snapshot",
+        action: "addImages",
+        options: snapshotOptions,
+        image_data: imageData
+    }));
+
     if (clearOverlayWhenMoving) {
         MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog
     }
@@ -252,57 +503,72 @@ function processingGifStarted(pathStillSnapshot) {
 
 function processingGifCompleted(pathAnimatedSnapshot) {
     Window.processingGifCompleted.disconnect(processingGifCompleted);
-    button.clicked.connect(onClicked);
-    buttonConnected = true;
+    if (!buttonConnected) {
+        button.clicked.connect(onButtonClicked);
+        buttonConnected = true;
+    }
 
-    var confirmShareContents = [
-        { localPath: pathAnimatedSnapshot, href: href },
-        {
-            containsGif: true,
-            processingGif: false,
-            canShare: !!isDomainOpen(domainId),
-            openFeedAfterShare: shouldOpenFeedAfterShare()
-        }];
-    readyData = confirmShareContents;
+    snapshotOptions = {
+        containsGif: true,
+        processingGif: false,
+        canShare: !isDomainOpen(domainId)
+    }
+    imageData = [{ localPath: pathAnimatedSnapshot, href: href }];
+    Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot);
 
     tablet.emitScriptEvent(JSON.stringify({
         type: "snapshot",
-        action: readyData
+        action: "addImages",
+        options: snapshotOptions,
+        image_data: imageData
     }));
 }
-
+function maybeDeleteSnapshotStories() {
+    storyIDsToMaybeDelete.forEach(function (element, idx, array) {
+        request({
+            uri: METAVERSE_BASE + '/api/v1/user_stories/' + element,
+            method: 'DELETE'
+        }, function (error, response) {
+            if (error || (response.status !== 'success')) {
+                print("ERROR deleting snapshot story: ", error || response.status);
+                return;
+            } else {
+                print("SUCCESS deleting snapshot story with ID", element);
+            }
+        })
+    });
+    storyIDsToMaybeDelete = [];
+}
 function onTabletScreenChanged(type, url) {
+    button.editProperties({ isActive: shouldActivateButton });
+    shouldActivateButton = false;
     if (isInSnapshotReview) {
         tablet.webEventReceived.disconnect(onMessage);
         isInSnapshotReview = false;
     }
 }
-function onConnected() {
+function onUsernameChanged() {
     if (shareAfterLogin && Account.isLoggedIn()) {
-        print('sharing', snapshotToShareAfterLogin.path);
+        print('Sharing snapshot after login:', snapshotToShareAfterLogin.path);
         Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href);
         shareAfterLogin = false;
-        if (shouldOpenFeedAfterShare()) {
-            showFeedWindow();
-        }
     }
-    
 }
 
-button.clicked.connect(onClicked);
+button.clicked.connect(onButtonClicked);
 buttonConnected = true;
-Window.snapshotShared.connect(snapshotShared);
+Window.snapshotShared.connect(snapshotUploaded);
 tablet.screenChanged.connect(onTabletScreenChanged);
-Account.usernameChanged.connect(onConnected);
+Account.usernameChanged.connect(onUsernameChanged);
 Script.scriptEnding.connect(function () {
     if (buttonConnected) {
-        button.clicked.disconnect(onClicked);
+        button.clicked.disconnect(onButtonClicked);
         buttonConnected = false;
     }
     if (tablet) {
         tablet.removeButton(button);
     }
-    Window.snapshotShared.disconnect(snapshotShared);
+    Window.snapshotShared.disconnect(snapshotUploaded);
     tablet.screenChanged.disconnect(onTabletScreenChanged);
 });