Index: includes/site-control.php
===================================================================
--- includes/site-control.php	(revision 3062)
+++ includes/site-control.php	(working copy)
@@ -5,33 +5,32 @@
 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';
 
-	/**
-	 * Enqueue scripts and styles
-	 */
+	public function __construct( $manager, $id, $args = [ ] ) {
+		parent::__construct( $manager, $id, $args );
+
+		$this->capability = 'edit_theme_options';
+		$this->section = 'wcsc_sites';
+	}
+
 	public function enqueue() {
-		wp_enqueue_style(  'wordcamp-site-cloner' );
+		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' )
-		);
+		include dirname( __DIR__ ) . '/templates/site-control.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>
+		<?php
 	}
 }
Index: templates/site-control.php
===================================================================
--- templates/site-control.php	(revision 3062)
+++ templates/site-control.php	(working copy)
@@ -1,15 +1,20 @@
-<?php defined( 'WPINC' ) or die(); ?>
+<?php
+/**
+ *  Top level template for the output of the Site Cloner Customizer Control
+ */
 
-<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 ); ?>" />
+defined( 'WPINC' ) or die();
+?>
+<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>
 
-	<h3 class="wcsc-site-name">
-		<?php echo esc_html( $this->site_name ); ?>
-	</h3>
-
-	<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
Index: wordcamp-site-cloner.css
===================================================================
--- wordcamp-site-cloner.css	(revision 3062)
+++ wordcamp-site-cloner.css	(working copy)
@@ -1,43 +1,51 @@
+#wcsc-cloner div.filters{
+    padding: 8px;
+}
+
+#wcsc-cloner div.filters label{
+    position: relative;
+}
+
 .control-section-wcsc-sites {
-	padding: 0 8px;
+    padding: 0 8px;
 }
 
-	#wcsc-sites {
-		overflow: auto;
-	}
+#wcsc-sites {
+    overflow: auto;
+}
 
-		.wcscSite {
-			position: relative;
-			cursor: pointer;
-			border: 1px solid #DEDEDE;
-			box-shadow: 0 1px 1px -1px rgba( 0, 0, 0, 0.1 );
-		}
+.wcsc-site {
+    position: relative;
+    cursor: pointer;
+    border: 1px solid #DEDEDE;
+    box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.1);
+}
 
-			.wcsc-site-screenshot {
-				transition: opacity 0.2s ease-in-out 0s;
-			}
+.wcsc-site-screenshot {
+    transition: opacity 0.2s ease-in-out 0s;
+}
 
-				.wcscSite:hover .wcsc-site-screenshot {
-					opacity: 0.4;
-				}
+.wcsc-site:hover .wcsc-site-screenshot {
+    opacity: 0.4;
+}
 
-			.wcsc-live-preview-label {
-				opacity: 0;
-				position: absolute;
-				top: 35%;
-				right: 25%;
-				left: 25%;
-				background: rgba( 0, 0, 0, 0.7 ) none repeat scroll 0 0;
-				color: #FFF;
-				font-size: 15px;
-				text-shadow: 0 1px 0 rgba( 0, 0, 0, 0.6 );
-				font-weight: 600;
-				padding: 15px 12px;
-				text-align: center;
-				border-radius: 3px;
-				transition: opacity 0.1s ease-in-out 0s;
-			}
+.wcsc-live-preview-label {
+    opacity: 0;
+    position: absolute;
+    top: 35%;
+    right: 25%;
+    left: 25%;
+    background: rgba(0, 0, 0, 0.7) none repeat scroll 0 0;
+    color: #FFF;
+    font-size: 15px;
+    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
+    font-weight: 600;
+    padding: 15px 12px;
+    text-align: center;
+    border-radius: 3px;
+    transition: opacity 0.1s ease-in-out 0s;
+}
 
-				.wcscSite:hover .wcsc-live-preview-label {
-					opacity: 1;
-				}
+.wcsc-site:hover .wcsc-live-preview-label {
+    opacity: 1;
+}
Index: wordcamp-site-cloner.js
===================================================================
--- wordcamp-site-cloner.js	(revision 3062)
+++ wordcamp-site-cloner.js	(working copy)
@@ -1,69 +1,398 @@
-( function( wp, $ ) {
+(function ( $, Backbone, win, wp ) {
 	'use strict';
 
-	if ( ! wp || ! wp.customize ) {
+	var api = wp.customize,
+	    wcsc;
+
+	if ( !wp || !wp.customize ) {
 		return;
 	}
 
-	var api = wp.customize;
+	wcsc = wp.wcSiteCloner = { models: {}, views: {}, collections: {}, 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 );
+	// @todo dynamically set the real URL
+	wcsc.settings.apiUrl = win.location.protocol + '//' + win.location.hostname + '/wp-admin/customize.php?get_wordcamp_sites=1';
 
-			if ( urlParams.hasOwnProperty( 'wcsc_source_site_id' ) ) {
-				this.expand();
-				api( 'wcsc_source_site_id' ).set( urlParams.wcsc_source_site_id );
+	// @todo implement real l10n
+	var l10n = wcsc.settings.l10n = {
+		search: 'Search'
+	};
+
+
+	// Model for a single site
+	wcsc.models.Site = Backbone.Model.extend( {
+		idAttribute: 'site_id'
+	} );
+
+	// 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' );
+
+			this.$searchContainer = $( '#wcsc-cloner div.filters' );
+
+			// 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 ) );
+		},
+
+		render: function () {
+
+			// View for listing the matching sites
+			this.resultsView = new wcsc.views.SearchResults( {
+				collection: this.collection,
+				parent: this
+			} );
+
+			// render search form
+			this.renderSearch();
+
+			this.resultsView.render();
+			this.$el.empty().append( this.resultsView.el );
+		},
+
+		// Render the search input view
+		renderSearch: function () {
+			var self = this,
+			    view;
+
+			view = new wcsc.views.SearchInput( {
+				collection: this.collection,
+				parent: this
+			} );
+
+			view.render();
+
+			this.$searchContainer
+				.append( $.parseHTML( '<label class="screen-reader-text" for="wp-filter-search-input">' + l10n.search + '</label>' ) );
+			this.$searchContainer
+				.append( view.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 );
+	// Collection representing the list of cloneable sites
+	wcsc.collections.Sites = Backbone.Collection.extend( {
+		model: wcsc.models.Site,
+		terms: '',
+		url: wcsc.settings.apiUrl,
+
+		// Runs the search against the current collection and triggers the update event
+		doSearch: function ( value ) {
+
+			//duplicate search
+			if ( this.terms === value ) {
+				return;
+			}
+
+			this.terms = value;
+
+			//if there are terms, run the serach filter
+			if ( this.terms.length > 0 ) {
+				this.search( this.terms );
+			}
+
+			if ( this.terms === '' ) {
+				this.resetCanonical();
+			}
+
+			this.trigger( 'wcsc:updated' );
 		},
 
-		/**
-		 * 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 );
+		// resets this collection to the filtered search results
+		search: function ( term ) {
+			var match, results, haystack, name;
 
-			if ( api( 'wcsc_source_site_id' ).get() == previewUrlParams.wcsc_source_site_id ) {
+			this.resetCanonical( { silent: true } );
+
+			// 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' );
+
+			// Find results
+			// _.filter and .test
+			results = this.filter( function ( data ) {
+				name = data.get( 'name' ).replace( /(<([^>]+)>)/ig, '' );
+
+				return match.test( name );
+			} );
+
+			if ( results.length === 0 ) {
+				this.trigger( 'query:empty' );
+			}
+
+			this.reset( results );
+		},
+
+		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 ) {
+			options = options || {};
+			this.reset( wcsc.settings.siteData, options );
+		}
+	} );
+
+
+	// 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.render();
+		},
+
+		render: function () {
+			var data = this.model.toJSON();
+
+			this.$el.html( this.html( data ) );
+		},
+
+		preventPreview: function () {
+			this.touchDrag = true;
+		},
+
+		preview: function ( event ) {
+			var current, preview;
+
+			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 );
+		}
+
+	} );
+
+	wcsc.views.SearchResults = Backbone.View.extend( {
+		className: 'wcsc-results',
+
+		liveSiteCount: 0,
+
+		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( self.collection, 'wcsc:updated', 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();
+
+			//if ( this.options.collection.size() > 0 ) {
+			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;
 			}
 
-			if ( api.settings.theme.stylesheet === previewUrlParams.theme ) {
-				api( 'wcsc_source_site_id' ).set( previewUrlParams.wcsc_source_site_id );
+			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 input field
+	wcsc.views.SearchInput = Backbone.View.extend( {
+		tagName: 'input',
+		className: 'wcsc-filter-search',
+		id: 'wcsc-filter-search-input',
+		searching: false,
+
+		attributes: {
+			type: 'search',
+
+		},
+
+		events: {
+			'input': 'search',
+			'keyup': 'search',
+			'blur': 'pushState'
+		},
+
+		initialize: function ( options ) {
+			this.parent = options.parent;
+		},
+
+		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.collection.doSearch( event.target.value );
+
+			// if search is initiated and key is not return
+			if ( this.searching && event.which !== 13 ) {
+				options.replace = true;
 			} else {
-				window.parent.location = previewUrl;
+				this.searching = true;
 			}
+
+		}, 500 ),
+
+		pushState: function ( event ) {
+			this.searching = false;
 		}
 	} );
 
+	api.controlConstructor.wcscSearch = api.Control.extend( {
+		ready: function () {
+			var control        = this,
+			    urlParams      = getUrlParams( win.location.href ),
+			    siteCollection = new wcsc.collections.Sites();
+
+			//setup the backbone view for the control
+			//@todo would be nice to delay the fetch until this section is loaded
+			siteCollection.fetch( {
+				success: function ( collection ) {
+					wcsc.settings.siteData = collection.toJSON();
+
+					control.view = new wcsc.views.SiteSearch( {
+						parent: this,
+						collection: collection
+					} );
+
+					control.renderSearch();
+				}
+			} );
+
+			// 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();
+			}
+
+			$( '#wcsc-cloner' ).on( 'wcsc:previewSite', '.wcsc-site', function ( event, site ) {
+				control.previewSite( site );
+			} );
+		},
+
+		previewSite: function ( site ) {
+
+			if ( api( 'wcsc_source_site_id' ).get() == site.get( 'site_id' ) ) {
+				//we're already previewing this site
+				return;
+			}
+
+			if ( api.settings.theme.stylesheet === site.get( 'theme_slug' ) ) {
+				api( 'wcsc_source_site_id' ).set( site.get( 'site_id' ) );
+			} else {
+				//@todo properly set this
+				win.parent.location = win.location.protocol + '//' + win.location.hostname +
+					'/wp-admin/customize.php?theme=' + site.get( 'theme_slug' ) + '&wcsc_source_site_id=' + site.get( 'site_id' );
+			}
+		},
+
+		renderSearch: function () {
+			this.view.render();
+		}
+
+	} );
+
 	/**
 	 * Parse the URL parameters
 	 *
@@ -75,12 +404,12 @@
 	 */
 	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, " " ) );
-			};
+		    urlParams = {},
+		    pl        = /\+/g,  // Regex for replacing addition symbol with a space
+		    search    = /([^&=]+)=?([^&]*)/g,
+		    decode    = function ( s ) {
+			    return decodeURIComponent( s.replace( pl, " " ) );
+		    };
 
 		questionMarkIndex = url.indexOf( '?' );
 
@@ -96,4 +425,4 @@
 
 		return urlParams;
 	}
-} )( window.wp, jQuery );
+})( jQuery, Backbone, window, wp )
\ No newline at end of file
Index: wordcamp-site-cloner.php
===================================================================
--- wordcamp-site-cloner.php	(revision 3062)
+++ wordcamp-site-cloner.php	(working copy)
@@ -7,36 +7,35 @@
 /*
 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
-
-add_action( 'plugins_loaded',        __NAMESPACE__ . '\get_wordcamp_sites' );
+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' );
+add_action( 'admin_menu', __NAMESPACE__ . '\add_submenu_page' );
+add_action( 'customize_register', __NAMESPACE__ . '\register_customizer_components' );
 
 /**
  * Register scripts and styles
  */
 function register_scripts() {
 	wp_register_style(
-		'wordcamp-site-cloner',
-		plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css',
-		array(),
-		1
+			'wordcamp-site-cloner',
+			plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.css',
+			array(),
+			1
 	);
 
 	wp_register_script(
-		'wordcamp-site-cloner',
-		plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
-		array( 'jquery', 'customize-controls' ),
-		1,
-		true
+			'wordcamp-site-cloner',
+			plugin_dir_url( __FILE__ ) . 'wordcamp-site-cloner.js',
+			array( 'jquery', 'customize-controls', 'wp-backbone' ),
+			1,
+			true
 	);
 }
 
@@ -48,11 +47,11 @@
  */
 function add_submenu_page() {
 	\add_submenu_page(
-		'themes.php',
-		__( 'Clone Another WordCamp', 'wordcamporg' ),
-		__( 'Clone Another WordCamp', 'wordcamporg' ),
-		'switch_themes',
-		'customize.php?autofocus[panel]=wordcamp_site_cloner'
+			'themes.php',
+			__( 'Clone Another WordCamp', 'wordcamporg' ),
+			__( 'Clone Another WordCamp', 'wordcamporg' ),
+			'switch_themes',
+			'customize.php?autofocus[panel]=wordcamp_site_cloner'
 	);
 }
 
@@ -63,52 +62,29 @@
  */
 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()
+			$wp_customize,
+			'wcsc_source_site_id',
+			array()
 	) );
 
-	$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' ),
+			)
 	);
 
-	$wp_customize->add_section( new Sites_Section(
-		$wp_customize,
-		'wcsc_sites',
-		array(
-			'panel' => 'wordcamp_site_cloner',
-			'title' => __( 'WordCamp Sites', 'wordcamporg' ),
-		)
-	) );
-
-	foreach( get_wordcamp_sites() as $wordcamp ) {
-		if ( get_current_blog_id() == $wordcamp['site_id'] ) {
-			continue;
-		}
-
-		$wp_customize->add_control( new Site_Control(
+	$wp_customize->add_control( new Site_Control(
 			$wp_customize,
-			'wcsc_site_id_' . $wordcamp['site_id'],
+			'wcsc_site_search',
 			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'],
+					'type'     => 'wcscSearch',
+					'label'    => __( 'Search', 'wordcamporg' ),
+					'settings' => 'wcsc_source_site_id',
+					'section'  => 'wcsc_sites'
 			)
-		) );
-	}
+	) );
 }
 
 /**
@@ -125,7 +101,7 @@
 	require_once( WP_PLUGIN_DIR . '/wcpt/wcpt-wordcamp/wordcamp-loader.php' );
 
 	// 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'] ) ) {
+	if ( 'customize.php' != basename( $_SERVER[ 'SCRIPT_NAME' ] ) && empty( $_REQUEST[ 'wp_customize' ] ) ) {
 		return array();
 	}
 
@@ -139,23 +115,23 @@
 
 	$sites = array();
 	$wordcamps = get_posts( 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
-		'meta_key'       => 'Start Date (YYYY-mm-dd)',
-		'orderby'        => 'meta_value_num',
+			'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
+			'meta_key'       => 'Start Date (YYYY-mm-dd)',
+			'orderby'        => 'meta_value_num',
 
-		'meta_query' => array(
-			array(
-				'key'     => 'Start Date (YYYY-mm-dd)',
-				'value'   => strtotime( 'now - 1 month' ),
-				'compare' => '<'
+			'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 );
+	foreach ( $wordcamps as $wordcamp ) {
+		$site_id = get_wordcamp_site_id( $wordcamp );
 		$site_url = get_post_meta( $wordcamp->ID, 'URL', true );
 
 		if ( ! $site_id || ! $site_url ) {
@@ -165,10 +141,10 @@
 		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 ),
+				'site_id'        => $site_id,
+				'name'           => get_wordcamp_name(),
+				'theme_slug'     => get_stylesheet(),
+				'screenshot_url' => get_screenshot_url( $site_url ),
 		);
 
 		restore_current_blog();
@@ -195,3 +171,40 @@
 
 	return apply_filters( 'wcsc_site_screenshot_url', $screenshot_url );
 }
+
+/**
+ * Quick and ugly handling to provide a JSON endpoint for the WordCamp Sites data
+ *
+ * @todo Make a real API
+ */
+add_action( 'wp_loaded', function() {
+	if ( ! empty( $_GET[ 'get_wordcamp_sites' ] ) ) {
+		$sites = \WordCamp\Site_Cloner\get_wordcamp_sites();
+
+		$fakeSites = array_merge( $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites,
+				$sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites, $sites );
+
+		//hack to expand with fake data for now so the front-end can be worked on without having to create 100s of sites
+		if ( defined( 'WCSC_MOCK_FILL_SITES' ) && WCSC_MOCK_FILL_SITES ) {
+			$fake_site_id = 10000;
+			foreach ( $fakeSites as &$site ) {
+				$site[ 'site_id' ] = $fake_site_id;
+				$site[ 'name' ] = $site[ 'name' ] . " Mock Only (Don't Click) - " . $fake_site_id;
+				$fake_site_id++;
+			}
+
+			$sites = array_merge( $sites, $fakeSites );
+		}
+
+		wp_send_json( $sites );
+	}
+} );
\ No newline at end of file
