My objective is to work on front-end development and design for advanced web applications. I want to further my javascript skills and explore the newest front-end technologies such as HTML5 canvas and jQuery Mobile, as well as gain more experience with back-end programming.
Projects:
-
RayV.com Corporate Site
I did everything on this site, starting with two bare Linux servers, the HTML5 Boilerplate template, and a few JS plugins.
I've set up a syncing system using Git for fast and foolproof backups and updates. After making changes on my local testing server, I just enter a few terminal commands to automatically upload the new and modified files to the production servers.
Highlights:
- Visual design and illustrations including homepage slider illustrations
- Dynamic PHP templates for header, footer and sidebar
- Git syncing system
- Subdomain management using Apache virtual hosts and mod_rewrite
- integration of JavaScript sliders, tabs, and dropdown menu
-
RayV Template Theming Tool
This is a tool for creating custom CSS themes for RayV's white-label subscription video sites. I designed everything from IA through visual, and coded everything from HTML/CSS to JS and PHP. This tool can be easily used by non-technical users to create themes for the RayV TVMP template.
It uses extensive jQuery to implement a variety of controls over the visual design. Users can share designs by copying and pasting the URL from the address bar. Once they are satisified with their theme, they simply copy and paste the CSS output from the lower right textbox into the CSS override in the CMS, which is on different domain. (The theming tool does not work well on IE due to use of a special plugin to recreate CSS3 effects on IE. However, generated CSS is fully compatible with IE 7-9)
Below is a link to a prototype site with CSS generated by theming tool. I used PHP session cookies to create a full simulation of registration and purchase flow.
Sample Code:
$(document).ready(function() { // on pageload, sets all inputs to values from Get string // Read a page's GET URL variables and return them as an associative array. function getUrlVars() { var vars = [], hash; var hashes = window.location.href.slice(window.location.href.indexOf("?") + 1).split("&"); for(var i = 0; i < hashes.length; i++) { hash = hashes[i].split("="); vars.push(hash[0]); vars[hash[0]] = hash[1]; } return vars; } var urlVarArray = getUrlVars(); // if query string is present, checks for each parameter, then sets values for form inputs if (getUrlVars().length > 1) { if (window.location.href.indexOf("color1") > -1) { $("#color1").val(unescape(urlVarArray["color1"])); } if (window.location.href.indexOf("color2") > -1) { $("#color2").val(unescape(urlVarArray["color2"])); } if (window.location.href.indexOf("color3") > -1) { $("#color3").val(unescape(urlVarArray["color3"])); } if (window.location.href.indexOf("color4") > -1) { $("#color4").val(unescape(urlVarArray["color4"])); } if (window.location.href.indexOf("color5") > -1) { $("#color5").val(unescape(urlVarArray["color5"])); } if (window.location.href.indexOf("corner") > -1) { $("#corner").val(unescape(urlVarArray["corner"])); } if (window.location.href.indexOf("margin") > -1) { $("#margin").val(unescape(urlVarArray["margin"])); } if (window.location.href.indexOf("fade") > -1) { $(".faderadio").val([ (unescape(urlVarArray["fade"])) ]); } } //Sets some variables based on form input values var bodycolor = $("#color1").val(); var boxcolor = $("#color2").val(); var bordrcolor; var h1color = $("#color3").val(); var linkcolor = $("#color4").val(); var textcolor = $("#color5").val(); var radius = parseInt($("#corner").val()); var margin = parseInt($("#margin").val()); var fade = $("#pickerform input[type='radio']:checked").val(); // farbtastic color picker code /////////////////////// var f = $.farbtastic("#picker"); var p = $("#picker").css("opacity", 0.25); var selected; $(".colorwell") .each(function () { f.linkTo(this); $(this).css("opacity", 0.75); }) .focus(function() { if (selected) { $(selected).css("opacity", 0.75).removeClass("colorwell-selected"); } f.linkTo(this); p.css("opacity", 1); $(selected = this).css("opacity", 1).addClass("colorwell-selected"); }); // Slider code //////////////////////////////////////// $("#slider1").slider({ value: radius, max: 20, slide: function(event, ui) { $(".whitebox, .mainbox, .firstnav, .lastnav").css({"-moz-border-radius":ui.value,"border-radius":ui.value}); $(".buttons>a, .button2>a, .btndisabled>a").css({"-moz-border-radius":(ui.value+2),"border-radius":(ui.value+2)}); $(".mrec").css("margin-top",((ui.value+5)/2)); $("#corner").val(ui.value); } }); $("#slider2").slider({ value: margin, max: 20, slide: function(event, ui) { $(".topmargin").css("margin-top",ui.value); $(".botmargin").css("margin-bottom",ui.value); $(".rmain").css("width",(322-ui.value)); $(".rmain780").css("width",(182-ui.value)); $("#margin").val(ui.value); } }); // Fade Radio Button ///////////////////////////////////// $(".faderadio").click(function() { fade = this.value; fadeupdate(); }) //convert box background to border color function boxtobordr(boxcolor) { var boxunpack = f.unpack(boxcolor); var bordrhsl = f.RGBToHSL(boxunpack); bordrhsl[1] = bordrhsl[1]*.6; if (bordrhsl[2] < .5) { bordrhsl[2] = bordrhsl[2]+(Math.abs(bordrhsl[2] - .7)*.3); } else { bordrhsl[2] = bordrhsl[2]-(Math.abs(bordrhsl[2] - .3)*.3); } var bordrrgb = f.HSLToRGB(bordrhsl); bordrcolor = f.pack(bordrrgb); } // CSS updater functions function colorupdate() { bodycolor = $("#color1").val(); $("body").css("background-color",bodycolor); boxcolor = $("#color2").val(); $(".whitebox").css("background-color",boxcolor); boxtobordr(boxcolor); $(".bordr").css("border-color",bordrcolor); $(".btndisabled a").css("background-color",bordrcolor); h1color = $("#color3").val(); $("h1").css("color",h1color ); linkcolor = $("#color4").val(); $("a,.linkcolor").css("color",linkcolor); $(".buttons > a").css({"background-color":linkcolor,"color":boxcolor}); $(".btndisabled > a").css("color",boxcolor); var stylestring = "" $("#styletag").remove(); $("head").append(stylestring); textcolor = $("#color5").val(); $("body").css("color",textcolor); } function cornerupdate() { $(".whitebox, .mainbox, .firstnav, .lastnav").css({"-moz-border-radius":radius,"border-radius":radius}); $(".buttons>a, .button2>a, .btndisabled>a").css({"-moz-border-radius":(radius+2),"border-radius":(radius+2)}); $(".mrec").css("margin-top",((radius+5))); } function marginupdate() { $(".topmargin").css("margin-top",margin); $(".botmargin").css("margin-bottom",margin); $(".rmain").css("width",(322-margin)); $(".rmain780").css("width",(182-margin)); } function fadeupdate() { $(".whitebox,.fadebox").removeClass("kfade wfade nofade").addClass(fade); if (fade=="kfade") { fadeclass="fadek"; fadealign="bottom"; } else if (fade=="wfade") { fadeclass="fadew"; fadealign="top"; } else { fadeclass="fadeno"; } } //Focus on background color input $("#color1").focus(); //any movement over pickerform updates color $("#pickerform").bind("mouseover mousemove", colorupdate ); //colorpicker expand and collapse var expanded = true; $(".expand").click(function() { if (expanded == true) { $("#themerdiv").animate({ right: -180 }); expanded = false; $(this).text("«") } else { $("#themerdiv").animate({ right: 0 }); expanded = true; $(this).text("»") }; }) //All link clicks are routed through form $(".link,.buttons>a,.acctnav>ul>li>a").click(function(){ var target = ($(this).attr("href")); $("#pickerform").attr("action", target) $("#pickerform").submit(); return false; }) // reset button /////////////////// $("#reset").click(function() { window.location = (location.protocol+"//"+location.host+location.pathname); }) colorupdate(); cornerupdate(); fadeupdate(); marginupdate(); //URL copy paste var cssString = "body {background-color:"+bodycolor+"; color:"+textcolor+";}\n.whitebox {background-color:"+boxcolor+";}\n.bordr {border-color:"+bordrcolor+";}\n.btndisabled a {background-color:"+bordrcolor+";color:"+boxcolor+";}\nh1 {color:"+h1color+";}\na, .linkcolor {color:"+linkcolor+";}\n.buttons a,.linkinvert,.current {background-color:"+linkcolor+";color:"+boxcolor+";}\n.linkinvert a, .current a {color:"+boxcolor+"}\n.whitebox, .mainbox, .firstnav, .lastnav {-moz-border-radius:"+radius+"px;border-radius:"+radius+"px;}\n.buttons a, .button2 a, .btndisabled a {-moz-border-radius:"+(radius+2)+"px;border-radius:"+(radius+2)+"px;}\n.topmargin {margin-top:"+margin+"px;}\n.botmargin {margin-bottom:"+margin+"px;}\n.rmain {width:"+(322-margin)+"px;}\n.rmain780 {width:"+(182-margin)+"px;}\n#banner.kfade {background-image:url('/App_Themes/Default/images/"+fadeclass+"128.png'); background-position:0 "+fadealign+";}\nheader nav.kfade {background-image:url('/App_Themes/Default/images/"+fadeclass+"32.png'); background-position:0 "+fadealign+";}\n#main .whitebox.kfade {background-image:url('/App_Themes/Default/images/"+fadeclass+"256.png'); background-position:0 "+fadealign+";}\nfooter div.kfade {background-image:url('/App_Themes/Default/images/"+fadeclass+"32.png'); background-position:0 "+fadealign+";}\n.acctnav li a.kfade {background-image:url('/App_Themes/Default/images/"+fadeclass+"32.png'); background-position:0 "+fadealign+";}\n.vscrollbtn.kfade {background-image:url('/App_Themes/Default/images/"+fadeclass+"16.png'); background-position:0 "+fadealign+";}"; $("#urlcopypaste").val(cssString); //End of themer code//////////////////////////////////////////////////////////////////////////////// //Sets height of right column to equal the left column var lmainheight = $(".lmain").css("height"); $(".rmain").css("height", lmainheight); var lmainheight = $(".lmain780").css("height"); $(".rmain780").css("height", lmainheight); //Payment method switcher $("#ccradio").click( function() { $("#cctable").css("display", "block") $("#pptable").css("display", "none") }) $("#ppradio").click( function() { $("#cctable").css("display", "none") $("#pptable").css("display", "block") }) // initialize scrollable with mousewheel support $(".scrollable").scrollable({ vertical: true, mousewheel: true }); // initialize packages scrollable $(".pkgscroll").scrollable({mousewheel: true}); // disables right arrow for 1-item scrollables $(".items").each(function(){ if ($(this).children('.item').length == 1) { $(this).parent().next('.next').addClass('disabled'); } }) //inverts backgorund and text for account nav hovers $(".acctnav li a").hover(function(){ $(this).addClass("linkinvert"); }, function(){ $(this).removeClass("linkinvert"); }) $(".guide-navigation span").hover(function(){ if($(this).hasClass("disabled-btn")){ } else { $(this).addClass("linkinvert"); } }, function(){ $(this).removeClass("linkinvert"); }) //Enables overlays $(".package_overlay > .button2[rel]").overlay({ mask: { color: '#777', loadSpeed: 200, opacity: 0.8 } }); //Nav login script $("#navusername_fake").click(function(){ $(this).hide(); $("#navusername").show().focus(); }) $("#navpassword_fake").click(function(){ $(this).hide(); $("#navpassword").show().focus(); }) $("#navloginlink").click(function(e) { if ($("#btn_nav_login").hasClass("btndisabled")) { e.preventDefault(); } }) $("#navusername").keyup(function(){ if ($(this).val() != "" && $("#navpassword").val() != "") { $("#btn_nav_login").removeClass("btndisabled").addClass("buttons"); colorupdate(); } else { $("#btn_nav_login").removeClass("buttons").addClass("btndisabled"); colorupdate(); } }) $("#navpassword").keyup(function(){ if ($(this).val() != "" && $("#navusername").val() != "") { $("#btn_nav_login").removeClass("btndisabled").addClass("buttons"); colorupdate(); } else { $("#btn_nav_login").removeClass("buttons").addClass("btndisabled"); colorupdate(); } }) }); -
IA for LG Smart TV app for RayV
RayV contracted with LG to develop a live TV subscription service for the LG app store. The app will be availale on LG Smart TVs. It is basically a 10-foot UI for the RayV TVMP web app that I previously designed. I created the IA design for the app registration and purchase flow.
-
Banner Snippet Loader for Interpolls
One of the most time-consuming tasks at Interpolls was that we had to load each ad banner text snippet in browser to ensure that every snippet would load properly. There were sometimes up to 100 placements, which would take up to an hour to test for each ad campaign. I developed an automated tool to load every banner in a paginated serises of webpages, significantly reducing test time.
My old boss reports that the company is still using this program to cut down on QA time. The language is CFML, which I realize is pretty much a dead language, but I feel that I demonstrates my problem-solving skills.
Snippet Loader Code:
<cfsetting showdebugoutput="no" enablecfoutputonly="no"> <cfparam name="campaigndir" default=""> <cfparam name="pagenum" default="1"> <cfparam name="perpage" default="10"> <cfif perpage lt 1> <cfset perpage = 10> <cfelseif perpage gte 1> <cfset perpage = Int(perpage)> <cfelse> <cfset perpage = 10> </cfif> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>Text Snippet Loader</title> <style> body { background-color:#ddd; font-family: arial, sans-serif; font-size: 12px; text-align: left; padding: 8px } h1 { margin: 0 0 5px; } #formdiv { margin: 10px 0; } .snipdiv { padding: 0 0 6px; } .snipnum, a.pagelink { color: #fff; background: #05b; padding: 2px 5px; margin-right: 2px; font-weight: bold; text-decoration: none; } a.pagelink { display: block; margin-right: 0px; } a.pagelink:hover { background: #0ce; } div.pageconfigs { padding: 2px 5px; background: #fff; } div.pagelet { float: left; width: 100px; margin: 3px 3px 0 0; } div.pagingdiv { color: #f00; font-weight: bold; height: 3em; } .filename { padding: 6px 0; } .snipwrite { background: #fff; padding: 6px; margin-bottom: 8px; } .configset { color: #f00; font-weight: bold; } </style> </head> <body> <h1>Text Snippet Loader</h1> <div id="formdiv"> <form id="sform" name="sform" action="snippetloader.poll" method="get" onsubmit="return validateform(this)"> <cfoutput> Campaign root directory: http://hs.interpolls.com/cache/<input name="campaigndir" type="text" size="50" value="#campaigndir#" tabindex="2"> <input type="submit" value="load text snippets"> <br> Snippets per page: <input name="perpage" type="text" size="4" value="#perpage#"> </form> </div> </cfoutput> <!--- Build and Sort Array of Snippets---> <cfset coldfdir = REReplace("#Application.rootDir#/cache/#campaigndir#","/","\","ALL")> <cfdirectory action="List" directory="#coldfdir#" name="snipfiles" filter="*banner.txt" sort="ASC"> <cfset sniparr = arrayNew(2)> <cfset i = 1> <cfloop condition="i neq 0"> <cfif snipfiles.name[i] eq ""> <cfset i = 0> <cfelse> <cffile action="read" file="#Application.rootDir#\cache\#campaigndir#\#snipfiles.name[i]#" variable="sniptext"> <cfset st = REFind("\d+",sniptext,1,"TRUE")> <cfset c = Mid(sniptext, st.pos[1], st.len[1])> <cfset sniparr[i][1] = c> <cfset sniparr[i][2] = sniptext> <cfset sniparr[i][3] = snipfiles.name[i]> <cfset i = i + 1> </cfif> </cfloop> <cfloop index="outer" from="1" to="#arrayLen(sniparr)#"> <cfloop index="inner" from="1" to="#arrayLen(sniparr)-1#"> <cfif sniparr[inner][1] gt sniparr[outer][1]> <cfset arraySwap(sniparr,outer,inner)> </cfif> </cfloop> </cfloop> <cfif arrayLen(sniparr) neq 0> <!--- Paging System ---> <cfset pagecount = Int(arrayLen(sniparr) / perpage) + 1> <cfset escapecmpdir = Replace(campaigndir,"/","%2F","ALL")> <cfset firstconfigset = (pagenum * perpage) - (perpage-1)> <cfif pagenum neq pagecount> <cfset lastconfigset = firstconfigset + (perpage-1)> <cfelse> <cfset lastconfigset = arrayLen(sniparr)> </cfif> <cfset pagelinks = ""> <cfloop index="i" from="1" to="#pagecount-1#"> <cfset pagelinks = pagelinks & "<div class=""pagelet""><a class=""pagelink"" href=""snippetloader.poll?campaigndir=#escapecmpdir#&pagenum=#i#&perpage=#perpage#"">#i*perpage-(perpage-1)#"> <cfif (i*perpage-(perpage-1)) neq (i*perpage)> <cfset pagelinks = pagelinks & "- #i*perpage#"> </cfif> <cfset pagelinks = pagelinks & "</a><div class=""pageconfigs"">#sniparr[i*perpage-(perpage-1)][1]#"> <cfif (sniparr[i*perpage-(perpage-1)][1]) neq (sniparr[i*perpage][1])> <cfset pagelinks = pagelinks & "- #sniparr[i*perpage][1]#"> </cfif> <cfset pagelinks = pagelinks & "</div></div>"> </cfloop> <cfif Int(arrayLen(sniparr)/perpage) neq (arrayLen(sniparr)/perpage)> <cfset pagelinks = pagelinks & "<div class=""pagelet""><a class=""pagelink"" href=""snippetloader.poll?campaigndir=#escapecmpdir#&pagenum=#pagecount#&perpage=#perpage#"">#pagecount*perpage-(perpage-1)#-#arrayLen(sniparr)#</a><div class=""pageconfigs"">#sniparr[pagecount*perpage-(perpage-1)][1]# - #sniparr[arrayLen(sniparr)][1]#</div></div> "> </cfif> <cfoutput> <div class="pagingdiv"> #pagelinks# </div> <div style="clear:both"></div> <hr /> </cfoutput> <!--- Format Snippets and Write To Page ---> <cfloop index="i" from="#firstconfigset#" to="#lastconfigset#"> <cfset c = sniparr[i][1]> <cfset sniptext = sniparr[i][2]> <cfset snipname = sniparr[i][3]> <cfset snipwrite = Replace(sniptext,"<","<","ALL")> <cfset snipwrite = Replace(snipwrite,">",">","ALL")> <cfset snipwrite = REReplace(snipwrite,"ipoll_c\s=\s\d+;","ipoll_c = <span class='configset'>#c#</span>;","ONE")> <cfset snipwrite = Replace(snipwrite,"#Chr(13)##Chr(10)#","<br>","ALL")> <cfoutput> <div class="snipdiv"> <div class="filename"><span class="snipnum">#i#</span>#snipname#</div> <div class="snipwrite">#snipwrite#</div> #sniptext# </div><hr /> </cfoutput> </cfloop> <cfoutput> <div class="pagingdiv"> #pagelinks# </div> </cfoutput> </cfif> </body> </html>Generated HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html> <head> <title>Text Snippet Loader</title> <style> body { background-color:#ddd; font-family: arial, sans-serif; font-size: 12px; text-align: left; padding: 8px } h1 { margin: 0 0 5px; } #formdiv { margin: 10px 0; } .snipdiv { padding: 0 0 6px; } .snipnum, a.pagelink { color: #fff; background: #05b; padding: 2px 5px; margin-right: 2px; font-weight: bold; text-decoration: none; } a.pagelink { display: block; margin-right: 0px; } a.pagelink:hover { background: #0ce; } div.pageconfigs { padding: 2px 5px; background: #fff; } div.pagelet { float: left; width: 100px; margin: 3px 3px 0 0; } div.pagingdiv { color: #f00; font-weight: bold; height: 3em; } .filename { padding: 6px 0; } .snipwrite { background: #fff; padding: 6px; margin-bottom: 8px; } .configset { color: #f00; font-weight: bold; } </style> </head> <body> <h1>Text Snippet Loader</h1> <div id="formdiv"> <form id="sform" name="sform" action="snippetloader.poll" method="get" onsubmit="return validateform(this)"> Campaign root directory: http://hs.interpolls.com/cache/<input name="campaigndir" type="text" size="50" value="aol/slurpeeapr10/" tabindex="2"> <input type="submit" value="load text snippets"> <br> Snippets per page: <input name="perpage" type="text" size="4" value="5"> </form> </div> <div class="pagingdiv"> <div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=1&perpage=5">1- 5</a><div class="pageconfigs">1000- 1004</div></div><div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=2&perpage=5">6- 10</a><div class="pageconfigs">1005- 3001</div></div><div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=3&perpage=5">11- 15</a><div class="pageconfigs">3002- 3007</div></div><div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=4&perpage=5">16-19</a><div class="pageconfigs">3008 - 3012</div></div> </div> <div style="clear:both"></div> <hr /> <div class="snipdiv"> <div class="filename"><span class="snipnum">6</span>aol_content-vertical-movies-300x250-entertainment-movies-38-geos-and-a13-24-38-select-geos-and-a13-24_slurpee-apr-10_300x250banner.txt</div> <div class="snipwrite"><br></div> </div><hr /> <div class="snipdiv"> <div class="filename"><span class="snipnum">7</span>aol_audience-rosters-mri-household-lifestyle-clusters-aol-oo-728x90_slurpee-apr-10_728x90banner.txt</div> <div class="snipwrite"><br></div> </div><hr /> <div class="snipdiv"> <div class="filename"><span class="snipnum">8</span>aol_behavioral-audience-behaviors-moviegoer-aol-media_slurpee-apr-10_728x90banner.txt</div> <div class="snipwrite"><br></div> </div><hr /> <div class="snipdiv"> <div class="filename"><span class="snipnum">9</span>aol_content-vertical-movies-728x90-entertainment-movies-38-geos-and-a13-24-38-select-geos-and-a13-24_slurpee-apr-10_728x90banner.txt</div> <div class="snipwrite"><br></div> </div><hr /> <div class="snipdiv"> <div class="filename"><span class="snipnum">10</span>msn_microsoft-united-states-english-age-18-24-bt-movie-watchers-yes_slurpee-apr-10_160x600banner.txt</div> <div class="snipwrite"><br></div> </div><hr /> <div class="pagingdiv"> <div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=1&perpage=5">1- 5</a><div class="pageconfigs">1000- 1004</div></div><div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=2&perpage=5">6- 10</a><div class="pageconfigs">1005- 3001</div></div><div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=3&perpage=5">11- 15</a><div class="pageconfigs">3002- 3007</div></div><div class="pagelet"><a class="pagelink" href="snippetloader.poll?campaigndir=aol%2Fslurpeeapr10%2F&pagenum=4&perpage=5">16-19</a><div class="pageconfigs">3008 - 3012</div></div> </div> </body> </html>Screenshot of generated page:
Skills:
- HTML/CSS
-
JavaScript
- JQUERY
- JSON
- AJAX
-
PHP
- Wordpress
- Drupal
- MySQL
- Python
- Linux
- Apache
Favorite Tools:
- Espresso, Textmate with Zen Coding
- Chrome, Firefox with Firebug and Web Developer toolbar
- Photoshop
- OmniGraffle
- LAMP stack, MAMP for local testing
- Git VCS
- Mac OSX
- Charles, Fiddler HTTP proxy
- HTML5 Boilerplate
- CSS3 Pie