diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ac52de0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+# rake standalone
+standalone/flying-focus.js
+
+# rake safari
+FlyingFocus.safariextension/flying-focus.css
+FlyingFocus.safariextension/flying-focus.js
diff --git a/FlyingFocus.safariextension/Icon-64.png b/FlyingFocus.safariextension/Icon-64.png
new file mode 100644
index 0000000..5bdca83
Binary files /dev/null and b/FlyingFocus.safariextension/Icon-64.png differ
diff --git a/FlyingFocus.safariextension/Info.plist b/FlyingFocus.safariextension/Info.plist
new file mode 100644
index 0000000..4c8dbd6
--- /dev/null
+++ b/FlyingFocus.safariextension/Info.plist
@@ -0,0 +1,54 @@
+
+
+
+
+ Author
+ Nikita Vasilyev
+ Builder Version
+ 9537.66
+ CFBundleDisplayName
+ Flying Focus
+ CFBundleIdentifier
+ com.n12v.flying-focus
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1.0
+ Chrome
+
+ Content
+
+ Scripts
+
+ End
+
+ flying-focus.js
+
+
+ Stylesheets
+
+ flying-focus.css
+
+
+ Description
+ Smooth focus transition
+ ExtensionInfoDictionaryVersion
+ 1.0
+ Permissions
+
+ Website Access
+
+ Include Secure Pages
+
+ Level
+ All
+
+
+ Update Manifest URL
+ http://n12v.com/focus-transition/update.plist
+ Website
+ http://n12v.com/focus-transition/
+
+
diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt
new file mode 100644
index 0000000..bc57aac
--- /dev/null
+++ b/MIT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) Nikita Vasilyev
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..84d7e96
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,22 @@
+# [Focus Transition](http://n12v.com/focus-transition/)
+
+
+
+Flying Focus is a UI concept.
+
+# How to build
+
+## A single-file library
+
+Create a flying-focus.js that can be included to any web page.
+It includes all necessary CSS and has no external dependencies.
+
+ rake standalone
+
+## Safari extension
+
+ rake safari
+
+## Chrome extension
+
+No build step required. Just load it as an unpacked extension from `chrome/`.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..287e626
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,28 @@
+task :default => [:safari, :standalone]
+
+desc 'Build standalone file'
+task :standalone => ['chrome/flying-focus.js', 'chrome/flying-focus.css'] do
+ require 'jspp'
+ File.open('standalone/flying-focus.js', 'w') { |file|
+ text = JSPP('standalone/flying-focus.jspp.js')
+ file.write(text)
+ }
+ puts 'standalone/flying-focus.js'
+end
+
+
+desc 'Build Safari extension to ./FlyingFocus.safariextension/'
+task :safari => ['chrome/flying-focus.js', 'chrome/flying-focus.css'] do
+ cp_r ['chrome/flying-focus.js', 'chrome/flying-focus.css'], 'FlyingFocus.safariextension'
+ puts 'FlyingFocus.safariextension'
+end
+
+#FIXME
+#task :firefox do
+#
+#end
+
+#FIXME
+#task :ie do
+#
+#end
diff --git a/chrome/flying-focus.css b/chrome/flying-focus.css
new file mode 100644
index 0000000..4cf379b
--- /dev/null
+++ b/chrome/flying-focus.css
@@ -0,0 +1,28 @@
+#flying-focus {
+ position: absolute;
+ margin: 0;
+ background: transparent;
+ -webkit-transition-property: left, top, width, height, opacity;
+ transition-property: left, top, width, height, opacity;
+ -webkit-transition-timing-function: cubic-bezier(0, 0.2, 0, 1);
+ transition-timing-function: cubic-bezier(0, 0.2, 0, 1);
+ visibility: hidden;
+ pointer-events: none;
+ box-shadow: 0 0 2px 3px #78aeda, 0 0 2px #78aeda inset; border-radius: 2px;
+}
+#flying-focus.flying-focus_visible {
+ visibility: visible;
+ z-index: 9999;
+}
+.flying-focus_target {
+ outline: none !important; /* Doens't work in Firefox :( */
+}
+
+/* Replace it with @supports rule when browsers catch up */
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+ #flying-focus {
+ box-shadow: none;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -3px;
+ }
+}
diff --git a/chrome/flying-focus.js b/chrome/flying-focus.js
new file mode 100644
index 0000000..0bea586
--- /dev/null
+++ b/chrome/flying-focus.js
@@ -0,0 +1,70 @@
+var flyingFocus = document.createElement('flying-focus'); // use uniq element name to decrease the chances of a conflict with website styles
+flyingFocus.id = 'flying-focus';
+document.body.appendChild(flyingFocus);
+
+var DURATION = 100;
+flyingFocus.style.transitionDuration = flyingFocus.style.WebkitTransitionDuration = DURATION / 1000 + 's';
+
+function offsetOf(elem) {
+ var rect = elem.getBoundingClientRect();
+ var docElem = document.documentElement;
+ var win = document.defaultView;
+ var body = document.body;
+
+ var clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ scrollTop = win.pageYOffset || docElem.scrollTop || body.scrollTop,
+ scrollLeft = win.pageXOffset || docElem.scrollLeft || body.scrollLeft,
+ top = rect.top + scrollTop - clientTop,
+ left = rect.left + scrollLeft - clientLeft;
+
+ return {top: top, left: left};
+}
+
+var movingId = 0;
+
+var prevFocused;
+
+document.documentElement.addEventListener('focus', function(event) {
+ var target = event.target;
+ if (target.id === 'flying-focus') {
+ return;
+ }
+ var offset = offsetOf(target);
+ flyingFocus.style.left = offset.left + 'px';
+ flyingFocus.style.top = offset.top + 'px';
+ flyingFocus.style.width = target.offsetWidth + 'px';
+ flyingFocus.style.height = target.offsetHeight + 'px';
+
+ // Would be nice to use:
+ //
+ // flyingFocus.style['outline-offset'] = getComputedStyle(target, null)['outline-offset']
+ //
+ // but it always '0px' in WebKit and Blink for some reason :(
+
+ if (prevFocused) {
+ target.classList.add('flying-focus_target');
+ show();
+ if (movingId) {
+ clearTimeout(movingId);
+ }
+ movingId = setTimeout(function() {
+ target.classList.remove('flying-focus_target');
+ hide();
+ }, DURATION);
+ }
+ prevFocused = target;
+}, true);
+
+document.documentElement.addEventListener('blur', function() {
+ hide();
+}, true);
+
+
+function hide() {
+ flyingFocus.classList.remove('flying-focus_visible');
+}
+
+function show() {
+ flyingFocus.classList.add('flying-focus_visible');
+}
diff --git a/chrome/icon_128.png b/chrome/icon_128.png
new file mode 100644
index 0000000..41d1177
Binary files /dev/null and b/chrome/icon_128.png differ
diff --git a/chrome/icon_48.png b/chrome/icon_48.png
new file mode 100644
index 0000000..34bdc72
Binary files /dev/null and b/chrome/icon_48.png differ
diff --git a/chrome/manifest.json b/chrome/manifest.json
new file mode 100644
index 0000000..9daa421
--- /dev/null
+++ b/chrome/manifest.json
@@ -0,0 +1,17 @@
+{
+ "manifest_version": 2,
+ "name": "Flying Focus",
+ "version": "1.0",
+ "description": "Smooth focus transition",
+ "content_scripts": [
+ {
+ "js": ["flying-focus.js"],
+ "css": ["flying-focus.css"],
+ "matches": [
+ ""
+ ]
+ }
+ ],
+ "author": "Nikita Vasilyev",
+ "homepage_url": "http://n12v.com/focus-transition/"
+}
diff --git a/standalone/flying-focus.jspp.js b/standalone/flying-focus.jspp.js
new file mode 100644
index 0000000..b652ca8
--- /dev/null
+++ b/standalone/flying-focus.jspp.js
@@ -0,0 +1,11 @@
+(function() {
+
+if (document.getElementById('flying-focus')) return;
+
+/*> ../chrome/flying-focus.js */
+
+var style = document.createElement('style');
+style.textContent = "/*> ../chrome/flying-focus.css */";
+document.body.appendChild(style);
+
+})();