diff --git a/ui/base.js b/ui/base.js
index 503cb400..66912c5d 100755
--- a/ui/base.js
+++ b/ui/base.js
@@ -311,6 +311,7 @@ function updateInfo(elem, data, target){
 	elem
 		.addClass('overlay-info')
 		.html('')
+		.off()
 
 	if(typeof(data) == typeof('abc')){
 		elem.html(data)
@@ -391,6 +392,7 @@ function showErrorStatus(message){
 	message = Array.apply(Array, arguments)
 	message.splice(0, 0, 'Error:')
 	return updateStatus.apply(null, message)
+		.one('click', function(){ $(this).fadeOut() })
 		.stop()
 		.show()
 }
diff --git a/ui/data.js b/ui/data.js
index de0a29bb..a05dfa97 100755
--- a/ui/data.js
+++ b/ui/data.js
@@ -37,15 +37,17 @@ var STUB_IMAGE_DATA = {
 // Data format...
 var DATA = {
 	version: '2.0',
-	current: 0,
+
+	// current position, GID...
+	current: null,
+
 	// the ribbon cache...
 	// in the simplest form this is a list of lists of GIDs
-	ribbons: [
-		$(new Array(100)).map(function(i){return i}).toArray()
-	],
+	ribbons: [],
+
 	// flat ordered list of images in current context...
 	// in the simplest form this is a list of GIDs.
-	order: $(new Array(100)).map(function(i){return i}).toArray(),
+	order: [],
 
 	// this can be used to store the filename/path of the file containing 
 	// image data...
@@ -55,6 +57,7 @@ var DATA = {
 // the images object, this is indexed by image GID and contains all 
 // the needed data...
 var IMAGES = {}
+// list of image GIDs that have been updated...
 var IMAGES_UPDATED = []
 
 var MARKED = []
@@ -493,7 +496,7 @@ function updateRibbonOrder(){
 	for(var i=0; i < DATA.ribbons.length; i++){
 		DATA.ribbons[i].sort(imageOrderCmp)
 	}
-	loadData()
+	reloadViewer()
 }
 
 
@@ -509,7 +512,7 @@ function imagesFromUrls(lst){
 	$.each(lst, function(i, e){
 
 		// this is ugly but I'm bored so this is pretty...
-		var ii = i < 10			? '0000000' + i 
+		var ii =  i < 10		? '0000000' + i 
 				: i < 100		? '000000' + i
 				: i < 1000		? '00000' + i
 				: i < 10000		? '0000' + i
@@ -866,12 +869,14 @@ function loadImages(ref_gid, count, ribbon){
 }
 
 
+/*
 // NOTE: this is here for testing...
 function loadImagesAround(ref_gid, count, ribbon){
 	var ribbon_i = getRibbonIndex(ribbon)
 	var gid = getGIDBefore(ref_gid, ribbon_i)
 	return loadImages(ref_gid, count, ribbon).filter('[gid="'+JSON.stringify(gid)+'"]').click()
 }
+*/
 
 
 // Roll ribbon and load new images in the updated section.
@@ -918,7 +923,7 @@ function rollImages(n, ribbon, extend, no_compensate_shift){
 }
 
 
-function loadData(images_per_screen){
+function reloadViewer(images_per_screen){
 	var ribbons_set = $('.ribbon-set')
 	var current = DATA.current
 	// if no width is given, use the current or default...
@@ -961,39 +966,6 @@ function loadSettings(){
 
 
 
-/**********************************************************************
-* Actions...
-*/
-
-function reverseImageOrder(){
-	DATA.order.reverse()
-	updateRibbonOrder()
-}
-
-
-// NOTE: using imageOrderCmp as a cmp function here will yield odd 
-// 		results -- in-place sorting a list based on relative element 
-// 		positions within itself is fun ;)
-function sortImages(cmp, reverse){
-	cmp = cmp == null ? imageDateCmp : cmp
-	DATA.order.sort(cmp)
-	if(reverse){
-		DATA.order.reverse()
-	}
-	updateRibbonOrder()
-}
-
-
-// shirt-hands...
-function sortImagesByDate(reverse){
-	return sortImages(reverse)
-}
-function sortImagesByName(reverse){
-	return sortImages(imageNameCmp, reverse)
-}
-
-
-
 /**********************************************************************
 * Image caching...
 */
@@ -1085,7 +1057,7 @@ function loadLocalStorageMarks(attr){
 		marked = '[]'
 	}
 	MARKED = JSON.parse(marked)
-	return loadData()
+	return reloadViewer()
 }
 function saveLocalStorageMarks(attr){
 	attr = attr == null ? DATA_ATTR : attr
@@ -1115,7 +1087,7 @@ function loadLocalStorage(attr){
 	BASE_URL = d.base_url
 	DATA = d.data
 	IMAGES = loadLocalStorageImages(attr)
-	return loadData()
+	return reloadViewer()
 }
 function saveLocalStorage(attr){
 	attr = attr == null ? DATA_ATTR : attr
@@ -1138,14 +1110,6 @@ if(window.CEF_dumpJSON != null){
 	var listDir = CEF_listDir
 	var removeFile = CEF_removeFile
 	var runSystem = CEF_runSystem
-
-// PhoneGap
-} else if(false) {
-	// XXX
-	var dumpJSON = null 
-	var listDir = null 
-	var removeFile = null
-	var runSystem = null
 }
 
 
@@ -1265,7 +1229,7 @@ function loadFileState(data_path, callback){
 				DATA = json.data
 				IMAGES = json.images
 				MARKED = []
-				loadData()
+				reloadViewer()
 
 			// version 2.0
 			} else if(json.version == '2.0') {
@@ -1274,7 +1238,7 @@ function loadFileState(data_path, callback){
 							normalizePath(DATA.image_file, base) 
 							: null)
 					.done(function(){
-						loadData()
+						reloadViewer()
 						callback != null && callback()
 						res.resolve()
 					})
@@ -1393,7 +1357,7 @@ function loadDir(path, raw_load){
 		DATA.ribbons = ribbonsFromFavDirs()
 		MARKED = []
 
-		loadData()
+		reloadViewer()
 		showStatus('Done.')
 	}
 }
@@ -1401,7 +1365,7 @@ function loadDir(path, raw_load){
 
 function updateRibbonsFromFavDirs(){
 	DATA.ribbons = ribbonsFromFavDirs(null, null, imageOrderCmp)
-	loadData()
+	reloadViewer()
 }
 
 
@@ -1420,379 +1384,37 @@ function openImage(){
 
 
 /**********************************************************************
-* Info & status...
+* Actions...
 */
 
-// XXX do we need a full rewrite here, or will it be better to just fill
-// 		the slots...
-function updateGlobalImageInfo(image){
-	image = image == null ? getImage() : $(image)
-	image = image.length == 0 ? getImage() : image
-
-	var elem = $('.global-image-info')
-	if(elem.length == 0){
-		elem = $('
')
-	}
-
-	// no image no update...
-	if(image.length == 0){
-		return elem
-	}
-
-	var gid = getImageGID(image)
-	var r = getRibbonIndex(getRibbon(image))
-	var data = IMAGES[gid]
-	var date = new Date(data.ctime * 1000)
-
-	var meta = []
-
-	image.hasClass('marked') ? meta.push(
-			'M'+
-			'Marked') : ''
-
-	var orientation = data.orientation
-	orientation = orientation == null ? 0 : orientation
-	orientation != 0 ? meta.push(
-			'R'+
-			'Rotated: '+orientation+'°CW') : ''
-
-
-	meta = meta.join(', ') 
-	meta = meta != '' ? '( '+ meta +' )' : ''
-
-	return updateInfo(elem,
-			// path...
-			''+
-				''+
-					data.path.split('/').pop() +
-				''+
-				''+
-					normalizePath(data.path) +
-				''+ 
-			' '+ 
-
-			// metadata...
-			''+
-				meta + ' GID:'+
-				// XXX do we need to display a short gid?
-				//gid +
-				''+ 
-					gid.slice(gid.length-6) +
-				''+
-				' '+
-					(gid.length >= 6 ? 
-					 	(gid.slice(0, gid.length-6) +''+ gid.slice(gid.length-6) +'') 
-						: gid)+
-				''+
-			' '+
-
-			// date...
-			''+
-				'TS:' + date.toShortDate() + ''+
-				'' + date.toString() + ''+
-			''+
-
-			// position...
-			'('+ 
-				(DATA.ribbons[r].indexOf(gid)+1) +'/'+ DATA.ribbons[r].length +
-			')')
+function reverseImageOrder(){
+	DATA.order.reverse()
+	updateRibbonOrder()
 }
 
 
-function updateInlineImageInfo(image){
-	image = image == null ? getImage() : $(image)
-	image = image.length == 0 ? getImage() : image
-
-	var elem = $('.inline-image-info')
-	if(elem.length == 0){
-		elem = $('')
+// NOTE: using imageOrderCmp as a cmp function here will yield odd 
+// 		results -- in-place sorting a list based on relative element 
+// 		positions within itself is fun ;)
+function sortImages(cmp, reverse){
+	cmp = cmp == null ? imageDateCmp : cmp
+	DATA.order.sort(cmp)
+	if(reverse){
+		DATA.order.reverse()
 	}
-
-	// no image no update...
-	if(image.length == 0){
-		return elem
-	}
-
-
-	var gid = getImageGID(image)
-	var r = getRibbonIndex(getRibbon(image))
-	var data = IMAGES[gid]
-	var date = new Date(data.ctime * 1000)
-
-	var orientation = data.orientation
-	orientation = orientation == null ? 0 : orientation
-
-	return updateInfo(elem,
-			// name...
-			data.path.split('/').pop() +'
'+
-
-			// date...
-			''+
-				//date.toShortDate() +
-				'' + date.toShortDate() + ''+
-				'' + date.toString() + ''+
-			''+
-			'',
-			image)
+	updateRibbonOrder()
 }
 
 
-function inlineImageInfoHoverHandler(evt){
-	var img = $(evt.target).closest('.image')
-	if(img.length > 0){
-		if(img.find('.inline-image-info:visible').length == 0){
-			updateInlineImageInfo(img)
-		}
-	}
+// shirt-hands...
+function sortImagesByDate(reverse){
+	return sortImages(reverse)
 }
-
-
-
-/**********************************************************************
-* Setup
-*/
-
-// Setup event handlers for data bindings...
-//
-// This does two jobs:
-// 	- maintain DATA state
-// 		- editor actions
-// 		- focus
-// 		- marking
-// 	- maintain view consistency
-// 		- centering/moving (roll)
-// 		- shifting (expand/contract)
-// 		- zooming (expand/contract)
-//
-function setupDataBindings(viewer){
-	viewer = viewer == null ? $('.viewer') : viewer
-	viewer
-		// XXX need to maintain the correct number of images per ribbon
-		// 		per zoom setting -- things get really odd when a ribbon 
-		// 		is smaller than it should be...
-		// XXX this does not get called on marking...
-		.on('preCenteringRibbon', function(evt, ribbon, image){
-			// NOTE: we do not need to worry about centering the ribbon 
-			//		here, just ball-park-load the correct batch...
-
-			var gid = getImageGID(image)
-			var r = getRibbonIndex(ribbon)
-			var gr = DATA.ribbons[r]
-			var img_before = getImageBefore(image, ribbon)
-			var gid_before = getGIDBefore(gid, r)
-			var screen_size = getScreenWidthInImages()
-			screen_size = screen_size < 1 ? 1 : screen_size
-			var l = ribbon.find('.image').length
-
-			// load images if we do a long jump -- start, end or some mark 
-			// outside of currently loaded section...
-			if(gid_before == null 
-					|| gid_before != getImageGID(img_before) 
-					// also load if we run out of images in the current ribbon,
-					// likely due to shifting...
-					|| ( gr.length > l 
-						&& l < screen_size * LOAD_SCREENS)){
-				loadImages(gid, Math.round(screen_size * LOAD_SCREENS), ribbon)
-			} 
-
-			// roll the ribbon while we are advancing...
-			var head = img_before.prevAll('.image')
-			var tail = img_before.nextAll('.image')
-
-			// NOTE: if this is greater than the number of images currently 
-			//		loaded, it might lead to odd effects...
-			var frame_size = Math.ceil((screen_size * LOAD_SCREENS) / 2)
-			var threshold = Math.floor(frame_size / 2) 
-			threshold = threshold < 1 ? 1 : threshold
-
-			// do the loading...
-			// XXX need to expand/contract the ribbon depending on speed...
-			// 		...might also be a good idea to load smaller images 
-			// 		while scrolling really fast...
-			// XXX use extendRibbon, to both roll and expand/contract...
-			// XXX BUG: when rolling a ribbon, this will sometimes 
-			// 		misalign an image...
-			// 		...where exactly this happens in the ribbon depends on 
-			// 		its size and LOAD_SCREENS...
-			// 		NOTE: calling centerView() will fix this.
-			// 		...the problem is in centerRibbon
-			if(tail.length < threshold){
-				var rolled = rollImages(frame_size, ribbon)
-			}
-			if(head.length < threshold){
-				var rolled = rollImages(-frame_size, ribbon)
-			}
-		})
-
-
-		.on('shiftedImage', function(evt, image, from, to){
-			from = getRibbonIndex(from)
-			var ribbon = to
-			to = getRibbonIndex(to)
-
-			var gid = getImageGID(image)
-			var after = getGIDBefore(gid, to)
-
-			// remove the elem from the from ribbon...
-			var index = DATA.ribbons[from].indexOf(gid)
-			var img = DATA.ribbons[from].splice(index, 1)
-
-			// put the elem in the to ribbon...
-			index = after == null ? 0 : DATA.ribbons[to].indexOf(after) + 1
-			DATA.ribbons[to].splice(index, 0, gid)
-
-			// indicators...
-			flashIndicator(from < to ? 'next' : 'prev')
-		})
-
-
-		.on('createdRibbon', function(evt, index){
-			index = getRibbonIndex(index)
-			DATA.ribbons.splice(index, 0, [])
-		})
-		.on('removedRibbon', function(evt, index){
-			DATA.ribbons.splice(index, 1)
-		})
-
-
-		.on('requestedFirstImage', function(evt, ribbon){
-			var r = getRibbonIndex(ribbon)
-			var gr = DATA.ribbons[r]
-			rollImages(-gr.length, ribbon)
-		})
-		.on('requestedLastImage', function(evt, ribbon){
-			var r = getRibbonIndex(ribbon)
-			var gr = DATA.ribbons[r]
-			rollImages(gr.length, ribbon)
-		})
-
-
-		.on('fittingImages', function(evt, n){
-			// load correct amount of images in each ribbon!!!
-			var screen_size = getScreenWidthInImages()
-			var gid = getImageGID()
-			$('.ribbon').each(function(){
-				var r = $(this)
-				loadImages(gid, Math.round(screen_size * LOAD_SCREENS), r)
-			})
-			centerView(null, 'css')
-
-			// update settings...
-			if(toggleSingleImageMode('?') == 'on'){
-				SETTINGS['screen-images-single-image-mode'] = n
-			} else {
-				SETTINGS['screen-images-ribbon-mode'] = n
-			}
-
-			// update previews...
-			updateImages()
-		})
-
-
-		.on('focusingImage', function(evt, image){
-			image = $(image)
-			DATA.current = getImageGID(image)
-		})
-
-
-		// basic image manipulation...
-		.on('rotatingLeft rotatingRight', function(evt, image){
-			$(image).each(function(i, e){
-				var img = $(this)
-				var gid = getImageGID(img) 
-				var orientation = img.attr('orientation')
-
-				// change the image orientation status and add to 
-				// updated list...
-				IMAGES[gid].orientation = orientation
-				if(IMAGES_UPDATED.indexOf(gid) == -1){
-					IMAGES_UPDATED.push(gid)
-				}
-			})
-		})
-
-
-		// marks...
-		// XXX toggle marking a block is not yet supported...
-		.on('togglingMark', function(evt, img, action){
-			var gid = getImageGID(img) 
-
-			// add marked image to list...
-			if(action == 'on'){
-				MARKED.push(gid)
-
-			// remove marked image from list...
-			} else {
-				MARKED.splice(MARKED.indexOf(gid), 1)
-			}
-		})
-		.on('removeingRibbonMarks', function(evt, ribbon){
-			$.each(DATA.ribbons[getRibbonIndex(ribbon)], function(_, e){
-				var i = MARKED.indexOf(e)
-				if(i != -1){
-					MARKED.splice(i, 1)
-				}
-			})
-		})
-		.on('removeingAllMarks', function(evt){
-			MARKED.splice(0, MARKED.length)
-		})
-		.on('markingRibbon', function(evt, ribbon){
-			$.each(DATA.ribbons[getRibbonIndex(ribbon)], function(_, e){
-				var i = MARKED.indexOf(e)
-				if(i == -1){
-					MARKED.push(e)
-				}
-			})
-		})
-		.on('markingAll', function(evt){
-			MARKED.splice(0, MARKED.length)
-			MARKED.concat(DATA.order)
-		})
-		.on('invertingMarks', function(evt, ribbon){
-			$.each(DATA.ribbons[getRibbonIndex(ribbon)], function(_, e){
-				var i = MARKED.indexOf(e)
-				if(i == -1){
-					MARKED.push(e)
-				} else {
-					MARKED.splice(i, 1)
-				}
-			})
-		})
-
-
-		// caching...
-		.on('reloadedRibbon updatedRibbon', function(evt, ribbon){
-
-			window.DEBUG && console.log('>>> (ribbon:', getRibbonIndex(ribbon), ') Updating cache...')
-
-			preCacheRibbonImages(ribbon)
-		})
-
-		// info...
-		.on([
-				'focusingImage',
-				'rotatingLeft',
-				'rotateingRight',
-				'togglingMark'
-			].join(' '), 
-			function(evt, image){
-				updateGlobalImageInfo($(image))
-			})
-		.on([
-				'removeingAllMarks',
-				'removeingRibbonMarks',
-				'markingAll',
-				'markingRibbon',
-				'invertingMarks'
-			].join(' '), 
-			function(){
-				updateGlobalImageInfo()
-			})
+function sortImagesByName(reverse){
+	return sortImages(imageNameCmp, reverse)
 }
 
 
 
-
 /**********************************************************************
 * vim:set ts=4 sw=4 spell :                                          */
diff --git a/ui/index.html b/ui/index.html
index 818ef460..dd5cd5ea 100755
--- a/ui/index.html
+++ b/ui/index.html
@@ -21,7 +21,9 @@
 
 
 
+
 
+
 
 
 
@@ -121,7 +123,7 @@ $(function(){
 			DATA = convertDataGen1(image_list)
 			DATA = DATA.data
 			IMAGES = DATA.images
-			loadData()
+			reloadViewer()
 		}
 		var loading = $.Deferred().resolve()
 	}
diff --git a/ui/info.js b/ui/info.js
new file mode 100755
index 00000000..d54ac4d0
--- /dev/null
+++ b/ui/info.js
@@ -0,0 +1,146 @@
+/**********************************************************************
+* 
+*
+**********************************************************************/
+
+
+
+/**********************************************************************
+* Info & status...
+*/
+
+// XXX do we need a full rewrite here, or will it be better to just fill
+// 		the slots...
+function updateGlobalImageInfo(image){
+	image = image == null ? getImage() : $(image)
+	image = image.length == 0 ? getImage() : image
+
+	var elem = $('.global-image-info')
+	if(elem.length == 0){
+		elem = $('')
+	}
+
+	// no image no update...
+	if(image.length == 0){
+		return elem
+	}
+
+	var gid = getImageGID(image)
+	var r = getRibbonIndex(getRibbon(image))
+	var data = IMAGES[gid]
+	var date = new Date(data.ctime * 1000)
+
+	var meta = []
+
+	image.hasClass('marked') ? meta.push(
+			'M'+
+			'Marked') : ''
+
+	var orientation = data.orientation
+	orientation = orientation == null ? 0 : orientation
+	orientation != 0 ? meta.push(
+			'R'+
+			'Rotated: '+orientation+'°CW') : ''
+
+
+	meta = meta.join(', ') 
+	meta = meta != '' ? '( '+ meta +' )' : ''
+
+	return updateInfo(elem,
+			// path...
+			''+
+				''+
+					data.path.split('/').pop() +
+				''+
+				''+
+					normalizePath(data.path) +
+				''+ 
+			' '+ 
+
+			// metadata...
+			''+
+				meta + ' GID:'+
+				// XXX do we need to display a short gid?
+				//gid +
+				''+ 
+					gid.slice(gid.length-6) +
+				''+
+				' '+
+					(gid.length >= 6 ? 
+					 	(gid.slice(0, gid.length-6) +''+ gid.slice(gid.length-6) +'') 
+						: gid)+
+				''+
+			' '+
+
+			// date...
+			''+
+				'TS:' + date.toShortDate() + ''+
+				'' + date.toString() + ''+
+			''+
+
+			// position...
+			'('+ 
+				(DATA.ribbons[r].indexOf(gid)+1) +'/'+ DATA.ribbons[r].length +
+			')')
+}
+
+
+function updateInlineImageInfo(image){
+	image = image == null ? getImage() : $(image)
+	image = image.length == 0 ? getImage() : image
+
+	var elem = $('.inline-image-info')
+	if(elem.length == 0){
+		elem = $('')
+	}
+
+	// no image no update...
+	if(image.length == 0){
+		return elem
+	}
+
+
+	var gid = getImageGID(image)
+	var r = getRibbonIndex(getRibbon(image))
+	var data = IMAGES[gid]
+	var date = new Date(data.ctime * 1000)
+
+	var orientation = data.orientation
+	orientation = orientation == null ? 0 : orientation
+
+	return updateInfo(elem,
+			// name...
+			data.path.split('/').pop() +'
'+
+
+			// date...
+			''+
+				//date.toShortDate() +
+				'' + date.toShortDate() + ''+
+				'' + date.toString() + ''+
+			''+
+			'',
+			image)
+}
+
+
+function inlineImageInfoHoverHandler(evt){
+	var img = $(evt.target).closest('.image')
+	if(img.length > 0){
+		if(img.find('.inline-image-info:visible').length == 0){
+			updateInlineImageInfo(img)
+		}
+	}
+}
+
+
+
+/**********************************************************************
+* vim:set ts=4 sw=4 spell nowrap :                                   */
diff --git a/ui/keybindings.js b/ui/keybindings.js
index e42eb4ff..e2342bcc 100755
--- a/ui/keybindings.js
+++ b/ui/keybindings.js
@@ -287,7 +287,10 @@ var KEYBOARD_CONFIG = {
 				default: doc('Rotate image right', 
 					function(){ rotateRight() }),
 				ctrl: doc('Reverse image order', 
-					function(){ reverseImageOrder() }),
+					function(){ 
+						event.preventDefault()
+						reverseImageOrder() 
+					}),
 			},
 
 
@@ -375,11 +378,9 @@ var KEYBOARD_CONFIG = {
 						} 
 						centerRibbons()
 					}),
-				ctrl: doc('Mark current image',
-					function(){ 
-						var action = toggleImageMark() 
-					}),
+				ctrl: 'Ins',
 			},
+		Ins: doc('Mark current image', function(){ toggleImageMark() }),
 		I: {
 				// XXX group this with other info stuff into a single on/off toggle...
 				default: doc('Toggle image info display',
diff --git a/ui/marks.js b/ui/marks.js
index 483cb917..efa11735 100755
--- a/ui/marks.js
+++ b/ui/marks.js
@@ -44,7 +44,7 @@ function loadMarkedOnlyData(cmp, no_cleanout_marks){
 		order: DATA.order,
 	}
 	DATA.current = getGIDBefore(cur, 0)
-	loadData()
+	reloadViewer()
 	toggleMarkesView('off')
 	return DATA
 }
@@ -53,7 +53,7 @@ function loadMarkedOnlyData(cmp, no_cleanout_marks){
 // XXX name this in a better way...
 function loadAllImages(){
 	DATA = ALL_DATA
-	loadData()
+	reloadViewer()
 	return DATA
 }
 
diff --git a/ui/setup.js b/ui/setup.js
new file mode 100755
index 00000000..da9a5f3b
--- /dev/null
+++ b/ui/setup.js
@@ -0,0 +1,253 @@
+/**********************************************************************
+* 
+*
+**********************************************************************/
+
+
+/**********************************************************************
+* Setup
+*/
+
+// Setup event handlers for data bindings...
+//
+// This does two jobs:
+// 	- maintain DATA state
+// 		- editor actions
+// 		- focus
+// 		- marking
+// 	- maintain view consistency
+// 		- centering/moving (roll)
+// 		- shifting (expand/contract)
+// 		- zooming (expand/contract)
+//
+function setupDataBindings(viewer){
+	viewer = viewer == null ? $('.viewer') : viewer
+	viewer
+		// XXX need to maintain the correct number of images per ribbon
+		// 		per zoom setting -- things get really odd when a ribbon 
+		// 		is smaller than it should be...
+		// XXX this does not get called on marking...
+		.on('preCenteringRibbon', function(evt, ribbon, image){
+			// NOTE: we do not need to worry about centering the ribbon 
+			//		here, just ball-park-load the correct batch...
+
+			var gid = getImageGID(image)
+			var r = getRibbonIndex(ribbon)
+			var gr = DATA.ribbons[r]
+			var img_before = getImageBefore(image, ribbon)
+			var gid_before = getGIDBefore(gid, r)
+			var screen_size = getScreenWidthInImages()
+			screen_size = screen_size < 1 ? 1 : screen_size
+			var l = ribbon.find('.image').length
+
+			// load images if we do a long jump -- start, end or some mark 
+			// outside of currently loaded section...
+			if(gid_before == null 
+					|| gid_before != getImageGID(img_before) 
+					// also load if we run out of images in the current ribbon,
+					// likely due to shifting...
+					|| ( gr.length > l 
+						&& l < screen_size * LOAD_SCREENS)){
+				loadImages(gid, Math.round(screen_size * LOAD_SCREENS), ribbon)
+			} 
+
+			// roll the ribbon while we are advancing...
+			var head = img_before.prevAll('.image')
+			var tail = img_before.nextAll('.image')
+
+			// NOTE: if this is greater than the number of images currently 
+			//		loaded, it might lead to odd effects...
+			var frame_size = Math.ceil((screen_size * LOAD_SCREENS) / 2)
+			var threshold = Math.floor(frame_size / 2) 
+			threshold = threshold < 1 ? 1 : threshold
+
+			// do the loading...
+			// XXX need to expand/contract the ribbon depending on speed...
+			// 		...might also be a good idea to load smaller images 
+			// 		while scrolling really fast...
+			// XXX use extendRibbon, to both roll and expand/contract...
+			// XXX BUG: when rolling a ribbon, this will sometimes 
+			// 		misalign an image...
+			// 		...where exactly this happens in the ribbon depends on 
+			// 		its size and LOAD_SCREENS...
+			// 		NOTE: calling centerView() will fix this.
+			// 		...the problem is in centerRibbon
+			if(tail.length < threshold){
+				var rolled = rollImages(frame_size, ribbon)
+			}
+			if(head.length < threshold){
+				var rolled = rollImages(-frame_size, ribbon)
+			}
+		})
+
+
+		.on('shiftedImage', function(evt, image, from, to){
+			from = getRibbonIndex(from)
+			var ribbon = to
+			to = getRibbonIndex(to)
+
+			var gid = getImageGID(image)
+			var after = getGIDBefore(gid, to)
+
+			// remove the elem from the from ribbon...
+			var index = DATA.ribbons[from].indexOf(gid)
+			var img = DATA.ribbons[from].splice(index, 1)
+
+			// put the elem in the to ribbon...
+			index = after == null ? 0 : DATA.ribbons[to].indexOf(after) + 1
+			DATA.ribbons[to].splice(index, 0, gid)
+
+			// indicators...
+			flashIndicator(from < to ? 'next' : 'prev')
+		})
+
+
+		.on('createdRibbon', function(evt, index){
+			index = getRibbonIndex(index)
+			DATA.ribbons.splice(index, 0, [])
+		})
+		.on('removedRibbon', function(evt, index){
+			DATA.ribbons.splice(index, 1)
+		})
+
+
+		.on('requestedFirstImage', function(evt, ribbon){
+			var r = getRibbonIndex(ribbon)
+			var gr = DATA.ribbons[r]
+			rollImages(-gr.length, ribbon)
+		})
+		.on('requestedLastImage', function(evt, ribbon){
+			var r = getRibbonIndex(ribbon)
+			var gr = DATA.ribbons[r]
+			rollImages(gr.length, ribbon)
+		})
+
+
+		.on('fittingImages', function(evt, n){
+			// load correct amount of images in each ribbon!!!
+			var screen_size = getScreenWidthInImages()
+			var gid = getImageGID()
+			$('.ribbon').each(function(){
+				var r = $(this)
+				loadImages(gid, Math.round(screen_size * LOAD_SCREENS), r)
+			})
+			centerView(null, 'css')
+
+			// update settings...
+			if(toggleSingleImageMode('?') == 'on'){
+				SETTINGS['screen-images-single-image-mode'] = n
+			} else {
+				SETTINGS['screen-images-ribbon-mode'] = n
+			}
+
+			// update previews...
+			updateImages()
+		})
+
+
+		.on('focusingImage', function(evt, image){
+			image = $(image)
+			DATA.current = getImageGID(image)
+		})
+
+
+		// basic image manipulation...
+		.on('rotatingLeft rotatingRight', function(evt, image){
+			$(image).each(function(i, e){
+				var img = $(this)
+				var gid = getImageGID(img) 
+				var orientation = img.attr('orientation')
+
+				// change the image orientation status and add to 
+				// updated list...
+				IMAGES[gid].orientation = orientation
+				if(IMAGES_UPDATED.indexOf(gid) == -1){
+					IMAGES_UPDATED.push(gid)
+				}
+			})
+		})
+
+
+		// marks...
+		// XXX toggle marking a block is not yet supported...
+		.on('togglingMark', function(evt, img, action){
+			var gid = getImageGID(img) 
+
+			// add marked image to list...
+			if(action == 'on'){
+				MARKED.push(gid)
+
+			// remove marked image from list...
+			} else {
+				MARKED.splice(MARKED.indexOf(gid), 1)
+			}
+		})
+		.on('removeingRibbonMarks', function(evt, ribbon){
+			$.each(DATA.ribbons[getRibbonIndex(ribbon)], function(_, e){
+				var i = MARKED.indexOf(e)
+				if(i != -1){
+					MARKED.splice(i, 1)
+				}
+			})
+		})
+		.on('removeingAllMarks', function(evt){
+			MARKED.splice(0, MARKED.length)
+		})
+		.on('markingRibbon', function(evt, ribbon){
+			$.each(DATA.ribbons[getRibbonIndex(ribbon)], function(_, e){
+				var i = MARKED.indexOf(e)
+				if(i == -1){
+					MARKED.push(e)
+				}
+			})
+		})
+		.on('markingAll', function(evt){
+			MARKED.splice(0, MARKED.length)
+			MARKED.concat(DATA.order)
+		})
+		.on('invertingMarks', function(evt, ribbon){
+			$.each(DATA.ribbons[getRibbonIndex(ribbon)], function(_, e){
+				var i = MARKED.indexOf(e)
+				if(i == -1){
+					MARKED.push(e)
+				} else {
+					MARKED.splice(i, 1)
+				}
+			})
+		})
+
+
+		// caching...
+		.on('reloadedRibbon updatedRibbon', function(evt, ribbon){
+
+			window.DEBUG && console.log('>>> (ribbon:', getRibbonIndex(ribbon), ') Updating cache...')
+
+			preCacheRibbonImages(ribbon)
+		})
+
+		// info...
+		.on([
+				'focusingImage',
+				'rotatingLeft',
+				'rotateingRight',
+				'togglingMark'
+			].join(' '), 
+			function(evt, image){
+				updateGlobalImageInfo($(image))
+			})
+		.on([
+				'removeingAllMarks',
+				'removeingRibbonMarks',
+				'markingAll',
+				'markingRibbon',
+				'invertingMarks'
+			].join(' '), 
+			function(){
+				updateGlobalImageInfo()
+			})
+}
+
+
+
+/**********************************************************************
+* vim:set ts=4 sw=4 :                                                */