diff --git a/js/ui/workspaces.js b/js/ui/workspaces.js
index 4f0ee0a..dc55c3c 100644
--- a/js/ui/workspaces.js
+++ b/js/ui/workspaces.js
@@ -26,6 +26,12 @@ WINDOWCLONE_TITLE_COLOR.from_pixel(0xffffffff);
 const FRAME_COLOR = new Clutter.Color();
 FRAME_COLOR.from_pixel(0xffffffff);
 
+const SCROLL_SCALE_AMOUNT = 100 / 5;
+
+// Overlay color for when zooming in.
+const OVERLAY_COLOR = new Clutter.Color();
+OVERLAY_COLOR.from_pixel(0x000000AA);
+
 // Define a layout scheme for small window counts. For larger
 // counts we fall back to an algorithm. We need more schemes here
 // unless we have a really good algorithm.
@@ -40,6 +46,19 @@ const POSITIONS = {
         5: [[0.165, 0.25, 0.32], [0.495, 0.25, 0.32], [0.825, 0.25, 0.32], [0.25, 0.75, 0.32], [0.75, 0.75, 0.32]]
 };
 
+// This is a global for now, it SHOULD be a Workspaces property
+// but I have no way of accessing that from the window.
+var _global_browseModeProps = {active: false, overlay: null, step: null};
+
+function _interpolate(start, end, step) {
+    return start + (end - start) * (step);
+}
+
+
+function _clamp(value, min, max) {
+    return Math.max(min, Math.min(max, value));
+}
+
 // Spacing between workspaces. At the moment, the same spacing is used
 // in both zoomed-in and zoomed-out views; this is slightly
 // metaphor-breaking, but the alternatives are also weird.
@@ -48,6 +67,37 @@ const FRAME_SIZE = GRID_SPACING / 3;
 
 let buttonSize = false;
 
+function ScaledPoint(x, y, scaleX, scaleY) {
+    [this.x, this.y, this.scaleX, this.scaleY] = arguments;
+}
+
+ScaledPoint.prototype = {
+    getPosition : function() {
+        return [this.x, this.y];
+    },
+
+    getScale : function() {
+        return [this.scaleX, this.scaleY];
+    },
+
+    setPosition : function(x, y) {
+        [this.x, this.y] = arguments;
+    },
+
+    setScale : function(scaleX, scaleY) {
+        [this.scaleX, this.scaleY] = arguments;
+    },
+
+    interpPosition : function(other, step) {
+        return [_interpolate(this.x, other.x, step), _interpolate(this.y, other.y, step)];
+    },
+
+    interpScale: function(other, step) {
+        return [_interpolate(this.scaleY, other.scaleX, step), _interpolate(this.scaleY, other.scaleY, step)];
+    }
+};
+
+
 function WindowClone(realWindow) {
     this._init(realWindow);
 }
@@ -67,16 +117,19 @@ WindowClone.prototype = {
         this.actor.connect('button-release-event',
                            Lang.bind(this, this._onButtonRelease));
 
+        this.actor.connect('scroll-event',
+                           Lang.bind(this, this._onScroll));
+
         this.actor.connect('enter-event',
                            Lang.bind(this, this._onEnter));
         this.actor.connect('leave-event',
                            Lang.bind(this, this._onLeave));
         this._havePointer = false;
 
         this._draggable = DND.makeDraggable(this.actor);
         this._draggable.connect('drag-begin', Lang.bind(this, this._onDragBegin));
         this._draggable.connect('drag-end', Lang.bind(this, this._onDragEnd));
         this._inDrag = false;
     },
 
     destroy: function () {
@@ -95,6 +150,9 @@ WindowClone.prototype = {
 
         actor.raise_top();
         this._updateTitle();
+
+        if (_global_browseModeProps.active)
+            this._zoomStart(this.actor);
     },
 
     _onLeave: function (actor, event) {
@@ -104,16 +162,114 @@ WindowClone.prototype = {
             return;
 
         this._havePointer = false;
+        this._updateTitle();
 
-        if (Tweener.isTweening(this.actor))
-            return;
+        if (this._zoomStep)
+            this._zoomEnd(this.actor, true);
+    },
+
+    _onScroll : function (actor, event) {
+        let direction = event.get_scroll_direction();
+        if (direction == Clutter.ScrollDirection.UP) {
+            if (this._zoomStep == undefined)
+                this._zoomStart(actor);
+            if (this._zoomStep < 100) {
+                this._zoomStep += SCROLL_SCALE_AMOUNT;
+                this._zoomUpdate(actor);
+            }
+        } else if (direction == Clutter.ScrollDirection.DOWN) {
+            if (this._zoomStep > 0) {
+                this._zoomStep -= SCROLL_SCALE_AMOUNT;
+                this._zoomStep = Math.max(0, this._zoomStep);
+                this._zoomUpdate(actor);
+            }
+            if (this._zoomStep <= 0.0)
+                this._zoomEnd(actor);
+        }
 
-    	actor.raise(this.stackAbove);
-        this._updateTitle();
+    },
+
+    _zoomUpdate : function (actor) {
+        [actor.x, actor.y]             = this._globalOrig.interpPosition(this._target, this._zoomStep / 100);
+        [actor.scale_x, actor.scale_y] = this._globalOrig.interpScale(this._target, this._zoomStep / 100);
+
+        let global = Shell.Global.get();
+        [width, height] = actor.get_transformed_size();
+
+        actor.x = _clamp(actor.x, 0, global.screen_width  - width);
+        actor.y = _clamp(actor.y, Panel.PANEL_HEIGHT, global.screen_height - height);
+    },
+
+    _zoomStart : function (actor) {
+        let global = Shell.Global.get();
+        let props  = _global_browseModeProps;
+        this._zoomOverlay = props.overlay || new Clutter.Rectangle({ reactive: true,
+                                                                     color: OVERLAY_COLOR,
+                                                                     border_width: 0,
+                                                                     x: 0, y: 0,
+                                                                     width: global.screen_width,
+                                                                     height: global.screen_height,
+                                                                     opacity: 255 });
+        this._zoomOverlay.show();
+        global.stage.add_actor(this._zoomOverlay);
+
+        this._localOrig  = new ScaledPoint(actor.x, actor.y, actor.scale_x, actor.scale_y);
+        this._globalOrig = new ScaledPoint();
+        let parent = this._origParent = actor.get_parent();
+        [width, height] = actor.get_transformed_size();
+        this._globalOrig.setPosition.apply(this._globalOrig, actor.get_transformed_position());
+        this._globalOrig.setScale(width / actor.width, height / actor.height);
+
+        this._zoomOverlay.raise_top();
+        this._zoomOverlay.show();
+
+        actor.reparent(global.stage);
+
+        [actor.x, actor.y]             = this._globalOrig.getPosition();
+        [actor.scale_x, actor.scale_y] = this._globalOrig.getScale();
+
+        actor.raise_top();
+
+        this._target = new ScaledPoint(0, 0, 1.0, 1.0);
+        this._target.setPosition(actor.x - (actor.width - width) / 2, actor.y - (actor.height - height) / 2);
+        this._zoomStep = props.step || 0;
+
+        this._hideEventId = Main.overview.connect('hidden', Lang.bind(this, function (a, e) { this._zoomEnd(this.actor); }));
+        this._zoomUpdate(actor);
+    },
+
+    _zoomEnd : function (actor, browseMode) {
+        actor.reparent(this._origParent);
+
+        [actor.x, actor.y]             = this._localOrig.getPosition();
+        [actor.scale_x, actor.scale_y] = this._localOrig.getScale();
+
+        this._adjustTitle();
+
+        if (browseMode) {
+            this._zoomOverlay.hide();
+            _global_browseModeProps.active  = true;
+            _global_browseModeProps.step    = this._zoomStep;
+            _global_browseModeProps.overlay = this._zoomOverlay;
+        } else {
+            _global_browseModeProps.active  = false;
+            _global_browseModeProps.step    = null;
+            _global_browseModeProps.overlay = null;
+            this._zoomOverlay.destroy();
+            Main.overview.disconnect(this._hideEventId);
+        }
+        this._localPosition  = undefined;
+        this._localScale     = undefined;
+        this._globalPosition = undefined;
+        this._globalScale    = undefined;
+        this._targetPosition = undefined;
+        this._zoomStep       = undefined;
+        this._zoomOverlay    = undefined;
     },
 
     _onButtonRelease : function (actor, event) {
         this.emit('selected', event.get_time());
+        _global_browseModeProps.active = false;
     },
 
     _onDragBegin : function (draggable, time) {
