diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 00000000..1481f664
Binary files /dev/null and b/favicon.ico differ
diff --git a/scripts/blocks.js b/scripts/blocks.js
new file mode 100644
index 00000000..f49165b7
--- /dev/null
+++ b/scripts/blocks.js
@@ -0,0 +1,274 @@
+$.fn.extend({
+ long_name: function() {
+ var names;
+ names = [];
+ this.each(function() {
+ var e, parts;
+ e = this;
+ parts = [e.tagName.toLowerCase()];
+ e.id ? parts.push('#' + e.id) : null;
+ e.className ? parts.push('.' + e.className.split(/\s/).join('.')) : null;
+ return names.push(parts.join(''));
+ });
+ return '[' + names.join(', ') + ']';
+ },
+ info: function(){
+ return this.closest('.wrapper').long_name();
+ },
+ isSameNode: function(jqOrNode) {
+ var _a, other, self;
+ self = this.get(0);
+ other = (typeof (_a = jqOrNode.TEXT_NODE) !== "undefined" && _a !== null) ? jqOrNode.TEXT_NODE : (jqOrNode = jqOrNode.get(0));
+ return self.isSameNode(other);
+ },
+ matchesClass: function(jq, klass){
+ return !(this.hasClass(klass) && !jq.hasClass(klass));
+ },
+ relOffset: function(){
+ // this is horribly innefficient, I know
+ var canvas_pos = $('.scripts_workspace').offset();
+ var this_pos = this.offset();
+ return {left: this_pos.left - canvas_pos.left, top: this_pos.top - canvas_pos.top};
+ },
+ center: function() {
+ return this.each(function() {
+ var dx, dy, p, ph, pw, t;
+ t = $(this);
+ dx = t.outerWidth() / 2;
+ dy = t.outerHeight() / 2;
+ p = t.offsetParent();
+ pw = p.innerWidth() / 2;
+ ph = p.innerHeight() / 2;
+ return t.css({
+ position: 'absolute',
+ left: pw - dx,
+ top: ph - dy
+ });
+ });
+ }
+});
+
+function button(options){
+ return $('');
+}
+
+function block(options){
+ if (options.button){
+ return button(options);
+ }
+ opts = $.extend({
+ klass: 'control',
+ tab: true, // Something can come after
+ trigger: false, // This is the start of a handler
+ slot: true, // something can come before
+ containers: 0, // Something cannot be inside
+ label: 'Step', // label is its own mini-language
+ type: null
+ }, options);
+ if (opts.trigger){
+ opts.slot = false; // can't have both slot and trigger
+ }
+ if (opts['type']){
+ opts.tab = false; // values nest, but do not follow
+ opts.slot = false;
+ }
+ var wrapper = $('
');
+ var block = wrapper.children();
+ if (opts['type']){
+ block.addClass(opts['type'].name.toLowerCase());
+ wrapper.addClass('value').addClass(opts['type'].name.toLowerCase());
+ }
+ if (opts.trigger){
+ block.append('');
+ }else if(opts.slot){
+ block.append('');
+ }
+ for (var i = 0; i < opts.containers; i++){
+ block.append('
');
+ }
+ if (opts.tab){
+ wrapper.append('
');
+ }
+ return wrapper;
+}
+
+function label(value){
+ // Recognize special values in the label string and replace them with
+ // appropriate markup. Some values are dynamic and based on the objects currently
+ // in the environment
+ //
+ // values include:
+ //
+ // [flag] => the start flag image
+ // [key] => any valid key on the keyboard
+ // [sprite] => any currently defined sprite
+ // [number] => an empty number socket
+ // [number:default] => a number socket with a default value
+ // [boolean] => an empty boolean socket
+ // [boolean:default] => a boolean with a default value
+ // [string] => an empty string socket
+ // [string:default] => a string socket with a default value
+ // [message] => a message combo box
+ // [stop] => stop sign graphic /\[number:(-?\d*\.\d+)\]/
+ value = value.replace(/\[number:(-?\d*\.?\d+)\]/g, '');
+ value = value.replace(/\[number\]/g, '');
+ value = value.replace(/\[boolean:(true|false)\]/g, '');
+ value = value.replace(/\[boolean\]/g, '');
+ value = value.replace(/\[string:(.+)\]/g, '');
+ value = value.replace(/\[string\]/g, '');
+ return value;
+}
+
+
+$('.content_wrap .wrapper').live('click', function(event) {
+ var copy = $(this).clone();
+ $('.scripts_workspace').append(copy);
+ copy.center();
+ $('#menu_overlay .close').click();
+});
+
+$('.scripts_workspace .wrapper').live('dragstart', {handle: '.block'}, function(event, callback) {
+ var drag = $(this);
+ var pos;
+ if(drag.parent().is('.scripts_workspace')){
+ pos = drag.position();
+ }else{
+ pos = drag.relOffset();
+ }
+ if(drag.parent().is('.socket')){
+ drag.siblings('input').show();
+ }
+ if(!drag.parent().is('.scripts_workspace')){
+ console.log('unparenting drag node');
+ $('.scripts_workspace').append(this);
+ }
+ callback.offX = pos.left;
+ callback.offY = pos.top;
+ if(drag.parent().is('.socket')){
+ drag.siblings('input').show();
+ }
+ drag.css('z-index', 100);
+});
+
+
+$('.scripts_workspace .wrapper').live('drag', {handle: '.block'}, function(event, callback){
+ $(this).css({
+ left: callback.offX + callback.deltaX,
+ top: callback.offY + callback.deltaY
+ });
+});
+
+$('.scripts_workspace .wrapper').live('dragend', {handle: '.block'}, function(event, callback){
+ $(this).css('z-index', 0);
+});
+
+$('.scripts_workspace .tab').live('dropstart', function(event, callback){
+ // return false for any tab which is an inappropriate target
+ // console.log('tab dropstart for %s', $(this));
+ var tab = $(this);
+ var self = tab.closest('.wrapper');
+ var container = tab.parent(); // either .contained or .next
+ var drag = $(callback.drag);
+ if (container.find('.wrapper').length) {
+ // console.log('%s tab is filled');
+ return false;
+ }
+ if($.contains(this.parentNode, callback.drag)){
+ // console.log('drag target is already a child of parentNode');
+ return false;
+ }
+ if($.contains(callback.drag, this)){
+ // console.log('drag target is contained by this drag element');
+ return false;
+ }
+ if(self.isSameNode(drag)){
+ // console.log('drag target and dragged node are the same');
+ return false;
+ }
+ if(drag.hasClass('value')){
+ console.log('cannot attache a value block to a tab');
+ return false;
+ }
+ // console.log('it appears to be OK to add %o to %s', callback.drag, $(this).info());
+ $(this).css('border', '1px dashed red');
+ $(event.drag).find('.slot').css('background-color', 'green');
+ return true;
+});
+
+$('.scripts_workspace .tab').live('drop', function(event, callback) {
+ // console.log('tab drop');
+ try{
+ $(this).parent().append($(callback.drag).closest('.wrapper'));
+ $(callback.drag).closest('.wrapper').css({position: 'relative', top: 0, left: 0});
+ }catch(e){
+ console.log('What just happened? I was trying to append %o to %o', $(callback.drag).closest('.wrapper').get(), $(this).parent().get());
+ }
+ $(this).css('border', '');
+ $(event.drag).find('.slot').css('background-color', '');
+
+});
+
+$('.scripts_workspace .tab').live('dropend', function(event, callback) {
+ // console.log('tab dropend');
+ $(this).css('border', 0);
+ $(callback.drag).find('.slot').css('background-color', '');
+});
+
+$('.scripts_workspace .socket').live('dropstart', function(event, callback) {
+ // console.log('socket dropstart for %s', $(this));
+ // console.log('drop targets: %o', event);
+ var socket = $(this);
+ var self = socket.closest('.wrapper');
+ var drag = $(callback.drag);
+ if(socket.find('.wrapper').length){
+ // console.log('Cannot drop on a socket that is already filled');
+ return false;
+ }
+ if(!drag.is('.value')){
+ // console.log('Cannot drop a non-value block on a socket');
+ return false;
+ }
+ if($.contains(this, callback.drag)){
+ // console.log('drag target is already in this socket');
+ return false;
+ }
+ if($.contains(callback.drag, this)){
+ // console.log('socket is contained by this drag element');
+ return false;
+ }
+ if(self.isSameNode(drag)){
+ // console.log('Cannot drag to the same node');
+ return false;
+ }
+ if(!socket.matchesClass(drag, 'boolean')){
+ // console.log('Can only add a boolean block to a boolean socket');
+ return false;
+ }
+ if (!socket.matchesClass(drag, 'number')){
+ // console.log('Can only add a number block to a number socket');
+ return false;
+ }
+ if (!socket.matchesClass(drag, 'string')){
+ // console.log('Can only add a string block to a string socket');
+ return false;
+ }
+ // console.log('dropping %s on %s looks acceptable', $(this).info(), $(callback.drag).info());
+ socket.find('input').css('border', '1px dashed red');
+ socket.css('border', '1px dashed green');
+ return true;
+});
+
+$('.scripts_workspace .socket').live('drop', function(event, callback) {
+ // console.log('socket drop');
+ var socket = $(this);
+ var drag = $(callback.drag);
+ socket.find('input').css('border', '0px').hide();
+ socket.css('border', '');
+ socket.append(drag);
+ drag.css({position: 'relative', top: 0, left: 0, border: ''});
+});
+
+$('.scripts_workspace .socket').live('dropend', function(event, callback) {
+ // console.log('socket dropend');
+ // $(callback.drag).css('border', '1px solid white');
+});
diff --git a/scripts/dom.js b/scripts/dom.js
new file mode 100644
index 00000000..b4548ae0
--- /dev/null
+++ b/scripts/dom.js
@@ -0,0 +1,137 @@
+var DomElem, Elements, HtmlElem, LeafElem, Leaves, _a, _b, _c, _d, _e, _f, elem, isEmpty, isIdClassString, isObject, isString, name, parseArgs, root;
+var __hasProp = Object.prototype.hasOwnProperty, __slice = Array.prototype.slice, __extends = function(child, parent) {
+ var ctor = function(){ };
+ ctor.prototype = parent.prototype;
+ child.__superClass__ = parent.prototype;
+ child.prototype = new ctor();
+ child.prototype.constructor = child;
+ };
+isIdClassString = function(str) {
+ return str && isString(str) && (str[0] === '#' || str[0] === '.');
+};
+isObject = function(obj) {
+ var _a;
+ return !(isString(obj) || (typeof (_a = obj.parseIdClass) !== "undefined" && _a !== null));
+};
+isString = function(obj) {
+ return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
+};
+isEmpty = function(obj) {
+ var _a, key;
+ _a = obj;
+ for (key in _a) { if (__hasProp.call(_a, key)) {
+ if (hasOwnProperty.call(obj, key)) {
+ return false;;
+ }
+ }}
+ return true;
+};
+parseArgs = function() {
+ var args, attributes, idClassString;
+ var _a = arguments.length, _b = _a >= 1;
+ args = __slice.call(arguments, 0, _a - 0);
+ idClassString = args.length && isIdClassString(args[0]) ? args.shift() : null;
+ attributes = args.length && isObject(args[0]) ? args.shift() : {};
+ return [idClassString, attributes, args];
+};
+DomElem = function(name, idClassString, attributes, children) {
+ this.name = name;
+ this.children = children;
+ this.attributes = attributes;
+ this.parseIdClass(idClassString);
+ return this;
+};
+DomElem.prototype.parseIdClass = function(str) {
+ var names;
+ if (str) {
+ names = str.split('.');
+ names[0][0] === '#' ? (this.id = names.shift().slice(1)) : null;
+ names.length && names[0] === '' ? names.shift() : null;
+ this.classes = names;
+ return this.classes;
+ } else {
+ this.id = null;
+ this.classes = [];
+ return this.classes;
+ }
+};
+DomElem.prototype.attrsToString = function() {
+ var _a, atts, key, value;
+ atts = [];
+ if (this.id) {
+ atts.push("id=\"" + this.id + "\"");
+ }
+ if (!(isEmpty(this.classes))) {
+ atts.push(("class=\"" + (this.classes.join(' ')) + "\""));
+ }
+ _a = this.attributes;
+ for (key in _a) { if (__hasProp.call(_a, key)) {
+ value = _a[key];
+ atts.push("" + key + "=\"" + value + "\"");
+ }}
+ return atts.length ? ' ' + atts.join(' ') : '';
+};
+DomElem.prototype.childrenToString = function() {
+ var _a, _b, _c, _d, child, kids;
+ kids = (function() {
+ _a = []; _c = this.children;
+ for (_b = 0, _d = _c.length; _b < _d; _b++) {
+ child = _c[_b];
+ _a.push(isString(child) ? child : child.toString());
+ }
+ return _a;
+ }).call(this);
+ return kids.join('');
+};
+DomElem.prototype.toString = function() {
+ return "<" + this.name + (this.attrsToString()) + ">" + (this.childrenToString()) + "" + this.name + ">";
+};
+
+LeafElem = function() {
+ return DomElem.apply(this, arguments);
+};
+__extends(LeafElem, DomElem);
+LeafElem.prototype.toString = function() {
+ return "<" + this.name + (this.attrsToString()) + "/>";
+};
+
+HtmlElem = function() {
+ return DomElem.apply(this, arguments);
+};
+__extends(HtmlElem, DomElem);
+HtmlElem.prototype.toString = function() {
+ return "" + (HtmlElem.__superClass__.toString.call(this));
+};
+
+elem = function(name, isLeaf) {
+ return function() {
+ var _c, args, attributes, children, idClassString;
+ var _a = arguments.length, _b = _a >= 1;
+ args = __slice.call(arguments, 0, _a - 0);
+ _c = parseArgs.apply(this, args);
+ idClassString = _c[0];
+ attributes = _c[1];
+ children = _c[2];
+ if (name === 'html') {
+ return new HtmlElem(name, idClassString, attributes, children);
+ } else if (isLeaf) {
+ return new LeafElem(name, idClassString, attributes, children);
+ } else {
+ return new DomElem(name, idClassString, attributes, children);
+ }
+ };
+};
+Elements = ['a', 'abbr', 'address', 'article', 'aside', 'audio', 'b', 'bdo', 'blockquote', 'body', 'button', 'canvas', 'caption', 'cite', 'code', 'colgroup', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'i', 'iframe', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'map', 'mark', 'menu', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 'samp', 'script', 'section', 'select', 'small', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'ul', 'variable', 'video'];
+Leaves = ['area', 'col', 'base', 'br', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'wbr'];
+root = (typeof exports !== "undefined" && exports !== null) ? exports : this;
+root['html'] = elem('html');
+_b = Elements;
+for (_a = 0, _c = _b.length; _a < _c; _a++) {
+ name = _b[_a];
+ root[name] = elem(name, false);
+}
+_e = Leaves;
+for (_d = 0, _f = _e.length; _d < _f; _d++) {
+ name = _e[_d];
+ root[name] = elem(name, true);
+}
\ No newline at end of file
diff --git a/scripts/scratch.js b/scripts/scratch.js
new file mode 100644
index 00000000..2b4a727b
--- /dev/null
+++ b/scripts/scratch.js
@@ -0,0 +1,167 @@
+$(function() {
+ return $('a[rel]').overlay({
+ mask: 'darkred',
+ effect: 'apple',
+ onBeforeLoad: function() {
+ var klass = this.getTrigger().attr('class');
+ var menu = menus[klass];
+ return this.getOverlay()
+ .find('.content_wrap').empty()
+ .append(menus[this.getTrigger().attr('class')]);
+ }
+ });
+});
+
+function log(msg){
+ $('.workspace .stage').append('' + msg + '
');
+}
+
+$('.scripts_workspace')[0].ontouchmove = function(event){
+ rlog('cancelling a touch event');
+ event.preventDefault();
+ event.stopPropagation(true);
+};
+
+function menu(klass, specs){
+ var col = $('');
+ var half = Math.round((specs.length + 1) / 2);
+ var col1 = col.find('td').eq(0);
+ var col2 = col.find('td').eq(1);
+ specs.forEach(function(spec, idx){
+ spec.klass = klass;
+ if (idx < half){
+ col1.append(block(spec));
+ }else{
+ col2.append(block(spec));
+ }
+ });
+ return col;
+}
+
+var menus = {
+ motion: menu('motion', [
+ {label: 'move [number: 10] steps'},
+ {label: 'turn [clockwise] [number:15] degrees'},
+ {label: 'turn [counterclockwise] [number:15] degrees'},
+ {label: 'point in direction [degrees:90]'},
+ {label: 'point towards [sprite_or_mouse]'},
+ {label: 'go to x: [number:0] y: [number:0]'},
+ {label: 'go to [sprite_or_mouse]'},
+ {label: 'glide [number:1] secs to x: [number:0] y: [number:0]'},
+ {label: 'change x by [number:10]'},
+ {label: 'set x to [number:0]'},
+ {label: 'change y by [number:10]'},
+ {label: 'set y to [number:0]'},
+ {label: 'if on edge, bounce'},
+ {label: 'x position', 'type': Number},
+ {label: 'y position', 'type': Number},
+ {label: 'direction', 'type': Number}
+ ]),
+ looks: menu('looks', [
+ {label: 'switch to costume [costume]'},
+ {label: 'next costume'},
+ {label: 'costume #', 'type': Number},
+ {label: 'say [string:Hello!] for [number:2] secs'},
+ {label: 'say [string:Hello!]'},
+ {label: 'think [string:Hmm…] for [number:2] secs'},
+ {label: 'think [string:Hmm…]'},
+ {label: 'change [effect] effect by [number:25]'},
+ {label: 'set [effect] effect to [number:0]'},
+ {label: 'clear graphic effects'},
+ {label: 'change size by [number:10]'},
+ {label: 'set size to [number:100]%'},
+ {label: 'size', 'type': Number},
+ {label: 'show'},
+ {label: 'hide'},
+ {label: 'go to front'},
+ {label: 'go back [number:1] layers'}
+ ]),
+ sound: menu('sound', [
+ {label: 'play sound [sound_or_record]'},
+ {label: 'play sound [sound_or_record] until done'},
+ {label: 'stop all sounds'},
+ {label: 'play drum [drum:48] for [number:0.2] beats'},
+ {label: 'rest for [number:0.2] beats'},
+ {label: 'play note [note:60] for [number:0.5] beats'},
+ {label: 'set instrument to [instrument:1]'},
+ {label: 'change volume by [number:-10]'},
+ {label: 'set volume to [number:100]%'},
+ {label: 'volume', 'type': Number},
+ {label: 'change tempo by [number:20]'},
+ {label: 'set tempo to [number:60] bpm'},
+ {label: 'tempo', 'type': Number}
+ ]),
+ pen: menu('pen', [
+ {label: 'clear'},
+ {label: 'pen down'},
+ {label: 'pen up'},
+ {label: 'set pen color to [color_picker]'},
+ {label: 'change pen color by [number:10]'},
+ {label: 'set pen color to [number:0]'},
+ {label: 'change pen shade by [number:10]'},
+ {label: 'set pen shade to [number:50]'},
+ {label: 'change pen size by [number:1]'},
+ {label: 'set pen size to [number:1]'},
+ {label: 'stamp'}
+ ]),
+ control: menu('control', [
+ {label: 'when [flag] clicked', trigger: true},
+ {label: 'when [key] key pressed', trigger: true},
+ {label: 'when [sprite] clicked', trigger: true},
+ {label: 'wait [number:1] secs'},
+ {label: 'forever', containers: 1, tab: false},
+ {label: 'repeat [number:10]', containers: 1},
+ {label: 'broadcast [message]'},
+ {label: 'broadcast [message] and wait'},
+ {label: 'when I receive [message]', trigger: true},
+ {label: 'forever if [boolean]', containers: 1, tab: false},
+ {label: 'if [boolean]', containers: 1},
+ {label: 'if [boolean] else', containers: 2},
+ {label: 'wait until [boolean]'},
+ {label: 'repeat until [boolean]'},
+ {label: 'stop script'},
+ {label: 'stop all [stop]', tab: false}
+ ]),
+ sensing: menu('sensing', [
+ {label: 'touching [sprite_or_mouse_or_edge]?', 'type': Boolean},
+ {label: 'touching [color_eyedropper]?', 'type': Boolean},
+ {label: '[color_eyedropper] is touching [color_eyedropper]?', 'type': Boolean},
+ {label: "[ask [string:What's your name?] and wait"},
+ {label: 'answer', 'type': String},
+ {label: 'mouse x', 'type': Number},
+ {label: 'mouse y', 'type': Number},
+ {label: 'mouse down', 'type': Boolean},
+ {label: 'key [key] pressed?', 'type': Boolean},
+ {label: 'distance to [sprite_or_mouse]', 'type': Number},
+ {label: 'reset timer'},
+ {label: 'timer', 'type': Number},
+ {label: '[property] of [sprite_or_stage]', type: Number},
+ {label: 'loudness', type: Number},
+ {label: 'loud?', type: Boolean},
+ {label: '[sensor] sensor value', type: Number},
+ {label: 'sensor [button_or_connection]?', type: Boolean}
+ ]),
+ operators: menu('operators', [
+ {label: '[number] + [number]', type: Number},
+ {label: '[number] - [number]', type: Number},
+ {label: '[number] * [number]', type: Number},
+ {label: '[number] / [number]', type: Number},
+ {label: 'pick random [number:1] to [number:10]', type: Number},
+ {label: '[number] < [number]', type: Boolean},
+ {label: '[number] = [number]', type: Boolean},
+ {label: '[number] > [number]', type: Boolean},
+ {label: '[boolean] and [boolean]', type: Boolean},
+ {label: '[boolean] or [boolean]', type: Boolean},
+ {label: 'not [boolean]', type: Boolean},
+ {label: 'join [string:hello] [string:world]', type: String},
+ {label: 'letter [number:1] of [string:world]', type: String},
+ {label: 'length of [string:world]', type: Number},
+ {label: '[number] mod [number]', type: Number},
+ {label: 'round [number]', type: Number},
+ {label: '[function] of [number:10]', type: Number}
+ ]),
+ variables: menu('variables', [
+ {button: 'Make a variable'},
+ {button: 'Make a list'}
+ ])
+};
\ No newline at end of file