diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php
index 8628d2c..88bc3d1 100644
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/site-control.php
@@ -5,12 +5,16 @@ namespace WordCamp\Site_Cloner;
 defined( 'WPINC' ) or die();
 
 /**
- * Custom Customizer Control for a WordCamp site
+ * Custom Customizer Control for Search WordCamp sites to clone
  */
 class Site_Control extends \WP_Customize_Control {
-	public $site_id, $site_name, $screenshot_url, $theme_slug;
-	public $settings = 'wcsc_source_site_id';
-	public $section  = 'wcsc_sites';
+
+	public function __construct( $manager, $id, $args = [ ] ) {
+		parent::__construct( $manager, $id, $args );
+
+		$this->capability = 'edit_theme_options';
+		$this->section = 'wcsc_sites';
+	}
 
 	/**
 	 * Enqueue scripts and styles
@@ -18,20 +22,21 @@ class Site_Control extends \WP_Customize_Control {
 	public function enqueue() {
 		wp_enqueue_style(  'wordcamp-site-cloner' );
 		wp_enqueue_script( 'wordcamp-site-cloner' );
+		add_action( 'customize_controls_print_footer_scripts', array( $this, 'print_view_templates' ) );
 	}
 
-	/**
-	 * Render the control's content
-	 */
 	public function render_content() {
-		$preview_url = add_query_arg(
-			array(
-				'theme'               => rawurlencode( $this->theme_slug ),
-				'wcsc_source_site_id' => rawurlencode( $this->site_id ),
-			),
-			admin_url( 'customize.php' )
-		);
-
 		require( dirname( __DIR__ ) . '/templates/site-control.php' );
 	}
+
+	public function print_view_templates() {
+		?>
+		<script id="tmpl-wcsc-site-option" type="text/html">
+			<?php include dirname( __DIR__ ) . '/templates/site-option.php'; ?>
+		</script>
+		<script id="tmpl-wcsc-site-filters" type="text/html">
+			<?php include dirname( __DIR__ ) . '/templates/site-filters.php'; ?>
+		</script>
+		<?php
+	}
 }
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/sites-section.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/sites-section.php
deleted file mode 100644
index 69b85e4..0000000
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/sites-section.php
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-namespace WordCamp\Site_Cloner;
-
-defined( 'WPINC' ) or die();
-
-/**
- * Custom Customizer Section for WordCamp sites
- */
-class Sites_Section extends \WP_Customize_Section {
-	public $type = 'wcsc-sites';
-
-	/**
-	 * Render the section's content
-	 */
-	protected function render() {
-		require_once( dirname( __DIR__ ) . '/templates/sites-section.php' );
-	}
-}
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/source-site-id-setting.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/source-site-id-setting.php
index ae450ea..5e84f42 100644
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/source-site-id-setting.php
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/includes/source-site-id-setting.php
@@ -25,6 +25,8 @@ class Source_Site_ID_Setting extends \WP_Customize_Setting {
 			return;
 		}
 
+		// disable the current site's custom css from being output
+		remove_action( 'wp_head', array( 'Jetpack_Custom_CSS', 'link_tag' ), 101 );
 		add_action( 'wp_head',                 array( $this, 'preview_source_site_css'  ), 99 );   // wp_print_styles is too early; the theme's stylesheet would get enqueued later and take precedence
 		add_filter( 'get_post_metadata',       array( $this, 'preview_jetpack_postmeta' ), 10, 4 );
 		add_filter( 'safecss_skip_stylesheet', array( $this, 'preview_skip_stylesheet'  ) );
@@ -109,6 +111,8 @@ class Source_Site_ID_Setting extends \WP_Customize_Setting {
 	 * to the URL.
 	 *
 	 * @param int $source_site_id
+	 *
+	 * @return null
 	 */
 	protected function update( $source_site_id ) {
 		switch_to_blog( $source_site_id );
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php
index 952a134..9b60dba 100644
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-control.php
@@ -1,15 +1,20 @@
-<?php defined( 'WPINC' ) or die(); ?>
+<?php
+defined( 'WPINC' ) or die();
 
-<div id="wcsc-site-<?php echo esc_attr( $this->site_id ); ?>" class="wcscSite" data-preview-url="<?php echo esc_url( $preview_url ); ?>">
-	<div class="wcsc-site-screenshot">
-		<img src="<?php echo esc_url( $this->screenshot_url ); ?>" alt="<?php echo esc_attr( $this->site_name ); ?>" />
-	</div>
-
-	<h3 class="wcsc-site-name">
-		<?php echo esc_html( $this->site_name ); ?>
+/**
+ *  Top level template for the output of the Site Cloner Customizer Control
+ */
+?>
+<div id="wcsc-cloner">
+	<h3>
+		<?php esc_html_e( 'WordCamp Sites', 'wordcamporg' ); ?>
+		<span id="wcsc-sites-count" class="title-count wcsc-sites-count"></span>
 	</h3>
+	<div class="filters">
+	</div>
 
-	<span id="live-preview-label-<?php echo esc_attr( $this->site_id ); ?>" class="wcsc-live-preview-label">
-		<?php _e( 'Live Preview', 'wordcamporg' ); ?>
-	</span>
-</div>
+	<div class="wcsc-search">
+		<ul id="wcsc-results">
+		</ul>
+	</div>
+</div>
\ No newline at end of file
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-filters.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-filters.php
new file mode 100644
index 0000000..ecf9a9e
--- /dev/null
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-filters.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ *  Template for the search and drop down filters
+ */
+
+defined( 'WPINC' ) or die();
+
+?>
+	<div class="wcsc-filter">
+		<label for="wcsc-filter-search-input">
+			<span class="customize-control-title"><?php _e( 'Search', 'wordcamporg' ); ?></span>
+			<div class="customize-control-content">
+				<input type="search" id="wcsc-filter-search-input" class="wcsc-filter-search"/>
+			</div>
+		</label>
+	</div>
+
+	<div class="wcsc-filter">
+		<label for="wcsc-filter-theme_slug">
+			<span class="customize-control-title"><?php _e( 'Theme', 'wordcamporg' ); ?></span>
+			<div class="customize-control-content">
+				<select id="wcsc-filter-theme_slug" data-filter="theme_slug">
+					<option value="">Any</option>
+					<# _.each(data.themeOptions, function(themeOption) { #>
+						<option value="{{themeOption.slug}}">{{{themeOption.name}}}</option>
+						<# }); #>
+				</select>
+			</div>
+		</label>
+	</div>
+
+	<div class="wcsc-filter">
+		<label for="wcsc-filter-year">
+			<span class="customize-control-title"><?php _e( 'WordCamp Year', 'wordcamporg' ); ?></span>
+			<div class="customize-control-content">
+				<select id="wcsc-filter-year" data-filter="year">
+					<option value="">Any</option>
+					<# _.each(data.yearOptions, function(yearOption) { #>
+						<option value="{{yearOption}}">{{yearOption}}</option>
+						<# }); #>
+				</select>
+			</div>
+		</label>
+	</div>
+
+	<div class="wcsc-filter">
+		<label for="wcsc-filter-css_preprocessor">
+			<span class="customize-control-title"><?php _e( 'CSS Preprocessor', 'wordcamporg' ); ?></span>
+			<div class="customize-control-content">
+				<select id="wcsc-filter-css_preprocessor" data-filter="css_preprocessor">
+					<option value="">Any</option>
+					<# _.each(data.preprocessorOptions, function(preprocessorOption) { #>
+						<option value="{{preprocessorOption}}">{{preprocessorOption}}</option>
+						<# }); #>
+				</select>
+			</div>
+		</label>
+	</div>
+<?php
\ No newline at end of file
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php
new file mode 100644
index 0000000..a8c7213
--- /dev/null
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/site-option.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ *  Template for a single Site representation within the Site Cloner Control
+ */
+
+defined( 'WPINC' ) or die();
+
+?>
+	<div class="wcsc-site-screenshot">
+		<img src="{{ data.screenshot_url }}" alt="{{ data.name }}"/>
+	</div>
+
+	<h3 class="wcsc-site-name">
+		{{ data.name }}
+	</h3>
+
+	<# if ( data.active ) { #>
+		<span id="live-previewing-{{ data.site_id }}" class="wcsc-previewing-label">
+			<?php _e( 'Viewing', 'wordcamporg' ); ?>
+		</span>
+	<# } else { #>
+		<span id="live-preview-label-{{ data.site_id }}" class="wcsc-live-preview-label">
+			<?php _e( 'Live Preview', 'wordcamporg' ); ?>
+		</span>
+	<# } #>
+<?php
\ No newline at end of file
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php
deleted file mode 100644
index a05272c..0000000
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/templates/sites-section.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php defined( 'WPINC' ) or die(); ?>
-
-<li id="section-<?php echo esc_attr( $this->id ); ?>" class="accordion-section control-section control-section-<?php echo esc_attr( $this->type ); ?>">
-	<h3>
-		<?php esc_html_e( 'WordCamp Sites' ); ?>
-
-		<span class="title-count wcsc-sites-count">
-			<?php echo count( $this->controls ); ?>
-		</span>
-	</h3>
-
-	<div class="wcsc-sites-section-content">
-		<ul id="wcsc-sites"></ul>
-	</div>
-</li>
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css
index 0a33876..98eaf07 100644
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.css
@@ -6,22 +6,23 @@
 		overflow: auto;
 	}
 
-		.wcscSite {
+		.wcsc-site {
 			position: relative;
 			cursor: pointer;
 			border: 1px solid #DEDEDE;
 			box-shadow: 0 1px 1px -1px rgba( 0, 0, 0, 0.1 );
+			margin-top: 5px;
 		}
 
 			.wcsc-site-screenshot {
 				transition: opacity 0.2s ease-in-out 0s;
 			}
 
-				.wcscSite:hover .wcsc-site-screenshot {
+				.wcsc-site:hover .wcsc-site-screenshot {
 					opacity: 0.4;
 				}
 
-			.wcsc-live-preview-label {
+			.wcsc-live-preview-label, .wcsc-previewing-label {
 				opacity: 0;
 				position: absolute;
 				top: 35%;
@@ -38,6 +39,10 @@
 				transition: opacity 0.1s ease-in-out 0s;
 			}
 
-				.wcscSite:hover .wcsc-live-preview-label {
+				.wcsc-site:hover .wcsc-live-preview-label, .wcsc-site .wcsc-previewing-label {
 					opacity: 1;
 				}
+
+div.wcsc-filter {
+	margin-bottom: 10px;
+}
\ No newline at end of file
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js
index 5c05f82..5bf3f64 100644
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.js
@@ -1,69 +1,473 @@
-( function( wp, $ ) {
+(function ( wp, $, Backbone, win, settings ) {
 	'use strict';
 
-	if ( ! wp || ! wp.customize ) {
+	if ( !wp || !wp.customize ) {
 		return;
 	}
 
-	var api = wp.customize;
+	var api  = wp.customize,
+			wcsc = wp.wcSiteCloner = { models: {}, views: {}, collections: {}, routers: {}, settings: {} };
 
-	/**
-	 * The Clone Another WordCamp panel
-	 */
-	api.panelConstructor.wcscPanel = api.Panel.extend( {
-		/**
-		 * Initialize the panel after it's loaded
-		 *
-		 * Ideally, the Previewer would be set to the requested site ID during the initial PHP request, rather than
-		 * loading the host site in the Previewer, and then refreshing it to use the requested site. That became a
-		 * rabbit hole, though, so it's done this way instead.
-		 */
-		ready : function() {
-			var urlParams = getUrlParams( window.location.href );
+	wcsc.settings = settings || {};
 
-			if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) {
-				this.expand();
-				api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id );
+	// Model for a single site
+	wcsc.models.Site = Backbone.Model.extend( {
+		defaults: {
+			'active': false
+		},
+		idAttribute: 'site_id'
+	} );
+
+	// Model representing the filter state for searching/filtering sites
+	wcsc.models.SearchFilter = Backbone.Model.extend( {
+		's': '',
+		'theme_slug': '',
+		'year': '',
+		'css_preprocessor': ''
+	} );
+
+	// Top level view for the Site Cloner Control
+	wcsc.views.SiteSearch = Backbone.View.extend( {
+		el: '#wcsc-cloner .wcsc-search',
+
+		// index of the currently viewed page of results
+		page: 0,
+
+		initialize: function ( options ) {
+
+			// update scroller position
+			_.bindAll( this, 'scroller' );
+
+			// container that will be scrolled within
+			this.$container = $( '#wcsc-cloner' ).parents( 'ul.accordion-section-content' );
+			// bind scrolling within the container to check for infinite scroll
+			this.$container.bind( 'scroll', _.throttle( this.scroller, 300 ) );
+
+			// the model and view for filtering the site results
+			this.filterView = new wcsc.views.SearchFilters( {
+				model: this.collection.searchFilter,
+				parent: this
+			} );
+
+			// View for listing the matching sites
+			this.resultsView = new wcsc.views.SearchResults( {
+				collection: this.collection,
+				parent: this
+			} );
+
+		},
+
+		render: function () {
+			this.filterView.render();
+
+			this.resultsView.render();
+
+			this.$el.empty().append( this.resultsView.el );
+		},
+
+		// Checks if a user has reached the bottom of the list
+		// and triggers a scroll event to show more sites if needed
+		scroller: function () {
+			var visibleBottom, threshold, elementHeight, containerHeight, scrollTop;
+
+			scrollTop = this.$container.scrollTop();
+			containerHeight = this.$container.innerHeight();
+			elementHeight = this.$container.get( 0 ).scrollHeight;
+
+			visibleBottom = scrollTop + containerHeight;
+			threshold = Math.round( elementHeight * 0.9 );
+
+			if ( visibleBottom > threshold ) {
+				this.trigger( 'wcsc:scroll' );
 			}
 		}
+
 	} );
 
-	/**
-	 * Custom control representing a site that can be previewed/imported
-	 */
-	api.controlConstructor.wcscSite = api.Control.extend( {
-		/**
-		 * Initialize the control after it's loaded
-		 */
-		ready : function() {
-			this.container.on( 'click', '.wcscSite', this.previewSite );
-		},
-
-		/**
-		 * Preview the selected site
-		 *
-		 * If the site is using a different theme, then reload the entire Customizer with the theme URL parameter
-		 * set, so that the Theme Switcher will handle previewing the new theme for us. Otherwise just set the ID
-		 * to refresh the Previewer with the current theme and the new site's CSS, etc.
-		 *
-		 * @param {object} event
-		 */
-		previewSite : function( event ) {
-			var previewUrl       = $( this ).data( 'preview-url' ),
-				previewUrlParams = getUrlParams( previewUrl );
-
-			if ( api( 'wcsc_source_site_id' ).get() == previewUrlParams.wcsc_source_site_id ) {
+	// Collection representing the list of cloneable sites
+	wcsc.collections.Sites = Backbone.Collection.extend( {
+		model: wcsc.models.Site,
+		url: wcsc.settings.apiUrl,
+
+		initialize: function ( options ) {
+			this.searchFilter = options.searchFilter || {};
+
+			this.listenTo( this.searchFilter, 'change', this.applyFilter );
+		},
+
+		// Filter this collection by the updated searchFilter attributes
+		applyFilter: function () {
+			var filters       = this.searchFilter.toJSON(),
+					activeFilters = _.pick( filters, _.identity ),
+					term          = '',
+					sites;
+
+			// nothing actually changed, so don't update the collection
+			if ( _.isEmpty( this.searchFilter.changedAttributes() ) ) {
+				return;
+			}
+
+			//no active filters.  Reset to the full list and bail
+			if ( _.isEmpty( activeFilters ) ) {
+				this.resetCanonical();
+				return;
+			}
+
+			this.resetCanonical( { silent: true } );
+
+			// remove the search query restriction since we already filtered by word matches above
+			if ( activeFilters.s ) {
+				term = activeFilters.s;
+				delete activeFilters.s;
+			}
+
+			sites = this.where( activeFilters );
+
+			if ( term ) {
+				sites = this.filterBySearch( sites, term );
+			}
+
+			this.reset( sites );
+
+		},
+
+		// internal method for filtering sites by search terms
+		filterBySearch: function ( sites, term ) {
+			var match, name;
+
+			// Escape the term string for RegExp meta characters
+			term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' );
+
+			// Consider spaces as word delimiters and match the whole string
+			// so matching terms can be combined
+			term = term.replace( / /g, ')(?=.*' );
+			match = new RegExp( '^(?=.*' + term + ').+', 'i' );
+
+			return _.filter( sites, function ( site ) {
+				name = site.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
+
+				return match.test( name );
+			} );
+		},
+
+		paginate: function ( pageIndex ) {
+			var collection = this;
+			pageIndex = pageIndex || 0;
+
+			collection = _( collection.rest( 20 * pageIndex ) );
+			collection = _( collection.first( 20 ) );
+			return collection;
+		},
+
+		// Resets the site collection dataset to the canonical list originally pulled from the api
+		resetCanonical: function ( options ) {
+			var activeSiteId = api( 'wcsc_source_site_id' ).get(),
+					activeSite;
+			options = options || {};
+
+			this.reset( wcsc.settings.siteData, options );
+
+			//restore the currently active site
+			if ( activeSiteId ) {
+				activeSite = this.find( { site_id: activeSiteId } );
+				if ( typeof activeSite !== 'undefined' ) {
+					activeSite.set( { active: true } );
+				}
+			}
+		}
+	} );
+
+	// View for a single site
+	wcsc.views.Site = Backbone.View.extend( {
+		className: 'wcsc-site',
+
+		attributes: function () {
+			return {
+				'id': 'wcsc-site-' + this.model.get( 'site_id' ),
+				'data-site-id': this.model.get( 'site_id' )
+			}
+		},
+
+		html: wp.template( 'wcsc-site-option' ),
+
+		touchDrag: false,
+
+		events: {
+			'click': 'preview',
+			'keydown': 'preview',
+			'touchend': 'preview',
+			'touchmove': 'preventPreview'
+		},
+
+		initialize: function ( options ) {
+			this.parent = options.parent;
+
+			this.listenTo( this.model, 'change', this.render );
+
+			this.render();
+		},
+
+		render: function () {
+			var data = this.model.toJSON();
+			this.$el.html( this.html( data ) );
+		},
+
+		preventPreview: function () {
+			this.touchDrag = true;
+		},
+
+		preview: function ( event ) {
+			event = event || window.event;
+
+			//ignore touches caused by scrolling
+			if ( this.touchDrag === true ) {
+				this.touchDrag = false;
+			}
+
+			event.preventDefault();
+
+			this.$el.trigger( 'wcsc:previewSite', this.model );
+		}
+
+	} );
+
+	// View for the site results list
+	wcsc.views.SearchResults = Backbone.View.extend( {
+		className: 'wcsc-results',
+
+		initialize: function ( options ) {
+			var self = this;
+
+			this.parent = options.parent;
+
+			this.$siteCount = $( '#wcsc-sites-count' );
+
+			// Rerender the view whenever a collection change is complete
+			this.listenTo( this.collection, 'reset', function () {
+				//reset pagination
+				self.parent.page = 0;
+				self.render( this );
+			} );
+
+			this.listenTo( this.parent, 'wcsc:scroll', function () {
+				self.renderSites( self.parent.page );
+			} );
+		},
+
+		render: function () {
+			this.$el.empty();
+
+			this.renderSites( this.parent.page );
+
+			this.$siteCount.text( this.collection.length );
+		},
+
+		renderSites: function ( page ) {
+			var self = this;
+
+			// get a collection of just the requested page
+			this.instance = this.collection.paginate( page );
+
+			if ( this.instance.size() === 0 ) {
+				this.parent.trigger( 'wcsc:end' );
+				return;
+			}
+
+			this.instance.each( function ( site ) {
+				var siteView = new wcsc.views.Site( {
+					model: site,
+					parent: self
+				} );
+
+				siteView.render();
+
+				self.$el.append( siteView.el );
+			} );
+
+			this.parent.page++;
+		}
+
+	} );
+
+	// View for the search and dropdown filters
+	wcsc.views.SearchFilters = Backbone.View.extend( {
+		el: '#wcsc-cloner .filters',
+		className: 'wscs-filters',
+		html: wp.template( 'wcsc-site-filters' ),
+		events: {
+			"input #wcsc-filter-search-input": "search",
+			"keyup #wcsc-filter-search-input": "search",
+			"change select": "applyFilter"
+		},
+		initialize: function ( options ) {
+			this.parent = options.parent;
+		},
+
+		render: function () {
+			var data = {};
+
+			data.themeOptions = wcsc.settings.themes;
+			data.yearOptions = _.uniq( this.parent.collection.pluck( 'year' ) ).sort();
+			data.preprocessorOptions = _.uniq( this.parent.collection.pluck( 'css_preprocessor' ) ).sort();
+
+			this.$el.html( this.html( data ) );
+
+			this.$searchInput = $( '#wcsc-filter-search-input' );
+			this.$themeFilter = $( '#wcsc-filter-theme_slug' );
+			this.$yearFilter = $( '#wcsc-filter-year' );
+			this.$preprocessorFilter = $( '#wcsc-filter-css_preprocessor' );
+		},
+
+		search: function ( event ) {
+			// Clear on escape.
+			if ( event.type === 'keyup' && event.which === 27 ) {
+				event.target.value = '';
+			}
+
+			/**
+			 * Since doSearch is debounced, it will only run when user input comes to a rest
+			 */
+			this.doSearch( event );
+		},
+
+		doSearch: _.debounce( function ( event ) {
+			var options = {};
+
+			this.model.set( 's', event.target.value );
+
+		}, 500 ),
+
+		applyFilter: function ( event ) {
+			var $target = $( event.target ),
+					value   = $target.val(),
+					filter  = $target.data( 'filter' );
+			this.model.set( filter, value );
+		},
+
+		// Set the inputs to the set of filters as triggered by the router on initial load
+		setInputs: function ( filters ) {
+			this.model.set( filters, { silent: true } );
+			this.$searchInput.val( this.model.get( 's' ) );
+			this.$themeFilter.val( this.model.get( 'theme_slug' ) );
+			this.$yearFilter.val( this.model.get( 'year' ) );
+			this.$preprocessorFilter.val( this.model.get( 'css_preprocessor' ) );
+			this.model.trigger( 'change', this.model );
+		}
+
+	} );
+
+	// Sets up listener to store the user's selected filters and search so a user's position can be
+	// restored as well as possible after a theme changes causes a full refresh
+	wcsc.routers.FilterState = Backbone.Router.extend( {
+		routes: {
+			'wcsc?*filters': 'applyFilters'
+		},
+		initialize: function ( options ) {
+			this.parent = options.parent;
+
+			//any time the collection is reset, we need to update the displayed route
+			this.listenTo(this.parent.view.collection, 'reset', this.updateLocation);
+		},
+
+		// Applies the filters set in the query string to the view
+		applyFilters: function ( queryString ) {
+			var filters = deserializeQueryString( queryString );
+
+			this.parent.view.filterView.setInputs(filters);
+		},
+		updateLocation: function () {
+			var filters = this.parent.view.collection.searchFilter.toJSON(),
+					activeFilters = _.pick( filters, _.identity ),
+					queryString = $.param( activeFilters );
+			this.navigate( 'wcsc?' + queryString );
+		}
+	} );
+
+	// Customizer Control wrapping the site search applet
+	api.controlConstructor.wcscSearch = api.Control.extend( {
+		ready: function () {
+			var filter = new wcsc.models.SearchFilter(); //top level model representing the current filter applied to the collection
+
+			this.siteCollection = new wcsc.collections.Sites( { searchFilter: filter } );
+
+			// fill the site collection and setup search when complete
+			this.siteCollection.fetch( {
+				success: this.setupSearch.bind(this)
+			} );
+		},
+
+		// Initialize the site search instance for cloning other sites
+		setupSearch: function () {
+			var control   = this,
+					urlParams = getUrlParams( win.location.href ),
+					currentSite;
+
+			//Set a canonical array of all sites prior to filtering
+			wcsc.settings.siteData = this.siteCollection.toJSON();
+
+			// if the wcsc_source_site_id is set, its most likely from a user previewing a site, so bring them back
+			if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) {
+				api.section( this.section() ).expand();
+				currentSite = this.siteCollection.find({site_id: urlParams.wcsc_source_site_id});
+				if(currentSite) {
+					this.setActiveSite(currentSite);
+				}
+			}
+
+			$( '#wcsc-cloner' ).on( 'wcsc:previewSite', '.wcsc-site', function ( event, site ) {
+				control.previewSite( site );
+			} );
+
+			// setup the top level Site Search View
+			this.view = new wcsc.views.SiteSearch( {
+				parent: this,
+				collection: this.siteCollection
+			} );
+
+			this.view.render();
+
+			// Initialize the router to allow state to be restored after a full refresh
+			wcsc.router = new wcsc.routers.FilterState( { parent: this } );
+			Backbone.history.start();
+
+		},
+
+		previewSite: function ( site ) {
+			var queryString, routerFragment;
+			if ( api( 'wcsc_source_site_id' ).get() == site.get( 'site_id' ) ) {
+				//we're already previewing this site
 				return;
 			}
 
-			if ( api.settings.theme.stylesheet === previewUrlParams.theme ) {
-				api( 'wcsc_source_site_id' ).set( previewUrlParams.wcsc_source_site_id );
+			if ( api.settings.theme.stylesheet === site.get( 'theme_slug' ) ) {
+				this.setActiveSite( site );
 			} else {
-				window.parent.location = previewUrl;
+
+				//we have to do a full refresh when changing themes or other controls won't correlate to the current theme.
+				queryString = $.param( {
+					'theme': site.get( 'theme_slug' ),
+					'wcsc_source_site_id': site.get( 'site_id' )
+				} );
+
+				routerFragment = Backbone.history.getFragment();
+				win.parent.location = wcsc.settings.customizerUrl + '?' + queryString +
+					'#' + routerFragment;
 			}
+		},
+
+		// Set the active site and update the model to reflect the change
+		setActiveSite: function(site) {
+			var site_id = site.get('site_id');
+			this.siteCollection.each( function ( _site ) {
+				_site.set( { active: false } );
+			} );
+			site.set( { active: true } );
+			api( 'wcsc_source_site_id' ).set( site.get('site_id') );
 		}
+
 	} );
 
+
 	/**
 	 * Parse the URL parameters
 	 *
@@ -74,26 +478,44 @@
 	 * @returns {object}
 	 */
 	function getUrlParams( url ) {
-		var match, questionMarkIndex, query,
-			urlParams = {},
-			pl        = /\+/g,  // Regex for replacing addition symbol with a space
-			search    = /([^&=]+)=?([^&]*)/g,
-			decode    = function ( s ) {
-				return decodeURIComponent( s.replace( pl, " " ) );
-			};
+		var questionMarkIndex, query, hashIndex;
+
+		//strip hash first
+		hashIndex = url.indexOf('#');
+		if(hashIndex > -1) {
+			url = url.substring(0, hashIndex);
+		}
 
 		questionMarkIndex = url.indexOf( '?' );
 
 		if ( -1 === questionMarkIndex ) {
-			return urlParams;
+			return {};
 		} else {
 			query = url.substring( questionMarkIndex + 1 );
 		}
 
-		while ( match = search.exec( query ) ) {
+		return deserializeQueryString( query );
+	}
+
+	/**
+	 * Deserialize a query string into an object
+	 *
+	 * @param queryString
+	 * @returns {{}}
+	 */
+	function deserializeQueryString( queryString ) {
+		var match,
+				urlParams = {},
+				pl        = /\+/g,  // Regex for replacing addition symbol with a space
+				search    = /([^&=]+)=?([^&]*)/g,
+				decode    = function ( s ) {
+					return decodeURIComponent( s.replace( pl, " " ) );
+				};
+
+		while ( match = search.exec( queryString ) ) {
 			urlParams[ decode( match[ 1 ] ) ] = decode( match[ 2 ] );
 		}
 
 		return urlParams;
 	}
-} )( window.wp, jQuery );
+})( wp, jQuery, Backbone, window, _wcscSettings );
\ No newline at end of file
diff --git a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php
index c398534..2776352 100755
--- a/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php
+++ b/wordcamp.org/public_html/wp-content/plugins/wordcamp-site-cloner/wordcamp-site-cloner.php
@@ -7,18 +7,39 @@ defined( 'WPINC' ) or die();
 /*
 Plugin Name: WordCamp Site Cloner
 Description: Allows organizers to clone another WordCamp's theme and custom CSS as a starting point for their site.
-Version:     0.1
+Version:     0.2.0-alpha
 Author:      WordCamp.org
 Author URI:  http://wordcamp.org
 License:     GPLv2 or later
 */
 
-// todo if Jetpack_Custom_CSS:get_css is callable, register these, otherwise fatal errors
+/**
+ * Register the plugin's hooks after `plugins_loaded` so we can first verify that JetPack is available.  If not,
+ * this plugin can't be used.
+ */
+function register_hooks() {
+	if ( ! class_exists( '\JetPack' ) ) {
+		return;
+	}
+	add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\register_scripts' );
+	add_action( 'admin_menu',            __NAMESPACE__ . '\add_submenu_page' );
+	add_action( 'customize_register',    __NAMESPACE__ . '\register_customizer_components' );
+	add_action( 'rest_api_init',         __NAMESPACE__ . '\initialize_site_list_API' );
+
+  // Setup a daily cron to run on the main central.wordcamp.org site to keep the site list up to date.
+	if ( is_main_site() ) {
+		$hook = 'wcsc_prime_sites';
+		if ( ! wp_next_scheduled( $hook ) ) {
+			wp_schedule_event( time(), 'daily', $hook );
+		}
+
+		add_action( $hook, __NAMESPACE__ . '\prime_wordcamp_sites' );
+	}
+}
+
+add_action( 'plugins_loaded', __NAMESPACE__ . '\register_hooks' );
+
 
-add_action( 'plugins_loaded',        __NAMESPACE__ . '\get_wordcamp_sites' );
-add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\register_scripts' );
-add_action( 'admin_menu',            __NAMESPACE__ . '\add_submenu_page' );
-add_action( 'customize_register',    __NAMESPACE__ . '\register_customizer_components' );
 
 /**
  * Register scripts and styles
@@ -28,16 +49,31 @@ function register_scripts() {
 		'wordcamp-site-cloner',
 		plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css',
 		array(),
-		1
+		'0.2.0-alpha'
 	);
 
 	wp_register_script(
 		'wordcamp-site-cloner',
 		plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
-		array( 'jquery', 'customize-controls' ),
-		1,
+		array( 'jquery', 'customize-controls', 'wp-backbone' ),
+		'0.2.0-alpha',
 		true
 	);
+
+	$availableThemes = [ ];
+	foreach ( wp_get_themes( array( 'allowed' => true ) ) as $theme ) {
+		$themeName = $theme->display( 'Name' );
+		$availableThemes[] = array(
+			'slug' => $theme->get_stylesheet(),
+			'name' => $themeName ?: $theme->get_stylesheet()
+		);
+	}
+
+	wp_localize_script( 'wordcamp-site-cloner', '_wcscSettings', array(
+		'apiUrl'        => get_rest_url( null, '/wordcamp-site-cloner/v1/sites/' ),
+		'customizerUrl' => admin_url( 'customize.php' ),
+		'themes' => $availableThemes
+	) );
 }
 
 /**
@@ -52,7 +88,7 @@ function add_submenu_page() {
 		__( 'Clone Another WordCamp', 'wordcamporg' ),
 		__( 'Clone Another WordCamp', 'wordcamporg' ),
 		'switch_themes',
-		'customize.php?autofocus[panel]=wordcamp_site_cloner'
+		'customize.php?autofocus[section]=wcsc_sites'
 	);
 }
 
@@ -63,50 +99,42 @@ function add_submenu_page() {
  */
 function register_customizer_components( $wp_customize ) {
 	require_once( __DIR__ . '/includes/source-site-id-setting.php' );
-	require_once( __DIR__ . '/includes/sites-section.php' );
 	require_once( __DIR__ . '/includes/site-control.php' );
 
-	$wp_customize->register_control_type( __NAMESPACE__ . '\Site_Control' );
-
 	$wp_customize->add_setting( new Source_Site_ID_Setting(
 		$wp_customize,
 		'wcsc_source_site_id',
-		array()
+		array(
+			'capability' => 'switch_themes'
+		)
 	) );
 
-	$wp_customize->add_panel(
-		'wordcamp_site_cloner',
-		array(
-			'type'        => 'wcscPanel',
-			'title'       => __( 'Clone Another WordCamp', 'wordcamporg' ),
-			'description' => __( "Clone another WordCamp's theme and custom CSS as a starting point for your site.", 'wordcamporg' ),
+	$wp_customize->add_section( 'wcsc_sites', array(
+			'title'      => __( 'Clone Another WordCamp', 'wordcamporg' ),
+			'capability' => 'switch_themes'
 		)
 	);
 
-	$wp_customize->add_section( new Sites_Section(
+	$wp_customize->add_control( new Site_Control(
 		$wp_customize,
-		'wcsc_sites',
+		'wcsc_site_search',
 		array(
-			'panel' => 'wordcamp_site_cloner',
-			'title' => __( 'WordCamp Sites', 'wordcamporg' ),
+			'type'     => 'wcscSearch',
+			'label'    => __( 'Search', 'wordcamporg' ),
+			'settings' => 'wcsc_source_site_id',
+			'section'  => 'wcsc_sites'
 		)
 	) );
+}
 
-	foreach( get_wordcamp_sites() as $wordcamp ) {
-		if ( get_current_blog_id() == $wordcamp['site_id'] ) {
-			continue;
-		}
-
-		$wp_customize->add_control( new Site_Control(
-			$wp_customize,
-			'wcsc_site_id_' . $wordcamp['site_id'],
-			array(
-				'type'           => 'wcscSite',                      // todo should be able to set this in control instead of here, but if do that then control contents aren't rendered
-				'site_id'        => $wordcamp['site_id'],
-				'site_name'      => $wordcamp['name'],
-				'theme_slug'     => $wordcamp['theme_slug'],
-				'screenshot_url' => $wordcamp['screenshot_url'],
-			)
+/**
+ * Register the api route for the customizer to use to retriever the site list
+ */
+function initialize_site_list_API() {
+	if ( current_user_can( 'switch_themes' ) ) {
+		register_rest_route( 'wordcamp-site-cloner/v1', '/sites', array(
+			'methods'  => 'GET',
+			'callback' => __NAMESPACE__ . '\get_wordcamp_sites'
 		) );
 	}
 }
@@ -114,61 +142,117 @@ function register_customizer_components( $wp_customize ) {
 /**
  * Get required data for relevant WordCamp sites
  *
- * This isn't actually used until register_customizer_components(), but it's called during `plugins_loaded` in
- * order to prime the cache. That has to be done before `setup_theme`, because the Theme Switcher will override
- * the current theme when `?theme=` is present in the URL parameters, and it's safer to just avoid that than to
- * muck with the internals and try to reverse it on the fly.
- *
  * @return array
  */
 function get_wordcamp_sites() {
-	require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' );
+	$transient_key = 'wcsc_sites';
 
-	// plugins_loaded is runs on every screen, but we only need this when loading the Customizer and Previewer
-	if ( 'customize.php' != basename( $_SERVER['SCRIPT_NAME'] ) && empty( $_REQUEST['wp_customize'] ) ) {
-		return array();
+	if ( false == ( $sites = get_site_transient( $transient_key ) ) ) {
+		$sites = _get_wordcamp_sites();
+
+		//set the transient longer than needed to be sure it doesn't expire before the cron runs again.
+		set_site_transient( $transient_key, $sites, DAY_IN_SECONDS * 2 );
 	}
 
-	$transient_key = 'wcsc_sites';
+	// remove the current site from the result set
+	unset( $sites[ get_current_blog_id() ] );
+
+	return array_values( $sites );
+}
+
+/**
+ * Queries and filters the available sites to clone by a single page.  If more than one page of WordCamp posts exists,
+ * a cron will be scheduled in 10 minutes to update the next page's worth of sites in the cache.
+ *
+ * @internal
+ *
+ * @param array $args {
+ *     An array of elements that make up a post to update or insert.
+ *
+ *     @type int    $page                  The page of results to select. Default 1.
+ *     @type int    $posts_per_page        The number of results to return in the initial query. Default 500
+ *     @type bool   $schedule_cron         Whether to schedule a cron job to prime the next page of results.  Default true
+ * }
+ *
+ * @return array WordCamp Sites array.
+ */
+function _get_wordcamp_sites($args = array()) {
 
-	if ( $sites = get_site_transient( $transient_key ) ) {
-		return $sites;
+	$options = wp_parse_args( $args, array(
+		'page'           => 1,
+		'posts_per_page' => 500,
+		'schedule_cron'  => true
+	) );
+
+	$page = absint($options['page']);
+
+	//verify that the required plugins and JetPack modules are available
+	require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' );
+	if ( ! \Jetpack::is_module_active( 'custom-css' ) ) {
+		\Jetpack::activate_module( 'custom-css', false, false );
 	}
 
-	switch_to_blog( BLOG_ID_CURRENT_SITE ); // central.wordcamp.org
+	switch_to_blog( get_current_site()->blog_id );
 
 	$sites = array();
-	$wordcamps = get_posts( array(
+	$wordcamp_query = new \WP_Query( array(
 		'post_type'      => 'wordcamp',
 		'post_status'    => \WordCamp_Loader::get_public_post_statuses(),
-		'posts_per_page' => 125, // todo temporary workaround until able to add filters to make hundreds of sites manageable
+		'posts_per_page' => $options[ 'posts_per_page' ],
 		'meta_key'       => 'Start Date (YYYY-mm-dd)',
 		'orderby'        => 'meta_value_num',
-
-		'meta_query' => array(
+		'order'          => 'DESC',
+		'paged'          => $page,
+		'meta_query'     => array(
 			array(
 				'key'     => 'Start Date (YYYY-mm-dd)',
 				'value'   => strtotime( 'now - 1 month' ),
 				'compare' => '<'
-			),
+			)
 		),
 	) );
 
-	foreach( $wordcamps as $wordcamp ) {
-		$site_id  = get_wordcamp_site_id( $wordcamp );
+	//If there are more pages to process, reschedule the cron to run again with the next page
+	if ( $options[ 'schedule_cron' ] && $wordcamp_query->max_num_pages > $page && ! wp_next_scheduled( 'wcsc_prime_sites', [ $page + 1 ] ) ) {
+		wp_schedule_single_event( time() + 10 * MINUTE_IN_SECONDS, 'wcsc_prime_sites', [ $page + 1 ] );
+	}
+
+	$wordcamps = $wordcamp_query->get_posts();
+
+	$coming_soon_settings_handler = false;
+	if(class_exists('\WCCSP_Settings')) {
+		$coming_soon_settings_handler = isset( $GLOBALS[ 'WCCSP_Settings' ] ) ? $GLOBALS[ 'WCCSP_Settings' ] : new \WCCSP_Settings();
+	}
+
+	foreach ( $wordcamps as $wordcamp ) {
+		$site_id = get_wordcamp_site_id( $wordcamp );
 		$site_url = get_post_meta( $wordcamp->ID, 'URL', true );
+		$start_date = get_post_meta( $wordcamp->ID, 'Start Date (YYYY-mm-dd)', true );
 
-		if ( ! $site_id || ! $site_url ) {
+		if ( ! $site_id || ! $site_url || ! $start_date ) {
 			continue;
 		}
 
 		switch_to_blog( $site_id );
 
-		$sites[] = array(
-			'site_id'        => $site_id,
-			'name'           => get_wordcamp_name(),
-			'theme_slug'     => get_stylesheet(),
-			'screenshot_url' => get_screenshot_url( $site_url ),
+		//skip any sites with the coming soon enabled.
+		if ( $coming_soon_settings_handler ) {
+			$coming_soon_settings = $coming_soon_settings_handler->get_settings();
+			if ( isset( $coming_soon_settings[ 'enabled' ] ) && $coming_soon_settings[ 'enabled' ] === 'on' ) {
+				restore_current_blog();
+				continue;
+			}
+		}
+
+		$preprocessor = \Jetpack_Custom_CSS::get_preprocessor();
+
+		$sites[ $site_id ] = array(
+			'site_id'          => $site_id,
+			'name'             => get_wordcamp_name(),
+			'theme_slug'       => get_stylesheet(),
+			'screenshot_url'   => get_screenshot_url( $site_url ),
+			'year'             => date( 'Y', $start_date ),
+			'css_preprocessor' => is_array( $preprocessor ) && isset( $preprocessor[ 'name' ] ) ? $preprocessor[ 'name' ] : 'none'
 		);
 
 		restore_current_blog();
@@ -176,12 +260,46 @@ function get_wordcamp_sites() {
 
 	restore_current_blog();
 
-	set_site_transient( $transient_key, $sites, DAY_IN_SECONDS );
-
 	return $sites;
 }
 
 /**
+ * Daily cron job callback to keep the site list transient valid.  This is only run on the main site.
+ *
+ * Since it can be expensive to loop through 100's of sites with switch_to_blog, we're breaking the cron up
+ * into smaller groups and updating a set at a time.  This means that users getting the initial load may not
+ * see every site, but since they are ordered by date descending, most applicable sites should be available
+ * immediately if the transient gets flushed.
+ *
+ * The current logic assumes that no sites that have previously made the list of sites will end up getting
+ * removed.  If this changes, a separate cron will likely need to be added to prune sites from the list.
+ *
+ */
+function prime_wordcamp_sites($page = 1) {
+	$transient_key = 'wcsc_sites';
+
+	$sites = _get_wordcamp_sites( compact( 'page' ) );
+
+	$stale_sites = get_site_transient( $transient_key );
+
+	if ( ! empty( $stale_sites ) ) {
+		//merge the fresh sites with the previously stored sites, overriding old copies with the new
+		$sites = array_merge( $stale_sites, $sites );
+
+		//sort the sites in descending order by year.  More exact dates can be added if the rough order causes confusion.
+		uasort( $sites, function( $site_a, $site_b ) {
+			if ( $site_a[ 'year' ] === $site_b[ 'year' ] ) {
+				return 0;
+			}
+			return ( $site_a[ 'year' ] < $site_b[ 'year' ] ? 1 : -1 );
+		} );
+	}
+
+	//set the transient longer than needed to be sure it doesn't expire before the cron runs again.
+	set_site_transient( 'wcsc_sites', $sites, DAY_IN_SECONDS * 2 );
+}
+
+/**
  * Get the mShot URL for the given site URL
  *
  * Allow it to be filtered so that production URLs can be changed to match development URLs in local environments.
@@ -194,4 +312,4 @@ function get_screenshot_url( $site_url ) {
 	$screenshot_url = add_query_arg( 'w', 275, 'https://www.wordpress.com/mshots/v1/' . rawurlencode( $site_url ) );
 
 	return apply_filters( 'wcsc_site_screenshot_url', $screenshot_url );
-}
+}
\ No newline at end of file
