add 37signals' chalk

This commit is contained in:
Sami Samhuri 2010-11-24 21:55:01 -08:00
parent 511d19dbd0
commit 4b05c87dd2
10 changed files with 631 additions and 0 deletions

173
Chalk/chalk.js Normal file
View file

@ -0,0 +1,173 @@
(function() {
window.addEventListener("DOMContentLoaded", function() {
var activateTool, activeTool, background, body, canvas, chalkboard, close, closeShareWindow, context, count, createPattern, draw, erasePattern, ledge, lightswitch, openShareWindow, output, points, redPattern, setStroke, shade, share, skip, tools, updateOffsets, whitePattern, x, xOffset, y, yOffset;
body = $("body");
canvas = $("#canvas");
chalkboard = $("#chalkboard");
close = $("#close");
ledge = $("#ledge");
lightswitch = $("#lightswitch");
output = $("#output");
shade = $("#shade");
share = $("#share");
canvas.attr("width", chalkboard.width());
canvas.attr("height", chalkboard.height());
xOffset = (yOffset = 0);
context = canvas.get(0).getContext("2d");
context.lineWidth = 8;
context.lineCap = "round";
context.lineJoin = "miter";
background = new Image();
background.src = "images/background.jpg";
background.onload = function() {
return context.drawImage(background, -128, -129);
};
updateOffsets = function() {
var offsets;
offsets = chalkboard.offset();
xOffset = offsets.left;
return (yOffset = offsets.top);
};
window.addEventListener("orientationchange", updateOffsets);
updateOffsets();
window.addEventListener("touchmove", function(event) {
return event.preventDefault();
});
/* Tools */
createPattern = function(name, callback) {
var image;
image = new Image();
image.src = ("images/chalk-tile-" + (name));
return (image.onload = function() {
return callback(context.createPattern(image, "repeat"));
});
};
setStroke = function(pattern, width) {
context.strokeStyle = pattern;
return (context.lineWidth = width);
};
whitePattern = (redPattern = (erasePattern = null));
createPattern("white.png", function(p) {
return setStroke(whitePattern = p, 8);
});
createPattern("red.png", function(p) {
return (redPattern = p);
});
createPattern("erase.jpg", function(p) {
return (erasePattern = p);
});
tools = ["eraser", "red_chalk", "white_chalk"];
activeTool = "";
activateTool = function(tool) {
var _len, _ref, id, index;
if (tool === activeTool) {
return null;
}
tools.splice(tools.indexOf(tool), 1);
tools.push(activeTool = tool);
_ref = tools;
for (index = 0, _len = _ref.length; index < _len; index++) {
id = _ref[index];
$("#" + (id) + ", #" + (id) + "_tool").css("z-index", index);
}
$("#tools div.tool").removeClass("active");
$("#" + (id) + "_tool").addClass("active");
switch (tool) {
case "red_chalk":
return (context.strokeStyle = setStroke(redPattern, 8));
case "white_chalk":
return (context.strokeStyle = setStroke(whitePattern, 8));
case "eraser":
return (context.strokeStyle = setStroke(erasePattern, 32));
}
};
activateTool("white_chalk");
ledge.delegate("a", "click", function(target) {
return activateTool($(target).attr("id"));
});
/* Drawing */
skip = false;
count = 0;
points = [null];
x = (y = null);
draw = function(point) {
var _ref;
if (point) {
if (skip) {
return (skip = false);
} else {
context.moveTo(x, y);
_ref = point;
x = _ref[0];
y = _ref[1];
return context.lineTo(x, y);
}
} else {
return (skip = true);
}
};
canvas.bind("touchstart", function(event) {
var _ref, touch;
touch = event.touches[0];
_ref = [touch.pageX - xOffset, touch.pageY - yOffset];
x = _ref[0];
y = _ref[1];
return event.preventDefault();
});
canvas.bind("touchmove", function(event) {
var touch;
touch = event.touches[0];
return points.push([touch.pageX - xOffset, touch.pageY - yOffset]);
});
canvas.bind("touchend", function(event) {
return points.push([x, y], [x, y], null);
});
setInterval(function() {
var start;
if (!(points.length)) {
return null;
}
start = new Date();
context.beginPath();
while (points.length && new Date() - start < 10) {
draw(points.shift());
}
return context.stroke();
}, 30);
/* Shade */
lightswitch.bind("click", function(event) {
if (body.hasClass("shade")) {
body.removeClass("shade");
} else {
body.addClass("shade");
}
return event.preventDefault();
});
/* Share */
openShareWindow = function() {
share.addClass("active");
return setTimeout(function() {
output.attr("src", canvas.get(0).toDataURL());
return (output.get(0).onload = function() {
return body.addClass("share");
});
}, 10);
};
closeShareWindow = function() {
share.removeClass("active");
body.removeClass("share");
output.get(0).onload = null;
return output.attr("src", "images/chalk-sprites.png");
};
share.bind("touchstart", function() {
return openShareWindow();
});
close.bind("click", function(event) {
closeShareWindow();
return event.preventDefault();
});
return output.bind("touchcancel", function() {
return setTimeout(closeShareWindow, 50);
});
});
}).call(this);

BIN
Chalk/images/background.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
Chalk/images/chalk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

61
Chalk/index.html Normal file
View file

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html manifest="chalk.manifest">
<head>
<meta charset="utf-8">
<title>Chalk</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" type="text/css" href="stylesheets/chalk.css">
<link rel="apple-touch-icon-precomposed" href="images/chalk.png"/>
<script type="application/javascript" src="zepto.min.js"></script>
<script type="application/javascript" src="chalk.js"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-1876445-15']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div class="share_container">
<div class="close">
<a id="close" href="#"></a>
<div class="instruction"></div>
</div>
<div class="cover">
<div class="instruction"></div>
</div>
</div>
<div id="shade">
<div class="dim"></div>
<div class="dim"></div>
</div>
<a id="lightswitch" href="#"></a>
<button id="share" href="#"></button>
<div id="chalkboard">
<img id="output">
<canvas id="canvas"></canvas>
<div id="ledge">
<div id="targets">
<a id="red_chalk" class="chalk"></a>
<a id="white_chalk" class="chalk"></a>
<a id="eraser"></a>
</div>
<div id="tools">
<div id="red_chalk_tool" class="tool chalk">
<div class="indicator"></div>
</div>
<div id="white_chalk_tool" class="tool chalk">
<div class="indicator"></div>
</div>
<div id="eraser_tool" class="tool eraser">
<div class="indicator"></div>
</div>
</div>
</div>
</div>
</body>
</html>

384
Chalk/stylesheets/chalk.css Normal file
View file

@ -0,0 +1,384 @@
html {
background: #000 url(../images/background.jpg) no-repeat;
-webkit-user-select: none;
}
@media only screen and (orientation:landscape) {
html {
background-position-y: -129px;
}
}
@media only screen and (orientation:portrait) {
html {
background-position-x: -128px;
}
}
body {
margin: 0;
padding: 0;
}
#shade {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
z-index: 10;
visibility: hidden;
-webkit-transition: visibility 0.5s ease-in-out;
}
#shade div.dim {
position: absolute;
top: 0;
left: 0;
background: none;
-webkit-transition: background 0.5s ease-in-out;
}
@media only screen and (orientation:portrait) {
#shade {
height: 946px;
}
#shade div.dim {
width: 768px;
height: 129px;
}
#shade div.dim:nth-child(1) {
top: 0;
}
#shade div.dim:nth-child(2) {
top: 818px;
}
}
@media only screen and (orientation:landscape) {
#shade {
width: 1024px;
}
#shade div.dim {
width: 128px;
height: 690px;
}
#shade div.dim:nth-child(1) {
left: 0;
}
#shade div.dim:nth-child(2) {
left: 896px;
}
}
body.shade #shade {
-webkit-transition: none;
visibility: visible;
}
body.shade #shade div.dim {
background: rgba(0, 0, 0, 0.67);
}
body.shade #ledge {
-webkit-transition-property: opacity, visibility;
-webkit-transition-duration: 0.5s, 0s;
-webkit-transition-delay: 0s, 0.5s;
-webkit-transition-timing-function: ease-in-out;
opacity: 0;
visibility: hidden;
}
body.shade #share {
opacity: 0;
}
#lightswitch {
display: none;
}
@media only screen and (orientation:landscape) {
#lightswitch {
display: block;
position: absolute;
top: 336px;
width: 30px;
height: 100px;
z-index: 11;
}
}
#share {
position: absolute;
background: url(../images/chalk-sprites.png) no-repeat 0 -462px;
width: 49px;
height: 36px;
margin: 0;
padding: 0;
border: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-transition: opacity 0.5s ease-in-out;
}
#share.active {
background-position-y: -498px;
}
@media only screen and (orientation:landscape) {
#share {
top: 85px;
left: 935px;
}
}
@media only screen and (orientation:portrait) {
#share {
top: 38px;
left: 706px;
}
}
#chalkboard {
position: absolute;
}
#chalkboard,
#canvas,
#output {
width: 768px;
height: 690px;
}
@media only screen and (orientation:landscape) {
#chalkboard {
top: 0;
left: 127px;
}
}
@media only screen and (orientation:portrait) {
#chalkboard {
top: 129px;
left: 0;
}
}
/* Tools */
#ledge {
position: absolute;
background: url(../images/chalk-sprites.png) no-repeat 0 0;
width: 266px;
height: 107px;
margin: 0;
padding: 0;
bottom: 20px;
right: 20px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-transition: opacity 0.5s ease-in-out;
}
#tools div.tool {
position: absolute;
bottom: 30px;
background-image: url(../images/chalk-sprites.png);
background-repeat: no-repeat;
background-position-x: 1px;
}
#targets {
position: absolute;
z-index: 5;
overflow: hidden;
bottom: 15px;
height: 62px;
left: 32px;
width: 196px;
}
#targets a {
position: absolute;
display: block;
bottom: 0;
padding: 15px 0;
}
#tools div.chalk {
width: 110px;
height: 32px;
}
#tools div.indicator {
position: absolute;
opacity: 0;
background-image: url(../images/chalk-sprites.png);
background-repeat: no-repeat;
background-position-y: -230px;
width: 36px;
height: 25px;
bottom: -15px;
left: 50%;
margin-left: -18px;
-webkit-transition: opacity 0.25s ease-in-out;
}
#tools div.tool.active div.indicator {
opacity: 1;
}
#red_chalk_tool {
background-position-y: -113px;
left: 5px;
}
#white_chalk_tool {
background-position-y: -151px;
left: 60px;
}
#eraser_tool {
width: 140px;
height: 42px;
background-position-y: -183px;
left: 110px;
}
#targets a.chalk {
width: 60px;
height: 15px;
}
#red_chalk {
left: 0;
}
#white_chalk {
left: 55px;
}
#eraser {
left: 106px;
height: 30px;
width: 90px;
}
/* Shade screen */
#share {
-webkit-transition: opacity 0.5s ease-in-out;
}
#output {
display: none;
}
body.share #ledge,
body.share #share {
opacity: 0;
}
body.share #canvas {
display: none;
}
body.share #output {
display: block;
}
div.share_container div.close,
div.share_container div.cover {
-webkit-transition: all 0.5s ease-in-out;
}
div.share_container div.instruction,
div.share_container div.close a {
-webkit-transition: opacity 0.5s ease-in-out;
opacity: 0;
}
body.share div.share_container div.instruction,
body.share div.share_container div.close a {
-webkit-transition: opacity 0.5s 0.5s ease-in-out;
opacity: 1;
}
@media only screen and (orientation:landscape) {
div.share_container div.close,
div.share_container div.cover {
width: 0;
height: 690px;
}
div.share_container div.close {
float: right;
}
div.share_container div.cover {
float: left;
}
}
@media only screen and (orientation:portrait) {
div.share_container div.close,
div.share_container div.cover {
height: 0;
width: 768px;
}
div.share_container div.cover {
position: absolute;
bottom: 0;
left: 0;
}
}
body.share div.share_container div.close,
body.share div.share_container div.cover {
background-color: #000;
}
div.share_container div.close a {
background: url(../images/chalk-sprites.png) no-repeat 0 -260px;
width: 36px;
height: 36px;
margin: 15px 10px 0 0;
padding: 0;
float: right;
}
@media only screen and (orientation:landscape) {
body.share div.share_container { width: 1024px; height: 690px; }
body.share div.share_container div.close { width: 128px; height: 690px; float: right; }
body.share div.share_container div.close div.instruction { visibility: hidden; }
body.share div.share_container div.cover {
width: 128px;
}
body.share div.share_container div.cover div.instruction {
width: 100px;
height: 100px;
background: url(../images/chalk-sprites.png) no-repeat 0 -360px;
margin: 250px 0 0 10px;
}
}
@media only screen and (orientation:portrait) {
body.share div.share_container { width: 768px; height: 946px; }
body.share div.share_container div.close { height: 130px; }
body.share div.share_container div.close div.instruction {
position: absolute;
top: 40px;
left: 120px;
width: 502px;
height: 68px;
background: url(../images/chalk-sprites.png) no-repeat 0 -297px;
}
body.share div.share_container div.cover { height: 129px; }
body.share div.share_container div.cover div.instruction { visibility: hidden; }
}

13
Chalk/zepto.min.js vendored Normal file
View file

@ -0,0 +1,13 @@
var Zepto=function(){function h(a){return a.filter(function(c){return c!==l&&c!==null})}function i(a,c){this.dom=a;this.selector=c}function b(a,c){return c!==l?b(c).find(a):new i(h(a instanceof i?a.dom:a instanceof Array?a:a instanceof Element?[a]:d.call(e.querySelectorAll(a))),a)}var d=[].slice,e=document,g={append:"beforeEnd",prepend:"afterBegin",before:"beforeBegin",after:"afterEnd"},f,m,l;if(String.prototype.trim===l)String.prototype.trim=function(){return this.replace(/^\s+/,"").replace(/\s+$/,
"")};i.prototype=b.fn;b.extend=function(a,c){for(f in c)a[f]=c[f]};camelize=function(a){return a.replace(/-+(.)?/g,function(c,j){return j?j.toUpperCase():""})};b.fn={compact:function(){this.dom=h(this.dom);return this},get:function(a){return a===l?this.dom:this.dom[a]},remove:function(){return this.each(function(a){a.parentNode.removeChild(a)})},each:function(a){this.dom.forEach(a);return this},filter:function(a){return b(this.dom.filter(function(c){return d.call(c.parentNode.querySelectorAll(a)).indexOf(c)>=
0}))},is:function(a){return this.dom.length>0&&b(this.dom[0]).filter(a).dom.length>0},first:function(){this.dom=h([this.dom[0]]);return this},find:function(a){return b(this.dom.map(function(c){return d.call(c.querySelectorAll(a))}).reduce(function(c,j){return c.concat(j)},[]))},closest:function(a){var c=this.dom[0].parentNode;for(a=d.call(e.querySelectorAll(a));c&&a.indexOf(c)<0;)c=c.parentNode;return b(c&&c!==e?c:[])},pluck:function(a){return this.dom.map(function(c){return c[a]})},show:function(){return this.css("display",
"block")},hide:function(){return this.css("display","none")},prev:function(){return b(this.pluck("previousElementSibling"))},next:function(){return b(this.pluck("nextElementSibling"))},html:function(a){return a===l?this.dom.length>0?this.dom[0].innerHTML:null:this.each(function(c){c.innerHTML=a})},attr:function(a,c){return typeof a=="string"&&c===l?this.dom.length>0?this.dom[0].getAttribute(a)||undefined:null:this.each(function(j){if(typeof a=="object")for(f in a)j.setAttribute(f,a[f]);else j.setAttribute(a,
c)})},offset:function(){var a=this.dom[0].getBoundingClientRect();return{left:a.left+e.body.scrollLeft,top:a.top+e.body.scrollTop,width:a.width,height:a.height}},css:function(a,c){if(c===l&&typeof a=="string")return this.dom[0].style[camelize(a)];m="";for(f in a)m+=f+":"+a[f]+";";if(typeof a=="string")m=a+":"+c;return this.each(function(j){j.style.cssText+=";"+m})},index:function(a){return this.dom.indexOf(b(a).get(0))},bind:function(a,c){return this.each(function(j){a.split(/\s/).forEach(function(n){j.addEventListener(n,
c,false)})})},delegate:function(a,c,j){return this.each(function(n){n.addEventListener(c,function(o){for(var k=o.target,p=d.call(n.querySelectorAll(a));k&&p.indexOf(k)<0;)k=k.parentNode;k&&k!==n&&k!==e&&j(k,o)},false)})},live:function(a,c){b(e.body).delegate(this.selector,a,c);return this},hasClass:function(a){return RegExp("(^|\\s)"+a+"(\\s|$)").test(this.dom[0].className)},addClass:function(a){return this.each(function(c){!b(c).hasClass(a)&&(c.className+=(c.className?" ":"")+a)})},removeClass:function(a){return this.each(function(c){c.className=
c.className.replace(RegExp("(^|\\s)"+a+"(\\s|$)")," ").trim()})},trigger:function(a){return this.each(function(c){var j;c.dispatchEvent(j=e.createEvent("Events"),j.initEvent(a,true,false))})}};["width","height"].forEach(function(a){b.fn[a]=function(){return this.offset()[a]}});for(f in g)b.fn[f]=function(a){return function(c){return this.each(function(j){j["insertAdjacent"+(c instanceof Element?"Element":"HTML")](a,c)})}}(g[f]);i.prototype=b.fn;return b}();"$"in window||(window.$=Zepto);
(function(h){function i(b){var d={},e=b.match(/(Android)\s+([0-9\.]+)/),g=b.match(/(iPhone\sOS)\s([0-9_]+)/),f=b.match(/(iPad).*OS\s([0-9_]+)/);b=b.match(/(webOS)\/([0-9\.]+)/);if(e){d.android=true;d.version=e[2]}if(g){d.ios=true;d.version=g[2].replace(/_/g,".");d.iphone=true}if(f){d.ios=true;d.version=f[2].replace(/_/g,".");d.ipad=true}if(b){d.webos=true;d.version=b[2]}return d}h.os=i(navigator.userAgent);h.__detect=i})(Zepto);
(function(h){h.fn.anim=function(i,b,d){var e=[],g,f;for(f in i)f==="opacity"?g=i[f]:e.push(f+"("+i[f]+")");return this.css({"-webkit-transition":"all "+(b||0.5)+"s "+(d||""),"-webkit-transform":e.join(" "),opacity:g})}})(Zepto);
(function(h){var i=document,b={},d;i.ontouchstart=function(e){var g=Date.now(),f=g-(b.last||g);b.target="tagName"in e.touches[0].target?e.touches[0].target:e.touches[0].target.parentNode;d&&clearTimeout(d);b.x1=e.touches[0].pageX;if(f>0&&f<=250)b.isDoubleTap=true;b.last=g};i.ontouchmove=function(e){b.x2=e.touches[0].pageX};i.ontouchend=function(){if(b.isDoubleTap){h(b.target).trigger("doubleTap");b={}}else if(b.x2>0){Math.abs(b.x1-b.x2)>30&&h(b.target).trigger("swipe");b.x1=b.x2=b.last=0}else if("last"in
b)d=setTimeout(function(){d=null;h(b.target).trigger("tap");b={}},250)};i.ontouchcancel=function(){b={}};["swipe","doubleTap","tap"].forEach(function(e){h.fn[e]=function(g){return this.bind(e,g)}})})(Zepto);
(function(h){function i(b,d,e){var g=new XMLHttpRequest;g.onreadystatechange=function(){if(g.readyState==4&&(g.status==200||g.status==0))e(g.responseText)};g.open(b,d,true);g.setRequestHeader("X-Requested-With","XMLHttpRequest");g.send(null)}h.get=function(b,d){i("GET",b,d)};h.post=function(b,d){i("POST",b,d)};h.getJSON=function(b,d){h.get(b,function(e){d(JSON.parse(e))})};h.fn.load=function(b,d){var e=this,g=b.split(/\s/),f;if(!this.length)return this;if(g.length>1){b=g[0];f=g[1]}h.get(b,function(m){e.html(f?
h(document.createElement("div")).html(m).find(f).html():m);d&&d()});return this}})(Zepto);(function(h){var i=[],b;h.fn.remove=function(){return this.each(function(d){if(d.tagName=="IMG"){i.push(d);d.src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";b&&clearTimeout(b);b=setTimeout(function(){i=[]},6E4)}d.parentNode.removeChild(d)})}})(Zepto);