Paul Shorey | UI Design & Development Download resume | hidden | hidden
2016 - All Events .NYC
"Mobile-first" design all the way! Responsive AngularJS web app, made with Ionic, to work as a standalone iPhone and Android app too. An experiment in content curation and aggregation. PhantomJS bot constantly crawls the web, updating events in the database. After a busy workday, see what's going on in town. This crawling system and user interface can very quickly be adapted to any type of content or niche.
 
Code samples below | http://allevents.nyc »
2015-2016 - YouNow
Not just a cool app, but a revolution. Worked on an Agile team to constantly build new features and optimize the existing codebase, using the latest technology. Younow.com »
2014 - Angular CMS
Learning AngularJS, wanted to see what I can do, came up with an "angular" design for a custom content management system. Triangle shapes pointing from the top menu (dark blue), to the content menu (blue), to the content form (light blue). Great practice fitting a lot of functinality into a simple app. Try it at: http://angularcms.net »
2014 - Carte Blanche Jazz Band
Fun website to promote a very cool band. An illustrator painted and scanned all these graphics. I made them dance on the screen. CarteBlancheJazzBand.com »
2014 - Shien Cosmetics
Custom designed shopify e-commerce theme. Responsive, of course. ShienCosmetics.com »
2014 - Photo gallery
A simple photo viewer and browser. Use the mouse, or keyboard just as well. Magnification feature could zoom in a bit deeper. Preview: PaulShorey.artspaces.net/photo »
2009-2014 - Focus Features Films
Served as lead developer at this enterprise movie news site, until they closed the NYC office. Many people relied on it for movie and industry news. It later evolved to be more strictly mini-sites and marketing sites for individual feature films. I've made sites and apps for films like Moonrise Kingdom, Tinker Tailor Soldier Spy, Coraline, and Boxtrolls. FocusFeatures.com »
2012 - Focus Features TV
Google TV was supposed to be the next big thing: a smart TV that searched content from all over the web, and had channels like Roku and Samsung do. I developed a prototype for a Tv channel. It runs in a modified the Chrome browser, as most other Google inventions. But the user would only have the arrow keys, "enter" and "esc" to navigate it. FocusFeatures.com/TV »
2008 - User Friendly CMS
Before Wordpress re-designed their UI in 2009, it was difficult to use, though not as difficult as Joomla/Droopal/Typo3. Back then it was an tedious work for independent website owners to manage their site using a full featured CMS. I designed and developed this "easy" CMS for my clients. Made with PHP/MySQL on the backend and jQuery UI on the front. It is built into the site itself. It uses custom html tags that lay dormant until the user signs in. Since then, most of my clients migrated to SquareSpace and Shoppify. But, the CMS and demo site are still running. Login here: http://userfriendlycms.artspaces.net »
2008 - Early websites
Adding here mainly for my own nostalgia, but these have been made in 2008/2009, on a custom PHP CMS platform. Some are still in use and functional, just like the custom CMS that runs them (above). JewelryByTracie.com »  HandMadeByLaura.com »  PurpleLanternStudio.com »
Contracts, Partners, Full time work
I just want to create cool things. Come from an art background, but really like tech. Enjoy meeting and working with new people. Lets get together and talk code, business, life...
Please, download my resume and contact me, above
Code samples
 
OOP Javascript:
Clock hands UI »
Fibonacci sequence »
Simple JSON app »
 
AngularJS meets ReactJS:


angular.module('appNyc.components', [])

.directive('reactEventslist', function (reactDirective, EventService,$timeout, $rootScope) {
	return {
		restrict: 'A',
		scope: {
			data: '='
		},
		link: function (scope, element, attrs) {
			scope.vm = {};

			// load template -- react component
			scope.list_ready = function(){
				var query = {};
				query.category = scope.data.category;
				query.scene = scope.data.scene;
				query.time = scope.data.time;
				EventService.getEvents(query)
				.then(function (response) {
					scope.vm.events = response.data.data;

					ReactDOM.render(  
					  React.createElement(React.html.eventslist, {vm:scope.vm,data:scope.data}), 
					  angular.element(element)[0]
					);

				}, function (error) {
					console.error(error);
				});
			}

			// loading animation -- default state
			scope.list_reset = function(){
				scope.vm = {};
				ReactDOM.render( 
					React.createElement(React.html.eventslist_loading, {data:scope.data}), 
					angular.element(element)[0]
				);
				$timeout(function(){
					if (element) {
						$(element).addClass('ready');
					}
				},1000);
			}
			scope.list_reset();

			// lazyload -- make API call only for content in view
			if (!$rootScope.lazyLoadedLists) {
				$rootScope.lazyLoadedLists = {};
			}
			scope.element = element;
			scope.$watch(
			function( scope ) {
				element = scope.element;
				var window_width = (window.innerWidth || document.documentElement.clientWidth);
				var rect = element[0].getBoundingClientRect();
				if (rect.left > 0 && rect.left < window_width) {

					if (!$rootScope.lazyLoadedLists[ scope.data.category ]) {
						$rootScope.lazyLoadedLists[ scope.data.category ] = scope;
						console.log('listing ',scope.data.category);
						scope.list_ready()
					}

				} else {
					if ($rootScope.lazyLoadedLists[ scope.data.category ]) {
						delete $rootScope.lazyLoadedLists[ scope.data.category ];
						scope.list_reset();
					}
				}
			});

		}
	}
})

;
			

if (!React.html) {
	React.html = {};
}
React.html['eventslist'] = React.createClass({
	bindClick(e, link) {
		// Ionic opens href links in its own browser, this is to escape that. However, thinking of ditching Ionic for something faster like famo.us
		window.open(link, '_system');
		e.preventDefault();
	},
	render: function (events) {

		var rows = [];
		var old_timestring = '';
		for (var i=0; i < this.props.events.length; i++) {
			var event = this.props.events[i];
			if (!event.texts) {
				break;
			}
			var timestring = Date.create(event.timestamp).short();
			var todayEnd = moment().endOf('day').format('x');
			if (event.timestamp < todayEnd - 1) { // party must end before midnight, because we don't know when exactly tomorrow's dates are, most come in as 12:00am
				timestring = 'today';
			} else if (event.timestamp < todayEnd - 1 + 1000*60*60*24) {
				timestring = 'tomorrow';
			} else if (event.timestamp < todayEnd - 1 + 1000*60*60*24 *6) {
				timestring = 'this week';
			} else if (event.timestamp < todayEnd - 1 + 1000*60*60*24 *30) {
				timestring = 'this month';
			}

			// 
			if (timestring != old_timestring) {
				rows.push(
{timestring}
); old_timestring = timestring; } // var time = moment(event.timestamp).format('h:mma'); if (time=='12:00am') { time = ''; } var subtext = []; if (timestring.indexOf('week') != -1 || timestring.indexOf('month') != -1) { subtext.push( {moment(event.timestamp).format('MMM D') +' '+time}); } else if (time && time!='12:00am') { subtext.push( {time}); } subtext.push({event.source_host.substr(0, event.source_host.indexOf('.'))}); // rows.push(
{event.texts[0]} {event.texts[1]} {event.texts[2]}
{subtext}
); } return
{rows}
; } });
AngularJS directives. Custom carousel, and scroll style events. Used in allevents.nyc homepage:

.directive('scrollable', function ($timeout) {
	return {
		restrict: 'A',
		scope: {
			which: '='
		},
		link: function (scope, element, attrs) {

			// to scroll or not to scroll
			scope.$watch(
				function () {
					if (scope.$parent.vm.listJustAdded) {
						return scope.$parent.vm.listJustAdded;
					} else {
						return false;
					}
				},
				function (newValue, oldValue) {
					// scroll to beginning
					if (newValue && newValue != oldValue) {
						var target = element[0];
						var duration = 400; // target.clientWidth / 2;

						var scrollTo = 0;
						target.doNotScroll = 'scroll--changed';
						$timeout(function () {
							$(target)
								.animate({
									scrollLeft: 0
								}, {
									duration: duration
								});

							$timeout(
								scope.scrollCheck,
								duration + 10
							);

						}, 100);
					}
				}
			);

			// the arrows
			$(element)
			.siblings('[scrollable-left]')
			.click(function () {
				var target = element[0];
				if (target.doNotScroll) {
					return;
				}
				var duration = 400;

				var scrollTo = target.scrollLeft - target.clientWidth;
				$(target)
					.animate({
						scrollLeft: scrollTo
					}, {
						duration: duration
					});

				target.doNotScroll = 'scrollable-left';
				$timeout(
					scope.scrollCheck,
					duration
				);
			});
			$(element)
			.siblings('[scrollable-right]')
			.click(function () {
				var target = element[0];
				if (target.doNotScroll) {
					return;
				}
				var duration = 400;

				var scrollTo = target.scrollLeft + target.clientWidth;
				$(target)
					.animate({
						scrollLeft: scrollTo
					}, {
						duration: duration
					});

				target.doNotScroll = 'scrollable-right';
				$timeout(
					scope.scrollCheck,
					duration
				);
			});

			// finish scroll position to nearest column or page
			scope.scrollfix = function(){
				var target = element[0];
				var duration = 200;
				if (target.doNotScroll) {
					return;
				}
				// what direction?
				var round = 'ceil';
				if (target.scrollLeft < target.scrollLeftLast) {
					round = 'floor';
				}
				// finish scrolling - to closest column
				var columns = Math[round](target.scrollLeft / target.firstElementChild.firstElementChild.clientWidth);
				var scrollTo = target.firstElementChild.firstElementChild.clientWidth * columns;
				// go
				$(target).animate({
					scrollLeft: scrollTo
				}, {
					duration: duration
				});
				// done
				target.doNotScroll = true;
				$timeout(
					scope.scrollCheck,
					duration
				);
			};
			$(element).scroll(function(){
				window.clearTimeout(scope.scrollfix_timeout);
				scope.scrollfix_timeout = window.setTimeout(function(){
					scope.scrollfix();
				},222); // timeout must be greater than duration of 
			});

			// ends - disable arrow when at beginning or end
			scope.scrollCheck = function () {
				var target = element[0];
				target.doNotScroll = false;
				target.scrollLeftLast = target.scrollLeft;
				if (target.scrollLeft<10) {
					$(element).siblings('[scrollable-left]').addClass('scrollEnd');
				} else {
					$(element).siblings('[scrollable-left]').removeClass('scrollEnd');
				}
				if (target.scrollLeft > ( target.scrollWidth - target.parentElement.scrollWidth - 10 ) ) {
					$(element).siblings('[scrollable-right]').addClass('scrollEnd');
				} else {
					$(element).siblings('[scrollable-right]').removeClass('scrollEnd');
				}
			};
			$timeout(
				scope.scrollCheck,
				500
			);
			$(window).on('resize',scope.scrollCheck);
			scope.$on('$destroy',function(){
				$(window).off('resize',scope.scrollCheck);
			});
		}
	}
})
			
More code at github.com/paulshorey