Add iD editor
authorTom MacWright <tom@macwright.org>
Mon, 1 Apr 2013 21:19:15 +0000 (17:19 -0400)
committerJohn Firebaugh <john.firebaugh@gmail.com>
Fri, 3 May 2013 17:52:41 +0000 (10:52 -0700)
iD sources live in vendor/assets/iD, and are managed with
vendorer, using the "rewrite" functionality to replace relative
paths in the CSS with <%= asset_path(…) %> directives.

To avoid needing to namespace all of its CSS classes, iD
is embedded via an iframe.

To setup, edit application.yml to include an `id_key` key.
(It can use the same consumer key as P2 if you like.)

59 files changed:
Vendorfile
app/assets/stylesheets/common.css.scss
app/controllers/site_controller.rb
app/views/site/_id.html.erb [new file with mode: 0644]
app/views/site/id_iframe.html.erb [new file with mode: 0644]
config/environments/production.rb
config/example.application.yml
config/locales/en.yml
config/routes.rb
lib/editors.rb
vendor/assets/iD/iD.css.erb [new file with mode: 0644]
vendor/assets/iD/iD.js [new file with mode: 0644]
vendor/assets/iD/iD/img/background-pattern-1.png [new file with mode: 0644]
vendor/assets/iD/iD/img/background-pattern-opacity.png [new file with mode: 0644]
vendor/assets/iD/iD/img/bing_maps.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-draw-connect-line.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-draw-connect-line2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-draw-connect-vertex.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-draw-connect-vertex2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-draw.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-draw2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-grab.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-grab2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-grabbing.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-grabbing2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-pointer.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-pointer2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-pointing.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-pointing2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-acting.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-acting2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-add.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-add2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-area.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-area2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-line.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-line2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-point.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-point2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-remove.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-remove2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-split.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-split2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-vertex.png [new file with mode: 0644]
vendor/assets/iD/iD/img/cursor-select-vertex2x.png [new file with mode: 0644]
vendor/assets/iD/iD/img/feature-icons.png [new file with mode: 0644]
vendor/assets/iD/iD/img/loader-black.gif [new file with mode: 0644]
vendor/assets/iD/iD/img/loader-white.gif [new file with mode: 0644]
vendor/assets/iD/iD/img/loader_bg.gif [new file with mode: 0644]
vendor/assets/iD/iD/img/logo.png [new file with mode: 0644]
vendor/assets/iD/iD/img/mini-loader.gif [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/cemetery.png [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/construction.png [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/dots.png [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/farmland.png [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/orchard.png [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/vineyard.png [new file with mode: 0644]
vendor/assets/iD/iD/img/pattern/wetland.png [new file with mode: 0644]
vendor/assets/iD/iD/img/sprite.svg [new file with mode: 0644]

index 02d9fdd..e67cd16 100644 (file)
@@ -37,4 +37,14 @@ folder 'vendor/assets' do
       file 'ohauth.js'
     end
   end
+
+  folder 'iD' do
+    from 'git://github.com/systemed/iD', :branch => 'embed' do
+      folder 'iD/img', 'dist/img'
+      file 'iD.css.erb', 'dist/iD.css' do |path|
+        rewrite(path) { |content| content.gsub(/url\('?(img\/[^')]+)'?\)/, 'url(<%= asset_path("iD/\1") %>)') }
+      end
+      file 'iD.js', 'dist/iD.js'
+    end
+  end
 end
index b2a2374..d94f511 100644 (file)
@@ -1784,3 +1784,11 @@ a.button.submit {
     text-align: right;
   }
 }
+
+/*
+ * Rules for the iD editor
+ */
+.id-embed {
+  width: 100%;
+  height: 100%;
+}
index 1ea3f7c..744b653 100644 (file)
@@ -65,4 +65,8 @@ class SiteController < ApplicationController
   def preview
     render :text => RichText.new(params[:format], params[:text]).to_html
   end
+
+  def id_iframe
+    render "id_iframe", :layout => false
+  end
 end
diff --git a/app/views/site/_id.html.erb b/app/views/site/_id.html.erb
new file mode 100644 (file)
index 0000000..680c45c
--- /dev/null
@@ -0,0 +1,34 @@
+<% if defined? ID_KEY %>
+<iframe frameBorder="0" id="id-embed" class="id-embed"></iframe>
+<% token = @user.access_token(ID_KEY) %>
+<script>
+var keys = {
+    '<%= request.protocol + request.host_with_port %>': {
+        oauth_consumer_key: "<%= token.client_application.key %>",
+        oauth_secret: "<%= token.client_application.secret %>",
+        oauth_token: "<%= token.token %>",
+        oauth_token_secret: "<%= token.secret %>"
+    }
+};
+
+var coord = {};
+<% if @lat && @lon && @zoom -%>
+coord.lat = <%= @lat %>;
+coord.lon = <%= @lon %>;
+coord.zoom = <%= @zoom %>;
+<% else -%>
+var params = OSM.mapParams();
+coord.lat = params.lat;
+coord.lon = params.lon;
+coord.zoom = params.zoom;
+<% end -%>
+var url = 'id_iframe#map=' + coord.zoom + '/' + coord.lon + '/' + coord.lat +
+    '&preauth=' + JSON.stringify(keys);
+$('#id-embed').attr('src', url);
+</script>
+<% else%>
+<script type="text/javascript">alert("<%= t 'site.edit.id_not_configured' %>")</script>
+<% end %>
+
+<script type="text/javascript">
+</script>
diff --git a/app/views/site/id_iframe.html.erb b/app/views/site/id_iframe.html.erb
new file mode 100644 (file)
index 0000000..0422446
--- /dev/null
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset='utf-8'>
+  <%= stylesheet_link_tag 'iD' %>
+  <!--[if !IE || gte IE 9]><!-->
+  <%= javascript_include_tag 'iD' %>
+  <!-- <![endif]-->
+</head>
+<body>
+<div id='id-container'></div>
+<script>
+  if (typeof iD == 'undefined') {
+    document.getElementById('id-container').innerHTML = 'This editor is supported ' +
+      'in Firefox, Chrome, Safari, Opera, and Internet Explorer 9 and above. ' +
+      'Please upgrade your browser or use Potlatch 2 to edit the map.';
+    document.getElementById('id-container').className = 'unsupported';
+  } else {
+    var qs = iD.util.stringQs(location.hash);
+    if (qs.preauth) {
+      var preauth = JSON.parse(qs.preauth);
+
+      var id = iD()
+        .embed(true)
+        .imagePath("/assets/iD/img/"); <%# Can't use asset_path('id/img/') in production. %>
+
+      id.connection()
+        .url(Object.keys(preauth)[0])
+        .keys(preauth);
+
+      d3.select('#id-container')
+        .call(id.ui());
+    }
+  }
+</script>
+</body>
+</html>
index d7689ca..6486be0 100644 (file)
@@ -62,6 +62,7 @@ OpenStreetMap::Application.configure do
   config.assets.precompile += %w( large-rtl.css small-rtl.css print-rtl.css )
   config.assets.precompile += %w( browse.css leaflet-all.css leaflet.ie.css )
   config.assets.precompile += %w( embed.js embed.css )
+  config.assets.precompile += %w( iD.js iD.css )
 
   # Disable delivery errors, bad email addresses will be ignored
   # config.action_mailer.raise_delivery_errors = false
index 30f367f..9a6d83a 100644 (file)
@@ -78,6 +78,8 @@ defaults: &defaults
   #potlatch2_key: ""
   # OAuth consumer key for the web site
   #oauth_key: ""
+  # OAuth consumer key for iD
+  #id_key: ""
   # Whether to require users to view the CTs before continuing to edit...
   require_terms_seen: false
   # Whether to require users to agree to the CTs before editing
index 67ef5d0..0186d44 100644 (file)
@@ -85,6 +85,9 @@ en:
     potlatch:
       name: "Potlatch 1"
       description: "Potlatch 1 (in-browser editor)"
+    id:
+      name: "iD"
+      description: "iD (in-browser editor)"
     potlatch2:
       name: "Potlatch 2"
       description: "Potlatch 2 (in-browser editor)"
@@ -1312,6 +1315,7 @@ en:
       potlatch_unsaved_changes: "You have unsaved changes. (To save in Potlatch, you should deselect the current way or point, if editing in live mode, or click save if you have a save button.)"
       potlatch2_not_configured: "Potlatch 2 has not been configured - please see http://wiki.openstreetmap.org/wiki/The_Rails_Port#Potlatch_2 for more information"
       potlatch2_unsaved_changes: "You have unsaved changes. (To save in Potlatch 2, you should click save.)"
+      id_not_configured: "iD has not been configured"
       no_iframe_support: "Your browser doesn't support HTML iframes, which are necessary for this feature."
     sidebar:
       search_results: Search Results
index a4a8faf..99dca52 100644 (file)
@@ -131,6 +131,7 @@ OpenStreetMap::Application.routes.draw do
   match '/logout' => 'user#logout', :via => [:get, :post]
   match '/offline' => 'site#offline', :via => :get
   match '/key' => 'site#key', :via => :get
+  match '/id_iframe' => 'site#id_iframe', :via => :get
   match '/user/new' => 'user#new', :via => :get
   match '/user/terms' => 'user#terms', :via => [:get, :post]
   match '/user/save' => 'user#save', :via => :post
index 3b5de3d..101a6f8 100644 (file)
@@ -1,4 +1,4 @@
 module Editors 
-  ALL_EDITORS = [ "potlatch", "potlatch2", "remote" ]
-  RECOMMENDED_EDITORS = [ "potlatch2", "remote" ]
+  ALL_EDITORS = [ "potlatch", "potlatch2", "id", "remote" ]
+  RECOMMENDED_EDITORS = [ "id", "potlatch2", "remote" ]
 end
diff --git a/vendor/assets/iD/iD.css.erb b/vendor/assets/iD/iD.css.erb
new file mode 100644 (file)
index 0000000..323b2ba
--- /dev/null
@@ -0,0 +1,4320 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+   v2.0 | 20110126
+   License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+       margin: 0;
+       padding: 0;
+       border: 0;
+       font-size: 100%;
+       font: inherit;
+       vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+       display: block;
+}
+body {
+       line-height: 1;
+}
+ol, ul {
+       list-style: none;
+}
+blockquote, q {
+       quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+       content: '';
+       content: none;
+}
+table {
+       border-collapse: collapse;
+       border-spacing: 0;
+}
+a { text-decoration: none;}
+/*
+ * 1. Corrects font family not being inherited in all browsers.
+ * 2. Corrects font size not being inherited in all browsers.
+ * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome
+ */
+
+button,
+input,
+select,
+textarea {
+    font-family: inherit; /* 1 */
+    font-size: 100%; /* 2 */
+    margin: 0; /* 3 */
+    padding: 0;
+}
+
+/*
+ * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+    line-height: normal;
+}
+
+/* Hide default number spinner controls */
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+display: none;
+}
+
+/*
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Corrects inability to style clickable `input` types in iOS.
+ * 3. Improves usability and consistency of cursor style between image-type
+ *    `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+    -webkit-appearance: button; /* 2 */
+    cursor: pointer; /* 3 */
+}
+
+/*
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+input[disabled] {
+    cursor: default;
+}
+
+/*
+ * 1. Addresses box sizing set to `content-box` in IE 8/9.
+ * 2. Removes excess padding in IE 8/9.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+    box-sizing: border-box; /* 1 */
+    padding: 0; /* 2 */
+}
+
+/*
+ * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome
+ *    (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+    -webkit-appearance: textfield; /* 1 */
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box; /* 2 */
+    box-sizing: border-box;
+}
+
+/*
+ * Removes inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+}
+
+/*
+ * Removes inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+    border: 0;
+    padding: 0;
+}
+
+/*
+** Markup free clearing
+** Details: http://www.positioniseverything.net/easyclearing.html
+*/
+.cf:before,
+.cf:after {
+    content: " "; /* 1 */
+    display: table; /* 2 */
+}
+
+.cf:after {
+    clear: both;
+}
+/* tiles */
+img.tile {
+    position:absolute;
+    transform-origin:0 0;
+    -ms-transform-origin:0 0;
+    -webkit-transform-origin:0 0;
+    -moz-transform-origin:0 0;
+    -o-transform-origin:0 0;
+
+    opacity: 0;
+
+    -webkit-transition: opacity 200ms linear;
+    transition: opacity 200ms linear;
+    -moz-transition: opacity 200ms linear;
+}
+
+img.tile-loaded {
+    opacity: 1;
+}
+
+/* base styles */
+path {
+    fill: none;
+}
+
+use {
+    pointer-events: none;
+}
+
+g.point .shadow,
+g.vertex .shadow,
+g.midpoint .shadow {
+    pointer-events: all;
+}
+
+path.shadow {
+    pointer-events: stroke;
+}
+
+.shadow {
+    -webkit-transition: 200ms;
+       -moz-transition: 200ms;
+            transition: 200ms;
+}
+
+/* points */
+
+g.point .stroke {
+    stroke: #444;
+    stroke-width: 1;
+    fill: #fff;
+}
+
+g.point .shadow {
+    fill: none;
+    stroke: #f6634f;
+    stroke-width: 8;
+    stroke-opacity: 0;
+}
+
+.behavior-hover g.point.hover:not(.selected) .shadow {
+    stroke-opacity: 0.5;
+}
+
+g.point.selected .shadow {
+    stroke-opacity: 0.7;
+}
+
+g.point.active, g.point.active * {
+    pointer-events: none;
+}
+
+/* vertices and midpoints */
+
+g.vertex .fill {
+    fill: none;
+}
+
+g.vertex .stroke {
+    stroke: #666;
+    stroke-width: 1;
+    fill: white;
+}
+
+g.vertex.shared .stroke {
+    fill: #aaa;
+}
+
+g.vertex.tagged .fill {
+    fill: #000;
+}
+
+g.midpoint .fill {
+    fill: #ddd;
+    stroke: black;
+    stroke-opacity: .5;
+    opacity: .5;
+}
+
+g.vertex .shadow,
+g.midpoint .shadow {
+    fill: #f6634f;
+    fill-opacity: 0;
+}
+
+.behavior-hover g.vertex.hover:not(.selected) .shadow,
+.behavior-hover g.midpoint.hover:not(.selected) .shadow {
+    fill-opacity: 0.3;
+}
+
+g.vertex.selected .shadow {
+    fill-opacity: 0.5;
+}
+
+.mode-draw-area g.midpoint,
+.mode-draw-line g.midpoint,
+.mode-add-area g.midpoint,
+.mode-add-line g.midpoint,
+.mode-add-point g.midpoint,
+.mode-drag-node g.midpoint {
+  display: none;
+}
+
+/* lines */
+
+path.line {
+    stroke-linecap: round;
+    stroke-linejoin: bevel;
+}
+
+path.stroke {
+    stroke: black;
+    stroke-width: 4;
+}
+
+path.shadow {
+    stroke: #f6634f;
+    stroke-width: 10;
+    stroke-opacity: 0;
+}
+
+.behavior-hover path.shadow.hover:not(.selected) {
+    stroke-opacity: 0.3;
+}
+
+path.shadow.selected {
+    stroke-opacity: 0.7;
+}
+
+path.area.stroke,
+path.line.member-type-multipolygon.stroke {
+    stroke-width:2;
+}
+path.area.stroke.selected,
+path.line.member-type-multipolygon.stroke.selected {
+    stroke-width:4 !important;
+}
+
+path.area.stroke {
+    stroke:#fff;
+}
+path.area.fill {
+    fill:#fff;
+    fill-opacity:0.3;
+    fill-rule: evenodd;
+}
+
+path.line.stroke {
+    stroke: white;
+    stroke-width: 2;
+}
+
+path.stroke.tag-natural {
+    stroke: #b6e199;
+    stroke-width:1;
+}
+path.fill.tag-natural {
+    fill: #b6e199;
+}
+
+path.stroke.tag-natural-water {
+    stroke: #77d3de;
+}
+path.fill.tag-natural-water {
+    fill: #77d3de;
+}
+
+path.stroke.tag-building {
+    stroke: #e06e5f;
+    stroke-width: 1;
+}
+path.fill.tag-building {
+    fill: #e06e5f;
+}
+
+/* Landuse */
+
+
+path.stroke.tag-landuse,
+path.stroke.tag-natural-wood,
+path.stroke.tag-natural-tree,
+path.stroke.tag-natural-grassland,
+path.stroke.tag-leisure-park {
+    stroke: #8cd05f;
+    stroke-width: 1;
+}
+
+path.stroke.tag-landuse-residential {
+    stroke: #e06e5f;
+}
+
+path.stroke.tag-landuse-retail,
+path.stroke.tag-landuse-commercial {
+    stroke: #eab056;
+}
+
+path.stroke.tag-landuse-industrial {
+    stroke: #e4a4f5;
+}
+
+path.stroke.tag-landuse-basin,
+path.stroke.tag-landuse-reservoir {
+    stroke: #77d3de;
+}
+
+path.stroke.tag-landuse-quarry {
+    stroke: #a6957b;
+}
+
+path.stroke.tag-landuse-residential,
+path.stroke.tag-landuse-construction {
+    stroke: #e06e5f;
+}
+
+path.stroke.tag-landuse-meadow,
+path.stroke.tag-natural-wetland {
+    stroke: #b6e199;
+}
+
+path.stroke.tag-natural-beach {
+    stroke: #ffff7e;
+}
+
+path.stroke.tag-natural-scrub {
+    stroke: #dbf08b;
+}
+
+path.fill.tag-landuse,
+path.fill.tag-natural-wood,
+path.fill.tag-natural-tree,
+path.fill.tag-natural-grassland,
+path.fill.tag-natural-grass,
+path.fill.tag-leisure-park {
+    fill: #8cd05f;
+    fill-opacity: 0.2;
+}
+
+path.fill.tag-landuse-retail,
+path.fill.tag-landuse-residential,
+path.fill.tag-landuse-commercial,
+path.fill.tag-landuse-industrial {
+    fill-opacity: 0.1;
+}
+
+path.fill.tag-natural-wetland,
+path.fill.tag-natural-beach,
+path.fill.tag-natural-scrub,
+path.fill.tag-landuse-cemetery,
+path.fill.tag-landuse-meadow,
+path.fill.tag-landuse-farm,
+path.fill.tag-landuse-farmland,
+path.fill.tag-landuse-construction,
+path.fill.tag-landuse-orchard {
+    /* background color is applied a further opacity later */
+    fill-opacity: 0.8;
+}
+
+.pattern-color-beach,
+.pattern-color-scrub,
+.pattern-color-meadow,
+.pattern-color-wetland,
+.pattern-color-cemetery,
+.pattern-color-farm,
+.pattern-color-farmland,
+.pattern-color-construction,
+.pattern-color-orchard {
+    fill-opacity: 0.2;
+}
+
+path.fill.tag-landuse-basin,
+path.fill.tag-landuse-reservoir {
+    fill: #77d3de;
+}
+
+path.fill.tag-landuse-quarry {
+    fill: #a6957b;
+}
+
+path.fill.tag-landuse-residential {
+    fill: #e06e5f;
+}
+
+path.fill.tag-landuse-farm,
+path.fill.tag-landuse-farmland {
+    fill: url(#pattern-farmland) #8cd05f;
+}
+
+.pattern-color-farm,
+.pattern-color-farmland {
+    fill: url(#pattern-farmland) #8cd05f;
+}
+
+path.fill.tag-landuse-meadow {
+    fill: url(#pattern-meadow) #b6e199;
+}
+.pattern-color-meadow {
+    fill: #b6e199;
+}
+
+path.fill.tag-natural-wetland {
+    fill: url(#pattern-wetland) #b6e199;
+}
+.pattern-color-wetland {
+    fill: #B6E199;
+}
+
+path.fill.tag-natural-beach {
+    fill: url(#pattern-beach) #ffff7e;
+}
+.pattern-color-beach {
+    fill: #ffff7e;
+}
+
+path.fill.tag-natural-scrub {
+    fill: url(#pattern-scrub) #dbf08b;
+}
+.pattern-color-scrub {
+    fill: #dbf08b;
+}
+
+path.fill.tag-landuse-cemetery {
+    fill: url(#pattern-cemetery) #8cd05f;
+}
+.pattern-color-cemetery {
+    fill: #8cd05f
+}
+
+path.fill.tag-landuse-orchard {
+    fill: url(#pattern-orchard) #8cd05f;
+}
+.pattern-color-orchard {
+    fill: #8cd05f
+}
+
+path.fill.tag-landuse-construction {
+    fill: url(#pattern-construction) #e06e5f;
+}
+.pattern-color-construction {
+    fill: #e06e5f;
+}
+
+path.fill.tag-landuse-retail,
+path.fill.tag-landuse-commercial {
+    fill: #eab056;
+}
+
+path.fill.tag-landuse-industrial {
+    fill: #e4a4f5;
+}
+
+path.stroke.tag-amenity-parking {
+    stroke: #aaa;
+    stroke-width: 1;
+}
+path.fill.tag-amenity-parking {
+    fill: #aaa;
+}
+
+path.fill.tag-place,
+path.fill.tag-boundary {
+    fill: none;
+}
+
+/* highways */
+
+path.shadow.tag-highway {
+    stroke-width:16;
+}
+path.casing.tag-highway {
+    stroke:#444;
+    stroke-width:10;
+}
+path.stroke.tag-highway {
+    stroke:#ccc;
+    stroke-width:8;
+}
+
+svg[data-zoom="16"] path.shadow.tag-highway {
+    stroke-width:12;
+}
+svg[data-zoom="16"] path.casing.tag-highway {
+    stroke-width:6;
+}
+svg[data-zoom="16"] path.stroke.tag-highway {
+    stroke-width:4;
+}
+
+path.stroke.tag-highway-motorway,
+path.stroke.tag-highway-motorway_link,
+path.stroke.tag-construction-motorway {
+    stroke:#58a9ed;
+}
+
+path.casing.tag-highway-motorway,
+path.casing.tag-highway-motorway_link,
+path.casing.tag-construction-motorway {
+    stroke:#2c5476;
+}
+
+path.stroke.tag-highway-trunk,
+path.stroke.tag-highway-trunk_link,
+path.stroke.tag-construction-trunk {
+    stroke:#8cd05f;
+}
+path.casing.tag-highway-trunk,
+path.casing.tag-highway-trunk_link,
+path.casing.tag-construction-trunk {
+    stroke:#46682f;
+}
+
+path.stroke.tag-highway-primary,
+path.stroke.tag-highway-primary_link,
+path.stroke.tag-construction-primary {
+    stroke:#e06d5f;
+}
+path.casing.tag-highway-primary,
+path.casing.tag-highway-primary_link,
+path.casing.tag-construction-primary {
+    stroke:#70372f;
+}
+
+path.stroke.tag-highway-secondary,
+path.stroke.tag-highway-secondary_link,
+path.stroke.tag-construction-secondary {
+    stroke:#eab056;
+}
+path.casing.tag-highway-secondary,
+path.casing.tag-highway-secondary_link,
+path.casing.tag-construction-secondary {
+    stroke:#75582b;
+}
+
+path.stroke.tag-highway-tertiary,
+path.stroke.tag-highway-tertiary_link,
+path.stroke.tag-construction-tertiary {
+    stroke:#ffff7e;
+}
+path.casing.tag-highway-tertiary,
+path.casing.tag-highway-tertiary_link,
+path.casing.tag-construction-tertiary {
+    stroke:#7f7f3f;
+}
+
+path.stroke.tag-highway-unclassified,
+path.stroke.tag-construction-unclassified {
+    stroke:#eaeaea;
+}
+path.casing.tag-highway-unclassified,
+path.casing.tag-construction-unclassified {
+    stroke:#444;
+}
+
+path.stroke.tag-highway-residential,
+path.stroke.tag-construction-residential {
+    stroke:#fff;
+}
+path.casing.tag-highway-residential,
+path.casing.tag-construction-residential {
+    stroke:#444;
+}
+
+path.stroke.tag-highway-living_street {
+    stroke:#ccc;
+    stroke-width:4;
+}
+path.casing.tag-highway-living_street {
+    stroke:#fff;
+    stroke-width:6;
+}
+
+path.stroke.line.tag-highway-pedestrian {
+    stroke:#fff;
+    stroke-dasharray: 2, 8;
+    stroke-width:4 !important;
+    shapeRendering: auto;
+}
+path.casing.line.tag-highway-pedestrian {
+    stroke:#8cd05f;
+    stroke-width:6 !important;
+}
+path.stroke.area.tag-highway-pedestrian {
+    stroke:#fff;
+    stroke-width: 2;
+}
+path.fill.area.tag-highway-pedestrian {
+    fill:#ccc;
+}
+
+path.stroke.tag-highway-service {
+    stroke:#fff;
+    stroke-width:4;
+}
+path.casing.tag-highway-service {
+    stroke:#666;
+    stroke-width:6;
+}
+svg[data-zoom="16"] path.stroke.tag-highway-service {
+    stroke-width:2;
+}
+svg[data-zoom="16"] path.casing.tag-highway-service {
+    stroke-width:4;
+}
+
+path.stroke.tag-highway-track {
+    stroke: #fff;
+    stroke-width: 4;
+}
+path.casing.tag-highway-track {
+    stroke: #996600;
+    stroke-width: 6;
+    stroke-linecap: butt;
+    stroke-dasharray: 6, 6;
+}
+svg[data-zoom="16"] path.stroke.tag-highway-track {
+    stroke-width:2;
+}
+svg[data-zoom="16"] path.casing.tag-highway-track {
+    stroke-width:4;
+}
+
+path.stroke.tag-highway-path {
+    stroke: #000;
+    stroke-width: 1 !important;
+    stroke-linecap: butt;
+    stroke-dasharray: 8, 4;
+}
+path.casing.tag-highway-path {
+    stroke-width: 1 !important;
+    stroke: #fff;
+}
+
+path.stroke.tag-highway-footway,
+path.stroke.tag-highway-cycleway,
+path.stroke.tag-highway-bridleway {
+    stroke-width: 4;
+    stroke-linecap: butt;
+    stroke-dasharray: 6, 6;
+}
+path.casing.tag-highway-footway,
+path.casing.tag-highway-cycleway,
+path.casing.tag-highway-bridleway {
+    stroke-width: 6;
+    stroke: #fff;
+}
+
+svg[data-zoom="16"] path.stroke.tag-highway-footway,
+svg[data-zoom="16"] path.stroke.tag-highway-cycleway,
+svg[data-zoom="16"] path.stroke.tag-highway-bridleway {
+    stroke-width: 2;
+}
+svg[data-zoom="16"] path.casing.tag-highway-footway,
+svg[data-zoom="16"] path.casing.tag-highway-cycleway,
+svg[data-zoom="16"] path.casing.tag-highway-bridleway {
+    stroke-width: 4;
+}
+
+path.stroke.tag-highway-footway {
+    stroke: #ae8681;
+}
+path.stroke.tag-highway-cycleway {
+    stroke: #58a9ed;
+}
+path.stroke.tag-highway-bridleway {
+    stroke: #e06d5f;
+}
+
+path.stroke.tag-highway-steps {
+    stroke: #81d25c;
+    stroke-width: 4;
+    stroke-linecap: butt;
+    stroke-dasharray: 3, 3;
+}
+path.casing.tag-highway-steps {
+    stroke-width: 6;
+    stroke: #fff;
+}
+
+/* aeroways */
+
+path.stroke.tag-aeroway-taxiway {
+    stroke: #805C80;
+    stroke-width: 4;
+}
+path.shadow.tag-aeroway-runway {
+    stroke-width: 20;
+}
+path.stroke.tag-aeroway-runway {
+    stroke: #fff;
+    stroke-width: 2;
+    stroke-linecap: butt;
+    stroke-dasharray: 24, 48;
+}
+path.casing.tag-aeroway-runway {
+    stroke-width: 10;
+    stroke: #000;
+    stroke-linecap: square;
+}
+path.fill.tag-aeroway-runway {
+    fill: #000;
+    fill-opacity: 0.6;
+}
+path.stroke.tag-aeroway-apron {
+    stroke: #805C80;
+}
+path.fill.tag-aeroway-apron {
+    fill: #805C80;
+    fill-opacity: 0.2;
+}
+
+
+/* bridges */
+
+path.casing.tag-bridge-yes {
+    stroke-width: 14;
+    stroke-opacity: 0.5;
+    stroke: #000;
+}
+
+path.casing.tag-highway-living_street.tag-bridge-yes,
+path.casing.tag-highway-path.tag-bridge-yes {
+    stroke-width: 6;
+}
+
+path.casing.line.tag-highway-pedestrian,
+path.casing.tag-highway-service.tag-bridge-yes,
+path.casing.tag-highway-track.tag-bridge-yes,
+path.casing.tag-highway-steps.tag-bridge-yes,
+path.casing.tag-highway-footway.tag-bridge-yes,
+path.casing.tag-highway-cycleway.tag-bridge-yes,
+path.casing.tag-highway-bridleway.tag-bridge-yes {
+    stroke-width: 8;
+}
+
+path.shadow.tag-highway-residential.tag-bridge {
+    stroke-width:22;
+}
+
+path.shadow.tag-highway-living_street.tag-bridge-yes,
+path.shadow.tag-highway-path.tag-bridge-yes,
+path.shadow.line.tag-highway-pedestrian,
+path.shadow.tag-highway-service.tag-bridge-yes,
+path.shadow.tag-highway-track.tag-bridge-yes,
+path.shadow.tag-highway-steps.tag-bridge-yes,
+path.shadow.tag-highway-footway.tag-bridge-yes,
+path.shadow.tag-highway-cycleway.tag-bridge-yes,
+path.shadow.tag-highway-bridleway.tag-bridge-yes {
+    stroke-width: 16;
+}
+
+/* tunnels */
+
+path.stroke.tag-highway.tag-tunnel-yes {
+    stroke-opacity: 0.3;
+}
+
+path.casing.tag-highway.tag-tunnel-yes {
+    stroke-opacity: 0.5;
+}
+
+path.stroke.tag-highway-construction,
+path.casing.tag-highway-construction {
+    stroke-linecap: butt;
+    stroke-dasharray: 7, 7;
+}
+
+/* construction */
+
+svg[data-zoom="16"] path.stroke.tag-highway-construction,
+svg[data-zoom="16"] path.casing.tag-highway-construction {
+    stroke-linecap: butt;
+    stroke-dasharray: 5, 5;
+}
+
+/* railways */
+
+.line.stroke.tag-railway {
+    stroke: #eee;
+    stroke-width: 2;
+    stroke-linecap: butt;
+    stroke-dasharray: 12,12;
+}
+.line.casing.tag-railway {
+    stroke: #555;
+    stroke-width: 4;
+}
+
+.line.stroke.tag-railway-abandoned {
+    stroke: #eee;
+}
+.line.casing.tag-railway-abandoned {
+    stroke: #999;
+}
+
+.line.stroke.tag-railway-subway {
+    stroke: #666;
+}
+.line.casing.tag-railway-subway {
+    stroke: #222;
+}
+
+.line.stroke.tag-railway-platform {
+    stroke: #999;
+    stroke-width: 4;
+    stroke-dasharray: none;
+}
+.line.casing.tag-railway-platform {
+    stroke: none;
+}
+
+/* waterways */
+
+path.fill.tag-waterway {
+    fill: #77d3de;
+}
+
+path.stroke.tag-waterway {
+    stroke: #77d3de;
+    stroke-width: 2;
+}
+path.casing.tag-waterway {
+    stroke: #77d3de;
+    stroke-width: 4;
+}
+
+path.stroke.tag-waterway-river {
+    stroke-width: 4;
+}
+path.casing.tag-waterway-river {
+    stroke-width: 6;
+}
+
+svg[data-zoom="16"] path.stroke.tag-waterway-river {
+    stroke-width: 4;
+}
+svg[data-zoom="16"] path.casing.tag-waterway-river {
+    stroke-width: 6;
+}
+
+path.stroke.tag-waterway-ditch {
+    stroke: #6591ff;
+    stroke-width: 1;
+}
+path.casing.tag-waterway-ditch {
+    stroke: #6591ff;
+    stroke-width: 3;
+}
+
+/* power */
+
+path.stroke.tag-power {
+    stroke: #939393;
+    stroke-width: 2;
+}
+path.casing.tag-power {
+    stroke: none;
+}
+
+/* boundary */
+
+path.stroke.tag-boundary {
+    stroke: #fff;
+    stroke-width: 2;
+    stroke-linecap: butt;
+    stroke-dasharray: 20, 5, 5, 5;
+}
+path.casing.tag-boundary {
+    stroke: #82B5FE;
+    stroke-width: 6;
+}
+
+path.casing.tag-boundary-protected_area,
+path.casing.tag-boundary-national_park {
+    stroke: #b0e298;
+}
+
+
+text {
+    font-size:10px;
+    pointer-events: none;
+    color: #222;
+    opacity: 1;
+}
+
+.oneway .textpath.tag-waterway {
+    fill: #002F35;
+}
+
+marker#oneway-marker path {
+    fill:#000;
+    opacity: .5;
+}
+
+text.tag-oneway {
+    fill:#91CFFF;
+    stroke:#2C6B9B;
+    stroke-width:1;
+    pointer-events:none;
+}
+
+/*
+ * Labels
+ */
+
+text.arealabel-halo,
+text.linelabel-halo,
+text.pointlabel-halo,
+text.arealabel,
+text.linelabel,
+text.pointlabel {
+    font-size: 12px;
+    font-weight: bold;
+    fill: #333;
+    text-anchor: middle;
+    pointer-events: none;
+    -webkit-transition: opacity 100ms linear;
+    transition: opacity 100ms linear;
+    -moz-transition: opacity 100ms linear;
+}
+
+.linelabel-halo .textpath,
+.linelabel .textpath {
+  dominant-baseline: middle;
+}
+
+/* Opera doesn't support dominant-baseline. See #715 */
+.opera .linelabel-halo .textpath,
+.opera .linelabel .textpath {
+  baseline-shift: -33%;
+  dominant-baseline: auto;
+}
+
+.layer-halo text {
+    opacity: 0.7;
+    stroke: #fff;
+    stroke-width: 5px;
+    stroke-miterlimit: 1;
+}
+
+text.point {
+  font-size: 10px;
+}
+
+/* Cursors */
+
+#map:hover {
+    cursor: auto; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-grab.png") %>) 9 9, auto; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-grab.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-grab2x.png") %>) 2x
+        ) 9 9, auto;
+}
+
+#map:active {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-grabbing.png") %>) 9 9, auto; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-grabbing.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-grabbing2x.png") %>) 2x
+        ) 9 9, auto;
+}
+
+.mode-browse .point,
+.mode-select .point {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-point.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-point.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-point2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-select .vertex,
+.mode-browse .vertex {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-vertex.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-vertex.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-vertex2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-browse .line,
+.mode-select .line {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-line.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-line.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-line2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-select .area,
+.mode-browse .area {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-area.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-area.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-area2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-select .midpoint,
+.mode-browse .midpoint {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-split.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-split.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-split2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-select .behavior-multiselect .point,
+.mode-select .behavior-multiselect .vertex,
+.mode-select .behavior-multiselect .line,
+.mode-select .behavior-multiselect .area {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-add.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-add.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-add2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-select .behavior-multiselect .selected {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-remove.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-remove.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-remove2x.png") %>) 2x
+        ), pointer;
+}
+
+#map .point:active,
+#map .vertex:active,
+#map .line:active,
+#map .area:active,
+#map .midpoint:active,
+#map .mode-select .selected {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-select-acting.png") %>), pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-select-acting.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-select-acting2x.png") %>) 2x
+        ), pointer;
+}
+
+.mode-draw-line #map:hover,
+.mode-draw-area #map:hover,
+.mode-add-line  #map:hover,
+.mode-add-area  #map:hover,
+.mode-drag-node #map:hover {
+    cursor: crosshair; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-draw.png") %>) 9 9, crosshair; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-draw.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-draw2x.png") %>) 2x
+        ) 9 9, crosshair;
+}
+
+.mode-draw-line .behavior-hover .way,
+.mode-draw-area .behavior-hover .way,
+.mode-add-line  .behavior-hover .way,
+.mode-add-area  .behavior-hover .way,
+.mode-drag-node .behavior-hover .way {
+    cursor: crosshair; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-draw-connect-line.png") %>) 9 9, crosshair; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-draw-connect-line.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-draw-connect-line2x.png") %>) 2x
+        ) 9 9, crosshair;
+}
+
+.mode-draw-line .behavior-hover .vertex,
+.mode-draw-area .behavior-hover .vertex,
+.mode-add-line  .behavior-hover .vertex,
+.mode-add-area  .behavior-hover .vertex,
+.mode-drag-node .behavior-hover .vertex {
+    cursor: crosshair; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-draw-connect-vertex.png") %>) 9 9, crosshair; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-draw-connect-vertex.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-draw-connect-vertex2x.png") %>) 2x
+        ) 9 9, crosshair;
+}
+
+.mode-add-point #map:hover,
+.lasso #map:hover,
+.lasso .way,
+.lasso .vertex {
+    cursor: crosshair; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-draw.png") %>) 9 9, crosshair; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-draw.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-draw2x.png") %>) 2x
+        ) 9 9, crosshair;
+}
+
+.lasso #map {
+    pointer-events: visibleStroke;
+}
+
+/* GPX Paths */
+path.gpx {
+    stroke:#6AFF25;
+    stroke-width:2;
+    fill:transparent;
+    pointer-events: none;
+}
+
+/* Modes */
+
+.mode-draw-line .vertex.active,
+.mode-draw-area .vertex.active,
+.mode-drag-node .vertex.active {
+    display: none;
+}
+
+.mode-draw-line .way.active,
+.mode-draw-area .way.active,
+.mode-drag-node .active {
+    pointer-events: none;
+}
+
+/* Ensure drawing doesn't interact with area fills. */
+.mode-add-point .area.fill,
+.mode-draw-line .area.fill,
+.mode-draw-area .area.fill,
+.mode-add-line .area.fill,
+.mode-add-area .area.fill,
+.mode-drag-node .area.fill {
+    pointer-events: none;
+}
+/* Basics
+------------------------------------------------------- */
+
+body {
+    font:normal 12px/1.6666 'Helvetica Neue', Arial, sans-serif;
+    margin:0;
+    padding:0;
+    min-width: 768px;
+    color:#333;
+    overflow: hidden;
+    -webkit-font-smoothing: subpixel-antialiased;
+}
+
+.unsupported {
+    text-align: center;
+    vertical-align: middle;
+    padding-top: 100px;
+    font-size: 15px;
+}
+
+.id-container {
+    height: 100%;
+    width: 100%;
+    position: fixed;
+    min-width: 768px;
+}
+
+.limiter {
+    position: relative;
+    max-width: 1200px;
+}
+
+.spinner {
+    opacity: .5;
+    z-index: 2;
+    position: relative;
+}
+
+.spinner img {
+    position: fixed;
+    height: 40px;
+    width: 40px;
+    right: 10px;
+    top: 10px;
+    margin: auto;
+    border-radius: 4px;
+    background: black;
+}
+
+div, textarea, label, input, form, span, ul, li, ol, a, button, h1, h2, h3, h4, h5, p, img {
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+}
+
+a, button, input, textarea {
+    -webkit-tap-highlight-color:rgba(0,0,0,0);
+    -webkit-touch-callout:none;
+}
+
+a,
+button,
+.checkselect label:hover,
+.opacity-options li,
+.radial-menu-item {
+    cursor: pointer; /* Opera */
+    cursor: url(<%= asset_path("iD/img/cursor-pointer.png") %>) 6 1, pointer; /* FF */
+    cursor: -webkit-image-set(
+        url(<%= asset_path("iD/img/cursor-pointer.png") %>) 1x,
+        url(<%= asset_path("iD/img/cursor-pointer2x.png") %>) 2x
+        ) 6 1, pointer;
+}
+
+h2 {
+    font-size: 25px;
+    line-height: 1.25;
+    font-weight: bold;
+    margin-bottom: 20px;
+}
+
+h3:last-child,
+h2:last-child,
+h4:last-child { margin-bottom: 0;}
+
+h3 {
+    font-size: 16px;
+    line-height: 1.25;
+    font-weight: bold;
+    margin-bottom: 10px;
+}
+
+h4, h5 {
+    font-size: 12px;
+    font-weight: bold;
+    padding-bottom: 10px;
+}
+
+:focus {
+    outline-color: transparent;
+    outline-style: none;
+}
+
+p {
+    font-size: 12px;
+    margin:0;
+    padding:0;
+}
+
+p:last-child {
+    padding-bottom: 0;
+}
+
+em {
+    font-style: italic;
+}
+
+strong {
+    font-weight: bold;
+}
+
+a:visited, a {
+    color: #7092ff;
+    -webkit-transition: all 100ms;
+       -moz-transition: all 100ms;
+         -o-transition: all 100ms;
+            transition: all 100ms;
+}
+
+a:hover {
+    color:#597be7;
+}
+
+/* Forms
+------------------------------------------------------- */
+
+textarea  {
+    resize: vertical;
+    font:normal 12px/20px 'Helvetica Neue', Arial, sans-serif;
+}
+
+textarea,
+input[type=text],
+input[type=search],
+input[type=number],
+input[type=url],
+input[type=tel],
+input[type=email] {
+    background-color: white;
+    border:1px solid #ccc;
+    padding:5px 10px;
+    height:30px;
+    width: 100%;
+    border-radius:4px;
+    -webkit-transition: all 100ms;
+       -moz-transition: all 100ms;
+       -o-transition: all 100ms;
+            transition: all 100ms;
+}
+
+textarea:focus,
+input:focus {
+    background-color: #F1F1F1;
+}
+
+input.major {
+    width: 100%;
+    padding:5px 10px;
+    font-size: 18px;
+    font-weight: bold;
+    height:60px;
+}
+
+/* remove bottom border radius when combox is open */
+.combobox + div textarea:focus,
+.combobox + div input:focus {
+    border-bottom-left-radius: 0;
+    border-bottom-right-radius: 0;
+}
+
+/* tables */
+
+table {
+    background-color: white;
+    border-collapse: collapse;
+    width:100%;
+    border-spacing:0;
+}
+
+table th {
+    text-align:left;
+}
+
+table.tags, table.tags td, table.tags th {
+    border: 1px solid #CCC;
+    padding: 4px;
+}
+
+/* Grid
+------------------------------------------------------- */
+
+.col0    { float:left; width:04.1666%; }
+.col1    { float:left; width:08.3333%; }
+.col2    { float:left; width:16.6666%; }
+.col3    { float:left; width:25.0000%; max-width: 300px; }
+.col4    { float:left; width:33.3333%; max-width: 400px; }
+.col5    { float:left; width:41.6666%; max-width: 500px; }
+.col6    { float:left; width:50.0000%; max-width: 600px; }
+.col7    { float:left; width:58.3333%; }
+.col8    { float:left; width:66.6666%; }
+.col9    { float:left; width:75.0000%; }
+.col10   { float:left; width:83.3333%; }
+.col11   { float:left; width:91.6666%; }
+.col12   { float:left; width:100.0000%; }
+
+/* UI Lists
+------------------------------------------------------- */
+
+ul li { list-style: none;}
+
+ul.toggle-list {
+    border-radius: 4px;
+    border: 1px solid #CCC;
+    margin-bottom: 10px;
+}
+
+ul.toggle-list li a {
+    position: relative;
+    padding: 5px 10px 5px 25px;
+    display:block;
+    border-top: 1px solid #ccc;
+}
+
+ul.toggle-list li:first-child a {
+    border-top: 0;
+    border-radius: 3px 3px 0 0;
+}
+
+ul.toggle-list li:last-child a {
+    border-radius: 0 0 3px 3px;
+}
+
+ul.toggle-list li:only-child a {
+    border-radius: 3px;
+}
+
+ul.toggle-list li a:hover { background-color: #ececec;}
+
+ul.toggle-list li a.selected { background-color: #e8ebff;}
+
+ul.link-list li {
+    float: right;
+    border-left: 1px solid rgba(255,255,255,.5);
+    padding: 5px 0 5px 5px;
+    margin-left: 5px;
+}
+
+ul.link-list li:last-child {
+    border-left: 0;
+    margin-left: 0;
+    padding-left: 0;
+}
+
+.toggle-list a::before {
+    content: "";
+    display: inline-block;
+    border-radius: 50%;
+    height: 12px;
+    width: 12px;
+    margin-right: 10px;
+    border: 1px solid #CCC;
+    position: absolute;
+    left: 5px;
+    top: 8px;
+}
+
+.toggle-list a:hover::before {
+    box-shadow: inset 0 0 0 2px white;
+}
+
+.toggle-list a.selected::before {
+    background: #7092ff;
+    box-shadow: inset 0 0 0 2px white;
+}
+
+/* Utility Classes
+------------------------------------------------------- */
+.fillL {
+    background: white;
+    color: #333;
+}
+
+.fillL2 {
+    background: #f7f7f7 url(<%= asset_path("iD/img/background-pattern-1.png") %>) repeat;
+    color: #333;
+}
+
+.fillL3 {
+    background: #f1f1f1;
+    color: #333;
+}
+
+.fillD {
+    background:rgba(0,0,0,.5);
+    color: white;
+}
+
+.fl { float: left;}
+.fr { float: right;}
+
+div.hide,
+form.hide,
+button.hide,
+a.hide {
+    display: none;
+}
+
+.deemphasize {
+    color: #a9a9a9;
+}
+
+.content {
+    box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
+}
+
+.loading {
+    background: url(<%= asset_path("iD/img/loader_bg.gif") %>);
+    background-size:5px 5px;
+}
+
+.panewrap {
+    position:absolute;
+    width:200%;
+    height:100%;
+    right: -100%;
+}
+
+.pane {
+    position:absolute;
+    width:50%;
+    height:100%;
+}
+
+.pane:first-child {
+    left: 0;
+}
+
+.pane:last-child {
+    right: 0;
+}
+
+/* Buttons */
+
+button {
+    text-align: center;
+    font-weight:bold;
+    line-height:20px;
+    border:0;
+    background: white;
+    color:#333;
+    font-size:12px;
+    display: inline-block;
+    height:40px;
+    border-radius:4px;
+    /* Crashes Safari: https://github.com/systemed/iD/issues/1188 */
+    /*-webkit-transition: all 100ms;*/
+       -moz-transition: all 100ms;
+         -o-transition: all 100ms;
+            transition: all 100ms;
+}
+
+button:focus,
+button:hover {
+    background-color: #ececec;
+}
+
+button[disabled],
+button.disabled {
+    background-color: rgba(255,255,255,.25);
+    color: rgba(0,0,0,.5);
+    cursor: auto;
+}
+
+button.active:not([disabled]):not(.disabled) {
+    background: #7092ff;
+}
+
+button.minor {
+    position: absolute;
+    top: 0;
+    right: 0;
+    height: 100%;
+    width: 20px;
+    opacity: .5;
+    border-radius: 0;
+}
+
+.button-wrap {
+    display: inline-block;
+    padding-right:10px;
+    margin: 0;
+}
+
+.button-wrap button:only-child { width: 100%;}
+.button-wrap:last-of-type { padding-right: 0;}
+
+.joined button {
+    border-radius:0;
+    border-right: 1px solid rgba(0,0,0,.5);
+}
+
+.joined button:first-child {
+    border-radius:4px 0 0 4px;
+}
+
+.joined button:last-child {
+    border-right-width: 0;
+    border-radius:0 4px 4px 0;
+}
+
+button.action {
+    background: #7092ff;
+}
+
+button.action:hover {
+    background: #597BE7;
+}
+
+button.save.has-count {
+    padding: 9px;
+}
+
+button.save .count {
+    display: none;
+}
+
+button.save.has-count .count {
+    display: block;
+    position: absolute;
+    top: 5px;
+    background: rgba(255, 255, 255, .5);
+    color: #333;
+    padding: 10px;
+    height: 30px;
+    line-height: 12px;
+    border-radius: 4px;
+    margin: auto;
+    margin-left: 8.3333%;
+}
+
+button.save.has-count .count::before {
+    content: "";
+    margin: auto;
+    width: 0;
+    height: 0;
+    position: absolute;
+    left: -6px;
+    top: 0;
+    bottom: 0;
+    border-top:    6px solid transparent;
+    border-bottom: 6px solid transparent;
+    border-right:  6px solid rgba(255,255,255,.5);
+}
+
+/* Icons */
+
+.icon {
+  display:inline-block;
+  vertical-align:top;
+  width:20px;
+  height:20px;
+  background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 0;
+  text-indent:-9999px;
+  overflow:hidden;
+  }
+
+.icon-pre-text {
+    margin-right: 3px;
+}
+
+.user-icon {
+    max-height: 20px;
+    max-width: 20px;
+    height: auto;
+    width: auto;
+    border-radius: 3px;
+}
+
+/* Definitions for every icon */
+.icon.browse     { background-position:   0 0;}
+.icon.add-point  { background-position: -20px 0;}
+.icon.add-line   { background-position: -40px 0;}
+.icon.add-area   { background-position: -60px 0;}
+.icon.undo       { background-position: -80px 0;}
+.icon.redo       { background-position: -100px 0;}
+.icon.apply      { background-position: -120px 0;}
+.icon.save       { background-position: -140px 0;}
+.icon.close      { background-position: -160px 0;}
+.icon.delete     { background-position: -180px 0;}
+.icon.remove     { background-position: -200px 0;}
+.icon.inspect    { background-position: -220px 0;}
+.icon.zoom-in    { background-position: -240px 0;}
+.icon.zoom-out   { background-position: -260px 0;}
+.icon.plus       { background-position: -240px 0;}
+.icon.search     { background-position: -280px 0;}
+.icon.geocode    { background-position: -280px 0;}
+.icon.layers     { background-position: -300px 0;}
+.icon.avatar     { background-position: -320px 0;}
+.icon.nearby     { background-position: -340px 0;}
+.icon.geolocate  { background-position: -360px 0;}
+.icon.warning    { background-position: -380px 0;}
+.icon.back       { background-position: -420px 0;}
+.icon.forward    { background-position: -440px 0;}
+.icon.help       { background-position: -460px 0;}
+
+.icon.inspect.light    { background-position: -220px -20px;}
+.icon.geocode.light    { background-position: -280px -20px;}
+.icon.help.light       { background-position: -460px -20px;}
+.icon.avatar.light     { background-position: -320px -20px;}
+.icon.nearby.light     { background-position: -340px -20px;}
+
+.icon.back.blue        { background-position: -420px -20px;}
+.icon.forward.blue     { background-position: -440px -20px;}
+
+button[disabled] .icon.browse     { background-position:   0 -40px;}
+button[disabled] .icon.add-point  { background-position: -20px -40px;}
+button[disabled] .icon.add-line   { background-position: -40px -40px;}
+button[disabled] .icon.add-area   { background-position: -60px -40px;}
+button.disabled  .icon.undo       { background-position: -80px -40px;}
+button.disabled  .icon.redo       { background-position: -100px -40px;}
+button[disabled] .apply.icon      { background-position: -120px -40px;}
+button[disabled] .close.icon      { background-position: -160px -40px;}
+button[disabled] .delete.icon     { background-position: -180px -40px;}
+button[disabled] .icon.remove     { background-position: -200px -40px;}
+button[disabled] .icon.inspect    { background-position: -220px -40px;}
+button[disabled] .icon.zoom-in    { background-position: -240px -40px;}
+button[disabled] .icon.zoom-out   { background-position: -260px -40px;}
+button[disabled] .icon.geocode    { background-position: -280px -40px;}
+button[disabled] .icon.layers     { background-position: -300px -40px;}
+button[disabled] .icon.avatar     { background-position: -320px -40px;}
+button[disabled] .icon.nearby     { background-position: -340px -40px;}
+
+.icon-operation-delete        { background-position: 0 -140px;}
+.icon-operation-circularize   { background-position: -20px -140px;}
+.icon-operation-straighten    { background-position: -40px -140px;}
+.icon-operation-split         { background-position: -60px -140px;}
+.icon-operation-disconnect    { background-position: -80px -140px;}
+.icon-operation-reverse       { background-position: -100px -140px;}
+.icon-operation-move          { background-position: -120px -140px;}
+.icon-operation-merge         { background-position: -140px -140px;}
+.icon-operation-orthogonalize { background-position: -160px -140px;}
+.icon-operation-rotate        { background-position: -180px -140px;}
+.icon-operation-simplify      { background-position: -200px -140px;}
+
+.icon-operation-disabled-delete        { background-position: 0 -160px;}
+.icon-operation-disabled-circularize   { background-position: -20px -160px;}
+.icon-operation-disabled-straighten    { background-position: -40px -160px;}
+.icon-operation-disabled-split         { background-position: -60px -160px;}
+.icon-operation-disabled-disconnect    { background-position: -80px -160px;}
+.icon-operation-disabled-reverse       { background-position: -100px -160px;}
+.icon-operation-disabled-move          { background-position: -120px -160px;}
+.icon-operation-disabled-merge         { background-position: -140px -160px;}
+.icon-operation-disabled-orthogonalize { background-position: -160px -160px;}
+.icon-operation-disabled-rotate        { background-position: -180px -160px;}
+.icon-operation-disabled-simplify      { background-position: -200px -160px;}
+
+/* Out link is special */
+
+.icon.out-link   { height: 14px; width: 14px; background-position: -500px 0;}
+a:hover .icon.out-link   { background-position: -500px -14px;}
+
+/* Universal preset icons */
+
+.icon.source     { background-position:   0 -200px;}
+.icon.address    { background-position: -20px -200px;}
+.icon.telephone  { background-position: -40px -200px;}
+.icon.website    { background-position: -60px -200px;}
+.icon.elevation  { background-position: -80px -200px;}
+.icon.wikipedia  { background-position: -100px -200px;}
+.icon.note       { background-position: -120px -200px;}
+.icon.wheelchair { background-position: -140px -200px;}
+
+/* ToolBar / Persistent UI Elements
+------------------------------------------------------- */
+
+#bar {
+    position:absolute;
+    padding: 10px;
+    left:0;
+    top:0;
+    right:0;
+    height:60px;
+}
+
+/* Header for modals / panes
+------------------------------------------------------- */
+
+.header {
+    border-bottom: 1px solid #ccc;
+    z-index: 2;
+    height: 60px;
+    position: relative;
+}
+
+.header h3 {
+    margin-right: 40px;
+    margin-bottom: 0;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+}
+
+.modal > button,
+.header button {
+    height: 100%;
+    border-radius: 0;
+    border-left: 1px solid #CCC;
+    width: 40px;
+    text-align: center;
+    overflow: hidden;
+    position: absolute;
+    right: 0;
+    top: 0;
+}
+
+.modal > button {
+    height: 59px;
+    z-index: 3;
+}
+
+/* Inspector
+------------------------------------------------------- */
+
+.inspector-wrap {
+    position: absolute;
+    height: 100%;
+    right: 0;
+    overflow: hidden;
+}
+
+.inspector-body {
+    overflow-y: scroll;
+    overflow-x: hidden;
+    position: absolute;
+    right: 0;
+    left: 0;
+    bottom: 30px;
+    top: 60px;
+}
+
+.pane:first-child .inspector-body {
+    top: 120px;
+}
+
+.inspector-inner {
+    padding: 20px;
+    position: relative;
+}
+
+.inspector-wrap .header button.preset-reset {
+    border-right: 1px solid #CCC;
+    position: relative;
+}
+
+.inspector-wrap .header button.preset-reset > div {
+    height: 100%;
+    padding: 20px 0;
+}
+
+.inspector-wrap .header button.preset-reset .col12:last-child {
+    position: absolute;
+    width: 100%;
+    padding: 20px 0;
+    opacity: 0;
+}
+
+.inspector-wrap .header button:hover .col12:first-child {
+    opacity: 0;
+}
+
+.inspector-wrap .header button:hover .col12:last-child {
+    opacity: 1;
+}
+
+.inspector-wrap .header button.line > div {
+    padding: 0;
+}
+
+.inspector-toggle {
+    color:#fff;
+    width: 100%;
+    display: block;
+    background:#7092ff;
+    border: 0;
+}
+
+/* Presets
+------------------------------------------------------- */
+
+/* Preset grid  */
+
+.preset-grid  {
+    width:100%;
+    padding: 20px 10px 10px 20px;
+    border-bottom: 1px solid #ccc;
+}
+
+.grid-button-wrap {
+    padding: 0 10px 10px 0;
+    height: 120px;
+}
+
+.grid-entry {
+    width: 100%;
+    height: 100%;
+    position: relative;
+    border: 1px solid #ccc;
+    overflow: hidden;
+    float: left;
+}
+
+.grid-inner {
+    margin-bottom: 20px;
+}
+
+.preset-grid.filtered .grid-button-wrap:first-child .grid-entry {
+    background: #ececec;
+}
+
+.preset-icon {
+    position: absolute;
+    top: 30px;
+    left: 0;
+    right: 0;
+    margin: auto;
+}
+
+.preset-icon-line {
+    top: 15px;
+    left: -10px;
+    right: -10px;
+}
+
+.grid-entry .label {
+    background: #f6f6f6;
+    text-align: left;
+    position: absolute;
+    padding: 5px 10px;
+    height: 30px;
+    bottom: 0;
+    left: 0; right: 0;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    border-top: 1px solid rgba(0, 0, 0, .1);
+    }
+
+.grid-button-wrap button.tag-reference-button {
+    float: right;
+    position: static;
+    margin-top: -30px;
+    margin-right: 1px;
+    height: 29px;
+    border-left: 1px solid #CCC;
+    border-radius: 0 0 3px 0;
+}
+
+.current .grid-entry,
+.current .grid-entry .label {
+    background-color: #E8EBFF;
+}
+
+.category .grid-entry:after,
+.category .grid-entry:before {
+    content: "";
+    position: absolute;
+    top: 7px;
+    left: 0; right: 0;
+    border-top: 1px solid #ccc;
+    border-radius: 6px;
+    height: 6px;
+}
+
+.category .grid-entry:before {
+    top: 3px;
+}
+
+.preset-grid-search-wrap .icon {
+    display: block;
+    position: absolute;
+    left: 10px;
+    top: 80px;
+    pointer-events: none;
+}
+
+.preset-grid-search-wrap input {
+    position: absolute;
+    top: 60px;
+    border-radius: 0;
+    border-width: 0;
+    border-bottom-width: 1px;
+    text-indent: 30px;
+}
+
+.preset-search-result {
+    padding: 0 10px;
+    height:30px;
+    margin: 5px;
+}
+
+.subgrid {
+    width: 100%;
+    width: -webkit-calc(100% + 10px);
+    width: calc(100% + 10px);
+    margin-left: -10px;
+    overflow: hidden;
+}
+
+.subgrid .preset-grid {
+    padding: 10px 0 0 10px;
+    border: 1px solid #CCC;
+    margin-top: 0;
+    border-radius: 8px;
+}
+
+.subgrid .arrow {
+    border: solid rgba(0, 0, 0, 0);
+    border-width: 10px;
+    border-bottom-color: #CCC;
+    width: 0;
+    height: 0;
+    margin-left: 33.3333%;
+    margin-left: -webkit-calc(16.6666% - 10px);
+    margin-left: calc(16.6666% - 10px);
+    margin-top: -10px;
+}
+.subgrid.arrow-1 .arrow {
+    margin-left: 50%;
+    margin-left: -webkit-calc(50% - 10px);
+    margin-left: calc(50% - 10px);
+}
+
+.subgrid.arrow-2 .arrow {
+    margin-left: 280px;
+    margin-left: -webkit-calc(84.4444% - 10px);
+    margin-left: calc(84.4444% - 10px);
+}
+
+.show-more {
+    text-align: center;
+    width: 100%;
+    border-bottom: 1px solid #ccc;
+    border-radius: 0;
+}
+
+.show-more a {
+    color: #222;
+}
+
+/* Preset icon colors */
+
+.inspector-body-line .icon.feature-marker-stroked {
+    top: 30px;
+}
+
+.preset-icon-fill.icon-area {
+    cursor: inherit;
+    height: 45px;
+    width: 45px;
+    margin: auto;
+    position: absolute;
+    left: 0; right: 0; top: 19px;
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -240px -80px;
+}
+
+.preset-icon-fill.tag-shop,
+.preset-icon-fill.tag-building {
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 -80px;
+}
+
+.preset-icon-fill.tag-natural-water {
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -60px -80px;
+}
+
+.preset-icon-fill.tag-landuse,
+.preset-icon-fill.tag-natural-wood,
+.preset-icon-fill.tag-natural-tree,
+.preset-icon-fill.tag-natural-grassland,
+.preset-icon-fill.tag-leisure-park {
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -120px -80px;
+}
+
+.preset-icon-fill.tag-amenity-parking {
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -180px -80px;
+}
+
+/* preset form basics */
+
+.tag-wrap .preset-icon-wrap {
+    border-bottom: 1px solid #CCC;
+    background-color: #e8ebff;
+}
+
+.tag-wrap .preset-icon-wrap::after {
+    content: "";
+    position: absolute;
+    height: 0;
+    width: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    margin: auto;
+    border: solid rgba(0, 0, 0, 0);
+    border-width: 10px;
+    border-bottom-color: #CCC;
+}
+
+.tag-wrap .preset-icon-wrap > div {
+    height: 90px;
+    width: 33.3333%;
+    width: -webkit-calc(33.3333% - 10px);
+    width: calc(33.3333% - 10px);
+    margin: auto;
+    border-radius: 4px;
+    border: 1px solid #CCC;
+    position: relative;
+}
+
+.inspector-preset .form-field {
+    padding-left: 20px;
+    padding-right: 20px;
+}
+
+.form-label {
+    position: relative;
+    font-weight: bold;
+    border: 1px solid #cfcfcf;
+    padding: 5px 0 5px 10px;
+    background: #f6f6f6;
+    display: block;
+    border-radius: 4px 4px 0 0;
+}
+
+.form-label button {
+    border-left: 1px solid #CCC;
+}
+
+.form-label .modified-icon {
+    border-right: 0;
+    opacity: 0;
+    right: 20px;
+}
+
+.modified .form-label .modified-icon {
+    opacity: .5;
+}
+
+.form-label button.tag-reference-button {
+    border-top-right-radius: 3px;
+}
+
+.form-field > input,
+.form-field > textarea,
+.form-field .preset-input-wrap {
+    border: 1px solid #CCC;
+    border-top: 0;
+    border-radius: 0 0 4px 4px;
+}
+
+.form-field textarea {
+    height: 65px;
+}
+
+.form-field-name input.localized-main {
+    height: 35px;
+    font-size: 18px;
+    font-weight: bold;
+}
+
+/* adding additional preset fields */
+
+.more-buttons {
+    margin-top: 20px;
+    border-top: 1px solid #CCC;
+}
+
+.more-buttons:nth-last-child(2) {
+    border-bottom: 1px solid #CCC;
+}
+
+button.preset-add-field {
+    width: 25%;
+    height: 40px;
+    -webkit-transition: width 200ms;
+    -moz-transition: width 200ms;
+    -o-transition: width 200ms;
+    transition: width 200ms;
+}
+
+/* set width based on # of buttons */
+
+button.preset-add-field:nth-last-child(4),
+button.preset-add-field:nth-last-child(4) ~ button.preset-add-field {
+    width: 25%;
+}
+
+button.preset-add-field:nth-last-child(5),
+button.preset-add-field:nth-last-child(5) ~ button.preset-add-field {
+    width: 20%;
+}
+
+button.preset-add-field:nth-last-child(6),
+button.preset-add-field:nth-last-child(6) ~ button.preset-add-field {
+    width: 16.6666%;
+}
+
+button.preset-add-field:nth-last-child(7),
+button.preset-add-field:nth-last-child(7) ~ button.preset-add-field {
+    width: 14.2857%;
+}
+
+button.preset-add-field:nth-last-child(8),
+button.preset-add-field:nth-last-child(8) ~ button.preset-add-field {
+    width: 12.5%;
+}
+
+.preset-add-field .tooltip.top .tooltip-arrow {
+    border-top-color: #000;
+}
+
+.preset-add-field .tooltip-inner {
+    background: #000;
+    color: #ccc;
+}
+
+.preset-fav button.fav {
+    height: 30px;
+    margin: 5px;
+    padding: 0 10px;
+}
+
+/* preset form access */
+
+.preset-input-wrap li {
+    border-bottom: 1px solid #CCC;
+}
+.preset-input-wrap li:last-child {
+    border-bottom: 0;
+}
+
+.preset-input-wrap .label {
+    background: #F6F6F6;
+    padding: 5px;
+}
+
+.preset-input-access-wrap input {
+    border-radius: 0;
+    border-width: 0;
+    border-left-width: 1px;
+}
+
+.preset-input-wrap li:last-child input {
+    border-bottom-right-radius: 4px;
+}
+
+/* preset form numbers */
+
+input[type=number] {
+    position: relative;
+    padding-right: 65px;
+}
+
+.spin-control {
+    width: 41px;
+    height: 29px;
+    border-left: 1px solid #CCC;
+    display: inline-block;
+    margin-left: -41px;
+    margin-bottom: -11px;
+    position: relative;
+}
+
+.spin-control button {
+    position: relative;
+    float: left;
+    height: 100%;
+    width: 50%;
+    border-left: 1px solid #CCC;
+    border-right: 1px solid #CCC;
+    border-radius: 0;
+    border-left: 0;
+    background: rgba(0, 0, 0, 0);
+}
+
+.spin-control button.decrement {
+    border-bottom-right-radius: 3px;
+}
+
+.spin-control button.decrement::after,
+.spin-control button.increment::after {
+    content:"";
+    height: 0; width: 0;
+    position: absolute;
+    left: 0; right: 0; bottom: 0; top: 0;
+    margin: auto;
+}
+
+.spin-control button.decrement::after {
+    border-top: 5px solid #CCC;
+    border-left: 5px solid transparent;
+    border-right: 5px solid transparent;
+}
+
+.spin-control button.increment::after {
+    border-bottom: 5px solid #CCC;
+    border-left: 5px solid transparent;
+    border-right: 5px solid transparent;
+}
+
+/* preset form checkbox */
+
+.checkselect label:last-of-type {
+    display: block;
+    padding: 5px;
+    box-sizing: border-box;
+    color: #999;
+}
+
+.checkselect label:hover {
+    background: #f1f1f1;
+}
+
+.checkselect .set {
+    color: inherit;
+}
+
+.checkselect input[type="checkbox"] {
+    margin-right: 5px;
+    width: 20px;
+    vertical-align: middle;
+    opacity: 0.5;
+}
+
+.checkselect .set input[type="checkbox"] {
+    opacity: 1;
+}
+
+/* Preset form radio button */
+
+.radio-wrap button {
+    position: relative;
+    text-align: left;
+    font-weight: normal;
+    height: 30px;
+    border-radius: 0;
+    border-bottom: 1px solid #CCC;
+    color: #7092FF;
+    width: 100%;
+    padding-left: 25px;
+}
+
+.radio-wrap button::before {
+    content: "";
+    display: inline-block;
+    border-radius: 50%;
+    height: 12px;
+    width: 12px;
+    margin-right: 10px;
+    border: 1px solid #CCC;
+    position: absolute;
+    left: 5px;
+    top: 8px;
+}
+
+.radio-wrap button:hover::before {
+    box-shadow: inset 0 0 0 2px white;
+}
+
+.radio-wrap button.active::before {
+    background: #7092ff;
+    box-shadow: inset 0 0 0 2px white;
+}
+
+.radio-wrap button:last-child {
+    border-bottom: 0;
+}
+
+.radio-wrap button.active {
+    background-color: #E8EBFF !important;
+}
+
+.radio-wrap button.remove {
+    border-radius: 0 0 3px 3px;
+}
+.radio-wrap button.remove .icon {
+    position: absolute;
+    left: 2px;
+}
+
+.radio-wrap button.remove::before {
+    content: none;
+}
+
+.form-field .localized-main {
+    width: 90%;
+    border-radius: 0 0 0 4px;
+}
+
+.form-field .localized-add {
+    width: 10%;
+    height: 35px;
+    border-radius: 0 0 4px 0;
+    border-bottom: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+    vertical-align: top;
+}
+
+.form-field .localized-wrap .entry .localized-lang {
+    border-top: none;
+    border-right: none;
+    border-radius: 0;
+    width: 30%;
+}
+
+.form-field .localized-wrap .entry .localized-value {
+    border-top: none;
+    border-radius: 0;
+    width: 60%;
+}
+
+.form-field .localized-wrap .entry .localized-remove {
+    height: 30px;
+    border-radius: 0;
+    border-bottom: 1px solid #ccc;
+    border-right: 1px solid #ccc;
+    vertical-align: top;
+    width: 10%;
+}
+
+.form-field .localized-wrap .entry:last-child .localized-lang {
+    border-radius: 0 0 0 4px;
+}
+
+.form-field .localized-wrap .entry:last-child .localized-remove {
+    border-radius: 0 0 4px 0;
+}
+
+.form-field .wiki-lang {
+    width: 30%;
+    border-right: none;
+    border-radius: 0 0 0 4px;
+}
+
+.form-field .wiki-title {
+    width: 60%;
+    border-right: none;
+    border-radius: 0;
+}
+
+.form-field .wiki-link {
+    border-radius: 0 0 4px 0;
+    border: 1px solid #ccc;
+    border-top: none;
+    height: 30px;
+    width: 10%;
+    float: right;
+    padding: 5px;
+    text-align: center;
+    -webkit-transition: all 100ms;
+       -moz-transition: all 100ms;
+         -o-transition: all 100ms;
+            transition: all 100ms;
+}
+
+.form-field .wiki-link:hover {
+    background: #ececec;
+}
+
+#preset-input-maxspeed {
+    border-right: none;
+    border-radius: 0 0 0 4px;
+    width: 80%;
+}
+
+.form-field .maxspeed-unit {
+    border-radius: 0 0 4px 0;
+    border: 1px solid #ccc;
+    border-top: none;
+    height: 30px;
+    width: 20%;
+    float: right;
+    padding: 5px;
+    text-align: center;
+    color: #A9A9A9;
+    font-weight: bold;
+}
+
+/* Preset form address */
+
+.form-field .addr-housename {
+    border: 0;
+}
+
+.form-field .addr-number {
+    width: 20%;
+    border-left: 0;
+    border-right: 0;
+    border-bottom: 0;
+    border-radius: 0;
+}
+
+.form-field .addr-street {
+    width: 80%;
+    border-right: 0;
+    border-bottom: 0;
+    border-radius: 0;
+}
+
+.form-field .addr-city {
+    border-left: 0;
+    border-right: 0;
+    border-bottom: 0;
+    border-radius: 0 0 4px 4px;
+}
+
+/* combobox dropdown */
+
+div.combobox {
+    z-index: 9999;
+    display: none;
+    box-shadow: 0 0 10px 0 rgba(0,0,0,.1);
+    margin-top: -1px;
+    background: white;
+    max-height: 120px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    border: 1px solid #ccc;
+    border-radius: 0 0 4px 4px;
+}
+
+.combobox a {
+    display: block;
+    padding: 5px 10px;
+    border-top:1px solid #ccc;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    overflow: hidden;
+}
+
+.combobox a.selected,
+.combobox a:hover {
+    background: #ececec;
+}
+
+.combobox a:first-child {
+    border-top: 0;
+}
+
+.combobox-carat {
+    margin-left: -20px;
+    margin-right: 10px;
+    display:inline-block;
+    border-top: 5px solid #ccc;
+    border-left: 5px solid transparent;
+    border-right: 5px solid transparent;
+}
+
+/* tag editor */
+
+.inspector-inner.additional-tags {
+    border-top: 1px solid #ccc;
+}
+
+.tag-list {
+    margin-right: 40px;
+}
+
+.tag-row {
+    width: 100%;
+    position: relative;
+    clear: both;
+}
+
+.tag-row input {
+    border: 0;
+    border-radius: 0;
+    border-bottom: 1px solid #CCC;
+    border-left: 1px solid #CCC;
+}
+
+.tag-row input.key {
+    background-color: #f6f6f6;
+}
+
+.tag-row input.value {
+    border-right: 1px solid #CCC;
+}
+
+.tag-row:first-child input.key {
+    border-top: 1px solid #CCC;
+    border-top-left-radius: 4px;
+}
+
+.tag-row:first-child input.value {
+    border-top: 1px solid #CCC;
+}
+
+.tag-row button {
+    position: absolute;
+    height: 30px;
+    right: -20px;
+    border: 1px solid #CCC;
+    border-top-width: 0; border-left-width: 0;
+    border-radius: 0;
+    opacity: 1;
+    background: #fafafa;
+}
+
+.tag-row button:hover {
+    background: #f1f1f1;
+}
+
+.tag-row button .icon {
+    opacity: .5;
+}
+
+.tag-row:first-child button {
+    border-top-width: 1px;
+}
+
+.tag-row:first-child button.tag-help-button {
+    border-top-right-radius: 4px;
+}
+
+.tag-row:last-child button.tag-help-button {
+    border-bottom-right-radius: 4px;
+}
+
+.tag-row button.tag-help-button {
+    right: -40px;
+}
+
+/* Adding form fields to tag editor */
+
+.inspector-inner .add-tag {
+    width: -webkit-calc(50% - 20px);
+    width: calc(50% - 20px);
+    height: 30px;
+    border-top: 0;
+    background: rgba(0,0,0,.5);
+    border-radius: 0 0 4px 4px;
+}
+
+.inspector-inner .add-tag:hover {
+    background: rgba(0,0,0,.8);
+}
+
+.inspector-inner .add-tag .label {
+    display: none;
+}
+
+/* Tag reference */
+
+.preset-inspect {
+    position: relative;
+}
+
+.tag-help {
+    overflow: hidden;
+}
+
+.tag-help a {
+    margin-top: 5px;
+    display: block;
+}
+
+.grid-pane .tag-reference-wrap {
+    padding: 10px 0 20px 0;
+}
+
+.tag-pane .tag-reference-wrap {
+    padding-top: 20px;
+}
+
+.additional-tags .tag-reference-wrap {
+    border-bottom: 1px solid #ccc;
+    padding: 20px 0;
+}
+
+.additional-tags div.tag-help {
+    float: left;
+    width: 33.3333%;
+    width: -webkit-calc(100% - 40px);
+    width: calc(100% + 40px);
+}
+
+img.wiki-image {
+    float: left;
+    width: 33.3333%;
+    width: -webkit-calc(33.3333% - 10px);
+    width: calc(33.3333% - 10px);
+    margin-right: 20px;
+    border-radius: 4px;
+    max-height: 200px;
+}
+
+/* Map Controls */
+
+.map-control {
+    z-index: 100;
+    left:0px;
+    position:absolute;
+}
+
+.map-control > button {
+    width: 30px;
+    background: rgba(0,0,0,.5);
+    border-radius: 0;
+}
+
+.map-control > button:hover {
+    background: rgba(0, 0, 0, .8);
+}
+
+.map-control > button.active:hover {
+    background: #7092ff;
+}
+
+.map-overlay {
+    z-index: -1;
+    right: 75%;
+    max-width: 260px;
+    min-width: 210px;
+    position: fixed;
+    left: 30px;
+    display: block;
+    padding: 10px 10px 0 10px;
+}
+
+/* Zoomer */
+
+.zoombuttons {
+    top: 180px;
+    width: 30px;
+}
+
+.zoombuttons button.zoom-in {
+    border-radius:0 4px 0 0;
+}
+
+/* Background Settings */
+
+.background-control {
+    top: 80px;
+}
+
+.background-control button {
+    border-radius:0 4px 0 0;
+}
+
+.background-control button.active {
+    border-radius: 0;
+}
+
+.nudge-container {
+    border-top: 1px solid #CCC;
+    margin: 0 -10px;
+}
+
+.background-control .adjustments button:last-child {
+    border: 0;
+}
+
+.hide-toggle {
+    display: block;
+    padding: 0 0 10px 12px;
+    position: relative;
+}
+
+.hide-toggle:before {
+    content: '';
+    display: block;
+    position: absolute;
+    height: 0;
+    width: 0;
+    left: 0;
+    top: 5px;
+    border-top:  4px solid transparent;
+    border-bottom:  4px solid transparent;
+    border-left:  8px solid #7092ff;
+}
+
+.hide-toggle.expanded:before {
+    border-top:  8px solid #7092ff;
+    border-bottom: 0;
+    border-right:  4px solid transparent;
+    border-left:  4px solid transparent;
+}
+
+.background-control .nudge-container button {
+    float: left;
+    display: block;
+    width:20%;
+    border-radius: 0;
+    border-right: 1px solid #CCC;
+    position: relative;
+}
+
+.background-control .nudge::after {
+    content: '';
+    display: block;
+    position: absolute;
+    margin: auto;
+    left: 0; right: 0; top: 0; bottom: 0;
+    height: 0;
+    width: 0;
+}
+
+.background-control .nudge.left::after {
+    border-top:  5px solid transparent;
+    border-bottom:  5px solid transparent;
+    border-left:  5px solid #222;
+}
+
+.background-control .nudge.right::after {
+    border-top:  5px solid transparent;
+    border-bottom:  5px solid transparent;
+    border-right:  5px solid #222;
+}
+
+.background-control .nudge.top::after {
+    border-right:  5px solid transparent;
+    border-left:  5px solid transparent;
+    border-bottom:  5px solid #222;
+}
+
+.background-control .nudge.bottom::after {
+    border-right:  5px solid transparent;
+    border-left:  5px solid transparent;
+    border-top:  5px solid #222;
+}
+
+.opacity-options {
+    background: url(<%= asset_path("iD/img/background-pattern-opacity.png") %>) 0 0 repeat;
+    height:20px;
+    width:62px;
+    position: absolute;
+    right: 10px;
+    top: 10px;
+    border: 1px solid #ccc;
+}
+
+.opacity-options li {
+    height: 100%;
+    display: block;
+    float: left;
+}
+
+.opacity-options li .select-box{
+    position: absolute;
+    width:20px;
+    height:18px;
+    z-index: 9999;
+}
+
+.background-control li:hover .select-box,
+.background-control li.selected .select-box {
+    border: 2px solid #7092ff;
+    background: rgba(89, 123, 231, .5);
+    opacity: .5;
+}
+
+.background-control li.selected:hover .select-box,
+.background-control li.selected .select-box {
+    opacity: 1;
+}
+
+.background-control .opacity {
+    background:#222;
+    display:inline-block;
+    width:20px;
+    height:18px;
+}
+
+.background-control .layer-toggle-gpx .layer-extent {
+    border-left: 1px solid #CCC;
+}
+
+.background-control .layer-toggle-gpx.selected .layer-extent {
+    display:inline-block;
+}
+
+/* Geocoder */
+
+.geocode-control, .geocode-control form {
+    top:260px;
+}
+
+.geocode-control form {
+    padding: 5px;
+}
+
+.geocode-control input {
+    width: 100%;
+}
+
+.geocode-control div.map-overlay {
+    border-top: 1px solid #CCC;
+    z-index: 100;
+    max-height: 240px;
+    overflow-y: auto;
+    padding: 0;
+    margin-top: 40px;
+}
+
+.geocode-control div.map-overlay span {
+    display: inline-block;
+    border-bottom: 1px solid #CCC;
+    padding: 5px 10px;
+}
+.geocode-control div.map-overlay span.not-found {
+    line-height: 28px;
+    width: 100%;
+}
+
+.geocode-control a:focus {
+    text-decoration: underline;
+}
+
+/* Geolocator */
+
+.geolocate-control {
+    top:300px;
+}
+
+.geolocate-control button {
+    border-radius: 0 0 4px 0;
+}
+
+/* Help */
+
+.help-control {
+    top: 120px;
+}
+
+.help-control button {
+    border-radius: 0 0 4px 0;
+}
+
+.help-wrap {
+    position: absolute;
+    top:60px;
+    bottom: 30px;
+    padding: 20px 20px 20px 50px;
+    left: 0;
+    overflow-y: scroll;
+}
+
+.help-wrap p {
+    font-size: 15px;
+    margin-bottom: 20px;
+}
+
+.help-wrap .left-content .body p code {
+    padding:2px 4px;
+    background:#eee;
+}
+
+.help-wrap .toc {
+    /* This is two columns, 41.66666 x .4 = 16.6666 */
+    width:40%;
+    float:right;
+    margin-left: 20px;
+    margin-bottom: 20px;
+    padding-left: 5px
+}
+
+.help-wrap .toc li a,
+.help-wrap .nav a {
+    display: block;
+    border: 1px solid #CCC;
+    padding: 5px 10px;
+}
+
+.help-wrap .toc li a {
+    border-bottom: 0;
+}
+
+.help-wrap .toc li a:hover,
+.help-wrap .nav a:hover  {
+    background: #ececec;
+}
+
+.help-wrap .toc li a.selected {
+    background: #E8EBFF;
+}
+
+.help-wrap .toc li:first-child a {
+    border-radius: 4px 4px 0 0;
+}
+
+.help-wrap .toc li:nth-last-child(2) a {
+    border-bottom: 1px solid #CCC;
+    border-radius: 0 0 4px 4px
+}
+
+.help-wrap .toc li.walkthrough a {
+    overflow: hidden;
+    margin-top: 10px;
+    border-bottom: 1px solid #ccc;
+    border-radius: 4px;
+}
+
+.help-wrap .nav {
+    position: relative;
+}
+
+.help-wrap .nav a {
+    float: left;
+    width: 50%;
+    text-align: center;
+}
+
+.help-wrap .nav a:first-child {
+    border-radius: 4px 0 0 4px;
+}
+
+.help-wrap .nav a:last-child:not(:only-child) {
+    border-radius: 0 4px 4px 0;
+    border-left: 0;
+}
+
+.help-wrap .nav a:only-child {
+    width: 100%;
+    border-radius: 4px;
+}
+
+/* Map
+------------------------------------------------------- */
+
+#map {
+    display:block;
+    position:absolute;
+    overflow:hidden;
+    top:0px;
+    left:0;
+    right:0;
+    bottom:0;
+    background:#000;
+}
+
+#surface, #layer-g, .layer-layer {
+    position:absolute;
+    top:0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    transform-origin:0 0;
+    -ms-transform-origin:0 0;
+    -webkit-transform-origin:0 0;
+    -moz-transform-origin:0 0;
+    -o-transform-origin:0 0;
+    -moz-user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+
+#surface {
+  position: static;
+}
+
+/* About Section
+------------------------------------------------------- */
+
+.about-block {
+    position: absolute;
+    right:0px;
+    bottom:0px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    border-radius: 0;
+    opacity: .625;
+    -webkit-transition: opacity 200ms;
+    -moz-transition: opacity 200ms;
+    transition: opacity 200ms;
+}
+
+.about-block:hover {
+    opacity: 1;
+}
+
+#about {
+    text-align: right;
+    margin-right: 10px;
+}
+
+.source-switch a {
+    padding: 2px 4px 4px 4px;
+    border-radius: 2px;
+}
+.source-switch a.live {
+    background: #d32232;
+    color:#fff;
+}
+
+/* Attribution overlay */
+.attribution {
+    position: absolute;
+    bottom: 35px;
+    left:10px;
+    color:#888;
+    font-size:10px;
+}
+
+.source-image {
+    height:20px;
+    vertical-align:top;
+}
+
+.user-list a:not(:last-child):after {
+    content: ', ';
+}
+
+/* API Status */
+
+.api-status {
+    float: left;
+}
+
+.api-status.offline,
+.api-status.readonly {
+    background: red;
+    padding: 5px 10px;
+}
+
+/* Account Information */
+
+.account {
+    float: left;
+    padding: 5px 10px;
+}
+
+.account .logout {
+    margin-left:10px;
+    border-left: 1px solid white;
+    padding-left: 10px;
+}
+
+/* Modals
+------------------------------------------------------- */
+
+.modal {
+    display: inline-block;
+    position:absolute;
+    left: 0;
+    right: 0;
+    margin: auto;
+    z-index: 3;
+}
+
+.modal .loader {
+    margin-bottom: 10px;
+}
+
+.modal .description {
+    text-align: center;
+}
+
+.shaded {
+    z-index: 2;
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    overflow: auto;
+}
+
+.shaded:before {
+    content:'';
+    background:rgba(0,0,0,0.5);
+    position:fixed;
+    left:0px; right:0px; top:0px; bottom:0px;
+}
+
+.modal-section {
+    padding: 20px;
+    border-bottom: 1px solid #CCC;
+}
+
+.modal-section:last-child {
+    border-bottom: 0;
+}
+
+.loading-modal {
+    text-align: center;
+}
+
+.modal-actions button,
+.modal-actions a {
+    background-size: white;
+    font-weight: normal;
+    color: #7092FF;
+    border-bottom: 1px solid #CCC;
+    border-radius: 0;
+    height: 180px;
+    text-align: center;
+    display: inline-block;
+}
+
+.modal-actions a {
+    /* `button` elements have box-algin: auto, need
+    compensate this for `a`*/
+    padding-top: 25px;
+}
+
+.modal-actions button:hover,
+.modal-actions a:hover {
+    background-color: #ececec;
+}
+
+.modal-actions a:before,
+.modal-actions button:before,
+.walkthrough a:before {
+    background-size: white;
+    display: block;
+    content: '';
+    height: 100px;
+    width: 100px;
+    margin: auto;
+    margin-bottom: 10px;
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 -220px;
+}
+
+.modal-actions :first-child {
+    border-right: 1px solid #CCC;
+}
+
+/* Restore Modal
+------------------------------------------------------- */
+
+.modal-actions .restore:before {
+    background-position: -400px -220px;
+}
+
+.modal-actions .reset:before {
+    background-position: -500px -220px;
+}
+
+/* Success Modal
+------------------------------------------------------- */
+
+.modal-actions .twitter:before {
+    background-position: -100px -220px;
+}
+
+/* Splash Modal
+------------------------------------------------------- */
+
+.modal-actions .walkthrough:before,
+.walkthrough a:before {
+    background-position: -200px -220px;
+}
+
+.modal-actions .start:before {
+    background-position: -300px -220px;
+}
+
+/* Commit Modal
+------------------------------------------------------- */
+
+.commit-modal a.user-info {
+    display: inline-block;
+}
+
+.commit-modal .commit-info {
+    margin-top: 10px;
+    padding-bottom: 20px;
+}
+
+.commit-modal .user-info img {
+    float: left;
+}
+
+.commit-modal h3 small.count {
+    margin-right: 10px;
+    text-align: center;
+    float: left;
+    height: 12px;
+    min-width: 12px;
+    font-size:12px;
+    line-height: 12px;
+    border-radius:24px;
+    padding:5px;
+    background:#7092ff;
+    color:#fff;
+}
+
+.commit-modal .changeset-list {
+    overflow: auto;
+    border:1px solid #ccc;
+    border-radius: 4px;
+    background:#fff;
+    max-height: 160px;
+}
+
+.commit-modal .warning-section .changeset-list button {
+    border-left: 1px solid #CCC;
+}
+
+.commit-modal .changeset-list li {
+    position: relative;
+    border-top:1px solid #ccc;
+    padding:5px 10px;
+}
+
+.changeset-list li span.count {
+    font-size:10px;
+    color:#555;
+}
+
+.changeset-list li span.count:before { content: '('; }
+
+.changeset-list li span.count:after { content: ')'; }
+
+.changeset-list li:first-child { border-top: 0;}
+
+/* Notices
+------------------------------------------------------- */
+
+.notice {
+    float:left;
+    width:25%;
+    padding-right: 10px;
+    text-align:center;
+}
+
+.notice .zoom-to {
+    width:100%;
+    height: 40px;
+    border-radius: 5px;
+    line-height: 40px;
+    background: #fff;
+    color: #000;
+    opacity: 0.9;
+}
+
+.notice .zoom-to:hover {
+    background: #d8e1ff;
+}
+
+.notice .zoom-to .icon {
+    margin-top:10px;
+    margin-right:10px;
+}
+
+.icon.zoom-in-invert {
+    background-position: -240px -40px;
+}
+
+/* Tooltips
+------------------------------------------------------- */
+
+.tooltip {
+    width: 200px;
+    position: absolute;
+    display: none;
+    color:#333;
+    text-align: left;
+    font-size: 12px;
+}
+
+.tooltip.in {
+    opacity: 0.8;
+    z-index: 1030;
+    height: auto;
+    display: block;
+}
+
+.tooltip.top {
+    margin-top: -20px;
+    text-align: center;
+}
+
+.tooltip.right {
+    margin-left: 20px;
+}
+
+.tooltip.bottom {
+    margin-top: 20px;
+    text-align: center;
+}
+
+.tooltip.left {
+    margin-left: -20px;
+}
+
+.tooltip-inner {
+    display: inline-block;
+    padding: 10px;
+    font-weight: normal;
+    background-color: white;
+}
+
+.tail {
+    width: 200px;
+    height: 400px;
+    pointer-events: none;
+    opacity: .8;
+    margin-top: -200px;
+    position: absolute;
+    background: transparent;
+}
+
+.tail::after {
+    content: "";
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid;
+    top: 50%;
+    right: -5px;
+    margin-top: -5px;
+    border-left-color: white;
+    border-width: 5px 0 5px 5px;
+}
+
+.tail div {
+    padding: 10px;
+    background: white;
+    position: absolute;
+    top: 180px;
+    left: 0;
+    right: 0;
+    margin: auto;
+}
+
+.left.tail::after {
+    content: "";
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid;
+    top: 50%;
+    left: -5px;
+    margin-top: -5px;
+    border-right-color: white;
+    border-width: 5px 5px 5px 0;
+}
+
+.tooltip-arrow {
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+    bottom: -5px;
+    left: 50%;
+    margin-left: -5px;
+    border-top-color: white;
+    border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+    top: 50%;
+    left: -5px;
+    margin-top: -5px;
+    border-right-color: white;
+    border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+    top: 50%;
+    right: -5px;
+    margin-top: -5px;
+    border-left-color: white;
+    border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+    top: -5px;
+    left: 50%;
+    margin-left: -5px;
+    border-bottom-color: white;
+    border-width: 0 5px 5px;
+}
+
+
+/* Exceptions for tooltips that are up against the edge of the screen */
+.add-point .tooltip {
+    left: -20px !important; }
+
+.curtain-tooltip.intro-points-add .tooltip-arrow,
+.add-point .tooltip .tooltip-arrow {
+    left: 60px;
+}
+
+.tooltip .keyhint-wrap {
+    padding: 5px 0 5px 0;
+}
+
+.tooltip-inner .keyhint {
+    color: #222;
+    font-size: 10px;
+    padding: 0 7px;
+    font-weight: bold;
+    display: inline-block;
+    border-radius: 2px;
+    border: 1px solid #CCC;
+    position: relative;
+    z-index: 1;
+    text-align: left;
+    clear: both;
+}
+
+.tooltip .keyhint .keyhint-label {
+    display: inline-block;
+}
+
+.tooltip-inner .keyhint::after {
+    content: "";
+    position: absolute;
+    border-radius: 2px;
+    height: 10px;
+    width: 100%;
+    z-index: 0;
+    bottom: -4px;
+    left: -1px;
+    border: 1px solid #CCC;
+    border-top: 0;
+}
+
+.radial-menu-tooltip {
+    background-color: rgba(255, 255, 255, 0.8);
+    display: none;
+    position: absolute;
+    width: 200px;
+}
+
+.radial-menu-background {
+    stroke: black;
+    stroke-opacity: 0.5;
+}
+
+.radial-menu-item {
+    fill: white;
+}
+
+.radial-menu-item:hover {
+    fill: #ececec;
+}
+
+.radial-menu-item:active {
+    fill: #ececec;
+}
+
+.radial-menu-item.disabled {
+    cursor: auto;
+    fill: rgba(255,255,255,.5);
+}
+
+.radial-menu .icon {
+    pointer-events: none;
+}
+
+.lasso-box {
+    fill-opacity:0.1;
+    stroke: #fff;
+    stroke-width: 1;
+    stroke-opacity: 1;
+    stroke-dasharray: 5, 5;
+}
+
+/* Media Queries
+------------------------------------------------------- */
+
+@media only screen and (max-width: 840px) {
+    #bar .label {display: none;}
+    #bar .icon.icon-pre-text { margin-right: 0;}
+    /* override hide for save button */
+    #bar .save .label { display: block;}
+}
+
+/* Scrollbars
+ ----------------------------------------------------- */
+
+::-webkit-scrollbar {
+    height: 20px;
+    overflow: visible;
+    width: 10px;
+    background: white;
+    border-left: 1px solid #DDD;
+}
+
+::-webkit-scrollbar-track {
+    background-clip: padding-box;
+    border: solid transparent;
+    border-width: 0;
+}
+
+::-webkit-scrollbar-thumb {
+    background-color: rgba(0,0,0,.2);
+    background-clip: padding-box;
+    border: solid transparent;
+    border-width: 3px 3px 3px 4px;
+    border-radius: 6px;
+}
+::-webkit-scrollbar-track:hover,
+::-webkit-scrollbar-track:active {
+    background-color: rgba(0,0,0,.05);
+}
+
+/* Intro walkthrough
+ ----------------------------------------------------- */
+
+.curtain-darkness {
+    pointer-events: all;
+    fill-opacity: 0.7;
+    fill: #222;
+    fill-rule: evenodd;
+}
+
+.intro-nav-wrap {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 30px;
+    padding: 10px;
+    z-index: 1001;
+}
+
+.intro-nav-wrap button.step {
+    width: 20%;
+}
+
+.intro-nav-wrap button.step.finished {
+    background: #8cd05f;
+}
+
+.intro-nav-wrap button.step .icon {
+    display: none;
+}
+
+.intro-nav-wrap button.step.finished .icon {
+    display: inline-block;
+}
+
+
+.curtain-tooltip .tooltip-inner {
+    text-align: left;
+    padding: 20px;
+}
+
+.curtain-tooltip .tooltip-inner {
+    font-size: 15px;
+}
+
+.curtain-tooltip .tooltip-inner .bold {
+    font-weight: bold;
+    display: block;
+    border-top: 1px solid #CCC;
+    margin-top: 10px;
+    margin-left: -20px;
+    margin-right: -20px;
+    padding: 10px 20px 0 20px;
+}
+
+.curtain-tooltip .tooltip-inner .bold:only-child {
+    border: 0;
+    padding: 0;
+    margin: 0;
+}
+
+.curtain-tooltip.intro-points-describe {
+    top: 133px !important;
+}
+
+/* Tooltip illustrations */
+
+.intro-points-add .tooltip-inner::before,
+.intro-areas-add .tooltip-inner::before,
+.intro-lines-add .tooltip-inner::before {
+    display: block;
+    content: "";
+    height: 80px;
+    width: 200px;
+    background:transparent url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat 0 -320px;
+}
+
+.intro-areas-add .tooltip-inner::before {
+    background-position: 0 -400px;
+}
+
+.intro-lines-add .tooltip-inner::before {
+    background-position: 0 -480px;
+}
+
+.huge-modal-button {
+    width: 100%;
+    height: auto;
+    padding: 20px;
+}
+
+.huge-modal-button .illustration {
+    height: 100px;
+    width: 100px;
+    background: rgba(0, 0, 0, 0) url(<%= asset_path("iD/img/sprite.svg") %>) no-repeat -301px -220px;
+    margin: auto;
+}
+/* glue: 0.3 hash: 5d99d90788 */
+.feature-waterway-stream,
+.feature-waterway-river,
+.feature-waterway-ditch,
+.feature-waterway-canal,
+.feature-railway-subway,
+.feature-railway-rail,
+.feature-railway-monorail,
+.feature-railway-light_rail,
+.feature-railway-disused,
+.feature-railway-abandoned,
+.feature-power-line,
+.feature-other-line,
+.feature-highway-unclassified,
+.feature-highway-trunk,
+.feature-highway-trunk-link,
+.feature-highway-track,
+.feature-highway-tertiary,
+.feature-highway-tertiary-link,
+.feature-highway-steps,
+.feature-highway-service,
+.feature-highway-secondary,
+.feature-highway-secondary-link,
+.feature-highway-road,
+.feature-highway-residential,
+.feature-highway-primary,
+.feature-highway-primary-link,
+.feature-highway-path,
+.feature-highway-motorway,
+.feature-highway-motorway-link,
+.feature-highway-living_street,
+.feature-highway-footway,
+.feature-highway-cycleway,
+.feature-highway-bridleway,
+.feature-category-water,
+.feature-category-roads,
+.feature-category-rail,
+.feature-category-path,
+.feature-zoo,
+.feature-x,
+.feature-wetland,
+.feature-water,
+.feature-waste-basket,
+.feature-warehouse,
+.feature-triangle,
+.feature-triangle-stroked,
+.feature-town-hall,
+.feature-toilets,
+.feature-theatre,
+.feature-tennis,
+.feature-swimming,
+.feature-star,
+.feature-star-stroked,
+.feature-square,
+.feature-square-stroked,
+.feature-soccer,
+.feature-slaughterhouse,
+.feature-skiing,
+.feature-shop,
+.feature-school,
+.feature-roadblock,
+.feature-restaurant,
+.feature-religious-muslim,
+.feature-religious-jewish,
+.feature-religious-christian,
+.feature-rail,
+.feature-rail-underground,
+.feature-rail-above,
+.feature-prison,
+.feature-post,
+.feature-police,
+.feature-place-of-worship,
+.feature-pitch,
+.feature-pharmacy,
+.feature-parking,
+.feature-parking-garage,
+.feature-park2,
+.feature-park,
+.feature-oil-well,
+.feature-museum,
+.feature-monument,
+.feature-minefield,
+.feature-marker,
+.feature-marker-stroked,
+.feature-london-underground,
+.feature-logging,
+.feature-lodging,
+.feature-library,
+.feature-industrial,
+.feature-hospital,
+.feature-heliport,
+.feature-harbor,
+.feature-grocery,
+.feature-golf,
+.feature-garden,
+.feature-fuel,
+.feature-fire-station,
+.feature-ferry,
+.feature-fast-food,
+.feature-embassy,
+.feature-danger,
+.feature-dam,
+.feature-cross,
+.feature-cricket,
+.feature-commercial,
+.feature-college,
+.feature-circle,
+.feature-circle-stroked,
+.feature-cinema,
+.feature-cemetery,
+.feature-campsite,
+.feature-cafe,
+.feature-bus,
+.feature-bicycle,
+.feature-beer,
+.feature-basketball,
+.feature-baseball,
+.feature-bar,
+.feature-bank,
+.feature-art-gallery,
+.feature-america-football,
+.feature-alcohol-shop,
+.feature-airport,
+.feature-airfield,
+.feature-zoo-18,
+.feature-x-18,
+.feature-wetland-18,
+.feature-water-18,
+.feature-waste-basket-18,
+.feature-warehouse-18,
+.feature-triangle-stroked-18,
+.feature-triangle-18,
+.feature-town-hall-18,
+.feature-toilets-18,
+.feature-theatre-18,
+.feature-tennis-18,
+.feature-swimming-18,
+.feature-star-stroked-18,
+.feature-star-18,
+.feature-square-stroked-18,
+.feature-square-18,
+.feature-soccer-18,
+.feature-slaughterhouse-18,
+.feature-skiing-18,
+.feature-shop-18,
+.feature-school-18,
+.feature-roadblock-18,
+.feature-restaurant-18,
+.feature-religious-muslim-18,
+.feature-religious-jewish-18,
+.feature-religious-christian-18,
+.feature-rail-underground-18,
+.feature-rail-above-18,
+.feature-rail-18,
+.feature-prison-18,
+.feature-post-18,
+.feature-police-18,
+.feature-place-of-worship-18,
+.feature-pitch-18,
+.feature-pharmacy-18,
+.feature-parking-garage-18,
+.feature-parking-18,
+.feature-park2-18,
+.feature-park-18,
+.feature-oil-well-18,
+.feature-museum-18,
+.feature-monument-18,
+.feature-minefield-18,
+.feature-marker-stroked-18,
+.feature-marker-18,
+.feature-london-underground-18,
+.feature-logging-18,
+.feature-lodging-18,
+.feature-library-18,
+.feature-industrial-18,
+.feature-hospital-18,
+.feature-heliport-18,
+.feature-harbor-18,
+.feature-grocery-18,
+.feature-golf-18,
+.feature-garden-18,
+.feature-fuel-18,
+.feature-fire-station-18,
+.feature-ferry-18,
+.feature-fast-food-18,
+.feature-embassy-18,
+.feature-danger-18,
+.feature-dam-18,
+.feature-cross-18,
+.feature-cricket-18,
+.feature-commercial-18,
+.feature-college-18,
+.feature-circle-stroked-18,
+.feature-circle-18,
+.feature-cinema-18,
+.feature-cemetery-18,
+.feature-campsite-18,
+.feature-cafe-18,
+.feature-bus-18,
+.feature-bicycle-18,
+.feature-beer-18,
+.feature-basketball-18,
+.feature-baseball-18,
+.feature-bar-18,
+.feature-bank-18,
+.feature-art-gallery-18,
+.feature-america-football-18,
+.feature-alcohol-shop-18,
+.feature-airport-18,
+.feature-airfield-18,
+.feature-zoo-12,
+.feature-x-12,
+.feature-wetland-12,
+.feature-water-12,
+.feature-waste-basket-12,
+.feature-warehouse-12,
+.feature-triangle-stroked-12,
+.feature-triangle-12,
+.feature-town-hall-12,
+.feature-toilets-12,
+.feature-theatre-12,
+.feature-tennis-12,
+.feature-swimming-12,
+.feature-star-stroked-12,
+.feature-star-12,
+.feature-square-stroked-12,
+.feature-square-12,
+.feature-soccer-12,
+.feature-slaughterhouse-12,
+.feature-skiing-12,
+.feature-shop-12,
+.feature-school-12,
+.feature-roadblock-12,
+.feature-restaurant-12,
+.feature-religious-muslim-12,
+.feature-religious-jewish-12,
+.feature-religious-christian-12,
+.feature-rail-underground-12,
+.feature-rail-above-12,
+.feature-rail-12,
+.feature-prison-12,
+.feature-post-12,
+.feature-police-12,
+.feature-place-of-worship-12,
+.feature-pitch-12,
+.feature-pharmacy-12,
+.feature-parking-garage-12,
+.feature-parking-12,
+.feature-park2-12,
+.feature-park-12,
+.feature-oil-well-12,
+.feature-museum-12,
+.feature-monument-12,
+.feature-minefield-12,
+.feature-marker-stroked-12,
+.feature-marker-12,
+.feature-london-underground-12,
+.feature-logging-12,
+.feature-lodging-12,
+.feature-library-12,
+.feature-industrial-12,
+.feature-hospital-12,
+.feature-heliport-12,
+.feature-harbor-12,
+.feature-grocery-12,
+.feature-golf-12,
+.feature-garden-12,
+.feature-fuel-12,
+.feature-fire-station-12,
+.feature-ferry-12,
+.feature-fast-food-12,
+.feature-embassy-12,
+.feature-danger-12,
+.feature-dam-12,
+.feature-cross-12,
+.feature-cricket-12,
+.feature-commercial-12,
+.feature-college-12,
+.feature-circle-stroked-12,
+.feature-circle-12,
+.feature-cinema-12,
+.feature-cemetery-12,
+.feature-campsite-12,
+.feature-cafe-12,
+.feature-bus-12,
+.feature-bicycle-12,
+.feature-beer-12,
+.feature-basketball-12,
+.feature-baseball-12,
+.feature-bar-12,
+.feature-bank-12,
+.feature-art-gallery-12,
+.feature-america-football-12,
+.feature-alcohol-shop-12,
+.feature-airport-12,
+.feature-airfield-12{background-image:url(<%= asset_path("iD/img/feature-icons.png") %>);background-repeat:no-repeat}
+.feature-waterway-stream{background-position:0px 0px;width:60px;height:60px;}
+.feature-waterway-river{background-position:-60px 0px;width:60px;height:60px;}
+.feature-waterway-ditch{background-position:0px -60px;width:60px;height:60px;}
+.feature-waterway-canal{background-position:-60px -60px;width:60px;height:60px;}
+.feature-railway-subway{background-position:-120px 0px;width:60px;height:60px;}
+.feature-railway-rail{background-position:-120px -60px;width:60px;height:60px;}
+.feature-railway-monorail{background-position:0px -120px;width:60px;height:60px;}
+.feature-railway-light_rail{background-position:-60px -120px;width:60px;height:60px;}
+.feature-railway-disused{background-position:-120px -120px;width:60px;height:60px;}
+.feature-railway-abandoned{background-position:-180px 0px;width:60px;height:60px;}
+.feature-power-line{background-position:-180px -60px;width:60px;height:60px;}
+.feature-other-line{background-position:-180px -120px;width:60px;height:60px;}
+.feature-highway-unclassified{background-position:0px -180px;width:60px;height:60px;}
+.feature-highway-trunk{background-position:-60px -180px;width:60px;height:60px;}
+.feature-highway-trunk-link{background-position:-120px -180px;width:60px;height:60px;}
+.feature-highway-track{background-position:-180px -180px;width:60px;height:60px;}
+.feature-highway-tertiary{background-position:-240px 0px;width:60px;height:60px;}
+.feature-highway-tertiary-link{background-position:-240px -60px;width:60px;height:60px;}
+.feature-highway-steps{background-position:-240px -120px;width:60px;height:60px;}
+.feature-highway-service{background-position:-240px -180px;width:60px;height:60px;}
+.feature-highway-secondary{background-position:0px -240px;width:60px;height:60px;}
+.feature-highway-secondary-link{background-position:-60px -240px;width:60px;height:60px;}
+.feature-highway-road{background-position:-120px -240px;width:60px;height:60px;}
+.feature-highway-residential{background-position:-180px -240px;width:60px;height:60px;}
+.feature-highway-primary{background-position:-240px -240px;width:60px;height:60px;}
+.feature-highway-primary-link{background-position:-300px 0px;width:60px;height:60px;}
+.feature-highway-path{background-position:-300px -60px;width:60px;height:60px;}
+.feature-highway-motorway{background-position:-300px -120px;width:60px;height:60px;}
+.feature-highway-motorway-link{background-position:-300px -180px;width:60px;height:60px;}
+.feature-highway-living_street{background-position:-300px -240px;width:60px;height:60px;}
+.feature-highway-footway{background-position:0px -300px;width:60px;height:60px;}
+.feature-highway-cycleway{background-position:-60px -300px;width:60px;height:60px;}
+.feature-highway-bridleway{background-position:-120px -300px;width:60px;height:60px;}
+.feature-category-water{background-position:-180px -300px;width:60px;height:60px;}
+.feature-category-roads{background-position:-240px -300px;width:60px;height:60px;}
+.feature-category-rail{background-position:-300px -300px;width:60px;height:60px;}
+.feature-category-path{background-position:-360px 0px;width:60px;height:60px;}
+.feature-zoo{background-position:-360px -60px;width:24px;height:24px;}
+.feature-x{background-position:-384px -60px;width:24px;height:24px;}
+.feature-wetland{background-position:-360px -84px;width:24px;height:24px;}
+.feature-water{background-position:-384px -84px;width:24px;height:24px;}
+.feature-waste-basket{background-position:-360px -108px;width:24px;height:24px;}
+.feature-warehouse{background-position:-384px -108px;width:24px;height:24px;}
+.feature-triangle{background-position:-360px -132px;width:24px;height:24px;}
+.feature-triangle-stroked{background-position:-384px -132px;width:24px;height:24px;}
+.feature-town-hall{background-position:-360px -156px;width:24px;height:24px;}
+.feature-toilets{background-position:-384px -156px;width:24px;height:24px;}
+.feature-theatre{background-position:-360px -180px;width:24px;height:24px;}
+.feature-tennis{background-position:-384px -180px;width:24px;height:24px;}
+.feature-swimming{background-position:-360px -204px;width:24px;height:24px;}
+.feature-star{background-position:-384px -204px;width:24px;height:24px;}
+.feature-star-stroked{background-position:-360px -228px;width:24px;height:24px;}
+.feature-square{background-position:-384px -228px;width:24px;height:24px;}
+.feature-square-stroked{background-position:-360px -252px;width:24px;height:24px;}
+.feature-soccer{background-position:-384px -252px;width:24px;height:24px;}
+.feature-slaughterhouse{background-position:-360px -276px;width:24px;height:24px;}
+.feature-skiing{background-position:-384px -276px;width:24px;height:24px;}
+.feature-shop{background-position:-360px -300px;width:24px;height:24px;}
+.feature-school{background-position:-384px -300px;width:24px;height:24px;}
+.feature-roadblock{background-position:-360px -324px;width:24px;height:24px;}
+.feature-restaurant{background-position:-384px -324px;width:24px;height:24px;}
+.feature-religious-muslim{background-position:0px -360px;width:24px;height:24px;}
+.feature-religious-jewish{background-position:-24px -360px;width:24px;height:24px;}
+.feature-religious-christian{background-position:-48px -360px;width:24px;height:24px;}
+.feature-rail{background-position:-72px -360px;width:24px;height:24px;}
+.feature-rail-underground{background-position:-96px -360px;width:24px;height:24px;}
+.feature-rail-above{background-position:-120px -360px;width:24px;height:24px;}
+.feature-prison{background-position:-144px -360px;width:24px;height:24px;}
+.feature-post{background-position:-168px -360px;width:24px;height:24px;}
+.feature-police{background-position:-192px -360px;width:24px;height:24px;}
+.feature-place-of-worship{background-position:-216px -360px;width:24px;height:24px;}
+.feature-pitch{background-position:-240px -360px;width:24px;height:24px;}
+.feature-pharmacy{background-position:-264px -360px;width:24px;height:24px;}
+.feature-parking{background-position:-288px -360px;width:24px;height:24px;}
+.feature-parking-garage{background-position:-312px -360px;width:24px;height:24px;}
+.feature-park2{background-position:-336px -360px;width:24px;height:24px;}
+.feature-park{background-position:-360px -360px;width:24px;height:24px;}
+.feature-oil-well{background-position:-384px -360px;width:24px;height:24px;}
+.feature-museum{background-position:0px -384px;width:24px;height:24px;}
+.feature-monument{background-position:-24px -384px;width:24px;height:24px;}
+.feature-minefield{background-position:-48px -384px;width:24px;height:24px;}
+.feature-marker{background-position:-72px -384px;width:24px;height:24px;}
+.feature-marker-stroked{background-position:-96px -384px;width:24px;height:24px;}
+.feature-london-underground{background-position:-120px -384px;width:24px;height:24px;}
+.feature-logging{background-position:-144px -384px;width:24px;height:24px;}
+.feature-lodging{background-position:-168px -384px;width:24px;height:24px;}
+.feature-library{background-position:-192px -384px;width:24px;height:24px;}
+.feature-industrial{background-position:-216px -384px;width:24px;height:24px;}
+.feature-hospital{background-position:-240px -384px;width:24px;height:24px;}
+.feature-heliport{background-position:-264px -384px;width:24px;height:24px;}
+.feature-harbor{background-position:-288px -384px;width:24px;height:24px;}
+.feature-grocery{background-position:-312px -384px;width:24px;height:24px;}
+.feature-golf{background-position:-336px -384px;width:24px;height:24px;}
+.feature-garden{background-position:-360px -384px;width:24px;height:24px;}
+.feature-fuel{background-position:-384px -384px;width:24px;height:24px;}
+.feature-fire-station{background-position:-420px 0px;width:24px;height:24px;}
+.feature-ferry{background-position:-420px -24px;width:24px;height:24px;}
+.feature-fast-food{background-position:-420px -48px;width:24px;height:24px;}
+.feature-embassy{background-position:-420px -72px;width:24px;height:24px;}
+.feature-danger{background-position:-420px -96px;width:24px;height:24px;}
+.feature-dam{background-position:-420px -120px;width:24px;height:24px;}
+.feature-cross{background-position:-420px -144px;width:24px;height:24px;}
+.feature-cricket{background-position:-420px -168px;width:24px;height:24px;}
+.feature-commercial{background-position:-420px -192px;width:24px;height:24px;}
+.feature-college{background-position:-420px -216px;width:24px;height:24px;}
+.feature-circle{background-position:-420px -240px;width:24px;height:24px;}
+.feature-circle-stroked{background-position:-420px -264px;width:24px;height:24px;}
+.feature-cinema{background-position:-420px -288px;width:24px;height:24px;}
+.feature-cemetery{background-position:-420px -312px;width:24px;height:24px;}
+.feature-campsite{background-position:-420px -336px;width:24px;height:24px;}
+.feature-cafe{background-position:-420px -360px;width:24px;height:24px;}
+.feature-bus{background-position:-420px -384px;width:24px;height:24px;}
+.feature-bicycle{background-position:0px -408px;width:24px;height:24px;}
+.feature-beer{background-position:-24px -408px;width:24px;height:24px;}
+.feature-basketball{background-position:-48px -408px;width:24px;height:24px;}
+.feature-baseball{background-position:-72px -408px;width:24px;height:24px;}
+.feature-bar{background-position:-96px -408px;width:24px;height:24px;}
+.feature-bank{background-position:-120px -408px;width:24px;height:24px;}
+.feature-art-gallery{background-position:-144px -408px;width:24px;height:24px;}
+.feature-america-football{background-position:-168px -408px;width:24px;height:24px;}
+.feature-alcohol-shop{background-position:-192px -408px;width:24px;height:24px;}
+.feature-airport{background-position:-216px -408px;width:24px;height:24px;}
+.feature-airfield{background-position:-240px -408px;width:24px;height:24px;}
+.feature-zoo-18{background-position:-264px -408px;width:18px;height:18px;}
+.feature-x-18{background-position:-282px -408px;width:18px;height:18px;}
+.feature-wetland-18{background-position:-300px -408px;width:18px;height:18px;}
+.feature-water-18{background-position:-318px -408px;width:18px;height:18px;}
+.feature-waste-basket-18{background-position:-336px -408px;width:18px;height:18px;}
+.feature-warehouse-18{background-position:-354px -408px;width:18px;height:18px;}
+.feature-triangle-stroked-18{background-position:-372px -408px;width:18px;height:18px;}
+.feature-triangle-18{background-position:-390px -408px;width:18px;height:18px;}
+.feature-town-hall-18{background-position:-408px -408px;width:18px;height:18px;}
+.feature-toilets-18{background-position:-426px -408px;width:18px;height:18px;}
+.feature-theatre-18{background-position:-444px 0px;width:18px;height:18px;}
+.feature-tennis-18{background-position:-444px -18px;width:18px;height:18px;}
+.feature-swimming-18{background-position:-444px -36px;width:18px;height:18px;}
+.feature-star-stroked-18{background-position:-444px -54px;width:18px;height:18px;}
+.feature-star-18{background-position:-444px -72px;width:18px;height:18px;}
+.feature-square-stroked-18{background-position:-444px -90px;width:18px;height:18px;}
+.feature-square-18{background-position:-444px -108px;width:18px;height:18px;}
+.feature-soccer-18{background-position:-444px -126px;width:18px;height:18px;}
+.feature-slaughterhouse-18{background-position:-444px -144px;width:18px;height:18px;}
+.feature-skiing-18{background-position:-444px -162px;width:18px;height:18px;}
+.feature-shop-18{background-position:-444px -180px;width:18px;height:18px;}
+.feature-school-18{background-position:-444px -198px;width:18px;height:18px;}
+.feature-roadblock-18{background-position:-444px -216px;width:18px;height:18px;}
+.feature-restaurant-18{background-position:-444px -234px;width:18px;height:18px;}
+.feature-religious-muslim-18{background-position:-444px -252px;width:18px;height:18px;}
+.feature-religious-jewish-18{background-position:-444px -270px;width:18px;height:18px;}
+.feature-religious-christian-18{background-position:-444px -288px;width:18px;height:18px;}
+.feature-rail-underground-18{background-position:-444px -306px;width:18px;height:18px;}
+.feature-rail-above-18{background-position:-444px -324px;width:18px;height:18px;}
+.feature-rail-18{background-position:-444px -342px;width:18px;height:18px;}
+.feature-prison-18{background-position:-444px -360px;width:18px;height:18px;}
+.feature-post-18{background-position:-444px -378px;width:18px;height:18px;}
+.feature-police-18{background-position:-444px -396px;width:18px;height:18px;}
+.feature-place-of-worship-18{background-position:-444px -414px;width:18px;height:18px;}
+.feature-pitch-18{background-position:0px -432px;width:18px;height:18px;}
+.feature-pharmacy-18{background-position:-18px -432px;width:18px;height:18px;}
+.feature-parking-garage-18{background-position:-36px -432px;width:18px;height:18px;}
+.feature-parking-18{background-position:-54px -432px;width:18px;height:18px;}
+.feature-park2-18{background-position:-72px -432px;width:18px;height:18px;}
+.feature-park-18{background-position:-90px -432px;width:18px;height:18px;}
+.feature-oil-well-18{background-position:-108px -432px;width:18px;height:18px;}
+.feature-museum-18{background-position:-126px -432px;width:18px;height:18px;}
+.feature-monument-18{background-position:-144px -432px;width:18px;height:18px;}
+.feature-minefield-18{background-position:-162px -432px;width:18px;height:18px;}
+.feature-marker-stroked-18{background-position:-180px -432px;width:18px;height:18px;}
+.feature-marker-18{background-position:-198px -432px;width:18px;height:18px;}
+.feature-london-underground-18{background-position:-216px -432px;width:18px;height:18px;}
+.feature-logging-18{background-position:-234px -432px;width:18px;height:18px;}
+.feature-lodging-18{background-position:-252px -432px;width:18px;height:18px;}
+.feature-library-18{background-position:-270px -432px;width:18px;height:18px;}
+.feature-industrial-18{background-position:-288px -432px;width:18px;height:18px;}
+.feature-hospital-18{background-position:-306px -432px;width:18px;height:18px;}
+.feature-heliport-18{background-position:-324px -432px;width:18px;height:18px;}
+.feature-harbor-18{background-position:-342px -432px;width:18px;height:18px;}
+.feature-grocery-18{background-position:-360px -432px;width:18px;height:18px;}
+.feature-golf-18{background-position:-378px -432px;width:18px;height:18px;}
+.feature-garden-18{background-position:-396px -432px;width:18px;height:18px;}
+.feature-fuel-18{background-position:-414px -432px;width:18px;height:18px;}
+.feature-fire-station-18{background-position:-432px -432px;width:18px;height:18px;}
+.feature-ferry-18{background-position:-462px 0px;width:18px;height:18px;}
+.feature-fast-food-18{background-position:-462px -18px;width:18px;height:18px;}
+.feature-embassy-18{background-position:-462px -36px;width:18px;height:18px;}
+.feature-danger-18{background-position:-462px -54px;width:18px;height:18px;}
+.feature-dam-18{background-position:-462px -72px;width:18px;height:18px;}
+.feature-cross-18{background-position:-462px -90px;width:18px;height:18px;}
+.feature-cricket-18{background-position:-462px -108px;width:18px;height:18px;}
+.feature-commercial-18{background-position:-462px -126px;width:18px;height:18px;}
+.feature-college-18{background-position:-462px -144px;width:18px;height:18px;}
+.feature-circle-stroked-18{background-position:-462px -162px;width:18px;height:18px;}
+.feature-circle-18{background-position:-462px -180px;width:18px;height:18px;}
+.feature-cinema-18{background-position:-462px -198px;width:18px;height:18px;}
+.feature-cemetery-18{background-position:-462px -216px;width:18px;height:18px;}
+.feature-campsite-18{background-position:-462px -234px;width:18px;height:18px;}
+.feature-cafe-18{background-position:-462px -252px;width:18px;height:18px;}
+.feature-bus-18{background-position:-462px -270px;width:18px;height:18px;}
+.feature-bicycle-18{background-position:-462px -288px;width:18px;height:18px;}
+.feature-beer-18{background-position:-462px -306px;width:18px;height:18px;}
+.feature-basketball-18{background-position:-462px -324px;width:18px;height:18px;}
+.feature-baseball-18{background-position:-462px -342px;width:18px;height:18px;}
+.feature-bar-18{background-position:-462px -360px;width:18px;height:18px;}
+.feature-bank-18{background-position:-462px -378px;width:18px;height:18px;}
+.feature-art-gallery-18{background-position:-462px -396px;width:18px;height:18px;}
+.feature-america-football-18{background-position:-462px -414px;width:18px;height:18px;}
+.feature-alcohol-shop-18{background-position:-462px -432px;width:18px;height:18px;}
+.feature-airport-18{background-position:0px -450px;width:18px;height:18px;}
+.feature-airfield-18{background-position:-18px -450px;width:18px;height:18px;}
+.feature-zoo-12{background-position:-408px -60px;width:12px;height:12px;}
+.feature-x-12{background-position:-408px -72px;width:12px;height:12px;}
+.feature-wetland-12{background-position:-408px -84px;width:12px;height:12px;}
+.feature-water-12{background-position:-408px -96px;width:12px;height:12px;}
+.feature-waste-basket-12{background-position:-408px -108px;width:12px;height:12px;}
+.feature-warehouse-12{background-position:-408px -120px;width:12px;height:12px;}
+.feature-triangle-stroked-12{background-position:-408px -132px;width:12px;height:12px;}
+.feature-triangle-12{background-position:-408px -144px;width:12px;height:12px;}
+.feature-town-hall-12{background-position:-408px -156px;width:12px;height:12px;}
+.feature-toilets-12{background-position:-408px -168px;width:12px;height:12px;}
+.feature-theatre-12{background-position:-408px -180px;width:12px;height:12px;}
+.feature-tennis-12{background-position:-408px -192px;width:12px;height:12px;}
+.feature-swimming-12{background-position:-408px -204px;width:12px;height:12px;}
+.feature-star-stroked-12{background-position:-408px -216px;width:12px;height:12px;}
+.feature-star-12{background-position:-408px -228px;width:12px;height:12px;}
+.feature-square-stroked-12{background-position:-408px -240px;width:12px;height:12px;}
+.feature-square-12{background-position:-408px -252px;width:12px;height:12px;}
+.feature-soccer-12{background-position:-408px -264px;width:12px;height:12px;}
+.feature-slaughterhouse-12{background-position:-408px -276px;width:12px;height:12px;}
+.feature-skiing-12{background-position:-408px -288px;width:12px;height:12px;}
+.feature-shop-12{background-position:-408px -300px;width:12px;height:12px;}
+.feature-school-12{background-position:-408px -312px;width:12px;height:12px;}
+.feature-roadblock-12{background-position:-408px -324px;width:12px;height:12px;}
+.feature-restaurant-12{background-position:-408px -336px;width:12px;height:12px;}
+.feature-religious-muslim-12{background-position:-360px -348px;width:12px;height:12px;}
+.feature-religious-jewish-12{background-position:-372px -348px;width:12px;height:12px;}
+.feature-religious-christian-12{background-position:-384px -348px;width:12px;height:12px;}
+.feature-rail-underground-12{background-position:-396px -348px;width:12px;height:12px;}
+.feature-rail-above-12{background-position:-408px -348px;width:12px;height:12px;}
+.feature-rail-12{background-position:-408px -360px;width:12px;height:12px;}
+.feature-prison-12{background-position:-408px -372px;width:12px;height:12px;}
+.feature-post-12{background-position:-408px -384px;width:12px;height:12px;}
+.feature-police-12{background-position:-408px -396px;width:12px;height:12px;}
+.feature-place-of-worship-12{background-position:-450px -432px;width:12px;height:12px;}
+.feature-pitch-12{background-position:-36px -450px;width:12px;height:12px;}
+.feature-pharmacy-12{background-position:-48px -450px;width:12px;height:12px;}
+.feature-parking-garage-12{background-position:-60px -450px;width:12px;height:12px;}
+.feature-parking-12{background-position:-72px -450px;width:12px;height:12px;}
+.feature-park2-12{background-position:-84px -450px;width:12px;height:12px;}
+.feature-park-12{background-position:-96px -450px;width:12px;height:12px;}
+.feature-oil-well-12{background-position:-108px -450px;width:12px;height:12px;}
+.feature-museum-12{background-position:-120px -450px;width:12px;height:12px;}
+.feature-monument-12{background-position:-132px -450px;width:12px;height:12px;}
+.feature-minefield-12{background-position:-144px -450px;width:12px;height:12px;}
+.feature-marker-stroked-12{background-position:-156px -450px;width:12px;height:12px;}
+.feature-marker-12{background-position:-168px -450px;width:12px;height:12px;}
+.feature-london-underground-12{background-position:-180px -450px;width:12px;height:12px;}
+.feature-logging-12{background-position:-192px -450px;width:12px;height:12px;}
+.feature-lodging-12{background-position:-204px -450px;width:12px;height:12px;}
+.feature-library-12{background-position:-216px -450px;width:12px;height:12px;}
+.feature-industrial-12{background-position:-228px -450px;width:12px;height:12px;}
+.feature-hospital-12{background-position:-240px -450px;width:12px;height:12px;}
+.feature-heliport-12{background-position:-252px -450px;width:12px;height:12px;}
+.feature-harbor-12{background-position:-264px -450px;width:12px;height:12px;}
+.feature-grocery-12{background-position:-276px -450px;width:12px;height:12px;}
+.feature-golf-12{background-position:-288px -450px;width:12px;height:12px;}
+.feature-garden-12{background-position:-300px -450px;width:12px;height:12px;}
+.feature-fuel-12{background-position:-312px -450px;width:12px;height:12px;}
+.feature-fire-station-12{background-position:-324px -450px;width:12px;height:12px;}
+.feature-ferry-12{background-position:-336px -450px;width:12px;height:12px;}
+.feature-fast-food-12{background-position:-348px -450px;width:12px;height:12px;}
+.feature-embassy-12{background-position:-360px -450px;width:12px;height:12px;}
+.feature-danger-12{background-position:-372px -450px;width:12px;height:12px;}
+.feature-dam-12{background-position:-384px -450px;width:12px;height:12px;}
+.feature-cross-12{background-position:-396px -450px;width:12px;height:12px;}
+.feature-cricket-12{background-position:-408px -450px;width:12px;height:12px;}
+.feature-commercial-12{background-position:-420px -450px;width:12px;height:12px;}
+.feature-college-12{background-position:-432px -450px;width:12px;height:12px;}
+.feature-circle-stroked-12{background-position:-444px -450px;width:12px;height:12px;}
+.feature-circle-12{background-position:-456px -450px;width:12px;height:12px;}
+.feature-cinema-12{background-position:-468px -450px;width:12px;height:12px;}
+.feature-cemetery-12{background-position:0px -468px;width:12px;height:12px;}
+.feature-campsite-12{background-position:-12px -468px;width:12px;height:12px;}
+.feature-cafe-12{background-position:-24px -468px;width:12px;height:12px;}
+.feature-bus-12{background-position:-36px -468px;width:12px;height:12px;}
+.feature-bicycle-12{background-position:-48px -468px;width:12px;height:12px;}
+.feature-beer-12{background-position:-60px -468px;width:12px;height:12px;}
+.feature-basketball-12{background-position:-72px -468px;width:12px;height:12px;}
+.feature-baseball-12{background-position:-84px -468px;width:12px;height:12px;}
+.feature-bar-12{background-position:-96px -468px;width:12px;height:12px;}
+.feature-bank-12{background-position:-108px -468px;width:12px;height:12px;}
+.feature-art-gallery-12{background-position:-120px -468px;width:12px;height:12px;}
+.feature-america-football-12{background-position:-132px -468px;width:12px;height:12px;}
+.feature-alcohol-shop-12{background-position:-144px -468px;width:12px;height:12px;}
+.feature-airport-12{background-position:-156px -468px;width:12px;height:12px;}
+.feature-airfield-12{background-position:-168px -468px;width:12px;height:12px;}
diff --git a/vendor/assets/iD/iD.js b/vendor/assets/iD/iD.js
new file mode 100644 (file)
index 0000000..11dd2f7
--- /dev/null
@@ -0,0 +1,70963 @@
+(function(exports) {
+
+  var bootstrap = (typeof exports.bootstrap === "object") ?
+    exports.bootstrap :
+    (exports.bootstrap = {});
+
+  bootstrap.tooltip = function() {
+
+    var tooltip = function(selection) {
+        selection.each(setup);
+      },
+      animation = d3.functor(false),
+      html = d3.functor(false),
+      title = function() {
+        var title = this.getAttribute("data-original-title");
+        if (title) {
+          return title;
+        } else {
+          title = this.getAttribute("title");
+          this.removeAttribute("title");
+          this.setAttribute("data-original-title", title);
+        }
+        return title;
+      },
+      over = "mouseenter.tooltip",
+      out = "mouseleave.tooltip",
+      placements = "top left bottom right".split(" "),
+      placement = d3.functor("top");
+
+    tooltip.title = function(_) {
+      if (arguments.length) {
+        title = d3.functor(_);
+        return tooltip;
+      } else {
+        return title;
+      }
+    };
+
+    tooltip.html = function(_) {
+      if (arguments.length) {
+        html = d3.functor(_);
+        return tooltip;
+      } else {
+        return html;
+      }
+    };
+
+    tooltip.placement = function(_) {
+      if (arguments.length) {
+        placement = d3.functor(_);
+        return tooltip;
+      } else {
+        return placement;
+      }
+    };
+
+    tooltip.show = function(selection) {
+      selection.each(show);
+    };
+
+    tooltip.hide = function(selection) {
+      selection.each(hide);
+    };
+
+    tooltip.toggle = function(selection) {
+      selection.each(toggle);
+    };
+
+    tooltip.destroy = function(selection) {
+      selection
+        .on(over, null)
+        .on(out, null)
+        .attr("title", function() {
+          return this.getAttribute("data-original-title") || this.getAttribute("title");
+        })
+        .attr("data-original-title", null)
+        .select(".tooltip")
+        .remove();
+    };
+
+    function setup() {
+      var root = d3.select(this),
+          animate = animation.apply(this, arguments),
+          tip = root.append("div")
+            .attr("class", "tooltip");
+
+      if (animate) {
+        tip.classed("fade", true);
+      }
+
+      // TODO "inside" checks?
+
+      tip.append("div")
+        .attr("class", "tooltip-arrow");
+      tip.append("div")
+        .attr("class", "tooltip-inner");
+
+      var place = placement.apply(this, arguments);
+      tip.classed(place, true);
+
+      root.on(over, show);
+      root.on(out, hide);
+    }
+
+    function show() {
+      var root = d3.select(this),
+          content = title.apply(this, arguments),
+          tip = root.select(".tooltip")
+            .classed("in", true),
+          markup = html.apply(this, arguments),
+          innercontent = tip.select(".tooltip-inner")[markup ? "html" : "text"](content),
+          place = placement.apply(this, arguments),
+          outer = getPosition(root.node()),
+          inner = getPosition(tip.node()),
+          pos;
+
+      switch (place) {
+        case "top":
+          pos = {x: outer.x + (outer.w - inner.w) / 2, y: outer.y - inner.h};
+          break;
+        case "right":
+          pos = {x: outer.x + outer.w, y: outer.y + (outer.h - inner.h) / 2};
+          break;
+        case "left":
+          pos = {x: outer.x - inner.w, y: outer.y + (outer.h - inner.h) / 2};
+          break;
+        case "bottom":
+          pos = {x: Math.max(0, outer.x + (outer.w - inner.w) / 2), y: outer.y + outer.h};
+          break;
+      }
+
+      tip.style(pos ?
+        {left: ~~pos.x + "px", top: ~~pos.y + "px"} :
+        {left: null, top: null});
+
+      this.tooltipVisible = true;
+    }
+
+    function hide() {
+      d3.select(this).select(".tooltip")
+        .classed("in", false);
+
+      this.tooltipVisible = false;
+    }
+
+    function toggle() {
+      if (this.tooltipVisible) {
+        hide.apply(this, arguments);
+      } else {
+        show.apply(this, arguments);
+      }
+    }
+
+    return tooltip;
+  };
+
+  function getPosition(node) {
+    var mode = d3.select(node).style('position');
+    if (mode === 'absolute' || mode === 'static') {
+      return {
+        x: node.offsetLeft,
+        y: node.offsetTop,
+        w: node.offsetWidth,
+        h: node.offsetHeight
+      };
+    } else {
+      return {
+        x: 0,
+        y: 0,
+        w: node.offsetWidth,
+        h: node.offsetHeight
+      };
+    }
+  }
+
+})(this);
+d3 = (function(){
+  var d3 = {version: "3.1.4"}; // semver
+d3.ascending = function(a, b) {
+  return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+};
+d3.descending = function(a, b) {
+  return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
+};
+d3.min = function(array, f) {
+  var i = -1,
+      n = array.length,
+      a,
+      b;
+  if (arguments.length === 1) {
+    while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+    while (++i < n) if ((b = array[i]) != null && a > b) a = b;
+  } else {
+    while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+    while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
+  }
+  return a;
+};
+d3.max = function(array, f) {
+  var i = -1,
+      n = array.length,
+      a,
+      b;
+  if (arguments.length === 1) {
+    while (++i < n && ((a = array[i]) == null || a != a)) a = undefined;
+    while (++i < n) if ((b = array[i]) != null && b > a) a = b;
+  } else {
+    while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+    while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
+  }
+  return a;
+};
+d3.extent = function(array, f) {
+  var i = -1,
+      n = array.length,
+      a,
+      b,
+      c;
+  if (arguments.length === 1) {
+    while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined;
+    while (++i < n) if ((b = array[i]) != null) {
+      if (a > b) a = b;
+      if (c < b) c = b;
+    }
+  } else {
+    while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined;
+    while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
+      if (a > b) a = b;
+      if (c < b) c = b;
+    }
+  }
+  return [a, c];
+};
+d3.sum = function(array, f) {
+  var s = 0,
+      n = array.length,
+      a,
+      i = -1;
+
+  if (arguments.length === 1) {
+    while (++i < n) if (!isNaN(a = +array[i])) s += a;
+  } else {
+    while (++i < n) if (!isNaN(a = +f.call(array, array[i], i))) s += a;
+  }
+
+  return s;
+};
+function d3_number(x) {
+  return x != null && !isNaN(x);
+}
+
+d3.mean = function(array, f) {
+  var n = array.length,
+      a,
+      m = 0,
+      i = -1,
+      j = 0;
+  if (arguments.length === 1) {
+    while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j;
+  } else {
+    while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j;
+  }
+  return j ? m : undefined;
+};
+// R-7 per <http://en.wikipedia.org/wiki/Quantile>
+d3.quantile = function(values, p) {
+  var H = (values.length - 1) * p + 1,
+      h = Math.floor(H),
+      v = +values[h - 1],
+      e = H - h;
+  return e ? v + e * (values[h] - v) : v;
+};
+
+d3.median = function(array, f) {
+  if (arguments.length > 1) array = array.map(f);
+  array = array.filter(d3_number);
+  return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined;
+};
+d3.bisector = function(f) {
+  return {
+    left: function(a, x, lo, hi) {
+      if (arguments.length < 3) lo = 0;
+      if (arguments.length < 4) hi = a.length;
+      while (lo < hi) {
+        var mid = lo + hi >>> 1;
+        if (f.call(a, a[mid], mid) < x) lo = mid + 1;
+        else hi = mid;
+      }
+      return lo;
+    },
+    right: function(a, x, lo, hi) {
+      if (arguments.length < 3) lo = 0;
+      if (arguments.length < 4) hi = a.length;
+      while (lo < hi) {
+        var mid = lo + hi >>> 1;
+        if (x < f.call(a, a[mid], mid)) hi = mid;
+        else lo = mid + 1;
+      }
+      return lo;
+    }
+  };
+};
+
+var d3_bisector = d3.bisector(function(d) { return d; });
+d3.bisectLeft = d3_bisector.left;
+d3.bisect = d3.bisectRight = d3_bisector.right;
+d3.shuffle = function(array) {
+  var m = array.length, t, i;
+  while (m) {
+    i = Math.random() * m-- | 0;
+    t = array[m], array[m] = array[i], array[i] = t;
+  }
+  return array;
+};
+d3.permute = function(array, indexes) {
+  var permutes = [],
+      i = -1,
+      n = indexes.length;
+  while (++i < n) permutes[i] = array[indexes[i]];
+  return permutes;
+};
+
+d3.zip = function() {
+  if (!(n = arguments.length)) return [];
+  for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); ++i < m;) {
+    for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n;) {
+      zip[j] = arguments[j][i];
+    }
+  }
+  return zips;
+};
+
+function d3_zipLength(d) {
+  return d.length;
+}
+
+d3.transpose = function(matrix) {
+  return d3.zip.apply(d3, matrix);
+};
+d3.keys = function(map) {
+  var keys = [];
+  for (var key in map) keys.push(key);
+  return keys;
+};
+d3.values = function(map) {
+  var values = [];
+  for (var key in map) values.push(map[key]);
+  return values;
+};
+d3.entries = function(map) {
+  var entries = [];
+  for (var key in map) entries.push({key: key, value: map[key]});
+  return entries;
+};
+d3.merge = function(arrays) {
+  return Array.prototype.concat.apply([], arrays);
+};
+d3.range = function(start, stop, step) {
+  if (arguments.length < 3) {
+    step = 1;
+    if (arguments.length < 2) {
+      stop = start;
+      start = 0;
+    }
+  }
+  if ((stop - start) / step === Infinity) throw new Error("infinite range");
+  var range = [],
+       k = d3_range_integerScale(Math.abs(step)),
+       i = -1,
+       j;
+  start *= k, stop *= k, step *= k;
+  if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k);
+  else while ((j = start + step * ++i) < stop) range.push(j / k);
+  return range;
+};
+
+function d3_range_integerScale(x) {
+  var k = 1;
+  while (x * k % 1) k *= 10;
+  return k;
+}
+function d3_class(ctor, properties) {
+  try {
+    for (var key in properties) {
+      Object.defineProperty(ctor.prototype, key, {
+        value: properties[key],
+        enumerable: false
+      });
+    }
+  } catch (e) {
+    ctor.prototype = properties;
+  }
+}
+
+d3.map = function(object) {
+  var map = new d3_Map;
+  for (var key in object) map.set(key, object[key]);
+  return map;
+};
+
+function d3_Map() {}
+
+d3_class(d3_Map, {
+  has: function(key) {
+    return d3_map_prefix + key in this;
+  },
+  get: function(key) {
+    return this[d3_map_prefix + key];
+  },
+  set: function(key, value) {
+    return this[d3_map_prefix + key] = value;
+  },
+  remove: function(key) {
+    key = d3_map_prefix + key;
+    return key in this && delete this[key];
+  },
+  keys: function() {
+    var keys = [];
+    this.forEach(function(key) { keys.push(key); });
+    return keys;
+  },
+  values: function() {
+    var values = [];
+    this.forEach(function(key, value) { values.push(value); });
+    return values;
+  },
+  entries: function() {
+    var entries = [];
+    this.forEach(function(key, value) { entries.push({key: key, value: value}); });
+    return entries;
+  },
+  forEach: function(f) {
+    for (var key in this) {
+      if (key.charCodeAt(0) === d3_map_prefixCode) {
+        f.call(this, key.substring(1), this[key]);
+      }
+    }
+  }
+});
+
+var d3_map_prefix = "\0", // prevent collision with built-ins
+    d3_map_prefixCode = d3_map_prefix.charCodeAt(0);
+
+d3.nest = function() {
+  var nest = {},
+      keys = [],
+      sortKeys = [],
+      sortValues,
+      rollup;
+
+  function map(mapType, array, depth) {
+    if (depth >= keys.length) return rollup
+        ? rollup.call(nest, array) : (sortValues
+        ? array.sort(sortValues)
+        : array);
+
+    var i = -1,
+        n = array.length,
+        key = keys[depth++],
+        keyValue,
+        object,
+        setter,
+        valuesByKey = new d3_Map,
+        values;
+
+    while (++i < n) {
+      if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
+        values.push(object);
+      } else {
+        valuesByKey.set(keyValue, [object]);
+      }
+    }
+
+    if (mapType) {
+      object = mapType();
+      setter = function(keyValue, values) {
+        object.set(keyValue, map(mapType, values, depth));
+      };
+    } else {
+      object = {};
+      setter = function(keyValue, values) {
+        object[keyValue] = map(mapType, values, depth);
+      };
+    }
+
+    valuesByKey.forEach(setter);
+    return object;
+  }
+
+  function entries(map, depth) {
+    if (depth >= keys.length) return map;
+
+    var array = [],
+        sortKey = sortKeys[depth++];
+
+    map.forEach(function(key, keyMap) {
+      array.push({key: key, values: entries(keyMap, depth)});
+    });
+
+    return sortKey
+        ? array.sort(function(a, b) { return sortKey(a.key, b.key); })
+        : array;
+  }
+
+  nest.map = function(array, mapType) {
+    return map(mapType, array, 0);
+  };
+
+  nest.entries = function(array) {
+    return entries(map(d3.map, array, 0), 0);
+  };
+
+  nest.key = function(d) {
+    keys.push(d);
+    return nest;
+  };
+
+  // Specifies the order for the most-recently specified key.
+  // Note: only applies to entries. Map keys are unordered!
+  nest.sortKeys = function(order) {
+    sortKeys[keys.length - 1] = order;
+    return nest;
+  };
+
+  // Specifies the order for leaf values.
+  // Applies to both maps and entries array.
+  nest.sortValues = function(order) {
+    sortValues = order;
+    return nest;
+  };
+
+  nest.rollup = function(f) {
+    rollup = f;
+    return nest;
+  };
+
+  return nest;
+};
+
+d3.set = function(array) {
+  var set = new d3_Set();
+  if (array) for (var i = 0; i < array.length; i++) set.add(array[i]);
+  return set;
+};
+
+function d3_Set() {}
+
+d3_class(d3_Set, {
+  has: function(value) {
+    return d3_map_prefix + value in this;
+  },
+  add: function(value) {
+    this[d3_map_prefix + value] = true;
+    return value;
+  },
+  remove: function(value) {
+    value = d3_map_prefix + value;
+    return value in this && delete this[value];
+  },
+  values: function() {
+    var values = [];
+    this.forEach(function(value) {
+      values.push(value);
+    });
+    return values;
+  },
+  forEach: function(f) {
+    for (var value in this) {
+      if (value.charCodeAt(0) === d3_map_prefixCode) {
+        f.call(this, value.substring(1));
+      }
+    }
+  }
+});
+d3.behavior = {};
+var d3_document = document,
+    d3_window = window;
+// Copies a variable number of methods from source to target.
+d3.rebind = function(target, source) {
+  var i = 1, n = arguments.length, method;
+  while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
+  return target;
+};
+
+// Method is assumed to be a standard D3 getter-setter:
+// If passed with no arguments, gets the value.
+// If passed with arguments, sets the value and returns the target.
+function d3_rebind(target, source, method) {
+  return function() {
+    var value = method.apply(source, arguments);
+    return value === source ? target : value;
+  };
+}
+
+d3.dispatch = function() {
+  var dispatch = new d3_dispatch,
+      i = -1,
+      n = arguments.length;
+  while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+  return dispatch;
+};
+
+function d3_dispatch() {}
+
+d3_dispatch.prototype.on = function(type, listener) {
+  var i = type.indexOf("."),
+      name = "";
+
+  // Extract optional namespace, e.g., "click.foo"
+  if (i >= 0) {
+    name = type.substring(i + 1);
+    type = type.substring(0, i);
+  }
+
+  if (type) return arguments.length < 2
+      ? this[type].on(name)
+      : this[type].on(name, listener);
+
+  if (arguments.length === 2) {
+    if (listener == null) for (type in this) {
+      if (this.hasOwnProperty(type)) this[type].on(name, null);
+    }
+    return this;
+  }
+};
+
+function d3_dispatch_event(dispatch) {
+  var listeners = [],
+      listenerByName = new d3_Map;
+
+  function event() {
+    var z = listeners, // defensive reference
+        i = -1,
+        n = z.length,
+        l;
+    while (++i < n) if (l = z[i].on) l.apply(this, arguments);
+    return dispatch;
+  }
+
+  event.on = function(name, listener) {
+    var l = listenerByName.get(name),
+        i;
+
+    // return the current listener, if any
+    if (arguments.length < 2) return l && l.on;
+
+    // remove the old listener, if any (with copy-on-write)
+    if (l) {
+      l.on = null;
+      listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
+      listenerByName.remove(name);
+    }
+
+    // add the new listener, if any
+    if (listener) listeners.push(listenerByName.set(name, {on: listener}));
+
+    return dispatch;
+  };
+
+  return event;
+}
+
+d3.event = null;
+
+function d3_eventCancel() {
+  d3.event.stopPropagation();
+  d3.event.preventDefault();
+}
+
+function d3_eventSource() {
+  var e = d3.event, s;
+  while (s = e.sourceEvent) e = s;
+  return e;
+}
+
+// Like d3.dispatch, but for custom events abstracting native UI events. These
+// events have a target component (such as a brush), a target element (such as
+// the svg:g element containing the brush) and the standard arguments `d` (the
+// target element's data) and `i` (the selection index of the target element).
+function d3_eventDispatch(target) {
+  var dispatch = new d3_dispatch,
+      i = 0,
+      n = arguments.length;
+
+  while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+
+  // Creates a dispatch context for the specified `thiz` (typically, the target
+  // DOM element that received the source event) and `argumentz` (typically, the
+  // data `d` and index `i` of the target element). The returned function can be
+  // used to dispatch an event to any registered listeners; the function takes a
+  // single argument as input, being the event to dispatch. The event must have
+  // a "type" attribute which corresponds to a type registered in the
+  // constructor. This context will automatically populate the "sourceEvent" and
+  // "target" attributes of the event, as well as setting the `d3.event` global
+  // for the duration of the notification.
+  dispatch.of = function(thiz, argumentz) {
+    return function(e1) {
+      try {
+        var e0 =
+        e1.sourceEvent = d3.event;
+        e1.target = target;
+        d3.event = e1;
+        dispatch[e1.type].apply(thiz, argumentz);
+      } finally {
+        d3.event = e0;
+      }
+    };
+  };
+
+  return dispatch;
+}
+
+d3.mouse = function(container) {
+  return d3_mousePoint(container, d3_eventSource());
+};
+
+// https://bugs.webkit.org/show_bug.cgi?id=44083
+var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0;
+
+function d3_mousePoint(container, e) {
+  var svg = container.ownerSVGElement || container;
+  if (svg.createSVGPoint) {
+    var point = svg.createSVGPoint();
+    if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) {
+      svg = d3.select(d3_document.body).append("svg")
+          .style("position", "absolute")
+          .style("top", 0)
+          .style("left", 0);
+      var ctm = svg[0][0].getScreenCTM();
+      d3_mouse_bug44083 = !(ctm.f || ctm.e);
+      svg.remove();
+    }
+    if (d3_mouse_bug44083) {
+      point.x = e.pageX;
+      point.y = e.pageY;
+    } else {
+      point.x = e.clientX;
+      point.y = e.clientY;
+    }
+    point = point.matrixTransform(container.getScreenCTM().inverse());
+    return [point.x, point.y];
+  }
+  var rect = container.getBoundingClientRect();
+  return [e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop];
+};
+
+var d3_array = d3_arraySlice; // conversion for NodeLists
+
+function d3_arrayCopy(pseudoarray) {
+  var i = -1, n = pseudoarray.length, array = [];
+  while (++i < n) array.push(pseudoarray[i]);
+  return array;
+}
+
+function d3_arraySlice(pseudoarray) {
+  return Array.prototype.slice.call(pseudoarray);
+}
+
+try {
+  d3_array(d3_document.documentElement.childNodes)[0].nodeType;
+} catch(e) {
+  d3_array = d3_arrayCopy;
+}
+
+var d3_arraySubclass = [].__proto__?
+
+// Until ECMAScript supports array subclassing, prototype injection works well.
+function(array, prototype) {
+  array.__proto__ = prototype;
+}:
+
+// And if your browser doesn't support __proto__, we'll use direct extension.
+function(array, prototype) {
+  for (var property in prototype) array[property] = prototype[property];
+};
+
+d3.touches = function(container, touches) {
+  if (arguments.length < 2) touches = d3_eventSource().touches;
+  return touches ? d3_array(touches).map(function(touch) {
+    var point = d3_mousePoint(container, touch);
+    point.identifier = touch.identifier;
+    return point;
+  }) : [];
+};
+
+function d3_selection(groups) {
+  d3_arraySubclass(groups, d3_selectionPrototype);
+  return groups;
+}
+
+var d3_select = function(s, n) { return n.querySelector(s); },
+    d3_selectAll = function(s, n) { return n.querySelectorAll(s); },
+    d3_selectRoot = d3_document.documentElement,
+    d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector,
+    d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); };
+
+// Prefer Sizzle, if available.
+if (typeof Sizzle === "function") {
+  d3_select = function(s, n) { return Sizzle(s, n)[0] || null; };
+  d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); };
+  d3_selectMatches = Sizzle.matchesSelector;
+}
+
+var d3_selectionPrototype = [];
+
+d3.selection = function() {
+  return d3_selectionRoot;
+};
+
+d3.selection.prototype = d3_selectionPrototype;
+
+
+d3_selectionPrototype.select = function(selector) {
+  var subgroups = [],
+      subgroup,
+      subnode,
+      group,
+      node;
+
+  if (typeof selector !== "function") selector = d3_selection_selector(selector);
+
+  for (var j = -1, m = this.length; ++j < m;) {
+    subgroups.push(subgroup = []);
+    subgroup.parentNode = (group = this[j]).parentNode;
+    for (var i = -1, n = group.length; ++i < n;) {
+      if (node = group[i]) {
+        subgroup.push(subnode = selector.call(node, node.__data__, i));
+        if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
+      } else {
+        subgroup.push(null);
+      }
+    }
+  }
+
+  return d3_selection(subgroups);
+};
+
+function d3_selection_selector(selector) {
+  return function() {
+    return d3_select(selector, this);
+  };
+}
+
+d3_selectionPrototype.selectAll = function(selector) {
+  var subgroups = [],
+      subgroup,
+      node;
+
+  if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+
+  for (var j = -1, m = this.length; ++j < m;) {
+    for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+      if (node = group[i]) {
+        subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i)));
+        subgroup.parentNode = node;
+      }
+    }
+  }
+
+  return d3_selection(subgroups);
+};
+
+function d3_selection_selectorAll(selector) {
+  return function() {
+    return d3_selectAll(selector, this);
+  };
+}
+var d3_nsPrefix = {
+  svg: "http://www.w3.org/2000/svg",
+  xhtml: "http://www.w3.org/1999/xhtml",
+  xlink: "http://www.w3.org/1999/xlink",
+  xml: "http://www.w3.org/XML/1998/namespace",
+  xmlns: "http://www.w3.org/2000/xmlns/"
+};
+
+d3.ns = {
+  prefix: d3_nsPrefix,
+  qualify: function(name) {
+    var i = name.indexOf(":"),
+        prefix = name;
+    if (i >= 0) {
+      prefix = name.substring(0, i);
+      name = name.substring(i + 1);
+    }
+    return d3_nsPrefix.hasOwnProperty(prefix)
+        ? {space: d3_nsPrefix[prefix], local: name}
+        : name;
+  }
+};
+
+d3_selectionPrototype.attr = function(name, value) {
+  if (arguments.length < 2) {
+
+    // For attr(string), return the attribute value for the first node.
+    if (typeof name === "string") {
+      var node = this.node();
+      name = d3.ns.qualify(name);
+      return name.local
+          ? node.getAttributeNS(name.space, name.local)
+          : node.getAttribute(name);
+    }
+
+    // For attr(object), the object specifies the names and values of the
+    // attributes to set or remove. The values may be functions that are
+    // evaluated for each element.
+    for (value in name) this.each(d3_selection_attr(value, name[value]));
+    return this;
+  }
+
+  return this.each(d3_selection_attr(name, value));
+};
+
+function d3_selection_attr(name, value) {
+  name = d3.ns.qualify(name);
+
+  // For attr(string, null), remove the attribute with the specified name.
+  function attrNull() {
+    this.removeAttribute(name);
+  }
+  function attrNullNS() {
+    this.removeAttributeNS(name.space, name.local);
+  }
+
+  // For attr(string, string), set the attribute with the specified name.
+  function attrConstant() {
+    this.setAttribute(name, value);
+  }
+  function attrConstantNS() {
+    this.setAttributeNS(name.space, name.local, value);
+  }
+
+  // For attr(string, function), evaluate the function for each element, and set
+  // or remove the attribute as appropriate.
+  function attrFunction() {
+    var x = value.apply(this, arguments);
+    if (x == null) this.removeAttribute(name);
+    else this.setAttribute(name, x);
+  }
+  function attrFunctionNS() {
+    var x = value.apply(this, arguments);
+    if (x == null) this.removeAttributeNS(name.space, name.local);
+    else this.setAttributeNS(name.space, name.local, x);
+  }
+
+  return value == null
+      ? (name.local ? attrNullNS : attrNull) : (typeof value === "function"
+      ? (name.local ? attrFunctionNS : attrFunction)
+      : (name.local ? attrConstantNS : attrConstant));
+}
+function d3_collapse(s) {
+  return s.trim().replace(/\s+/g, " ");
+}
+d3.requote = function(s) {
+  return s.replace(d3_requote_re, "\\$&");
+};
+
+var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+
+d3_selectionPrototype.classed = function(name, value) {
+  if (arguments.length < 2) {
+
+    // For classed(string), return true only if the first node has the specified
+    // class or classes. Note that even if the browser supports DOMTokenList, it
+    // probably doesn't support it on SVG elements (which can be animated).
+    if (typeof name === "string") {
+      var node = this.node(),
+          n = (name = name.trim().split(/^|\s+/g)).length,
+          i = -1;
+      if (value = node.classList) {
+        while (++i < n) if (!value.contains(name[i])) return false;
+      } else {
+        value = node.getAttribute("class");
+        while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
+      }
+      return true;
+    }
+
+    // For classed(object), the object specifies the names of classes to add or
+    // remove. The values may be functions that are evaluated for each element.
+    for (value in name) this.each(d3_selection_classed(value, name[value]));
+    return this;
+  }
+
+  // Otherwise, both a name and a value are specified, and are handled as below.
+  return this.each(d3_selection_classed(name, value));
+};
+
+function d3_selection_classedRe(name) {
+  return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
+}
+
+// Multiple class names are allowed (e.g., "foo bar").
+function d3_selection_classed(name, value) {
+  name = name.trim().split(/\s+/).map(d3_selection_classedName);
+  var n = name.length;
+
+  function classedConstant() {
+    var i = -1;
+    while (++i < n) name[i](this, value);
+  }
+
+  // When the value is a function, the function is still evaluated only once per
+  // element even if there are multiple class names.
+  function classedFunction() {
+    var i = -1, x = value.apply(this, arguments);
+    while (++i < n) name[i](this, x);
+  }
+
+  return typeof value === "function"
+      ? classedFunction
+      : classedConstant;
+}
+
+function d3_selection_classedName(name) {
+  var re = d3_selection_classedRe(name);
+  return function(node, value) {
+    if (c = node.classList) return value ? c.add(name) : c.remove(name);
+    var c = node.getAttribute("class") || "";
+    if (value) {
+      re.lastIndex = 0;
+      if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
+    } else {
+      node.setAttribute("class", d3_collapse(c.replace(re, " ")));
+    }
+  };
+}
+
+d3_selectionPrototype.style = function(name, value, priority) {
+  var n = arguments.length;
+  if (n < 3) {
+
+    // For style(object) or style(object, string), the object specifies the
+    // names and values of the attributes to set or remove. The values may be
+    // functions that are evaluated for each element. The optional string
+    // specifies the priority.
+    if (typeof name !== "string") {
+      if (n < 2) value = "";
+      for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
+      return this;
+    }
+
+    // For style(string), return the computed style value for the first node.
+    if (n < 2) return d3_window.getComputedStyle(this.node(), null).getPropertyValue(name);
+
+    // For style(string, string) or style(string, function), use the default
+    // priority. The priority is ignored for style(string, null).
+    priority = "";
+  }
+
+  // Otherwise, a name, value and priority are specified, and handled as below.
+  return this.each(d3_selection_style(name, value, priority));
+};
+
+function d3_selection_style(name, value, priority) {
+
+  // For style(name, null) or style(name, null, priority), remove the style
+  // property with the specified name. The priority is ignored.
+  function styleNull() {
+    this.style.removeProperty(name);
+  }
+
+  // For style(name, string) or style(name, string, priority), set the style
+  // property with the specified name, using the specified priority.
+  function styleConstant() {
+    this.style.setProperty(name, value, priority);
+  }
+
+  // For style(name, function) or style(name, function, priority), evaluate the
+  // function for each element, and set or remove the style property as
+  // appropriate. When setting, use the specified priority.
+  function styleFunction() {
+    var x = value.apply(this, arguments);
+    if (x == null) this.style.removeProperty(name);
+    else this.style.setProperty(name, x, priority);
+  }
+
+  return value == null
+      ? styleNull : (typeof value === "function"
+      ? styleFunction : styleConstant);
+}
+
+d3_selectionPrototype.property = function(name, value) {
+  if (arguments.length < 2) {
+
+    // For property(string), return the property value for the first node.
+    if (typeof name === "string") return this.node()[name];
+
+    // For property(object), the object specifies the names and values of the
+    // properties to set or remove. The values may be functions that are
+    // evaluated for each element.
+    for (value in name) this.each(d3_selection_property(value, name[value]));
+    return this;
+  }
+
+  // Otherwise, both a name and a value are specified, and are handled as below.
+  return this.each(d3_selection_property(name, value));
+};
+
+function d3_selection_property(name, value) {
+
+  // For property(name, null), remove the property with the specified name.
+  function propertyNull() {
+    delete this[name];
+  }
+
+  // For property(name, string), set the property with the specified name.
+  function propertyConstant() {
+    this[name] = value;
+  }
+
+  // For property(name, function), evaluate the function for each element, and
+  // set or remove the property as appropriate.
+  function propertyFunction() {
+    var x = value.apply(this, arguments);
+    if (x == null) delete this[name];
+    else this[name] = x;
+  }
+
+  return value == null
+      ? propertyNull : (typeof value === "function"
+      ? propertyFunction : propertyConstant);
+}
+
+d3_selectionPrototype.text = function(value) {
+  return arguments.length
+      ? this.each(typeof value === "function"
+      ? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null
+      ? function() { this.textContent = ""; }
+      : function() { this.textContent = value; })
+      : this.node().textContent;
+};
+
+d3_selectionPrototype.html = function(value) {
+  return arguments.length
+      ? this.each(typeof value === "function"
+      ? function() { var v = value.apply(this, arguments); this.innerHTML = v == null ? "" : v; } : value == null
+      ? function() { this.innerHTML = ""; }
+      : function() { this.innerHTML = value; })
+      : this.node().innerHTML;
+};
+
+// TODO append(node)?
+// TODO append(function)?
+d3_selectionPrototype.append = function(name) {
+  name = d3.ns.qualify(name);
+
+  function append() {
+    return this.appendChild(d3_document.createElementNS(this.namespaceURI, name));
+  }
+
+  function appendNS() {
+    return this.appendChild(d3_document.createElementNS(name.space, name.local));
+  }
+
+  return this.select(name.local ? appendNS : append);
+};
+
+d3_selectionPrototype.insert = function(name, before) {
+  name = d3.ns.qualify(name);
+
+  if (typeof before !== "function") before = d3_selection_selector(before);
+
+  function insert(d, i) {
+    return this.insertBefore(
+        d3_document.createElementNS(this.namespaceURI, name),
+        before.call(this, d, i));
+  }
+
+  function insertNS(d, i) {
+    return this.insertBefore(
+        d3_document.createElementNS(name.space, name.local),
+        before.call(this, d, i));
+  }
+
+  return this.select(name.local ? insertNS : insert);
+};
+
+// TODO remove(selector)?
+// TODO remove(node)?
+// TODO remove(function)?
+d3_selectionPrototype.remove = function() {
+  return this.each(function() {
+    var parent = this.parentNode;
+    if (parent) parent.removeChild(this);
+  });
+};
+
+d3_selectionPrototype.data = function(value, key) {
+  var i = -1,
+      n = this.length,
+      group,
+      node;
+
+  // If no value is specified, return the first value.
+  if (!arguments.length) {
+    value = new Array(n = (group = this[0]).length);
+    while (++i < n) {
+      if (node = group[i]) {
+        value[i] = node.__data__;
+      }
+    }
+    return value;
+  }
+
+  function bind(group, groupData) {
+    var i,
+        n = group.length,
+        m = groupData.length,
+        n0 = Math.min(n, m),
+        updateNodes = new Array(m),
+        enterNodes = new Array(m),
+        exitNodes = new Array(n),
+        node,
+        nodeData;
+
+    if (key) {
+      var nodeByKeyValue = new d3_Map,
+          dataByKeyValue = new d3_Map,
+          keyValues = [],
+          keyValue;
+
+      for (i = -1; ++i < n;) {
+        keyValue = key.call(node = group[i], node.__data__, i);
+        if (nodeByKeyValue.has(keyValue)) {
+          exitNodes[i] = node; // duplicate selection key
+        } else {
+          nodeByKeyValue.set(keyValue, node);
+        }
+        keyValues.push(keyValue);
+      }
+
+      for (i = -1; ++i < m;) {
+        keyValue = key.call(groupData, nodeData = groupData[i], i);
+        if (node = nodeByKeyValue.get(keyValue)) {
+          updateNodes[i] = node;
+          node.__data__ = nodeData;
+        } else if (!dataByKeyValue.has(keyValue)) { // no duplicate data key
+          enterNodes[i] = d3_selection_dataNode(nodeData);
+        }
+        dataByKeyValue.set(keyValue, nodeData);
+        nodeByKeyValue.remove(keyValue);
+      }
+
+      for (i = -1; ++i < n;) {
+        if (nodeByKeyValue.has(keyValues[i])) {
+          exitNodes[i] = group[i];
+        }
+      }
+    } else {
+      for (i = -1; ++i < n0;) {
+        node = group[i];
+        nodeData = groupData[i];
+        if (node) {
+          node.__data__ = nodeData;
+          updateNodes[i] = node;
+        } else {
+          enterNodes[i] = d3_selection_dataNode(nodeData);
+        }
+      }
+      for (; i < m; ++i) {
+        enterNodes[i] = d3_selection_dataNode(groupData[i]);
+      }
+      for (; i < n; ++i) {
+        exitNodes[i] = group[i];
+      }
+    }
+
+    enterNodes.update
+        = updateNodes;
+
+    enterNodes.parentNode
+        = updateNodes.parentNode
+        = exitNodes.parentNode
+        = group.parentNode;
+
+    enter.push(enterNodes);
+    update.push(updateNodes);
+    exit.push(exitNodes);
+  }
+
+  var enter = d3_selection_enter([]),
+      update = d3_selection([]),
+      exit = d3_selection([]);
+
+  if (typeof value === "function") {
+    while (++i < n) {
+      bind(group = this[i], value.call(group, group.parentNode.__data__, i));
+    }
+  } else {
+    while (++i < n) {
+      bind(group = this[i], value);
+    }
+  }
+
+  update.enter = function() { return enter; };
+  update.exit = function() { return exit; };
+  return update;
+};
+
+function d3_selection_dataNode(data) {
+  return {__data__: data};
+}
+
+d3_selectionPrototype.datum = function(value) {
+  return arguments.length
+      ? this.property("__data__", value)
+      : this.property("__data__");
+};
+
+d3_selectionPrototype.filter = function(filter) {
+  var subgroups = [],
+      subgroup,
+      group,
+      node;
+
+  if (typeof filter !== "function") filter = d3_selection_filter(filter);
+
+  for (var j = 0, m = this.length; j < m; j++) {
+    subgroups.push(subgroup = []);
+    subgroup.parentNode = (group = this[j]).parentNode;
+    for (var i = 0, n = group.length; i < n; i++) {
+      if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+        subgroup.push(node);
+      }
+    }
+  }
+
+  return d3_selection(subgroups);
+};
+
+function d3_selection_filter(selector) {
+  return function() {
+    return d3_selectMatches(this, selector);
+  };
+}
+
+d3_selectionPrototype.order = function() {
+  for (var j = -1, m = this.length; ++j < m;) {
+    for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
+      if (node = group[i]) {
+        if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
+        next = node;
+      }
+    }
+  }
+  return this;
+};
+
+d3_selectionPrototype.sort = function(comparator) {
+  comparator = d3_selection_sortComparator.apply(this, arguments);
+  for (var j = -1, m = this.length; ++j < m;) this[j].sort(comparator);
+  return this.order();
+};
+
+function d3_selection_sortComparator(comparator) {
+  if (!arguments.length) comparator = d3.ascending;
+  return function(a, b) {
+    return (!a - !b) || comparator(a.__data__, b.__data__);
+  };
+}
+function d3_noop() {}
+
+d3_selectionPrototype.on = function(type, listener, capture) {
+  var n = arguments.length;
+  if (n < 3) {
+
+    // For on(object) or on(object, boolean), the object specifies the event
+    // types and listeners to add or remove. The optional boolean specifies
+    // whether the listener captures events.
+    if (typeof type !== "string") {
+      if (n < 2) listener = false;
+      for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
+      return this;
+    }
+
+    // For on(string), return the listener for the first node.
+    if (n < 2) return (n = this.node()["__on" + type]) && n._;
+
+    // For on(string, function), use the default capture.
+    capture = false;
+  }
+
+  // Otherwise, a type, listener and capture are specified, and handled as below.
+  return this.each(d3_selection_on(type, listener, capture));
+};
+
+function d3_selection_on(type, listener, capture) {
+  var name = "__on" + type,
+      i = type.indexOf("."),
+      wrap = d3_selection_onListener;
+
+  if (i > 0) type = type.substring(0, i);
+  var filter = d3_selection_onFilters.get(type);
+  if (filter) type = filter, wrap = d3_selection_onFilter;
+
+  function onRemove() {
+    var l = this[name];
+    if (l) {
+      this.removeEventListener(type, l, l.$);
+      delete this[name];
+    }
+  }
+
+  function onAdd() {
+    var l = wrap(listener, d3_array(arguments));
+    if (typeof Raven !== 'undefined') l = Raven.wrap(l);
+    onRemove.call(this);
+    this.addEventListener(type, this[name] = l, l.$ = capture);
+    l._ = listener;
+  }
+
+  function removeAll() {
+    var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"),
+        match;
+    for (var name in this) {
+      if (match = name.match(re)) {
+        var l = this[name];
+        this.removeEventListener(match[1], l, l.$);
+        delete this[name];
+      }
+    }
+  }
+
+  return i
+      ? listener ? onAdd : onRemove
+      : listener ? d3_noop : removeAll;
+}
+
+var d3_selection_onFilters = d3.map({
+  mouseenter: "mouseover",
+  mouseleave: "mouseout"
+});
+
+d3_selection_onFilters.forEach(function(k) {
+  if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
+});
+
+function d3_selection_onListener(listener, argumentz) {
+  return function(e) {
+    var o = d3.event; // Events can be reentrant (e.g., focus).
+    d3.event = e;
+    argumentz[0] = this.__data__;
+    try {
+      listener.apply(this, argumentz);
+    } finally {
+      d3.event = o;
+    }
+  };
+}
+
+function d3_selection_onFilter(listener, argumentz) {
+  var l = d3_selection_onListener(listener, argumentz);
+  return function(e) {
+    var target = this, related = e.relatedTarget;
+    if (!related || (related !== target && !(related.compareDocumentPosition(target) & 8))) {
+      l.call(target, e);
+    }
+  };
+}
+
+d3_selectionPrototype.each = function(callback) {
+  return d3_selection_each(this, function(node, i, j) {
+    callback.call(node, node.__data__, i, j);
+  });
+};
+
+function d3_selection_each(groups, callback) {
+  for (var j = 0, m = groups.length; j < m; j++) {
+    for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
+      if (node = group[i]) callback(node, i, j);
+    }
+  }
+  return groups;
+}
+
+d3_selectionPrototype.call = function(callback) {
+  var args = d3_array(arguments);
+  callback.apply(args[0] = this, args);
+  return this;
+};
+
+d3_selectionPrototype.empty = function() {
+  return !this.node();
+};
+
+d3_selectionPrototype.node = function() {
+  for (var j = 0, m = this.length; j < m; j++) {
+    for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+      var node = group[i];
+      if (node) return node;
+    }
+  }
+  return null;
+};
+
+function d3_selection_enter(selection) {
+  d3_arraySubclass(selection, d3_selection_enterPrototype);
+  return selection;
+}
+
+var d3_selection_enterPrototype = [];
+
+d3.selection.enter = d3_selection_enter;
+d3.selection.enter.prototype = d3_selection_enterPrototype;
+
+d3_selection_enterPrototype.append = d3_selectionPrototype.append;
+d3_selection_enterPrototype.insert = d3_selectionPrototype.insert;
+d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
+d3_selection_enterPrototype.node = d3_selectionPrototype.node;
+
+
+d3_selection_enterPrototype.select = function(selector) {
+  var subgroups = [],
+      subgroup,
+      subnode,
+      upgroup,
+      group,
+      node;
+
+  for (var j = -1, m = this.length; ++j < m;) {
+    upgroup = (group = this[j]).update;
+    subgroups.push(subgroup = []);
+    subgroup.parentNode = group.parentNode;
+    for (var i = -1, n = group.length; ++i < n;) {
+      if (node = group[i]) {
+        subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i));
+        subnode.__data__ = node.__data__;
+      } else {
+        subgroup.push(null);
+      }
+    }
+  }
+
+  return d3_selection(subgroups);
+};
+
+d3_selectionPrototype.transition = function() {
+  var id = d3_transitionInheritId || ++d3_transitionId,
+      subgroups = [],
+      subgroup,
+      node,
+      transition = Object.create(d3_transitionInherit);
+
+  transition.time = Date.now();
+
+  for (var j = -1, m = this.length; ++j < m;) {
+    subgroups.push(subgroup = []);
+    for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+      if (node = group[i]) d3_transitionNode(node, i, id, transition);
+      subgroup.push(node);
+    }
+  }
+
+  return d3_transition(subgroups, id);
+};
+
+var d3_selectionRoot = d3_selection([[d3_document]]);
+
+d3_selectionRoot[0].parentNode = d3_selectRoot;
+
+// TODO fast singleton implementation!
+// TODO select(function)
+d3.select = function(selector) {
+  return typeof selector === "string"
+      ? d3_selectionRoot.select(selector)
+      : d3_selection([[selector]]); // assume node
+};
+
+// TODO selectAll(function)
+d3.selectAll = function(selector) {
+  return typeof selector === "string"
+      ? d3_selectionRoot.selectAll(selector)
+      : d3_selection([d3_array(selector)]); // assume node[]
+};
+
+d3.behavior.zoom = function() {
+  var translate = [0, 0],
+      translate0, // translate when we started zooming (to avoid drift)
+      scale = 1,
+      scale0, // scale when we started touching
+      scaleExtent = d3_behavior_zoomInfinity,
+      event = d3_eventDispatch(zoom, "zoom"),
+      x0,
+      x1,
+      y0,
+      y1,
+      touchtime; // time of last touchstart (to detect double-tap)
+
+  function zoom() {
+    this.on("mousedown.zoom", mousedown)
+        .on("mousemove.zoom", mousemove)
+        .on(d3_behavior_zoomWheel + ".zoom", mousewheel)
+        .on("dblclick.zoom", dblclick)
+        .on("touchstart.zoom", touchstart)
+        .on("touchmove.zoom", touchmove)
+        .on("touchend.zoom", touchstart);
+  }
+
+  zoom.translate = function(x) {
+    if (!arguments.length) return translate;
+    translate = x.map(Number);
+    rescale();
+    return zoom;
+  };
+
+  zoom.scale = function(x) {
+    if (!arguments.length) return scale;
+    scale = +x;
+    rescale();
+    return zoom;
+  };
+
+  zoom.scaleExtent = function(x) {
+    if (!arguments.length) return scaleExtent;
+    scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number);
+    return zoom;
+  };
+
+  zoom.x = function(z) {
+    if (!arguments.length) return x1;
+    x1 = z;
+    x0 = z.copy();
+    translate = [0, 0];
+    scale = 1;
+    return zoom;
+  };
+
+  zoom.y = function(z) {
+    if (!arguments.length) return y1;
+    y1 = z;
+    y0 = z.copy();
+    translate = [0, 0];
+    scale = 1;
+    return zoom;
+  };
+
+  function location(p) {
+    return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale];
+  }
+
+  function point(l) {
+    return [l[0] * scale + translate[0], l[1] * scale + translate[1]];
+  }
+
+  function scaleTo(s) {
+    scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
+  }
+
+  function translateTo(p, l) {
+    l = point(l);
+    translate[0] += p[0] - l[0];
+    translate[1] += p[1] - l[1];
+  }
+
+  function rescale() {
+    if (x1) x1.domain(x0.range().map(function(x) { return (x - translate[0]) / scale; }).map(x0.invert));
+    if (y1) y1.domain(y0.range().map(function(y) { return (y - translate[1]) / scale; }).map(y0.invert));
+  }
+
+  function dispatch(event) {
+    rescale();
+    d3.event.preventDefault();
+    event({type: "zoom", scale: scale, translate: translate});
+  }
+
+  function mousedown() {
+    var target = this,
+        event_ = event.of(target, arguments),
+        eventTarget = d3.event.target,
+        moved = 0,
+        w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup),
+        l = location(d3.mouse(target));
+
+    d3_window.focus();
+    d3_eventCancel();
+
+    function mousemove() {
+      moved = 1;
+      translateTo(d3.mouse(target), l);
+      dispatch(event_);
+    }
+
+    function mouseup() {
+      if (moved) d3_eventCancel();
+      w.on("mousemove.zoom", null).on("mouseup.zoom", null);
+      if (moved && d3.event.target === eventTarget) {
+          w.on("click.zoom", click, true);
+          window.setTimeout(function() {
+              // Remove click block if click didn't fire
+              w.on("click.zoom", null);
+          }, 0);
+      }
+    }
+
+    function click() {
+      d3_eventCancel();
+      w.on("click.zoom", null);
+    }
+  }
+
+  function mousewheel() {
+    if (!translate0) translate0 = location(d3.mouse(this));
+    scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale);
+    translateTo(d3.mouse(this), translate0);
+    dispatch(event.of(this, arguments));
+  }
+
+  function mousemove() {
+    translate0 = null;
+  }
+
+  function dblclick() {
+    var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2;
+    scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1));
+    translateTo(p, l);
+    dispatch(event.of(this, arguments));
+  }
+
+  function touchstart() {
+    var touches = d3.touches(this),
+        now = Date.now();
+
+    scale0 = scale;
+    translate0 = {};
+    touches.forEach(function(t) { translate0[t.identifier] = location(t); });
+    d3_eventCancel();
+
+    if (touches.length === 1) {
+      if (now - touchtime < 500) { // dbltap
+        var p = touches[0], l = location(touches[0]);
+        scaleTo(scale * 2);
+        translateTo(p, l);
+        dispatch(event.of(this, arguments));
+      }
+      touchtime = now;
+    }
+  }
+
+  function touchmove() {
+    var touches = d3.touches(this),
+        p0 = touches[0],
+        l0 = translate0[p0.identifier];
+    if (p1 = touches[1]) {
+      var p1, l1 = translate0[p1.identifier];
+      p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2];
+      l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2];
+      scaleTo(d3.event.scale * scale0);
+    }
+    translateTo(p0, l0);
+    touchtime = null;
+    dispatch(event.of(this, arguments));
+  }
+
+  return d3.rebind(zoom, event, "on");
+};
+
+var d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent
+
+// https://developer.mozilla.org/en-US/docs/Mozilla_event_reference/wheel
+var d3_behavior_zoomDelta, d3_behavior_zoomWheel
+    = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() { return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); }, "wheel")
+    : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() { return d3.event.wheelDelta; }, "mousewheel")
+    : (d3_behavior_zoomDelta = function() { return -d3.event.detail; }, "MozMousePixelScroll");
+function d3_functor(v) {
+  return typeof v === "function" ? v : function() { return v; };
+}
+
+d3.functor = d3_functor;
+
+var d3_timer_id = 0,
+    d3_timer_byId = {},
+    d3_timer_queue = null,
+    d3_timer_interval, // is an interval (or frame) active?
+    d3_timer_timeout; // is a timeout active?
+
+// The timer will continue to fire until callback returns true.
+d3.timer = function(callback, delay, then) {
+  if (arguments.length < 3) {
+    if (arguments.length < 2) delay = 0;
+    else if (!isFinite(delay)) return;
+    then = Date.now();
+  }
+
+  // If the callback's already in the queue, update it.
+  var timer = d3_timer_byId[callback.id];
+  if (timer && timer.callback === callback) {
+    timer.then = then;
+    timer.delay = delay;
+  }
+
+  // Otherwise, add the callback to the queue.
+  else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = {
+    callback: callback,
+    then: then,
+    delay: delay,
+    next: d3_timer_queue
+  };
+
+  // Start animatin'!
+  if (!d3_timer_interval) {
+    d3_timer_timeout = clearTimeout(d3_timer_timeout);
+    d3_timer_interval = 1;
+    d3_timer_frame(d3_timer_step);
+  }
+};
+
+function d3_timer_step() {
+  var elapsed,
+      now = Date.now(),
+      t1 = d3_timer_queue;
+
+  while (t1) {
+    elapsed = now - t1.then;
+    if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed);
+    t1 = t1.next;
+  }
+
+  var delay = d3_timer_flush() - now;
+  if (delay > 24) {
+    if (isFinite(delay)) {
+      clearTimeout(d3_timer_timeout);
+      d3_timer_timeout = setTimeout(d3_timer_step, delay);
+    }
+    d3_timer_interval = 0;
+  } else {
+    d3_timer_interval = 1;
+    d3_timer_frame(d3_timer_step);
+  }
+}
+
+d3.timer.flush = function() {
+  var elapsed,
+      now = Date.now(),
+      t1 = d3_timer_queue;
+
+  while (t1) {
+    elapsed = now - t1.then;
+    if (!t1.delay) t1.flush = t1.callback(elapsed);
+    t1 = t1.next;
+  }
+
+  d3_timer_flush();
+};
+
+// Flush after callbacks to avoid concurrent queue modification.
+function d3_timer_flush() {
+  var t0 = null,
+      t1 = d3_timer_queue,
+      then = Infinity;
+  while (t1) {
+    if (t1.flush) {
+      delete d3_timer_byId[t1.callback.id];
+      t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next;
+    } else {
+      then = Math.min(then, t1.then + t1.delay);
+      t1 = (t0 = t1).next;
+    }
+  }
+  return then;
+}
+
+var d3_timer_frame = d3_window.requestAnimationFrame
+    || d3_window.webkitRequestAnimationFrame
+    || d3_window.mozRequestAnimationFrame
+    || d3_window.oRequestAnimationFrame
+    || d3_window.msRequestAnimationFrame
+    || function(callback) { setTimeout(callback, 17); };
+var π = Math.PI,
+    ε = 1e-6,
+    d3_radians = π / 180,
+    d3_degrees = 180 / π;
+
+function d3_sgn(x) {
+  return x > 0 ? 1 : x < 0 ? -1 : 0;
+}
+
+function d3_acos(x) {
+  return Math.acos(Math.max(-1, Math.min(1, x)));
+}
+
+function d3_asin(x) {
+  return x > 1 ? π / 2 : x < -1 ? -π / 2 : Math.asin(x);
+}
+
+function d3_sinh(x) {
+  return (Math.exp(x) - Math.exp(-x)) / 2;
+}
+
+function d3_cosh(x) {
+  return (Math.exp(x) + Math.exp(-x)) / 2;
+}
+
+function d3_haversin(x) {
+  return (x = Math.sin(x / 2)) * x;
+}
+d3.geo = {};
+function d3_identity(d) {
+  return d;
+}
+function d3_true() {
+  return true;
+}
+
+function d3_geo_spherical(cartesian) {
+  return [
+    Math.atan2(cartesian[1], cartesian[0]),
+    Math.asin(Math.max(-1, Math.min(1, cartesian[2])))
+  ];
+}
+
+function d3_geo_sphericalEqual(a, b) {
+  return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε;
+}
+
+// General spherical polygon clipping algorithm: takes a polygon, cuts it into
+// visible line segments and rejoins the segments by interpolating along the
+// clip edge.
+function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) {
+  var subject = [],
+      clip = [];
+
+  segments.forEach(function(segment) {
+    if ((n = segment.length - 1) <= 0) return;
+    var n, p0 = segment[0], p1 = segment[n];
+
+    // If the first and last points of a segment are coincident, then treat as
+    // a closed ring.
+    // TODO if all rings are closed, then the winding order of the exterior
+    // ring should be checked.
+    if (d3_geo_sphericalEqual(p0, p1)) {
+      listener.lineStart();
+      for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
+      listener.lineEnd();
+      return;
+    }
+
+    var a = {point: p0, points: segment, other: null, visited: false, entry: true, subject: true},
+        b = {point: p0, points: [p0], other: a, visited: false, entry: false, subject: false};
+    a.other = b;
+    subject.push(a);
+    clip.push(b);
+    a = {point: p1, points: [p1], other: null, visited: false, entry: false, subject: true};
+    b = {point: p1, points: [p1], other: a, visited: false, entry: true, subject: false};
+    a.other = b;
+    subject.push(a);
+    clip.push(b);
+  });
+  clip.sort(compare);
+  d3_geo_clipPolygonLinkCircular(subject);
+  d3_geo_clipPolygonLinkCircular(clip);
+  if (!subject.length) return;
+
+  if (inside) for (var i = 1, e = !inside(clip[0].point), n = clip.length; i < n; ++i) {
+    clip[i].entry = (e = !e);
+  }
+
+  var start = subject[0],
+      current,
+      points,
+      point;
+  while (1) {
+    // Find first unvisited intersection.
+    current = start;
+    while (current.visited) if ((current = current.next) === start) return;
+    points = current.points;
+    listener.lineStart();
+    do {
+      current.visited = current.other.visited = true;
+      if (current.entry) {
+        if (current.subject) {
+          for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]);
+        } else {
+          interpolate(current.point, current.next.point, 1, listener);
+        }
+        current = current.next;
+      } else {
+        if (current.subject) {
+          points = current.prev.points;
+          for (var i = points.length; --i >= 0;) listener.point((point = points[i])[0], point[1]);
+        } else {
+          interpolate(current.point, current.prev.point, -1, listener);
+        }
+        current = current.prev;
+      }
+      current = current.other;
+      points = current.points;
+    } while (!current.visited);
+    listener.lineEnd();
+  }
+}
+
+function d3_geo_clipPolygonLinkCircular(array) {
+  if (!(n = array.length)) return;
+  var n,
+      i = 0,
+      a = array[0],
+      b;
+  while (++i < n) {
+    a.next = b = array[i];
+    b.prev = a;
+    a = b;
+  }
+  a.next = b = array[0];
+  b.prev = a;
+}
+
+function d3_geo_clip(pointVisible, clipLine, interpolate) {
+  return function(listener) {
+    var line = clipLine(listener);
+
+    var clip = {
+      point: point,
+      lineStart: lineStart,
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        clip.point = pointRing;
+        clip.lineStart = ringStart;
+        clip.lineEnd = ringEnd;
+        invisible = false;
+        invisibleArea = visibleArea = 0;
+        segments = [];
+        listener.polygonStart();
+      },
+      polygonEnd: function() {
+        clip.point = point;
+        clip.lineStart = lineStart;
+        clip.lineEnd = lineEnd;
+
+        segments = d3.merge(segments);
+        if (segments.length) {
+          d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener);
+        } else if (visibleArea < -ε || invisible && invisibleArea < -ε) {
+          listener.lineStart();
+          interpolate(null, null, 1, listener);
+          listener.lineEnd();
+        }
+        listener.polygonEnd();
+        segments = null;
+      },
+      sphere: function() {
+        listener.polygonStart();
+        listener.lineStart();
+        interpolate(null, null, 1, listener);
+        listener.lineEnd();
+        listener.polygonEnd();
+      }
+    };
+
+    function point(λ, φ) { if (pointVisible(λ, φ)) listener.point(λ, φ); }
+    function pointLine(λ, φ) { line.point(λ, φ); }
+    function lineStart() { clip.point = pointLine; line.lineStart(); }
+    function lineEnd() { clip.point = point; line.lineEnd(); }
+
+    var segments,
+        visibleArea,
+        invisibleArea,
+        invisible;
+
+    var buffer = d3_geo_clipBufferListener(),
+        ringListener = clipLine(buffer),
+        ring;
+
+    function pointRing(λ, φ) {
+      ringListener.point(λ, φ);
+      ring.push([λ, φ]);
+    }
+
+    function ringStart() {
+      ringListener.lineStart();
+      ring = [];
+    }
+
+    function ringEnd() {
+      pointRing(ring[0][0], ring[0][1]);
+      ringListener.lineEnd();
+
+      var clean = ringListener.clean(),
+          ringSegments = buffer.buffer(),
+          segment,
+          n = ringSegments.length;
+
+      // TODO compute on-the-fly?
+      if (!n) {
+        invisible = true;
+        invisibleArea += d3_geo_clipAreaRing(ring, -1);
+        ring = null;
+        return;
+      }
+      ring = null;
+
+      // No intersections.
+      // TODO compute on-the-fly?
+      if (clean & 1) {
+        segment = ringSegments[0];
+        visibleArea += d3_geo_clipAreaRing(segment, 1);
+        var n = segment.length - 1,
+            i = -1,
+            point;
+        listener.lineStart();
+        while (++i < n) listener.point((point = segment[i])[0], point[1]);
+        listener.lineEnd();
+        return;
+      }
+
+      // Rejoin connected segments.
+      // TODO reuse bufferListener.rejoin()?
+      if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
+
+      segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
+    }
+
+    return clip;
+  };
+}
+
+function d3_geo_clipSegmentLength1(segment) {
+  return segment.length > 1;
+}
+
+function d3_geo_clipBufferListener() {
+  var lines = [],
+      line;
+  return {
+    lineStart: function() { lines.push(line = []); },
+    point: function(λ, φ) { line.push([λ, φ]); },
+    lineEnd: d3_noop,
+    buffer: function() {
+      var buffer = lines;
+      lines = [];
+      line = null;
+      return buffer;
+    },
+    rejoin: function() {
+      if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
+    }
+  };
+}
+
+// Approximate polygon ring area (×2, since we only need the sign).
+// For an invisible polygon ring, we rotate longitudinally by 180°.
+// The invisible parameter should be 1, or -1 to rotate longitudinally.
+// Based on Robert. G. Chamberlain and William H. Duquette,
+// “Some Algorithms for Polygons on a Sphere”,
+// http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+function d3_geo_clipAreaRing(ring, invisible) {
+  if (!(n = ring.length)) return 0;
+  var n,
+      i = 0,
+      area = 0,
+      p = ring[0],
+      λ = p[0],
+      φ = p[1],
+      cosφ = Math.cos(φ),
+      x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)),
+      y0 = 1 - invisible * Math.cos(λ) * cosφ,
+      x1 = x0,
+      x, // λ'; λ rotated to south pole.
+      y; // φ' = 1 + sin(φ); φ rotated to south pole.
+  while (++i < n) {
+    p = ring[i];
+    cosφ = Math.cos(φ = p[1]);
+    x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ));
+    y = 1 - invisible * Math.cos(λ) * cosφ;
+
+    // If both the current point and the previous point are at the north pole,
+    // skip this point.
+    if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue;
+
+    // If this or the previous point is at the south pole, or if this segment
+    // goes through the south pole, the area is 0.
+    if (Math.abs(y) < ε || Math.abs(y0) < ε) {}
+
+    // If this segment goes through either pole…
+    else if (Math.abs(Math.abs(x - x0) - π) < ε) {
+      // For the north pole, compute lune area.
+      if (y + y0 > 2) area += 4 * (x - x0);
+      // For the south pole, the area is zero.
+    }
+
+    // If the previous point is at the north pole, then compute lune area.
+    else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1);
+
+    // Otherwise, the spherical triangle area is approximately
+    // δλ * (1 + sinφ0 + 1 + sinφ) / 2.
+    else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y);
+
+    x1 = x0, x0 = x, y0 = y;
+  }
+  return area;
+}
+
+// Intersection points are sorted along the clip edge. For both antimeridian
+// cutting and circle clipping, the same comparison is used.
+function d3_geo_clipSort(a, b) {
+  return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1])
+       - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]);
+}
+
+var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate);
+
+// Takes a line and cuts into visible segments. Return values:
+//   0: there were intersections or the line was empty.
+//   1: no intersections.
+//   2: there were intersections, and the first and last segments should be
+//      rejoined.
+function d3_geo_clipAntimeridianLine(listener) {
+  var λ0 = NaN,
+      φ0 = NaN,
+      sλ0 = NaN,
+      clean; // no intersections
+
+  return {
+    lineStart: function() {
+      listener.lineStart();
+      clean = 1;
+    },
+    point: function(λ1, φ1) {
+      var sλ1 = λ1 > 0 ? π : -π,
+          dλ = Math.abs(λ1 - λ0);
+      if (Math.abs(dλ - π) < ε) { // line crosses a pole
+        listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2);
+        listener.point(sλ0, φ0);
+        listener.lineEnd();
+        listener.lineStart();
+        listener.point(sλ1, φ0);
+        listener.point( λ1, φ0);
+        clean = 0;
+      } else if (sλ0 !== sλ1 && dλ >= π) { // line crosses antimeridian
+        // handle degeneracies
+        if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
+        if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
+        φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
+        listener.point(sλ0, φ0);
+        listener.lineEnd();
+        listener.lineStart();
+        listener.point(sλ1, φ0);
+        clean = 0;
+      }
+      listener.point(λ0 = λ1, φ0 = φ1);
+      sλ0 = sλ1;
+    },
+    lineEnd: function() {
+      listener.lineEnd();
+      λ0 = φ0 = NaN;
+    },
+    // if there are intersections, we always rejoin the first and last segments.
+    clean: function() { return 2 - clean; }
+  };
+}
+
+function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
+  var cosφ0,
+      cosφ1,
+      sinλ0_λ1 = Math.sin(λ0 - λ1);
+  return Math.abs(sinλ0_λ1) > ε
+      ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1)
+                 - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0))
+                 / (cosφ0 * cosφ1 * sinλ0_λ1))
+      : (φ0 + φ1) / 2;
+}
+
+function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
+  var φ;
+  if (from == null) {
+    φ = direction * π / 2;
+    listener.point(-π,  φ);
+    listener.point( 0,  φ);
+    listener.point( π,  φ);
+    listener.point( π,  0);
+    listener.point( π, -φ);
+    listener.point( 0, -φ);
+    listener.point(-π, -φ);
+    listener.point(-π,  0);
+    listener.point(-π,  φ);
+  } else if (Math.abs(from[0] - to[0]) > ε) {
+    var s = (from[0] < to[0] ? 1 : -1) * π;
+    φ = direction * s / 2;
+    listener.point(-s, φ);
+    listener.point( 0, φ);
+    listener.point( s, φ);
+  } else {
+    listener.point(to[0], to[1]);
+  }
+}
+// TODO
+// cross and scale return new vectors,
+// whereas add and normalize operate in-place
+
+function d3_geo_cartesian(spherical) {
+  var λ = spherical[0],
+      φ = spherical[1],
+      cosφ = Math.cos(φ);
+  return [
+    cosφ * Math.cos(λ),
+    cosφ * Math.sin(λ),
+    Math.sin(φ)
+  ];
+}
+
+function d3_geo_cartesianDot(a, b) {
+  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+}
+
+function d3_geo_cartesianCross(a, b) {
+  return [
+    a[1] * b[2] - a[2] * b[1],
+    a[2] * b[0] - a[0] * b[2],
+    a[0] * b[1] - a[1] * b[0]
+  ];
+}
+
+function d3_geo_cartesianAdd(a, b) {
+  a[0] += b[0];
+  a[1] += b[1];
+  a[2] += b[2];
+}
+
+function d3_geo_cartesianScale(vector, k) {
+  return [
+    vector[0] * k,
+    vector[1] * k,
+    vector[2] * k
+  ];
+}
+
+function d3_geo_cartesianNormalize(d) {
+  var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
+  d[0] /= l;
+  d[1] /= l;
+  d[2] /= l;
+}
+
+function d3_geo_equirectangular(λ, φ) {
+  return [λ, φ];
+}
+
+(d3.geo.equirectangular = function() {
+  return d3_geo_projection(d3_geo_equirectangular);
+}).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
+
+d3.geo.rotation = function(rotate) {
+  rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
+
+  function forward(coordinates) {
+    coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+    return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+  }
+
+  forward.invert = function(coordinates) {
+    coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+    return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+  };
+
+  return forward;
+};
+
+// Note: |δλ| must be < 2π
+function d3_geo_rotation(δλ, δφ, δγ) {
+  return δλ ? (δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ))
+    : d3_geo_rotationλ(δλ))
+    : (δφ || δγ ? d3_geo_rotationφγ(δφ, δγ)
+    : d3_geo_equirectangular);
+}
+
+function d3_geo_forwardRotationλ(δλ) {
+  return function(λ, φ) {
+    return λ += δλ, [λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ];
+  };
+}
+
+function d3_geo_rotationλ(δλ) {
+  var rotation = d3_geo_forwardRotationλ(δλ);
+  rotation.invert = d3_geo_forwardRotationλ(-δλ);
+  return rotation;
+}
+
+function d3_geo_rotationφγ(δφ, δγ) {
+  var cosδφ = Math.cos(δφ),
+      sinδφ = Math.sin(δφ),
+      cosδγ = Math.cos(δγ),
+      sinδγ = Math.sin(δγ);
+
+  function rotation(λ, φ) {
+    var cosφ = Math.cos(φ),
+        x = Math.cos(λ) * cosφ,
+        y = Math.sin(λ) * cosφ,
+        z = Math.sin(φ),
+        k = z * cosδφ + x * sinδφ;
+    return [
+      Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ),
+      Math.asin(Math.max(-1, Math.min(1, k * cosδγ + y * sinδγ)))
+    ];
+  }
+
+  rotation.invert = function(λ, φ) {
+    var cosφ = Math.cos(φ),
+        x = Math.cos(λ) * cosφ,
+        y = Math.sin(λ) * cosφ,
+        z = Math.sin(φ),
+        k = z * cosδγ - y * sinδγ;
+    return [
+      Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ),
+      Math.asin(Math.max(-1, Math.min(1, k * cosδφ - x * sinδφ)))
+    ];
+  };
+
+  return rotation;
+}
+
+d3.geo.circle = function() {
+  var origin = [0, 0],
+      angle,
+      precision = 6,
+      interpolate;
+
+  function circle() {
+    var center = typeof origin === "function" ? origin.apply(this, arguments) : origin,
+        rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert,
+        ring = [];
+
+    interpolate(null, null, 1, {
+      point: function(x, y) {
+        ring.push(x = rotate(x, y));
+        x[0] *= d3_degrees, x[1] *= d3_degrees;
+      }
+    });
+
+    return {type: "Polygon", coordinates: [ring]};
+  }
+
+  circle.origin = function(x) {
+    if (!arguments.length) return origin;
+    origin = x;
+    return circle;
+  };
+
+  circle.angle = function(x) {
+    if (!arguments.length) return angle;
+    interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
+    return circle;
+  };
+
+  circle.precision = function(_) {
+    if (!arguments.length) return precision;
+    interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
+    return circle;
+  };
+
+  return circle.angle(90);
+};
+
+// Interpolates along a circle centered at [0°, 0°], with a given radius and
+// precision.
+function d3_geo_circleInterpolate(radius, precision) {
+  var cr = Math.cos(radius),
+      sr = Math.sin(radius);
+  return function(from, to, direction, listener) {
+    if (from != null) {
+      from = d3_geo_circleAngle(cr, from);
+      to = d3_geo_circleAngle(cr, to);
+      if (direction > 0 ? from < to: from > to) from += direction * 2 * π;
+    } else {
+      from = radius + direction * 2 * π;
+      to = radius;
+    }
+    var point;
+    for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) {
+      listener.point((point = d3_geo_spherical([
+        cr,
+        -sr * Math.cos(t),
+        -sr * Math.sin(t)
+      ]))[0], point[1]);
+    }
+  };
+}
+
+// Signed angle of a cartesian point relative to [cr, 0, 0].
+function d3_geo_circleAngle(cr, point) {
+  var a = d3_geo_cartesian(point);
+  a[0] -= cr;
+  d3_geo_cartesianNormalize(a);
+  var angle = d3_acos(-a[1]);
+  return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
+}
+
+// Clip features against a small circle centered at [0°, 0°].
+function d3_geo_clipCircle(radius) {
+  var cr = Math.cos(radius),
+      smallRadius = cr > 0,
+      notHemisphere = Math.abs(cr) > ε, // TODO optimise for this common case
+      interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
+
+  return d3_geo_clip(visible, clipLine, interpolate);
+
+  function visible(λ, φ) {
+    return Math.cos(λ) * Math.cos(φ) > cr;
+  }
+
+  // Takes a line and cuts into visible segments. Return values used for
+  // polygon clipping:
+  //   0: there were intersections or the line was empty.
+  //   1: no intersections.
+  //   2: there were intersections, and the first and last segments should be
+  //      rejoined.
+  function clipLine(listener) {
+    var point0, // previous point
+        c0, // code for previous point
+        v0, // visibility of previous point
+        v00, // visibility of first point
+        clean; // no intersections
+    return {
+      lineStart: function() {
+        v00 = v0 = false;
+        clean = 1;
+      },
+      point: function(λ, φ) {
+        var point1 = [λ, φ],
+            point2,
+            v = visible(λ, φ),
+            c = smallRadius
+              ? v ? 0 : code(λ, φ)
+              : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
+        if (!point0 && (v00 = v0 = v)) listener.lineStart();
+        // Handle degeneracies.
+        // TODO ignore if not clipping polygons.
+        if (v !== v0) {
+          point2 = intersect(point0, point1);
+          if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
+            point1[0] += ε;
+            point1[1] += ε;
+            v = visible(point1[0], point1[1]);
+          }
+        }
+        if (v !== v0) {
+          clean = 0;
+          if (v) {
+            // outside going in
+            listener.lineStart();
+            point2 = intersect(point1, point0);
+            listener.point(point2[0], point2[1]);
+          } else {
+            // inside going out
+            point2 = intersect(point0, point1);
+            listener.point(point2[0], point2[1]);
+            listener.lineEnd();
+          }
+          point0 = point2;
+        } else if (notHemisphere && point0 && smallRadius ^ v) {
+          var t;
+          // If the codes for two points are different, or are both zero,
+          // and there this segment intersects with the small circle.
+          if (!(c & c0) && (t = intersect(point1, point0, true))) {
+            clean = 0;
+            if (smallRadius) {
+              listener.lineStart();
+              listener.point(t[0][0], t[0][1]);
+              listener.point(t[1][0], t[1][1]);
+              listener.lineEnd();
+            } else {
+              listener.point(t[1][0], t[1][1]);
+              listener.lineEnd();
+              listener.lineStart();
+              listener.point(t[0][0], t[0][1]);
+            }
+          }
+        }
+        if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
+          listener.point(point1[0], point1[1]);
+        }
+        point0 = point1, v0 = v, c0 = c;
+      },
+      lineEnd: function() {
+        if (v0) listener.lineEnd();
+        point0 = null;
+      },
+      // Rejoin first and last segments if there were intersections and the first
+      // and last points were visible.
+      clean: function() { return clean | ((v00 && v0) << 1); }
+    };
+  }
+
+  // Intersects the great circle between a and b with the clip circle.
+  function intersect(a, b, two) {
+    var pa = d3_geo_cartesian(a),
+        pb = d3_geo_cartesian(b);
+
+    // We have two planes, n1.p = d1 and n2.p = d2.
+    // Find intersection line p(t) = c1 n1 + c2 n2 + t (n1 ⨯ n2).
+    var n1 = [1, 0, 0], // normal
+        n2 = d3_geo_cartesianCross(pa, pb),
+        n2n2 = d3_geo_cartesianDot(n2, n2),
+        n1n2 = n2[0], // d3_geo_cartesianDot(n1, n2),
+        determinant = n2n2 - n1n2 * n1n2;
+
+    // Two polar points.
+    if (!determinant) return !two && a;
+
+    var c1 =  cr * n2n2 / determinant,
+        c2 = -cr * n1n2 / determinant,
+        n1xn2 = d3_geo_cartesianCross(n1, n2),
+        A = d3_geo_cartesianScale(n1, c1),
+        B = d3_geo_cartesianScale(n2, c2);
+    d3_geo_cartesianAdd(A, B);
+
+    // Solve |p(t)|^2 = 1.
+    var u = n1xn2,
+        w = d3_geo_cartesianDot(A, u),
+        uu = d3_geo_cartesianDot(u, u),
+        t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
+
+    if (t2 < 0) return;
+
+    var t = Math.sqrt(t2),
+        q = d3_geo_cartesianScale(u, (-w - t) / uu);
+    d3_geo_cartesianAdd(q, A);
+    q = d3_geo_spherical(q);
+    if (!two) return q;
+
+    // Two intersection points.
+    var λ0 = a[0],
+        λ1 = b[0],
+        φ0 = a[1],
+        φ1 = b[1],
+        z;
+    if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
+    var δλ = λ1 - λ0,
+        polar = Math.abs(δλ - π) < ε,
+        meridian = polar || δλ < ε;
+
+    if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
+
+    // Check that the first point is between a and b.
+    if (meridian
+        ? polar
+          ? φ0 + φ1 > 0 ^ q[1] < (Math.abs(q[0] - λ0) < ε ? φ0 : φ1)
+          : φ0 <= q[1] && q[1] <= φ1
+        : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
+      var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
+      d3_geo_cartesianAdd(q1, A);
+      return [q, d3_geo_spherical(q1)];
+    }
+  }
+
+  // Generates a 4-bit vector representing the location of a point relative to
+  // the small circle's bounding box.
+  function code(λ, φ) {
+    var r = smallRadius ? radius : π - radius,
+        code = 0;
+    if (λ < -r) code |= 1; // left
+    else if (λ > r) code |= 2; // right
+    if (φ < -r) code |= 4; // below
+    else if (φ > r) code |= 8; // above
+    return code;
+  }
+}
+
+var d3_geo_clipViewMAX = 1e9;
+
+function d3_geo_clipView(x0, y0, x1, y1) {
+  return function(listener) {
+    var listener_ = listener,
+        bufferListener = d3_geo_clipBufferListener(),
+        segments,
+        polygon,
+        ring;
+
+    var clip = {
+      point: point,
+      lineStart: lineStart,
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        listener = bufferListener;
+        segments = [];
+        polygon = [];
+      },
+      polygonEnd: function() {
+        listener = listener_;
+        if ((segments = d3.merge(segments)).length) {
+          listener.polygonStart();
+          d3_geo_clipPolygon(segments, compare, inside, interpolate, listener);
+          listener.polygonEnd();
+        } else if (insidePolygon([x0, y0])) {
+          listener.polygonStart(), listener.lineStart();
+          interpolate(null, null, 1, listener);
+          listener.lineEnd(), listener.polygonEnd();
+        }
+        segments = polygon = ring = null;
+      }
+    };
+
+    function inside(point) {
+      var a = corner(point, -1),
+          i = insidePolygon([a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0]);
+      return i;
+    }
+
+    function insidePolygon(p) {
+      var wn = 0, // the winding number counter
+          n = polygon.length,
+          y = p[1];
+
+      for (var i = 0; i < n; ++i) {
+        for (var j = 1, v = polygon[i], m = v.length, a = v[0]; j < m; ++j) {
+          b = v[j];
+          if (a[1] <= y) {
+            if (b[1] >  y && isLeft(a, b, p) > 0) ++wn;
+          } else {
+            if (b[1] <= y && isLeft(a, b, p) < 0) --wn;
+          }
+          a = b;
+        }
+      }
+      return wn !== 0;
+    }
+
+    function isLeft(a, b, c) {
+      return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]);
+    }
+
+    function interpolate(from, to, direction, listener) {
+      var a = 0, a1 = 0;
+      if (from == null ||
+          (a = corner(from, direction)) !== (a1 = corner(to, direction)) ||
+          comparePoints(from, to) < 0 ^ direction > 0) {
+        do {
+          listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
+        } while ((a = (a + direction + 4) % 4) !== a1);
+      } else {
+        listener.point(to[0], to[1]);
+      }
+    }
+
+    function visible(x, y) {
+      return x0 <= x && x <= x1 && y0 <= y && y <= y1;
+    }
+
+    function point(x, y) {
+      if (visible(x, y)) listener.point(x, y);
+    }
+
+    var x__, y__, v__, // first point
+        x_, y_, v_, // previous point
+        first;
+
+    function lineStart() {
+      clip.point = linePoint;
+      if (polygon) polygon.push(ring = []);
+      first = true;
+      v_ = false;
+      x_ = y_ = NaN;
+    }
+
+    function lineEnd() {
+      // TODO rather than special-case polygons, simply handle them separately.
+      // Ideally, coincident intersection points should be jittered to avoid
+      // clipping issues.
+      if (segments) {
+        linePoint(x__, y__);
+        if (v__ && v_) bufferListener.rejoin();
+        segments.push(bufferListener.buffer());
+      }
+      clip.point = point;
+      if (v_) listener.lineEnd();
+    }
+
+    function linePoint(x, y) {
+      x = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, x));
+      y = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, y));
+      var v = visible(x, y);
+      if (polygon) ring.push([x, y]);
+      if (first) {
+        x__ = x, y__ = y, v__ = v;
+        first = false;
+        if (v) {
+          listener.lineStart();
+          listener.point(x, y);
+        }
+      } else {
+        if (v && v_) listener.point(x, y);
+        else {
+          var a = [x_, y_],
+              b = [x, y];
+          if (clipLine(a, b)) {
+            if (!v_) {
+              listener.lineStart();
+              listener.point(a[0], a[1]);
+            }
+            listener.point(b[0], b[1]);
+            if (!v) listener.lineEnd();
+          } else {
+            listener.lineStart();
+            listener.point(x, y);
+          }
+        }
+      }
+      x_ = x, y_ = y, v_ = v;
+    }
+
+    return clip;
+  };
+
+  function corner(p, direction) {
+    return Math.abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3
+        : Math.abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1
+        : Math.abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0
+        : direction > 0 ? 3 : 2; // Math.abs(p[1] - y1) < ε
+  }
+
+  function compare(a, b) {
+    return comparePoints(a.point, b.point);
+  }
+
+  function comparePoints(a, b) {
+    var ca = corner(a, 1),
+        cb = corner(b, 1);
+    return ca !== cb ? ca - cb
+        : ca === 0 ? b[1] - a[1]
+        : ca === 1 ? a[0] - b[0]
+        : ca === 2 ? a[1] - b[1]
+        : b[0] - a[0];
+  }
+
+  // Liang–Barsky line clipping.
+  function clipLine(a, b) {
+    var dx = b[0] - a[0],
+        dy = b[1] - a[1],
+        t = [0, 1];
+
+    if (Math.abs(dx) < ε && Math.abs(dy) < ε) return x0 <= a[0] && a[0] <= x1 && y0 <= a[1] && a[1] <= y1;
+
+    if (d3_geo_clipViewT(x0 - a[0],  dx, t) &&
+        d3_geo_clipViewT(a[0] - x1, -dx, t) &&
+        d3_geo_clipViewT(y0 - a[1],  dy, t) &&
+        d3_geo_clipViewT(a[1] - y1, -dy, t)) {
+      if (t[1] < 1) {
+        b[0] = a[0] + t[1] * dx;
+        b[1] = a[1] + t[1] * dy;
+      }
+      if (t[0] > 0) {
+        a[0] += t[0] * dx;
+        a[1] += t[0] * dy;
+      }
+      return true;
+    }
+
+    return false;
+  }
+}
+
+function d3_geo_clipViewT(num, denominator, t) {
+  if (Math.abs(denominator) < ε) return num <= 0;
+
+  var u = num / denominator;
+
+  if (denominator > 0) {
+    if (u > t[1]) return false;
+    if (u > t[0]) t[0] = u;
+  } else {
+    if (u < t[0]) return false;
+    if (u < t[1]) t[1] = u;
+  }
+  return true;
+}
+function d3_geo_compose(a, b) {
+
+  function compose(x, y) {
+    return x = a(x, y), b(x[0], x[1]);
+  }
+
+  if (a.invert && b.invert) compose.invert = function(x, y) {
+    return x = b.invert(x, y), x && a.invert(x[0], x[1]);
+  };
+
+  return compose;
+}
+
+d3.geo.stream = function(object, listener) {
+  if (d3_geo_streamObjectType.hasOwnProperty(object.type)) {
+    d3_geo_streamObjectType[object.type](object, listener);
+  } else {
+    d3_geo_streamGeometry(object, listener);
+  }
+};
+
+function d3_geo_streamGeometry(geometry, listener) {
+  if (d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
+    d3_geo_streamGeometryType[geometry.type](geometry, listener);
+  }
+}
+
+var d3_geo_streamObjectType = {
+  Feature: function(feature, listener) {
+    d3_geo_streamGeometry(feature.geometry, listener);
+  },
+  FeatureCollection: function(object, listener) {
+    var features = object.features, i = -1, n = features.length;
+    while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
+  }
+};
+
+var d3_geo_streamGeometryType = {
+  Sphere: function(object, listener) {
+    listener.sphere();
+  },
+  Point: function(object, listener) {
+    var coordinate = object.coordinates;
+    listener.point(coordinate[0], coordinate[1]);
+  },
+  MultiPoint: function(object, listener) {
+    var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate;
+    while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]);
+  },
+  LineString: function(object, listener) {
+    d3_geo_streamLine(object.coordinates, listener, 0);
+  },
+  MultiLineString: function(object, listener) {
+    var coordinates = object.coordinates, i = -1, n = coordinates.length;
+    while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
+  },
+  Polygon: function(object, listener) {
+    d3_geo_streamPolygon(object.coordinates, listener);
+  },
+  MultiPolygon: function(object, listener) {
+    var coordinates = object.coordinates, i = -1, n = coordinates.length;
+    while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
+  },
+  GeometryCollection: function(object, listener) {
+    var geometries = object.geometries, i = -1, n = geometries.length;
+    while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
+  }
+};
+
+function d3_geo_streamLine(coordinates, listener, closed) {
+  var i = -1, n = coordinates.length - closed, coordinate;
+  listener.lineStart();
+  while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]);
+  listener.lineEnd();
+}
+
+function d3_geo_streamPolygon(coordinates, listener) {
+  var i = -1, n = coordinates.length;
+  listener.polygonStart();
+  while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
+  listener.polygonEnd();
+}
+
+function d3_geo_resample(project) {
+  var δ2 = .5, // precision, px²
+      maxDepth = 16;
+
+  function resample(stream) {
+    var λ0, x0, y0, a0, b0, c0; // previous point
+
+    var resample = {
+      point: point,
+      lineStart: lineStart,
+      lineEnd: lineEnd,
+      polygonStart: function() { stream.polygonStart(); resample.lineStart = polygonLineStart; },
+      polygonEnd: function() { stream.polygonEnd(); resample.lineStart = lineStart; }
+    };
+
+    function point(x, y) {
+      x = project(x, y);
+      stream.point(x[0], x[1]);
+    }
+
+    function lineStart() {
+      x0 = NaN;
+      resample.point = linePoint;
+      stream.lineStart();
+    }
+
+    function linePoint(λ, φ) {
+      var c = d3_geo_cartesian([λ, φ]), p = project(λ, φ);
+      resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
+      stream.point(x0, y0);
+    }
+
+    function lineEnd() {
+      resample.point = point;
+      stream.lineEnd();
+    }
+
+    function polygonLineStart() {
+      var λ00, φ00, x00, y00, a00, b00, c00; // first point
+
+      lineStart();
+
+      resample.point = function(λ, φ) {
+        linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
+        resample.point = linePoint;
+      };
+
+      resample.lineEnd = function() {
+        resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
+        resample.lineEnd = lineEnd;
+        lineEnd();
+      };
+    }
+
+    return resample;
+  }
+
+  function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
+    var dx = x1 - x0,
+        dy = y1 - y0,
+        d2 = dx * dx + dy * dy;
+    if (d2 > 4 * δ2 && depth--) {
+      var a = a0 + a1,
+          b = b0 + b1,
+          c = c0 + c1,
+          m = Math.sqrt(a * a + b * b + c * c),
+          φ2 = Math.asin(c /= m),
+          λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a),
+          p = project(λ2, φ2),
+          x2 = p[0],
+          y2 = p[1],
+          dx2 = x2 - x0,
+          dy2 = y2 - y0,
+          dz = dy * dx2 - dx * dy2;
+      if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) {
+        resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
+        stream.point(x2, y2);
+        resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
+      }
+    }
+  }
+
+  resample.precision = function(_) {
+    if (!arguments.length) return Math.sqrt(δ2);
+    maxDepth = (δ2 = _ * _) > 0 && 16;
+    return resample;
+  };
+
+  return resample;
+}
+
+d3.geo.projection = d3_geo_projection;
+d3.geo.projectionMutator = d3_geo_projectionMutator;
+
+function d3_geo_projection(project) {
+  return d3_geo_projectionMutator(function() { return project; })();
+}
+
+function d3_geo_projectionMutator(projectAt) {
+  var project,
+      rotate,
+      projectRotate,
+      projectResample = d3_geo_resample(function(x, y) { x = project(x, y); return [x[0] * k + δx, δy - x[1] * k]; }),
+      k = 150, // scale
+      x = 480, y = 250, // translate
+      λ = 0, φ = 0, // center
+      δλ = 0, δφ = 0, δγ = 0, // rotate
+      δx, δy, // center
+      preclip = d3_geo_clipAntimeridian,
+      postclip = d3_identity,
+      clipAngle = null,
+      clipExtent = null;
+
+  function projection(point) {
+    point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
+    return [point[0] * k + δx, δy - point[1] * k];
+  }
+
+  function invert(point) {
+    point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
+    return point && [point[0] * d3_degrees, point[1] * d3_degrees];
+  }
+
+  projection.stream = function(stream) {
+    return d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(stream))));
+  };
+
+  projection.clipAngle = function(_) {
+    if (!arguments.length) return clipAngle;
+    preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
+    return projection;
+  };
+
+  projection.clipExtent = function(_) {
+    if (!arguments.length) return clipExtent;
+    clipExtent = _;
+    postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]);
+    return projection;
+  };
+
+  projection.scale = function(_) {
+    if (!arguments.length) return k;
+    k = +_;
+    return reset();
+  };
+
+  projection.translate = function(_) {
+    if (!arguments.length) return [x, y];
+    x = +_[0];
+    y = +_[1];
+    return reset();
+  };
+
+  projection.center = function(_) {
+    if (!arguments.length) return [λ * d3_degrees, φ * d3_degrees];
+    λ = _[0] % 360 * d3_radians;
+    φ = _[1] % 360 * d3_radians;
+    return reset();
+  };
+
+  projection.rotate = function(_) {
+    if (!arguments.length) return [δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees];
+    δλ = _[0] % 360 * d3_radians;
+    δφ = _[1] % 360 * d3_radians;
+    δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
+    return reset();
+  };
+
+  d3.rebind(projection, projectResample, "precision");
+
+  function reset() {
+    projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
+    var center = project(λ, φ);
+    δx = x - center[0] * k;
+    δy = y + center[1] * k;
+    return projection;
+  }
+
+  return function() {
+    project = projectAt.apply(this, arguments);
+    projection.invert = project.invert && invert;
+    return reset();
+  };
+}
+
+function d3_geo_projectionRadiansRotate(rotate, stream) {
+  return {
+    point: function(x, y) {
+      y = rotate(x * d3_radians, y * d3_radians), x = y[0];
+      stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]);
+    },
+    sphere: function() { stream.sphere(); },
+    lineStart: function() { stream.lineStart(); },
+    lineEnd: function() { stream.lineEnd(); },
+    polygonStart: function() { stream.polygonStart(); },
+    polygonEnd: function() { stream.polygonEnd(); }
+  };
+}
+
+function d3_geo_mercator(λ, φ) {
+  return [λ, Math.log(Math.tan(π / 4 + φ / 2))];
+}
+
+d3_geo_mercator.invert = function(x, y) {
+  return [x, 2 * Math.atan(Math.exp(y)) - π / 2];
+};
+
+function d3_geo_mercatorProjection(project) {
+  var m = d3_geo_projection(project),
+      scale = m.scale,
+      translate = m.translate,
+      clipExtent = m.clipExtent,
+      clipAuto;
+
+  m.scale = function() {
+    var v = scale.apply(m, arguments);
+    return v === m ? (clipAuto ? m.clipExtent(null) : m) : v;
+  };
+
+  m.translate = function() {
+    var v = translate.apply(m, arguments);
+    return v === m ? (clipAuto ? m.clipExtent(null) : m) : v;
+  };
+
+  m.clipExtent = function(_) {
+    var v = clipExtent.apply(m, arguments);
+    if (v === m) {
+      if (clipAuto = _ == null) {
+        var k = π * scale(), t = translate();
+        clipExtent([[t[0] - k, t[1] - k], [t[0] + k, t[1] + k]]);
+      }
+    } else if (clipAuto) {
+      v = null;
+    }
+    return v;
+  };
+
+  return m.clipExtent(null);
+}
+
+(d3.geo.mercator = function() {
+  return d3_geo_mercatorProjection(d3_geo_mercator);
+}).raw = d3_geo_mercator;
+
+function d3_geo_conic(projectAt) {
+  var φ0 = 0,
+      φ1 = π / 3,
+      m = d3_geo_projectionMutator(projectAt),
+      p = m(φ0, φ1);
+
+  p.parallels = function(_) {
+    if (!arguments.length) return [φ0 / π * 180, φ1 / π * 180];
+    return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
+  };
+
+  return p;
+}
+
+function d3_geo_conicEqualArea(φ0, φ1) {
+  var sinφ0 = Math.sin(φ0),
+      n = (sinφ0 + Math.sin(φ1)) / 2,
+      C = 1 + sinφ0 * (2 * n - sinφ0),
+      ρ0 = Math.sqrt(C) / n;
+
+  function forward(λ, φ) {
+    var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
+    return [
+      ρ * Math.sin(λ *= n),
+      ρ0 - ρ * Math.cos(λ)
+    ];
+  }
+
+  forward.invert = function(x, y) {
+    var ρ0_y = ρ0 - y;
+    return [
+      Math.atan2(x, ρ0_y) / n,
+      Math.asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n))
+    ];
+  };
+
+  return forward;
+}
+
+(d3.geo.conicEqualArea = function() {
+  return d3_geo_conic(d3_geo_conicEqualArea);
+}).raw = d3_geo_conicEqualArea;
+
+// A composite projection for the United States, 960×500. The set of standard
+// parallels for each region comes from USGS, which is published here:
+// http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers
+d3.geo.albersUsa = function() {
+  var lower48 = d3.geo.conicEqualArea()
+      .rotate([98, 0])
+      .center([0, 38])
+      .parallels([29.5, 45.5]);
+
+  var alaska = d3.geo.conicEqualArea()
+      .rotate([160, 0])
+      .center([0, 60])
+      .parallels([55, 65]);
+
+  var hawaii = d3.geo.conicEqualArea()
+      .rotate([160, 0])
+      .center([0, 20])
+      .parallels([8, 18]);
+
+  var puertoRico = d3.geo.conicEqualArea()
+      .rotate([60, 0])
+      .center([0, 10])
+      .parallels([8, 18]);
+
+  var alaskaInvert,
+      hawaiiInvert,
+      puertoRicoInvert;
+
+  function albersUsa(coordinates) {
+    return projection(coordinates)(coordinates);
+  }
+
+  function projection(point) {
+    var lon = point[0],
+        lat = point[1];
+    return lat > 50 ? alaska
+        : lon < -140 ? hawaii
+        : lat < 21 ? puertoRico
+        : lower48;
+  }
+
+  albersUsa.invert = function(coordinates) {
+    return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates);
+  };
+
+  albersUsa.scale = function(x) {
+    if (!arguments.length) return lower48.scale();
+    lower48.scale(x);
+    alaska.scale(x * .6);
+    hawaii.scale(x);
+    puertoRico.scale(x * 1.5);
+    return albersUsa.translate(lower48.translate());
+  };
+
+  albersUsa.translate = function(x) {
+    if (!arguments.length) return lower48.translate();
+    var dz = lower48.scale(),
+        dx = x[0],
+        dy = x[1];
+    lower48.translate(x);
+    alaska.translate([dx - .40 * dz, dy + .17 * dz]);
+    hawaii.translate([dx - .19 * dz, dy + .20 * dz]);
+    puertoRico.translate([dx + .58 * dz, dy + .43 * dz]);
+
+    alaskaInvert = d3_geo_albersUsaInvert(alaska, [[-180, 50], [-130, 72]]);
+    hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [[-164, 18], [-154, 24]]);
+    puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [[-67.5, 17.5], [-65, 19]]);
+
+    return albersUsa;
+  };
+
+  return albersUsa.scale(1000);
+};
+
+function d3_geo_albersUsaInvert(projection, extent) {
+  var a = projection(extent[0]),
+      b = projection([.5 * (extent[0][0] + extent[1][0]), extent[0][1]]),
+      c = projection([extent[1][0], extent[0][1]]),
+      d = projection(extent[1]);
+
+  var dya = b[1]- a[1],
+      dxa = b[0]- a[0],
+      dyb = c[1]- b[1],
+      dxb = c[0]- b[0];
+
+  var ma = dya / dxa,
+      mb = dyb / dxb;
+
+  // Find center of circle going through points [a, b, c].
+  var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma),
+      cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]);
+
+  // Radial distance² from center.
+  var dx0 = d[0] - cx,
+      dy0 = d[1] - cy,
+      dx1 = a[0] - cx,
+      dy1 = a[1] - cy,
+      r0 = dx0 * dx0 + dy0 * dy0,
+      r1 = dx1 * dx1 + dy1 * dy1;
+
+  // Angular extent.
+  var a0 = Math.atan2(dy0, dx0),
+      a1 = Math.atan2(dy1, dx1);
+
+  return function(coordinates) {
+    var dx = coordinates[0] - cx,
+        dy = coordinates[1] - cy,
+        r = dx * dx + dy * dy,
+        a = Math.atan2(dy, dx);
+    if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates);
+  };
+}
+
+d3.geo.area = function(object) {
+  d3_geo_areaSum = 0;
+  d3.geo.stream(object, d3_geo_area);
+  return d3_geo_areaSum;
+};
+
+var d3_geo_areaSum,
+    d3_geo_areaRingU,
+    d3_geo_areaRingV;
+
+var d3_geo_area = {
+  sphere: function() { d3_geo_areaSum += 4 * π; },
+  point: d3_noop,
+  lineStart: d3_noop,
+  lineEnd: d3_noop,
+
+  // Only count area for polygon rings.
+  polygonStart: function() {
+    d3_geo_areaRingU = 1, d3_geo_areaRingV = 0;
+    d3_geo_area.lineStart = d3_geo_areaRingStart;
+  },
+  polygonEnd: function() {
+    var area = 2 * Math.atan2(d3_geo_areaRingV, d3_geo_areaRingU);
+    d3_geo_areaSum += area < 0 ? 4 * π + area : area;
+    d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
+  }
+};
+
+function d3_geo_areaRingStart() {
+  var λ00, φ00, λ0, cosφ0, sinφ0; // start point and two previous points
+
+  // For the first point, …
+  d3_geo_area.point = function(λ, φ) {
+    d3_geo_area.point = nextPoint;
+    λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), sinφ0 = Math.sin(φ);
+  };
+
+  // For subsequent points, …
+  function nextPoint(λ, φ) {
+    λ *= d3_radians;
+    φ = φ * d3_radians / 2 + π / 4; // half the angular distance from south pole
+
+    // Spherical excess E for a spherical triangle with vertices: south pole,
+    // previous point, current point.  Uses a formula derived from Cagnoli’s
+    // theorem.  See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2).
+    var dλ = λ - λ0,
+        cosφ = Math.cos(φ),
+        sinφ = Math.sin(φ),
+        k = sinφ0 * sinφ,
+        u0 = d3_geo_areaRingU,
+        v0 = d3_geo_areaRingV,
+        u = cosφ0 * cosφ + k * Math.cos(dλ),
+        v = k * Math.sin(dλ);
+    // ∑ arg(z) = arg(∏ z), where z = u + iv.
+    d3_geo_areaRingU = u0 * u - v0 * v;
+    d3_geo_areaRingV = v0 * u + u0 * v;
+
+    // Advance the previous points.
+    λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
+  }
+
+  // For the last point, return to the start.
+  d3_geo_area.lineEnd = function() {
+    nextPoint(λ00, φ00);
+  };
+}
+
+d3.geo.bounds = d3_geo_bounds(d3_identity);
+
+function d3_geo_bounds(projectStream) {
+  var x0, y0, x1, y1;
+
+  var bound = {
+    point: boundPoint,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+
+    // While inside a polygon, ignore points in holes.
+    polygonStart: function() { bound.lineEnd = boundPolygonLineEnd; },
+    polygonEnd: function() { bound.point = boundPoint; }
+  };
+
+  function boundPoint(x, y) {
+    if (x < x0) x0 = x;
+    if (x > x1) x1 = x;
+    if (y < y0) y0 = y;
+    if (y > y1) y1 = y;
+  }
+
+  function boundPolygonLineEnd() {
+    bound.point = bound.lineEnd = d3_noop;
+  }
+
+  return function(feature) {
+    y1 = x1 = -(x0 = y0 = Infinity);
+    d3.geo.stream(feature, projectStream(bound));
+    return [[x0, y0], [x1, y1]];
+  };
+}
+
+d3.geo.centroid = function(object) {
+  d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+  d3.geo.stream(object, d3_geo_centroid);
+  var m;
+  if (d3_geo_centroidW &&
+      Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) {
+    return [
+      Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees,
+      Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees
+    ];
+  }
+};
+
+var d3_geo_centroidDimension,
+    d3_geo_centroidW,
+    d3_geo_centroidX,
+    d3_geo_centroidY,
+    d3_geo_centroidZ;
+
+var d3_geo_centroid = {
+  sphere: function() {
+    if (d3_geo_centroidDimension < 2) {
+      d3_geo_centroidDimension = 2;
+      d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+    }
+  },
+  point: d3_geo_centroidPoint,
+  lineStart: d3_geo_centroidLineStart,
+  lineEnd: d3_geo_centroidLineEnd,
+  polygonStart: function() {
+    if (d3_geo_centroidDimension < 2) {
+      d3_geo_centroidDimension = 2;
+      d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+    }
+    d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
+  },
+  polygonEnd: function() {
+    d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
+  }
+};
+
+// Arithmetic mean of Cartesian vectors.
+function d3_geo_centroidPoint(λ, φ) {
+  if (d3_geo_centroidDimension) return;
+  ++d3_geo_centroidW;
+  λ *= d3_radians;
+  var cosφ = Math.cos(φ *= d3_radians);
+  d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW;
+  d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW;
+  d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW;
+}
+
+function d3_geo_centroidRingStart() {
+  var λ00, φ00; // first point
+
+  d3_geo_centroidDimension = 1;
+  d3_geo_centroidLineStart();
+  d3_geo_centroidDimension = 2;
+
+  var linePoint = d3_geo_centroid.point;
+  d3_geo_centroid.point = function(λ, φ) {
+    linePoint(λ00 = λ, φ00 = φ);
+  };
+  d3_geo_centroid.lineEnd = function() {
+    d3_geo_centroid.point(λ00, φ00);
+    d3_geo_centroidLineEnd();
+    d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
+  };
+}
+
+function d3_geo_centroidLineStart() {
+  var x0, y0, z0; // previous point
+
+  if (d3_geo_centroidDimension > 1) return;
+  if (d3_geo_centroidDimension < 1) {
+    d3_geo_centroidDimension = 1;
+    d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+  }
+
+  d3_geo_centroid.point = function(λ, φ) {
+    λ *= d3_radians;
+    var cosφ = Math.cos(φ *= d3_radians);
+    x0 = cosφ * Math.cos(λ);
+    y0 = cosφ * Math.sin(λ);
+    z0 = Math.sin(φ);
+    d3_geo_centroid.point = nextPoint;
+  };
+
+  function nextPoint(λ, φ) {
+    λ *= d3_radians;
+    var cosφ = Math.cos(φ *= d3_radians),
+        x = cosφ * Math.cos(λ),
+        y = cosφ * Math.sin(λ),
+        z = Math.sin(φ),
+        w = Math.atan2(
+          Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w),
+          x0 * x + y0 * y + z0 * z);
+    d3_geo_centroidW += w;
+    d3_geo_centroidX += w * (x0 + (x0 = x));
+    d3_geo_centroidY += w * (y0 + (y0 = y));
+    d3_geo_centroidZ += w * (z0 + (z0 = z));
+  }
+}
+
+function d3_geo_centroidLineEnd() {
+  d3_geo_centroid.point = d3_geo_centroidPoint;
+}
+
+// TODO Unify this code with d3.geom.polygon area?
+
+var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
+  point: d3_noop,
+  lineStart: d3_noop,
+  lineEnd: d3_noop,
+
+  // Only count area for polygon rings.
+  polygonStart: function() {
+    d3_geo_pathAreaPolygon = 0;
+    d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
+  },
+  polygonEnd: function() {
+    d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
+    d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2);
+  }
+};
+
+function d3_geo_pathAreaRingStart() {
+  var x00, y00, x0, y0;
+
+  // For the first point, …
+  d3_geo_pathArea.point = function(x, y) {
+    d3_geo_pathArea.point = nextPoint;
+    x00 = x0 = x, y00 = y0 = y;
+  };
+
+  // For subsequent points, …
+  function nextPoint(x, y) {
+    d3_geo_pathAreaPolygon += y0 * x - x0 * y;
+    x0 = x, y0 = y;
+  }
+
+  // For the last point, return to the start.
+  d3_geo_pathArea.lineEnd = function() {
+    nextPoint(x00, y00);
+  };
+}
+function d3_geo_pathBuffer() {
+  var pointCircle = d3_geo_pathCircle(4.5),
+      buffer = [];
+
+  var stream = {
+    point: point,
+
+    // While inside a line, override point to moveTo then lineTo.
+    lineStart: function() { stream.point = pointLineStart; },
+    lineEnd: lineEnd,
+
+    // While inside a polygon, override lineEnd to closePath.
+    polygonStart: function() { stream.lineEnd = lineEndPolygon; },
+    polygonEnd: function() { stream.lineEnd = lineEnd; stream.point = point; },
+
+    pointRadius: function(_) {
+      pointCircle = d3_geo_pathCircle(_);
+      return stream;
+    },
+
+    result: function() {
+      if (buffer.length) {
+        var result = buffer.join("");
+        buffer = [];
+        return result;
+      }
+    }
+  };
+
+  function point(x, y) {
+    buffer.push("M", x, ",", y, pointCircle);
+  }
+
+  function pointLineStart(x, y) {
+    buffer.push("M", x, ",", y);
+    stream.point = pointLine;
+  }
+
+  function pointLine(x, y) {
+    buffer.push("L", x, ",", y);
+  }
+
+  function lineEnd() {
+    stream.point = point;
+  }
+
+  function lineEndPolygon() {
+    buffer.push("Z");
+  }
+
+  return stream;
+}
+
+// TODO Unify this code with d3.geom.polygon centroid?
+// TODO Enforce positive area for exterior, negative area for interior?
+
+var d3_geo_pathCentroid = {
+  point: d3_geo_pathCentroidPoint,
+
+  // For lines, weight by length.
+  lineStart: d3_geo_pathCentroidLineStart,
+  lineEnd: d3_geo_pathCentroidLineEnd,
+
+  // For polygons, weight by area.
+  polygonStart: function() {
+    d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
+  },
+  polygonEnd: function() {
+    d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+    d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
+    d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
+  }
+};
+
+function d3_geo_pathCentroidPoint(x, y) {
+  if (d3_geo_centroidDimension) return;
+  d3_geo_centroidX += x;
+  d3_geo_centroidY += y;
+  ++d3_geo_centroidZ;
+}
+
+function d3_geo_pathCentroidLineStart() {
+  var x0, y0;
+
+  if (d3_geo_centroidDimension !== 1) {
+    if (d3_geo_centroidDimension < 1) {
+      d3_geo_centroidDimension = 1;
+      d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+    } else return;
+  }
+
+  d3_geo_pathCentroid.point = function(x, y) {
+    d3_geo_pathCentroid.point = nextPoint;
+    x0 = x, y0 = y;
+  };
+
+  function nextPoint(x, y) {
+    var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+    d3_geo_centroidX += z * (x0 + x) / 2;
+    d3_geo_centroidY += z * (y0 + y) / 2;
+    d3_geo_centroidZ += z;
+    x0 = x, y0 = y;
+  }
+}
+
+function d3_geo_pathCentroidLineEnd() {
+  d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+}
+
+function d3_geo_pathCentroidRingStart() {
+  var x00, y00, x0, y0;
+
+  if (d3_geo_centroidDimension < 2) {
+    d3_geo_centroidDimension = 2;
+    d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+  }
+
+  // For the first point, …
+  d3_geo_pathCentroid.point = function(x, y) {
+    d3_geo_pathCentroid.point = nextPoint;
+    x00 = x0 = x, y00 = y0 = y;
+  };
+
+  // For subsequent points, …
+  function nextPoint(x, y) {
+    var z = y0 * x - x0 * y;
+    d3_geo_centroidX += z * (x0 + x);
+    d3_geo_centroidY += z * (y0 + y);
+    d3_geo_centroidZ += z * 3;
+    x0 = x, y0 = y;
+  }
+
+  // For the last point, return to the start.
+  d3_geo_pathCentroid.lineEnd = function() {
+    nextPoint(x00, y00);
+  };
+}
+
+function d3_geo_pathContext(context) {
+  var pointRadius = 4.5;
+
+  var stream = {
+    point: point,
+
+    // While inside a line, override point to moveTo then lineTo.
+    lineStart: function() { stream.point = pointLineStart; },
+    lineEnd: lineEnd,
+
+    // While inside a polygon, override lineEnd to closePath.
+    polygonStart: function() { stream.lineEnd = lineEndPolygon; },
+    polygonEnd: function() { stream.lineEnd = lineEnd; stream.point = point; },
+
+    pointRadius: function(_) {
+      pointRadius = _;
+      return stream;
+    },
+
+    result: d3_noop
+  };
+
+  function point(x, y) {
+    context.moveTo(x, y);
+    context.arc(x, y, pointRadius, 0, 2 * π);
+  }
+
+  function pointLineStart(x, y) {
+    context.moveTo(x, y);
+    stream.point = pointLine;
+  }
+
+  function pointLine(x, y) {
+    context.lineTo(x, y);
+  }
+
+  function lineEnd() {
+    stream.point = point;
+  }
+
+  function lineEndPolygon() {
+    context.closePath();
+  }
+
+  return stream;
+}
+
+d3.geo.path = function() {
+  var pointRadius = 4.5,
+      projection,
+      context,
+      projectStream,
+      contextStream;
+
+  function path(object) {
+    if (object) d3.geo.stream(object, projectStream(
+        contextStream.pointRadius(typeof pointRadius === "function"
+            ? +pointRadius.apply(this, arguments)
+            : pointRadius)));
+    return contextStream.result();
+  }
+
+  path.area = function(object) {
+    d3_geo_pathAreaSum = 0;
+    d3.geo.stream(object, projectStream(d3_geo_pathArea));
+    return d3_geo_pathAreaSum;
+  };
+
+  path.centroid = function(object) {
+    d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0;
+    d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
+    return d3_geo_centroidZ ? [d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ] : undefined;
+  };
+
+  path.bounds = function(object) {
+    return d3_geo_bounds(projectStream)(object);
+  };
+
+  path.projection = function(_) {
+    if (!arguments.length) return projection;
+    projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
+    return path;
+  };
+
+  path.context = function(_) {
+    if (!arguments.length) return context;
+    contextStream = (context = _) == null ? new d3_geo_pathBuffer : new d3_geo_pathContext(_);
+    return path;
+  };
+
+  path.pointRadius = function(_) {
+    if (!arguments.length) return pointRadius;
+    pointRadius = typeof _ === "function" ? _ : +_;
+    return path;
+  };
+
+  return path.projection(d3.geo.albersUsa()).context(null);
+};
+
+function d3_geo_pathCircle(radius) {
+  return "m0," + radius
+      + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius)
+      + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius)
+      + "z";
+}
+
+function d3_geo_pathProjectStream(project) {
+  var resample = d3_geo_resample(function(λ, φ) { return project([λ * d3_degrees, φ * d3_degrees]); });
+  return function(stream) {
+    stream = resample(stream);
+    return {
+      point: function(λ, φ) { stream.point(λ * d3_radians, φ * d3_radians); },
+      sphere: function() { stream.sphere(); },
+      lineStart: function() { stream.lineStart(); },
+      lineEnd: function() { stream.lineEnd(); },
+      polygonStart: function() { stream.polygonStart(); },
+      polygonEnd: function() { stream.polygonEnd(); }
+    };
+  };
+}
+d3.geom = {};
+
+d3.geom.polygon = function(coordinates) {
+
+  coordinates.area = function() {
+    var i = 0,
+        n = coordinates.length,
+        area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1];
+    while (++i < n) {
+      area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][1];
+    }
+    return area * .5;
+  };
+
+  coordinates.centroid = function(k) {
+    var i = -1,
+        n = coordinates.length,
+        x = 0,
+        y = 0,
+        a,
+        b = coordinates[n - 1],
+        c;
+    if (!arguments.length) k = -1 / (6 * coordinates.area());
+    while (++i < n) {
+      a = b;
+      b = coordinates[i];
+      c = a[0] * b[1] - b[0] * a[1];
+      x += (a[0] + b[0]) * c;
+      y += (a[1] + b[1]) * c;
+    }
+    return [x * k, y * k];
+  };
+
+  // The Sutherland-Hodgman clipping algorithm.
+  // Note: requires the clip polygon to be counterclockwise and convex.
+  coordinates.clip = function(subject) {
+    var input,
+        i = -1,
+        n = coordinates.length,
+        j,
+        m,
+        a = coordinates[n - 1],
+        b,
+        c,
+        d;
+    while (++i < n) {
+      input = subject.slice();
+      subject.length = 0;
+      b = coordinates[i];
+      c = input[(m = input.length) - 1];
+      j = -1;
+      while (++j < m) {
+        d = input[j];
+        if (d3_geom_polygonInside(d, a, b)) {
+          if (!d3_geom_polygonInside(c, a, b)) {
+            subject.push(d3_geom_polygonIntersect(c, d, a, b));
+          }
+          subject.push(d);
+        } else if (d3_geom_polygonInside(c, a, b)) {
+          subject.push(d3_geom_polygonIntersect(c, d, a, b));
+        }
+        c = d;
+      }
+      a = b;
+    }
+    return subject;
+  };
+
+  return coordinates;
+};
+
+function d3_geom_polygonInside(p, a, b) {
+  return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
+}
+
+// Intersect two infinite lines cd and ab.
+function d3_geom_polygonIntersect(c, d, a, b) {
+  var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3,
+      y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3,
+      ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
+  return [x1 + ua * x21, y1 + ua * y21];
+}
+
+var d3_ease_default = function() { return d3_identity; };
+
+var d3_ease = d3.map({
+  linear: d3_ease_default,
+  poly: d3_ease_poly,
+  quad: function() { return d3_ease_quad; },
+  cubic: function() { return d3_ease_cubic; },
+  sin: function() { return d3_ease_sin; },
+  exp: function() { return d3_ease_exp; },
+  circle: function() { return d3_ease_circle; },
+  elastic: d3_ease_elastic,
+  back: d3_ease_back,
+  bounce: function() { return d3_ease_bounce; }
+});
+
+var d3_ease_mode = d3.map({
+  "in": d3_identity,
+  "out": d3_ease_reverse,
+  "in-out": d3_ease_reflect,
+  "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); }
+});
+
+d3.ease = function(name) {
+  var i = name.indexOf("-"),
+      t = i >= 0 ? name.substring(0, i) : name,
+      m = i >= 0 ? name.substring(i + 1) : "in";
+  t = d3_ease.get(t) || d3_ease_default;
+  m = d3_ease_mode.get(m) || d3_identity;
+  return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1))));
+};
+
+function d3_ease_clamp(f) {
+  return function(t) {
+    return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
+  };
+}
+
+function d3_ease_reverse(f) {
+  return function(t) {
+    return 1 - f(1 - t);
+  };
+}
+
+function d3_ease_reflect(f) {
+  return function(t) {
+    return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t)));
+  };
+}
+
+function d3_ease_quad(t) {
+  return t * t;
+}
+
+function d3_ease_cubic(t) {
+  return t * t * t;
+}
+
+// Optimized clamp(reflect(poly(3))).
+function d3_ease_cubicInOut(t) {
+  if (t <= 0) return 0;
+  if (t >= 1) return 1;
+  var t2 = t * t, t3 = t2 * t;
+  return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
+}
+
+function d3_ease_poly(e) {
+  return function(t) {
+    return Math.pow(t, e);
+  };
+}
+
+function d3_ease_sin(t) {
+  return 1 - Math.cos(t * π / 2);
+}
+
+function d3_ease_exp(t) {
+  return Math.pow(2, 10 * (t - 1));
+}
+
+function d3_ease_circle(t) {
+  return 1 - Math.sqrt(1 - t * t);
+}
+
+function d3_ease_elastic(a, p) {
+  var s;
+  if (arguments.length < 2) p = 0.45;
+  if (arguments.length) s = p / (2 * π) * Math.asin(1 / a);
+  else a = 1, s = p / 4;
+  return function(t) {
+    return 1 + a * Math.pow(2, 10 * -t) * Math.sin((t - s) * 2 * π / p);
+  };
+}
+
+function d3_ease_back(s) {
+  if (!s) s = 1.70158;
+  return function(t) {
+    return t * t * ((s + 1) * t - s);
+  };
+}
+
+function d3_ease_bounce(t) {
+  return t < 1 / 2.75 ? 7.5625 * t * t
+      : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75
+      : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375
+      : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
+}
+
+function d3_transition(groups, id) {
+  d3_arraySubclass(groups, d3_transitionPrototype);
+
+  groups.id = id; // Note: read-only!
+
+  return groups;
+}
+
+var d3_transitionPrototype = [],
+    d3_transitionId = 0,
+    d3_transitionInheritId,
+    d3_transitionInherit = {ease: d3_ease_cubicInOut, delay: 0, duration: 250};
+
+d3_transitionPrototype.call = d3_selectionPrototype.call;
+d3_transitionPrototype.empty = d3_selectionPrototype.empty;
+d3_transitionPrototype.node = d3_selectionPrototype.node;
+
+d3.transition = function(selection) {
+  return arguments.length
+      ? (d3_transitionInheritId ? selection.transition() : selection)
+      : d3_selectionRoot.transition();
+};
+
+d3.transition.prototype = d3_transitionPrototype;
+
+
+d3_transitionPrototype.select = function(selector) {
+  var id = this.id,
+      subgroups = [],
+      subgroup,
+      subnode,
+      node;
+
+  if (typeof selector !== "function") selector = d3_selection_selector(selector);
+
+  for (var j = -1, m = this.length; ++j < m;) {
+    subgroups.push(subgroup = []);
+    for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+      if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i))) {
+        if ("__data__" in node) subnode.__data__ = node.__data__;
+        d3_transitionNode(subnode, i, id, node.__transition__[id]);
+        subgroup.push(subnode);
+      } else {
+        subgroup.push(null);
+      }
+    }
+  }
+
+  return d3_transition(subgroups, id);
+};
+
+d3_transitionPrototype.selectAll = function(selector) {
+  var id = this.id,
+      subgroups = [],
+      subgroup,
+      subnodes,
+      node,
+      subnode,
+      transition;
+
+  if (typeof selector !== "function") selector = d3_selection_selectorAll(selector);
+
+  for (var j = -1, m = this.length; ++j < m;) {
+    for (var group = this[j], i = -1, n = group.length; ++i < n;) {
+      if (node = group[i]) {
+        transition = node.__transition__[id];
+        subnodes = selector.call(node, node.__data__, i);
+        subgroups.push(subgroup = []);
+        for (var k = -1, o = subnodes.length; ++k < o;) {
+          d3_transitionNode(subnode = subnodes[k], k, id, transition);
+          subgroup.push(subnode);
+        }
+      }
+    }
+  }
+
+  return d3_transition(subgroups, id);
+};
+
+d3_transitionPrototype.filter = function(filter) {
+  var subgroups = [],
+      subgroup,
+      group,
+      node;
+
+  if (typeof filter !== "function") filter = d3_selection_filter(filter);
+
+  for (var j = 0, m = this.length; j < m; j++) {
+    subgroups.push(subgroup = []);
+    for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+      if ((node = group[i]) && filter.call(node, node.__data__, i)) {
+        subgroup.push(node);
+      }
+    }
+  }
+
+  return d3_transition(subgroups, this.id, this.time).ease(this.ease());
+};
+function d3_Color() {}
+
+d3_Color.prototype.toString = function() {
+  return this.rgb() + "";
+};
+
+d3.hsl = function(h, s, l) {
+  return arguments.length === 1
+      ? (h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l)
+      : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl))
+      : d3_hsl(+h, +s, +l);
+};
+
+function d3_hsl(h, s, l) {
+  return new d3_Hsl(h, s, l);
+}
+
+function d3_Hsl(h, s, l) {
+  this.h = h;
+  this.s = s;
+  this.l = l;
+}
+
+var d3_hslPrototype = d3_Hsl.prototype = new d3_Color;
+
+d3_hslPrototype.brighter = function(k) {
+  k = Math.pow(0.7, arguments.length ? k : 1);
+  return d3_hsl(this.h, this.s, this.l / k);
+};
+
+d3_hslPrototype.darker = function(k) {
+  k = Math.pow(0.7, arguments.length ? k : 1);
+  return d3_hsl(this.h, this.s, k * this.l);
+};
+
+d3_hslPrototype.rgb = function() {
+  return d3_hsl_rgb(this.h, this.s, this.l);
+};
+
+function d3_hsl_rgb(h, s, l) {
+  var m1,
+      m2;
+
+  /* Some simple corrections for h, s and l. */
+  h = h % 360; if (h < 0) h += 360;
+  s = s < 0 ? 0 : s > 1 ? 1 : s;
+  l = l < 0 ? 0 : l > 1 ? 1 : l;
+
+  /* From FvD 13.37, CSS Color Module Level 3 */
+  m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
+  m1 = 2 * l - m2;
+
+  function v(h) {
+    if (h > 360) h -= 360;
+    else if (h < 0) h += 360;
+    if (h < 60) return m1 + (m2 - m1) * h / 60;
+    if (h < 180) return m2;
+    if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
+    return m1;
+  }
+
+  function vv(h) {
+    return Math.round(v(h) * 255);
+  }
+
+  return d3_rgb(vv(h + 120), vv(h), vv(h - 120));
+}
+
+d3.hcl = function(h, c, l) {
+  return arguments.length === 1
+      ? (h instanceof d3_Hcl ? d3_hcl(h.h, h.c, h.l)
+      : (h instanceof d3_Lab ? d3_lab_hcl(h.l, h.a, h.b)
+      : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b)))
+      : d3_hcl(+h, +c, +l);
+};
+
+function d3_hcl(h, c, l) {
+  return new d3_Hcl(h, c, l);
+}
+
+function d3_Hcl(h, c, l) {
+  this.h = h;
+  this.c = c;
+  this.l = l;
+}
+
+var d3_hclPrototype = d3_Hcl.prototype = new d3_Color;
+
+d3_hclPrototype.brighter = function(k) {
+  return d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
+};
+
+d3_hclPrototype.darker = function(k) {
+  return d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
+};
+
+d3_hclPrototype.rgb = function() {
+  return d3_hcl_lab(this.h, this.c, this.l).rgb();
+};
+
+function d3_hcl_lab(h, c, l) {
+  return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
+}
+
+d3.lab = function(l, a, b) {
+  return arguments.length === 1
+      ? (l instanceof d3_Lab ? d3_lab(l.l, l.a, l.b)
+      : (l instanceof d3_Hcl ? d3_hcl_lab(l.l, l.c, l.h)
+      : d3_rgb_lab((l = d3.rgb(l)).r, l.g, l.b)))
+      : d3_lab(+l, +a, +b);
+};
+
+function d3_lab(l, a, b) {
+  return new d3_Lab(l, a, b);
+}
+
+function d3_Lab(l, a, b) {
+  this.l = l;
+  this.a = a;
+  this.b = b;
+}
+
+// Corresponds roughly to RGB brighter/darker
+var d3_lab_K = 18;
+
+// D65 standard referent
+var d3_lab_X = 0.950470,
+    d3_lab_Y = 1,
+    d3_lab_Z = 1.088830;
+
+var d3_labPrototype = d3_Lab.prototype = new d3_Color;
+
+d3_labPrototype.brighter = function(k) {
+  return d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+};
+
+d3_labPrototype.darker = function(k) {
+  return d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+};
+
+d3_labPrototype.rgb = function() {
+  return d3_lab_rgb(this.l, this.a, this.b);
+};
+
+function d3_lab_rgb(l, a, b) {
+  var y = (l + 16) / 116,
+      x = y + a / 500,
+      z = y - b / 200;
+  x = d3_lab_xyz(x) * d3_lab_X;
+  y = d3_lab_xyz(y) * d3_lab_Y;
+  z = d3_lab_xyz(z) * d3_lab_Z;
+  return d3_rgb(
+    d3_xyz_rgb( 3.2404542 * x - 1.5371385 * y - 0.4985314 * z),
+    d3_xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z),
+    d3_xyz_rgb( 0.0556434 * x - 0.2040259 * y + 1.0572252 * z)
+  );
+}
+
+function d3_lab_hcl(l, a, b) {
+  return d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l);
+}
+
+function d3_lab_xyz(x) {
+  return x > 0.206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
+}
+function d3_xyz_lab(x) {
+  return x > 0.008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
+}
+
+function d3_xyz_rgb(r) {
+  return Math.round(255 * (r <= 0.00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - 0.055));
+}
+
+d3.rgb = function(r, g, b) {
+  return arguments.length === 1
+      ? (r instanceof d3_Rgb ? d3_rgb(r.r, r.g, r.b)
+      : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb))
+      : d3_rgb(~~r, ~~g, ~~b);
+};
+
+function d3_rgb(r, g, b) {
+  return new d3_Rgb(r, g, b);
+}
+
+function d3_Rgb(r, g, b) {
+  this.r = r;
+  this.g = g;
+  this.b = b;
+}
+
+var d3_rgbPrototype = d3_Rgb.prototype = new d3_Color;
+
+d3_rgbPrototype.brighter = function(k) {
+  k = Math.pow(0.7, arguments.length ? k : 1);
+  var r = this.r,
+      g = this.g,
+      b = this.b,
+      i = 30;
+  if (!r && !g && !b) return d3_rgb(i, i, i);
+  if (r && r < i) r = i;
+  if (g && g < i) g = i;
+  if (b && b < i) b = i;
+  return d3_rgb(
+      Math.min(255, Math.floor(r / k)),
+      Math.min(255, Math.floor(g / k)),
+      Math.min(255, Math.floor(b / k)));
+};
+
+d3_rgbPrototype.darker = function(k) {
+  k = Math.pow(0.7, arguments.length ? k : 1);
+  return d3_rgb(
+      Math.floor(k * this.r),
+      Math.floor(k * this.g),
+      Math.floor(k * this.b));
+};
+
+d3_rgbPrototype.hsl = function() {
+  return d3_rgb_hsl(this.r, this.g, this.b);
+};
+
+d3_rgbPrototype.toString = function() {
+  return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
+};
+
+function d3_rgb_hex(v) {
+  return v < 0x10
+      ? "0" + Math.max(0, v).toString(16)
+      : Math.min(255, v).toString(16);
+}
+
+function d3_rgb_parse(format, rgb, hsl) {
+  var r = 0, // red channel; int in [0, 255]
+      g = 0, // green channel; int in [0, 255]
+      b = 0, // blue channel; int in [0, 255]
+      m1, // CSS color specification match
+      m2, // CSS color specification type (e.g., rgb)
+      name;
+
+  /* Handle hsl, rgb. */
+  m1 = /([a-z]+)\((.*)\)/i.exec(format);
+  if (m1) {
+    m2 = m1[2].split(",");
+    switch (m1[1]) {
+      case "hsl": {
+        return hsl(
+          parseFloat(m2[0]), // degrees
+          parseFloat(m2[1]) / 100, // percentage
+          parseFloat(m2[2]) / 100 // percentage
+        );
+      }
+      case "rgb": {
+        return rgb(
+          d3_rgb_parseNumber(m2[0]),
+          d3_rgb_parseNumber(m2[1]),
+          d3_rgb_parseNumber(m2[2])
+        );
+      }
+    }
+  }
+
+  /* Named colors. */
+  if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b);
+
+  /* Hexadecimal colors: #rgb and #rrggbb. */
+  if (format != null && format.charAt(0) === "#") {
+    if (format.length === 4) {
+      r = format.charAt(1); r += r;
+      g = format.charAt(2); g += g;
+      b = format.charAt(3); b += b;
+    } else if (format.length === 7) {
+      r = format.substring(1, 3);
+      g = format.substring(3, 5);
+      b = format.substring(5, 7);
+    }
+    r = parseInt(r, 16);
+    g = parseInt(g, 16);
+    b = parseInt(b, 16);
+  }
+
+  return rgb(r, g, b);
+}
+
+function d3_rgb_hsl(r, g, b) {
+  var min = Math.min(r /= 255, g /= 255, b /= 255),
+      max = Math.max(r, g, b),
+      d = max - min,
+      h,
+      s,
+      l = (max + min) / 2;
+  if (d) {
+    s = l < .5 ? d / (max + min) : d / (2 - max - min);
+    if (r == max) h = (g - b) / d + (g < b ? 6 : 0);
+    else if (g == max) h = (b - r) / d + 2;
+    else h = (r - g) / d + 4;
+    h *= 60;
+  } else {
+    s = h = 0;
+  }
+  return d3_hsl(h, s, l);
+}
+
+function d3_rgb_lab(r, g, b) {
+  r = d3_rgb_xyz(r);
+  g = d3_rgb_xyz(g);
+  b = d3_rgb_xyz(b);
+  var x = d3_xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / d3_lab_X),
+      y = d3_xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / d3_lab_Y),
+      z = d3_xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / d3_lab_Z);
+  return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
+}
+
+function d3_rgb_xyz(r) {
+  return (r /= 255) <= 0.04045 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
+}
+
+function d3_rgb_parseNumber(c) { // either integer or percentage
+  var f = parseFloat(c);
+  return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
+}
+
+var d3_rgb_names = d3.map({
+  aliceblue: "#f0f8ff",
+  antiquewhite: "#faebd7",
+  aqua: "#00ffff",
+  aquamarine: "#7fffd4",
+  azure: "#f0ffff",
+  beige: "#f5f5dc",
+  bisque: "#ffe4c4",
+  black: "#000000",
+  blanchedalmond: "#ffebcd",
+  blue: "#0000ff",
+  blueviolet: "#8a2be2",
+  brown: "#a52a2a",
+  burlywood: "#deb887",
+  cadetblue: "#5f9ea0",
+  chartreuse: "#7fff00",
+  chocolate: "#d2691e",
+  coral: "#ff7f50",
+  cornflowerblue: "#6495ed",
+  cornsilk: "#fff8dc",
+  crimson: "#dc143c",
+  cyan: "#00ffff",
+  darkblue: "#00008b",
+  darkcyan: "#008b8b",
+  darkgoldenrod: "#b8860b",
+  darkgray: "#a9a9a9",
+  darkgreen: "#006400",
+  darkgrey: "#a9a9a9",
+  darkkhaki: "#bdb76b",
+  darkmagenta: "#8b008b",
+  darkolivegreen: "#556b2f",
+  darkorange: "#ff8c00",
+  darkorchid: "#9932cc",
+  darkred: "#8b0000",
+  darksalmon: "#e9967a",
+  darkseagreen: "#8fbc8f",
+  darkslateblue: "#483d8b",
+  darkslategray: "#2f4f4f",
+  darkslategrey: "#2f4f4f",
+  darkturquoise: "#00ced1",
+  darkviolet: "#9400d3",
+  deeppink: "#ff1493",
+  deepskyblue: "#00bfff",
+  dimgray: "#696969",
+  dimgrey: "#696969",
+  dodgerblue: "#1e90ff",
+  firebrick: "#b22222",
+  floralwhite: "#fffaf0",
+  forestgreen: "#228b22",
+  fuchsia: "#ff00ff",
+  gainsboro: "#dcdcdc",
+  ghostwhite: "#f8f8ff",
+  gold: "#ffd700",
+  goldenrod: "#daa520",
+  gray: "#808080",
+  green: "#008000",
+  greenyellow: "#adff2f",
+  grey: "#808080",
+  honeydew: "#f0fff0",
+  hotpink: "#ff69b4",
+  indianred: "#cd5c5c",
+  indigo: "#4b0082",
+  ivory: "#fffff0",
+  khaki: "#f0e68c",
+  lavender: "#e6e6fa",
+  lavenderblush: "#fff0f5",
+  lawngreen: "#7cfc00",
+  lemonchiffon: "#fffacd",
+  lightblue: "#add8e6",
+  lightcoral: "#f08080",
+  lightcyan: "#e0ffff",
+  lightgoldenrodyellow: "#fafad2",
+  lightgray: "#d3d3d3",
+  lightgreen: "#90ee90",
+  lightgrey: "#d3d3d3",
+  lightpink: "#ffb6c1",
+  lightsalmon: "#ffa07a",
+  lightseagreen: "#20b2aa",
+  lightskyblue: "#87cefa",
+  lightslategray: "#778899",
+  lightslategrey: "#778899",
+  lightsteelblue: "#b0c4de",
+  lightyellow: "#ffffe0",
+  lime: "#00ff00",
+  limegreen: "#32cd32",
+  linen: "#faf0e6",
+  magenta: "#ff00ff",
+  maroon: "#800000",
+  mediumaquamarine: "#66cdaa",
+  mediumblue: "#0000cd",
+  mediumorchid: "#ba55d3",
+  mediumpurple: "#9370db",
+  mediumseagreen: "#3cb371",
+  mediumslateblue: "#7b68ee",
+  mediumspringgreen: "#00fa9a",
+  mediumturquoise: "#48d1cc",
+  mediumvioletred: "#c71585",
+  midnightblue: "#191970",
+  mintcream: "#f5fffa",
+  mistyrose: "#ffe4e1",
+  moccasin: "#ffe4b5",
+  navajowhite: "#ffdead",
+  navy: "#000080",
+  oldlace: "#fdf5e6",
+  olive: "#808000",
+  olivedrab: "#6b8e23",
+  orange: "#ffa500",
+  orangered: "#ff4500",
+  orchid: "#da70d6",
+  palegoldenrod: "#eee8aa",
+  palegreen: "#98fb98",
+  paleturquoise: "#afeeee",
+  palevioletred: "#db7093",
+  papayawhip: "#ffefd5",
+  peachpuff: "#ffdab9",
+  peru: "#cd853f",
+  pink: "#ffc0cb",
+  plum: "#dda0dd",
+  powderblue: "#b0e0e6",
+  purple: "#800080",
+  red: "#ff0000",
+  rosybrown: "#bc8f8f",
+  royalblue: "#4169e1",
+  saddlebrown: "#8b4513",
+  salmon: "#fa8072",
+  sandybrown: "#f4a460",
+  seagreen: "#2e8b57",
+  seashell: "#fff5ee",
+  sienna: "#a0522d",
+  silver: "#c0c0c0",
+  skyblue: "#87ceeb",
+  slateblue: "#6a5acd",
+  slategray: "#708090",
+  slategrey: "#708090",
+  snow: "#fffafa",
+  springgreen: "#00ff7f",
+  steelblue: "#4682b4",
+  tan: "#d2b48c",
+  teal: "#008080",
+  thistle: "#d8bfd8",
+  tomato: "#ff6347",
+  turquoise: "#40e0d0",
+  violet: "#ee82ee",
+  wheat: "#f5deb3",
+  white: "#ffffff",
+  whitesmoke: "#f5f5f5",
+  yellow: "#ffff00",
+  yellowgreen: "#9acd32"
+});
+
+d3_rgb_names.forEach(function(key, value) {
+  d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb));
+});
+
+d3.interpolateRgb = d3_interpolateRgb;
+
+function d3_interpolateRgb(a, b) {
+  a = d3.rgb(a);
+  b = d3.rgb(b);
+  var ar = a.r,
+      ag = a.g,
+      ab = a.b,
+      br = b.r - ar,
+      bg = b.g - ag,
+      bb = b.b - ab;
+  return function(t) {
+    return "#"
+        + d3_rgb_hex(Math.round(ar + br * t))
+        + d3_rgb_hex(Math.round(ag + bg * t))
+        + d3_rgb_hex(Math.round(ab + bb * t));
+  };
+}
+
+d3.transform = function(string) {
+  var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
+  return (d3.transform = function(string) {
+    g.setAttribute("transform", string);
+    var t = g.transform.baseVal.consolidate();
+    return new d3_transform(t ? t.matrix : d3_transformIdentity);
+  })(string);
+};
+
+// Compute x-scale and normalize the first row.
+// Compute shear and make second row orthogonal to first.
+// Compute y-scale and normalize the second row.
+// Finally, compute the rotation.
+function d3_transform(m) {
+  var r0 = [m.a, m.b],
+      r1 = [m.c, m.d],
+      kx = d3_transformNormalize(r0),
+      kz = d3_transformDot(r0, r1),
+      ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
+  if (r0[0] * r1[1] < r1[0] * r0[1]) {
+    r0[0] *= -1;
+    r0[1] *= -1;
+    kx *= -1;
+    kz *= -1;
+  }
+  this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
+  this.translate = [m.e, m.f];
+  this.scale = [kx, ky];
+  this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
+};
+
+d3_transform.prototype.toString = function() {
+  return "translate(" + this.translate
+      + ")rotate(" + this.rotate
+      + ")skewX(" + this.skew
+      + ")scale(" + this.scale
+      + ")";
+};
+
+function d3_transformDot(a, b) {
+  return a[0] * b[0] + a[1] * b[1];
+}
+
+function d3_transformNormalize(a) {
+  var k = Math.sqrt(d3_transformDot(a, a));
+  if (k) {
+    a[0] /= k;
+    a[1] /= k;
+  }
+  return k;
+}
+
+function d3_transformCombine(a, b, k) {
+  a[0] += k * b[0];
+  a[1] += k * b[1];
+  return a;
+}
+
+var d3_transformIdentity = {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0};
+d3.interpolateNumber = d3_interpolateNumber;
+
+function d3_interpolateNumber(a, b) {
+  b -= a;
+  return function(t) { return a + b * t; };
+}
+
+d3.interpolateTransform = d3_interpolateTransform;
+
+function d3_interpolateTransform(a, b) {
+  var s = [], // string constants and placeholders
+      q = [], // number interpolators
+      n,
+      A = d3.transform(a),
+      B = d3.transform(b),
+      ta = A.translate,
+      tb = B.translate,
+      ra = A.rotate,
+      rb = B.rotate,
+      wa = A.skew,
+      wb = B.skew,
+      ka = A.scale,
+      kb = B.scale;
+
+  if (ta[0] != tb[0] || ta[1] != tb[1]) {
+    s.push("translate(", null, ",", null, ")");
+    q.push({i: 1, x: d3_interpolateNumber(ta[0], tb[0])}, {i: 3, x: d3_interpolateNumber(ta[1], tb[1])});
+  } else if (tb[0] || tb[1]) {
+    s.push("translate(" + tb + ")");
+  } else {
+    s.push("");
+  }
+
+  if (ra != rb) {
+    if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360; // shortest path
+    q.push({i: s.push(s.pop() + "rotate(", null, ")") - 2, x: d3_interpolateNumber(ra, rb)});
+  } else if (rb) {
+    s.push(s.pop() + "rotate(" + rb + ")");
+  }
+
+  if (wa != wb) {
+    q.push({i: s.push(s.pop() + "skewX(", null, ")") - 2, x: d3_interpolateNumber(wa, wb)});
+  } else if (wb) {
+    s.push(s.pop() + "skewX(" + wb + ")");
+  }
+
+  if (ka[0] != kb[0] || ka[1] != kb[1]) {
+    n = s.push(s.pop() + "scale(", null, ",", null, ")");
+    q.push({i: n - 4, x: d3_interpolateNumber(ka[0], kb[0])}, {i: n - 2, x: d3_interpolateNumber(ka[1], kb[1])});
+  } else if (kb[0] != 1 || kb[1] != 1) {
+    s.push(s.pop() + "scale(" + kb + ")");
+  }
+
+  n = q.length;
+  return function(t) {
+    var i = -1, o;
+    while (++i < n) s[(o = q[i]).i] = o.x(t);
+    return s.join("");
+  };
+}
+
+d3.interpolateObject = d3_interpolateObject;
+
+function d3_interpolateObject(a, b) {
+  var i = {},
+      c = {},
+      k;
+  for (k in a) {
+    if (k in b) {
+      i[k] = d3_interpolateByName(k)(a[k], b[k]);
+    } else {
+      c[k] = a[k];
+    }
+  }
+  for (k in b) {
+    if (!(k in a)) {
+      c[k] = b[k];
+    }
+  }
+  return function(t) {
+    for (k in i) c[k] = i[k](t);
+    return c;
+  };
+}
+
+d3.interpolateArray = d3_interpolateArray;
+
+function d3_interpolateArray(a, b) {
+  var x = [],
+      c = [],
+      na = a.length,
+      nb = b.length,
+      n0 = Math.min(a.length, b.length),
+      i;
+  for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
+  for (; i < na; ++i) c[i] = a[i];
+  for (; i < nb; ++i) c[i] = b[i];
+  return function(t) {
+    for (i = 0; i < n0; ++i) c[i] = x[i](t);
+    return c;
+  };
+}
+
+d3.interpolateString = d3_interpolateString;
+
+function d3_interpolateString(a, b) {
+  var m, // current match
+      i, // current index
+      j, // current index (for coalescing)
+      s0 = 0, // start index of current string prefix
+      s1 = 0, // end index of current string prefix
+      s = [], // string constants and placeholders
+      q = [], // number interpolators
+      n, // q.length
+      o;
+
+  // Reset our regular expression!
+  d3_interpolate_number.lastIndex = 0;
+
+  // Find all numbers in b.
+  for (i = 0; m = d3_interpolate_number.exec(b); ++i) {
+    if (m.index) s.push(b.substring(s0, s1 = m.index));
+    q.push({i: s.length, x: m[0]});
+    s.push(null);
+    s0 = d3_interpolate_number.lastIndex;
+  }
+  if (s0 < b.length) s.push(b.substring(s0));
+
+  // Find all numbers in a.
+  for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) {
+    o = q[i];
+    if (o.x == m[0]) { // The numbers match, so coalesce.
+      if (o.i) {
+        if (s[o.i + 1] == null) { // This match is followed by another number.
+          s[o.i - 1] += o.x;
+          s.splice(o.i, 1);
+          for (j = i + 1; j < n; ++j) q[j].i--;
+        } else { // This match is followed by a string, so coalesce twice.
+          s[o.i - 1] += o.x + s[o.i + 1];
+          s.splice(o.i, 2);
+          for (j = i + 1; j < n; ++j) q[j].i -= 2;
+        }
+      } else {
+          if (s[o.i + 1] == null) { // This match is followed by another number.
+          s[o.i] = o.x;
+        } else { // This match is followed by a string, so coalesce twice.
+          s[o.i] = o.x + s[o.i + 1];
+          s.splice(o.i + 1, 1);
+          for (j = i + 1; j < n; ++j) q[j].i--;
+        }
+      }
+      q.splice(i, 1);
+      n--;
+      i--;
+    } else {
+      o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x));
+    }
+  }
+
+  // Remove any numbers in b not found in a.
+  while (i < n) {
+    o = q.pop();
+    if (s[o.i + 1] == null) { // This match is followed by another number.
+      s[o.i] = o.x;
+    } else { // This match is followed by a string, so coalesce twice.
+      s[o.i] = o.x + s[o.i + 1];
+      s.splice(o.i + 1, 1);
+    }
+    n--;
+  }
+
+  // Special optimization for only a single match.
+  if (s.length === 1) {
+    return s[0] == null ? q[0].x : function() { return b; };
+  }
+
+  // Otherwise, interpolate each of the numbers and rejoin the string.
+  return function(t) {
+    for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t);
+    return s.join("");
+  };
+}
+
+var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g;
+
+d3.interpolate = d3_interpolate;
+
+function d3_interpolate(a, b) {
+  var i = d3.interpolators.length, f;
+  while (--i >= 0 && !(f = d3.interpolators[i](a, b)));
+  return f;
+}
+
+function d3_interpolateByName(name) {
+  return name == "transform"
+      ? d3_interpolateTransform
+      : d3_interpolate;
+}
+
+d3.interpolators = [
+  d3_interpolateObject,
+  function(a, b) { return Array.isArray(b) && d3_interpolateArray(a, b); },
+  function(a, b) { return (typeof a === "string" || typeof b === "string") && d3_interpolateString(a + "", b + ""); },
+  function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3_interpolateRgb(a, b); },
+  function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3_interpolateNumber(a, b); }
+];
+
+d3_transitionPrototype.tween = function(name, tween) {
+  var id = this.id;
+  if (arguments.length < 2) return this.node().__transition__[id].tween.get(name);
+  return d3_selection_each(this, tween == null
+        ? function(node) { node.__transition__[id].tween.remove(name); }
+        : function(node) { node.__transition__[id].tween.set(name, tween); });
+};
+
+function d3_transition_tween(groups, name, value, tween) {
+  var id = groups.id;
+  return d3_selection_each(groups, typeof value === "function"
+      ? function(node, i, j) { node.__transition__[id].tween.set(name, tween(value.call(node, node.__data__, i, j))); }
+      : (value = tween(value), function(node) { node.__transition__[id].tween.set(name, value); }));
+}
+
+d3_transitionPrototype.attr = function(nameNS, value) {
+  if (arguments.length < 2) {
+
+    // For attr(object), the object specifies the names and values of the
+    // attributes to transition. The values may be functions that are
+    // evaluated for each element.
+    for (value in nameNS) this.attr(value, nameNS[value]);
+    return this;
+  }
+
+  var interpolate = d3_interpolateByName(nameNS),
+      name = d3.ns.qualify(nameNS);
+
+  // For attr(string, null), remove the attribute with the specified name.
+  function attrNull() {
+    this.removeAttribute(name);
+  }
+  function attrNullNS() {
+    this.removeAttributeNS(name.space, name.local);
+  }
+
+  return d3_transition_tween(this, "attr." + nameNS, value, function(b) {
+
+    // For attr(string, string), set the attribute with the specified name.
+    function attrString() {
+      var a = this.getAttribute(name), i;
+      return a !== b && (i = interpolate(a, b), function(t) { this.setAttribute(name, i(t)); });
+    }
+    function attrStringNS() {
+      var a = this.getAttributeNS(name.space, name.local), i;
+      return a !== b && (i = interpolate(a, b), function(t) { this.setAttributeNS(name.space, name.local, i(t)); });
+    }
+
+    return b == null ? (name.local ? attrNullNS : attrNull)
+        : (b += "", name.local ? attrStringNS : attrString);
+  });
+};
+
+d3_transitionPrototype.attrTween = function(nameNS, tween) {
+  var name = d3.ns.qualify(nameNS);
+
+  function attrTween(d, i) {
+    var f = tween.call(this, d, i, this.getAttribute(name));
+    return f && function(t) { this.setAttribute(name, f(t)); };
+  }
+
+  function attrTweenNS(d, i) {
+    var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
+    return f && function(t) { this.setAttributeNS(name.space, name.local, f(t)); };
+  }
+
+  return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
+};
+
+d3_transitionPrototype.style = function(name, value, priority) {
+  var n = arguments.length;
+  if (n < 3) {
+
+    // For style(object) or style(object, string), the object specifies the
+    // names and values of the attributes to set or remove. The values may be
+    // functions that are evaluated for each element. The optional string
+    // specifies the priority.
+    if (typeof name !== "string") {
+      if (n < 2) value = "";
+      for (priority in name) this.style(priority, name[priority], value);
+      return this;
+    }
+
+    // For style(string, string) or style(string, function), use the default
+    // priority. The priority is ignored for style(string, null).
+    priority = "";
+  }
+
+  var interpolate = d3_interpolateByName(name);
+
+  // For style(name, null) or style(name, null, priority), remove the style
+  // property with the specified name. The priority is ignored.
+  function styleNull() {
+    this.style.removeProperty(name);
+  }
+
+  // Otherwise, a name, value and priority are specified, and handled as below.
+  return d3_transition_tween(this, "style." + name, value, function(b) {
+
+    // For style(name, string) or style(name, string, priority), set the style
+    // property with the specified name, using the specified priority.
+    function styleString() {
+      var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i;
+      return a !== b && (i = interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); });
+    }
+
+    return b == null ? styleNull
+        : (b += "", styleString);
+  });
+};
+
+d3_transitionPrototype.styleTween = function(name, tween, priority) {
+  if (arguments.length < 3) priority = "";
+  return this.tween("style." + name, function(d, i) {
+    var f = tween.call(this, d, i, d3_window.getComputedStyle(this, null).getPropertyValue(name));
+    return f && function(t) { this.style.setProperty(name, f(t), priority); };
+  });
+};
+
+d3_transitionPrototype.text = function(value) {
+  return d3_transition_tween(this, "text", value, d3_transition_text);
+};
+
+function d3_transition_text(b) {
+  if (b == null) b = "";
+  return function() { this.textContent = b; };
+}
+
+d3_transitionPrototype.remove = function() {
+  return this.each("end.transition", function() {
+    var p;
+    if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this);
+  });
+};
+
+d3_transitionPrototype.ease = function(value) {
+  var id = this.id;
+  if (arguments.length < 1) return this.node().__transition__[id].ease;
+  if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
+  return d3_selection_each(this, function(node) { node.__transition__[id].ease = value; });
+};
+
+d3_transitionPrototype.delay = function(value) {
+  var id = this.id;
+  return d3_selection_each(this, typeof value === "function"
+      ? function(node, i, j) { node.__transition__[id].delay = value.call(node, node.__data__, i, j) | 0; }
+      : (value |= 0, function(node) { node.__transition__[id].delay = value; }));
+};
+
+d3_transitionPrototype.duration = function(value) {
+  var id = this.id;
+  return d3_selection_each(this, typeof value === "function"
+      ? function(node, i, j) { node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j) | 0); }
+      : (value = Math.max(1, value | 0), function(node) { node.__transition__[id].duration = value; }));
+};
+
+d3_transitionPrototype.each = function(type, listener) {
+  var id = this.id;
+  if (arguments.length < 2) {
+    var inherit = d3_transitionInherit,
+        inheritId = d3_transitionInheritId;
+    d3_transitionInheritId = id;
+    d3_selection_each(this, function(node, i, j) {
+      d3_transitionInherit = node.__transition__[id];
+      type.call(node, node.__data__, i, j);
+    });
+    d3_transitionInherit = inherit;
+    d3_transitionInheritId = inheritId;
+  } else {
+    d3_selection_each(this, function(node) {
+      node.__transition__[id].event.on(type, listener);
+    });
+  }
+  return this;
+};
+
+d3_transitionPrototype.transition = function() {
+  var id0 = this.id,
+      id1 = ++d3_transitionId,
+      subgroups = [],
+      subgroup,
+      group,
+      node,
+      transition;
+
+  for (var j = 0, m = this.length; j < m; j++) {
+    subgroups.push(subgroup = []);
+    for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+      if (node = group[i]) {
+        transition = Object.create(node.__transition__[id0]);
+        transition.delay += transition.duration;
+        d3_transitionNode(node, i, id1, transition);
+      }
+      subgroup.push(node);
+    }
+  }
+
+  return d3_transition(subgroups, id1);
+};
+
+function d3_transitionNode(node, i, id, inherit) {
+  var lock = node.__transition__ || (node.__transition__ = {active: 0, count: 0}),
+      transition = lock[id];
+
+  if (!transition) {
+    var time = inherit.time;
+
+    transition = lock[id] = {
+      tween: new d3_Map,
+      event: d3.dispatch("start", "end"), // TODO construct lazily?
+      time: time,
+      ease: inherit.ease,
+      delay: inherit.delay,
+      duration: inherit.duration
+    };
+
+    ++lock.count;
+
+    d3.timer(function(elapsed) {
+      var d = node.__data__,
+          ease = transition.ease,
+          event = transition.event,
+          delay = transition.delay,
+          duration = transition.duration,
+          tweened = [];
+
+      return delay <= elapsed
+          ? start(elapsed)
+          : d3.timer(start, delay, time), 1;
+
+      function start(elapsed) {
+        if (lock.active > id) return stop();
+        lock.active = id;
+        event.start.call(node, d, i);
+
+        transition.tween.forEach(function(key, value) {
+          if (value = value.call(node, d, i)) {
+            tweened.push(value);
+          }
+        });
+
+        if (!tick(elapsed)) d3.timer(tick, 0, time);
+        return 1;
+      }
+
+      function tick(elapsed) {
+        if (lock.active !== id) return stop();
+
+        var t = (elapsed - delay) / duration,
+            e = ease(t),
+            n = tweened.length;
+
+        while (n > 0) {
+          tweened[--n].call(node, e);
+        }
+
+        if (t >= 1) {
+          stop();
+          event.end.call(node, d, i);
+          return 1;
+        }
+      }
+
+      function stop() {
+        if (--lock.count) delete lock[id];
+        else delete node.__transition__;
+        return 1;
+      }
+    }, 0, time);
+
+    return transition;
+  }
+}
+
+d3.xhr = function(url, mimeType, callback) {
+  var xhr = {},
+      dispatch = d3.dispatch("progress", "load", "error"),
+      headers = {},
+      response = d3_identity,
+      request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest);
+
+  "onload" in request
+      ? request.onload = request.onerror = respond
+      : request.onreadystatechange = function() { request.readyState > 3 && respond(); };
+
+  function respond() {
+    var s = request.status;
+    !s && request.responseText || s >= 200 && s < 300 || s === 304
+        ? dispatch.load.call(xhr, response.call(xhr, request))
+        : dispatch.error.call(xhr, request);
+  }
+
+  request.onprogress = function(event) {
+    var o = d3.event;
+    d3.event = event;
+    try { dispatch.progress.call(xhr, request); }
+    finally { d3.event = o; }
+  };
+
+  xhr.header = function(name, value) {
+    name = (name + "").toLowerCase();
+    if (arguments.length < 2) return headers[name];
+    if (value == null) delete headers[name];
+    else headers[name] = value + "";
+    return xhr;
+  };
+
+  // If mimeType is non-null and no Accept header is set, a default is used.
+  xhr.mimeType = function(value) {
+    if (!arguments.length) return mimeType;
+    mimeType = value == null ? null : value + "";
+    return xhr;
+  };
+
+  // Specify how to convert the response content to a specific type;
+  // changes the callback value on "load" events.
+  xhr.response = function(value) {
+    response = value;
+    return xhr;
+  };
+
+  // Convenience methods.
+  ["get", "post"].forEach(function(method) {
+    xhr[method] = function() {
+      return xhr.send.apply(xhr, [method].concat(d3_array(arguments)));
+    };
+  });
+
+  // If callback is non-null, it will be used for error and load events.
+  xhr.send = function(method, data, callback) {
+    if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
+    request.open(method, url, true);
+    if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
+    if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
+    if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
+    if (callback != null) xhr.on("error", callback).on("load", function(request) { callback(null, request); });
+    request.send(data == null ? null : data);
+    return xhr;
+  };
+
+  xhr.abort = function() {
+    request.abort();
+    return xhr;
+  };
+
+  d3.rebind(xhr, dispatch, "on");
+
+  if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null;
+  return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
+};
+
+function d3_xhr_fixCallback(callback) {
+  return callback.length === 1
+      ? function(error, request) { callback(error == null ? request : null); }
+      : callback;
+}
+
+d3.text = function() {
+  return d3.xhr.apply(d3, arguments).response(d3_text);
+};
+
+function d3_text(request) {
+  return request.responseText;
+}
+
+d3.json = function(url, callback) {
+  return d3.xhr(url, "application/json", callback).response(d3_json);
+};
+
+function d3_json(request) {
+  return JSON.parse(request.responseText);
+}
+
+d3.html = function(url, callback) {
+  return d3.xhr(url, "text/html", callback).response(d3_html);
+};
+
+function d3_html(request) {
+  var range = d3_document.createRange();
+  range.selectNode(d3_document.body);
+  return range.createContextualFragment(request.responseText);
+}
+
+d3.xml = function() {
+  return d3.xhr.apply(d3, arguments).response(d3_xml);
+};
+
+function d3_xml(request) {
+  return request.responseXML;
+}
+  return d3;
+})();
+d3.combobox = function() {
+    var event = d3.dispatch('accept'),
+        id = d3.combobox.id ++,
+        data = [];
+
+    var fetcher = function(val, data, cb) {
+        cb(data.filter(function(d) {
+            return d.title
+                .toString()
+                .toLowerCase()
+                .indexOf(val.toLowerCase()) !== -1;
+        }));
+    };
+
+    var combobox = function(input) {
+        var idx = -1, container, shown = false;
+
+        input
+            .classed('combobox-input', true)
+            .each(function() {
+                var parent = this.parentNode,
+                    sibling = this.nextSibling;
+                d3.select(parent)
+                    .insert('div', function() { return sibling; })
+                    .attr('class', 'combobox-carat')
+                    .on('mousedown', function () {
+                        // prevent the form element from blurring. it blurs
+                        // on mousedown
+                        d3.event.stopPropagation();
+                        d3.event.preventDefault();
+                        mousedown();
+                    });
+            });
+
+        function updateSize() {
+            var rect = input.node().getBoundingClientRect();
+            container.style({
+                'left': rect.left + 'px',
+                'width': rect.width + 'px',
+                'top': rect.height + rect.top + 'px'
+            });
+        }
+
+        function blur() {
+            // hide the combobox whenever the input element
+            // loses focus
+            slowHide();
+        }
+
+        function show() {
+            if (!shown) {
+                container = d3.select(document.body)
+                    .insert('div', ':first-child')
+                    .attr('class', 'combobox')
+                    .style({
+                        position: 'absolute',
+                        display: 'block',
+                        left: '0px'
+                    });
+
+                shown = true;
+            }
+        }
+
+        function hide() {
+            if (shown) {
+                idx = -1;
+                container.remove();
+                shown = false;
+            }
+        }
+
+        function slowHide() {
+            window.setTimeout(hide, 150);
+        }
+        function keydown() {
+           if (!shown) return;
+           switch (d3.event.keyCode) {
+               // down arrow
+               case 40:
+                   next();
+                   d3.event.preventDefault();
+                   break;
+               // up arrow
+               case 38:
+                   prev();
+                   d3.event.preventDefault();
+                   break;
+               // escape, tab
+               case 13:
+                   d3.event.preventDefault();
+                   break;
+           }
+           d3.event.stopPropagation();
+        }
+
+        function keyup() {
+            switch (d3.event.keyCode) {
+                // escape
+                case 27:
+                    hide();
+                    break;
+                // escape, tab
+                case 9:
+                case 13:
+                    if (!shown) return;
+                    accept();
+                    break;
+                default:
+                    update();
+                    d3.event.preventDefault();
+            }
+            d3.event.stopPropagation();
+        }
+
+        function accept() {
+            if (container.select('a.selected').node()) {
+                select(container.select('a.selected').datum());
+            }
+            hide();
+        }
+
+        function next() {
+            var len = container.selectAll('a').data().length;
+            idx = Math.min(idx + 1, len - 1);
+            highlight();
+        }
+
+        function prev() {
+            idx = Math.max(idx - 1, 0);
+            highlight();
+        }
+
+        var prevValue, prevCompletion;
+
+        function autocomplete(e, data) {
+
+            var value = input.property('value'),
+                match;
+
+            for (var i = 0; i < data.length; i++) {
+                if (data[i].value.toLowerCase().indexOf(value.toLowerCase()) === 0) {
+                    match = data[i].value;
+                    break;
+                }
+            }
+
+            // backspace
+            if (e.keyCode === 8) {
+                prevValue = value;
+                prevCompletion = '';
+
+            } else if (value && match && value !== prevValue + prevCompletion) {
+                prevValue = value;
+                prevCompletion = match.substr(value.length);
+                input.property('value', prevValue + prevCompletion);
+                input.node().setSelectionRange(value.length, value.length + prevCompletion.length);
+            }
+        }
+
+
+        function highlight() {
+            container
+                .selectAll('a')
+                .classed('selected', function(d, i) { return i == idx; });
+            var height = container.node().offsetHeight,
+                top = container.select('a.selected').node().offsetTop,
+                selectedHeight = container.select('a.selected').node().offsetHeight;
+            if ((top + selectedHeight) < height) {
+                container.node().scrollTop = 0;
+            } else {
+                container.node().scrollTop = top;
+            }
+        }
+
+        function update(value) {
+
+            if (typeof value === 'undefined') {
+                value = input.property('value');
+            }
+
+            var e = d3.event;
+
+            function render(data) {
+
+                if (data.length &&
+                    document.activeElement === input.node()) show();
+                else return hide();
+
+                autocomplete(e, data);
+
+                updateSize();
+
+                var options = container
+                    .selectAll('a.combobox-option')
+                    .data(data, function(d) { return d.value; });
+
+                options.enter()
+                    .append('a')
+                    .text(function(d) { return d.value; })
+                    .attr('class', 'combobox-option')
+                    .attr('title', function(d) { return d.title; })
+                    .on('click', select);
+
+                options.exit().remove();
+
+                options
+                    .classed('selected', function(d, i) { return i == idx; })
+                    .order();
+            }
+
+            fetcher.apply(input, [value, data, render]);
+        }
+
+        // select the choice given as d
+        function select(d) {
+            input
+                .property('value', d.value)
+                .trigger('change');
+            event.accept(d);
+            hide();
+        }
+
+        function mousedown() {
+
+            if (shown) return hide();
+
+            input.node().focus();
+            update('');
+
+            if (!container) return;
+
+            var entries = container.selectAll('a'),
+                height = container.node().scrollHeight / entries[0].length,
+                w = d3.select(window);
+
+            function getIndex(m) {
+                return Math.floor((m[1] + container.node().scrollTop) / height);
+            }
+
+            function withinBounds(m) {
+                var n = container.node();
+                return m[0] >= 0 && m[0] < n.offsetWidth &&
+                    m[1] >= 0 && m[1] < n.offsetHeight;
+            }
+
+            w.on('mousemove.typeahead', function() {
+                var m = d3.mouse(container.node());
+                var within = withinBounds(m);
+                var n = getIndex(m);
+                entries.classed('selected', function(d, i) { return within && i === n; });
+            });
+
+            w.on('mouseup.typeahead', function() {
+                var m = d3.mouse(container.node());
+                if (withinBounds(m)) select(d3.select(entries[0][getIndex(m)]).datum());
+                entries.classed('selected', false);
+                w.on('mouseup.typeahead', null);
+                w.on('mousemove.typeahead', null);
+            });
+        }
+
+        input
+            .on('blur.typeahead', blur)
+            .on('keydown.typeahead', keydown)
+            .on('keyup.typeahead', keyup)
+            .on('mousedown.typeahead', mousedown);
+
+        d3.select(document.body).on('scroll.combo' + id, function() {
+            if (shown) updateSize();
+        }, true);
+    };
+
+    combobox.fetcher = function(_) {
+        if (!arguments.length) return fetcher;
+        fetcher = _;
+        return combobox;
+    };
+
+    combobox.data = function(_) {
+        if (!arguments.length) return data;
+        data = _;
+        return combobox;
+    };
+
+    return d3.rebind(combobox, event, 'on');
+};
+
+d3.combobox.id = 0;
+d3.geo.tile = function() {
+  var size = [960, 500],
+      scale = 256,
+      scaleExtent = [0, 20],
+      translate = [size[0] / 2, size[1] / 2],
+      zoomDelta = 0;
+
+  function bound(_) {
+      return Math.min(scaleExtent[1], Math.max(scaleExtent[0], _));
+  }
+
+  function tile() {
+    var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0),
+        z0 = bound(Math.round(z + zoomDelta)),
+        k = Math.pow(2, z - z0 + 8),
+        origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k],
+        tiles = [],
+        cols = d3.range(Math.max(0, Math.floor(-origin[0])), Math.max(0, Math.ceil(size[0] / k - origin[0]))),
+        rows = d3.range(Math.max(0, Math.floor(-origin[1])), Math.max(0, Math.ceil(size[1] / k - origin[1])));
+
+    rows.forEach(function(y) {
+      cols.forEach(function(x) {
+        tiles.push([x, y, z0]);
+      });
+    });
+
+    tiles.translate = origin;
+    tiles.scale = k;
+
+    return tiles;
+  }
+
+  tile.scaleExtent = function(_) {
+    if (!arguments.length) return scaleExtent;
+    scaleExtent = _;
+    return tile;
+  };
+
+  tile.size = function(_) {
+    if (!arguments.length) return size;
+    size = _;
+    return tile;
+  };
+
+  tile.scale = function(_) {
+    if (!arguments.length) return scale;
+    scale = _;
+    return tile;
+  };
+
+  tile.translate = function(_) {
+    if (!arguments.length) return translate;
+    translate = _;
+    return tile;
+  };
+
+  tile.zoomDelta = function(_) {
+    if (!arguments.length) return zoomDelta;
+    zoomDelta = +_;
+    return tile;
+  };
+
+  return tile;
+};
+d3.jsonp = function (url, callback) {
+  function rand() {
+    var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
+      c = '', i = -1;
+    while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52));
+    return c;
+  }
+
+  function create(url) {
+    var e = url.match(/callback=d3.jsonp.(\w+)/),
+      c = e ? e[1] : rand();
+    d3.jsonp[c] = function(data) {
+      callback(data);
+      delete d3.jsonp[c];
+      script.remove();
+    };
+    return 'd3.jsonp.' + c;
+  }
+
+  var cb = create(url),
+    script = d3.select('head')
+    .append('script')
+    .attr('type', 'text/javascript')
+    .attr('src', url.replace(/(\{|%7B)callback(\}|%7D)/, cb));
+};
+/*
+ * This code is licensed under the MIT license.
+ *
+ * Copyright © 2013, iD authors.
+ *
+ * Portions copyright © 2011, Keith Cirkel
+ * See https://github.com/keithamus/jwerty
+ *
+ */
+d3.keybinding = function(namespace) {
+    var bindings = [];
+
+    function matches(binding, event) {
+        for (var p in binding.event) {
+            if (event[p] != binding.event[p])
+                return false;
+        }
+
+        return (!binding.capture) === (event.eventPhase !== Event.CAPTURING_PHASE);
+    }
+
+    function capture() {
+        for (var i = 0; i < bindings.length; i++) {
+            var binding = bindings[i];
+            if (matches(binding, d3.event)) {
+                binding.callback();
+            }
+        }
+    }
+
+    function bubble() {
+        var tagName = d3.select(d3.event.target).node().tagName;
+        if (tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA') {
+            return;
+        }
+        capture();
+    }
+
+    function keybinding(selection) {
+        selection = selection || d3.select(document);
+        selection.on('keydown.capture' + namespace, capture, true);
+        selection.on('keydown.bubble' + namespace, bubble, false);
+        return keybinding;
+    }
+
+    keybinding.off = function(selection) {
+        selection = selection || d3.select(document);
+        selection.on('keydown.capture' + namespace, null);
+        selection.on('keydown.bubble' + namespace, null);
+        return keybinding;
+    };
+
+    keybinding.on = function(code, callback, capture) {
+        var binding = {
+            event: {
+                keyCode: 0,
+                shiftKey: false,
+                ctrlKey: false,
+                altKey: false,
+                metaKey: false
+            },
+            capture: capture,
+            callback: callback
+        };
+
+        code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);
+
+        for (var i = 0; i < code.length; i++) {
+            // Normalise matching errors
+            if (code[i] === '++') code[i] = '+';
+
+            if (code[i] in d3.keybinding.modifierCodes) {
+                binding.event[d3.keybinding.modifierProperties[d3.keybinding.modifierCodes[code[i]]]] = true;
+            } else if (code[i] in d3.keybinding.keyCodes) {
+                binding.event.keyCode = d3.keybinding.keyCodes[code[i]];
+            }
+        }
+
+        bindings.push(binding);
+
+        return keybinding;
+    };
+
+    return keybinding;
+};
+
+(function () {
+    d3.keybinding.modifierCodes = {
+        // Shift key, ⇧
+        '⇧': 16, shift: 16,
+        // CTRL key, on Mac: ⌃
+        '⌃': 17, ctrl: 17,
+        // ALT key, on Mac: ⌥ (Alt)
+        '⌥': 18, alt: 18, option: 18,
+        // META, on Mac: ⌘ (CMD), on Windows (Win), on Linux (Super)
+        '⌘': 91, meta: 91, cmd: 91, 'super': 91, win: 91
+    };
+
+    d3.keybinding.modifierProperties = {
+        16: 'shiftKey',
+        17: 'ctrlKey',
+        18: 'altKey',
+        91: 'metaKey'
+    };
+
+    d3.keybinding.keyCodes = {
+        // Backspace key, on Mac: ⌫ (Backspace)
+        '⌫': 8, backspace: 8,
+        // Tab Key, on Mac: ⇥ (Tab), on Windows ⇥⇥
+        '⇥': 9, '⇆': 9, tab: 9,
+        // Return key, ↩
+        '↩': 13, 'return': 13, enter: 13, '⌅': 13,
+        // Pause/Break key
+        'pause': 19, 'pause-break': 19,
+        // Caps Lock key, ⇪
+        '⇪': 20, caps: 20, 'caps-lock': 20,
+        // Escape key, on Mac: ⎋, on Windows: Esc
+        '⎋': 27, escape: 27, esc: 27,
+        // Space key
+        space: 32,
+        // Page-Up key, or pgup, on Mac: ↖
+        '↖': 33, pgup: 33, 'page-up': 33,
+        // Page-Down key, or pgdown, on Mac: ↘
+        '↘': 34, pgdown: 34, 'page-down': 34,
+        // END key, on Mac: ⇟
+        '⇟': 35, end: 35,
+        // HOME key, on Mac: ⇞
+        '⇞': 36, home: 36,
+        // Insert key, or ins
+        ins: 45, insert: 45,
+        // Delete key, on Mac: ⌦ (Delete)
+        '⌦': 46, del: 46, 'delete': 46,
+        // Left Arrow Key, or ←
+        '←': 37, left: 37, 'arrow-left': 37,
+        // Up Arrow Key, or ↑
+        '↑': 38, up: 38, 'arrow-up': 38,
+        // Right Arrow Key, or →
+        '→': 39, right: 39, 'arrow-right': 39,
+        // Up Arrow Key, or ↓
+        '↓': 40, down: 40, 'arrow-down': 40,
+        // odities, printing characters that come out wrong:
+        // Num-Multiply, or *
+        '*': 106, star: 106, asterisk: 106, multiply: 106,
+        // Num-Plus or +
+        '+': 107, 'plus': 107,
+        // Num-Subtract, or -
+        '-': 109, subtract: 109,
+        // Semicolon
+        ';': 186, semicolon:186,
+        // = or equals
+        '=': 187, 'equals': 187,
+        // Comma, or ,
+        ',': 188, comma: 188,
+        'dash': 189, //???
+        // Period, or ., or full-stop
+        '.': 190, period: 190, 'full-stop': 190,
+        // Slash, or /, or forward-slash
+        '/': 191, slash: 191, 'forward-slash': 191,
+        // Tick, or `, or back-quote
+        '`': 192, tick: 192, 'back-quote': 192,
+        // Open bracket, or [
+        '[': 219, 'open-bracket': 219,
+        // Back slash, or \
+        '\\': 220, 'back-slash': 220,
+        // Close backet, or ]
+        ']': 221, 'close-bracket': 221,
+        // Apostrophe, or Quote, or '
+        '\'': 222, quote: 222, apostrophe: 222
+    };
+
+    // NUMPAD 0-9
+    var i = 95, n = 0;
+    while (++i < 106) {
+        d3.keybinding.keyCodes['num-' + n] = i;
+        ++n;
+    }
+
+    // 0-9
+    i = 47; n = 0;
+    while (++i < 58) {
+        d3.keybinding.keyCodes[n] = i;
+        ++n;
+    }
+
+    // F1-F25
+    i = 111; n = 1;
+    while (++i < 136) {
+        d3.keybinding.keyCodes['f' + n] = i;
+        ++n;
+    }
+
+    // a-z
+    i = 64;
+    while (++i < 91) {
+        d3.keybinding.keyCodes[String.fromCharCode(i).toLowerCase()] = i;
+    }
+})();
+d3.selection.prototype.one = function (type, listener, capture) {
+    var target = this, typeOnce = type + ".once";
+    function one() {
+        target.on(typeOnce, null);
+        listener.apply(this, arguments);
+    }
+    target.on(typeOnce, one, capture);
+    return this;
+};
+d3.selection.prototype.size = function (size) {
+    if (!arguments.length) {
+        var node = this.node();
+        return [node.offsetWidth,
+                node.offsetHeight];
+    }
+    return this.attr({width: size[0], height: size[1]});
+};
+d3.selection.prototype.trigger = function (type) {
+    this.each(function() {
+        var evt = document.createEvent('HTMLEvents');
+        evt.initEvent(type, true, true);
+        this.dispatchEvent(evt);
+    });
+};
+d3.typeahead = function() {
+    var event = d3.dispatch('accept'),
+        autohighlight = false,
+        data;
+
+    var typeahead = function(selection) {
+        var container,
+            hidden,
+            idx = autohighlight ? 0 : -1;
+
+        function setup() {
+            var rect = selection.node().getBoundingClientRect();
+            container = d3.select(document.body)
+                .append('div').attr('class', 'typeahead')
+                .style({
+                    position: 'absolute',
+                    left: rect.left + 'px',
+                    top: rect.bottom + 'px'
+                });
+            selection
+                .on('keyup.typeahead', key);
+            hidden = false;
+        }
+
+        function hide() {
+            container.remove();
+            idx = autohighlight ? 0 : -1;
+            hidden = true;
+        }
+
+        function slowHide() {
+            if (autohighlight) {
+                if (container.select('a.selected').node()) {
+                    select(container.select('a.selected').datum());
+                    event.accept();
+                }
+            }
+            window.setTimeout(hide, 150);
+        }
+
+        selection
+            .on('focus.typeahead', setup)
+            .on('blur.typeahead', slowHide);
+
+        function key() {
+           var len = container.selectAll('a').data().length;
+           if (d3.event.keyCode === 40) {
+               idx = Math.min(idx + 1, len - 1);
+               return highlight();
+           } else if (d3.event.keyCode === 38) {
+               idx = Math.max(idx - 1, 0);
+               return highlight();
+           } else if (d3.event.keyCode === 13) {
+               if (container.select('a.selected').node()) {
+                   select(container.select('a.selected').datum());
+               }
+               event.accept();
+               hide();
+           } else {
+               update();
+           }
+        }
+
+        function highlight() {
+            container
+                .selectAll('a')
+                .classed('selected', function(d, i) { return i == idx; });
+        }
+
+        function update() {
+            if (hidden) setup();
+
+            data(selection, function(data) {
+                container.style('display', function() {
+                    return data.length ? 'block' : 'none';
+                });
+
+                var options = container
+                    .selectAll('a')
+                    .data(data, function(d) { return d.value; });
+
+                options.enter()
+                    .append('a')
+                    .text(function(d) { return d.value; })
+                    .attr('title', function(d) { return d.title; })
+                    .on('click', select);
+
+                options.exit().remove();
+
+                options
+                    .classed('selected', function(d, i) { return i == idx; });
+            });
+        }
+
+        function select(d) {
+            selection
+                .property('value', d.value)
+                .trigger('change');
+        }
+
+    };
+
+    typeahead.data = function(_) {
+        if (!arguments.length) return data;
+        data = _;
+        return typeahead;
+    };
+
+    typeahead.autohighlight = function(_) {
+        if (!arguments.length) return autohighlight;
+        autohighlight = _;
+        return typeahead;
+    };
+
+    return d3.rebind(typeahead, event, 'on');
+};
+// Tooltips and svg mask used to highlight certain features
+d3.curtain = function() {
+
+    var event = d3.dispatch(),
+        surface,
+        tooltip,
+        darkness;
+
+    function curtain(selection) {
+
+        surface = selection.append('svg')
+            .attr('id', 'curtain')
+            .style({
+                'z-index': 1000,
+                'pointer-events': 'none',
+                'position': 'absolute',
+                'top': 0,
+                'left': 0
+            });
+
+        darkness = surface.append('path')
+            .attr({
+                x: 0,
+                y: 0,
+                'class': 'curtain-darkness'
+            });
+
+        d3.select(window).on('resize.curtain', resize);
+
+        tooltip = selection.append('div')
+            .attr('class', 'tooltip')
+            .style('z-index', 1002);
+
+        tooltip.append('div').attr('class', 'tooltip-arrow');
+        tooltip.append('div').attr('class', 'tooltip-inner');
+
+        resize();
+
+        function resize() {
+            surface.attr({
+                width: window.innerWidth,
+                height: window.innerHeight
+            });
+            curtain.cut(darkness.datum());
+        }
+    }
+
+    curtain.reveal = function(box, text, tooltipclass, duration) {
+        if (typeof box === 'string') box = d3.select(box).node();
+        if (box.getBoundingClientRect) box = box.getBoundingClientRect();
+
+        curtain.cut(box, duration);
+
+        if (text) {
+            // pseudo markdown bold text hack
+            var parts = text.split('**');
+            var html = parts[0] ? '<span>' + parts[0] + '</span>' : '';
+            if (parts[1]) html += '<span class="bold">' + parts[1] + '</span>';
+
+            var size = tooltip.classed('in', true)
+                .select('.tooltip-inner')
+                    .html(html)
+                    .size();
+
+            var pos;
+
+            var w = window.innerWidth,
+                h = window.innerHeight;
+
+            if (box.top + box.height < Math.min(100, box.width + box.left)) {
+                side = 'bottom';
+                pos = [box.left + box.width / 2 - size[0]/ 2, box.top + box.height];
+
+            } else if (box.left + box.width + 300 < window.innerWidth) {
+                side = 'right';
+                pos = [box.left + box.width, box.top + box.height / 2 - size[1] / 2];
+
+            } else if (box.left > 300) {
+                side = 'left';
+                pos = [box.left - 200, box.top + box.height / 2 - size[1] / 2];
+            } else {
+                side = 'bottom';
+                pos = [box.left, box.top + box.height];
+            }
+
+            pos = [
+                Math.min(Math.max(10, pos[0]), w - size[0] - 10),
+                Math.min(Math.max(10, pos[1]), h - size[1] - 10)
+            ];
+
+
+            if (duration !== 0 || !tooltip.classed(side)) tooltip.call(iD.ui.Toggle(true));
+
+            tooltip
+                .style('top', pos[1] + 'px')
+                .style('left', pos[0] + 'px')
+                .attr('class', 'curtain-tooltip tooltip in ' + side + ' ' + tooltipclass)
+                .select('.tooltip-inner')
+                    .html(html);
+
+        } else {
+            tooltip.call(iD.ui.Toggle(false));
+        }
+    };
+
+    curtain.cut = function(datum, duration) {
+        darkness.datum(datum);
+
+        (duration === 0 ? darkness : darkness.transition().duration(duration || 600))
+            .attr('d', function(d) {
+                var string = "M 0,0 L 0," + window.innerHeight + " L " +
+                    window.innerWidth + "," + window.innerHeight + "L" +
+                    window.innerWidth + ",0 Z";
+
+                if (!d) return string;
+                return string + 'M' +
+                    d.left + ',' + d.top + 'L' +
+                    d.left + ',' + (d.top + d.height) + 'L' +
+                    (d.left + d.width) + ',' + (d.top + d.height) + 'L' +
+                    (d.left + d.width) + ',' + (d.top) + 'Z';
+
+            });
+    };
+
+    curtain.remove = function() {
+        surface.remove();
+        tooltip.remove();
+    };
+
+    return d3.rebind(curtain, event, 'on');
+};
+var JXON = new (function () {
+  var
+    sValueProp = "keyValue", sAttributesProp = "keyAttributes", sAttrPref = "@", /* you can customize these values */
+    aCache = [], rIsNull = /^\s*$/, rIsBool = /^(?:true|false)$/i;
+
+  function parseText (sValue) {
+    if (rIsNull.test(sValue)) { return null; }
+    if (rIsBool.test(sValue)) { return sValue.toLowerCase() === "true"; }
+    if (isFinite(sValue)) { return parseFloat(sValue); }
+    if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
+    return sValue;
+  }
+
+  function EmptyTree () { }
+  EmptyTree.prototype.toString = function () { return "null"; };
+  EmptyTree.prototype.valueOf = function () { return null; };
+
+  function objectify (vValue) {
+    return vValue === null ? new EmptyTree() : vValue instanceof Object ? vValue : new vValue.constructor(vValue);
+  }
+
+  function createObjTree (oParentNode, nVerb, bFreeze, bNesteAttr) {
+    var
+      nLevelStart = aCache.length, bChildren = oParentNode.hasChildNodes(),
+      bAttributes = oParentNode.hasAttributes(), bHighVerb = Boolean(nVerb & 2);
+
+    var
+      sProp, vContent, nLength = 0, sCollectedTxt = "",
+      vResult = bHighVerb ? {} : /* put here the default value for empty nodes: */ true;
+
+    if (bChildren) {
+      for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) {
+        oNode = oParentNode.childNodes.item(nItem);
+        if (oNode.nodeType === 4) { sCollectedTxt += oNode.nodeValue; } /* nodeType is "CDATASection" (4) */
+        else if (oNode.nodeType === 3) { sCollectedTxt += oNode.nodeValue.trim(); } /* nodeType is "Text" (3) */
+        else if (oNode.nodeType === 1 && !oNode.prefix) { aCache.push(oNode); } /* nodeType is "Element" (1) */
+      }
+    }
+
+    var nLevelEnd = aCache.length, vBuiltVal = parseText(sCollectedTxt);
+
+    if (!bHighVerb && (bChildren || bAttributes)) { vResult = nVerb === 0 ? objectify(vBuiltVal) : {}; }
+
+    for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
+      sProp = aCache[nElId].nodeName.toLowerCase();
+      vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
+      if (vResult.hasOwnProperty(sProp)) {
+        if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; }
+        vResult[sProp].push(vContent);
+      } else {
+        vResult[sProp] = vContent;
+        nLength++;
+      }
+    }
+
+    if (bAttributes) {
+      var
+        nAttrLen = oParentNode.attributes.length,
+        sAPrefix = bNesteAttr ? "" : sAttrPref, oAttrParent = bNesteAttr ? {} : vResult;
+
+      for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
+        oAttrib = oParentNode.attributes.item(nAttrib);
+        oAttrParent[sAPrefix + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
+      }
+
+      if (bNesteAttr) {
+        if (bFreeze) { Object.freeze(oAttrParent); }
+        vResult[sAttributesProp] = oAttrParent;
+        nLength -= nAttrLen - 1;
+      }
+    }
+
+    if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) {
+      vResult[sValueProp] = vBuiltVal;
+    } else if (!bHighVerb && nLength === 0 && sCollectedTxt) {
+      vResult = vBuiltVal;
+    }
+
+    if (bFreeze && (bHighVerb || nLength > 0)) { Object.freeze(vResult); }
+
+    aCache.length = nLevelStart;
+
+    return vResult;
+  }
+
+  function loadObjTree (oXMLDoc, oParentEl, oParentObj) {
+    var vValue, oChild;
+
+    if (oParentObj instanceof String || oParentObj instanceof Number || oParentObj instanceof Boolean) {
+      oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 */
+    } else if (oParentObj.constructor === Date) {
+      oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString()));    
+    }
+
+    for (var sName in oParentObj) {
+      vValue = oParentObj[sName];
+      if (isFinite(sName) || vValue instanceof Function) { continue; } /* verbosity level is 0 */
+      if (sName === sValueProp) {
+        if (vValue !== null && vValue !== true) { oParentEl.appendChild(oXMLDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue))); }
+      } else if (sName === sAttributesProp) { /* verbosity level is 3 */
+        for (var sAttrib in vValue) { oParentEl.setAttribute(sAttrib, vValue[sAttrib]); }
+      } else if (sName.charAt(0) === sAttrPref) {
+        oParentEl.setAttribute(sName.slice(1), vValue);
+      } else if (vValue.constructor === Array) {
+        for (var nItem = 0; nItem < vValue.length; nItem++) {
+          oChild = oXMLDoc.createElement(sName);
+          loadObjTree(oXMLDoc, oChild, vValue[nItem]);
+          oParentEl.appendChild(oChild);
+        }
+      } else {
+        oChild = oXMLDoc.createElement(sName);
+        if (vValue instanceof Object) {
+          loadObjTree(oXMLDoc, oChild, vValue);
+        } else if (vValue !== null && vValue !== true) {
+          oChild.appendChild(oXMLDoc.createTextNode(vValue.toString()));
+        }
+        oParentEl.appendChild(oChild);
+     }
+   }
+  }
+
+  this.build = function (oXMLParent, nVerbosity /* optional */, bFreeze /* optional */, bNesteAttributes /* optional */) {
+    var _nVerb = arguments.length > 1 && typeof nVerbosity === "number" ? nVerbosity & 3 : /* put here the default verbosity level: */ 1;
+    return createObjTree(oXMLParent, _nVerb, bFreeze || false, arguments.length > 3 ? bNesteAttributes : _nVerb === 3);    
+  };
+
+  this.unbuild = function (oObjTree) {    
+    var oNewDoc = document.implementation.createDocument("", "", null);
+    loadObjTree(oNewDoc, oNewDoc, oObjTree);
+    return oNewDoc;
+  };
+
+  this.stringify = function (oObjTree) {
+    return (new XMLSerializer()).serializeToString(JXON.unbuild(oObjTree));
+  };
+})();
+// var myObject = JXON.build(doc);
+// we got our javascript object! try: alert(JSON.stringify(myObject));
+
+// var newDoc = JXON.unbuild(myObject);
+// we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc));
+/*!
+ * Lo-Dash 1.0.0-rc.3 <http://lodash.com>
+ * (c) 2012 John-David Dalton <http://allyoucanleet.com/>
+ * Based on Underscore.js 1.4.3 <http://underscorejs.org>
+ * (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
+ * Available under MIT license <http://lodash.com/license>
+ */
+;(function(window, undefined) {
+
+  /** Detect free variable `exports` */
+  var freeExports = typeof exports == 'object' && exports;
+
+  /** Detect free variable `global` and use it as `window` */
+  var freeGlobal = typeof global == 'object' && global;
+  if (freeGlobal.global === freeGlobal) {
+    window = freeGlobal;
+  }
+
+  /** Used for array and object method references */
+  var arrayRef = [],
+      // avoid a Closure Compiler bug by creatively creating an object
+      objectRef = new function(){};
+
+  /** Used to generate unique IDs */
+  var idCounter = 0;
+
+  /** Used internally to indicate various things */
+  var indicatorObject = objectRef;
+
+  /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */
+  var largeArraySize = 30;
+
+  /** Used to restore the original `_` reference in `noConflict` */
+  var oldDash = window._;
+
+  /** Used to detect template delimiter values that require a with-statement */
+  var reComplexDelimiter = /[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/;
+
+  /** Used to match HTML entities */
+  var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g;
+
+  /** Used to match empty string literals in compiled template source */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /** Used to match regexp flags from their coerced string values */
+  var reFlags = /\w*$/;
+
+  /** Used to insert the data object variable into compiled template source */
+  var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g;
+
+  /** Used to detect if a method is native */
+  var reNative = RegExp('^' +
+    (objectRef.valueOf + '')
+      .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
+      .replace(/valueOf|for [^\]]+/g, '.+?') + '$'
+  );
+
+  /**
+   * Used to match ES6 template delimiters
+   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6
+   */
+  var reEsTemplate = /\$\{((?:(?=\\?)\\?[\s\S])*?)}/g;
+
+  /** Used to match "interpolate" template delimiters */
+  var reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to ensure capturing order of template delimiters */
+  var reNoMatch = /($^)/;
+
+  /** Used to match HTML characters */
+  var reUnescapedHtml = /[&<>"']/g;
+
+  /** Used to match unescaped characters in compiled string literals */
+  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;
+
+  /** Used to fix the JScript [[DontEnum]] bug */
+  var shadowed = [
+    'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
+    'toLocaleString', 'toString', 'valueOf'
+  ];
+
+  /** Used to make template sourceURLs easier to identify */
+  var templateCounter = 0;
+
+  /** Native method shortcuts */
+  var ceil = Math.ceil,
+      concat = arrayRef.concat,
+      floor = Math.floor,
+      getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
+      hasOwnProperty = objectRef.hasOwnProperty,
+      push = arrayRef.push,
+      propertyIsEnumerable = objectRef.propertyIsEnumerable,
+      toString = objectRef.toString;
+
+  /* Native method shortcuts for methods with the same name as other `lodash` methods */
+  var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind,
+      nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray,
+      nativeIsFinite = window.isFinite,
+      nativeIsNaN = window.isNaN,
+      nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys,
+      nativeMax = Math.max,
+      nativeMin = Math.min,
+      nativeRandom = Math.random;
+
+  /** `Object#toString` result shortcuts */
+  var argsClass = '[object Arguments]',
+      arrayClass = '[object Array]',
+      boolClass = '[object Boolean]',
+      dateClass = '[object Date]',
+      funcClass = '[object Function]',
+      numberClass = '[object Number]',
+      objectClass = '[object Object]',
+      regexpClass = '[object RegExp]',
+      stringClass = '[object String]';
+
+  /** Detect various environments */
+  var isIeOpera = !!window.attachEvent,
+      isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera);
+
+  /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */
+  var isBindFast = nativeBind && !isV8;
+
+  /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */
+  var isKeysFast = nativeKeys && (isIeOpera || isV8);
+
+  /**
+   * Detect the JScript [[DontEnum]] bug:
+   *
+   * In IE < 9 an objects own properties, shadowing non-enumerable ones, are
+   * made non-enumerable as well.
+   */
+  var hasDontEnumBug;
+
+  /** Detect if own properties are iterated after inherited properties (IE < 9) */
+  var iteratesOwnLast;
+
+  /**
+   * Detect if `Array#shift` and `Array#splice` augment array-like objects
+   * incorrectly:
+   *
+   * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
+   * and `splice()` functions that fail to remove the last element, `value[0]`,
+   * of array-like objects even though the `length` property is set to `0`.
+   * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+   * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+   */
+  var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 },
+    arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]);
+
+  /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */
+  var nonEnumArgs = true;
+
+  (function() {
+    var props = [];
+    function ctor() { this.x = 1; }
+    ctor.prototype = { 'valueOf': 1, 'y': 1 };
+    for (var prop in new ctor) { props.push(prop); }
+    for (prop in arguments) { nonEnumArgs = !prop; }
+
+    hasDontEnumBug = !/valueOf/.test(props);
+    iteratesOwnLast = props[0] != 'x';
+  }(1));
+
+  /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */
+  var argsAreObjects = arguments.constructor == Object;
+
+  /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */
+  var noArgsClass = !isArguments(arguments);
+
+  /**
+   * Detect lack of support for accessing string characters by index:
+   *
+   * IE < 8 can't access characters by index and IE 8 can only access
+   * characters by index on string literals.
+   */
+  var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx';
+
+  /**
+   * Detect if a node's [[Class]] is unresolvable (IE < 9)
+   * and that the JS engine won't error when attempting to coerce an object to
+   * a string without a `toString` property value of `typeof` "function".
+   */
+  try {
+    var noNodeClass = ({ 'toString': 0 } + '', toString.call(document) == objectClass);
+  } catch(e) { }
+
+  /**
+   * Detect if sourceURL syntax is usable without erroring:
+   *
+   * The JS engine embedded in Adobe products will throw a syntax error when
+   * it encounters a single line comment beginning with the `@` symbol.
+   *
+   * The JS engine in Narwhal will generate the function `function anonymous(){//}`
+   * and throw a syntax error.
+   *
+   * Avoid comments beginning `@` symbols in IE because they are part of its
+   * non-standard conditional compilation support.
+   * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx
+   */
+  try {
+    var useSourceURL = (Function('//@')(), !isIeOpera);
+  } catch(e) { }
+
+  /** Used to identify object classifications that `_.clone` supports */
+  var cloneableClasses = {};
+  cloneableClasses[funcClass] = false;
+  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
+  cloneableClasses[boolClass] = cloneableClasses[dateClass] =
+  cloneableClasses[numberClass] = cloneableClasses[objectClass] =
+  cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;
+
+  /** Used to lookup a built-in constructor by [[Class]] */
+  var ctorByClass = {};
+  ctorByClass[arrayClass] = Array;
+  ctorByClass[boolClass] = Boolean;
+  ctorByClass[dateClass] = Date;
+  ctorByClass[objectClass] = Object;
+  ctorByClass[numberClass] = Number;
+  ctorByClass[regexpClass] = RegExp;
+  ctorByClass[stringClass] = String;
+
+  /** Used to determine if values are of the language type Object */
+  var objectTypes = {
+    'boolean': false,
+    'function': true,
+    'object': true,
+    'number': false,
+    'string': false,
+    'undefined': false
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\t': 't',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a `lodash` object, that wraps the given `value`, to enable
+   * method chaining.
+   *
+   * The chainable wrapper functions are:
+   * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`,
+   * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`,
+   * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`,
+   * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`,
+   * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `pick`, `pluck`,
+   * `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
+   * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`,
+   * `unshift`, `values`, `where`, `without`, `wrap`, and `zip`
+   *
+   * The non-chainable wrapper functions are:
+   * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, `identity`,
+   * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`,
+   * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`,
+   * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`,
+   * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`,
+   * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId`
+   *
+   * The wrapper functions `first` and `last` return wrapped values when `n` is
+   * passed, otherwise they return unwrapped values.
+   *
+   * @name _
+   * @constructor
+   * @category Chaining
+   * @param {Mixed} value The value to wrap in a `lodash` instance.
+   * @returns {Object} Returns a `lodash` instance.
+   */
+  function lodash(value) {
+    // exit early if already wrapped, even if wrapped by a different `lodash` constructor
+    if (value && typeof value == 'object' && value.__wrapped__) {
+      return value;
+    }
+    // allow invoking `lodash` without the `new` operator
+    if (!(this instanceof lodash)) {
+      return new lodash(value);
+    }
+    this.__wrapped__ = value;
+  }
+
+  /**
+   * By default, the template delimiters used by Lo-Dash are similar to those in
+   * embedded Ruby (ERB). Change the following template settings to use alternative
+   * delimiters.
+   *
+   * @static
+   * @memberOf _
+   * @type Object
+   */
+  lodash.templateSettings = {
+
+    /**
+     * Used to detect `data` property values to be HTML-escaped.
+     *
+     * @static
+     * @memberOf _.templateSettings
+     * @type RegExp
+     */
+    'escape': /<%-([\s\S]+?)%>/g,
+
+    /**
+     * Used to detect code to be evaluated.
+     *
+     * @static
+     * @memberOf _.templateSettings
+     * @type RegExp
+     */
+    'evaluate': /<%([\s\S]+?)%>/g,
+
+    /**
+     * Used to detect `data` property values to inject.
+     *
+     * @static
+     * @memberOf _.templateSettings
+     * @type RegExp
+     */
+    'interpolate': reInterpolate,
+
+    /**
+     * Used to reference the data object in the template text.
+     *
+     * @static
+     * @memberOf _.templateSettings
+     * @type String
+     */
+    'variable': ''
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The template used to create iterator functions.
+   *
+   * @private
+   * @param {Obect} data The data object used to populate the text.
+   * @returns {String} Returns the interpolated text.
+   */
+  var iteratorTemplate = template(
+    // conditional strict mode
+    "<% if (obj.useStrict) { %>'use strict';\n<% } %>" +
+
+    // the `iteratee` may be reassigned by the `top` snippet
+    'var index, iteratee = <%= firstArg %>, ' +
+    // assign the `result` variable an initial value
+    'result = <%= firstArg %>;\n' +
+    // exit early if the first argument is falsey
+    'if (!<%= firstArg %>) return result;\n' +
+    // add code before the iteration branches
+    '<%= top %>;\n' +
+
+    // array-like iteration:
+    '<% if (arrayLoop) { %>' +
+    'var length = iteratee.length; index = -1;\n' +
+    "if (typeof length == 'number') {" +
+
+    // add support for accessing string characters by index if needed
+    '  <% if (noCharByIndex) { %>\n' +
+    '  if (isString(iteratee)) {\n' +
+    "    iteratee = iteratee.split('')\n" +
+    '  }' +
+    '  <% } %>\n' +
+
+    // iterate over the array-like value
+    '  while (++index < length) {\n' +
+    '    <%= arrayLoop %>\n' +
+    '  }\n' +
+    '}\n' +
+    'else {' +
+
+    // object iteration:
+    // add support for iterating over `arguments` objects if needed
+    '  <%  } else if (nonEnumArgs) { %>\n' +
+    '  var length = iteratee.length; index = -1;\n' +
+    '  if (length && isArguments(iteratee)) {\n' +
+    '    while (++index < length) {\n' +
+    "      index += '';\n" +
+    '      <%= objectLoop %>\n' +
+    '    }\n' +
+    '  } else {' +
+    '  <% } %>' +
+
+    // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+    // (if the prototype or a property on the prototype has been set)
+    // incorrectly sets a function's `prototype` property [[Enumerable]]
+    // value to `true`. Because of this Lo-Dash standardizes on skipping
+    // the the `prototype` property of functions regardless of its
+    // [[Enumerable]] value.
+    '  <% if (!hasDontEnumBug) { %>\n' +
+    "  var skipProto = typeof iteratee == 'function' && \n" +
+    "    propertyIsEnumerable.call(iteratee, 'prototype');\n" +
+    '  <% } %>' +
+
+    // iterate own properties using `Object.keys` if it's fast
+    '  <% if (isKeysFast && useHas) { %>\n' +
+    '  var ownIndex = -1,\n' +
+    '      ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' +
+    '      length = ownProps.length;\n\n' +
+    '  while (++ownIndex < length) {\n' +
+    '    index = ownProps[ownIndex];\n' +
+    "    <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n  <% } %>" +
+    '    <%= objectLoop %>\n' +
+    '    <% if (!hasDontEnumBug) { %>}\n<% } %>' +
+    '  }' +
+
+    // else using a for-in loop
+    '  <% } else { %>\n' +
+    '  for (index in iteratee) {<%' +
+    '    if (!hasDontEnumBug || useHas) { %>\n    if (<%' +
+    "      if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% }" +
+    '      if (!hasDontEnumBug && useHas) { %> && <% }' +
+    '      if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' +
+    '    %>) {' +
+    '    <% } %>\n' +
+    '    <%= objectLoop %>;' +
+    '    <% if (!hasDontEnumBug || useHas) { %>\n    }<% } %>\n' +
+    '  }' +
+    '  <% } %>' +
+
+    // Because IE < 9 can't set the `[[Enumerable]]` attribute of an
+    // existing property and the `constructor` property of a prototype
+    // defaults to non-enumerable, Lo-Dash skips the `constructor`
+    // property when it infers it's iterating over a `prototype` object.
+    '  <% if (hasDontEnumBug) { %>\n\n' +
+    '  var ctor = iteratee.constructor;\n' +
+    '    <% for (var k = 0; k < 7; k++) { %>\n' +
+    "  index = '<%= shadowed[k] %>';\n" +
+    '  if (<%' +
+    "      if (shadowed[k] == 'constructor') {" +
+    '        %>!(ctor && ctor.prototype === iteratee) && <%' +
+    '      } %>hasOwnProperty.call(iteratee, index)) {\n' +
+    '    <%= objectLoop %>\n' +
+    '  }' +
+    '    <% } %>' +
+    '  <% } %>' +
+    '  <% if (arrayLoop || nonEnumArgs) { %>\n}<% } %>\n' +
+
+    // add code to the bottom of the iteration function
+    '<%= bottom %>;\n' +
+    // finally, return the `result`
+    'return result'
+  );
+
+  /** Reusable iterator options for `assign` and `defaults` */
+  var assignIteratorOptions = {
+    'args': 'object, source, guard',
+    'top':
+      "for (var argsIndex = 1, argsLength = typeof guard == 'number' ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n" +
+      '  if ((iteratee = arguments[argsIndex])) {',
+    'objectLoop': 'result[index] = iteratee[index]',
+    'bottom': '  }\n}'
+  };
+
+  /**
+   * Reusable iterator options shared by `each`, `forIn`, and `forOwn`.
+   */
+  var eachIteratorOptions = {
+    'args': 'collection, callback, thisArg',
+    'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)",
+    'arrayLoop': 'if (callback(iteratee[index], index, collection) === false) return result',
+    'objectLoop': 'if (callback(iteratee[index], index, collection) === false) return result'
+  };
+
+  /** Reusable iterator options for `forIn` and `forOwn` */
+  var forOwnIteratorOptions = {
+    'arrayLoop': null
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a function optimized to search large arrays for a given `value`,
+   * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {Mixed} value The value to search for.
+   * @param {Number} [fromIndex=0] The index to search from.
+   * @param {Number} [largeSize=30] The length at which an array is considered large.
+   * @returns {Boolean} Returns `true` if `value` is found, else `false`.
+   */
+  function cachedContains(array, fromIndex, largeSize) {
+    fromIndex || (fromIndex = 0);
+
+    var length = array.length,
+        isLarge = (length - fromIndex) >= (largeSize || largeArraySize);
+
+    if (isLarge) {
+      var cache = {},
+          index = fromIndex - 1;
+
+      while (++index < length) {
+        // manually coerce `value` to a string because `hasOwnProperty`, in some
+        // older versions of Firefox, coerces objects incorrectly
+        var key = array[index] + '';
+        (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]);
+      }
+    }
+    return function(value) {
+      if (isLarge) {
+        var key = value + '';
+        return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1;
+      }
+      return indexOf(array, value, fromIndex) > -1;
+    }
+  }
+
+  /**
+   * Used by `_.max` and `_.min` as the default `callback` when a given
+   * `collection` is a string value.
+   *
+   * @private
+   * @param {String} value The character to inspect.
+   * @returns {Number} Returns the code unit of given character.
+   */
+  function charAtCallback(value) {
+    return value.charCodeAt(0);
+  }
+
+  /**
+   * Used by `sortBy` to compare transformed `collection` values, stable sorting
+   * them in ascending order.
+   *
+   * @private
+   * @param {Object} a The object to compare to `b`.
+   * @param {Object} b The object to compare to `a`.
+   * @returns {Number} Returns the sort order indicator of `1` or `-1`.
+   */
+  function compareAscending(a, b) {
+    var ai = a.index,
+        bi = b.index;
+
+    a = a.criteria;
+    b = b.criteria;
+
+    // ensure a stable sort in V8 and other engines
+    // http://code.google.com/p/v8/issues/detail?id=90
+    if (a !== b) {
+      if (a > b || typeof a == 'undefined') {
+        return 1;
+      }
+      if (a < b || typeof b == 'undefined') {
+        return -1;
+      }
+    }
+    return ai < bi ? -1 : 1;
+  }
+
+  /**
+   * Creates a function that, when called, invokes `func` with the `this`
+   * binding of `thisArg` and prepends any `partailArgs` to the arguments passed
+   * to the bound function.
+   *
+   * @private
+   * @param {Function|String} func The function to bind or the method name.
+   * @param {Mixed} [thisArg] The `this` binding of `func`.
+   * @param {Array} partialArgs An array of arguments to be partially applied.
+   * @returns {Function} Returns the new bound function.
+   */
+  function createBound(func, thisArg, partialArgs) {
+    var isFunc = isFunction(func),
+        isPartial = !partialArgs,
+        key = thisArg;
+
+    // juggle arguments
+    if (isPartial) {
+      partialArgs = thisArg;
+    }
+    if (!isFunc) {
+      thisArg = func;
+    }
+
+    function bound() {
+      // `Function#bind` spec
+      // http://es5.github.com/#x15.3.4.5
+      var args = arguments,
+          thisBinding = isPartial ? this : thisArg;
+
+      if (!isFunc) {
+        func = thisArg[key];
+      }
+      if (partialArgs.length) {
+        args = args.length
+          ? partialArgs.concat(slice(args))
+          : partialArgs;
+      }
+      if (this instanceof bound) {
+        // ensure `new bound` is an instance of `bound` and `func`
+        noop.prototype = func.prototype;
+        thisBinding = new noop;
+        noop.prototype = null;
+
+        // mimic the constructor's `return` behavior
+        // http://es5.github.com/#x13.2.2
+        var result = func.apply(thisBinding, args);
+        return isObject(result) ? result : thisBinding;
+      }
+      return func.apply(thisBinding, args);
+    }
+    return bound;
+  }
+
+  /**
+   * Produces an iteration callback bound to an optional `thisArg`. If `func` is
+   * a property name, the callback will return the property value for a given element.
+   *
+   * @private
+   * @param {Function|String} [func=identity|property] The function called per
+   * iteration or property name to query.
+   * @param {Mixed} [thisArg] The `this` binding of `callback`.
+   * @param {Object} [accumulating] Used to indicate that the callback should
+   *  accept an `accumulator` argument.
+   * @returns {Function} Returns a callback function.
+   */
+  function createCallback(func, thisArg, accumulating) {
+    if (!func) {
+      return identity;
+    }
+    if (typeof func != 'function') {
+      return function(object) {
+        return object[func];
+      };
+    }
+    if (typeof thisArg != 'undefined') {
+      if (accumulating) {
+        return function(accumulator, value, index, object) {
+          return func.call(thisArg, accumulator, value, index, object);
+        };
+      }
+      return function(value, index, object) {
+        return func.call(thisArg, value, index, object);
+      };
+    }
+    return func;
+  }
+
+  /**
+   * Creates compiled iteration functions.
+   *
+   * @private
+   * @param {Object} [options1, options2, ...] The compile options object(s).
+   *  useHas - A boolean to specify using `hasOwnProperty` checks in the object loop.
+   *  args - A string of comma separated arguments the iteration function will accept.
+   *  top - A string of code to execute before the iteration branches.
+   *  arrayLoop - A string of code to execute in the array loop.
+   *  objectLoop - A string of code to execute in the object loop.
+   *  bottom - A string of code to execute after the iteration branches.
+   *
+   * @returns {Function} Returns the compiled function.
+   */
+  function createIterator() {
+    var data = {
+      'arrayLoop': '',
+      'bottom': '',
+      'hasDontEnumBug': hasDontEnumBug,
+      'isKeysFast': isKeysFast,
+      'objectLoop': '',
+      'nonEnumArgs': nonEnumArgs,
+      'noCharByIndex': noCharByIndex,
+      'shadowed': shadowed,
+      'top': '',
+      'useHas': true
+    };
+
+    // merge options into a template data object
+    for (var object, index = 0; object = arguments[index]; index++) {
+      for (var key in object) {
+        data[key] = object[key];
+      }
+    }
+    var args = data.args;
+    data.firstArg = /^[^,]+/.exec(args)[0];
+
+    // create the function factory
+    var factory = Function(
+        'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' +
+        'nativeKeys, propertyIsEnumerable',
+      'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}'
+    );
+    // return the compiled function
+    return factory(
+      createCallback, hasOwnProperty, isArguments, isString, objectTypes,
+      nativeKeys, propertyIsEnumerable
+    );
+  }
+
+  /**
+   * A function compiled to iterate `arguments` objects, arrays, objects, and
+   * strings consistenly across environments, executing the `callback` for each
+   * element in the `collection`. The `callback` is bound to `thisArg` and invoked
+   * with three arguments; (value, index|key, collection). Callbacks may exit
+   * iteration early by explicitly returning `false`.
+   *
+   * @private
+   * @param {Array|Object|String} collection The collection to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {Mixed} [thisArg] The `this` binding of `callback`.
+   * @returns {Array|Object|String} Returns `collection`.
+   */
+  var each = createIterator(eachIteratorOptions);
+
+  /**
+   * Used by `template` to escape characters for inclusion in compiled
+   * string literals.
+   *
+   * @private
+   * @param {String} match The matched character to escape.
+   * @returns {String} Returns the escaped character.
+   */
+  function escapeStringChar(match) {
+    return '\\' + stringEscapes[match];
+  }
+
+  /**
+   * Used by `escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {String} match The matched character to escape.
+   * @returns {String} Returns the escaped character.
+   */
+  function escapeHtmlChar(match) {
+    return htmlEscapes[match];
+  }
+
+  /**
+   * Checks if `value` is a DOM node in IE < 9.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the `value` is a DOM node, else `false`.
+   */
+  function isNode(value) {
+    // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+    // methods that are `typeof` "string" and still can coerce nodes to strings
+    return typeof value.toString != 'function' && typeof (value + '') == 'string';
+  }
+
+  /**
+   * A no-operation function.
+   *
+   * @private
+   */
+  function noop() {
+    // no operation performed
+  }
+
+  /**
+   * Slices the `collection` from the `start` index up to, but not including,
+   * the `end` index.
+   *
+   * Note: This function is used, instead of `Array#slice`, to support node lists
+   * in IE < 9 and to ensure dense arrays are returned.
+   *
+   * @private
+   * @param {Array|Object|String} collection The collection to slice.
+   * @param {Number} start The start index.
+   * @param {Number} end The end index.
+   * @returns {Array} Returns the new array.
+   */
+  function slice(array, start, end) {
+    start || (start = 0);
+    if (typeof end == 'undefined') {
+      end = array ? array.length : 0;
+    }
+    var index = -1,
+        length = end - start || 0,
+        result = Array(length < 0 ? 0 : length);
+
+    while (++index < length) {
+      result[index] = array[start + index];
+    }
+    return result;
+  }
+
+  /**
+   * Used by `unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {String} match The matched character to unescape.
+   * @returns {String} Returns the unescaped character.
+   */
+  function unescapeHtmlChar(match) {
+    return htmlUnescapes[match];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Assigns own enumerable properties of source object(s) to the `destination`
+   * object. Subsequent sources will overwrite propery assignments of previous
+   * sources.
+   *
+   * @static
+   * @memberOf _
+   * @alias extend
+   * @category Objects
+   * @param {Object} object The destination object.
+   * @param {Object} [source1, source2, ...] The source objects.
+   * @returns {Object} Returns the destination object.
+   * @example
+   *
+   * _.assign({ 'name': 'moe' }, { 'age': 40 });
+   * // => { 'name': 'moe', 'age': 40 }
+   */
+  var assign = createIterator(assignIteratorOptions);
+
+  /**
+   * Checks if `value` is an `arguments` object.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
+   * @example
+   *
+   * (function() { return _.isArguments(arguments); })(1, 2, 3);
+   * // => true
+   *
+   * _.isArguments([1, 2, 3]);
+   * // => false
+   */
+  function isArguments(value) {
+    return toString.call(value) == argsClass;
+  }
+  // fallback for browsers that can't detect `arguments` objects by [[Class]]
+  if (noArgsClass) {
+    isArguments = function(value) {
+      return value ? hasOwnProperty.call(value, 'callee') : false;
+    };
+  }
+
+  /**
+   * Iterates over `object`'s own and inherited enumerable properties, executing
+   * the `callback` for each property. The `callback` is bound to `thisArg` and
+   * invoked with three arguments; (value, key, object). Callbacks may exit iteration
+   * early by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {Mixed} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns `object`.
+   * @example
+   *
+   * function Dog(name) {
+   *   this.name = name;
+   * }
+   *
+   * Dog.prototype.bark = function() {
+   *   alert('Woof, woof!');
+   * };
+   *
+   * _.forIn(new Dog('Dagny'), function(value, key) {
+   *   alert(key);
+   * });
+   * // => alerts 'name' and 'bark' (order is not guaranteed)
+   */
+  var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, {
+    'useHas': false
+  });
+
+  /**
+   * Iterates over an object's own enumerable properties, executing the `callback`
+   * for each property. The `callback` is bound to `thisArg` and invoked with three
+   * arguments; (value, key, object). Callbacks may exit iteration early by explicitly
+   * returning `false`.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The object to iterate over.
+   * @param {Function} [callback=identity] The function called per iteration.
+   * @param {Mixed} [thisArg] The `this` binding of `callback`.
+   * @returns {Object} Returns `object`.
+   * @example
+   *
+   * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
+   *   alert(key);
+   * });
+   * // => alerts '0', '1', and 'length' (order is not guaranteed)
+   */
+  var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions);
+
+  /**
+   * A fallback implementation of `isPlainObject` that checks if a given `value`
+   * is an object created by the `Object` constructor, assuming objects created
+   * by the `Object` constructor have no inherited enumerable properties and that
+   * there are no `Object.prototype` extensions.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`.
+   */
+  function shimIsPlainObject(value) {
+    // avoid non-objects and false positives for `arguments` objects
+    var result = false;
+    if (!(value && typeof value == 'object') || isArguments(value)) {
+      return result;
+    }
+    // check that the constructor is `Object` (i.e. `Object instanceof Object`)
+    var ctor = value.constructor;
+    if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) {
+      // IE < 9 iterates inherited properties before own properties. If the first
+      // iterated property is an object's own property then there are no inherited
+      // enumerable properties.
+      if (iteratesOwnLast) {
+        forIn(value, function(value, key, object) {
+          result = !hasOwnProperty.call(object, key);
+          return false;
+        });
+        return result === false;
+      }
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      forIn(value, function(value, key) {
+        result = key;
+      });
+      return result === false || hasOwnProperty.call(value, result);
+    }
+    return result;
+  }
+
+  /**
+   * A fallback implementation of `Object.keys` that produces an array of the
+   * given object's own enumerable property names.
+   *
+   * @private
+   * @param {Object} object The object to inspect.
+   * @returns {Array} Returns a new array of property names.
+   */
+  function shimKeys(object) {
+    var result = [];
+    forOwn(object, function(value, key) {
+      result.push(key);
+    });
+    return result;
+  }
+
+  /**
+   * Used to convert characters to HTML entities:
+   *
+   * Though the `>` character is escaped for symmetry, characters like `>` and `/`
+   * don't require escaping in HTML and have no special meaning unless they're part
+   * of a tag or an unquoted attribute value.
+   * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
+   */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;'
+  };
+
+  /** Used to convert HTML entities to characters */
+  var htmlUnescapes = invert(htmlEscapes);
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a clone of `value`. If `deep` is `true`, nested objects will also
+   * be cloned, otherwise they will be assigned by reference.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Mixed} value The value to clone.
+   * @param {Boolean} deep A flag to indicate a deep clone.
+   * @param- {Object} [guard] Internally used to allow this method to work with
+   *  others like `_.map` without using their callback `index` argument for `deep`.
+   * @param- {Array} [stackA=[]] Internally used to track traversed source objects.
+   * @param- {Array} [stackB=[]] Internally used to associate clones with their
+   *  source counterparts.
+   * @returns {Mixed} Returns the cloned `value`.
+   * @example
+   *
+   * var stooges = [
+   *   { 'name': 'moe', 'age': 40 },
+   *   { 'name': 'larry', 'age': 50 },
+   *   { 'name': 'curly', 'age': 60 }
+   * ];
+   *
+   * var shallow = _.clone(stooges);
+   * shallow[0] === stooges[0];
+   * // => true
+   *
+   * var deep = _.clone(stooges, true);
+   * deep[0] === stooges[0];
+   * // => false
+   */
+  function clone(value, deep, guard, stackA, stackB) {
+    if (value == null) {
+      return value;
+    }
+    if (guard) {
+      deep = false;
+    }
+    // inspect [[Class]]
+    var isObj = isObject(value);
+    if (isObj) {
+      var className = toString.call(value);
+      if (!cloneableClasses[className] || (noNodeClass && isNode(value))) {
+        return value;
+      }
+      var isArr = isArray(value);
+    }
+    // shallow clone
+    if (!isObj || !deep) {
+      return isObj
+        ? (isArr ? slice(value) : assign({}, value))
+        : value;
+    }
+    var ctor = ctorByClass[className];
+    switch (className) {
+      case boolClass:
+      case dateClass:
+        return new ctor(+value);
+
+      case numberClass:
+      case stringClass:
+        return new ctor(value);
+
+      case regexpClass:
+        return ctor(value.source, reFlags.exec(value));
+    }
+    // check for circular references and return corresponding clone
+    stackA || (stackA = []);
+    stackB || (stackB = []);
+
+    var length = stackA.length;
+    while (length--) {
+      if (stackA[length] == value) {
+        return stackB[length];
+      }
+    }
+    // init cloned object
+    var result = isArr ? ctor(value.length) : {};
+
+    // add the source value to the stack of traversed objects
+    // and associate it with its clone
+    stackA.push(value);
+    stackB.push(result);
+
+    // recursively populate clone (susceptible to call stack limits)
+    (isArr ? forEach : forOwn)(value, function(objValue, key) {
+      result[key] = clone(objValue, deep, null, stackA, stackB);
+    });
+
+    // add array properties assigned by `RegExp#exec`
+    if (isArr) {
+      if (hasOwnProperty.call(value, 'index')) {
+        result.index = value.index;
+      }
+      if (hasOwnProperty.call(value, 'input')) {
+        result.input = value.input;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates a deep clone of `value`. Functions and DOM nodes are **not** cloned.
+   * The enumerable properties of `arguments` objects and objects created by
+   * constructors other than `Object` are cloned to plain `Object` objects.
+   *
+   * Note: This function is loosely based on the structured clone algorithm.
+   * See http://www.w3.org/TR/html5/common-dom-interfaces.html#internal-structured-cloning-algorithm.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Mixed} value The value to deep clone.
+   * @returns {Mixed} Returns the deep cloned `value`.
+   * @example
+   *
+   * var stooges = [
+   *   { 'name': 'moe', 'age': 40 },
+   *   { 'name': 'larry', 'age': 50 },
+   *   { 'name': 'curly', 'age': 60 }
+   * ];
+   *
+   * var deep = _.cloneDeep(stooges);
+   * deep[0] === stooges[0];
+   * // => false
+   */
+  function cloneDeep(value) {
+    return clone(value, true);
+  }
+
+  /**
+   * Assigns own enumerable properties of source object(s) to the `destination`
+   * object for all `destination` properties that resolve to `null`/`undefined`.
+   * Once a property is set, additional defaults of the same property will be
+   * ignored.
+   *
+   * @static
+   * @memberOf _
+   * @category Objects
+   * @param {Object} object The destination object.
+   * @param {Object} [default1, default2, ...] The default objects.
+   * @returns {Object} Returns the destination object.
+   * @example
+   *
+   * var iceCream = { 'flavor': 'chocolate' };
+   * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' });
+   * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' }
+   */
+  var defaults = createIterator(assignIteratorOptions, {
+    'objectLoop': 'if (result[index] == null) ' + assignIteratorOptions.objectLoop
+  });
+
+  /**
+   * Creates a sorted array of all enumerable properties, own and inherited,
+   * of `object` that have function values.
+   *
+   * @static