Bug 1057602 - Uplift Enhanced Tiles to Firefox 33. a=sledru
☠☠ backed out by 5ff5ecda2dfb ☠ ☠
authorEd Lee <edilee@mozilla.com>
Fri, 22 Aug 2014 16:23:00 -0400
changeset 217631 0dfba827f5fd9727a0440fd2044b0696325ac761
parent 217630 b256c5f6fc2ba287f688c32b90de855b35654402
child 217632 b8bfb14bab0c2dc2f796d318857f2a58df00e69f
push id515
push userraliiev@mozilla.com
push dateMon, 06 Oct 2014 12:51:51 +0000
treeherdermozilla-release@267c7a481bef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssledru
bugs1057602
milestone33.0a2
Bug 1057602 - Uplift Enhanced Tiles to Firefox 33. a=sledru
browser/app/profile/firefox.js
browser/base/content/newtab/customize.js
browser/base/content/newtab/grid.js
browser/base/content/newtab/intro.js
browser/base/content/newtab/newTab.css
browser/base/content/newtab/newTab.js
browser/base/content/newtab/newTab.xul
browser/base/content/newtab/page.js
browser/base/content/newtab/sites.js
browser/base/content/test/general/browser_tabopen_reflows.js
browser/base/content/test/newtab/browser.ini
browser/base/content/test/newtab/browser_newtab_background_captures.js
browser/base/content/test/newtab/browser_newtab_bug721442.js
browser/base/content/test/newtab/browser_newtab_bug725996.js
browser/base/content/test/newtab/browser_newtab_bug752841.js
browser/base/content/test/newtab/browser_newtab_bug765628.js
browser/base/content/test/newtab/browser_newtab_bug991111.js
browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
browser/base/content/test/newtab/browser_newtab_enhanced.js
browser/base/content/test/newtab/browser_newtab_intro.js
browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
browser/base/content/test/newtab/browser_newtab_update.js
browser/base/content/test/newtab/head.js
browser/themes/linux/jar.mn
browser/themes/linux/newtab/newTab.css
browser/themes/osx/jar.mn
browser/themes/osx/newtab/newTab.css
browser/themes/shared/newtab/controls.png
browser/themes/shared/newtab/controls.svg
browser/themes/shared/newtab/controls@2x.png
browser/themes/shared/newtab/newTab.inc.css
browser/themes/windows/jar.mn
browser/themes/windows/newtab/newTab.css
testing/profiles/prefs_general.js
testing/talos/talos.json
toolkit/components/telemetry/Histograms.json
toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
toolkit/content/directoryLinks.json
toolkit/content/jar.mn
toolkit/modules/DirectoryLinksProvider.jsm
toolkit/modules/NewTabUtils.jsm
toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
toolkit/modules/tests/xpcshell/test_NewTabUtils.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1503,30 +1503,33 @@ pref("prompts.tab_modal.enabled", true);
 // Whether the Panorama should animate going in/out of tabs
 pref("browser.panorama.animate_zoom", true);
 
 // Defines the url to be used for new tabs.
 pref("browser.newtab.url", "about:newtab");
 // Activates preloading of the new tab url.
 pref("browser.newtab.preload", true);
 
+// Remembers if the about:newtab intro has been shown
+pref("browser.newtabpage.introShown", false);
+
 // Toggles the content of 'about:newtab'. Shows the grid when enabled.
 pref("browser.newtabpage.enabled", true);
 
 // number of rows of newtab grid
 pref("browser.newtabpage.rows", 3);
 
 // number of columns of newtab grid
-pref("browser.newtabpage.columns", 3);
+pref("browser.newtabpage.columns", 5);
 
 // directory tiles download URL
-pref("browser.newtabpage.directory.source", "chrome://global/content/directoryLinks.json");
+pref("browser.newtabpage.directory.source", "https://tiles.up.mozillalabs.com/v2/links/fetch");
 
-// endpoint to send newtab click reports
-pref("browser.newtabpage.directory.reportClickEndPoint", "https://tiles.up.mozillalabs.com/ping/click");
+// endpoint to send newtab click and view pings
+pref("browser.newtabpage.directory.ping", "https://tiles.up.mozillalabs.com/v2/links/");
 
 // Enable the DOM fullscreen API.
 pref("full-screen-api.enabled", true);
 
 // True if the fullscreen API requires approval upon a domain entering fullscreen.
 // Domains that have already had fullscreen permission granted won't re-request
 // approval.
 pref("full-screen-api.approval-required", true);
new file mode 100644
--- /dev/null
+++ b/browser/base/content/newtab/customize.js
@@ -0,0 +1,94 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+let gCustomize = {
+  _nodeIDSuffixes: [
+    "blank",
+    "button",
+    "classic",
+    "enhanced",
+    "panel",
+    "what",
+  ],
+
+  _nodes: {},
+
+  init: function() {
+    for (let idSuffix of this._nodeIDSuffixes) {
+      this._nodes[idSuffix] = document.getElementById("newtab-customize-" + idSuffix);
+    }
+
+    this._nodes.button.setAttribute("title", newTabString("customize.title"));
+    ["enhanced", "classic", "blank", "what"].forEach(name => {
+      this._nodes[name].firstChild.textContent = newTabString("customize." + name);
+    });
+
+    this._nodes.button.addEventListener("click", e => this.showPanel());
+    this._nodes.blank.addEventListener("click", e => {
+      gAllPages.enabled = false;
+    });
+    this._nodes.classic.addEventListener("click", e => {
+      gAllPages.enabled = true;
+      gAllPages.enhanced = false;
+    });
+    this._nodes.enhanced.addEventListener("click", e => {
+      gAllPages.enabled = true;
+      gAllPages.enhanced = true;
+    });
+    this._nodes.what.addEventListener("click", e => {
+      gIntro.showPanel();
+    });
+
+    this.updateSelected();
+  },
+
+  showPanel: function() {
+    if (!DirectoryLinksProvider.enabled) {
+      gAllPages.enabled = !gAllPages.enabled;
+      return;
+    }
+
+    let nodes = this._nodes;
+    let {button, panel} = nodes;
+    if (button.hasAttribute("active")) {
+      return Promise.resolve(nodes);
+    }
+
+    panel.openPopup(button);
+    button.setAttribute("active", true);
+    panel.addEventListener("popuphidden", function onHidden() {
+      panel.removeEventListener("popuphidden", onHidden);
+      button.removeAttribute("active");
+    });
+
+    return new Promise(resolve => {
+      panel.addEventListener("popupshown", function onShown() {
+        panel.removeEventListener("popupshown", onShown);
+        resolve(nodes);
+      });
+    });
+  },
+
+  updateSelected: function() {
+    let {enabled, enhanced} = gAllPages;
+
+    if (!DirectoryLinksProvider.enabled) {
+      this._nodes.button.setAttribute("title", newTabString(enabled ? "hide" : "show"));
+      return;
+    }
+
+    let selected = enabled ? enhanced ? "enhanced" : "classic" : "blank";
+    ["enhanced", "classic", "blank"].forEach(id => {
+      let node = this._nodes[id];
+      if (id == selected) {
+        node.setAttribute("selected", true);
+      }
+      else {
+        node.removeAttribute("selected");
+      }
+    });
+  },
+};
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -2,17 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 #endif
 
 /**
  * Define various fixed dimensions
  */
-const GRID_BOTTOM_EXTRA = 4; // title's line-height extends 4px past the margin
+const GRID_BOTTOM_EXTRA = 7; // title's line-height extends 7px past the margin
 const GRID_WIDTH_EXTRA = 1; // provide 1px buffer to allow for rounding error
 
 /**
  * This singleton represents the grid that contains all sites.
  */
 let gGrid = {
   /**
    * The DOM node of the grid.
@@ -156,24 +156,24 @@ let gGrid = {
     let site = document.createElementNS(HTML_NAMESPACE, "div");
     site.classList.add("newtab-site");
     site.setAttribute("draggable", "true");
 
     // Create the site's inner HTML code.
     site.innerHTML =
       '<a class="newtab-link">' +
       '  <span class="newtab-thumbnail"/>' +
+      '  <span class="newtab-thumbnail enhanced-content"/>' +
       '  <span class="newtab-title"/>' +
       '</a>' +
       '<input type="button" title="' + newTabString("pin") + '"' +
       '       class="newtab-control newtab-control-pin"/>' +
       '<input type="button" title="' + newTabString("block") + '"' +
       '       class="newtab-control newtab-control-block"/>' +
-      '<input type="button" title="' + newTabString("sponsored") + '"' +
-      '       class="newtab-control newtab-control-sponsored"/>';
+      '<span class="newtab-sponsored">' + newTabString("sponsored.button") + '</span>';
 
     this._siteFragment = document.createDocumentFragment();
     this._siteFragment.appendChild(site);
   },
 
   /**
    * Renders the sites, creates all sites and puts them into their cells.
    */
@@ -197,17 +197,16 @@ let gGrid = {
     if (this._cellMargin === undefined) {
       let refCell = document.querySelector(".newtab-cell");
       this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) * 2;
       this._cellHeight = refCell.offsetHeight + this._cellMargin;
       this._cellWidth = refCell.offsetWidth + this._cellMargin;
     }
 
     let availSpace = document.documentElement.clientHeight - this._cellMargin -
-                     document.querySelector("#newtab-margin-undo-container").offsetHeight -
                      document.querySelector("#newtab-search-container").offsetHeight;
     let visibleRows = Math.floor(availSpace / this._cellHeight);
     this._node.style.height = this._computeHeight() + "px";
     this._node.style.maxHeight = this._computeHeight(visibleRows) + "px";
     this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
                                 GRID_WIDTH_EXTRA + "px";
   },
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/newtab/intro.js
@@ -0,0 +1,62 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+const PREF_INTRO_SHOWN = "browser.newtabpage.introShown";
+
+let gIntro = {
+  _introShown: Services.prefs.getBoolPref(PREF_INTRO_SHOWN),
+
+  _nodeIDSuffixes: [
+    "panel",
+  ],
+
+  _nodes: {},
+
+  init: function() {
+    for (let idSuffix of this._nodeIDSuffixes) {
+      this._nodes[idSuffix] = document.getElementById("newtab-intro-" + idSuffix);
+    }
+
+    this._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel());
+  },
+
+  showIfNecessary: function() {
+    if (!DirectoryLinksProvider.enabled) {
+      return;
+    }
+
+    if (!this._introShown) {
+      Services.prefs.setBoolPref(PREF_INTRO_SHOWN, true);
+      this.showPanel();
+    }
+  },
+
+  showPanel: function() {
+    // Open the customize menu first
+    gCustomize.showPanel().then(nodes => {
+      // Point the panel at the 'what' menu item
+      this._nodes.panel.openPopup(nodes.what);
+    });
+  },
+
+  _setUpPanel: function() {
+    // Build the panel if necessary
+    if (this._nodes.panel.childNodes.length == 0) {
+      let h1 = document.createElementNS(HTML_NAMESPACE, "h1");
+      this._nodes.panel.appendChild(h1);
+      h1.textContent = newTabString("intro.header");
+
+      ['<a href="' + TILES_EXPLAIN_LINK + '">' + newTabString("learn.link") + "</a>",
+       '<a href="' + TILES_PRIVACY_LINK + '">' + newTabString("privacy.link") + "</a>",
+       '<input type="button" class="newtab-customize"/>',
+      ].forEach((arg, index) => {
+        let paragraph = document.createElementNS(HTML_NAMESPACE, "p");
+        this._nodes.panel.appendChild(paragraph);
+        paragraph.innerHTML = newTabString("intro.paragraph" + (index + 1), [arg]);
+      });
+    }
+  },
+};
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -23,49 +23,75 @@ input[type=button] {
 #newtab-scrollbox:not([page-disabled]) {
   overflow: auto;
 }
 
 /* UNDO */
 #newtab-undo-container {
   transition: opacity 100ms ease-out;
   display: -moz-box;
-  margin-bottom: 26px; /* 32 - 6 search form top "padding" */
   -moz-box-align: center;
   -moz-box-pack: center;
 }
 
 #newtab-undo-container[undo-disabled] {
   opacity: 0;
   pointer-events: none;
 }
 
-/* TOGGLE */
-#newtab-toggle {
+/* CUSTOMIZE */
+#newtab-customize-button {
   position: absolute;
-  top: 12px;
-  right: 12px;
+  top: 6px;
+  right: 6px;
+}
+
+#newtab-customize-button:-moz-locale-dir(rtl) {
+  left: 6px;
+  right: auto;
 }
 
-#newtab-toggle:-moz-locale-dir(rtl) {
-  left: 12px;
-  right: auto;
+#newtab-intro-panel {
+  color: #737373;
+  font-size: 15px;
+  line-height: 20px;
+  margin-top: -32px;
+  padding: 10px;
+  width: 500px;
+}
+
+#newtab-intro-panel h1 {
+  color: #343f48;
+  font-family: Open Sans, sans-serif;
+  font-size: 30px;
+}
+
+#newtab-intro-panel a {
+  color: #4a90e2;
 }
 
 /* MARGINS */
 #newtab-vertical-margin {
   display: -moz-box;
   position: relative;
   -moz-box-flex: 1;
   -moz-box-orient: vertical;
 }
 
 #newtab-margin-undo-container {
   display: -moz-box;
-  -moz-box-pack: center;
+  left: 6px;
+  position: absolute;
+  top: 6px;
+  z-index: 1;
+}
+
+#newtab-margin-undo-container:-moz-locale-dir(rtl) {
+  left: auto;
+  right: 6px;
 }
 
 #newtab-horizontal-margin {
   display: -moz-box;
   -moz-box-flex: 1;
 }
 
 #newtab-margin-top,
@@ -78,17 +104,17 @@ input[type=button] {
   -moz-box-flex: 1;
 }
 
 #newtab-margin-bottom {
   -moz-box-flex: 2;
 }
 
 .newtab-side-margin {
-  min-width: 16px;
+  min-width: 10px;
   -moz-box-flex: 1;
 }
 
 /* GRID */
 #newtab-grid {
   -moz-box-flex: 5;
   overflow: hidden;
   text-align: center;
@@ -103,19 +129,19 @@ input[type=button] {
 #newtab-grid[locked],
 #newtab-grid[page-disabled] {
   pointer-events: none;
 }
 
 /* CELLS */
 .newtab-cell {
   display: -moz-box;
-  height: 150px;
-  margin: 16px;
-  width: 243px;
+  height: 180px;
+  margin: 20px 10px;
+  width: 290px;
 }
 
 /* SITES */
 .newtab-site {
   position: relative;
   -moz-box-flex: 1;
   transition: 100ms ease-out;
   transition-property: top, left, opacity;
@@ -136,38 +162,86 @@ input[type=button] {
 .newtab-thumbnail {
   position: absolute;
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
 }
 
-.newtab-thumbnail {
-  opacity: .8;
-  transition: opacity 100ms ease-out;
+/* TITLES */
+.newtab-title, .newtab-sponsored {
+  bottom: -26px;
+  overflow: hidden;
+  position: absolute;
+  right: 0;
+  text-align: center;
+  white-space: nowrap;
 }
 
-.newtab-thumbnail[dragged],
-.newtab-link:-moz-focusring > .newtab-thumbnail,
-.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-link > .newtab-thumbnail {
-  opacity: 1;
+.newtab-title {
+  font-size: 13px;
+  left: 0;
+  padding-top: 14px;
+  text-overflow: ellipsis;
+}
+
+.newtab-sponsored {
+  background-color: #f9f9f9;
+  border: 1px solid #dcdcdc;
+  border-radius: 2px;
+  color: #9b9b9b;
+  cursor: pointer;
+  display: none;
+  font-family: Arial;
+  font-size: 10px;
+  height: 17px;
+  line-height: 17px;
+  margin-bottom: -1px;
+  padding: 0 4px;
 }
 
-/* TITLES */
-.newtab-title {
-  bottom: -21px;
-  position: absolute;
+.newtab-sponsored:-moz-any(:hover, [active]) {
+  background-color: #dcdcdc;
+  color: #666666;
+}
+
+.newtab-sponsored:-moz-locale-dir(rtl) {
   left: 0;
-  line-height: 21px;
-  right: 0;
+  right: auto;
+}
+
+.newtab-site:-moz-any([type=enhanced], [type=sponsored]) .newtab-sponsored {
+  display: block;
+}
+
+.sponsored-explain,
+.sponsored-explain a {
+  color: white;
+}
+
+.sponsored-explain {
+  background-color: rgba(51, 51, 51, 0.95);
+  border-bottom-left-radius: 6px;
+  border-bottom-right-radius: 6px;
+  bottom: 0px;
+  line-height: 20px;
+  padding: 15px 10px;
+  position: absolute;
   text-align: start;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
+}
+
+#newtab-intro-panel input,
+.sponsored-explain input {
+  background-size: 20px;
+  height: 20px;
+  opacity: 1;
+  pointer-events: none;
+  position: static;
+  width: 20px;
 }
 
 /* CONTROLS */
 .newtab-control {
   position: absolute;
   top: 4px;
   opacity: 0;
   transition: opacity 100ms ease-out;
@@ -183,68 +257,40 @@ input[type=button] {
 }
 
 @media (-moz-touch-enabled) {
   .newtab-control {
     opacity: 1;
   }
 }
 
-.newtab-control-sponsored:-moz-locale-dir(rtl),
 .newtab-control-pin:-moz-locale-dir(ltr),
 .newtab-control-block:-moz-locale-dir(rtl) {
   left: 4px;
 }
 
-.newtab-control-sponsored:-moz-locale-dir(ltr),
 .newtab-control-block:-moz-locale-dir(ltr),
 .newtab-control-pin:-moz-locale-dir(rtl) {
   right: 4px;
 }
 
-.newtab-control.newtab-control-sponsored {
-  bottom: -20px;
-  height: 14px;
-  -moz-margin-end: -5px;
-  opacity: 1;
-  top: auto;
-  width: 14px;
-}
-
-.newtab-site:not([type=sponsored]) .newtab-control-sponsored {
-  display: none;
-}
-
 /* DRAG & DROP */
 
 /*
  * This is just a temporary drag element used for dataTransfer.setDragImage()
  * so that we can use custom drag images and elements. It needs an opacity of
  * 0.01 so that the core code detects that it's in fact a visible element.
  */
 .newtab-drag {
   width: 1px;
   height: 1px;
   background-color: #fff;
   opacity: 0.01;
 }
 
-/* SPONSORED PANEL */
-#sponsored-panel {
-  width: 330px;
-}
-
-#sponsored-panel description {
-  margin: 0;
-}
-
-#sponsored-panel .text-link {
-  margin: 12px 0 0;
-}
-
 /* SEARCH */
 #newtab-search-container {
   display: -moz-box;
   position: relative;
   -moz-box-align: center;
   -moz-box-pack: center;
 }
 
@@ -254,17 +300,17 @@ input[type=button] {
 }
 
 #newtab-search-form {
   display: -moz-box;
   -moz-box-flex: 1;
   -moz-box-orient: horizontal;
   -moz-box-align: center;
   height: 44px; /* 32 + 6 logo top "padding" + 6 logo bottom "padding" */
-  margin: 0 20px 10px; /* bottom: 32 - 16 tiles top margin - 6 logo bottom "padding" */
+  margin: 26px 20px 10px; /* top: 32 - 6 search form top "padding", bottom: 32 - 16 tiles top margin - 6 logo bottom "padding" */
   max-width: 600px; /* 2 * (290 cell width + 10 cell margin) */
 }
 
 #newtab-search-logo {
   display: -moz-box;
   width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */
   height: 38px; /* 26 image height + 6 top "padding" + 6 bottom "padding" */
   border: 1px solid transparent;
@@ -281,17 +327,17 @@ input[type=button] {
 #newtab-search-logo[active],
 #newtab-search-logo:hover {
   background-color: #e9e9e9;
   border: 1px solid rgb(226, 227, 229);
   border-radius: 2.5px;
 }
 
 #newtab-search-text {
-  height: 32px;
+  height: 38px;
   -moz-box-flex: 1;
 
   padding: 0 8px;
   background: hsla(0,0%,100%,.9) padding-box;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
               0 0 2px hsla(210,65%,9%,.1) inset,
@@ -305,17 +351,18 @@ input[type=button] {
 }
 
 #newtab-search-text:focus,
 #newtab-search-text[autofocus] {
   border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
 }
 
 #newtab-search-submit {
-  height: 32px;
+  height: 38px;
+  font-size: 13px !important;
 
   -moz-margin-start: -1px;
   background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
   padding: 0 9px;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   -moz-border-start: 1px solid transparent;
   border-radius: 0 2.5px 2.5px 0;
@@ -355,44 +402,49 @@ input[type=button] {
 }
 
 #newtab-search-text + #newtab-search-submit:hover:active {
   box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
               0 0 1px hsla(211,79%,6%,.2) inset;
   transition-duration: 0ms;
 }
 
+#newtab-customize-panel .panel-arrowcontent,
 #newtab-search-panel .panel-arrowcontent {
   -moz-padding-start: 0;
   -moz-padding-end: 0;
   padding-top: 0;
   padding-bottom: 0;
   background: rgb(248, 250, 251);
 }
 
+.newtab-customize-panel-item,
 .newtab-search-panel-engine {
   -moz-box-align: center;
   padding-top: 4px;
   padding-bottom: 4px;
   -moz-padding-start: 24px;
   -moz-padding-end: 24px;
 }
 
+.newtab-customize-panel-item:not(:last-child),
 .newtab-search-panel-engine:not(:last-child) {
   border-bottom: 1px solid #ccc;
 }
 
 .newtab-search-panel-engine > image {
   -moz-margin-end: 8px;
   width: 16px;
   height: 16px;
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 
+.newtab-customize-panel-item > label,
 .newtab-search-panel-engine > label {
   -moz-padding-start: 0;
   -moz-margin-start: 0;
   color: rgb(130, 132, 133);
 }
 
+.newtab-customize-panel-item[selected],
 .newtab-search-panel-engine[selected] {
   background: url("chrome://global/skin/menu/shared-menu-check.png") center left 4px no-repeat transparent;
 }
--- a/browser/base/content/newtab/newTab.js
+++ b/browser/base/content/newtab/newTab.js
@@ -30,33 +30,86 @@ let {
   gridPrefs: gGridPrefs
 } = NewTabUtils;
 
 XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
   return Services.strings.
     createBundle("chrome://browser/locale/newTab.properties");
 });
 
-function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
+function newTabString(name, args) {
+  switch (name) {
+    case "customize.title":
+      return "Customize your New Tab page";
+
+    case "customize.enhanced":
+      return "Enhanced";
+
+    case "customize.classic":
+      return "Classic";
+
+    case "customize.blank":
+      return "Blank";
+
+    case "customize.what":
+    case "intro.header":
+      return "What is this page?";
+
+    case "intro.paragraph1":
+      return "When you open a new tab, you’ll see tiles from the sites you frequently visit, along with tiles that we think might be of interest to you. Some of these tiles may be sponsored by Mozilla partners. We’ll always indicate to you which tiles are sponsored. %1$S".replace("%1$S", args[0]);
+
+    case "intro.paragraph2":
+      return "In order to provide this service, Mozilla collects and uses certain analytics information relating to your use of the tiles in accordance with our %1$S.".replace("%1$S", args[0]);
+
+    case "intro.paragraph3":
+      return "You can turn off the tiles feature by clicking the %1$S button for your preferences.".replace("%1$S", args[0]);
+
+    case "sponsored.button":
+      return "SPONSORED";
+
+    case "sponsored.explain":
+      return "This tile is being shown to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S".replace("%1$S", args[0]).replace("%2$S", args[1]);
+
+    case "enhanced.explain":
+      return "A Mozilla partner has visually enhanced this tile, replacing the screenshot. You can turn off enhanced tiles by clicking the %1$S button for your preferences. %2$S".replace("%1$S", args[0]).replace("%2$S", args[1]);
+
+    case "learn.link":
+      return "Learn more…";
+
+    case "privacy.link":
+      return "Privacy Notice";
+  }
+
+  let stringName = "newtab." + name;
+  if (!args) {
+    return gStringBundle.GetStringFromName(stringName);
+  }
+  return gStringBundle.formatStringFromName(stringName, args, args.length);
+}
 
 function inPrivateBrowsingMode() {
   return PrivateBrowsingUtils.isWindowPrivate(window);
 }
 
 const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
 const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
+const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-sponsored-tiles-work";
+const TILES_PRIVACY_LINK = "https://www.mozilla.org/privacy/";
+
 #include transformations.js
 #include page.js
 #include grid.js
 #include cells.js
 #include sites.js
 #include drag.js
 #include dragDataHelper.js
 #include drop.js
 #include dropTargetShim.js
 #include dropPreview.js
 #include updater.js
 #include undo.js
 #include search.js
+#include customize.js
+#include intro.js
 
 // Everything is loaded. Initialize the New Tab Page.
 gPage.init();
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -14,31 +14,43 @@
   <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd">
   %searchBarDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             title="&newtab.pageTitle;">
 
-  <xul:panel id="sponsored-panel" orient="vertical" type="arrow">
-    <xul:description id="sponsored-panel-release-descr">&newtab.sponsored.release.message;</xul:description>
-    <xul:description id="sponsored-panel-trial-descr">&newtab.sponsored.trial.message2;</xul:description>
-    <xul:label class="text-link"
-               href="https://support.mozilla.org/kb/how-do-sponsored-tiles-work"
-               value="&newtab.panel.link.text;" />
+  <xul:panel id="newtab-intro-panel" orient="vertical" type="arrow"
+             noautohide="true" position="leftcenter topright">
   </xul:panel>
 
   <xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
              noautohide="true">
     <xul:hbox id="newtab-search-manage" class="newtab-search-panel-engine">
       <xul:label>&cmd_engineManager.label;</xul:label>
     </xul:hbox>
   </xul:panel>
 
+  <xul:panel id="newtab-customize-panel" orient="vertical" type="arrow"
+             noautohide="true">
+    <xul:hbox id="newtab-customize-enhanced" class="newtab-customize-panel-item">
+      <xul:label/>
+    </xul:hbox>
+    <xul:hbox id="newtab-customize-classic" class="newtab-customize-panel-item">
+      <xul:label/>
+    </xul:hbox>
+    <xul:hbox id="newtab-customize-blank" class="newtab-customize-panel-item">
+      <xul:label/>
+    </xul:hbox>
+    <xul:hbox id="newtab-customize-what" class="newtab-customize-panel-item">
+      <xul:label/>
+    </xul:hbox>
+  </xul:panel>
+
   <div id="newtab-scrollbox">
 
     <div id="newtab-vertical-margin">
 
       <div id="newtab-margin-top"/>
 
       <div id="newtab-margin-undo-container">
         <div id="newtab-undo-container" undo-disabled="true">
@@ -73,14 +85,15 @@
         </div>
 
         <div class="newtab-side-margin"/>
       </div>
 
       <div id="newtab-margin-bottom"/>
 
     </div>
-    <input id="newtab-toggle" type="button"/>
+
+    <input id="newtab-customize-button" type="button"/>
   </div>
 
   <xul:script type="text/javascript;version=1.8"
               src="chrome://browser/content/newtab/newTab.js"/>
 </xul:window>
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -19,43 +19,45 @@ let gPage = {
     // Listen for 'unload' to unregister this page.
     addEventListener("unload", this, false);
 
     // XXX bug 991111 - Not all click events are correctly triggered when
     // listening from xhtml nodes -- in particular middle clicks on sites, so
     // listen from the xul window and filter then delegate
     addEventListener("click", this, false);
 
-    // Initialize sponsored panel
-    this._sponsoredPanel = document.getElementById("sponsored-panel");
-    let link = this._sponsoredPanel.querySelector(".text-link");
-    link.addEventListener("click", () => this._sponsoredPanel.hidePopup());
-    if (UpdateChannel.get().startsWith("release")) {
-      document.getElementById("sponsored-panel-trial-descr").style.display = "none";
-    }
-    else {
-      document.getElementById("sponsored-panel-release-descr").style.display = "none";
-    }
-
     // Check if the new tab feature is enabled.
     let enabled = gAllPages.enabled;
     if (enabled)
       this._init();
 
     this._updateAttributes(enabled);
+
+    // Initialize customize controls.
+    gCustomize.init();
+
+    // Initialize intro panel.
+    gIntro.init();
   },
 
   /**
    * Listens for notifications specific to this page.
    */
   observe: function Page_observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
+      gCustomize.updateSelected();
+
       let enabled = gAllPages.enabled;
       this._updateAttributes(enabled);
 
+      // Update thumbnails to the new enhanced setting
+      if (aData == "browser.newtabpage.enhanced") {
+        this.update();
+      }
+
       // Initialize the whole page if we haven't done that, yet.
       if (enabled) {
         this._init();
       } else {
         gUndoDialog.hide();
       }
     } else if (aTopic == "page-thumbnail:create" && gGrid.ready) {
       for (let site of gGrid.sites) {
@@ -75,47 +77,32 @@ let gPage = {
     let skipUpdate = aOnlyIfHidden && !document.hidden;
     // The grid might not be ready yet as we initialize it asynchronously.
     if (gGrid.ready && !skipUpdate) {
       gGrid.refresh();
     }
   },
 
   /**
-   * Shows sponsored panel
-   */
-  showSponsoredPanel: function Page_showSponsoredPanel(aTarget) {
-    if (this._sponsoredPanel.state == "closed") {
-      let self = this;
-      this._sponsoredPanel.addEventListener("popuphidden", function onPopupHidden(aEvent) {
-        self._sponsoredPanel.removeEventListener("popuphidden", onPopupHidden, false);
-        aTarget.removeAttribute("panelShown");
-      });
-    }
-    aTarget.setAttribute("panelShown", "true");
-    this._sponsoredPanel.openPopup(aTarget);
-  },
-
-  /**
    * Internally initializes the page. This runs only when/if the feature
    * is/gets enabled.
    */
   _init: function Page_init() {
     if (this._initialized)
       return;
 
     this._initialized = true;
 
     // Initialize search.
     gSearch.init();
 
     if (document.hidden) {
       addEventListener("visibilitychange", this);
     } else {
-      this.onPageFirstVisible();
+      setTimeout(_ => this.onPageFirstVisible());
     }
 
     // Initialize and render the grid.
     gGrid.init();
 
     // Initialize the drop target shim.
     gDropTargetShim.init();
 
@@ -127,55 +114,44 @@ let gPage = {
   },
 
   /**
    * Updates the 'page-disabled' attributes of the respective DOM nodes.
    * @param aValue Whether the New Tab Page is enabled or not.
    */
   _updateAttributes: function Page_updateAttributes(aValue) {
     // Set the nodes' states.
-    let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid, #newtab-search-container";
+    let nodeSelector = "#newtab-scrollbox, #newtab-grid, #newtab-search-container";
     for (let node of document.querySelectorAll(nodeSelector)) {
       if (aValue)
         node.removeAttribute("page-disabled");
       else
         node.setAttribute("page-disabled", "true");
     }
 
     // Enables/disables the control and link elements.
     let inputSelector = ".newtab-control, .newtab-link";
     for (let input of document.querySelectorAll(inputSelector)) {
       if (aValue) 
         input.removeAttribute("tabindex");
       else
         input.setAttribute("tabindex", "-1");
     }
-
-    // Update the toggle button's title.
-    let toggle = document.getElementById("newtab-toggle");
-    toggle.setAttribute("title", newTabString(aValue ? "hide" : "show"));
   },
 
   /**
    * Handles all page events.
    */
   handleEvent: function Page_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "unload":
         gAllPages.unregister(this);
         break;
       case "click":
         let {button, target} = aEvent;
-        if (target.id == "newtab-toggle") {
-          if (button == 0) {
-            gAllPages.enabled = !gAllPages.enabled;
-          }
-          break;
-        }
-
         // Go up ancestors until we find a Site or not
         while (target) {
           if (target.hasOwnProperty("_newtabSite")) {
             target._newtabSite.onClick(aEvent);
             break;
           }
           target = target.parentNode;
         }
@@ -196,47 +172,50 @@ let gPage = {
         break;
     }
   },
 
   onPageFirstVisible: function () {
     // Record another page impression.
     Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
 
-    // Initialize type counting with the types we want to count
-    let directoryCount = {};
-    for (let type of DirectoryLinksProvider.linkTypes) {
-      directoryCount[type] = 0;
-    }
-
     for (let site of gGrid.sites) {
       if (site) {
         site.captureIfMissing();
+      }
+    }
 
-        // Record which tile index a directory link was shown
-        let {directoryIndex, type} = site.link;
-        if (directoryIndex !== undefined) {
-          let tileIndex = site.cell.index;
-          // For telemetry, only handle the first 9 links in the first 9 cells
-          if (directoryIndex < 9) {
-            let shownId = "NEWTAB_PAGE_DIRECTORY_LINK" + directoryIndex + "_SHOWN";
-            Services.telemetry.getHistogramById(shownId).add(Math.min(9, tileIndex));
-          }
-        }
+    // Allow the document to reflow so the page has sizing info
+    let i = 0;
+    let checkSizing = _ => setTimeout(_ => {
+      if (document.documentElement.clientWidth == 0) {
+        checkSizing();
+      }
+      else {
+        this.onPageFirstSized();
+      }
+    });
+    checkSizing();
+  },
 
-        // Aggregate tile impression counts into directory types
-        if (type in directoryCount) {
-          directoryCount[type]++;
+  onPageFirstSized: function() {
+    // Work backwards to find the first visible site from the end
+    let {sites} = gGrid;
+    let lastIndex = sites.length;
+    while (lastIndex-- > 0) {
+      let site = sites[lastIndex];
+      if (site) {
+        let {node} = site;
+        let rect = node.getBoundingClientRect();
+        let target = document.elementFromPoint(rect.x + rect.width / 2,
+                                               rect.y + rect.height / 2);
+        if (node.contains(target)) {
+          break;
         }
       }
     }
 
-    DirectoryLinksProvider.reportShownCount(directoryCount);
-    // Record how many directory sites were shown, but place counts over the
-    // default 9 in the same bucket
-    for (let type of Object.keys(directoryCount)) {
-      let count = directoryCount[type];
-      let shownId = "NEWTAB_PAGE_DIRECTORY_" + type.toUpperCase() + "_SHOWN";
-      let shownCount = Math.min(10, count);
-      Services.telemetry.getHistogramById(shownId).add(shownCount);
-    }
+    DirectoryLinksProvider.reportSitesAction(gGrid.sites, "view", lastIndex);
+
+    // Show the panel now that anchors are sized
+    gIntro.showIfNecessary();
   }
 };
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -103,30 +103,31 @@ Site.prototype = {
    * Updates attributes for all nodes which status depends on this site being
    * pinned or unpinned.
    * @param aPinned Whether this site is now pinned or unpinned.
    */
   _updateAttributes: function (aPinned) {
     let control = this._querySelector(".newtab-control-pin");
 
     if (aPinned) {
-      control.setAttribute("pinned", true);
+      this.node.setAttribute("pinned", true);
       control.setAttribute("title", newTabString("unpin"));
     } else {
-      control.removeAttribute("pinned");
+      this.node.removeAttribute("pinned");
       control.setAttribute("title", newTabString("pin"));
     }
   },
 
   /**
    * Renders the site's data (fills the HTML fragment).
    */
   _render: function Site_render() {
+    let enhanced = gAllPages.enhanced && DirectoryLinksProvider.getEnhancedLink(this.link);
     let url = this.url;
-    let title = this.title || url;
+    let title = enhanced && enhanced.title || this.title || url;
     let tooltip = (title == url ? title : title + "\n" + url);
 
     let link = this._querySelector(".newtab-link");
     link.setAttribute("title", tooltip);
     link.setAttribute("href", url);
     this._querySelector(".newtab-title").textContent = title;
     this.node.setAttribute("type", this.link.type);
 
@@ -148,35 +149,50 @@ Site.prototype = {
       BackgroundPageThumbs.captureIfMissing(this.url);
     }
   },
 
   /**
    * Refreshes the thumbnail for the site.
    */
   refreshThumbnail: function Site_refreshThumbnail() {
+    // Only enhance tiles if that feature is turned on
+    let link = gAllPages.enhanced && DirectoryLinksProvider.getEnhancedLink(this.link) ||
+               this.link;
+
     let thumbnail = this._querySelector(".newtab-thumbnail");
-    if (this.link.bgColor) {
-      thumbnail.style.backgroundColor = this.link.bgColor;
+    if (link.bgColor) {
+      thumbnail.style.backgroundColor = link.bgColor;
     }
-    let uri = this.link.imageURI || PageThumbs.getThumbnailURL(this.url);
+
+    let uri = link.imageURI || PageThumbs.getThumbnailURL(this.url);
     thumbnail.style.backgroundImage = 'url("' + uri + '")';
+
+    if (link.enhancedImageURI) {
+      let enhanced = this._querySelector(".enhanced-content");
+      enhanced.style.backgroundImage = 'url("' + link.enhancedImageURI + '")';
+
+      if (this.link.type != link.type) {
+        this.node.setAttribute("type", "enhanced");
+        this.enhancedId = link.directoryId;
+      }
+    }
   },
 
   /**
    * Adds event handlers for the site and its buttons.
    */
   _addEventHandlers: function Site_addEventHandlers() {
     // Register drag-and-drop event handlers.
     this._node.addEventListener("dragstart", this, false);
     this._node.addEventListener("dragend", this, false);
     this._node.addEventListener("mouseover", this, false);
 
     // Specially treat the sponsored icon to prevent regular hover effects
-    let sponsored = this._querySelector(".newtab-control-sponsored");
+    let sponsored = this._querySelector(".newtab-sponsored");
     sponsored.addEventListener("mouseover", () => {
       this.cell.node.setAttribute("ignorehover", "true");
     });
     sponsored.addEventListener("mouseout", () => {
       this.cell.node.removeAttribute("ignorehover");
     });
   },
 
@@ -199,62 +215,86 @@ Site.prototype = {
       // We only want to get indices for the default configuration, everything
       // else goes in the same bucket.
       aIndex = 9;
     }
     Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
                       .add(aIndex);
   },
 
+  _toggleSponsored: function() {
+    let button = this._querySelector(".newtab-sponsored");
+    if (button.hasAttribute("active")) {
+      let explain = this._querySelector(".sponsored-explain");
+      explain.parentNode.removeChild(explain);
+
+      button.removeAttribute("active");
+    }
+    else {
+      let explain = document.createElementNS(HTML_NAMESPACE, "div");
+      explain.className = "sponsored-explain";
+      this.node.appendChild(explain);
+
+      let link = '<a href="' + TILES_EXPLAIN_LINK + '">' +
+                 newTabString("learn.link") + "</a>";
+      let type = this.node.getAttribute("type");
+      let icon = '<input type="button" class="newtab-control newtab-' +
+                 (type == "sponsored" ? "control-block" : "customize") + '"/>';
+      explain.innerHTML = newTabString(type + ".explain", [icon, link]);
+
+      button.setAttribute("active", "true");
+    }
+  },
+
   /**
    * Handles site click events.
    */
   onClick: function Site_onClick(aEvent) {
     let action;
     let pinned = this.isPinned();
     let tileIndex = this.cell.index;
     let {button, target} = aEvent;
+
+    // Handle tile/thumbnail link click
     if (target.classList.contains("newtab-link") ||
         target.parentElement.classList.contains("newtab-link")) {
       // Record for primary and middle clicks
       if (button == 0 || button == 1) {
         this._recordSiteClicked(tileIndex);
         action = "click";
       }
     }
+    // Handle sponsored explanation link click
+    else if (target.parentElement.classList.contains("sponsored-explain")) {
+      action = "sponsored_link";
+    }
     // Only handle primary clicks for the remaining targets
     else if (button == 0) {
       aEvent.preventDefault();
       if (target.classList.contains("newtab-control-block")) {
         this.block();
         action = "block";
       }
-      else if (target.classList.contains("newtab-control-sponsored")) {
-        gPage.showSponsoredPanel(target);
+      else if (target.classList.contains("sponsored-explain") ||
+               target.classList.contains("newtab-sponsored")) {
+        this._toggleSponsored();
         action = "sponsored";
       }
       else if (pinned) {
         this.unpin();
         action = "unpin";
       }
       else {
         this.pin();
         action = "pin";
       }
     }
 
-    // Specially count click actions for directory tiles
-    let typeIndex = DirectoryLinksProvider.linkTypes.indexOf(this.link.type);
-    if (action !== undefined && typeIndex != -1) {
-      if (action == "click") {
-        Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED")
-                          .add(typeIndex);
-      }
-      DirectoryLinksProvider.reportLinkAction(this.link, action, tileIndex, pinned);
-    }
+    // Report all link click actions
+    DirectoryLinksProvider.reportSitesAction(gGrid.sites, action, tileIndex);
   },
 
   /**
    * Handles all site events.
    */
   handleEvent: function Site_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "mouseover":
--- a/browser/base/content/test/general/browser_tabopen_reflows.js
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -17,16 +17,19 @@ const EXPECTED_REFLOWS = [
   "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
     "onselect@chrome://browser/content/browser.xul|",
 
   // switching focus in openLinkIn() causes reflows
   "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
     "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
     "BrowserOpenTab@chrome://browser/content/browser.js|",
 
+  // unpreloaded newtab pages explicitly waits for reflows for sizing
+  "gPage.onPageFirstVisible/checkSizing/<@chrome://browser/content/newtab/newTab.js|",
+
   // accessing element.scrollPosition in _fillTrailingGap() flushes layout
   "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
     "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
     "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
     "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
 
   // The TabView iframe causes reflows in the parent document.
   "iQClass_height@chrome://browser/content/tabview.js|" +
@@ -80,19 +83,20 @@ function test() {
         }, true);
       }
     };
     observer.onDownloadFail = observer.onManyLinksChanged;
     DirectoryLinksProvider.addObserver(observer);
     return deferred.promise;
   };
 
+  let gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref(PREF_PRELOAD);
-    Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE);
+    Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
     return watchLinksChangeOnce();
   });
 
   // run tests when directory source change completes
   watchLinksChangeOnce().then(() => {
     // Add a reflow observer and open a new tab.
     docShell.addWeakReflowObserver(observer);
     BrowserOpenTab();
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -18,17 +18,19 @@ skip-if = os == "mac" # Intermittent fai
 [browser_newtab_bug876313.js]
 [browser_newtab_bug991111.js]
 [browser_newtab_bug991210.js]
 [browser_newtab_bug998387.js]
 [browser_newtab_disable.js]
 [browser_newtab_drag_drop.js]
 [browser_newtab_drag_drop_ext.js]
 [browser_newtab_drop_preview.js]
+[browser_newtab_enhanced.js]
 [browser_newtab_focus.js]
+[browser_newtab_intro.js]
 [browser_newtab_perwindow_private_browsing.js]
 [browser_newtab_reportLinkAction.js]
 [browser_newtab_reset.js]
 [browser_newtab_search.js]
 support-files =
   searchEngineNoLogo.xml
   searchEngine1xLogo.xml
   searchEngine2xLogo.xml
--- a/browser/base/content/test/newtab/browser_newtab_background_captures.js
+++ b/browser/base/content/test/newtab/browser_newtab_background_captures.js
@@ -13,28 +13,27 @@ function runTests() {
   Cu.import("resource://gre/modules/PageThumbs.jsm", imports);
   Cu.import("resource:///modules/BrowserNewTabPreloader.jsm", imports);
 
   // Disable captures.
   let originalDisabledState = Services.prefs.getBoolPref(CAPTURE_PREF);
   Services.prefs.setBoolPref(CAPTURE_PREF, true);
 
   // Make sure the thumbnail doesn't exist yet.
-  let siteName = "newtab_background_captures";
-  let url = "http://example.com/#" + siteName;
+  let url = "http://example.com/";
   let path = imports.PageThumbsStorage.getFilePathForURL(url);
   let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   file.initWithPath(path);
   try {
     file.remove(false);
   }
   catch (err) {}
 
   // Add a top site.
-  yield setLinks(siteName);
+  yield setLinks("-1");
 
   // We need a handle to a hidden, pre-loaded newtab so we can verify that it
   // doesn't allow background captures.  Add a newtab, which triggers creation
   // of a hidden newtab, and then keep calling BrowserNewTabPreloader.newTab
   // until it returns true, meaning that it swapped the passed-in tab's docshell
   // for the hidden newtab docshell.
   let tab = gWindow.gBrowser.addTab("about:blank");
   yield addNewTabPageTab();
--- a/browser/base/content/test/newtab/browser_newtab_bug721442.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks([
-    {url: "http://example.com/#7", title: ""},
-    {url: "http://example.com/#8", title: "title"},
-    {url: "http://example.com/#9", title: "http://example.com/#9"}
+    {url: "http://example7.com/", title: ""},
+    {url: "http://example8.com/", title: "title"},
+    {url: "http://example9.com/", title: "http://example9.com/"}
   ]);
 
   yield addNewTabPageTab();
   checkGrid("7p,8p,9p,0,1,2,3,4,5");
 
-  checkTooltip(0, "http://example.com/#7", "1st tooltip is correct");
-  checkTooltip(1, "title\nhttp://example.com/#8", "2nd tooltip is correct");
-  checkTooltip(2, "http://example.com/#9", "3rd tooltip is correct");
+  checkTooltip(0, "http://example7.com/", "1st tooltip is correct");
+  checkTooltip(1, "title\nhttp://example8.com/", "2nd tooltip is correct");
+  checkTooltip(2, "http://example9.com/", "3rd tooltip is correct");
 }
 
 function checkTooltip(aIndex, aExpected, aMessage) {
   let link = getCell(aIndex).node.querySelector(".newtab-link");
   is(link.getAttribute("title"), aExpected, aMessage);
 }
--- a/browser/base/content/test/newtab/browser_newtab_bug725996.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js
@@ -5,19 +5,19 @@ function runTests() {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
   let cell = getCell(0).node;
 
-  sendDragEvent("drop", cell, "http://example.com/#99\nblank");
-  is(NewTabUtils.pinnedLinks.links[0].url, "http://example.com/#99",
+  sendDragEvent("drop", cell, "http://example99.com/\nblank");
+  is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
      "first cell is pinned and contains the dropped site");
 
   yield whenPagesUpdated();
   checkGrid("99p,0,1,2,3,4,5,6,7");
 
   sendDragEvent("drop", cell, "");
-  is(NewTabUtils.pinnedLinks.links[0].url, "http://example.com/#99",
+  is(NewTabUtils.pinnedLinks.links[0].url, "http://example99.com/",
      "first cell is still pinned with the site we dropped before");
 }
--- a/browser/base/content/test/newtab/browser_newtab_bug752841.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug752841.js
@@ -12,18 +12,18 @@ function runTests() {
     {row: 0, column: -1},
     {row: 2, column: 4},
     {row: 2, column: 5},
   ];
 
   // Expected length of grid
   let expectedValues = [1, 1, 1, 1, 8, 10];
 
-   // Values before setting new pref values (9 is the default value -> 3 x 3)
-  let previousValues = [9, 1, 1, 1, 1, 8];
+   // Values before setting new pref values (15 is the default value -> 5 x 3)
+  let previousValues = [15, 1, 1, 1, 1, 8];
 
   let existingTab, existingTabGridLength, newTab, newTabGridLength;
   yield addNewTabPageTab();
   existingTab = gBrowser.selectedTab;
 
   for (let i = 0; i < expectedValues.length; i++) {
     gBrowser.selectedTab = existingTab;
     existingTabGridLength = getGrid().cells.length;
--- a/browser/base/content/test/newtab/browser_newtab_bug765628.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
-const GOOD_DRAG_DATA = "http://example.com/#99\nsite 99";
+const GOOD_DRAG_DATA = "http://example99.com/\nsite 99";
 
 function runTests() {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7,8");
 
--- a/browser/base/content/test/newtab/browser_newtab_bug991111.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function runTests() {
-  yield setLinks("0");
+  yield setLinks("-1");
   yield addNewTabPageTab();
 
   // Remember if the click handler was triggered
   let cell = getCell(0);
   let clicked = false;
   cell.site.onClick = e => {
     clicked = true;
     executeSoon(TestRunner.next);
--- a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
@@ -1,20 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+
 /*
  * These tests make sure that dragging and dropping sites works as expected.
  * Sites contained in the grid need to shift around to indicate the result
  * of the drag-and-drop operation. If the grid is full and we're dragging
  * a new site into it another one gets pushed out.
  * This is a continuation of browser_newtab_drag_drop.js
  * to decrease test run time, focusing on external sites.
  */
 function runTests() {
+  registerCleanupFunction(_ => Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS));
+
   // drag a new site onto the very first cell
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
   yield simulateExternalDrop(0);
@@ -23,16 +27,18 @@ function runTests() {
   // drag a new site onto the grid and make sure that pinned cells don't get
   // pushed out
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,7,8");
 
   yield addNewTabPageTab();
   checkGrid("0,1,2,3,4,5,6,7p,8p");
 
+  // force the grid to be small enough that a pinned cell could be pushed out
+  Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, 3);
   yield simulateExternalDrop(7);
   checkGrid("0,1,2,3,4,5,7p,99p,8p");
 
   // drag a new site beneath a pinned cell and make sure the pinned cell is
   // not moved
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks(",,,,,,,,8");
 
@@ -47,9 +53,9 @@ function runTests() {
   yield setLinks("0,1,2,3,4,5,6,7,8");
   setPinnedLinks("0,1,2,,,,,,");
 
   yield addNewTabPageTab();
   checkGrid("0p,1p,2p");
 
   yield simulateExternalDrop(1);
   checkGrid("0p,99p,1p,2p,3,4,5,6,7");
-}
\ No newline at end of file
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PRELOAD_PREF = "browser.newtab.preload";
+
+gDirectorySource = "data:application/json," + JSON.stringify({
+  "en-US": [{
+    url: "http://example.com/",
+    enhancedImageURI: "data:image/png;base64,helloWORLD",
+    title: "title",
+    type: "organic"
+  }]
+});
+
+function runTests() {
+  let origEnhanced = NewTabUtils.allPages.enhanced;
+  registerCleanupFunction(() => {
+    Services.prefs.clearUserPref(PRELOAD_PREF);
+    NewTabUtils.allPages.enhanced = origEnhanced;
+  });
+
+  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+
+  function getData(cellNum) {
+    let cell = getCell(cellNum);
+    if (!cell.site)
+      return null;
+    let siteNode = cell.site.node;
+    return {
+      type: siteNode.getAttribute("type"),
+      enhanced: siteNode.querySelector(".enhanced-content").style.backgroundImage,
+      title: siteNode.querySelector(".newtab-title").textContent,
+    };
+  }
+
+  // Make the page have a directory link followed by a history link
+  yield setLinks("-1");
+
+  // Test with enhanced = false
+  NewTabUtils.allPages.enhanced = false;
+  yield addNewTabPageTab();
+  let {type, enhanced, title} = getData(0);
+  is(type, "organic", "directory link is organic");
+  isnot(enhanced, "", "directory link has enhanced image");
+  is(title, "title");
+
+  is(getData(1), null, "history link pushed out by directory link");
+
+  // Test with enhanced = true
+  NewTabUtils.allPages.enhanced = true;
+  yield addNewTabPageTab();
+  let {type, enhanced, title} = getData(0);
+  is(type, "organic", "directory link is still organic");
+  isnot(enhanced, "", "directory link still has enhanced image");
+  is(title, "title");
+
+  is(getData(1), null, "history link still pushed out by directory link");
+
+  // Test with a pinned link
+  setPinnedLinks("-1");
+  yield addNewTabPageTab();
+  let {type, enhanced, title} = getData(0);
+  is(type, "enhanced", "pinned history link is enhanced");
+  isnot(enhanced, "", "pinned history link has enhanced image");
+  is(title, "title");
+
+  is(getData(1), null, "directory link pushed out by pinned history link");
+
+  // Test pinned link with enhanced = false
+  NewTabUtils.allPages.enhanced = false;
+  yield addNewTabPageTab();
+  let {type, enhanced, title} = getData(0);
+  isnot(type, "enhanced", "history link is not enhanced");
+  is(enhanced, "", "history link has no enhanced image");
+  is(title, "site#-1");
+
+  is(getData(1), null, "directory link still pushed out by pinned history link");
+}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_intro.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const INTRO_PREF = "browser.newtabpage.introShown";
+const PRELOAD_PREF = "browser.newtab.preload";
+
+function runTests() {
+  // Test with preload false
+  Services.prefs.setBoolPref(INTRO_PREF, false);
+  Services.prefs.setBoolPref(PRELOAD_PREF, false);
+
+  let panel;
+  function maybeWaitForPanel() {
+    // If already open, no need to wait
+    if (panel.state == "open") {
+      executeSoon(TestRunner.next);
+      return;
+    }
+
+    // We're expecting the panel to open, so wait for it
+    panel.addEventListener("popupshown", TestRunner.next);
+    isnot(panel.state, "open", "intro panel can be slow to show");
+  }
+
+  yield addNewTabPageTab();
+  panel = getContentDocument().getElementById("newtab-intro-panel");
+  yield maybeWaitForPanel();
+  is(panel.state, "open", "intro automatically shown on first opening");
+  is(Services.prefs.getBoolPref(INTRO_PREF), true, "newtab remembers that the intro was shown");
+
+  yield addNewTabPageTab();
+  panel = getContentDocument().getElementById("newtab-intro-panel");
+  is(panel.state, "closed", "intro not shown on second opening");
+
+  // Test with preload true
+  Services.prefs.setBoolPref(INTRO_PREF, false);
+  Services.prefs.setBoolPref(PRELOAD_PREF, true);
+
+  yield addNewTabPageTab();
+  panel = getContentDocument().getElementById("newtab-intro-panel");
+  yield maybeWaitForPanel();
+  is(panel.state, "open", "intro automatically shown on preloaded opening");
+  is(Services.prefs.getBoolPref(INTRO_PREF), true, "newtab remembers that the intro was shown");
+}
--- a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -1,68 +1,67 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 gDirectorySource = "data:application/json," + JSON.stringify({
   "en-US": [{
-    url: "http://organic.localhost/",
+    url: "http://example.com/organic",
     type: "organic"
   }, {
-    url: "http://sponsored.localhost/",
+    url: "http://localhost/sponsored",
     type: "sponsored"
   }]
 });
 
 function runTests() {
   Services.prefs.setBoolPref(PRELOAD_PREF, false);
-  yield addNewTabPageTab();
 
-  let originalReportLinkAction  = DirectoryLinksProvider.reportLinkAction;
+  let originalReportSitesAction  = DirectoryLinksProvider.reportSitesAction;
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref(PRELOAD_PREF);
-    DirectoryLinksProvider.reportLinkAction = originalReportLinkAction;
+    DirectoryLinksProvider.reportSitesAction = originalReportSitesAction;
   });
 
   let expected = {};
-  DirectoryLinksProvider.reportLinkAction = function(link, action, tileIndex, pinned) {
+  DirectoryLinksProvider.reportSitesAction = function(sites, action, siteIndex) {
+    let {link} = sites[siteIndex];
     is(link.type, expected.type, "got expected type");
-    is(link.directoryIndex, expected.link, "got expected link index");
     is(action, expected.action, "got expected action");
-    is(tileIndex, expected.tile, "got expected tile index");
-    is(pinned, expected.pinned, "got expected pinned");
+    is(NewTabUtils.pinnedLinks.isPinned(link), expected.pinned, "got expected pinned");
     executeSoon(TestRunner.next);
   }
 
+  // Test that the last visible site (index 1) is reported
+  expected.type = "sponsored";
+  expected.action = "view";
+  expected.pinned = false;
+  yield addNewTabPageTab();
+  yield null; // wait for reportSitesAction
+
   // Click the pin button on the link in the 1th tile spot
   let siteNode = getCell(1).node.querySelector(".newtab-site");
   let pinButton = siteNode.querySelector(".newtab-control-pin");
-  expected.type = "sponsored";
-  expected.link = 1;
   expected.action = "pin";
-  expected.tile = 1;
-  expected.pinned = false;
+  expected.pinned = true;
   yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
 
   // Unpin that link
   expected.action = "unpin";
-  expected.pinned = true;
+  expected.pinned = false;
   yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
   yield whenPagesUpdated();
 
   // Block the site in the 0th tile spot
   let blockedSite = getCell(0).node.querySelector(".newtab-site");
   let blockButton = blockedSite.querySelector(".newtab-control-block");
   expected.type = "organic";
-  expected.link = 0;
   expected.action = "block";
-  expected.tile = 0;
   expected.pinned = false;
   yield EventUtils.synthesizeMouseAtCenter(blockButton, {}, getContentWindow());
   yield whenPagesUpdated();
 
   // Click the 1th link now in the 0th tile spot
   expected.type = "sponsored";
-  expected.link = 1;
   expected.action = "click";
   yield EventUtils.synthesizeMouseAtCenter(siteNode, {}, getContentWindow());
 }
--- a/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
+++ b/browser/base/content/test/newtab/browser_newtab_sponsored_icon_click.js
@@ -3,31 +3,34 @@
 
 function runTests() {
   yield setLinks("0");
   yield addNewTabPageTab();
 
   let site = getCell(0).node.querySelector(".newtab-site");
   site.setAttribute("type", "sponsored");
 
-  let sponsoredPanel = getContentDocument().getElementById("sponsored-panel");
-  is(sponsoredPanel.state, "closed", "Sponsored panel must be closed");
+  // test explain text appearing upon a click
+  let sponsoredButton = site.querySelector(".newtab-sponsored");
+  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
+  let explain = site.querySelector(".sponsored-explain");
+  isnot(explain, null, "Sponsored explanation shown");
+  ok(explain.querySelector("input").classList.contains("newtab-control-block"), "sponsored tiles show blocked image");
+  ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
 
-  function continueOnceOn(event) {
-    sponsoredPanel.addEventListener(event, function listener() {
-      sponsoredPanel.removeEventListener(event, listener);
-      executeSoon(TestRunner.next);
-    });
-  }
+  // test dismissing sponsored explain
+  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
+  is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
+  ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
 
-  // test sponsoredPanel appearing upon a click
-  continueOnceOn("popupshown");
-  let sponsoredButton = site.querySelector(".newtab-control-sponsored");
-  yield EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
-  is(sponsoredPanel.state, "open", "Sponsored panel opens on click");
-  ok(sponsoredButton.hasAttribute("panelShown"), "Sponsored button has panelShown attribute");
+  // test with enhanced tile
+  site.setAttribute("type", "enhanced");
+  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
+  explain = site.querySelector(".sponsored-explain");
+  isnot(explain, null, "Sponsored explanation shown");
+  ok(explain.querySelector("input").classList.contains("newtab-customize"), "enhanced tiles show customize image");
+  ok(sponsoredButton.hasAttribute("active"), "Sponsored button has active attribute");
 
-  // test sponsoredPanel hiding
-  continueOnceOn("popuphidden");
-  yield sponsoredPanel.hidePopup();
-  is(sponsoredPanel.state, "closed", "Sponsored panel correctly closed/hidden");
-  ok(!sponsoredButton.hasAttribute("panelShown"), "Sponsored button does not have panelShown attribute");
+  // test dismissing enhanced explain
+  EventUtils.synthesizeMouseAtCenter(sponsoredButton, {}, getContentWindow());
+  is(site.querySelector(".sponsored-explain"), null, "Sponsored explanation no longer shown");
+  ok(!sponsoredButton.hasAttribute("active"), "Sponsored button does not have active attribute");
 }
--- a/browser/base/content/test/newtab/browser_newtab_update.js
+++ b/browser/base/content/test/newtab/browser_newtab_update.js
@@ -42,10 +42,10 @@ function runTests() {
   yield addNewTabPageTab();
   checkGrid("2,1,3,4,,,,,");
 
   // Make sure these added links have the right type
   is(getCell(1).site.link.type, "history", "added link is history");
 }
 
 function link(id) {
-  return { url: "http://example.com/#" + id, title: "site#" + id };
+  return { url: "http://example" + id + ".com/", title: "site#" + id };
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -21,61 +21,80 @@ let principal = Services.scriptSecurityM
 
 let isMac = ("nsILocalFileMac" in Ci);
 let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
 let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 let gWindow = window;
 
 // Default to dummy/empty directory links
 let gDirectorySource = 'data:application/json,{"test":1}';
+let gOrigDirectorySource;
 
-// The tests assume all three rows of sites are shown, but the window may be too
-// short to actually show three rows.  Resize it if necessary.
-let requiredInnerHeight =
+// The tests assume all 3 rows and all 3 columns of sites are shown, but the
+// window may be too small to actually show everything.  Resize it if necessary.
+let requiredSize = {};
+requiredSize.innerHeight =
   40 + 32 + // undo container + bottom margin
   44 + 32 + // search bar + bottom margin
-  (3 * (150 + 32)) + // 3 rows * (tile height + title and bottom margin)
+  (3 * (180 + 32)) + // 3 rows * (tile height + title and bottom margin)
+  100; // breathing room
+requiredSize.innerWidth =
+  (3 * (290 + 20)) + // 3 cols * (tile width + side margins)
   100; // breathing room
 
-let oldInnerHeight = null;
-if (gBrowser.contentWindow.innerHeight < requiredInnerHeight) {
-  oldInnerHeight = gBrowser.contentWindow.innerHeight;
-  info("Changing browser inner height from " + oldInnerHeight + " to " +
-       requiredInnerHeight);
-  gBrowser.contentWindow.innerHeight = requiredInnerHeight;
-  let screenHeight = {};
+let oldSize = {};
+Object.keys(requiredSize).forEach(prop => {
+  info([prop, gBrowser.contentWindow[prop], requiredSize[prop]]);
+  if (gBrowser.contentWindow[prop] < requiredSize[prop]) {
+    oldSize[prop] = gBrowser.contentWindow[prop];
+    info("Changing browser " + prop + " from " + oldSize[prop] + " to " +
+         requiredSize[prop]);
+    gBrowser.contentWindow[prop] = requiredSize[prop];
+  }
+});
+let (screenHeight = {}, screenWidth = {}) {
   Cc["@mozilla.org/gfx/screenmanager;1"].
     getService(Ci.nsIScreenManager).
     primaryScreen.
-    GetAvailRectDisplayPix({}, {}, {}, screenHeight);
+    GetAvailRectDisplayPix({}, {}, screenWidth, screenHeight);
   screenHeight = screenHeight.value;
+  screenWidth = screenWidth.value;
   if (screenHeight < gBrowser.contentWindow.outerHeight) {
     info("Warning: Browser outer height is now " +
          gBrowser.contentWindow.outerHeight + ", which is larger than the " +
          "available screen height, " + screenHeight +
          ". That may cause problems.");
   }
+  if (screenWidth < gBrowser.contentWindow.outerWidth) {
+    info("Warning: Browser outer width is now " +
+         gBrowser.contentWindow.outerWidth + ", which is larger than the " +
+         "available screen width, " + screenWidth +
+         ". That may cause problems.");
+  }
 }
 
 registerCleanupFunction(function () {
   while (gWindow.gBrowser.tabs.length > 1)
     gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
 
-  if (oldInnerHeight)
-    gBrowser.contentWindow.innerHeight = oldInnerHeight;
+  Object.keys(oldSize).forEach(prop => {
+    if (oldSize[prop]) {
+      gBrowser.contentWindow[prop] = oldSize[prop];
+    }
+  });
 
   // Stop any update timers to prevent unexpected updates in later tests
   let timer = NewTabUtils.allPages._scheduleUpdateTimeout;
   if (timer) {
     clearTimeout(timer);
     delete NewTabUtils.allPages._scheduleUpdateTimeout;
   }
 
   Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
-  Services.prefs.clearUserPref(PREF_NEWTAB_DIRECTORYSOURCE);
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
 
   return watchLinksChangeOnce();
 });
 
 /**
  * Resolves promise when directory links are downloaded and written to disk
  */
 function watchLinksChangeOnce() {
@@ -96,16 +115,19 @@ function watchLinksChangeOnce() {
  */
 function test() {
   waitForExplicitFinish();
   // start TestRunner.run() after directory links is downloaded and written to disk
   watchLinksChangeOnce().then(() => {
     // Wait for hidden page to update with the desired links
     whenPagesUpdated(() => TestRunner.run(), true);
   });
+
+  // Save the original directory source (which is set globally for tests)
+  gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
 }
 
 /**
  * The test runner that controls the execution flow of our tests.
  */
 let TestRunner = {
   /**
@@ -180,27 +202,30 @@ function getGrid() {
 function getCell(aIndex) {
   return getGrid().cells[aIndex];
 }
 
 /**
  * Allows to provide a list of links that is used to construct the grid.
  * @param aLinksPattern the pattern (see below)
  *
- * Example: setLinks("1,2,3")
- * Result: [{url: "http://example.com/#1", title: "site#1"},
- *          {url: "http://example.com/#2", title: "site#2"}
- *          {url: "http://example.com/#3", title: "site#3"}]
+ * Example: setLinks("-1,0,1,2,3")
+ * Result: [{url: "http://example.com/", title: "site#-1"},
+ *          {url: "http://example0.com/", title: "site#0"},
+ *          {url: "http://example1.com/", title: "site#1"},
+ *          {url: "http://example2.com/", title: "site#2"},
+ *          {url: "http://example3.com/", title: "site#3"}]
  */
 function setLinks(aLinks) {
   let links = aLinks;
 
   if (typeof links == "string") {
     links = aLinks.split(/\s*,\s*/).map(function (id) {
-      return {url: "http://example.com/#" + id, title: "site#" + id};
+      return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+              title: "site#" + id};
     });
   }
 
   // Call populateCache() once to make sure that all link fetching that is
   // currently in progress has ended. We clear the history, fill it with the
   // given entries and call populateCache() now again to make sure the cache
   // has the desired contents.
   NewTabUtils.links.populateCache(function () {
@@ -261,26 +286,27 @@ function fillHistory(aLinks, aCallback) 
 }
 
 /**
  * Allows to specify the list of pinned links (that have a fixed position in
  * the grid.
  * @param aLinksPattern the pattern (see below)
  *
  * Example: setPinnedLinks("3,,1")
- * Result: 'http://example.com/#3' is pinned in the first cell. 'http://example.com/#1' is
+ * Result: 'http://example3.com/' is pinned in the first cell. 'http://example1.com/' is
  *         pinned in the third cell.
  */
 function setPinnedLinks(aLinks) {
   let links = aLinks;
 
   if (typeof links == "string") {
     links = aLinks.split(/\s*,\s*/).map(function (id) {
       if (id)
-        return {url: "http://example.com/#" + id, title: "site#" + id};
+        return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
+                title: "site#" + id};
     });
   }
 
   let string = Cc["@mozilla.org/supports-string;1"]
                  .createInstance(Ci.nsISupportsString);
   string.data = JSON.stringify(links);
   Services.prefs.setComplexValue("browser.newtabpage.pinned",
                                  Ci.nsISupportsString, string);
@@ -332,35 +358,34 @@ function addNewTabPageTab() {
 }
 
 /**
  * Compares the current grid arrangement with the given pattern.
  * @param the pattern (see below)
  * @param the array of sites to compare with (optional)
  *
  * Example: checkGrid("3p,2,,1p")
- * Result: We expect the first cell to contain the pinned site 'http://example.com/#3'.
- *         The second cell contains 'http://example.com/#2'. The third cell is empty.
- *         The fourth cell contains the pinned site 'http://example.com/#4'.
+ * Result: We expect the first cell to contain the pinned site 'http://example3.com/'.
+ *         The second cell contains 'http://example2.com/'. The third cell is empty.
+ *         The fourth cell contains the pinned site 'http://example4.com/'.
  */
 function checkGrid(aSitesPattern, aSites) {
   let length = aSitesPattern.split(",").length;
   let sites = (aSites || getGrid().sites).slice(0, length);
   let current = sites.map(function (aSite) {
     if (!aSite)
       return "";
 
     let pinned = aSite.isPinned();
-    let pinButton = aSite.node.querySelector(".newtab-control-pin");
-    let hasPinnedAttr = pinButton.hasAttribute("pinned");
+    let hasPinnedAttr = aSite.node.hasAttribute("pinned");
 
     if (pinned != hasPinnedAttr)
       ok(false, "invalid state (site.isPinned() != site[pinned])");
 
-    return aSite.url.replace(/^http:\/\/example\.com\/#(\d+)$/, "$1") + (pinned ? "p" : "");
+    return aSite.url.replace(/^http:\/\/example(\d+)\.com\/$/, "$1") + (pinned ? "p" : "");
   });
 
   is(current, aSitesPattern, "grid status = " + aSitesPattern);
 }
 
 /**
  * Blocks a site from the grid.
  * @param aIndex The cell index.
@@ -467,24 +492,26 @@ function startAndCompleteDragOperation(a
 
 /**
  * Helper function that creates a temporary iframe in the about:newtab
  * document. This will contain a link we can drag to the test the dropping
  * of links from external documents.
  */
 function createExternalDropIframe() {
   const url = "data:text/html;charset=utf-8," +
-              "<a id='link' href='http://example.com/%2399'>link</a>";
+              "<a id='link' href='http://example99.com/'>link</a>";
 
   let deferred = Promise.defer();
   let doc = getContentDocument();
   let iframe = doc.createElement("iframe");
   iframe.setAttribute("src", url);
   iframe.style.width = "50px";
   iframe.style.height = "50px";
+  iframe.style.position = "absolute";
+  iframe.style.zIndex = 50;
 
   let margin = doc.getElementById("newtab-margin-top");
   margin.appendChild(iframe);
 
   iframe.addEventListener("load", function onLoad() {
     iframe.removeEventListener("load", onLoad);
     executeSoon(() => deferred.resolve(iframe));
   });
@@ -586,16 +613,17 @@ function createDragEvent(aEventType, aDa
  * Resumes testing when all pages have been updated.
  * @param aCallback Called when done. If not specified, TestRunner.next is used.
  * @param aOnlyIfHidden If true, this resumes testing only when an update that
  *                      applies to pre-loaded, hidden pages is observed.  If
  *                      false, this resumes testing when any update is observed.
  */
 function whenPagesUpdated(aCallback, aOnlyIfHidden=false) {
   let page = {
+    observe: _ => _,
     update: function (onlyIfHidden=false) {
       if (onlyIfHidden == aOnlyIfHidden) {
         NewTabUtils.allPages.unregister(this);
         executeSoon(aCallback || TestRunner.next);
       }
     }
   };
 
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -111,18 +111,17 @@ browser.jar:
   skin/classic/browser/feeds/feedIcon16.png           (feeds/feedIcon16.png)
   skin/classic/browser/feeds/videoFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
 * skin/classic/browser/newtab/newTab.css              (newtab/newTab.css)
-  skin/classic/browser/newtab/controls.png            (../shared/newtab/controls.png)
-  skin/classic/browser/newtab/controls@2x.png         (../shared/newtab/controls@2x.png)
+  skin/classic/browser/newtab/controls.svg            (../shared/newtab/controls.svg)
   skin/classic/browser/places/bookmarksMenu.png       (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png    (places/bookmarksToolbar.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
   skin/classic/browser/places/bookmarks-menu-arrow.png           (places/bookmarks-menu-arrow.png)
   skin/classic/browser/places/calendar.png            (places/calendar.png)
 * skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
   skin/classic/browser/places/livemark-item.png       (places/livemark-item.png)
--- a/browser/themes/linux/newtab/newTab.css
+++ b/browser/themes/linux/newtab/newTab.css
@@ -9,11 +9,12 @@
   color: rgb(221,72,20);
 }
 
 #newtab-undo-close-button {
   height: 16px;
   width: 16px;
 }
 
+#newtab-intro-panel,
 .newtab-title {
   font-family: sans-serif;
 }
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -182,18 +182,17 @@ browser.jar:
   skin/classic/browser/feeds/subscribe-ui.css               (feeds/subscribe-ui.css)
   skin/classic/browser/feeds/feedIcon.png                   (feeds/feedIcon.png)
   skin/classic/browser/feeds/feedIcon16.png                 (feeds/feedIcon16.png)
   skin/classic/browser/feeds/videoFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/videoFeedIcon16.png            (feeds/feedIcon16.png)
   skin/classic/browser/feeds/audioFeedIcon.png              (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png            (feeds/feedIcon16.png)
 * skin/classic/browser/newtab/newTab.css                    (newtab/newTab.css)
-  skin/classic/browser/newtab/controls.png                  (../shared/newtab/controls.png)
-  skin/classic/browser/newtab/controls@2x.png               (../shared/newtab/controls@2x.png)
+  skin/classic/browser/newtab/controls.svg                  (../shared/newtab/controls.svg)
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/places/allBookmarks.png              (places/allBookmarks.png)
 * skin/classic/browser/places/places.css                    (places/places.css)
 * skin/classic/browser/places/organizer.css                 (places/organizer.css)
   skin/classic/browser/places/query.png                     (places/query.png)
   skin/classic/browser/places/query@2x.png                  (places/query@2x.png)
--- a/browser/themes/osx/newtab/newTab.css
+++ b/browser/themes/osx/newtab/newTab.css
@@ -4,11 +4,12 @@
 
 %include ../../shared/newtab/newTab.inc.css
 
 
 .newtab-undo-button {
   color: rgb(20,79,174);
 }
 
+#newtab-intro-panel,
 .newtab-title {
   font-family: Lucida Grande;
 }
deleted file mode 100644
index 7f3d5f9871659babb76e01bdb4870b1710981579..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/newtab/controls.svg
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<svg version="1.1"
+     id="icons-enhanced-tiles"
+     xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     x="0"
+     y="0"
+     width="256"
+     height="32"
+     viewBox="0 0 256 32">
+
+  <defs>
+    <style type="text/css"><![CDATA[
+      /* Glyph Styles */
+
+      .glyphShape-style {
+        fill: #737373;
+      }
+
+      .glyphShape-style-hover-gear {
+        fill: #4a90e2;
+      }
+
+      .glyphShape-style-hover-pin {
+        fill: #4a90e2;
+      }
+
+      .glyphShape-style-hover-delete {
+        fill: #ea0000;
+      }
+
+      .glyphShape-style-hover-active {
+        fill: #231f20;
+      }
+
+      /* Circle Background Styles */
+
+      .glyphShape-style-circle {
+        fill: #fff;
+      }
+
+      .glyphShape-style-circle-dropshadow {
+        fill: #000;
+        fill-opacity: .5;
+        filter: url(#filter-shadow-drop);
+      }
+      ]]></style>
+
+    <filter id="filter-shadow-drop" x="-10%" y="-10%" width="120%" height="120%">
+      <feOffset in="SourceAlpha" dx="0" dy=".75" result="filter-shadow-drop-offset" />
+      <feGaussianBlur in="filter-shadow-drop-offset" stdDeviation="1" result="filter-shadow-drop-blur"/>
+    </filter>
+
+    <path    id="glyphShape-gear" d="M28,16c0-1.7,0.9-3.1,2-3.3c-0.4-1.5-0.9-2.9-1.7-4.2c-0.9,0.7-2.6,0.3-3.8-0.9c-1.2-1.2-1.6-2.8-0.9-3.8 c-1.3-0.8-2.7-1.4-4.2-1.7c-0.2,1.1-1.6,2-3.3,2S13,3.1,12.8,2c-1.5,0.4-2.9,0.9-4.2,1.7c0.7,0.9,0.3,2.6-0.9,3.8 c-1.4,1.1-3,1.5-4,0.9C2.9,9.7,2.4,11.2,2,12.7c1.1,0.2,2,1.6,2,3.3s-0.9,3.1-2,3.3c0.4,1.5,0.9,2.9,1.7,4.2 c0.9-0.7,2.6-0.3,3.8,0.9c1.2,1.2,1.6,2.8,0.9,3.8c1.3,0.8,2.7,1.4,4.2,1.7c0.2-1.1,1.6-2,3.3-2s3.1,0.9,3.3,2 c1.5-0.4,2.9-0.9,4.2-1.7c-0.7-0.9-0.3-2.6,0.9-3.8c1.3-1.2,2.8-1.6,3.8-0.9c0.8-1.3,1.4-2.7,1.7-4.2C28.9,19.1,28,17.7,28,16z M16,24c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8S20.4,24,16,24z" />
+
+    <circle  id="glyphShape-circle" cx="16" cy="16" r="14" />
+
+    <path    id="glyphShape-pin" d="M19,15v-5h2V7H11v3h2v5l-3,2v2h5c0,4.5,0.4,8,1,8s1-3.5,1-8h5v-2L19,15z" />
+    <polygon id="glyphShape-delete" points="23,11 21,9 16,14 11,9 9,11 14,16 9,21 11,23 16,18 21,23 23,21 18,16" />
+
+  </defs>
+
+  <g id="icon-gear-default">
+    <use xlink:href="#glyphShape-gear"   class="glyphShape-style" />
+  </g>
+
+  <g id="icon-gear-default" transform="translate(32)">
+    <use xlink:href="#glyphShape-gear"   class="glyphShape-style-hover-gear" />
+  </g>
+
+  <g id="icon-pin-default" transform="translate(64)">
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
+    <use xlink:href="#glyphShape-pin"    class="glyphShape-style" />
+  </g>
+
+  <g id="icon-pin-hover" transform="translate(96)">
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
+    <use xlink:href="#glyphShape-pin"    class="glyphShape-style-hover-pin" />
+  </g>
+
+  <g id="icon-pin-hover-active" transform="translate(128)">
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
+    <use xlink:href="#glyphShape-pin"    class="glyphShape-style-hover-active" />
+  </g>
+
+  <g id="icon-delete-default" transform="translate(160)">
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
+    <use xlink:href="#glyphShape-delete" class="glyphShape-style" />
+  </g>
+
+  <g id="icon-delete-hover" transform="translate(192)">
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
+    <use xlink:href="#glyphShape-delete" class="glyphShape-style-hover-delete" />
+  </g>
+
+  <g id="icon-delete-hover-active" transform="translate(224)">
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle-dropshadow" />
+    <use xlink:href="#glyphShape-circle" class="glyphShape-style-circle" />
+    <use xlink:href="#glyphShape-delete" class="glyphShape-style-hover-active" />
+  </g>
+
+</svg>
deleted file mode 100644
index b29114c0268de5de75093cdb0f44a5ade2c6084b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -4,20 +4,23 @@
 
 :root {
   -moz-appearance: none;
   font-size: 75%;
   background-color: transparent;
 }
 
 /* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]),
+#newtab-scrollbox:not([page-disabled]) {
+  color: rgb(0,0,0);
+  background-color: #f9f9f9;
+}
+
 #newtab-scrollbox:not([page-disabled]) #newtab-margin-bottom {
-  color: rgb(0,0,0);
-  background-color: hsl(0,0%,95%);
+  background: inherit;
 }
 
 /* UNDO */
 #newtab-undo-container {
   padding: 4px 3px;
   border: 1px solid;
   border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
   background-color: rgba(255,255,255,.4);
@@ -53,144 +56,143 @@
   padding: 0;
   border: none;
 }
 
 #newtab-undo-close-button:-moz-focusring {
   outline: 1px dotted;
 }
 
-/* TOGGLE */
-#newtab-toggle {
-  width: 16px;
-  height: 16px;
-  padding: 0;
+/* CUSTOMIZE */
+#newtab-customize-button,
+.newtab-customize {
+  background-color: transparent;
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 32, 32, 0);
+  background-size: 28px;
   border: none;
-  background: -216px 0 transparent url(chrome://browser/skin/newtab/controls.png);
+  height: 28px;
+  width: 28px;
 }
 
-#newtab-toggle[page-disabled] {
-  background-position: -232px 0;
-}
-
-@media (min-resolution: 2dppx) {
-  #newtab-toggle {
-    background-image: url(chrome://browser/skin/newtab/controls@2x.png);
-    background-size: 296px;
-  }
+#newtab-customize-button:-moz-any(:hover, :active, [active]) {
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 64, 32, 32);
 }
 
 /* CELLS */
 .newtab-cell {
   background-color: rgba(255,255,255,.2);
-  border: 1px solid #dee0e1;
-  transition: border-color 100ms ease-out;
+  border-radius: 8px;
 }
 
 .newtab-cell:empty {
-  border: 1px dashed;
-  border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
-}
-
-.newtab-cell:hover:not(:empty):not([dragged]):not([ignorehover]) {
-  border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
+  outline: 2px dashed #c1c1c1;
+  -moz-outline-radius: 8px;
 }
 
 /* SITES */
 .newtab-site {
+  border-radius: inherit;
+  box-shadow: 0 1px 3px #c1c1c1;
   text-decoration: none;
   transition-property: top, left, opacity, box-shadow, background-color;
 }
 
 .newtab-cell:not([ignorehover]) > .newtab-site:hover,
 .newtab-site[dragged] {
-  box-shadow: 0 0 10px rgba(8,22,37,.3);
+  border: 2px solid white;
+  box-shadow: 0 0 6px 2px #4cb1ff;
+  margin: -2px;
 }
 
 .newtab-site[dragged] {
   transition-property: box-shadow, background-color;
   background-color: rgb(242,242,242);
 }
 
+/* LINKS */
+.newtab-link {
+  border-radius: 6px;
+}
+
 /* THUMBNAILS */
 .newtab-thumbnail {
   background-origin: padding-box;
   background-clip: padding-box;
   background-repeat: no-repeat;
   background-size: cover;
+  border-radius: inherit;
+  transition: opacity 100ms ease-out;
+}
+
+.newtab-thumbnail.enhanced-content:hover {
+  opacity: 0;
 }
 
 .newtab-site[type=affiliate] .newtab-thumbnail,
+.newtab-site[type=enhanced] .newtab-thumbnail,
 .newtab-site[type=organic] .newtab-thumbnail,
 .newtab-site[type=sponsored] .newtab-thumbnail {
   background-position: center center;
   background-size: auto;
 }
 
+/* Use a pseudo-element to overlay a gradient on the thumbnail */
+.newtab-site[type=history]:not(:hover) .newtab-thumbnail:first-child::after {
+  background-image: linear-gradient(0deg, rgba(251, 251, 251, 0.8), rgba(251, 251, 251, 0) 33%);
+  border-radius: inherit;
+  bottom: 0;
+  content: "";
+  left: 0;
+  pointer-events: none;
+  position: absolute;
+  right: 0;
+  top: 0;
+}
+
 /* TITLES */
 .newtab-title {
-  color: #525c66;
-  font-size: 13px;
+  color: #737373;
 }
 
-.newtab-site[type=sponsored] .newtab-title {
-  -moz-padding-end: 24px;
+.newtab-site:hover .newtab-title {
+  color: #4a4a4a;
+}
+
+.newtab-site[pinned] .newtab-title {
+  color: #2c72c4;
+  font-weight: bold;
 }
 
 /* CONTROLS */
 .newtab-control {
-  width: 24px;
-  height: 24px;
-  padding: 1px 2px 3px;
+  background-color: transparent;
+  background-size: 24px;
   border: none;
-  background: transparent url(chrome://browser/skin/newtab/controls.png);
-}
-
-@media (min-resolution: 2dppx) {
-  .newtab-control {
-    background-image: url(chrome://browser/skin/newtab/controls@2x.png);
-    background-size: 296px;
-  }
+  height: 24px;
+  width: 24px;
 }
 
-.newtab-control-pin:hover {
-  background-position: -24px 0;
-}
-
-.newtab-control-pin:active {
-  background-position: -48px 0;
+.newtab-control-pin,
+.newtab-site[pinned] .newtab-control-pin:hover:active {
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 96, 32, 64);
 }
 
-.newtab-control-pin[pinned] {
-  background-position: -72px 0;
+.newtab-control-pin:hover,
+.newtab-site[pinned] .newtab-control-pin:hover {
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 160, 32, 128);
 }
 
-.newtab-control-pin[pinned]:hover {
-  background-position: -96px 0;
-}
-
-.newtab-control-pin[pinned]:active {
-  background-position: -120px 0;
+.newtab-control-pin:hover:active,
+.newtab-site[pinned] .newtab-control-pin {
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 128, 32, 96);
 }
 
 .newtab-control-block {
-  background-position: -144px 0;
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 192, 32, 160);
 }
 
 .newtab-control-block:hover {
-  background-position: -168px 0;
-}
-
-.newtab-control-block:active {
-  background-position: -192px 0;
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 224, 32, 192);
 }
 
-.newtab-control-sponsored {
-  background-position: -249px -1px;
+.newtab-control-block:hover:active {
+  background-image: -moz-image-rect(url(chrome://browser/skin/newtab/controls.svg), 0, 256, 32, 224);
 }
-
-.newtab-control-sponsored:hover {
-  background-position: -265px -1px;
-}
-
-.newtab-control-sponsored[panelShown] {
-  background-position: -281px -1px;
-}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -133,18 +133,17 @@ browser.jar:
         skin/classic/browser/feeds/feedIcon16.png                    (feeds/feedIcon16.png)
         skin/classic/browser/feeds/audioFeedIcon.png                 (feeds/feedIcon.png)
         skin/classic/browser/feeds/audioFeedIcon16.png               (feeds/feedIcon16.png)
         skin/classic/browser/feeds/videoFeedIcon.png                 (feeds/feedIcon.png)
         skin/classic/browser/feeds/videoFeedIcon16.png               (feeds/feedIcon16.png)
         skin/classic/browser/feeds/subscribe.css                     (feeds/subscribe.css)
         skin/classic/browser/feeds/subscribe-ui.css                  (feeds/subscribe-ui.css)
 *       skin/classic/browser/newtab/newTab.css                       (newtab/newTab.css)
-        skin/classic/browser/newtab/controls.png                     (../shared/newtab/controls.png)
-        skin/classic/browser/newtab/controls@2x.png                  (../shared/newtab/controls@2x.png)
+        skin/classic/browser/newtab/controls.svg                     (../shared/newtab/controls.svg)
         skin/classic/browser/places/places.css                       (places/places.css)
 *       skin/classic/browser/places/organizer.css                    (places/organizer.css)
         skin/classic/browser/places/bookmark.png                     (places/bookmark.png)
         skin/classic/browser/places/query.png                        (places/query.png)
         skin/classic/browser/places/bookmarksMenu.png                (places/bookmarksMenu.png)
         skin/classic/browser/places/bookmarksToolbar.png             (places/bookmarksToolbar.png)
         skin/classic/browser/places/bookmarksToolbar-menuPanel.png   (places/bookmarksToolbar-menuPanel.png)
         skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
@@ -551,18 +550,17 @@ browser.jar:
         skin/classic/aero/browser/feeds/feedIcon16.png               (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/audioFeedIcon.png            (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/audioFeedIcon16.png          (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/videoFeedIcon.png            (feeds/feedIcon-aero.png)
         skin/classic/aero/browser/feeds/videoFeedIcon16.png          (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/feeds/subscribe.css                (feeds/subscribe.css)
         skin/classic/aero/browser/feeds/subscribe-ui.css             (feeds/subscribe-ui.css)
 *       skin/classic/aero/browser/newtab/newTab.css                  (newtab/newTab.css)
-        skin/classic/aero/browser/newtab/controls.png                (../shared/newtab/controls.png)
-        skin/classic/aero/browser/newtab/controls@2x.png             (../shared/newtab/controls@2x.png)
+        skin/classic/aero/browser/newtab/controls.svg                (../shared/newtab/controls.svg)
 *       skin/classic/aero/browser/places/places.css                  (places/places-aero.css)
 *       skin/classic/aero/browser/places/organizer.css               (places/organizer-aero.css)
         skin/classic/aero/browser/places/bookmark.png                (places/bookmark-aero.png)
         skin/classic/aero/browser/places/query.png                   (places/query-aero.png)
         skin/classic/aero/browser/places/bookmarksMenu.png           (places/bookmarksMenu-aero.png)
         skin/classic/aero/browser/places/bookmarksToolbar.png        (places/bookmarksToolbar-aero.png)
         skin/classic/aero/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel-aero.png)
         skin/classic/aero/browser/places/bookmarks-notification-finish.png   (places/bookmarks-notification-finish.png)
--- a/browser/themes/windows/newtab/newTab.css
+++ b/browser/themes/windows/newtab/newTab.css
@@ -8,11 +8,12 @@
 .newtab-undo-button {
   color: rgb(0,102,204);
 }
 
 .newtab-undo-button > .button-box {
   padding: 0;
 }
 
+#newtab-intro-panel,
 .newtab-title {
   font-family: Segoe UI;
 }
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -158,16 +158,20 @@ user_pref("geo.provider.testing", true);
 // Background thumbnails in particular cause grief, and disabling thumbnails
 // in general can't hurt - we re-enable them when tests need them.
 user_pref("browser.pagethumbnails.capturing_disabled", true);
 
 // Indicate that the download panel has been shown once so that whichever
 // download test runs first doesn't show the popup inconsistently.
 user_pref("browser.download.panel.shown", true);
 
+// Assume the about:newtab page's intro panels have been shown to not depend on
+// which test runs first and happens to open about:newtab
+user_pref("browser.newtabpage.introShown", true);
+
 // prefs for firefox metro.
 // Disable first-tun tab
 user_pref("browser.firstrun.count", 0);
 
 // Tell the PBackground infrastructure to run a test at startup.
 user_pref("pbackground.testing", true);
 
 // Enable webapps testing mode, which bypasses native installation.
@@ -214,10 +218,14 @@ user_pref("browser.translation.bing.auth
 user_pref("browser.translation.bing.translateArrayURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
 
 // Make sure we don't try to load snippets from the network.
 user_pref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
 
 // Enable debug logging in the mozApps implementation.
 user_pref("dom.mozApps.debug", true);
 
+// Don't fetch or send directory tiles data from real servers
+user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
+user_pref("browser.newtabpage.directory.ping", "");
+
 // Enable Loop
 user_pref("loop.enabled", true);
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -1,16 +1,16 @@
 {
     "talos.zip": {
-        "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.dea55d0aff4d.zip",
+        "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.49b74c08dad4.zip",
         "path": ""
     },
     "global": {
         "talos_repo": "https://hg.mozilla.org/build/talos",
-        "talos_revision": "dea55d0aff4d"
+        "talos_revision": "49b74c08dad4"
     },
     "suites": {
         "chromez": {
             "tests": ["tresize", "tcanvasmark"]
         },
         "dirtypaint": {
             "tests": ["tspaint_places_generated_med", "tspaint_places_generated_max"],
             "talos_addons": [
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -4229,94 +4229,16 @@
     "description": "Number of times about:newtab was shown from opening a new tab or window."
   },
   "NEWTAB_PAGE_SITE_CLICKED": {
     "expires_in_version": "35",
     "kind": "enumerated",
     "n_values": 10,
     "description": "Track click count on about:newtab tiles per index (0-8). For non-default row or column configurations all clicks into the '9' bucket."
   },
-  "NEWTAB_PAGE_DIRECTORY_LINK0_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #0 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK1_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #1 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK2_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #2 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK3_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #3 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK4_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #4 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK5_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #5 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK6_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #6 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK7_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #7 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_LINK8_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 10,
-    "description": "Track impression count of directory link #8 per index (0-8). Grid configurations showing 9+ tiles fall into the '9' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_AFFILIATE_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 11,
-    "description": "Number of affiliate directory links shown on about:newtab. For non-default row or column configurations, extra links fall into the '10' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_ORGANIC_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 11,
-    "description": "Number of organic directory links shown on about:newtab. For non-default row or column configurations, extra links fall into the '10' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_SPONSORED_SHOWN": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 11,
-    "description": "Number of sponsored directory links shown on about:newtab. For non-default row or column configurations, extra links fall into the '10' bucket."
-  },
-  "NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED": {
-    "expires_in_version": "35",
-    "kind": "enumerated",
-    "n_values": 3,
-    "description": "Track click count on about:newtab directory links per type (sponsored, affiliate, organic)."
-  },
   "PANORAMA_INITIALIZATION_TIME_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 15,
     "extended_statistics_ok": true,
     "description": "Time it takes to initialize Panorama (ms)"
   },
--- a/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_privacy.js
@@ -31,40 +31,39 @@ function runTests() {
     {scheme: "https", cacheControl: "no-store", diskCacheSSL: true},
 
     // Don't capture HTTPS pages by default.
     {scheme: "https", cacheControl: null, diskCacheSSL: false},
     {scheme: "https", cacheControl: "public", diskCacheSSL: false},
     {scheme: "https", cacheControl: "private", diskCacheSSL: false}
   ];
 
-  let urls = positive.map((combi) => {
-    let url = combi.scheme + URL;
-    if (combi.cacheControl)
-      url += "?" + combi.cacheControl;
-    return url;
-  });
-  yield addVisitsAndRepopulateNewTabLinks(urls, next);
-
   yield checkCombinations(positive, true);
   yield checkCombinations(negative, false);
 }
 
 function checkCombinations(aCombinations, aResult) {
   let combi = aCombinations.shift();
   if (!combi) {
     next();
     return;
   }
 
   let url = combi.scheme + URL;
   if (combi.cacheControl)
     url += "?" + combi.cacheControl;
   Services.prefs.setBoolPref(PREF_DISK_CACHE_SSL, combi.diskCacheSSL);
 
+  // Add the test page as a top link so it has a chance to be thumbnailed
+  addVisitsAndRepopulateNewTabLinks(url, _ => {
+    testCombination(combi, url, aCombinations, aResult);
+  });
+}
+
+function testCombination(combi, url, aCombinations, aResult) {
   let tab = gBrowser.selectedTab = gBrowser.addTab(url);
   let browser = gBrowser.selectedBrowser;
 
   whenLoaded(browser, function () {
     let msg = JSON.stringify(combi) + " == " + aResult;
     is(gBrowserThumbnails._shouldCapture(browser), aResult, msg);
     gBrowser.removeTab(tab);
 
deleted file mode 100644
--- a/toolkit/content/directoryLinks.json
+++ /dev/null
@@ -1,74 +0,0 @@
-{
-  "en-US": [
-    {
-      "url": "https://www.facebook.com/",
-      "bgColor": "#3a5898",
-      "type": "organic",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFYAAABWCAQAAAD%2FX6l8AAABeElEQVR4Ae3WMUsbcRjH8Yc7lARBFMHJNxDEwSWTL0CJg4OQvAK5w5fg7CRCHAQXR0G8wUEXZ8GhHLnMTTp27dDQcoHabzfpkEspqPn%2F8Pl912f4jI%2BZEZFSUBLqSgpSIsyIyVBYRmwkqCwxeqisZ4xRWWkIbfZYxzrWsY79xTdGYWOHdGnToI69tMQyazTYYo%2BzMLDPXNHE%2FlErBGzOJoYpYM%2BZwzSwxximgb3EVLCfWdDB7mAq2CdMB9vRwf6groO9w3SwR1NZNTpc8MAn8r8azAq7O4W6zpewXsSNSuo8QwgLu1qJ3YfQsIuV2JPwsFbZtRL23rH%2FMcc61rGOHZFPrBrbJa%2Fo51tjH7FX66sOtsZvHWwDdLAtJeyhEvZUCXurhO0rYb%2FrYFdAB9tUwrbfAzvgYGLVrO2J9zf%2BIjrWsY51rGMd61jHOtaxjnWsYx3rWMc69qNgxzLY0ihksIWRyGATIyaTwGbEhhGR0qcMFlvSJyXC%2FgBgpRmayCXO%2FwAAAABJRU5ErkJggg%3D%3D",
-      "title": "Facebook"
-    },
-    {
-      "url": "https://www.youtube.com/",
-      "bgColor": "#e5523f",
-      "type": "organic",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAABWCAQAAADMSHQwAAAJE0lEQVR4Ae3da3Bc9X344UdHK%2Bti3Wzj24SUTIDiEmyCgbou2MamNdBOWqD0ktKGwHggkwwt0CT%2FtjFpGTtDym0SOpMWUq4pDcPlb6xSxRjsgnEKOKXUt5AEY9KAa4MtW5IlIdvSqnlz5nd2dVaWRFXtin3OmzNzRnrxma%2B%2B58zO7JGysrKysrIPuwolaaBGjUa1GtRoVKlZpTpNjpqq1hENmvXrU68BUKFJlexxWrTrAxxxSCSjW7uMYw6qcEQ72vU57H3dunU6otswVAwoEZ8wzyfNdoJpGjWqV6vK%2BOrXo9chXTrsc8B2r9tcuqEbXe3TFigNb3rSvd4svdDX%2BWuzlJZ%2B3%2FDFUgod%2Ba7fU5pe8zveEhMpXpHNJZuZs7zmE6UR%2BmkLlbImG9UXf%2BirXaLUzfBAsYee5BsmgissLO7Q12o0MXy1uEN%2FwURxsdnFG3quOSaO34WMGzQJXveYfAtcLKj2t%2FYaW5dJ6nBAjeHolzFLZPje1inW5ERj4VJ3k3GVT0p6QlauW1wk6Q5j7UJJX3P7MEP3afKGKYbvOt9LBFljLCzUqDPjFmsknWOLpErzJd3noLFV7xxJh9FreNoMKDY1ftlzkWd0S1oq19mmS2ox1s5QJ6nW8NWrMBLVgmpjZT6R962XtEyuCyR1e85Ym2OimUOE%2Fy%2FpfPVDhH9Gj7F2oolmNhG%2Bp09Q5zxBnfMlrTX2Zsr1ruHr0q%2F4zCKDNpssEyz1jNhCkwVZrcbeLLku1CPotV42eVWDoEaN4jNdlAEtlgmWFdzQmxwYh4m%2BxjWSavUKntCs2DU7IUpZCGebUeAZZI2xx1RD2W9A0ruK32RTIvBTrwoiSwBTLZD0lLFXpclQsnJllYKpUepN7kLAYhnBa35m7E3RbOJpjlJndWnqhl4rV%2BTXfMmd7nCbFc6SbpGLxMcSwXwXJY46sXr1xttej7nVg94wtB9Z6wEP%2Bxf%2FbWhNGcB2u5wi9os%2BbjeWDrE4bvQFJ0t60Tc9Kd8%2FaxLrVSt2t%2FMEv%2BRHgFqjt88iWbFf9R2x5d4Um22zNAOgw83u1w0iN7hTuns94GWxOhf4nE8ppDkjttafCZba7STzBLttFau1zmL5FlnkHp%2BTqy0R%2BoCgQ1K%2FWLPRO2KXoFHwqoNi70hXgS0ut0cs6y49%2Fk6%2BXa60RVKPVq2udv%2FxVgdrJS3DEkktghcslu46D8vVP4xzBsQmG71KkWCaYIZgpnRzvGGBPXL9vVcGZT7bFmkeKNilIRLb7D3BYswvsKG%2F7lyF%2FbHf90E0GC%2FrLJHmLklZy3Uq5EXXSVMdiQ1oFZzoNKcL2rwImOX%2FGdo3fRC1xssr9kqzUZ9glbcM5V7%2FYbApkaBF0tedIWjVL57YXN%2F3kB2SZrrE6A0YT2f6qlUukHTADrH33SbpSqusskDSrQariATP6hVcanbqE8dvSPpz5%2FusuR6UdJnRm2q81Ljdf7rFSv9qiaQfi63TI%2Fi2f7TSSi%2B7QNCqS77%2BSNBlg3ThM%2BsaZwp2%2BhvANToF841exnj5TV8Uu0HSO2IvCD5iRWLggh4%2FkK8xktQi3QZdgJNMEWwUG7BFcKIao5U1XuoEH5e0T2yb4DTBSZJ2yjc5ktQq3VqxWZK6C5xPN1PpOSrIpF4ZsEe6AUlvD706eMcrBhvQIlYjqUqQEUSqlLY%2BaXodFmQF%2FZIOGyQqOLvBi94T6zc8WRNJG6Bfv%2BHoOH7opw3W4sMuC%2BjSLqgRzJbUd%2FzQ271ZcEOXv7s2ICs4YLttttpqu%2BeHrIpMStabJG2zS6wcvELw7%2BYZtki%2Bp%2BR6nLIPLpLv%2B%2FZL2qjsg6uI5MvqlXTMSJUdla8iI19GpaRJ%2Fm9llL6sfO0ZxaZTqTjHfdI1y%2Fd%2BOfTonWCe4arOGK3y6ug1fN2RYtNuIurIFN37Pd5XvLL6pOtwR%2BLaGa6U60jxhe4q4qeIGg06xQYEB60W%2FPqg0IciYyOaUDfDqYBakwU1gmrVgtPl64qMTFZSn6BfUnbUk3vYMf%2F7JhmZKkm1gIyMoELQ5VjBFtAeGZkDkioLnHc66HjOkK5Lh9Hql5WuzfFVFrxTHANU%2BYhgvyCSFUyRry0yMrt1CxYK5gv2aJemT%2Bzb5krXqdNo1ZmcOsUr7Bnh%2FeeHkhpTl8J2PWL7JM2QryMyMp1eF5zntwArzRbsLDAnM1SBr1mhkAHtRqtWneAw4Gb3GY5Wj4h9S9IssSWCo%2F5C7K%2BG%2FGvtH%2FnqYJ2kte70WQ9YJalVcFTQ4Gl%2F4nl%2FaSj7jVaVjOBlqz3qYqsNzyF%2FZLn7%2FfxnvCTpRLFLVQnu9mmPeshSGwXTLJKr3f6MkfoHKyXdJN8xjwv2miNYbjmMUehqM%2BwV63OzkXrWswY7XazR9e4SPOpR%2BW5UKdchXZGR%2Bi%2B3G9r1ugQ%2FkWa7zQrZa%2FROk%2BYzfsXxnW6GNCc7VbDadEOZ7cvyvUdk5L7sBYV9xz2SWgx2wFIvKWSP0bvcYH%2FgIZMc32KbpPlTSbWeU6WQSdanXN2XHrpBUqXBllkj3b0%2BI1ern8i1ywJtzpUUDTv0gCOSspKucLJc1%2Foufiw4LOgRvOY0%2F2aWXOe5Xq55XnKKNB%2Bz2RkG%2BykZg3WoE%2FQZLOtyn3ejUyRtt9pjBrvEOqeK3eMmPejUXyDXVkOpUOeIIJJUab3LbAOc6iuuAtMcEGsS1KsUm4yFtrrZg44CrvCIwc62za3%2ByZuCj7rSV9RLsyP9BYOzRYL9jkoX%2BZSF5ogcs8MmGxRS5SpnqbFLi52AaWoE7%2BoTvOVjCsnaK2mmjHyP2yEyz2%2BLUn5%2FpVlibXrFqp0A%2BJkN9pjsfOcqrN%2BrXnVIv6nOdI4ahZzu9RC6mDziD00c7%2FgokWK03kSyASLFqEWfieOJ4g19yFMmijatEClOd5ooviVbzKFftslEcNRtxf8S2IngWl3FHnq3q5S6%2BzxEsYfmYdeVeOYVlEJo7rXETqVowJeskBApbpvM9XnblZYnzXVHaf4fliUuMdd801QpXh1%2B4Hlr%2FBBKL3RQa7pf%2BPkxwzTT1GtSpVmlBtUiTbKq1RsrXY45phOdjurQp0Ondl0O6vaut%2B3RIRiP0GWRsg956HLosnLocuiy%2FwHzbGFqhjoa6QAAAABJRU5ErkJggg%3D%3D",
-      "title": "YouTube"
-    },
-    {
-      "url": "http://www.wired.com/?mbid=fftile",
-      "bgColor": "#000000",
-      "type": "sponsored",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAABWCAYAAAA9pePPAAAd%2FUlEQVR42u2dCZRV1ZWGQSMmMR2NSpYmGjWJHXBARAUnHFBQEREnFARxAiJxRDDOkigoosRZo3HAAYRyZhDHKKCJOHZ6JZ10J%2BnOYnV3ku4ktumE9%2B7Y%2F3dyf3JyV7GQoaTBqrX2Oveee6veq%2F3df%2B999nkFHdaVr7IsN2CcNm1at2efffYkz3%2F%2F%2B9%2F%2FRIf2r7X3ZQD33Xdfn5dffjlbuHDhVe1w%2Fn8oZkPGG264oe8LL7xQvvfeu6Xg3Obrb7311kYd2r%2FWHpgbb7xp4IsvvlgsWrSofPvtt0up5YkpU6Zs0g5nLYP5zne%2Bc%2Fwrr7xSvv7669mrr77aFAyUs%2FCHP%2FzhNg5rurdjh%2FavjxbM1KlTTwKMFFMITKrRcH72xhtv7NYOZy2BmTx58slyPCBygclee%2B21XOeNd955B1i%2FfeKJJ%2Fq4ipswYcIGHdq%2FPhow11xzzXBVZaXUkQFGMHIgaWz84Ac%2FIOf8WYoa4u%2BbNWvWhh3av9oezKRJk4YLSCkI2YIFCwIYjQGOgDSUe1BT%2Bfzzz1%2FYXk5%2FxGAIZQLCWsaKsRWC1KRiA9C8efMmt8P56HLMcByv3BJCmRWj4zBWRUEiK958881SpfUD%2BrZO7eV0G4PRmmUYVZkgBDA6zgUpQHnppZd8DJyUe6q1znx9bd4Opw3BTJw4cRjJX8oASqZjYBDGABMAWUFcJ7QBR92CN5988snt28vpNlv534hiAhgnf%2BWT3CHN6tE9zGHc01i8eDFh7eeyvVytfSzLaZ7ItlrHyLmAsSJyqYZjgADJuQZIKAc1MddQiV3q3t9JWUf4Pco%2BlnD4pTtiazCUDaUqA4DGzGELA46czrEVxBzm4waFA%2Fln7ty5wz92a50qRGxcm97whBNO2HANlMsj5FiqsgQAGg0hmCHEwBYuQDFBWQVwpCCUQ94ZH5fTH4vwNXv27Mn65fvX7wHOQQcdhBM6riKYATiVdQqtGI2FzI4PtnDRMhC54ekeqwc4zSocltp0m%2BrX%2BO53v7vReg9GoeI5ntDp06cP0%2BmY%2Fv37D7%2FllltiFXUcNWrURh8WUHyf%2BmHjgSPnBzhVeewwxuiwRn5pTVHcn3Cdn6Fq7UH92I0MZ70Go5bIjHfffZdwsVSh7Y9bb711eeCBB745dOjQ88aMGbNt%2FH0oiPC3Mj9fTzohLVW7vyQ8Oa9YNVHeaXVOQAvNpc8991xKaJszZ86zUvmWhrM%2Bg2nhadSYkRO0j5J13rJzuddee5XHHHPMksGDB98sSD3ruYlQV1dR%2FR6%2Fjn7%2BkXrq%2F8AKX6%2FRsPNtVgjzKMfzBoVipOxMrZuE96jjd6Ser6xva536E91SNRMbiucpFdTdd9%2Fd2GKLLdLtttuuPPTQQ8sjjjjiz8cdd9zsk08%2B%2Bfhx48Zt8mFVxLwrKf3MvaTKX7AnI2c3Wdu4EnN%2BYTQ0vZdl5%2BoG5FJMLjDZ008%2F3dB7LmfOnPmv11577R7xWme9AqNftoU2vJ7KRGEil%2FNCk%2FF73%2FtetttuuzV1S7L33nsT3sqBRw0sBeg9qWX88OHDd4h%2FJnloOdVcR1dSd9xxx5cF5B1W%2BOxq8hCQ34CAagyE9wAIPSyMWIDD%2B5NScoFoSDXlQw899N%2F333%2F%2F4f6dgLNegpHlGE8pjpoxY0YuZ%2Be6Ld1zzz2T3XffvehzcJ9y0KBB5fHHH%2F9r2V3Dhg3rXa%2Fm6mEu6n1REGwhIM%2BT1%2FQaKfmDCgwYQHERAAwpI587Z%2B4yMMot%2BeOPP44FODovH3vssaZsRLzWWS%2FA6ClsURihakqWQXl1AU9reIKfeuqpfODAgcDJlXeybt26Nbt3754cfPDB5CAUlCgPPa88dOpZZ531uToknuI6HIXGjeXUB3kgqjVLwuh840YnMJ555hneAzmGMTwsCmP5o48%2BinoaAl1qrtTHpcbFa531QjEkfz2VSQgh858jZITYb1A458wzzwxwpJpcIS4XoKRnz56NQw45pBS4UnDKIUOG%2FFR56GqFua7LWRPZaX7tG3go6KmRdwQGSC6leTgAEl5fAACCUvKWWS35ww8%2FnOtDhYXGpuZLAOl4crzWWecVUz25CaGE8CGHAQZzEg5z48aPA04AI9XkXbt2BVQqQE05vgDQSSedVJ544onva5wuUIdLMZ3i1wVQ3NLXw3CxYBHWANSgPCac8bq85uxnZhPCApBHHnkkKGbWzFmACaY8Uzwy%2FZFEcAopqnxQX7q3k%2BGss2CklBbWGOQYnlBUg0pwDOaQwojJ0QHOzjvvHNSz6667oh5gpfvss0%2FSp0%2Bf9KijjkI9waSg1wTqbKnmi7WK7ZNRXjhVr53wPvSaDXIK7wMwziuPtbSgGEAAiDGA0cI4nzF9RqHzVOEtoyjQ3Pzzzz9%2FM8NZJ8HI2SgmhDKZQwgwAhjD4pwRM5wddtgh79GjR77TTjvlu%2ByySwC1xx575Pvuu2966CGHNgcMGFCgIAAJzK8U7m4RpL153XqylhJY67xP%2B0Wv0QAK74dQRhhDMcBQLslViQW79957c5XhAdQDDzzAXCYoiXJPqfnFF198sfd13FpatxSDM%2BR4V2XB%2BbF6DIr846f51ltvzVWp5Z07dwaG4Vg94dp%2B%2B%2B2X9evXryEFpSoSCHMAWiqbN0RfCn1%2FF7%2BvlpaW3nr9X1dbzY24EiOEKUrlKuMDjHvuuSfYbbfdlt95552AARp5J5M1pB7C2s%2F1fXv6AeD3XqfAkIDnPzs%2F4SnF6VaKIWAAYx5nkXtoNvLUAmHTTTcFBDknADKcbrt2o5IrtAbK%2Bvbt2zz88MOTY489lhxk%2B7FK7ssFrYvfj%2FLL3%2Bt1%2F4U9Gb2HJit%2BlcOEseD8B%2B6XaeTB0LoogLnpppvyu%2B66C2C8J64DsFFVa79TOOvnBW9brHU6tlUoC7H9hRcTw7ABwseAYXRRQIgBGI444IADCG2ENVSDkYMYKRIIcYS3%2FMADDszVSUgBRAkuKMBBRb%2FX8UMKdUdVnxfYREp5mfelMVNoygDDa6EYQpgarfntt98e4OhTn7k25gKgm2%2B%2BmXlUxLWGvqcUqKbmh7bJWsf9KcY13JKZRSgj%2BUbtD4cyt0J87oLAC0DOQwLWQtNwKAhQDyPm%2FIN6ABRAKsRlhx12WHLkkUcmACIPSTmMb0l9Z3TX12x96bVZRGaoACCEK5yPUlCNensBitozubazgcQx1wHDPcAh55RSzoWRP9f8WocfCqQ1VC4%2FTiiTg5s43hYrxUpyuwQgLg44Bii54IwzzqjWOt0B4ryDcU54A1wIe2rz5KrgcsHJtdWQKA81WLCSh7RYZW30U83NkXP%2FLDC0X8gd5BUcHuBIWbk%2B8RnAXH%2F99eFc%2B0AYwGyFgDQFtcQEasoaXYiqeThBbfiuawJQ2aHsCBwvMLWuIJQtRRk4PIbB6JDmXPM392mOsMYxK3P1zICDYgARRgNyceAcpPVPrhI7Vw4KgKSeVJVc8%2Bijj85QEK2fQccMyr75zW8SnghjKIZcQigLQAzi6quvDnbdddeF829961v5t7%2F9bYChnkL3J4z6%2FlLH9%2Bs9AmX1y%2BnLLr2sPPfcc%2F9w9tlnT9Mv3w8otdaHS8KV2ednB7OHHPsT4jnJVg7PgIESgKGiwGEsLgwc0lxKY8wHOJdddlmufZ18xx13jOFgKCc2QlwAtP%2F%2B%2B4cQp04CCsqqQiE79phjOc8V5vLRo0fnV111Va4%2FigIMqsHxYe6KK64ATICizxswUtYT1jhmBE6q78kIa8pH8wR689WGIyjNCy%2B8sFRtXgpOcc455yyQjf7617%2F%2B%2Bdb6UysDR%2BHgiwKxiEqIspn2v6AUKAFnY7FiMKvIQNz9FWjOcYaVE5zfpUsXwFAUYA5v7iIQ4gBEiR0UpCou739E%2F1zhLEBRmENVnId8Nn78eMKY1QEUQKAS5sK1yy%2B%2FHGCMwEJNQM1QT5Vz3lKRsHprHa1k8%2FPOOy%2FR2BCYUlBKxm984xu%2FlF2vp2k37ltZFbk9olDxGTn%2BafJNpQoAFeQPt2msGObixWc8AsdrDxxGKb3tl7bF%2BVaOAdncPWA9RIEQAPXu3TtXoxRAFAq5FJQrzOUKc8EU5qjq8pEjR%2BZ6YAETAFxyySUBkB7gnBDICERUfOmll1pFmXJPQ%2FmKnPNzzfVY5bUOYAQjF4RcXdxUY1M5J5eSygsuuABQ%2Fyv1tOiNDlA%2F6pORKla4Z284KE0quHvRXzbOSgFpSh2FVQMMA0A1nvPolTpwtKGVs%2F7gKQXKZp%2FbDOUYjCHZDMfltSEBiPBGoQAgwhqAMAMKduqppwIIEAHORRddxAicMK8czRxwUBUGoIbyDru2%2FyUlHbZK%2BzpSCxACHAFhxDKpJtGIlYJXCg5KekPn55522mnb1ouF%2Bou2VqFo3TBB54AppZQmynG4isMaoyFxzSt14LChNXPWTEYWfyR2bx0Qxqwar3l87vBmQFRwLrENiZAWjLUQUAhzQOJcvTlUhK%2BAZOVggAEQOQlDPYS4AEe5JxGclf8MGyB4MUyOD3DOkhkUKpIymgJTkIuAJFUt0dytgtqrlTWRZdsqHEEZg2rkdPpobOficIeyENqYc%2BnMNbdtgEEoU4uFfRPUw%2BKQxaW3Dv4Cp2ulnAgSqomPubfHHj0C0F69egEIC%2BrRvg4WoKtQABI5KIDSYjU%2F5ZRTcvkj90Nt5cg3KAcjzBUKgw0VA6XAlFLS2JVZ65D8cX4wha7wYkE5giQAVCxc4xgVNQBV5SJUtFRP0VyNg7VXssnyVFR%2FUuTs4%2BT4D6oPazTihabXMYaCGRQhDAPMTIGhx8U5o9YoAY6V8rWvfQ04ccegXiCgHozwBhzWQM5BKMghDsUAiTAHHEAFJWltlCt6BEhjx47FR%2FgQSLZCOaip0FaiHlV6N3zofR0rRT%2BU0arxMWB4Yc8VhLlKQYms1PVgmvuR7GKB%2B%2FLy9uwB5YpNq%2Fr9pZh%2FpwMtpwc4DlsvvRw6AM4tKAOl0A1mz4SOAM1HusLBBB3l8B6B4zVNXLHVFYTFWwte%2FwCINRCAaPVQJBDegAQclAOoAMbjoKMHUdHhJ4EZi3LwFSNqAk4i9RQKvaWqtYc%2F1GfYIOzkb0AxHM%2FJwb5mBWGpoCRSDUpjPYSKfqu5uzV3YFwYAMclt0ObHN1Vzv8JFZtGr3UCHEA5rLGGAQzqIITRjveuI%2BcohtYK8zgHOEDB8YZTN%2BefuPcGHBcIBhRXcVYQlZwBeXQ1pwWsVYSvAMNIGkgFh%2FUOYW2%2BlLeF4Swv%2BQPGZhCAwTyHOefwolYRI6rKqOZ0LahIxygo1bbxi6pqTheQzethTk7fmGNB2FpQFlRhLcVY68iowlwmk1%2BAgFJQBxBQDoDY2PK%2BCe35EEa233579nWsnLhqs8WguC8uEuIKjjUQ%2BQcFUcWR04ADJBtgHPKCitREzUeMGMED7QcZSHQbmloLlZMmTnxbkHaMck7HVkOZgbgYYHTuicA4tBlI2LNHMYzMAUdvpkF40%2F0Oc%2F%2Bsp2iS8tAu8Wsbju77jPbfH1drPnR9BSuVUgqZ9%2BVRS4Ax7cFpbjqG3paeOFoqbskDhmPWHqFD8IUvfgGnO%2BcAKYYSQ3JjNA5vwHGI80IVBaEeQpwLBZfbLhQ4BhQ5CUj0%2B4CEHzPlo6ZUU2rB%2BisVB71a%2FQwbEBy2gGEwQLLhcJzPDzYcjrGRo0ZyHTB%2BOgwv1fc0NVdUC9by9NNP%2F0BzMwVpgJ7oT9bE20nJ%2Fz460lXST6SWAigoJYStGY%2BiChqOwKFcBobPAcIx8xyzGg9P%2FlZbbcX4VzBdAgxbfWEaA%2BLYG3Sox22eOMS51LZ6DIbRKnKoIxfhH%2FzaoNuiB%2Bh9qejIOOSHAzu%2BWlSSK1yNOadwPc49BlRXkBVmiFzDMp2jIkA5zJWCs1hvcqze8PYxHSnmJkpp1jrKLYmskFGJEbJQCnsiAOAYoxGJcoBGI5JjrlMY0JAkf7AjiqMNB4sXpK1CqjdI4yqOCo4cBCRaPYS3vof2zbX1HcJcKLcrSIDBDClaFzW0BqIdlgrOyDjUA8Z5JEBxyRyX0V7TxHnGqkIpDmPAMCSkG88DS3OpVNPUWLii0%2FF%2FaO5u5aK%2BUWk9lv0S4CiUpYJFLnGYslqC829XOLujMuYYvaHF3gmQaNvjoC233JKnPyjGIa1uhmQwYdw1wLF5gQogRhcJ5CEgoSBXcxigXNFZRQ57hRTS1O9eKryV6ihcWW%2FJFDIDCBYBc%2FKyk%2BuJf1kIk4MNw%2FkHGD7GDCiTYho6ToGj7y0raK9JRaP1tj6jp%2F00FQB%2FVDgrVZFlOqf6QiE4mw4wnWD2RdjE4pxj7z4CBlBscjm8hcXhpz%2F9aRyLgxM5PROIwkBqsJizxSW2uweYITnEYYQ4igTgYN4fonkahzofF2xJaCGLD0oVDHewdUCOSaQQ%2BmOZC4Ao5%2Fic4zjpY8AwyDicxVCAhTm8YZ4rZBkK0r2JrBw1elSp%2B3hzS%2FTkTVKzcp6glGwBU4F5z4StXvLHFJkc711GWvUYkLzLCDTuByYggUPFVqjrXEo9OLsBoBgIgDxaPTEcFOTcg8WL1LBZ1%2BtvFqruagOCEGfj3IAKdlwZ2W3VtVc60GbBqLWlniZrk7OWLSiXtWWwGIATPhBcHPgegzMI7vOIGUwA53PBSKWiRJaSgwh1SpY8NBlPu8AQypxTcD4OJ0zR0KQ3BSC34MktXOMeoAEFWKiKIqSxzbbbLFFLJvxZiJxcyJqyFAB1SPXiwOdxeBNo4AQLbZ6evajgMMC4k2BzqIsXrwX7RRpT1INixsmZr9FJJhHpGKeQrBPaMC4EHN5wsGEZSqwkwzIIG%2BCsHCo5oGCGIyB0chkzQUp0L8ZePZUMq2gcjwocpnAyINgjoXnozq63gpn31jCwuD%2FT9xcs8rTYI2QMkyMXKAyVKEhhqQSOFJFIGYZUV1C9tePCwOb8461umqWYVeQwF1d0Lhjc4W528Iex5egj5fCZUs4fWcULApZX7ZeU41gZcTgDgi0GcsaZkfNRxmlBJTEszNCAwjyAOGfMuJ%2BmISGISoafT9t94jUTcXhQhpwMFEBgHLOOcZfX28HAwTKFN1ojqSCdw%2B%2BvcHSw4EzX%2BHs5tMTk7KAiOT%2BxSup5yMqJQXlxiqEizFUctt%2B%2BYR1kQFYScAIoQ%2BpQb9fL0d0EYLLGX0RtFkJdQ%2BASWZz4PcbqiUtlnO58g%2BNtzHk%2BzkWG4vt8TKjDUA%2BAsFAZeh%2FEYK688krAuTUPHMB4BCTKSRXa%2FCGKa6M%2FltpFT%2FxVcuQ%2FEeI0koeA1JQl3lawGY7nDcZmBcVrIC9SUZBzD6V2DIm58Lkyt%2Btr2wGdBYAt5lfl4IJyDki0XnRO6yUzCCvJkMJxNRpMPacEIKcHWAaCMoLzDcNAPK%2FOQQXoFO7nHED8LEp8QAAKOByjGixuxQMPFRHiEsKa8k6puYfjVryAbCpnDtc4X5YASA4mzCVVLsqsIqvHiqmvfVwkGJBzkEOcKznCHIBYBwGo3joLn5qvQfqEHNVPCpomKL8HDiv5KsmHhaMsDm0OTfV8Y3X4HCc7r2D%2BHkB4jmPfY4Cohu81HBqHAOIe1Ixi%2FEEKtn6BZGgGxbVC%2BShVWEuVr1DOXMHZrO4QhaLecuidGn9jQJUiQh6qdwswz0UldpyL%2FFErf5oHBTm0YUCqgWllbz%2BekyO6yCbIsT8jB2GEuQpQs0ruhY7tZJfJVo%2Fh4FSbVcJ9vuZ5zg2Ie6wcq8WKAhZ7MphX1W69s8sIIJqIVhbGXHHdddemylWJKr1S1dsiqWqrquv%2BKUZ%2FyYk7yJnj5dh%2FoFAIYa57CHNBRZrPZIXDWgzLUKykepPUu6kAAkoMZqUAacW6mZw1Qo5%2FSVBSNs2o5mi9yKGs7LMA48wQxurKsRqcW%2BKqzeZrNsOzSgARA%2BQ8BsYmlju8qMhdDcKZt4LJUaiL3JOpimsS1qSif9Tv26VqjXSS8%2F6mLS%2FHbiLHDhaEOXLuUuchnaeyhsMcEAzJYDzGVZyLBMaee%2FUEUFDR6n6UFhUdJCfeK6f%2FBgWxepWjM4Foakzj5B%2BrxnnEinKOiVXie%2Bx8jmN1%2BJqBGJhHAHEfkDjntagugYShJkABifAmOA2FNHYal6ik3tdd3yq0e1z2pQXl3nrab5VTlwDIi1YZxUIadwqsGM955P4YkLe6V%2FnzznUVyQE7ylGXyH6EeshDgoMlrOxZn1A%2BA8lA7FirwudYXIUBA2czGoxDls85dq7xse%2FjGAOQwx%2BvX7WjMIc8FNRQ5VYKzP8otA1o5RMuG%2FjPC6Ni4Uty6vmCtBg4Gg0pqSwDhMObLS4QABOrZ7U%2FkM7Wca2a%2B6ycf6IcPQ8oBqT7WJM0ZakcX0Shympy0vd8HZbVwbwVUkEbhuOBwJyB2gzJEK0kzqkM3X4ixLF3jzWknlKWqkg4ZTmfcOlImGP0hELQp%2FT0H635J2R%2FMiAUgYpkKV2GuHKrrmE%2BD7bG%2FpSjtY8wyYH7CNIdctR%2FUiRgQKGBqWupQxwj5nDm%2FOPQ5nkrxyP3GcIyKKeMiENaXTXM1VXk7WDCHMoh9xSypio4OgRsBY%2BLW%2FKtVbIoKZ4UlN1lUwXoVwBiXSQloKIGKrJiYjhWEsdr%2FG9tvLcfT6r3s52cOV4w3haAUCjomD2ZpiyhWHD%2BiGDYsa7EPO8xVoDvY87hzNfigsAGHK7HIc7g3BGn496kTaXioFT5PSX%2BgEnt917uelBrnK3l6LMF53VBAhAKCotWAGmkmjMcw2oVTJupSKvaTQTkONmTcuxSFEQ3mfyj80ROpZFpx7fWBbBCNAZwBmCodrid7nMrxdCA4euYWz5Ucuw0GlJBpYmC2ArWwvU%2Bh64anBWuB7%2F61a9uLOf3V2ibIfsASBhlNoA0phrjUNb2X7xJINXC3J5y9lTZErrJGCqS45uypApZhasx55q4G2CLQxdm1cQmJ%2BFsX%2BMYGJ43KB9zjQ%2Bdc73Qz07JkargStlTaud81kuJD%2FO7t1Is7CqbpND2S41uoGIAStoglK14TVR%2Fk3LCF%2FTEnitnvyGzgko5sAkkO1vHNkNyPrIaDAjzOaPNkOrKQSUuq51%2FmAOO5sLIPkmq6wmbegpxixTitongrNLvLhCdBWWkwtxCASrocgMIOB8dlRV%2FlLaTHDpQqpkl539AJUc%2B0nEqBwMoc2hz6AJSnF8MwuqxxaCwIREAQ5ABxuc2A%2FKY6X3zXkq97o%2B10P7KysBxuc39jLE%2FBKafID0oMKHD3WEtf3VcTuunuxw9WfYLgWDRioKwBAOSqy%2FA1KowLO6hGUI9t3jOaomPsTo4jsk%2FqSzRfaXGMc43q%2FhHya2paCeBuXotMlnxmkifyfq8HDBKMBYR5gxITg8qkiMzQSisFOcXw7Gj6xXYsJPDNZ9jDm8Oax6lriGxmsg5qAb1AGa088hq%2FvooZkVw1z4gFFT%2FeK2cdJicO12O%2FwM5CCVVib8hhwKKY8OIwXi0UuLjVhM%2FZpUAweEMM5jq37YZsybA1Ks5xrUGYFXXRFLOznLgNYLwMwPSMZCaVE8aYzgYjvV5HZSVVE%2F8NldmQCGMcZ4xAkbH5xpMW%2F3bC%2BucivSxny3luNPk2JdkBZAId8CRgxM5OLVqTjoxOD1e9XMcq8ThyqWyrwEDs1o4zlCNrgHmggrMx%2F6%2FQAmL1pqKNpAzDxKE%2BwThd8AhD2mukPPIQ4lBREqxGUgMCAjcFwMDiJWTYdW%2FvDG%2BCrXt%2F6tTvHCrO0RP8I5y6BWC8GPgqLoLYQ5AAkc111qlhbMdyrzy13GYx%2BoL0QzVDD7hBBRzcTuYFayJ4jkp47PD9SVHz5clqIjQg3qAJOdnsSJ8HAMDRAhhJwx2ePP1AIYcox7g5e1gPmSxUI%2F3cmJvOfFOOfbXqAjDwUCSk1OrxiOQIpVYTR6xEMoArXFCO5iVbP2wJqrN7SBnXqSQ9h7hDZOTsUQObpLQq7zkPBNDaTX5a7ymHcwqhrm6gvgj3sH6kjPnDB0ydCkKqp5%2BemENHWeAMRRgUDIbjBa9gEmrlf91%2FMz2%2F29zNcNc3YFyfC9BuFUwlkhJJc7WeSFDQanMKgGOy2bApQqFgJnSDmbNr4liQNsKzvly%2BmIdE%2BasogQjdEULTZJ%2FWoWyG9vBtIGKyENxh1uO%2FhT%2FGqDGx2V%2FQhWAqoA0ZSmqQU0VuKntYNoQUO0%2FeDCk7oJwg8Z%2FU%2BhymAPSUs03dNweytbmmkh%2Fs7IVzUrNL5KxFnKYYx1zdTuYtaCiGqROqsgOV655SPPvk4f4l2rbe2XR11reDmduJ0G6Uf%2FQQv%2B2Wsf8HxngpdS0NcAAAAAAAElFTkSuQmCC",
-      "title": "WIRED"
-    },
-    {
-      "url": "https://www.mozilla.org/en-US/lightbeam/?utm_source=directory-tiles&utm_medium=firefox-browser",
-      "bgColor": "#bfdff0",
-      "type": "affiliate",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAABWCAYAAABmQby7AAAh5ElEQVR42uxdB3hVxdaV97BgeZanPgUE6YRAqKEpEBAEVBCBkNybSkCqEIoURSUWVJoQBRHpCiq91wQSOqGHmh4SSCGNQPpJwv73unfGezwfEAj9%2B2d%2Fbs%2FNOTNz2po9a%2B%2FZc3hEiRIlSpQoUaJEiRIlSpQoUaJEiRIlSpQoUaJEiRIlSpQoUaJEiRIlSpQoUaJEiRIlSpQoUfL%2FTpz%2FbVElSh5u8fuXRY1%2FK1Hy0ImzzSJXqN69Ynl750q2Y8paK3mIxMnJr6z4WbaKg%2Bek1%2Bt5JFau655U2d7154oVncv9XUaJkocIzP%2Bq1qjfyprNhlH1xgOJf%2FN2AL1e131rxYotLKBu3Ljfo48oUfKgip9f0N%2BWuUZT3%2FW13%2FyEqjcbVlS96ZCiao6DixjQhTUcB1OV%2Bl5BCtRKHmwwB%2F0N5ifsWo3dWKftN1TrzU8La70x5mrNlqOoRvMRDG5fquE4RIPVrtLAJ%2FjFWl2fUaBW8sDJ7NmHLYBcdpqedv9seXDttpOpdttvCu2cvrpau814qt3qM4Cbar4xmmo2H0k1mg3Tarb4mKo26r%2FrJXunpxWolTww0k%2BA%2BfnnnZ%2BdsDFu7%2BAZh%2Bk%2FdSdodTv9QHbtJ5Fdu%2B%2FIjq11bacvqXbrL6hWq3EM7DHEgNZqtRxD1RoP2F%2B1qvOzCtRKHhjL3M172nNfro7cO%2FNAAfUatUF7qYU%2F1XtvFtl1%2FIns3p5Odh2mkN1bExnc3zKwv6Labb6wWGymI1pt3lZrMmhvpXrm5xWoldw%2FMB%2B2gnn16qDnFgbHH5y5P4%2BmbE3SOg5YSZU6zqP6HyyiOu%2FNozrvzqY6nX9mcP%2FI4BZWWwK79efEjqMGSlLd8aNDCtRK7h%2BYIaY%2FXpwVEHswIKKY%2FHekaBPXn6NmnkupZs%2FFVN9lCdXt8TvVZWDbd11A9u%2FNYWDPsgK7w1SrxW47ga21H3PscZodg7t606GHqzfs%2FZICtZJ7JocFmOnChf%2FO3HruyIpQjQJOZmo%2FBaXQhOURVMv0F9X3Wk4NPJaSg%2FlPquf6B9V1Xkx1u%2F9G9u8zsN%2BdIyy2P6gIOLa01podg7tGM98jDwCoyzjzbKaTk1NZP787M1WPdmSbaF%2FuvyttK7lFy%2FzSgld2nUo5HsiWefXhDG3nmUyaGZxKny06SRXdl5Nj%2FzXUsO8qatB7BYN7GTm4L6V6JgZ3LwuwYbGZivxKdTrNAA2BtUZUBE4jQM2W2vfk6%2Fber9wpUOOF42VD8eJLAvO16t%2FpZ3k3gadAfQtgppzUV3%2Fdfj40NJFo24lLWsDJSxTMgJ69K41Gzj5CL%2FusoRa%2Bm6jxoPXUeMBaavThamros5IaeC0HsK0WmymJfbdFTEPmwlpbHcf2kyUF0ew4IgJQV3dwq3gHQF2mNGWdnc1NnZ3du3IHKHf7oEZ7fV5wdXVt4uJi6u%2Fi4mJ3i22WkVbYeD9i%2FyvOzqY3e%2FUyD%2BVrrm7dr5LAbhyag3TdVv5YVMbpveeu0pHobC3odCYdjLpC2M7dlUIfTg%2BhKkO2UMvRAeQ4bAs5Dt1ETQZvoMYD11mA3YCBbbHYbn%2BxtV5is9bv%2FAIKYgF1bRuoQT%2FOVqvr%2BpoEdWnB3KOHyaFXL1M31i69erm9IffLrdESu7iYP3JxcSN3d2%2Fi8hu8vb2fKA1IZPmuXX2e4fZ2sZKnZx%2Fi7RDLc%2B2HeypVpywjRxreVmIgn3Z1dScPDx%2Fia3eztq18kBuC%2Bc%2B1e8rPCog%2FHZZGtC%2F8inb8XDbtDrtMYRdyGNCXaW5wMvWYuJ8cPgmiNz8LpmZjd1CzUQHUdMRWauK7mZp8tMFqsfuutlKRv6317wLUHA2Bw9h%2BitVSt2ZQtxmP2cWIyg4eVSSoSwMofuFzPDx6k9nsRWzJDvA4U0YeN5QFQJ7m8jFmsyfxNt%2FNzQsgeb80INF3HG7rCNridgHoL7ATFOhm6stz86jRoHt366ilA%2FSz3HYs2mVQ8zW7%2BihAX0ca95stH8pLw2YfOLbqZCGdjM%2FRWOn0%2BRw6FJ1FUcm5FHDqMs3ZkUhO34VQ86%2F305tf7aWW43dTi892UvNPbMCGxQYVadSPOTastSdba5MA9fsLAWpEQST9EJx6PKIfUZXq%2BVSVoC4FoGfB2ppMFpDu4V3XBTS2DLgDnp4%2BEnzQ5jYAlk5g6dEe1NnZ7TsJypKuvUuXfk9yh5rKGsojTCFGGQNgcX%2B70a7J5EEM%2Bj4qm%2FFaYBaW2XuQ3yuvjQsM9V2WQCsPX9IA5NiUPAuQz7B1vpCeT4EM6FmBCVRv4hFymnqUWk08TG9%2BG0JvfL2PWvrtsQIbFvtjUBGrtW7Ufy017LMKFATREERCdKD2t8ar236N6IclpFfNcUhc1fqeNW8O1Eara%2F4Z1tHV1YMw9F8L0HqAMddtBlAzUOIZiIOvNezLqIJeDe2VAaiwFaDbKAHNoPxWgk7fjjy%2FvvMwJ67FdS10grcYYVpgf%2BfOnR%2B3XYtplwA0KJLFQoMmybaxvdnIjvF%2BbrXOjSItsuzNOuvYZ2z3tsDc0NWvfNtJe0Kbzz1PnouitJUH0yk8MZeSLhVQAgP5XEo%2BJfPvoDOXacrmC1Rr%2BilqP%2FM0tfE%2FQa1%2FOEatJjOwvzsIYFst9jimIqMDrTRkyEZQEAHq5QC1jn78AkcR0Q%2BE9BCn5hnFcTyjODiucl0Pu5JBbeTEVkDjhRst9I1oghGg8iGXZFmNllx0qvVGQMtyxvo4F44J%2Fl%2Bbr%2FkK6ARvM6XTpxfeH6wD9Ic3Cu0Z95c0UkggGdsyjAAltmlsA%2FWvd%2B9QfX1jmVKDufXU%2FWc6L0mm1vMiNdfFUbT2SAZFJeVRZk4hpV7WKI31YqZGwQxov%2FXx5PBLOHWcG0Ftees08wy1%2BVEAexIDe8IBavmlsNZjtutAvQ6gttGPHgzqLvOt0Y8OP2BWEU4i1eIZxVpvjOXcj4EJlezd6khQlwLQu%2FWAvlHd7t3NlRmIjfHb%2BJI%2B%2BMDzv3Awub132EJ2gCU18lY%2B1tHDw%2BNl62%2FTZhugzd%2FLMhzxqCna6MQjw2t6zmwFtGszPp4r6mXAwRXgfAF8X5xnp2wbTqFot65s19nZo4oRaNf6G50F94J6iMjo7%2Bc6jrR0StsiIgQ6BJ5%2FPfCZzebncT9c5zH8zc%2FmKa7bGnXx%2FK7RKV5ydXV7C%2B3ifvTt3hqY%2B8yo%2FJZ%2FyJkuS5Pp%2Fd8jtXd%2FCyePpVG08XgGxV7Mp7yCYrqSW0TZ%2BUWUzIDeefYyjVwbS%2B0WhlO33yKo04JIag9gzxbA9g%2Bl1lOOWq018%2BsWn%2B%2ByUJCmI%2FWW2sqp4Sgi%2BoGQnl2nnxjU4NPfgHpQrZajOUtvFGfp9UuqUtfkIHv6nQI0Xq4An4nrnOWyRS7O5kD9izSZTC%2Fyfn8%2Bfo63xWhTWk%2FWw1z3V95OlhSDy60RoFtndQq9UPZTFxfPmlx2A3eKS6ASQvm3aaK0zqKtNFYNXB7Xw%2B1F8%2F4zrEkMHg%2FR9h60LcqMYf2BNU%2FfLtdbgGuX9ym3YhSw5%2FbWosPgXsRzygdvlx1EXx5A5%2Bt2Z93M5RJZ8Qzk%2BfPRwWAIZL2uXbs%2Bg3fA%2B8%2BzFnA5Rzja%2FDsM9yTq5eK65Xl435fcfhzaxnEur%2FHvRV26dHnypkDtvGzZv61gnlL57RkHw7qv5KjFn5Gay9JIcl0eSS4rImn10TSKT82nq1eJCouuUjH%2FSL6kWcJ2A9ZGU3eUZe2%2BJIK66oDtNCuMrfVJaj2VrfX3VgoCUDfTW%2Br%2BaxD9QKwaIT3EqcGnBfX4Htl61hTUFiO1ms2HU9WGfZKq2LvUl2sU7wSgZefAw0R4zd29N8pvs71QzwoIkSFiIiMWAJsI8eFv3o%2BtF8GpRDkA0mQCbXBbijICYPt6OZvTfHz6gRtb6sqOwefF8a%2FFdezy9u6Lvwst9VzMOAfatpwXFlUPaIAF9ATHoWgX%2B11d3GWdg7CSAjSP6UaAVNwrygtwFaOeuEfwdktURnRoe0RscL24P7Qrni22uAd53wn8vCpJgPL9H%2FHy6mNpG%2F4JK%2FXu%2FSHhvMK%2FEec3T8ezwn174R2I54ryov5XJdAkHLSCucWAWRU6%2FxwS4bLmIrksi9TcGMReq6Oo99oo8lgXSRtPplNShkZ6AeXYeiqdeq%2BPIi8uh%2FJmrtdLAPu9RRHUYV6k1Vr%2FdMpCQd78%2FpAA9U4LqB2Hb0G82hL9aOAt%2BbSgHp1mIvdDRj2IU04Rn9ZqNB3KK1%2B8UyrXcLGTn0q4fUA7CUCbxqKsKL9FRyEWC2BcZU3mB%2B%2FFEYt6PXu6tmSLtFKAgYTVGs7HzBjuZZQDYAfoBFjwewefaxTXG4YRgbfynFGIXTP4anC7H8F6if0ZsJigBTLyIimHaK8QW66DtsbxdiCu34UBjfPi2rn%2BbD0FgMXXWfeNoFG4J7bYU3CfOovdEXXc3Nwq8rELAlzp%2FPsP8HZQG0RvhGXXBKjHoI4cbcTolC%2BOFXC9X%2Fn%2B%2B3L5JeK5XcW5RNupLi5oz%2BSFDsu%2F5UgYDyooQX3DWOc7%2Frt%2Bd9uYRuZV0fmeDEyf9dHUb2M0DdoSQ96bo2j9yTTmzYWkF%2Fy9gfd78vGBXA7lfdZxB%2BD6JrbsPf%2B0WuuO8yOp3Wy21jMAalhqQT8Qsx4VSI6%2BmxHSw5S5oB5LOLHpN0yRI%2BoBB1FaaV79MhLZefk1HD%2FCGsXVhnu5bUCDEthCbFbKgfgvrK0EHaIfRn7Ix%2BNsFMTkbgzbCdBBM%2FAi9ccBCLQNS4XOwhz2dclruW4OrgX7wd319XQcGnVxvVsRmzYcXyfq47qvgK%2BK%2FeOFVcWxSNyDod4KaXlhNXVcuys%2F02WgKnKf8VxQUDOdU%2Fyz7lwnuJM1wn4ZkUEnxrnwDPhZBLm4eFXTtdlaUg%2BAHrz6%2BnTT9lKf%2BmDOoSivDQmwyEUfboxmgEbTRwExNGJHLPkEslN4OoWysovoKlkF1CPjSiEtO3ERx1HOUn4A1%2BvL9b3ZYsPKO%2F8VyVycQb1AZ6mnglOHWMJ6zT8NYj69TVCPtaAe1tlEhPIQ9eist9KfU82Wo3nCZWhR9SaDqIqDVziWcsnvftxhQKP8duyDk6SzlAX8d0M5ESOdJ1hD1BHA%2FVPfJviybBPWT8dLHxNOXDUAXZwzFUDWn1fsv4hyxrAdAK3rgP2sx4c8DhUA7CCAVCzA6SXqHUZsHvskzTE6tagnzh2GjlJSOgHaQXviPD%2Fq9vtLSganWHJxvANJr8R1ANDtZAweW%2BuI4SYjPdD24v1dy38iW%2F7C3H1rBu3MYEDG5A%2FeFkO%2BO2Jo5M5YGrPnHA3YHUMbI1IpL6%2F4H4C%2BlFVI804k0SA%2BPpbLjQiOoaHbY2gQ1%2B%2B%2FyWqt3VdFMQURoJ6PKEgYOTGnbjX5CL3xzX5q8QU7iYJ6NB4MK72apJW2%2F4eV%2Fl5GPNhKD89nLg3asfUeWGgZjciyDX2mbrKefDng1zYAuP0iz2EENL%2Bgb2QdDJu6eHP6NQDdUA9oWG79tRoBDapjAAtGj6pwDCXXBw%2Ft1s37OZxHUgqEEnFOOHNQXI%2Brq9kDXF3SHbQj70meA2DjdjvDQeXn7IlOjTbFDOt0I6D1%2B42TQwLQVzFaScstrHsDPHsJaFhoCegbOoQ9vvrDvu%2FK0LQR%2B1JoeHCM9vEeBvP%2Bc%2FTFwTgaFRJHq8JTqECzwlmCOj1boxmhCfQJH%2F%2Bcy43m8h%2FvjqVhQTEWa91%2FsxXUbiujLJa6C9OP9uDUP5%2BlNtNCeRLmELVk6oEYNSZemvBsYiObldZx6Rki1%2BNrxKURwsNKl3z%2B3kdLvWN4pwGtdwoBbjhx4sEew7AJCw0awC91krAe0ol573oWGtzQENOWHSbjNgENsPjK4Rj3J9ooDyeNr1fmp0wTI0K%2B6JxXxSxkPvZBxe8CyaMBbABdGkGmTL15335YTpwXvBdOnnROhYM39VqAxvl192AENPG9%2FwPQfN31AeibstBGUPv8sKKJ76ZTaeOOpwGk2peHz9PkYxdowpHztCE6DVbZInKblqPRjwzoKVxm4tELNP5QPH1ygDsAd4bhOlD3XgdnMQqRE3pnYQS1%2BzWC2sywUo83EKP%2BYjdJK91EcGkHj2VUlyMemEG058mWup2mkf1b3xZiBrFGi5F5VZr07WAE812w0FttMWOPuuDJtkkMhMbczgCAcpiFw8Pn%2B0mOGEKNlOPbewdo8H%2F3VxEyAyBkJAEZf3AiZVRDWmqjyhEJCudXPM8FMoICxfXCcYOfIEJzglq4TbwxoAHIEix0aQEN8QsiS4Hhv6xtMi7obMrksxn0w%2FEL2qyTiTTzRCJtikmjYoFkuU3IyqfZp5JoDusMLjOVgf0Nd4JPQ2ygHsyg7sf0w2tNFLkui6RuiyPo7flspTmc13q6zUqDSzdjLu3IXNqRY9ONOd%2Bjkftf1IDzqBGXruo0tbC60ySq1fqz3JqOgw2Owd0FtAQe4qAcPpuP%2BKm0TAJsOcLZ6W88%2F70FtNswA6Dl5MdFHeWYwPsq4JolWMFrYYHBXxm4TlDe74RJE%2Bxj7SD4rIto4yqrpR6H8v5nuw%2FzXAvQzV7iPu8ToI3ZdROWbGvgHxKVsiA2i347e1FbHHaRNsWmkVb8T0BHZuTQ4vCLtDQihRaeSaZZJ5No2vEEgNpCQ0BbwMUHbo2mPhw1AfXoyVYa4bz2cyLoLbbSbdlKt%2Fn2ALUav4tajAmkRkw7avdZTa%2B5%2FkXPvbeInm83l15rNUtr7ryAWnafnlet8dC28oGUZupb5lhIDisVTtT1AC0fMGKy%2FKLChWWL56F3BPIrMJMlHTwiMkyblx7QPQyARhjwBoDG9fpKZxP3J9quL%2BiD4LCm%2FrhGTA7JKAbCgzeV1%2B1iXqkbofZhn%2BE%2Bf0d4DgoH8b4C2pjMP29LSP0%2FTp5P3MjT3WuiU7Wd5y%2BRVlT8D8pxJi2b1rPl3shgXx6ZwuBPpl8Y1D%2Bwpf7ycLyFg4%2FcxaAOZEdxcwz1XcegXh5FHyxmMM%2BLoGYzzpDDpKNU1W8%2FvT4mmOxGBFBzph2dR28lk992Gjh1L42Ydajw86XRNHbJqfxRM3e%2BbXUoZpc2l2Nnycn4pnEGQMv9IzDxIcAlePJ1pcxdsNDJ7JhVtr1sI6AtYB1idJIBVl3IsEhyYQbWKtQx%2BgpY1IAoir7T6Gbw9tpCeaZ5ctSSnZn3LdRNyHx53wFtBHXgyRiHoHNpSQcyNDqQdFnLKywmKVcFoHcnZFIQgx2gXvE3qK30w4859ei952jIDmssu%2BfqSHp3KVOO38N5JjGCnDmU139hGI1ZfJa%2BWhZOk9dE04%2Bb4%2BnHgETy336Rpu9I1X7ak0XfbYov%2BHr1WQuYncQnx24N0N4YWgHQowAJHhJeLH5LxWSFeNGjjYCWw6kI%2BueBd3K5k7D4%2FAL2AlTw8BGS49m3JrLOneLQIj6dh2Sl68WhxahxGvQA1lkX9ovDMWGJA2U9dEjRyaEWy21sm%2BmEAx%2Bb8cEHMifFvB2hPhHzPor708%2Bk8jM6hXPheeO5PxCANoI6LiurzonUrITYAiIGtEZCiph%2BRF%2FKIT5GB5MvA9QWa72E6Qk4NxzJUewgDtsXS2M58vH1rjiatvsCzd6TSL%2Ftv0h%2FHkilZQe5ExxK520G%2FRGSQYv3p1v0931pUO3Pw9nkv%2BVc3qcLDnWQ388rRT70DPGw8uDNY6ICINFpthiSc8VExmAxrOKlbJLt8QNtCadHWkOA29v7Qyg8fDndLCcwRv4T0G5rdbOPE4yAxqwggCyOJ8t4M%2FgpZh5tuRKmVQhbIcrQo4e5hpz6Rj0xGyg6rgnprwGIQuhmIK%2FCsTNEXxaijswVYV3B%2B0ZyPehCXFPfvgNwbJE412coj3OJHIvfADDEvxGrFm1BBUUytZGAxjUIx3WqvAZDxqCc5u8oAa3rVJmizUJD2O7W5DCRBdTpWQV1ErLy4skqGv4HPp2cnU%2FRmTkUmnKF9iVmUkBcBm05l06BvN3NAD904TIdv3CFTl%2FIprMXOIcaiwPieXs%2Bl05bNMeSVx2WkEOnzmfTibhsOs68fW%2F45cLtYXm0dF9i3ob90e1tH7a5eZFgwcvw8emPhwnAIRKhV%2ByzgBKJQvKlWYHalzDEGqkEnCR%2BKQMxvLMOQ3nWObCEMlEHipepf2FoD6oPW%2BkstB23WSxzNySgIegAcsGBjC707TsQ5T4Xxy%2F27t1PJvCsxrlx%2FaBHKAvFMUzFy44OlRM0ACXAhrbRSVEPijbENHUy7hflRfw6RF6PzGvB3yiPUQodTD5n2XlBT3Bv6PiYfjfEoUGLQnEMlh3ZdQYL3Qj3JA0G4t76%2BqUG9ZWCgtrsDCZJUINy5BQWUWa%2BRul5Gl1izSooAs%2B20ZIiosLCq5b4db5Fiy3Zell5RZSepXFedQFFcI71UQYxkpzWHkmnJfvTtPn7r9DkTXF5ny48%2Bpb8GGRp1xSKoP8SDIF4mJj0MOhMWCMG4CgBkHfQCYSF%2BlgMp5XgcMF6w%2FO%2F1slEKCxehq1k9phocyjahPI1dDcCCwtdYcVwToTVZGaczZKbxuKli04TxboDlswKMNOPyDNh9ROTHS7cxlbQD9ZjrPNBXYyhRD3HxzVxO8sRqUGWG%2F8dAV6NnBCkteoNhC3ubjoJsLOmox7v%2BwZlRFQlBjRL1nHlziTu%2FTdcn749mT%2BDY8jvwGSNHrCivRnoFDAcfLyOrX4phRjUYtuINUWCmkoQgN4oBQzwyzmFnNhUQOfTsFAgj%2FOsc9lK59LJuBztdFIx7T2TmrNhT1RbG%2FUptZS5E%2BWRaAMLAUuPvAbpDImVIeV0Ib8Vupjw3JIWEsjfN7uuEED38fHBVH%2BJggQnGbm5HgCM14bpZgCWk5D%2BYxztjOXRNpKVAHhYemNuC6bKS77%2Fktdjlvyebh%2FUDVmT9aC%2BCuX%2FIZwnlf8zANrGvWGhUy5rFjCfYhpyMCqLgk9f1jafyqVFuxJz%2Fgw462SLjd%2BeyIjDzaixvC5P4ytd7vMew3AnJ17sGOxJOgvtJ8EgQ3nQ670M%2FXUYr%2F%2FGYPxn2%2BhcJa2i0YuxjhHI0JJWv8iy%2BmMGMN%2Fw%2FnF%2BOV1f0rPR17%2BToHZgTbwZS11cDNphpRy5%2BcWUzWCGhU7PgpXW6ALTjvi0fC0pi%2BhEbGb2sh2RFu4ZBDDfPymj98RBMwSgZUbYLqRNIqcDwzVCfRhmcUxY5zxQkNsdGq8FJGhJFl52glsBgIzNG%2FfdzPXIpVpSS7M6%2F1ZHrLsB6uoFxcXhsMJJ2fla3OU8Onc5l2Izcymef8dn5lF0Wi5Fp%2BRSeCKcvhyL03c0JptCIrPwOQRe7XKFtrNl3h5RSMv3J2UFH4lpVQLNuC%2FAhmBSRSbeyAR%2BQS%2BsDgv%2FLSxzLgO918P2wRd5r6A0SE4ClZL7S9sBb%2FacCAsiaiM6xr0XCepcotdSsvPD4gsJUQ5tW1y6ZYJl3pkk8g9NsITuvuRY9Bf748hvdxx9vyuevtkeT5MCz9O0wAs0eWuiNmt3Js3aFpcwf2tsMyPNeABB3Q4OmHCI0nWhv1S22MfhYCJP%2BGH7JJct68%2FcFJSKNYBDfK2MmXvYGi0%2FfAnQKzifQr%2Fm5zMNMWuu%2B6Q%2BRCdHF%2F1CW3byevKIdgj%2BB6I78pzGBbPyvHcd1Dmp8eVjM3NOMP2lLbHp2qqoVFrEkyuIRX%2FHgB594Bz57uQkpYBoct8QRd1XRtLbS8LJcV6YVn9hEtlPPBj%2BSL3vqsqPQT6o1guqd56QVwGPG4rfcBD1AHnYLLNcnY6p%2F1vpBAirIZaPD%2Bhwhx4AgGJyBxNUN9MOg%2F84Fg2UdK67QTvQEHoIGgfwHv9%2B9myLJxsaElLlUPKV0MC0IuK8Dw0JS1M5pwMzhWN5%2Bht51UOQeYfEf6xmWRqp9VxxkTrNPB7eof8vNSy92NvvOd4AFI%2BxlsW5HjQrV9L3IfRO0cNnnV3fxQQGojiYzOjp6uqEcCdGHazYwb0hlo1RCjOicoW3CGUGGEBqQkdHLJ61E2LmPV3c3sY3%2BLDaBSFNRGHQAeBvwKIjTwVUjXU5QoCw4Bgx5KfTUJfLf4KUXYnH2wUwwPYUK0I6zz%2FyzDOIlb7s6euLxZDP%2Bk%2Fyr7HmbHLoXwn55H88ofDbI9asO%2BRH%2B27HShZrGqlpRURhrzUp9PbPx8KqdxmO4fmZKu3MldEWK9p8Ae2x4sLLGQEOvd8WTQJXqnE4fBgBjfWKmH6HswvAgVbB4cVkiTPPmAKAiLMzyA7BittWkSO2bw5Dph%2BWnrG8KtZVdhSx93SUBxXByhQB8iDE25EfgzWKADL%2F%2FRcfD2JrjY4VjhQBsSg3FVYflA5fhpKGozRALivA9KQA8X8F6F5lrcBIq%2FTEE08g%2B6taF5MXgPlaH88BDX8OiT72U1Q2fXYwvnA0T3uPCI61pJBiWZfn6shCtw0X6Z1fjoRVb%2B%2BN3ImK%2F2vybl20wf2lCmtl7GMtz%2Fo%2FAfBnRUd6XAJbfTL2jv%2FbkgC0s1wUjEkZ%2FK1bQxiKzw8AuLCa%2BvoAJyy0tRO4%2BgCw6Az4Jon1I5WmQaLNGIBUAP4IckdYhyF%2FHJ2D201ETotc7yiz%2BZCiylosr6e0Rk1WKiuA9LSwnK%2BwVuId1Rnpdqz1Hn30KQw9jZp06PwGbx1a2zm29tt%2B5sT4U5doxM6YwiGBMcUDNkcX88pwzWtzCnWdcziyfL32mM6u97x9W9Rp%2FOijjzbkthxY63DboCCvA9SiEz2D0UGB%2Ba7SKAB6OsCFPGiklmKCRWYfYuaR1ZU10ODkvYKV43ImUgBwOL7ZAb8C3xDBB2XE536v8P4%2F4PzJtY%2B83YTfmFrHlLpsA7RHlz%2Byg%2FWSXGN4JyMh%2FxIW%2BykB7gqsDOzHHBiQsLat7Zq1foe3bzES3%2Fdde%2FjU2COZNHj7efpwcxz1DcygbnNCIl%2BoUB8hrXbP126B9Mu2ZcuWbQlAM5BrweoLq%2Fy06Ejq3wW%2FR04hQIikJ%2FH1pBQdQKP4mI%2BIZCzSL9JFXgXvi9WvGBfU4TvRzjHBqXtgGZhuOrscAIrjyHqEAynLgktjgkpElOZze6A0y9Dh7oXDjcbLAeDlypXDnL9dzaZtEO5pU%2BGl6s5eC4J39V55WjMvPVXYdUbwoZdrtvQAmF%2B0b9MWFpopy%2BsAsOgk6p9UuIdiWPD6JKINoAugIACX%2BJrSFiy5ktYUllSfOCQSs7brOwdCcOxgdmVQW%2FIzZJwZFhidBlaa25oOpxLppvjkFz7GA0qC8kjHZR0LeoIQIuoLxzIeI8L9iCL9u7Fzv2cFUCvW7zqorUO3oUgyqgIOXrFjH1h39f3iB4puuNTHx266u8nvT3u%2FIq21fk0lHEN9R%2BDJkKqwqPp8E7QFwGM%2FcqR1zucLWBkus%2F6sEQ3zQf13%2FnileT9YbBnVwPlkuwgPggbdixh%2FGamlrass832OcDibf0KutdxXUjLVrSZYXSsqgWw%2BXW74Y8Y6JXzJ9N5yMr1iRTkUvxWAH0xBWAwrQ%2FBbn5gl4%2B76Wb6S4u6yjjHRSrYpcz9grWHB9e3Ic8p29MlN6l%2F5UnILBqjk%2FAv1r3YpeWjkfkwM4ZwKzEqUKFGiRIkSJUqUKFGiRIkSJUqUKFGiRIkSJUqUKFGiRIkSJUqUKFGiRIkSJUr%2Brz04IAAAAAAQ8v91QwLcBcSILdUkjUduAAAAAElFTkSuQmCC",
-      "title": "Lightbeam"
-    },
-    {
-      "url": "https://www.mozilla.org/en-US/?utm_source=directory-tiles&utm_medium=firefox-browser",
-      "bgColor": "#4d4e54",
-      "type": "affiliate",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAABWCAQAAADMSHQwAAAFWklEQVR4AezBgQAAAACAoP2pF6kCAAAAAAAAAABgdu0sNqoyDOP4C1WWgCylKgqKoNRCqaxCiyx2bBGNAVrCJkRkUYFAMEGNVUjYRIIIuAYlBqKAUSBcWEUIBlDCBRU6pKINCAUU0CibbSyL9e%2FNmTdvpudMF2ZGj5nfc%2FcwyZCH6Xd6DlMXTGUbe1jEjYhJMgEK2M5WGiDYZDCJ1QTJRWId4f%2BTDwkpoanT5bKXcgDgEqJpyFrKCJmWGLr2ycNa4rQzQf1OY0RzFdTziaFrn41Yh%2FQTjSqnBaLZA2puYujaZy3Wt06bBepPkhFNIaj5iaFrn4FY0522F3boNohmE6gFiaHrkvmEbNQuw3PoDYlPdP0zmOWsYoRpunkOvT4xdDTj%2F6FpS3vESTo5ZNMBwaYBGeQQIBWJmNsZRB7DybJD1DvN6EE%2BraM0dGdyGct4HiMj3kM3pzdz%2BIzLnEIQ5lJKyC6ynVfdxFJOEPKd52%2Bqk9jNNUIq2MYYBJtxlNSQxc4%2F64t8ykEuAJB63UNns5ojWMdZRqv4DV0IahL7CTcN4UHOEO5rkhBsulOEm13Yn40CalKIICRhdbiOoZOYQRHufqFvvIZeTkgV7t7F3TLEpD%2FeLtIRcTKLmqxz5vk5KkM%2FQxmRXCU5PkPnUF%2BXzGf6VqqI5FeaOK98mer%2BxiqM6tDvUJMV8Rm6J%2BH28T5bwKXfQDHWPYiTHVgvkEZXXsLarJejMdiMJkAp1uyoDi2UgSpiDSvYjPV9fIZOw%2FqS3k6%2FAmunnmU%2Fgsp0uj5YeYiT8Vg9ENf0ohLUbiTKQ%2Fd1Xr2ENO2mgDpHo3gM3RnUAdMPAPWx6T8CNdDlAVAQMTkC9ux1STusq7SK%2BtDCVnbQLqyrJKSClvEYOhXUF6bPBDXd9B9UGzqJS56XyJWgziIuOYyVh8RgaLecJKQ8%2FkPvMn0WqHkeT9UGuJzyTyMm07A6IWFZhbUFifHQN9OF%2B%2BlPFmcJuUCzf3Po%2Fh5Dr6s29ONYwxCTfKyHEGyGYl0gKYZDj2U9pVRS3W808cfQz2IFIkyZj5i0oBJrEBKjoUdxFG9nuMEfQxdEmGsI1mjEZC%2FW60iMhp5HZKdo4I%2Bh50Q4Hh7Bsg88n8MqRWI0dIBwFfzECXOTdBzxx9ATI5zRo7AGYx%2FgWx1jNvRhrJX0ozUNSeEKIUf9MvQDWFMQk5lYd2h%2FGmsWEqOhO2EtNteHKkIO%2B2XoplwGtQgxeRvUSW0%2FwdqH1GvoSlI8hl6g7QisttrfBeqQX4YWPgf1FWJyDNR7TvckVhUpdRg6HdQ1bvG4Y12o7QSslq73oyX%2BGXoIVlePL8F0QxDuxKrkPqQOQ9%2BLlezx93rV4xqRqX17UBf9M7QQBHWeCbSmBeO4Uu3pXXNKsUrIYlhYRpLkOXQbz7vQt0Ad1LYb1iHnVO%2FFfqxh%2Fhk6FbAq%2BAPrvPPtueGA9RfVnaah59DCMaydPOz0U7FKWOt62T3PJnYQ7hyZfhlayMfbZdJqfpWagUQYugALtjl9S8I47zmO2gjGfuh0FD%2BYPtvjrq0QRQ5iEqAMN0G6mCcONWvlDG3dbb43egBcT%2BqJWPCafd4Y8b%2Foyuka%2B6E7UOwkyBrTdze9PQsXEtQ%2F6YnY0IjZFNmDgW94CjEZTHHEBHlTB91u%2BtsQM%2FUbVJj36Gt%2BqoIo1ms%2Fi9NYUEw%2FhNkAQIY%2Fv0DTiVzGMJps2iExSgqPMpknCJBCEmLSh%2FFMZgTpNDJtY%2FJZyga2spFXGKr9SIoYhfwXh%2F6nHToQAQAAAADkb73IbXCjR49u9OhGjx7d6NGNHj260aMbPXp0o0c3evToRo8OZGoOg%2BFHUXQAAAAASUVORK5CYII%3D",
-      "title": "The Nonprofit Behind Firefox"
-    },
-    {
-      "url": "https://en.wikipedia.org/wiki/Main_Page",
-      "bgColor": "#ffffff",
-      "type": "organic",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAABWCAYAAABmQby7AAA5O0lEQVR4AeyaeVRU5xnGn1kZmBkYlrIo27AI6jCACyiCimBQwQpCRFDjqfU0EZPTENMEjetRG42xFWt7TKj1nERt1Lok7ifRti5NrWv0pCCRfd8ZGNZhhr7vd%2Bw5%2FSP%2FJPJHM%2BT1fHPHez%2FunTv39z3f877fSGBH8WP8GHKM8sjPfyO5p8fsD%2BBPen1Q3P379%2Fg7uY4fSJhMXbONkVFxgXp9Zk9PT%2FmHB%2F%2BwBMDwqAU6ODgYozXWrn3112lpC9cPDQ2hpqZ6mZe3zxx9SAi2btocAKAa%2F2cROi40QKvR%2BDQ1NTUCqJQrFFmvrFl70t3dHQqFAnKFfJKPt%2Fejve%2FvmQagZ1QCrdFoMRojLS11RX7%2B6%2BuLS0rh7u6GsLCwOddv3URvXx%2F6%2B%2FuGMMJR31Cv8fH2sQHoxXeIoOCgqZMnT003GAwveHh4TNHpXBlcrFiW42MwRqq9vb3x1VcPoaXnqNaoERwSbHhn05aHhft%2BsxjAY4yykMyIT8Boi%2BTk5IKk2YnvTomJQXd3F6QSCcoqKmAym7F%2F329%2FAaAIIxBqlco1JnbauoiIiFS1RhOgVDrYSkqKH697Iz8TQDu%2BJaKioiYEBQVFO7vowsaNG5ccFRU93dnZGQMDgzD3mGmw9cPT0xN379y5c%2BLE8Q1xcTNe9vXzWyyXy6Vjx%2Fqil%2Fo4qTXo6urCxQvnd8nlsqfXrl07AmBgVCh0e3s7Rkssy83JeHFJ9pbSkpJIohitba0oKXkClUoFK4bx4N692wkJM0cEZg6tWn0wL2%2FNErO5V4Do5OTEs8FsNUvp%2FwA9fnx4TITRmDopenJKcEhIrFot%2BsFs7kNbexsaGxthtdpgs9kwTP9qqmtgiDBOJc%2F%2FeWVlRZUXqbREIkXpkye0BfwDAsS1XslbW9BNYEdPmlKwedOGRAC1dg%2B0ydSJ0RD7C%2FefzMxcnHX9%2Bg0CxYy5KSk4ffYsnpaVWRRy2QMXF53yd4X70kd2JpjrEx0dDR%2BfMQSkFfxds1%2FfsXPnwfKysluOjk6B%2BiD9dKPRaHBzc2foxWdrbmpCU1MDdDo3DPQPkD9W0qCT8N9SI6htw2hva4NUKkVgoD5AQttPjh5BVWUl%2FPz90dLcjPDx4%2FGkpARBlBME0yjZ%2Fd7eexsKfmUE0GTXQMukUth7FBbuLzJMNGRt3rgJIaGhWJqTgzt379I0PoCkxETb%2FsLCtwH8LT5hJkYyKirK%2F0qQJjg4OKC3t5fUUwIt2YcF8xcsoP8sGB4ehsViQWdnB2prawSoNtqn0WqFFaLqCxxVjrAMWcAzqZLA5mNSqYTO10d9bdBqnXHqLycgk8sw1tcXfM6K8nJcPP8Z7VNg4aIMhIWHM%2BCeKfMWrAaw066BppuEPcfMhBnvZmSkr75w4aKwFpGRkUItPagyEBjAxQyJQ1x8%2FGfknb1FwvacMS02dgJVjmb7%2BvrFzJw5axb7XVOXiVRWjtbWNsi6zaTYPlAoFeLzdBDMfX39kMmkGJbYABtATFKS58zJKYasQ7yl41TFUKqEN9ZoNPS3DsJXs3VydHTE%2BU8%2Fxcat23Du7BnoqXI1ODiIyopygKBnBWfVnzY9boXdA003ab82Y99ez6IPDxbw%2B0pK%2BqzkQScaDGhra8eYMWOIBSn%2Bcfs2wSLTuLm6fe%2BpysXFeVxeXt4mRycncg%2BRxjE%2B3uDoJnjb2BrIZPD38xV2Yl1%2BPl5auVIobnVVFXKXLyeV1VIip0Y%2FqTgDLpXKMUwgqhxVpOBD0BDcSqUSg6TmrM5Ub4aDg0oofmdHB%2BaR4J%2F85M84fKgIG7dsw9Z3NiD1p4tgJpXn8%2FIs3EH9QkNDw27dvHmeizx2CzSPXHuNn69evZG3feRD2Z82UXLFNoPVkJdI6%2BrrxArEv7%2F%2B%2BtDU2Fgzvmc0N9R7ZGRkLBcQm3vQ3NzCs4CY%2FglycGL25Zf%2FZNsh4C0rKwOX245%2B%2FDGiyGPn5OZg9673wMldUnISOgh2m1TKSSAPNlGFqaurI%2F9sEcmflVSb%2FLfw0EMMOW23bN%2BB5UuXYFpcHBZmpOP40WOYlZjIfXmdhbylHLU1NZg9Jyn12JGPdgN42y6BrqeHao8RP2P6a9nZ2a%2FV1zcIsDKzsgQcnFhx2MivtrS2oqWlGdeufrEdzxH%2Bvn53a2vrTF5eXi59BC3DzDaAFZRV1cPDHa%2FmrUFISCgOHT4MJXlqhvDM6dPcRwyCz69cwct5eWI%2F75NgmN8TiDKGmQaFiSsXQnU9vbz4Xrgfe2e6hxaMnzARm7dtx64d27H2l68TuHMQERkFN6qW8GzAtkMqlRHU1UiZN%2F%2BtixcvWJ21Gpm9gS3nEpG9xZQpk89kZWamt7S0ClXUUJXM1dVTAMIelJO0alKrHlpEOXn8%2BMawsPDnXRUcrKqqvO%2FrOzaRLYJO58zXYujIKigE3CteWokzp04x7GJQlZaWCrNsMBpx%2BdJlglwJvV7%2F3%2BUBtsa84QFBPtoiEjtWZ04Qh%2BgaPNNI2XeziksJ%2BtpazH0hRUBfXV2NNLIcrOimTpMYFJBwlcTK1oMsSy%2BSkpLXh4eH4dy5c0EAXoSdhFTn6gZ7akqFfAXDzNM%2BPzxeFqa6L1USTGQFmsmTDgqo2js7eOrvT5k%2Ffyc1PG%2Bj5ehiAGQlvOh6aobU%2BsHBD57yTMAJWtrChch%2F802htmonR1y5dAmBQUHwoqSRQV6Uno7DfyyiZM%2BRVFcOklS2CpxMUn8N%2B2ZhQSwDFv78zxQaAnL21Vzx4Lp6LOVE2bm5IpE0k%2FLzwOLG1oN9ububmxhwagL%2F9u1%2FUdUnLOvG9b9nU4M9NCm%2F2EtrbWlKph8bFZECPVNiFf%2FGgZMxfsD8YJ9N22aRID5%2B9OgyNYxEo9W4WwDYn5MSn24tKCjQHThwIPT3Bw7s44SOwY6JjRWltcqqauGTf7ZqlZDhgAB%2F4XddCbbqqkpWdWEnAOH1aaDq0Ev3xIrLgPMx0fi4BOI9J34KuUKct6qiEl2mLgEwJ6R8bfbQnj%2FxEHbHMmhBQ2MTDww6bzeyl%2BZ%2B9PDBAxU1%2FNCbnF%2FsIRIS4lPf37PnvMlkQkNDA9sK0TgZZJCdnbWijsvKVt%2FYQA%2B0Ebdu3ngL3yEoqWLPacW3xzFaejbarFa3q1e%2FOGm1WXUxMTGrvvmmdEJxcfFw%2BH%2Fae%2FMwqaqr7XtVdTfdDEKjgCIioNhMMggqGhSQKBiJEhWjUQGTqNFEjTGDGdQMPn5xjqLGJE%2FACZOYwSFG40AcooJCmKQFtBmUoYEo89xD1Xv%2F1nXWxUm%2FJM%2F3vP8dYPe1u6qrTp1zqs%2B91773vYbTs1eOGQL6sUbnd97556O4aBB%2B7Od3YNu2dtXXvy7VYqMv9FgIsjDU%2FlA4pKS00UDcYm0Uy5HXa1jrHLykkDMFKfEZH8RygYtitYAzi3dv0T58YYmFxsvo%2B9spulIuiiO3uFMycf9mJw8b9i0z%2By%2FLeMud%2F4ULbW9oN91ww%2FLevXt1Xr16jQMkpuP2sko7pXIAdKw1Fu6DxUvs5alT132wcNH5%2F0T6MHvP9txkTT%2BskCLxVe3zi7KElYsXL77XzO60Jk20okL683ECz4Xafuhhhx3We4sAKF5t48dPKMgTmGemYIGKRVVsBwDkvMSHd0KnHYx0rCkOFwCIFcXhAviQG9Gl24pa7ZR2nSvBSue06NvAPqAT6Nvo0%2FBl6dvucrfyiuYAWee4ywhu2qTj8jqDgv07lamrK9x91x0Hm9knmV4UKn7Bst4uu%2ByyKwAzvBkwmwGOMrdM99z9Mzv2uOO0UDzWtiUKRCc5Ns4aPbp5t6uumlqzeLFdccUVo83seWvSKsqbDRd1eGz27NmHwccJEhJY7whAP%2F%2F880fIug3r3LnzmXr90xowrQcOHEiAkevHdbt2FY497ticeHEepQGwAmYesdJQHyLnmEnMoBGGB9DPu4UsOftgUFquICA3QklQNLTNVln6ljxnIOl5hQZuJ1QM8eh1UAqoCsdhX3gcBeiCKyJhpStExzSLODd3R1P7Dvlhw09hsF6UaUDrS1jW23nnnXcrFulAXfBPPvkEnizwofcutSelLBzaqRNOigTQBZ%2F2q446qgUXGD34nLFjJ%2F30lv%2FqhJpnSZP8VvLwww%2B%2FKjWCBZ8A057Psf8GAfxpeeu6DRo0qB%2Bfr6ystJjuL754XEGqSj4W3VtklTdu3OizA9YZgEErUCnadzhI59UCsKF8AEhtt8WViZDvWPDl8mXqxmdYNLJWkMNkJws7qIi%2Bb3N34mwVONk%2FsxDf07mzaBb0g8Z2cPEivKngx%2FPvxMxBnMnQYcMvfOA%2BDwNYmVlAz393nmW5yaFxoxZVbZiOp731Fl4zrJ%2BDEG334vHjXWEAcAAEYNF2aoqu0M%2Fq2tVw6%2FZ6ryQFaDxsjXfeeeeF8q49JFd1uSwhFx4%2BW%2FqpT31qjB5DCw4pDpd2UfvPsygFfKgUQSUAM4%2BcR6HYaLLqbIPbm%2FeQ3wQwaEE9ru3kPHO2Q4vZzVt8AAA%2BOWZayqJ31uJvneJCWolrl9rHUjcAPpYe61tkwai%2FoSpYaoKbWDRu2%2BozlBEDP%2FGuO1wZufq6b2HldR47xM8r5R4f8mszOz3Dru8hltX24gvPNR83btwPeT592jT71S9%2F6dIcC6RdukjrZK2vufbrxEyIZ273%2BAcsOUDE04ZbeoPUACWATPvKFVfWW5O2Y9vWoqxvcdGiRVhoBwMWkc%2FDO%2BmAkUcaoJMsWCwpKc2pOZihEcGbowUVCHXC96NHFAmsNQMHKgDN4LOAu4X8Bc31PXDXl8CxFZT0kdQSzgv6ALdmUHBAumsgnEPeBxznZbTyigrtf4tz6MU1H9jMt9%2B2E4YMcR2bCL7%2BAwaM%2Btnddw40s9mZBPQrr0y1rLazzhozvmPHQ0pYBBFh1lyAnTdnrp009GS3OgMHDXQdGgcLVo%2FY4oUL30cVIDhpq6boVgsXLVz5zFNPnWlmXPQDu3TpMkDWc4ToxAUC3JHz5893XRnQAUAsMb1JA6QoBkVAXVnZIifAJGA2PpcAvxEqBD0gxlnWtpUPNrZFRsS68hoOEyS3FjhmSqVGaKHH90G1cFd2zuVH9ZZE5ckT2V4ghW8DY36SgWS7%2F8b5UiwxX2y21qCsUnjpkUdV2WZRje0JVeEYRPMp5HVcZgFNvG5WW9%2B%2BR48EMCgFn7%2FgArv669fIUhXs%2Fnsn%2BnQ%2FbPgwUqrgjg6a%2BdULbM6cOf98ZerLj4lSHCOHxogF71XX79ix7V65pQcqxambgpZaAeDq6mr4sg8I%2FgbE%2F6lBQTZsWI%2FV989484We046kObA9noP9sb2yWMT927oThM1AovIGfTtmEwe7uD8Nzg9ooQoAs7JNpQbpJyFN%2ButuoMNKJzME77ENXLpCtAV60qNnL3vpr88j2Xn8NCBnn406r06dOg%2FPrKdQJ29Z7PPmzjlgyJCTRsFJuWAClIC9xR0oUhbs1NNG6mIdYrv8YpfYImVzLFemx6oVyysa6nddIFicIicGvLfbKaeMmCD3d18tKluhRnykKDhAIXCn4j%2F%2BY8NCM7Dyy5YtA5cAMgFXuMCbwXH9XOCrLBThyzhRcGX7IpBu5sfjPagCVKNN69aAMMYEdAkdGXc52zIY2A9vpZwuPPoL%2Fghg5yv3cI1id9DjUX5YLP%2F1ub9AlQii4nPQHWLGB2jmPl7dstbz%2FMpiFwjP6dq1S0suQCzMNiuAZ7su7kknn2RVPaoAmAMJPba2do149LqieGbr%2FgOO6XTEEUfmunbt6tYUOoEXDuBhjaEnQS%2F%2BNy0nsNbU1DieWcDBddkHfBhrv2bNaoKhoBwcE%2F4LbYDvJyA0zkE6c1spIO3h%2FEh%2FUmsWJy7uElc5auU%2Bf1feSeUn%2BvtY%2FKBEtABxcGlmCKgN2z3%2B6COem4gOThDTScOGyTu5DJkz%2Fo8MGMWND7hU3bLWS%2FmVxSaOey7cmQvFFcOJgeOAC4KVxZQBDjoWDFVjm8BfV7%2FLp262o%2FE%2B1pHpFisLMP7%2FNLaDc9KDV8NnZ82aJeCuLbDr7RpI0UpckmvulhSXPI4Szp%2FzIOsE7x6tpmaJycoTcuoOoYcnT7LPnXMOFIGBgfYsL2etkRCLq5tFXixILahGgDrhOrxMVswQZeQske7%2BvW9eZ1%2F%2ByhUaNAfbJ6IbvXsfHVQlWQtsk%2FU%2BbIyZXZ45Dq0Tt6y1f8yY3mLw4MGnInkBRiwzU3znzocbIMIhgWU2c3XBge7gE6ghqmwbDSCyzX9qsaiDKjAjsC9Z4KIsKRwUi45lL5Kdoue5jZs25tCn0YORzABJPlEbOGcNBm3fms8Y4a3yPtqiBQuVD9jZB%2BgPb7rRTv%2FMGXa%2BUsVeEM%2Ft3efoCEiShLdJVOgwO%2FzwLv694dnwXv4PvvDczd5dvktTEPj9xRMu8b8fmHiPrRQFE9Wy%2FnIG7dR3i2jEEp1r28o2He6beE8fvKiZAvSURx%2ByrLUzzhg9unv3I5tzgTzg3QPh837Ry7XIqrNdIZUFInEy4BnLoc9GSykWSGN7ss6AEb7LDFBEZ%2B7ZsyeZHwLU4SaFRTJe2xyeupwaR%2BK4ixYuNK0s3evHPrGecT5si%2Bry7J%2F%2FbLNlzU86%2BWSBqsoeVpy0MritZukSO%2BfcsfYpSWlIeoSNEuLLDAPgsO6MSqgG34mmXTOAGWhsk8wg7i20%2BKXvjXvbI%2FIuHDfejj1%2BsL3z9nQT%2FfKF4mY5c9hnSz0vF9XhOMcdfwJew%2B9nCtA6actaE6iGmwOSwJ2S4ImADw06PGyAnWlZOm1zo2FdkcuwmgmIiyzUBIKiVICcLHAO8BArweBgP%2FBwtOgvfelLhT59%2BuTEuR24%2F0o96pHk8M6xTwfy2jVrCa7nvUS%2ByyWgzjtQCbS%2F976J7t0ce95Ye%2F6lF2zEsOE29uxz7atXfU0Ls%2B4qqzAHKsTxfZ%2BNRU8cwKuHy4ad0ikwg%2F7uA%2BIwzbhIb%2FUM1N14RvFADsSaw%2BXJjhGwJ0BtcNKgYxPV5wNj5apaa9FQsC5du47KHKB10pa1dsQR3Y7H6rHoAlAE5lAGAIWDCw9IiVMoFmSr63aJhmzTRW4lkFQVe%2FaoKsI%2FAQmxGVAHTeVFAZh4i8Lbb7%2Bj0gaL8xXl5Tm9Bm8t3nzzzQUFG%2BFJBGBh1RM5LqykPyZRbGW2fsMG8dBDAXQCqyLbsD9rp4Cp799wg1Vpuv%2F858%2Bzz4webcdJmXnx5ZetV1UPt94jR40UgAsOMDrfCzAepO%2FJzERXJSaAy%2BATbamBVxNLjUSHhcXLmJAPJ188D0mPWUMDYwU0Ck2b%2FXAc%2FsZSSwkpI3ak74MP3NcKW5AZQP%2Fut49bhhr%2F6OZfnDCuNwsquHHijoZ7Jgu0esAM0PG6cSn98diBx8j6jYGHhoXdUyO9qe6O22%2B3BkkKgAOLOGPGjByKhF7CEgeVSYM6NGcALwtfyeINLh%2FOlQA8xwecnCuW2XMJL1Gi7F8F5sPFoW%2B74w774vhxNlzx0WSzILfhCApmjGWmzseqVStx5PA9SWTgvF1mJHaEdK2Im8Y0h4mOs0a2YxHdqP9Vvf6HG%2Bt2WusDKpE9RZWak4BAPRDJhW3KdKzBZva3zOjQ%2FHOy1BVjfFyXLl1bcK2QtZCiWrfGKVAHj3bJq7VeaysLLEeJc10scZnACZgFphxWkngLAIFFQk2gzZ4zt%2FDYo4%2FmNWhKypJYDVny3G233ZYTqBsBSVg5Gg90%2FY5HAI1lA3gs2phFwoqnPpdzy4189%2B3rv%2B2LvpNOPNFolFn40c0327VXX2133nob9KrJ5%2BpwvKCf%2B2BmEddensIjjzjSra5mFqPxHZmp%2FMgpXOPIIVO8XiA%2BUJKlClQqEGsHUh6OmbDupHoxmFloH6tuWel5fmWoU1SxF5IX036tuN5muYNJQnWa0YgHrTFxM9NdWuN19Qae%2B2tpANIYGDQVnClOnTq1lCAd7QOAkKbFIrCoWOd8gCrl9WOKxxPJtM2CDz6O%2BxruTswEf0dMR6oVed0HExr07%2F7whFXKEp824tO2UjEVP7jhB3bLrbfaow8%2F5OoD8dHRiM8AdBSg6aOB0K%2FfAOsg%2BQ33tXsXk4xwnlvSaThtoELIdMh50BMUF%2F6PaNvQtXLPrCkkOjaBUU7fjlK3rPRSfmWosYjqAhjKyspdg%2F3VL35hvY%2FuY6effjrvoTlz2dO0wBeIDvBEaUiJH%2BFAYUFZHHTsoOLMmTMKqpfBRoCUTJiiQkKZznNY3wCzmYeh%2BvFU1ovINkJUmS0U6LMKC4kMB78N2kFLnjurZUYhlFTSXwd7cepU59xHVVXZZ844XbU7xodWzkzyr7NBIgGSQAsA6QDWkmQA6BdghAv7wriE7QtaOK7VRS%2Bz9gcfQo4lHkrisSNOm%2F2wHYMUa83nkRY7Zyzru6VlqbVv36Er1geLArU4UXUo3nzjDfcQXvLFL8KnY9HWBLh5sxTfpUeD62qfuauvuqrkUyeeWNA0zoXMIdMpUIkNAXOKNhSZlj3Viyg1LHrBfDZQLPMGB6Bq1rEPt6a0WBimWjh%2FcMRYx0M7qlro8%2FBhLDczD%2FEpOImgFRw4idIrmgMZsKe%2BBbsO13c%2BXOgFnDZtzOuR7NzuGS3NKiooks6AEHDz7IdQWQc735Gk3U3i1%2BwPulSJ5JMlQOuELUMNWawjFw1nBnUtlLNnw4cPt9dff92WKuKub7%2B%2BpCThkYvAHBaJyXMsVSzgLFq4fAFMTh7IkjTiQtlIt%2FAs1nzwAfWYmb7TEh4LsggsSkGumA5YCmUk9GnkRMVyf8YXYygYtM2J697pCVWVEu9h6zatSXZlcbnH5S37hCvz%2BLGA2lBfpwFzKDODDxhLQM8Ct5AkHRDoBPXagpteC1pmm8YGXwC3sAy1PP%2FMDHViL9piUeG3Jw8daqoaan9VSQD047vvukvT%2FzKX5DZoSv1k3frELVyPUpAAyOgBrjSfTtMUrC%2F1LWIQ0QOAAIA6dcRDiIp0wqJilZk1mB34G2sd9ObftFA%2BwqqWCHwf48RhYBEshMOIuA3KmBGzjXMFKkApBM9qKdegpbEA5Te0A6BzUHkrffEHLVJIrA8wNPXSJGKv6WCGvuVLy4jBJvOcNC7WHQC%2FQt2y0kv5lS3K0a4VYOZCoFRIHzaKeVZVHWVvKWMFy0xFJOIqWrZwhwNSH4suqEVKE7YAnAMrnC1aFMpCeUUlFp1kervlvEjSGqAOoCJ5sfBjB8GJY9%2F%2FrqW5NJy%2ByfZYeBZuemwg64bsbI4jStMGYArAFQwm7gbggD5AsdXCtwf9l7coB7AkNQBQDoRjR5y%2BUoPvYz9OabNS7U982wrWRHeEd1MSjUFAqQVmIAYI%2B2qWKcrBl89QwyKXs%2BDiYqLKsrq%2FXgVccOH%2B6Cc%2FYjEmy7rS3dIs6li0QQFiioVLpy0kYARELNCuuepqB9KPVU6re%2FfuDrDf%2Ffa3dvG4cR7Uc9OPfmQewJRM6XX1dUksiKsq7EfAKEsi90ojg6WJ7O3bBrjTLcoN4PHD20fRGvaJtcT6e7IrM00%2BoQwMqGJzc56NOgK6id4j5JRMbzyCu0Q5yhL1Aotv9n9TLvbF%2F5GMFT7DOqB7jx5Ye7JhSjMFaNJ3stQACdakuTqLKTxcTNMAcvOWLSxyktjhHFM%2FAOQ5YE5ZxHwTq1jiC8u%2FPPuszZo7x7Xs4LYTLrnEeec3rrvOzhrzORVXHOBWGuu3VFRgvSxiey2kACeAU8IAR3DAdOvaLan1vK2Jg2XPDfCuWP4RurkUki5uLbft2ubKBS3Ki%2BGFxDlCXAY8m%2FOsFK9GYUGhYNFHxFw4ffgf4ISiOZBFb%2BrwImLezXMRbb7SzNopZLVP3772zJN%2Fsq5HHol2z%2F5ymeLQ%2FAMy1JlWCwCaC8FUSurVq6qadNrIkbolwwcJP9xNJQB3U1YVlCOkM9qDP39Q7ufjAsxwzlgMepJtWa7EXn%2FtNaNFwiveQxaiAI3Q1IULFlJ2DAeQLGUrxSzPp5aGJ7ZG%2FmGcWxw%2F3fDakVdI4D378yg6S8uPJcxAiRxngJQFItuThhWJA3w7BgGgTxaVOTJ5kqhB4lPWW71Uj%2FLEA8kszQCtVoz1u%2FPmiHJ01vG3pacxy0rP8ytDnYuyAwBhNU9XTTl448hTT5Pk9ZxRfLAxKgoVsTwVrgqgPPy7tUJQELgn1i%2BuYQpsKCpRqiA%2B5fvt1q2rtROoia5bLst6SMeOJBYkd9WqIvPD3%2BNcifGIticjHU4arOXimhqfBVrKuqPQJOfCLMM5ctsJ5DRRkkPlqvYcRT%2B3XGLBLVm8si9qfwB0ZuKcefld7v1iB7X3BAIBvxEFRa7zjqY7V%2Bh9IhDrnPr4WkEHVbes9Dy%2FMtS5cBu4EBFc%2F5Urr3CHhIrlw5mptMlUSUIsC0MyRdB09xh%2FQePC0ag7946Kn8fUHg4LWoR%2FDhs%2BPGgKv32RqcwXj6PQzVKokkSMBGCXpSRuuSM8GHkPvhtH3yOwOT34ODME7nw4MZFwhHLG%2Bcbm7AsLzPdj4ejx1gFkM4utw6p7rmBjvWaLj63ToYdoMBwkQ7BV1GUT%2F6vIcMHS851iwcxCGW%2FnWnXLSs%2FzK0OdRctm1AY0XjRoygo8%2FMgjNuLTI5DS8HR5ZjVRdiwIqZwPfYBfQy%2FSIEpz2ssuv9xzCSf%2FepJF0mvS5IJ%2BxMZfMkGDZmhSEIbP%2BWAg0g9XMwBAF057Jxl8xEoADGS0cIPvsfF6uOpx8eP1JAAJ8FpQJO9FP4dCUsScGYKBDA0xCy7jD8RjIC1yfF5QdF4H26ZzZBGJGkTNXj7C%2F3DG29Pst489Rp4ixXdCA2dB3ahuWel5fmWoU0CmOnIA4Y1cDOpTsEDEUkVmR2dJT3DcKAmAwyJq3qU5NI0pGTf1o1Om2L0%2Fu8defuklY0Om7OdEZQDQ7SrM8uQf%2F2Q3%2F%2FgncTuIGBAc04%2FR1OoWEjc07mUsN4vP%2F6BHsy3SIAOL%2FEfc7W4x06p5QsN9JmL7JaInFNRh9uI4tGLQM4sYbEP1EKUhM32jUxPO3zzpwQOSKAVmo88cwwIXCoOagieR75BTt6z0Un5lqOHsqMklxVa4WFxwLBTPk0TRpAxWfRRyAUgJ6BxkWM%2B0ZWSgSJ1YYGPHjnWK8P3vfs9luitUUX%2FVipX23F%2F%2BYnNmz7Fp097SAvGzyaKwTPvcGQMjDeT036G4yILWRXGZeD3oDZtTpswdJuZcugQ%2BzgKTsrgAEuWD%2FUCh%2BO7cwk3n9aw9%2FeRTdp1kSxJ%2Ba2tr0wOEFmlohJVCU3DYIAESmESqFVYeeqQB3VVA3mQblq1H845i6kh5tZmS7dAes9RaH9ByExeYfzpTegA5WuistOQ9eCQLHV5JXkuHfXplInc3jzr1VOskHkmS6go5VGiniMqs1NRPscd775%2BoBV87wMC%2B0uCNxyRTpsQfg7p4adwkWyWZXaAWDEYWrPyNhQfwLPYo1YWagdJAbLVA2NzP528vT9WtJ3rbYUQd9uppCxa8ZytqVyIN7s4pVG9KpyKbh%2BPhMCK3klmHGQ7PoDld2uZcm5DSQqMGfWmOJF7%2BLx9kCtBcyCy1nTu2V2uRh6sbEHBhEmUJ4GIB08B2IPmCqTG1yGP7eJ%2FOPnoKIFd%2B7WtOUwYOGkT6EYtBzx%2F8yc03p2kEFz%2Ftso50L%2FbDe5S6RR9HEw%2FKA1i4Qb6DyvXflDsfRwj36ga48HAsJrMGnH%2BGFqpU%2BydW5UuXXioVpYfTBpJz4caQjEULFtgoRRuqYVWbqDTJ82KOmA4qqnLXLryLLCg1cHaErOevQWN2oQolUYhyub%2BfKUATI5Cl1qpli0VSADar6lFrpl7AshtcwRkB%2BG4tugTrlYRGRktbMeIwsIrfVxxy6j0C6FlQpi0x%2B4xEVRadWFrOAaud5DN68DFAjhhdtnHre1C7dq4tlzAIoB8MhGRAoYxAL9Lfg6g6eDTy3YLqarfO0BCs%2BptvvuFS46dPG6mYbZf5LN2KqXPmOZTLz0eA5bz5m86xyOhhI%2F4mPps%2FypqV8%2F0JHZibKccKsQ5Z6kuXfVivtP83FUwPQJNpPs9zVJDgt2lNOeGpAN35Mit8qACPiVvcOTagiNsT4%2FUDXGH9ecR6YYkJGaWgDOcDXWA79kfMBdyXyqKSxw4FwOoHASAsNBnpoJ1pHnXEM0UYTBw3IgPDjxF1Qo7UDHH1NV%2FHMieLVXMpb%2B7s2aaq%2B3bKiBEKXvowgpp2g7mpkc75Pv37wYaSKNigKVH4nEpTER9OqbJPdu3cMVvdstJL%2BZW1JkD%2FnWoGAI1siygQAzAoqILljtJboSlDAQCqOw4aG3iNCxklZ%2FXcOWyS0tU6GSyF4OMcA4WFRRKcm23I9yPwCRoSxRgTXlz3rxpzgiyOGShLezPp0dJ0AcqyZvUaT7civ%2FDt6dNdralVAgFhyn31Gq5ynEvLtUgk%2FgLFhoP%2F689u4LJ7f4yBmqqgGvXxyvLNkCLRwt8ccerIQqYoh07Ystbmz3%2F3VYDDwpA1AGlKeOi2bSuPPMGwslykoBbEOPB3qsQB2Rr0Ev8bC4ZkhYXCMwhQAReDA3Xhn2v%2FiaVkoLBNWPQQ1OI4TYs0su%2BIx2iqhjQN4%2BTzSd%2BtTaN66A611L%2F296rnv8v35DyIxfYNV2jWOPGkk7hpkH%2FGYmAk%2B2GAIvUVcmTJN5glwKbxXZitNm3cjryIFMr%2FhEHyhmWslTKyM9ak0y6bOW%2Fe3FopD4dy77958%2BbptmhnK3vlRC4mQOdiJi5vDzM1Gnw0ncnSVHJjEeZxyR97XDJeuyhGwz6Z6nFKEJMN%2BNMKB32PsSIAEH0Yy9cUyIAMGsPfDIzI7WPfkeEdCa%2F9BvR30D%2F37J8F2g02UKoLoa3djjhCLuujJN89yWDzWcbvdCWFIloxsb7bRcnKypslxXkafH91DSwAHdRQIbJaIuEAx85TlrGWxxuVsQ5HLmrV%2F1tTIxPjum9%2BE2usAugvOBWIexFiZVo09zQoAAW4E5d5yGb16tS18M7fxCJj3aOeBlY6AI011ELsY7mM1wFSQJsGcNLTYDb2yyISGpTi5JZU0m%2FlFhENfJkMCwXIGZDMGmnuTjxGV0Xuwc1%2Ffv%2F9Tj969emDlSZOhBQqQAvA47NBNTj%2FJGzWbKu2hzLpDrKoPnBOjoGjBSrD%2FwuHCp2gq3dGnHraMnXLUs%2FzK4v9H%2F%2BY9YSpAYhH5fo%2B86wzcdnK4fARd3pKeY8ao%2Fo3KUUBkgBdGowMFGWqrOUzJBL4wIh4CBox2HDXLVCXXD79%2BbRDI71%2FBgnA9Uf2x%2BucD9afaMGFktzQflWaAZC7hxAqwXOMOucbhcihPvB36NXqxIkyQxX4z%2FjsZ23igw%2FaIEULAk5KfsXsEGsFD3ltexBxLfrsKitLivQg%2FSHTxbYMfhav7y9a%2BIS6Za3n%2BZXBTlbKzPnV1R%2BecsopnnV9lbx606ZNQ85yyxVhoYVEtnP5LRcWKw1qmj%2BJhRH8OUJIk5Sq7b4gfH%2FhIvg5wUgB9jS1aMqLg7vCSZlBUEYSDdjsPcVNc0MiatehjGBhux7Rjf3Ia%2Bjx0xE1x3mHlu1U4JmnnvbAJNqxxx%2FPfb2ZVbiPIRIcm3uL78ojnX1QcXSdaFNjAwUnW%2BI8ISsGq8xMAu3RWmGtnEtLf6NuWeu5IxTIndX2ve9%2B94FLL730q9Pemoby4WGb06dN92n3mm98A1C4VcbqELhENFyqGPkeisa41QW0gBmVgUKGgIKZwC0klpvPA4TcHqw0LQ3u8NJxDlACqAqfw2LilYTKIDdyLoAK9zWFHgEX24BOzmXZ0iW4xpl9FPB0MDMFciCcO2YCnCN8Fq2b51h5PfLcyxuwrQ9u9G1AXKIZiUUwVh%2Bl5JWXX6LoDf8IKiWdahls%2BTFjzrGs9hdffPERFnxcfN1SAt5LwD8yl6bXSlKXgqtinZLbJTfjORYrtOjk70LEe1B%2BAE8kwfMs2ggDleewC5Y7pD0GAjfFZx9Y4T2CmWNEwRq2w3jgwGFRSUUkgE4MB7NC8pHkXBrDKrNvwMrdCMiYIYSVNCtuI4E7XwvVZsSy%2BLlt08BA5ci7jNmYkia9%2Fp97%2F3KJZm8pVYXXFsmNzv1WOhzcgULqT6lbFnueXxntAsPOGe%2BqDTp2oFsi4o5PGzXKrSqlDGJxxCPT%2FlqBAI8coAIETN8rZfXgkflcSVpL9ueEcL4tGnPZl7%2FsJbYA%2BvvKipny2BTwgFs4iaJzjyU9FnMB5pgNACWd8wDcEWONO1ozgpIQkvcBNRaTwai3I6UMtzgWWvEbCwCn9uN6udMGrHHcc5D98kGOzw%2FeS0JLyRBnBuD7Q6OQLAE0%2F4ea9xfZrBnviIMfbwe0qWycPu2tR9Utiz3Pryz3KVOmfNfUsJZnn3OuW6qpmjqZxllcARAuMoswJClKE3wkZwT5gLy%2BU2AimD5uyAOIwqESWeB44ggOwpp%2FoIv%2F01tu8UFxu2rPPfP00%2FBiPhcg5nGPgfy8FxYR%2FXz16tokIWA75yvP4xpmBZ9xXHFJZg4aFIc4kQP0PRloJSVlLPDgzdAqvgudxWPcN5G%2FscoUX8Qdn9wTMSISfY2ABcfaI1VCSxRVOOvuc8%2F7%2FBZ1y2LPffmyr1jW2zmfG%2FP6SUOGDAWsAIB0LAqGU1WJCxVx0AAcigL1wEpSEw%2Fr%2Bv4HNdR1QxZzrgutoL9XzcLtfRs8%2BAS7RoFLD%2FzyF%2Fa4rPOZY87ygTP%2Boott8AmD7Qc33oj260FJADZa0%2BcRI7F6Va2eNTIomB2iHBkg5riAl%2B8RgGah5xnmWGB2A4hjz7zPZ5DvACYB%2F3BvFsWcE5%2FlTmB8L0uC%2Fj1PkVmjsZF4agrLsG8G2oa%2FPPdsB3wtltGWnzr1Jct6v%2B%2B%2BiZdi2ZC%2F%2BvXvZ1%2B46CLil7nIgDlCRt1CAWQcLFhsLjILQOIuuK0wSkHcUYsYjV%2F8%2FOdcaL9vypVXXeWDgfsWbtfjgw884PThyq9%2BDetHvHNattvjc0ofUCaBetVkpQBu6A3nAeXAsEMJyCMMtzyNqDioB0mvWOUI0A9XNgCGOnTSohcgE%2BdCEFKjZ%2Bps5dyidp3X5ciXeJVUBqDTqKNUk7p7VRU07lvdj%2BzeoG5Z7Xl%2BZb3XNzTWPP74b%2F7I4m2JFoV49E4ZcYqv3GkhWcVFxduHjAYYGnSBuWc2FprpH0AztbPtf0%2Be5O5ytGIKps8Uz8SJUy1QnzH6s76fuXPmwq%2FhvRzn31to9R0CLrMDejlhmw3hbeQ3nxWQY%2BMYCFhY9o%2BVXb1mNTcXDTCzEVY88WYK8OUV0BUGr46zlQEI%2F05CWA21Q%2B%2FD9xkkdejV3BzIy%2F7%2B%2FbXXliqEdbK6Zbnn%2BbU39MmTJ%2F3E1LBKK5avQJWgrG2SBJtwVzNc2kzfZFjvvkOUgA8gsXIAw8zjnMVpPxZg56B0wKMlu62i9rM7NeCnyG5z586l5C4LNTxtTa1y0AK4bBLYVOpUg%2BqgDADPrqEX6Q7QCBribzonSdFF4qRZtCZ15xri7rMAFFCTB0juZHBnvgbPKSKT3DApqWYqy8z%2BseLUESH8oabm%2FW%2BrW9Z7nl97QcfxMX%2FSpIduoSQYVnbTxs1czFjc%2BWtYOfhpO2WdkHqE9YJ%2FYsE6%2BH0BK2IAsNBT3MSzKAROLQAvd6pVHLYn4KJC8NrsWf9wDkqRl7gHeFjkdFhmOG04PtM9f4e0FqpI9AAxjeMwc5C13UPfjYEHQOHKFHQnB5AQVTYPGY5BgeqD1QfYdM7NjLiWnU6naMwWAD1fWvLGMQMHPaluWe%2B5i8eNt72p3XjDjTW6tUP3D2V1sLhJbATghafCf5NIut2JAVhNLGVUS4oAYoAMP52qOOT1G9ZTT06Ohz6yynNcFwZIr%2F7tFZt4%2F3322quv2eFdusRtlB2U6ewRDsd5LFy4QJ%2FFMeL3hAmZD2vLOQA29HQsMAMNPo8CQ6ScwCdwC8xYZe6gxf6RHvkuQ4YOdY69M0mvYl9l%2Bjw0A3oB2Om8F8cA5Mwqd915%2B8Fm9k%2FbC1rpa6%2B9antRE91Yfsarr776AdYLywZguIiAFn4JrwSk1PYIhwgLs6a8F5BRmf%2FuO%2B%2BEVnCjT8WQnKpgp%2BZacM52AJ%2F%2FhQvwtAkQd3FcO%2Fvcc7lhvqdIWQwO9cbEhc6gIpCIhSHlBViEsglgjlmEz3gcd3N1%2Fzuf3Bd8k1t2amc8%2B8zT9p3vfNuaUdJLVr5S%2B6JITC95%2BXD7x5qhHjmvriGx3g3O2XkPYNMYWA%2F%2B%2FP7xWqDuFWCmlbLa3osaVrjm%2Buuvv%2Fz222%2F7FYDZ7QEk46TUH6mRcVirTnq9bo%2FJtWG5URRO0L1PjpdshyMHj2OfPr0BInSGvyXZ3QDwCFzyfEDSqNSSiD53njhNwBLyGQqKgx5KfVUItHD2EtemnbdzXE4FkIcKk4qxznFsz9weddoo40afOVnhd8XzOZ5HGXp1pTy3akPtYEBwTruz4vMlDGDATNb4r3QDz8dsL2q58y%2B40PbGNvbcc54ZO%2Fbcs2pqFge33V1cfO1agIVHEXkhgERPp5mQ3ULNN6Z9qErCZRvd%2BcFNiSgNgMyGWgCosY7w9JIkiSDiQ4jiozUkNwYlXqOubqfLZR8rxiSXcG3exxoHHcLKA%2BxY%2FLENwG3RsjlBRH6%2BgP6u22614Z8%2BVQm9VVESjAUoZQnYLm5j4d8fCQ95s5Y4XLPOtpe10p49e9ne2KrfWzhmwICli1TsuweFW%2BDTWEjagdKgUQzapm5QzwVP30Y42ura1UYDGHEfky2bt6pvcZ4MkFEpiH5j4CD%2FwZV5TiP1K%2B7pXTQHFKlbsvgCtRw3BOhDgzgP7HCcQzhUsLiRsc4jg4TyBl4m2MNda%2F114ktYrHJzzkYdg9mDrxFhrgC%2FUXSjdWUb9lWcPPm%2Fh9te2PL6Yra39lGjRg5RatJqpDZc13jO4K5cfMsZFpbnAUAuPmBqIr15R5v2BFJaIQEZ76NCUEePElrIhPBfaARBR3Ssd8Rz0AKUuquWbZI1fX%2FRIgLtkRGd57LvsMaAmcdCusfi0QuS5zyslcZCl4FTbPTFKHybvED2l9x0KM939a9z38SfjdDzJfy9t%2FU8v%2FbSjl677rJLL%2B2jMrsrFlS%2FZy%2B98KKtkcXF%2Bxe3G0a94MIn1ZjoUARc0klAfTm6sVs4XM4WlZkCXF6907O61RvZp7%2Fm%2BHXeGtZ1d%2FQd%2B8KyH9W9ihJdxJKQ3MtgwIJH2hfSW6JM0BOQpx4ZhNwQqFHUCKcK6oW%2F57q2F4NnVmIg830ZXMV77r7r%2BPq6htfUbW%2FseX7txV1gWbVhypTHTlMg%2FS4s6F%2Bff85vHo%2FE1U5ckpp4ZIkwfTsgAkweW0F3qc%2BdMWVYX%2F0dFjpA1a5dB%2B6PQvSdc2d3iniHA6dAnVheXod77xKP7tC%2Bg4DcnOOiG8Oj6QHaBMg4UdTjveT4uNxJ28onqVNsH1Yd%2Fs95siCllgfn%2Bcc%2FPHG2Sp3NVLe9tedOPnmo7QutS5fD%2B9zy%2F%2F30jbfeeLPt7NmzpE7cZBtlnfEc1spqE5iEhgxwcUQ0UTxwbOixpIk7213TTP04YAAS0W4s3Pg0VhJQwckTF7WrLaEH%2B3uA2AtRuusdd3ziLYQ%2F89wBXIhCOXwmaIeAnLd5ilmhpNfBHQ8Rt94OoBlUPvA8e73tgT4jPPLwpAvM7Anby1sOWWofaQQkHX75ZZc%2Ft%2B6TdUfjGifQHvlqxYqV3N4BUHucMCDzBZqvp3IO6Ih5DitLi3BTtqY%2BB1Rlq%2BgHn4%2BYZMoZAMyGujqsKBYfcCafi0zzfFQzikGSSp8KQPN3DASX%2BeDvxJgg5dGxxuwXMEODBPKOZIhvfOD%2BiYD5Rdv7G8Ua19u%2B0vRdl0uj7nvttdc%2BJav1OVK3Bgw8xtOZKB2L5MXdsrB%2BbmLBLS2JaIsbDxH8hKs7uDDTOUDl80z1fDadswilQXVgULDvsP2RygVg496H8GYHO2BuYpXjTrglpbmoGsV9WPgcAUxQDpQOeLOy4TspxmTxwkm%2F%2FtVpZrbK9pGW4%2FYG%2B2KbMH7CTd26dv0xgUX9jxnI7SWI5QhpL5G73EBHA4EsKLUQW0t0GrHFaLpYfu697cmzxFjAxVFEkPmQ2JD0DpP1J77EObZZUlrXa24QFchA4KY9gBKAAvZEf06qGyXSW9QOIRSW90kVg9Js2LgB0DNTyGq3tnfenv6byZN%2BPQ7mY%2FtQy2v6tX2x33333T%2BZ9vb00W5dBWLUDW62yXQOQIopEPtCManfAdCY3gEdIMWNHbc45vbCyHBUWEK9QDmBBuDto%2FISlh1LDLWgLh4lF0gLw9qiRVN8EWWFfcei0I%2FdSAfUFiV4nXK0kCdwsZITqqvnR70%2Bj7H%2B05%2F%2BcK1KAF808vTTC%2Bq2L%2FVSfu2rbd36Dc8LfL0rDzzwe%2F1K%2B4%2FDlYxagWVlcQWgAOQBogtYVkBNEipJuBeOGycFZYVLfn2OPhrrjt5LsBA0AH0YpwmlcqV9HwCXpZiLW1XoC7o2Vr0yqbcBn%2BeOsZYzvd4ChwxPLUqZFTgX5%2FHmmeO8%2F64qRq3SOZAgDHWZPm3ai%2FPfnXePmb1g%2B2grXazE0n28LVSB8%2FGv%2FG3qPVde%2BbVH%2B%2FXv34dULqw1qgfxEatqaymRgE6MxQRM1IDAEgNkrK3UhEp3a7OwpL0nq%2FnG319XGtgQ58%2BAlvoe8Ay8lhQ2LzRGLiBUQgOndWuPqmuW6NHQHQYWAwkgE1wFfVmztNZzEol%2F%2Ftw5Y9Gx1zw0edKVZvY0asm%2B3Er9H2D7W%2B3qNbNvvOnGo%2Fv363f9hAmXfGvw4MHtcFrg7aOtFXgBJlo2VhPXN4s96EHnYhdrIzA%2B9ac%2Fco8%2F9U6u%2FT4y%2BSF0Y27d7MkCgDqi3XB6wItzCV1BQUEFISOFGGUWdzm97mAWqKEYM2ZMZ9ZwanKUUqYGHDOwqMI6E5995qkfmtkms%2F2tlKis3W1%2FUzjtbdXV1Q%2BOHv3Z7wwdOuw7kvXK2rdrR0ATlphFXHKn18PdYnPjzXmKdvvd41O8KtEhHQ%2FVgvF1slq4VZxc3N39M0gloUPToDBOJaJEGbdpI%2BC%2BLjLV6zxeGk2chefixUtUbHGbVJlBHJ8KrI%2Fccfut3FpgCbRnf0sA3fSfYfsbANz8%2B98%2FccPMme%2F8oqqqx0iV6vrMCSecOHbAgAFM9cSFOAemUCQRe1ht%2BHJVz55QEG5rzAKS3EEUDFLCoi5d3M87LHVktLCARFrBK5ksHOtRNJRk0NZVCwVZMQhenzVr5pOiGJS5nUNCQZO2H9D%2F9p%2Byv%2BG2Xrlw0aLJ9DfffLNPjx49xvY9uu%2FZBx9ySH%2BCieLWaj169XKrCshJOIVLw4vh4lhxQJzIgUkVUv7eBqhR1fxR8Ebq87jptge6DOhJtcs%2FWr70rWlvvqD6fb83s9ftP7f9gGYK%2FB%2Fb%2FoYy8Z5KGNB%2FLJCeoPt5j%2BzapcvJku4GS5s%2BgEwUQEipAng1Cz8eATS3YCNTnAAhgqEoCda3bz%2B8ftxrJQG52RZtu3bN6sKSd2bMFj9%2FSzHLL5nZ8%2BoeY%2F0%2Ft%2F2t9H%2F9j9rfcC2%2FrVtB0Clkc6CSZQcpLLWPZL%2B%2BygDqLmC3UjBRy4qKFi3LZa6lXpSonnN%2B4%2Fr1xbbFto3SkevEvbfLmGzV063yYH4oAC9SXMlCWeKZZJLZ%2F1Pb3%2F4P6UE4WBVIoqQAAAAASUVORK5CYII%3D",
-      "title": "Wikipedia"
-    },
-    {
-      "url": "http://www.trulia.com/?ecampaign=tiles",
-      "bgColor": "#53b50a",
-      "type": "sponsored",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABSCAQAAAAmjZj9AAAEMElEQVR4Ae3WfWhVdRzH8a%2FbnG4uHT1oBE4ra9UKLEEHRS6KNbNCloWtQEPSHkYkZAwCu%2BAiIxLDLBkYBUoLNjWo2TRry%2ByfaG35QBSI9OTSHta8bfO2u3ewc38%2Fzrk7Z5x71m5Lvq%2FPH3LPPXfeN9x7zhWllFJKKaWUUkoppZRS6v%2BIItbRxm9AH128zBzkP1sbxtOI3SXsYZA%2FiTEJCT%2Fnn0pO4ZXguVH%2BUB7llGQ5sAXj8UwDbyeBn00%2BL7iJOj4kDizLauAMLD7JLLCAnwiyCElbJ0Z2A6eRxGjNLHAtwd6bMIHCTowVmQXuw%2FiGakpYyLsYA0ydMIGFvMpJungEySzwDI4TFNvDDRgLJkZg9AlDONZ4LslJHEvGGJifpcDLKOcOKihjSnrgII5b3Ifthacy9bietuHFMY7Sllpt6pz3U49rEHKooZUeYJAa%2B6yzeYhrRbTZjRa4xZ61HHGtlG38jMUAB6h2B%2F6A4yHPf5rAUZY6spdgW1Ln9ODYzgw%2BxmKV51mYj7hWjBUcGHB0Ehv5Gz8tTDeBu3F86fo4bcARJzdC4EF2Q1YCNxNsPzlO4GqMg1yPcCEbSeJoRiIE9kNWAm%2FFIslhmmjhV%2FDeToSLOQfg%2B%2BbuR1JbQWx43RiNxFKrsoHpevmO7nELbMLo5HJ7Udvh%2FcUjCI34O01%2BRjd6b%2BAhbibH%2BaaMU%2BBpjDLEbhp9OBJMdgIX4%2B9FJHJgE7mI3XgETmFvam8jnh3BmOMECl8zUpKSyIF%2FUIyMc2DwOjFuMIFrGWkPEjlwG5LFwJkspoZaYmxlF%2FuIY8w3gRfwF%2BmWjiGwJkuBeazmK4LZQGEnXqfIHUPgbVkJnMXnAOEC78VrOzKGwIoMAi%2BNfJHpgvCBBQzgtiRrgVdFDFyPxQne4BlWspRyrua4X6DwKRZJpo9jYLnn%2BD0RA49gvMVkBLugwBew6ED%2B9cBfMKo9x1%2BPFJiPkUi7JRXS7x%2B4HIsdIQIfzTDwOIb7xjyb3lEC6wIDL8LoRTyrBf%2FAUghxK%2F0Io5O5CEIuM0MFNmMM8Vjq59u1HMMt%2FYf9YQoCAnNI%2BN7QqujDosIdmMcgRlVg4Cu4xelhiJ5QgWtw66adDobAJ%2FA1LM5xkkqfQOEAxlme5UYW8CDNeH1BvWDH9xjXBQYuYqRwgQWcwd9gWuB9eC3zDbyLUMS1QxhFSOAaIwYKd%2BPnR7ZimM%2FSsRCBQgP%2BnqfHP3AXjt%2BRUVbIOxEDhYc5i9e3XMPKEe%2FmCo6GCMxlE0N49bMO4Sn%2FwDuJDe9JZPSxkJdo4TP208gG%2B42tI5baXCRgs6ingz4gTjtPMBXhSvs6scvnARpooZU3KUUQVtmz3PfReWymiziQoIN6ZtuP%2BQd00s564TyfBmqgBmqgBmqgBmqgBmqgBmqgBmqgBmqgBmqgBmqgBp5n%2Bwe1m%2FPwFCNvawAAAABJRU5ErkJggg%3D%3D",
-      "title": "Trulia"
-    },
-    {
-      "url": "http://www.amazon.com/gp/bit/amazonbookmark.html?tag=mozilla-directory-tiles-20&partner=Mozilla",
-      "bgColor": "#000000",
-      "type": "organic",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAABWCAYAAABmQby7AAALOklEQVR4AezBgQAAAACAoP2pF6kCAAAAAAAAAAAAAIDZufcoG%2Bs9juNfQ%2BQ6pqmQGULKNbek4z6nOi7ndCFRYkliYY5EKZVOStFxkohcupAOknLpuKR0YRFyJNGIQy4HY2Q4TYbNmHnO%2B49prVnPep7f77nNNrt53mu9Tn%2Bwz172fPZ%2B9vPsTVgRzzCMZhiBRdiFTJMTWIMJ6ARxqQVS0LaA21AFYtIbU7AO3%2BIzjHdwvxXQF6%2FiK%2BzEMozBrRCfbsRgLMZuZJqkYwPGogvEpVq4HW196pCvDMSFrhiL97EB27ACb2AAGkJcqorb0A5t86WgJcSkPh7FAmzGv%2FE%2BRqA5BADs7rAD1iAPbtqGXhAH%2FgK70hAPQSdsh6rFiIeY9Md%2BqFrh8YeSgpXIgZu%2BRT%2BIAwn4CUH2KMSBIdgBXeexAE0hDpTAJth1ByTfczgHVZNQQjXo8fDbdIjGI1BVGs1wDk7aiIqQfBPhtCNoAHFoDPz2uqNX%2F%2BCbCVGojs%2FgtmykQjTicBR2DYDgH3DaGpSyGvRUBNUUiMJA2LUPTXEQbnobgh5w2yHEQzTeRlC9DFFojAiCbDLExvU4CD897WDQu2FXO7SH214xD%2FopBF1rj4NOw7%2FgpbuxHV56HqLwVwTd7dEatObIUBa7EUTDPQ46Dw9hC9yWg6q%2F3cm1OA8nnYTT5nscdB68FkEuvLQNcYoTmTNwUqbLw6XYaIKgmwSxMANB1tDjoLfBa4OdvtXIwUtohxr4A8ZD1ylU0gy6KBVBLYiFFxy%2BhWiPmmiNcdB1BlUgFirie%2Bg6CziqDcTkJodPvF7ojCfxX6j6Qj%2FowJsn%2BW%2Bm98OuC%2BgMsTAculr4HPRhzMFUD8%2FeXCzJ9w2cdAvEpIxmWOdxF8TCUG%2F3CeBKdEB7G23QBAd9nKx%2FAFVLICa1kQ5VKT4HvQOTMMXhn2%2BL8D83Q9U4iMKPUNXTx6A3ItHjoXELGptu%2B5rHcV3v8wR4h6MfvHcPQVcaxEI9B0fZqz0%2BWRf5GPQ809u%2FytgKVRm%2FvX9ej0zkwlxjiMJSqOrncdARm0tppXEcqs7gBoiFvVDVCmJyFdYqHqNbIArvQtUdED1fJ443QSw8BlWzNZf4zsKu07jCw6B%2FQkmIya1QlVXwNyejAdqgG0ZjIEpAFHRXIx7wOOjPITbmQNVSiI033Q8aQFKBx6gHRqE%2FRGM6VKVCPNoGXaMhNtY6eUFS2ABV3TwMegTEQmnNe%2FdTpht48nEhDXoKxOMJ2rMQG896HLSe90EPhXjwAnRtgdioiHSoagtRmAlVf3c56FzcDLHxeRCDroB66IB%2BGINpWIrMwAetv0D%2FElQNhNh4rpAGXQmN0REP4im8gSU4AlVDIC61hq4I6kNsNMMFzcluQ59PqhUuB30ciRAby70Ouj6exkKkIQf6ghv0QxB4%2BXh%2BAMTG2AAHXRujsVB%2FchzooMtjL3QNgih0gqpjuAqi8DBUpbkc9E6Iwkq3g66P95AFU1Ed9MM%2BBj2wkAd9HWbhNPQFP%2Bip0PUZRON%2BqNqHyyEKvaDqKBJdDHp7kINOVR%2BCwkFjMM4gv6gP%2Bo%2FQdQLVIRrDoOoAKkAUHoCqDFR1MegdQQ16NNz0E%2FYguxgN%2BnG46RB%2BRFZAg47HIejqA3Eg1cGgy%2FscdDqujvagu0NXBG%2BhCxohHuWxppgMuit05eAD3IEbcQXKYW5Ag14CXXMhDg2Fqv0o63PQx1ElWoN2eulmp%2BJSyrJiMOhyOAhVhxXfLpwWwKDvga50JEAc6glVJ1HD53vodCRGc9D9oSoTyRAbK4rBoPtC1WnN5bEZPgddFT97%2BSqqRkeoytF%2BUqzfzy7ERXPQy5xcGFf4uBgMWvdnfA2iMMfnoJc7GN5jEJdqa05w89AO4uO6%2FipINAd9xOcXZ7ZCVY8YH3RJ%2FMfJF7AUvvZxvfhh6MpEF3REClqgGkSjlIM%2FW2%2BIwjtQ9WK0B50FVapDTjLOQ9WwGB90Ev7n5vsKJtVxzuMrdF1cgJeO40tMxe0oCbGwwOcRehdUpUR70Kd8vEI%2FAV0LYnzQ1yHb01EIGAld%2F4RYmIug%2BsbmMe0DVd9DbDRHruZJVSnagz4DVdMU36PNhq6zqBbDg0528Ao9R3EEy4KuC6hjum0ifkXQzTW9WsfjOFTdB7Gwysm5RbQHfQyqsi3OnhthJ5z2YQwPOg57oOoiepofI817Z91H1Um4iMCzeIGZDFXH0dTl96gvovGlGPRH0JWLhRiJRciG24bE5KCBT%2BCkRRiJd3EW2hRfe70GEQTdBYuPxas5OAqdx0eYja%2Bd%2F2Xc6A%2F6QUSjVTE86P6IRhuiMOgIrinkv7R8EJUu1aDjccLnM345VO1C4xj%2FpPAA%2FDQHqvajqYtBn8B32GiyCxmuBw3Mh99y0ARyqQYt6IRcuO0c7tJ8%2BLAWVwb8HnoCVA0KdNBAOx8%2F4N6awXxp8b3jZIsPOT7FKHRGEsRGEv6MJ%2FAp8lCwZEghXFnJQArk0g4auNflWXWa6VOkqjhscTmqNMTCoz7%2B9Z0pnq59A5Ogqj3ERi%2BXj9GPphPqRPyAgi2xuUZcGSfwC2ajOcSjmzAdWUhHZYjCBLhtM5pANOI0R5CjEIVNsE1M6uB1nIRVv2ArhtgMtTq%2BQTYmQxRaYT8yLOzT%2FE3q23DA5rY7UA9i405k2PgOVRx8XPwqjiEP5rKwGak2j1ECNjp8jOqiFiQgtV38%2FzXH2zilOUJvQh%2BIQyUwDxk2JkAUHlPcdoXqHzfpit7ohQfQDbVQCqKQgAYQB8qiooWyEI1yNrctDdGoaOMyiEOVkYI%2B6In70QN1UdLB%2FTeCBCEyM7ksykRmJZeCBCn%2FCkhPjMWbmIOJGIQmiIN4UNGGr9vqbxwqMiLTkxMYbkuMxHv4At9hL%2FYgDeuwFKPQElKcROeOZieV4r%2FlIB6EZiW34L8TsA%2BGG9x2HKS4iMqd8MAuwEEMx2WQkCMJmImLMDw6xs%2FgckhwwkFPg5FvK%2FpAlEJXIxOGhTwLho10XBG%2B5QjeRBgFrERHSMhSNbyLN%2FEM%2BqITmqGOyXXohVUwTPaifDjowpEKw2QluqMExLfQ9zAKWBueFBauHjgFw2Q97kUpiGehlTAKmBcOuvDVwHoYFn7AM0iyvG2oArphFRajOqSALTAKGBoOOjrKYCoMG1mYjTshkVB9jMFOGAW8bLoqctT0643DQUdXH5yEobATr6IVykCKiWvRD6vxKwwLd0PytTf92lZIOOjoq40lMBzYihfRGXGQ35kkDMJC2xEDB9DVdNsnTb9nRDjoS%2BtBHIbh0G7MRXfUg8SgRLTBKKzDCRgac1EFYrIORr6LqB4O%2BtJLwEREYLiQgw2YgWFoUgTfnsShKm7F37AYh2A4tB3dIRbqIhdGvncg4aCLjib4EIZHZ7EfqzEe96MD6qAspJDFowG6YDCmYxt%2BRh4MF7LxguYDkjGmJ%2Fe14aCLptb4AEZAfsUerMcSzMCzGIg%2B6ID2aINkVDWpibZojw7oi1Q8j3ewDJtxABdg%2BHAGr6AuRKEEdsPINxwSDrpo%2BxMWwYiibJwxOQujkJ3G62gIcaBlzH0yGA4aQFNMxBEYvzM%2FYBhqQhwqgfkwsAeVw0HHpgSkYjmMGHYas3CPx%2B%2BzlMQv2IkbIOGgY199PI4NOAujiDuI1bgPNSA%2BNdCcMIaDjmGNMBBzcRhGEZCDzXgJPVEJEgoH7VYiWuIRzMeWKLz3jiANn2Ay7kJNlIaEwkEH7Uq0Qk88gilYgK%2BwDRnI1NiPTViNtzAOA9AZDcKvwBbdQf%2B%2FXToUAAAAAADkb51II4Nq6Bq6hq6hG7qGrqFr6Bq6oWvoGrqGLrivzmg60A23AAAAAElFTkSuQmCC",
-      "title": "Amazon"
-    },
-    {
-      "url": "http://www.bbc.com/?ocid=mozilla",
-      "bgColor": "#990000",
-      "type": "sponsored",
-      "imageURI": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALQAAAA0CAQAAACJdNphAAAErElEQVR4Ae3bbWxTVRzH8XYzHWBhGyoCgQ3MFNQ5MRG3N7ii20JC1CVMwc44MJlIREM0TsFIxpshPrwwBGJwgsEs8SFRQbKRmEweFBNENh2oc5tUwkj3kLAAy7J23dd3y%2F2fWGlzzz29L%2B7%2F9%2FL8c87pZ13Tc2%2BvD5PViE8khMkKKas3YrA86DTLg%2FagPWgP2oP2oD3oOENEiDBMXB%2F0Tqr%2FM2upZwdfEnUUujbJ6rVsYhdHuWYQOs53bKeKQrItM9zKSl7mIJftQlfj%2B9%2F4KeMgEw5B591g9RyqOWYA%2BgwbyLuBQwkNdOiHlinmrFFomceIOgh9mpX4Us69vEO%2FXmiZaRw2Ci2zgF5HoK9Sjx9fmgnphpYJ8JNRaJlChrVDd7FYdGcQWqaImFFombBm6HZmJlkpj2LKCVHKYgJOQC8gMpUuDrGF2coSzQ5C10ytfYFfaCGsvMgsujVCf88MVMAcnqSFi6JvgvM08zS5OqEXodYQK8RWyh2EXo9avzFfzPWmNug%2B8pXO6TQwSPIa5wsqnIOGQYLic9okNLTJuTRBx3lA6Sujh1Sqk6fwOwMNj4stDRiFRnx4LdEEvVPpWsc4qVcHjc5AbxabihiGvt%2FSsVAL9DBB0VNDAi1lFzoszkejhqELLB3LtEBvFR1LGQV3QN9h6bgLjEL3i%2BPEcxqg49wmOk6CO6BbxLa2GYZ%2BQczVqgH6iBhfBW6AnqRZfJfNJWoQepQtYqaHmNQA%2FZIYP2IcWhxYznGaz3iNJcr1q8%2FBwIHlV37gEzYq%2F%2BK5%2FAEaoJdZRoPEjEKnlOm0ZPSi0lnQAJ0gxzL6CLgNuoRuyBj0Gq6AFuhLYnSTW6Al9R5iGYLOpoJDWqA7xehbLoIWKaTdLLTICvpsQ5%2BSo66CFsniXbPQIrNo1%2FqO3pwp6CDrLVlDJUVk4VPykUPQRZa166imnHnq2sygwxb0gHKLDHDLgWWEA9wttnczvcYOLBfZxS1ivnuI2YBGXB693U3QABPUi%2B1vMAYNMESpvPlgC3q1GP%2FZXdCQYLm4E3HdIDREmWXpK7UFvVuMP%2B82aNgnNnjUKDQ8K77sXbUBfZlscSPjH7dBHxMvYK9h6CYxZ5cNaKgRHavdBt0qttdkGHqbmPOELegz%2BEXPDtKtBHHnoBvE5vYZhi4Tc%2F5pCxpqla4m0qlTPEjEKehegvI9ZRT6KzFjgDGb0IPMUfqeYYRU6nfW4sfnFPRJFolt5TNuEHq%2F8guMKrAJDW1kK51z2csYySvOYVZNHd9sQhcwYskA3RznPR7Gp%2BQNcAA6LFbv5zytbOdOdXW%2B0QANH%2BBTw2w28i1DyOrhU%2BrE3Xg70GlkHld0Q6eRStACDW8nXWMuZVTwBCGK5c%2FGTEIHOA4Zg15IVBs0HGBamusbg86jDTIGvZQ%2B0AgNHZSkz0yAfmehq7gAGYK%2BiRe5BpqhYYL3mZOGQT6vcwmcgp5JmBMAGYGezyu6f4guaow9LE%2FhT%2F0oH3Nd28NCImFeZTc%2FEjP1sJBIHVvZzzlDDwv18CHruI8c5VZHAZU08DUj3uNvtqFFTTLA33TSyV9ESZByedBmyoP2oD1oD9qD9qA96H8BYQFtOf3AiaEAAAAASUVORK5CYII%3D",
-      "title": "BBC"
-    },
-    {
-      "url": "https://webmaker.org/?utm_source=directory-tiles&utm_medium=firefox-browser",
-      "bgColor": "#083e5f",
-      "type": "affiliate",
-      "imageURI": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNy4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI%2BDQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMjQzIDE1MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMjQzIDE1MCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI%2BDQo8Zz4NCgk8Zz4NCgkJPHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTE2Mi45LDgwLjVjLTQuOSwwLjUtMS45LTQuOS0zLjctN2MwLjEtMC4xLDAuMS0wLjEsMC4yLTAuMmMwLjgtMC41LDEuNS0xLjIsMS45LTINCgkJCWMwLjMtMC4zLDAuNC0wLjYsMC4zLTAuOWMwLjEtMC4zLDAuMS0wLjYsMC4xLTAuOWMwLjItMi45LTIuNC01LjEtNS4xLTUuNGMtNC44LTAuNS05LDEuNi0xMi45LDQuMWMtNC40LDIuOC04LjYsNS45LTEzLDguNw0KCQkJYy05LjUsNS45LTIyLjQsMTEuNy0zMS44LDIuNWMtMy45LTMuOC01LjMtOS40LTQuNi0xNC42YzAuMy0yLjUsMS4yLTQuOSwyLjYtN2MxLjUtMi4zLDcuNC02LjMsOC44LTEuOGMwLjEsMC4zLDEsMC4xLDAuOS0wLjINCgkJCWMtMS0zLjQtNS42LTIuMy03LjgtMC45Yy0zLjgsMi40LTUuMyw3LjctNS41LDExLjljLTAuNSwxMC4yLDcuOSwxOC4xLDE3LjgsMThjMTAuMiwwLDE5LjQtNy4xLDI3LjUtMTIuNA0KCQkJYzUuMi0zLjQsMTAuNC04LjIsMTcuMS03LjljMy4xLDAuMSw1LjIsMi44LDUuMiw1LjJjLTAuMywwLTAuNiwwLjEtMC45LDAuNWMtMC4xLDAuMS0xLDEtMi40LDIuM2MwLTAuNC0wLjgtMC4yLTAuOSwwLjENCgkJCWMwLDAuMiwwLDAuNCwwLjEsMC42Yy0xLjQsMS4zLTMsMi45LTQuNCw0LjRjLTcuOCw4LTIxLjMsMjEuNy0zMC4zLDMwLjljLTUuMyw1LjUtOC45LDkuNS04LjksOS41czQtMy42LDkuNi04LjkNCgkJCWM5LjMtOC45LDIzLTIyLjUsMzEtMzAuMmMxLjctMS42LDMuNS0zLjUsNS01YzAuNywxLjEsMC4zLDMuMiwwLjQsNC4zYzAuMSwyLjIsMS44LDIuOCwzLjgsMi42QzE2My4yLDgxLDE2My41LDgwLjQsMTYyLjksODAuNXoNCgkJCSBNMTYwLjEsNzAuNmMwLjItMC4yLDAuNC0wLjIsMC42LTAuMWMtMC4xLDAuMy0wLjIsMC43LTAuMywxbC0xLjYsMS42YzAsMC0wLjEsMC0wLjEsMC4xYy0wLjItMC4xLTAuNS0wLjItMC44LTAuM0wxNjAuMSw3MC42eg0KCQkJIE0xNTUuMiw3Ni43Yy0wLjIsMC4yLTAuNCwwLjItMC42LDBjLTAuMi0wLjItMC4xLTAuNCwwLTAuNmwyLTJjMC4xLDAsMC4yLDAuMSwwLjMsMGMwLjQtMC4xLDAuNy0wLjEsMS4xLTAuMkwxNTUuMiw3Ni43eiIvPg0KCTwvZz4NCgk8Y2lyY2xlIGZpbGw9IiNGRkZGRkYiIGN4PSIxMTkuNyIgY3k9IjcyIiByPSI0MCIvPg0KCTxnPg0KCQk8cGF0aCBmaWxsPSIjQzAzODJCIiBkPSJNMTExLDg0LjhjMTAuMiwwLDE5LjQtNy4xLDI3LjUtMTIuNGM1LjItMy40LDEwLjQtOC4yLDE3LjEtNy45YzEuNSwwLjEsMi44LDAuNywzLjYsMS42DQoJCQljLTAuMS0wLjQtMC4xLTAuOS0wLjItMS4zYy0wLjctMC40LTEuNi0wLjctMi40LTAuOGMtNC44LTAuNS05LDEuNi0xMi45LDQuMWMtNC40LDIuOC04LjYsNS45LTEzLDguNw0KCQkJYy05LjUsNS45LTIyLjQsMTEuNy0zMS44LDIuNWMtMy45LTMuOC01LjMtOS40LTQuNi0xNC42YzAuMy0yLjUsMS4yLTQuOSwyLjYtN2MxLjUtMi4zLDcuNC02LjMsOC44LTEuOGMwLjEsMC4zLDEsMC4xLDAuOS0wLjINCgkJCWMtMS0zLjQtNS42LTIuMy03LjgtMC45Yy0zLjgsMi40LTUuMyw3LjctNS41LDExLjlDOTIuNyw3NywxMDEuMSw4NC45LDExMSw4NC44eiIvPg0KCQk8cGF0aCBmaWxsPSIjQzAzODJCIiBkPSJNMTU5LjIsNzMuNWMwLjEtMC4xLDAuMS0wLjEsMC4yLTAuMmMwLjEtMC4xLDAuMi0wLjEsMC4zLTAuMmMwLTAuMywwLTAuNiwwLTAuOWwtMC45LDAuOQ0KCQkJYzAsMC0wLjEsMC0wLjEsMC4xYy0wLjItMC4xLTAuNS0wLjItMC44LTAuM2wxLjctMS43YzAtMC4yLDAtMC40LDAtMC43Yy0wLjQsMC4zLTEuMiwxLjEtMi4yLDIuMWMwLTAuNC0wLjgtMC4yLTAuOSwwLjENCgkJCWMwLDAuMiwwLDAuNCwwLjEsMC42Yy0xLjQsMS4zLTMsMi45LTQuNCw0LjRjLTcuOCw4LTIxLjMsMjEuNy0zMC4zLDMwLjljLTEuMiwxLjItMi4yLDIuMy0zLjIsMy40YzAuMywwLDAuNiwwLDEsMGMwLDAsMCwwLDAuMSwwDQoJCQljMC45LTAuOCwxLjgtMS43LDIuOC0yLjdjOS4zLTguOSwyMy0yMi41LDMxLTMwLjJjMS43LTEuNiwzLjUtMy41LDUtNWMwLjcsMS4xLDAuMywzLjIsMC40LDQuM2MwLDAuMiwwLDAuNCwwLjEsMC42DQoJCQljMC4zLTEuNiwwLjUtMy4yLDAuNi00LjhDMTU5LjUsNzMuOSwxNTkuNCw3My43LDE1OS4yLDczLjV6IE0xNTUuMiw3Ni43Yy0wLjIsMC4yLTAuNCwwLjItMC42LDBjLTAuMi0wLjItMC4xLTAuNCwwLTAuNmwyLTINCgkJCWMwLjEsMCwwLjIsMC4xLDAuMywwYzAuNC0wLjEsMC43LTAuMSwxLjEtMC4yTDE1NS4yLDc2Ljd6Ii8%2BDQoJPC9nPg0KCTxwYXRoIGZpbGw9IiNDMDM4MkIiIGQ9Ik0xMDYuMiw1NS4yYzAuNiwwLjksMC44LDEuNywxLjIsMy40YzIuMi0yLjIsNS0zLjQsOC0zLjRjMi43LDAsNSwwLjksNi43LDIuN2MwLjUsMC41LDAuOSwxLDEuMywxLjYNCgkJYzMtMy4xLDUuNy00LjMsOS40LTQuM2MyLjYsMCw1LDAuOCw2LjUsMi4xYzEuOSwxLjYsMi41LDMuNiwyLjUsOC4xdjIzLjVoLTguNFY2N2MwLTQtMC41LTQuNy0yLjctNC43Yy0xLjYsMC0zLjksMS4xLTUuNywyLjgNCgkJdjIzLjdoLTguMlY2Ny4zYzAtNC4xLTAuNi01LjEtMy01LjFjLTEuNiwwLTMuOCwwLjgtNS43LDIuNXYyNC4xaC04LjR2LTIzYzAtNC44LTAuMy02LjgtMS4yLTguNEwxMDYuMiw1NS4yeiIvPg0KPC9nPg0KPC9zdmc%2BDQo%3D",
-      "title": "Webmaker"
-    }
-  ]
-}
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -31,17 +31,16 @@ toolkit.jar:
 *  content/global/browser-child.js            (browser-child.js)
    content/global/browser-content.js          (browser-content.js)
 *+  content/global/buildconfig.html            (buildconfig.html)
 *  content/global/contentAreaUtils.js         (contentAreaUtils.js)
    content/global/customizeToolbar.css        (customizeToolbar.css)
 *  content/global/customizeToolbar.js         (customizeToolbar.js)
    content/global/customizeToolbar.xul        (customizeToolbar.xul)
    content/global/devicestorage.properties    (devicestorage.properties)
-   content/global/directoryLinks.json         (directoryLinks.json)
    content/global/editMenuOverlay.js          (editMenuOverlay.js)
 *+ content/global/editMenuOverlay.xul         (editMenuOverlay.xul)
    content/global/finddialog.js               (finddialog.js)
 *+ content/global/finddialog.xul              (finddialog.xul)
    content/global/findUtils.js                (findUtils.js)
    content/global/filepicker.properties       (filepicker.properties)
 *+ content/global/globalOverlay.js            (globalOverlay.js)
 +  content/global/mozilla.xhtml               (mozilla.xhtml)
--- a/toolkit/modules/DirectoryLinksProvider.jsm
+++ b/toolkit/modules/DirectoryLinksProvider.jsm
@@ -23,40 +23,41 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyGetter(this, "gTextDecoder", () => {
   return new TextDecoder();
 });
 
 // The filename where directory links are stored locally
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
+const DIRECTORY_LINKS_TYPE = "application/json";
 
 // The preference that tells whether to match the OS locale
 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
 
 // The preference that tells what locale the user selected
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
 // The preference that tells where to obtain directory links
 const PREF_DIRECTORY_SOURCE = "browser.newtabpage.directory.source";
 
-// The preference that tells where to send click reports
-const PREF_DIRECTORY_REPORT_CLICK_ENDPOINT = "browser.newtabpage.directory.reportClickEndPoint";
+// The preference that tells where to send click/view pings
+const PREF_DIRECTORY_PING = "browser.newtabpage.directory.ping";
 
-// The preference that tells if telemetry is enabled
-const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
+// The preference that tells if newtab is enhanced
+const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
 // The frecency of a directory link
 const DIRECTORY_FRECENCY = 1000;
 
-const LINK_TYPES = Object.freeze([
-  "sponsored",
-  "affiliate",
-  "organic",
-]);
+// Divide frecency by this amount for pings
+const PING_SCORE_DIVISOR = 10000;
+
+// Allowed ping actions remotely stored as columns: case-insensitive [a-z0-9_]
+const PING_ACTIONS = ["block", "click", "pin", "sponsored", "sponsored_link", "unpin", "view"];
 
 /**
  * Singleton that serves as the provider of directory links.
  * Directory links are a hard-coded set of links shown if a user's link
  * inventory is empty.
  */
 let DirectoryLinksProvider = {
 
@@ -65,23 +66,33 @@ let DirectoryLinksProvider = {
   _observers: new Set(),
 
   // links download deferred, resolved upon download completion
   _downloadDeferred: null,
 
   // download default interval is 24 hours in milliseconds
   _downloadIntervalMS: 86400000,
 
+  /**
+   * A mapping from eTLD+1 to an enhanced link objects
+   */
+  _enhancedLinks: new Map(),
+
   get _observedPrefs() Object.freeze({
+    enhanced: PREF_NEWTAB_ENHANCED,
     linksURL: PREF_DIRECTORY_SOURCE,
     matchOSLocale: PREF_MATCH_OS_LOCALE,
     prefSelectedLocale: PREF_SELECTED_LOCALE,
   }),
 
   get _linksURL() {
+    if (!this.enabled) {
+      return "data:text/plain,{}";
+    }
+
     if (!this.__linksURL) {
       try {
         this.__linksURL = Services.prefs.getCharPref(this._observedPrefs["linksURL"]);
       }
       catch (e) {
         Cu.reportError("Error fetching directory links url from prefs: " + e);
       }
     }
@@ -115,46 +126,84 @@ let DirectoryLinksProvider = {
     try {
       return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
     }
     catch (e) {}
 
     return "en-US";
   },
 
-  get linkTypes() LINK_TYPES,
+  /**
+   * Set appropriate default ping behavior controlled by enhanced pref
+   */
+  _setDefaultEnhanced: function DirectoryLinksProvider_setDefaultEnhanced() {
+    if (!Services.prefs.prefHasUserValue(PREF_NEWTAB_ENHANCED)) {
+      let enhanced = true;
+      try {
+        // Default to not enhanced if DNT is set to tell websites to not track
+        if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled") &&
+            Services.prefs.getIntPref("privacy.donottrackheader.value") == 1) {
+          enhanced = false;
+        }
+      }
+      catch(ex) {}
+      Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, enhanced);
+    }
+  },
 
   observe: function DirectoryLinksProvider_observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
-      if (aData == this._observedPrefs["linksURL"]) {
-        delete this.__linksURL;
+      switch (aData) {
+        // Re-set the default in case the user clears the pref
+        case this._observedPrefs.enhanced:
+          this._setDefaultEnhanced();
+          break;
+
+        case this._observedPrefs.linksURL:
+          delete this.__linksURL;
+          // fallthrough
+
+        // Force directory download on changes to fetch related prefs
+        case this._observedPrefs.matchOSLocale:
+        case this._observedPrefs.prefSelectedLocale:
+          this._fetchAndCacheLinksIfNecessary(true);
+          break;
       }
     }
-    // force directory download on changes to any of the observed prefs
-    this._fetchAndCacheLinksIfNecessary(true);
   },
 
   _addPrefsObserver: function DirectoryLinksProvider_addObserver() {
     for (let pref in this._observedPrefs) {
       let prefName = this._observedPrefs[pref];
       Services.prefs.addObserver(prefName, this, false);
     }
   },
 
   _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
     for (let pref in this._observedPrefs) {
       let prefName = this._observedPrefs[pref];
       Services.prefs.removeObserver(prefName, this);
     }
   },
 
+  /**
+   * Get the eTLD+1 / base domain from a url spec
+   */
+  _extractSite: function DirectoryLinksProvider_extractSite(url) {
+    let linkURI = Services.io.newURI(url, null, null);
+    try {
+      return Services.eTLD.getBaseDomain(linkURI);
+    }
+    catch(ex) {}
+    return linkURI.asciiHost;
+  },
+
   _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
     let deferred = Promise.defer();
     let xmlHttp = new XMLHttpRequest();
-    xmlHttp.overrideMimeType("application/json");
 
     let self = this;
     xmlHttp.onload = function(aResponse) {
       let json = this.responseText;
       if (this.status && this.status != 200) {
         json = "{}";
       }
       OS.File.writeAtomic(self._directoryFilePath, json, {tmpPath: self._directoryFilePath + ".tmp"})
@@ -167,19 +216,22 @@ let DirectoryLinksProvider = {
     };
 
     xmlHttp.onerror = function(e) {
       deferred.reject("Fetching " + uri + " results in error code: " + e.target.status);
     };
 
     try {
       xmlHttp.open('POST', uri);
+      // Override the type so XHR doesn't complain about not well-formed XML
+      xmlHttp.overrideMimeType(DIRECTORY_LINKS_TYPE);
+      // Set the appropriate request type for servers that require correct types
+      xmlHttp.setRequestHeader("Content-Type", DIRECTORY_LINKS_TYPE);
       xmlHttp.send(JSON.stringify({
         locale: this.locale,
-        directoryCount: this._directoryCount,
       }));
     } catch (e) {
       deferred.reject("Error fetching " + uri);
       Cu.reportError(e);
     }
     return deferred.promise;
   },
 
@@ -225,108 +277,142 @@ let DirectoryLinksProvider = {
     return false;
   },
 
   /**
    * Reads directory links file and parses its content
    * @return a promise resolved to valid list of links or [] if read or parse fails
    */
   _readDirectoryLinksFile: function DirectoryLinksProvider_readDirectoryLinksFile() {
+    if (!this.enabled) {
+      return Promise.resolve([]);
+    }
+
     return OS.File.read(this._directoryFilePath).then(binaryData => {
       let output;
       try {
         let locale = this.locale;
         let json = gTextDecoder.decode(binaryData);
         let list = JSON.parse(json);
-        this._listId = list.id;
         output = list[locale];
       }
       catch (e) {
         Cu.reportError(e);
       }
       return output || [];
     },
     error => {
       Cu.reportError(error);
       return [];
     });
   },
 
   /**
-   * Report a click behavior on a link for an action
-   * @param link Link object from DirectoryLinksProvider
+   * Report some action on a newtab page (view, click)
+   * @param sites Array of sites shown on newtab page
    * @param action String of the behavior to report
-   * @param tileIndex Number for the tile position of the link
-   * @param pinned Boolean if the tile is pinned
+   * @param triggeringSiteIndex optional Int index of the site triggering action
+   * @return download promise
    */
-  reportLinkAction: function DirectoryLinksProvider_reportLinkAction(link, action, tileIndex, pinned) {
-    let reportClickEndPoint;
-    let telemetryEnabled = false;
+  reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) {
+    if (!this.enabled) {
+      return Promise.resolve();
+    }
+
+    let newtabEnhanced = false;
+    let pingEndPoint = "";
     try {
-      reportClickEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_REPORT_CLICK_ENDPOINT);
-      telemetryEnabled = Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED);
+      newtabEnhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
+      pingEndPoint = Services.prefs.getCharPref(PREF_DIRECTORY_PING);
     }
-    catch (ex) {
-      return;
+    catch (ex) {}
+
+    // Only send pings when enhancing tiles with an endpoint and valid action
+    let invalidAction = PING_ACTIONS.indexOf(action) == -1;
+    if (!newtabEnhanced || pingEndPoint == "" || invalidAction) {
+      return Promise.resolve();
     }
 
-    if (!telemetryEnabled) {
-      return;
+    let actionIndex;
+    let data = {
+      locale: this.locale,
+      tiles: sites.reduce((tiles, site, pos) => {
+        // Only add data for non-empty tiles
+        if (site) {
+          // Remember which tiles data triggered the action
+          let {link} = site;
+          let tilesIndex = tiles.length;
+          if (triggeringSiteIndex == pos) {
+            actionIndex = tilesIndex;
+          }
+
+          // Make the payload in a way so keys can be excluded when stringified
+          let id = link.directoryId;
+          tiles.push({
+            id: id || site.enhancedId,
+            pin: site.isPinned() ? 1 : undefined,
+            pos: pos != tilesIndex ? pos : undefined,
+            score: Math.round(link.frecency / PING_SCORE_DIVISOR) || undefined,
+            url: site.enhancedId && link.url,
+          });
+        }
+        return tiles;
+      }, []),
+    };
+
+    // Provide a direct index to the tile triggering the action
+    if (actionIndex !== undefined) {
+      data[action] = actionIndex;
     }
 
     // Package the data to be sent with the ping
     let ping = new XMLHttpRequest();
-    let queryParams = [
-      ["list", this._listId || ""],
-      ["link", link.directoryIndex],
-      ["action", action],
-      ["tile", tileIndex],
-      ["score", link.frecency],
-      ["pin", +pinned],
-    ].map(([key, val]) => encodeURIComponent(key) + "=" + encodeURIComponent(val));
+    ping.open("POST", pingEndPoint + (action == "view" ? "view" : "click"));
+    ping.send(JSON.stringify(data));
 
-    ping.open("GET", reportClickEndPoint + "?" + queryParams.join("&"));
-    ping.send();
+    // Use this as an opportunity to potentially fetch new links
+    return this._fetchAndCacheLinksIfNecessary();
   },
 
   /**
-   * Submits counts of shown directory links for each type and
-   * triggers directory download if sponsored link was shown
-   *
-   * @param object keyed on types containing counts
-   * @return download promise
+   * Get the enhanced link object for a link (whether history or directory)
    */
-  reportShownCount: function DirectoryLinksProvider_reportShownCount(directoryCount) {
-    // make a deep copy of directoryCount to avoid a leak
-    this._directoryCount = Cu.cloneInto(directoryCount, {});
-    if (directoryCount.sponsored > 0
-        || directoryCount.affiliate > 0
-        || directoryCount.organic > 0) {
-      return this._fetchAndCacheLinksIfNecessary();
-    }
-    return Promise.resolve();
+  getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
+    // Use the provided link if it's already enhanced
+    return link.enhancedImageURI && link ||
+           this._enhancedLinks.get(this._extractSite(link.url));
   },
 
   /**
    * Gets the current set of directory links.
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
     this._readDirectoryLinksFile().then(rawLinks => {
+      // Reset the cache of enhanced images for this new set of links
+      this._enhancedLinks.clear();
+
       // all directory links have a frecency of DIRECTORY_FRECENCY
       aCallback(rawLinks.map((link, position) => {
-        link.directoryIndex = position;
+        // Stash the enhanced image for the site
+        if (link.enhancedImageURI) {
+          this._enhancedLinks.set(this._extractSite(link.url), link);
+        }
+
         link.frecency = DIRECTORY_FRECENCY;
         link.lastVisitDate = rawLinks.length - position;
         return link;
       }));
     });
   },
 
   init: function DirectoryLinksProvider_init() {
+    this.enabled = this.locale == "en-US";
+
+    this._setDefaultEnhanced();
     this._addPrefsObserver();
     // setup directory file path and last download timestamp
     this._directoryFilePath = OS.Path.join(OS.Constants.Path.localProfileDir, DIRECTORY_LINKS_FILE);
     this._lastDownloadMS = 0;
     return Task.spawn(function() {
       // get the last modified time of the links file if it exists
       let doesFileExists = yield OS.File.exists(this._directoryFilePath);
       if (doesFileExists) {
@@ -338,17 +424,16 @@ let DirectoryLinksProvider = {
     }.bind(this));
   },
 
   /**
    * Return the object to its pre-init state
    */
   reset: function DirectoryLinksProvider_reset() {
     delete this.__linksURL;
-    delete this._directoryCount;
     this._removePrefsObserver();
     this._removeObservers();
   },
 
   addObserver: function DirectoryLinksProvider_addObserver(aObserver) {
     this._observers.add(aObserver);
   },
 
--- a/toolkit/modules/NewTabUtils.jsm
+++ b/toolkit/modules/NewTabUtils.jsm
@@ -37,18 +37,19 @@ XPCOMUtils.defineLazyGetter(this, "gCryp
 
 XPCOMUtils.defineLazyGetter(this, "gUnicodeConverter", function () {
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = 'utf8';
   return converter;
 });
 
-// The preference that tells whether this feature is enabled.
+// Boolean preferences that control newtab content
 const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
+const PREF_NEWTAB_ENHANCED = "browser.newtabpage.enhanced";
 
 // The preference that tells the number of rows of the newtab grid.
 const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
 
 // The preference that tells the number of columns of the newtab grid.
 const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
 
 // The maximum number of results PlacesProvider retrieves from history.
@@ -205,16 +206,21 @@ let AllPages = {
   _pages: [],
 
   /**
    * Cached value that tells whether the New Tab Page feature is enabled.
    */
   _enabled: null,
 
   /**
+   * Cached value that tells whether the New Tab Page feature is enhanced.
+   */
+  _enhanced: null,
+
+  /**
    * Adds a page to the internal list of pages.
    * @param aPage The page to register.
    */
   register: function AllPages_register(aPage) {
     this._pages.push(aPage);
     this._addObserver();
   },
 
@@ -242,16 +248,34 @@ let AllPages = {
    * Enables or disables the 'New Tab Page' feature.
    */
   set enabled(aEnabled) {
     if (this.enabled != aEnabled)
       Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, !!aEnabled);
   },
 
   /**
+   * Returns whether the history tiles are enhanced.
+   */
+  get enhanced() {
+    if (this._enhanced === null)
+      this._enhanced = Services.prefs.getBoolPref(PREF_NEWTAB_ENHANCED);
+
+    return this._enhanced;
+  },
+
+  /**
+   * Enables or disables the enhancement of history tiles feature.
+   */
+  set enhanced(aEnhanced) {
+    if (this.enhanced != aEnhanced)
+      Services.prefs.setBoolPref(PREF_NEWTAB_ENHANCED, !!aEnhanced);
+  },
+
+  /**
    * Returns the number of registered New Tab Pages (i.e. the number of open
    * about:newtab instances).
    */
   get length() {
     return this._pages.length;
   },
 
   /**
@@ -283,30 +307,38 @@ let AllPages = {
 
   /**
    * Implements the nsIObserver interface to get notified when the preference
    * value changes or when a new copy of a page thumbnail is available.
    */
   observe: function AllPages_observe(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed") {
       // Clear the cached value.
-      this._enabled = null;
+      switch (aData) {
+        case PREF_NEWTAB_ENABLED:
+          this._enabled = null;
+          break;
+        case PREF_NEWTAB_ENHANCED:
+          this._enhanced = null;
+          break;
+      }
     }
     // and all notifications get forwarded to each page.
     this._pages.forEach(function (aPage) {
       aPage.observe(aSubject, aTopic, aData);
     }, this);
   },
 
   /**
    * Adds a preference and new thumbnail observer and turns itself into a
    * no-op after the first invokation.
    */
   _addObserver: function AllPages_addObserver() {
     Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true);
+    Services.prefs.addObserver(PREF_NEWTAB_ENHANCED, this, true);
     Services.obs.addObserver(this, "page-thumbnail:create", true);
     this._addObserver = function () {};
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference])
 };
 
@@ -792,18 +824,44 @@ let Links = {
   /**
    * Gets the current set of links contained in the grid.
    * @return The links in the grid.
    */
   getLinks: function Links_getLinks() {
     let pinnedLinks = Array.slice(PinnedLinks.links);
     let links = this._getMergedProviderLinks();
 
-    // Filter blocked and pinned links.
+    function getBaseDomain(url) {
+      let uri;
+      try {
+        uri = Services.io.newURI(url, null, null);
+      } catch (e) {
+        return null;
+      }
+
+      try {
+        return Services.eTLD.getBaseDomain(uri);
+      } catch (e) {
+        return uri.asciiHost;
+      }
+    }
+
+    let baseDomains = new Set();
+    for (let link of pinnedLinks) {
+      if (link)
+        baseDomains.add(getBaseDomain(link.url));
+    }
+
+    // Filter blocked and pinned links and duplicate base domains.
     links = links.filter(function (link) {
+      let baseDomain = getBaseDomain(link.url);
+      if (baseDomain == null || baseDomains.has(baseDomain))
+        return false;
+      baseDomains.add(baseDomain);
+
       return !BlockedLinks.isBlocked(link) && !PinnedLinks.isPinned(link);
     });
 
     // Try to fill the gaps between pinned links.
     for (let i = 0; i < pinnedLinks.length && links.length; i++)
       if (!pinnedLinks[i])
         pinnedLinks[i] = links.shift();
 
--- a/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
+++ b/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
@@ -25,35 +25,35 @@ do_get_profile();
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
 const DIRECTORY_FRECENCY = 1000;
 const kURLData = {"en-US": [{"url":"http://example.com","title":"LocalSource"}]};
 const kTestURL = 'data:application/json,' + JSON.stringify(kURLData);
 
 // DirectoryLinksProvider preferences
 const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale;
 const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL;
-const kReportClickUrlPref = "browser.newtabpage.directory.reportClickEndPoint";
-const kTelemetryEnabledPref = "toolkit.telemetry.enabled";
+const kPingUrlPref = "browser.newtabpage.directory.ping";
+const kNewtabEnhancedPref = "browser.newtabpage.enhanced";
 
 // httpd settings
 var server;
 const kDefaultServerPort = 9000;
 const kBaseUrl = "http://localhost:" + kDefaultServerPort;
 const kExamplePath = "/exampleTest/";
 const kFailPath = "/fail/";
-const kReportClickPath = "/reportClick/";
+const kPingPath = "/ping/";
 const kExampleURL = kBaseUrl + kExamplePath;
 const kFailURL = kBaseUrl + kFailPath;
-const kReportClickUrl = kBaseUrl + kReportClickPath;
+const kPingUrl = kBaseUrl + kPingPath;
 
 // app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
 Services.prefs.setCharPref(kLocalePref, "en-US");
 Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
-Services.prefs.setCharPref(kReportClickUrlPref, kReportClickUrl);
-Services.prefs.setBoolPref(kTelemetryEnabledPref, true);
+Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
+Services.prefs.setBoolPref(kNewtabEnhancedPref, true);
 
 const kHttpHandlerData = {};
 kHttpHandlerData[kExamplePath] = {"en-US": [{"url":"http://example.com","title":"RemoteSource"}]};
 
 const expectedBodyObject = {locale: DirectoryLinksProvider.locale};
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                               "nsIBinaryInputStream",
                               "setInputStream");
@@ -166,39 +166,126 @@ function run_test() {
   run_next_test();
 
   // Teardown.
   do_register_cleanup(function() {
     server.stop(function() { });
     DirectoryLinksProvider.reset();
     Services.prefs.clearUserPref(kLocalePref);
     Services.prefs.clearUserPref(kSourceUrlPref);
-    Services.prefs.clearUserPref(kReportClickUrlPref);
-    Services.prefs.clearUserPref(kTelemetryEnabledPref);
+    Services.prefs.clearUserPref(kPingUrlPref);
+    Services.prefs.clearUserPref(kNewtabEnhancedPref);
   });
 }
 
-add_task(function test_reportLinkAction() {
-  let link = 1;
-  let action = "click";
-  let tile = 2;
-  let score = 3;
-  let pin = 1;
-  let expectedQuery = "list=&link=1&action=click&tile=2&score=3&pin=1"
-  let expectedPath = kReportClickPath;
+add_task(function test_reportSitesAction() {
+  yield DirectoryLinksProvider.init();
+  let deferred, expectedPath, expectedPost;
+  let done = false;
+  server.registerPrefixHandler(kPingPath, (aRequest, aResponse) => {
+    if (done) {
+      return;
+    }
 
-  let deferred = Promise.defer();
-  server.registerPrefixHandler(kReportClickPath, (aRequest, aResponse) => {
     do_check_eq(aRequest.path, expectedPath);
-    do_check_eq(aRequest.queryString, expectedQuery);
+
+    let bodyStream = new BinaryInputStream(aRequest.bodyInputStream);
+    let bodyObject = JSON.parse(NetUtil.readInputStreamToString(bodyStream, bodyStream.available()));
+    isIdentical(bodyObject, expectedPost);
+
     deferred.resolve();
   });
 
-  DirectoryLinksProvider.reportLinkAction({directoryIndex: link, frecency: score}, action, tile, pin);
-  return deferred.promise;
+  function sendPingAndTest(path, action, index) {
+    deferred = Promise.defer();
+    expectedPath = kPingPath + path;
+    DirectoryLinksProvider.reportSitesAction(sites, action, index);
+    return deferred.promise;
+  }
+
+  // Start with a single pinned link at position 3
+  let sites = [,,{
+    isPinned: _ => true,
+    link: {
+      directoryId: 1,
+      frecency: 30000,
+      url: "http://directory1/",
+    },
+  }];
+
+  // Make sure we get the click ping for the directory link with fields we want
+  // and unwanted fields removed by stringify/parse
+  expectedPost = JSON.parse(JSON.stringify({
+    click: 0,
+    locale: "en-US",
+    tiles: [{
+      id: 1,
+      pin: 1,
+      pos: 2,
+      score: 3,
+      url: undefined,
+    }],
+  }));
+  yield sendPingAndTest("click", "click", 2);
+
+  // Try a pin click ping
+  delete expectedPost.click;
+  expectedPost.pin = 0;
+  yield sendPingAndTest("click", "pin", 2);
+
+  // Try a block click ping
+  delete expectedPost.pin;
+  expectedPost.block = 0;
+  yield sendPingAndTest("click", "block", 2);
+
+  // A view ping has no actions
+  delete expectedPost.block;
+  expectedPost.view = 0;
+  yield sendPingAndTest("view", "view", 2);
+
+  // Remove the identifier that makes it a directory link so just plain history
+  delete sites[2].link.directoryId;
+  delete expectedPost.tiles[0].id;
+  yield sendPingAndTest("view", "view", 2);
+
+  // Add directory link at position 0
+  sites[0] = {
+    isPinned: _ => false,
+    link: {
+      directoryId: 1234,
+      frecency: 1000,
+      url: "http://directory/",
+    }
+  };
+  expectedPost.tiles.unshift(JSON.parse(JSON.stringify({
+    id: 1234,
+    pin: undefined,
+    pos: undefined,
+    score: undefined,
+    url: undefined,
+  })));
+  expectedPost.view = 1;
+  yield sendPingAndTest("view", "view", 2);
+
+  // Make the history tile enhanced so it reports both id and url
+  sites[2].enhancedId = "id from enhanced";
+  expectedPost.tiles[1].id = "id from enhanced";
+  expectedPost.tiles[1].url = sites[2].link.url;
+  yield sendPingAndTest("view", "view", 2);
+
+  // Click the 0th site / 0th tile
+  delete expectedPost.view;
+  expectedPost.click = 0;
+  yield sendPingAndTest("click", "click", 0);
+
+  // Click the 2th site / 1th tile
+  expectedPost.click = 1;
+  yield sendPingAndTest("click", "click", 2);
+
+  done = true;
 });
 
 add_task(function test_fetchAndCacheLinks_local() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
   // Trigger cache of data or chrome uri files in profD
   yield DirectoryLinksProvider._fetchAndCacheLinks(kTestURL);
   let data = yield readJsonFile();
@@ -228,17 +315,17 @@ add_task(function test_fetchAndCacheLink
   // File should be empty.
   let data = yield readJsonFile();
   isIdentical(data, "");
 });
 
 add_task(function test_fetchAndCacheLinks_unknownHost() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
-  let nonExistentServer = "http://nosuchhost.localhost";
+  let nonExistentServer = "http://localhost:56789/";
   try {
     yield DirectoryLinksProvider._fetchAndCacheLinks(nonExistentServer);
     do_throw("BAD URIs should fail");
   } catch (e) {
     do_check_true(e.startsWith("Fetching " + nonExistentServer + " results in error code: "))
   }
 
   // File should be empty.
@@ -282,44 +369,44 @@ add_task(function test_linksURL_locale()
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links;
   let expected_data;
 
   links = yield fetchData();
   do_check_eq(links.length, 1);
-  expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
+  expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
   isIdentical(links, expected_data);
 
   yield promiseDirectoryDownloadOnPrefChange("general.useragent.locale", "zh-CN");
 
   links = yield fetchData();
   do_check_eq(links.length, 2)
   expected_data = [
-    {url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2, directoryIndex: 0},
-    {url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 1}
+    {url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2},
+    {url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}
   ];
   isIdentical(links, expected_data);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider__prefObserver_url() {
   yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL});
 
   let links = yield fetchData();
   do_check_eq(links.length, 1);
-  let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
+  let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
   isIdentical(links, expectedData);
 
   // tests these 2 things:
   // 1. _linksURL is properly set after the pref change
   // 2. invalid source url is correctly handled
-  let exampleUrl = 'http://nosuchhost.localhost/bad';
+  let exampleUrl = 'http://localhost:56789/bad';
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl);
   do_check_eq(DirectoryLinksProvider._linksURL, exampleUrl);
 
   // since the download fail, the directory file must remain the same
   let newLinks = yield fetchData();
   isIdentical(newLinks, expectedData);
 
   // now remove the file, and re-download
@@ -407,42 +494,27 @@ add_task(function test_DirectoryLinksPro
   // then wait for testObserver to fire and test that json is downloaded
   yield testObserver.deferred.promise;
   let data = yield readJsonFile();
   isIdentical(data, kHttpHandlerData[kExamplePath]);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_fetchDirectoryOnShowCount() {
+add_task(function test_DirectoryLinksProvider_fetchDirectoryOnShow() {
   yield promiseSetupDirectoryLinksProvider();
 
   // set lastdownload to 0 to make DirectoryLinksProvider want to download
   DirectoryLinksProvider._lastDownloadMS = 0;
   do_check_true(DirectoryLinksProvider._needsDownload);
 
-  // Tell DirectoryLinksProvider that newtab has no room for sponsored links
-  let directoryCount = {sponsored: 0};
-  yield DirectoryLinksProvider.reportShownCount(directoryCount);
-  // the provider must skip download, hence that lastdownload is still 0
-  do_check_eq(DirectoryLinksProvider._lastDownloadMS, 0);
-
-  // make room for sponsored links and repeat, download should happen
-  directoryCount.sponsored = 1;
-  yield DirectoryLinksProvider.reportShownCount(directoryCount);
+  // download should happen on view
+  yield DirectoryLinksProvider.reportSitesAction([], "view");
   do_check_true(DirectoryLinksProvider._lastDownloadMS != 0);
 
-  // test that directoryCount object reaches the backend server
-  expectedBodyObject.directoryCount = directoryCount;
-  // set kSourceUrlPref to kExampleURL, causing request to test http server
-  // server handler validates that expectedBodyObject has correct directoryCount
-  yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, kExampleURL);
-  // reset expectedBodyObject to its original state
-  delete expectedBodyObject.directoryCount;
-
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider_fetchDirectoryOnInit() {
   // ensure preferences are set to defaults
   yield promiseSetupDirectoryLinksProvider();
   // now clean to provider, so we can init it again
   yield promiseCleanDirectoryLinksProvider();
@@ -461,8 +533,86 @@ add_task(function test_DirectoryLinksPro
   // write bogus json to a file and attempt to fetch from it
   let directoryLinksFilePath = OS.Path.join(OS.Constants.Path.profileDir, DIRECTORY_LINKS_FILE);
   yield OS.File.writeAtomic(directoryLinksFilePath, '{"en-US":');
   let data = yield fetchData();
   isIdentical(data, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
+
+add_task(function test_DirectoryLinksProvider_getEnhancedLink() {
+  let data = {"en-US": [
+    {url: "http://example.net", enhancedImageURI: "net1"},
+    {url: "http://example.com", enhancedImageURI: "com1"},
+    {url: "http://example.com", enhancedImageURI: "com2"},
+  ]};
+  let dataURI = 'data:application/json,' + JSON.stringify(data);
+  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
+  let links = yield fetchData();
+  do_check_eq(links.length, 3);
+
+  function checkEnhanced(url, image) {
+    let enhanced = DirectoryLinksProvider.getEnhancedLink({url: url});
+    do_check_eq(enhanced && enhanced.enhancedImageURI, image);
+  }
+
+  // Get the expected image for the same site
+  checkEnhanced("http://example.net/", "net1");
+  checkEnhanced("http://sub.example.net/", "net1");
+  checkEnhanced("http://example.net/path", "net1");
+  checkEnhanced("https://www.example.net/", "net1");
+
+  // Get the image of the last entry
+  checkEnhanced("http://example.com", "com2");
+
+  // Get the inline enhanced image
+  let inline = DirectoryLinksProvider.getEnhancedLink({
+    url: "http://example.com/echo",
+    enhancedImageURI: "echo",
+  });
+  do_check_eq(inline.enhancedImageURI, "echo");
+  do_check_eq(inline.url, "http://example.com/echo");
+
+  // Undefined for not enhanced
+  checkEnhanced("http://example.org", undefined);
+  checkEnhanced("http://localhost", undefined);
+  checkEnhanced("http://127.0.0.1", undefined);
+
+  // Make sure old data is not cached
+  data = {"en-US": [
+    {url: "http://example.com", enhancedImageURI: "fresh"},
+  ]};
+  dataURI = 'data:application/json,' + JSON.stringify(data);
+  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+  links = yield fetchData();
+  do_check_eq(links.length, 1);
+  checkEnhanced("http://example.net", undefined);
+  checkEnhanced("http://example.com", "fresh");
+});
+
+add_task(function test_DirectoryLinksProvider_setDefaultEnhanced() {
+  function checkDefault(expected) {
+    Services.prefs.clearUserPref(kNewtabEnhancedPref);
+    do_check_eq(Services.prefs.getBoolPref(kNewtabEnhancedPref), expected);
+  }
+
+  // Use the default donottrack prefs (enabled = false, value = 1)
+  Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+  Services.prefs.clearUserPref("privacy.donottrackheader.value");
+  checkDefault(true);
+
+  // Turn on DNT - no track
+  Services.prefs.setBoolPref("privacy.donottrackheader.enabled", true);
+  checkDefault(false);
+
+  // Set DNT - do track
+  Services.prefs.setIntPref("privacy.donottrackheader.value", 0);
+  checkDefault(true);
+
+  // Turn off DNT header
+  Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+  checkDefault(true);
+
+  // Clean up
+  Services.prefs.clearUserPref("privacy.donottrackheader.value");
+});
--- a/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
+++ b/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
@@ -186,14 +186,14 @@ function makeLinks(frecRangeStart, frecR
   for (let i = frecRangeEnd; i > frecRangeStart; i -= step) {
     links.push(makeLink(i));
   }
   return links;
 }
 
 function makeLink(frecency) {
   return {
-    url: "http://example.com/" + frecency,
+    url: "http://example" + frecency + ".com/",
     title: "My frecency is " + frecency,
     frecency: frecency,
     lastVisitDate: 0,
   };
 }