├── README ├── Images ├── Header.png ├── FooterTop.png ├── ExampleTop.png ├── BodyBackground.png ├── ExampleBottom.png ├── ExampleCenter.png ├── FilterEquations.png ├── FilterParamsKnob.png ├── FilterSchematic.png ├── FooterBackground.png └── FilterParamsKnobDrag.png ├── .gitignore ├── Fonts ├── Insolent │ ├── webfonts │ │ ├── eot │ │ │ ├── style_173184.eot │ │ │ └── style_173185.eot │ │ ├── ttf │ │ │ ├── style_173184.ttf │ │ │ └── style_173185.ttf │ │ ├── woff │ │ │ ├── style_173184.woff │ │ │ └── style_173185.woff │ │ └── svg │ │ │ └── index.html │ └── stylesheet.css └── BorisBlackBloxx │ ├── borisblackbloxx-webfont.eot │ ├── borisblackbloxx-webfont.ttf │ ├── borisblackbloxx-webfont.woff │ ├── specimen_files │ ├── borisblackbloxx-cleartype.png │ ├── easytabs.js │ ├── grid_12-825-55-15.css │ └── specimen_stylesheet.css │ ├── generator_config.txt │ ├── stylesheet.css │ └── BorisBlackBloxxRegular-demo.html ├── Examples ├── CookieExample.js ├── ParkExample.js └── FilterExample.js ├── TangleKit ├── TangleKit.css ├── BVTouchable.js ├── sprintf.js └── TangleKit.js ├── TangleTemplate.html ├── download.html ├── style.css ├── index.html ├── Tangle.js ├── guide.html └── reference.html /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Images/Header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/Header.png -------------------------------------------------------------------------------- /Images/FooterTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/FooterTop.png -------------------------------------------------------------------------------- /Images/ExampleTop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/ExampleTop.png -------------------------------------------------------------------------------- /Images/BodyBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/BodyBackground.png -------------------------------------------------------------------------------- /Images/ExampleBottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/ExampleBottom.png -------------------------------------------------------------------------------- /Images/ExampleCenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/ExampleCenter.png -------------------------------------------------------------------------------- /Images/FilterEquations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/FilterEquations.png -------------------------------------------------------------------------------- /Images/FilterParamsKnob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/FilterParamsKnob.png -------------------------------------------------------------------------------- /Images/FilterSchematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/FilterSchematic.png -------------------------------------------------------------------------------- /Images/FooterBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/FooterBackground.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.perspectivev3 3 | *.pbxuser 4 | .DS_Store 5 | xcuserdata 6 | *.xcuserdatad 7 | *.xcuserstate -------------------------------------------------------------------------------- /Images/FilterParamsKnobDrag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Images/FilterParamsKnobDrag.png -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/eot/style_173184.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/Insolent/webfonts/eot/style_173184.eot -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/eot/style_173185.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/Insolent/webfonts/eot/style_173185.eot -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/ttf/style_173184.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/Insolent/webfonts/ttf/style_173184.ttf -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/ttf/style_173185.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/Insolent/webfonts/ttf/style_173185.ttf -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/woff/style_173184.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/Insolent/webfonts/woff/style_173184.woff -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/woff/style_173185.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/Insolent/webfonts/woff/style_173185.woff -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/borisblackbloxx-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/BorisBlackBloxx/borisblackbloxx-webfont.eot -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/borisblackbloxx-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/BorisBlackBloxx/borisblackbloxx-webfont.ttf -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/borisblackbloxx-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/BorisBlackBloxx/borisblackbloxx-webfont.woff -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/specimen_files/borisblackbloxx-cleartype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worrydream/Tangle/HEAD/Fonts/BorisBlackBloxx/specimen_files/borisblackbloxx-cleartype.png -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/generator_config.txt: -------------------------------------------------------------------------------- 1 | # Font Squirrel Font-face Generator Configuration File 2 | # Upload this file to the generator to recreate the settings 3 | # you used to create these fonts. 4 | 5 | {"mode":"easy","formats":["ttf","woff","eot_lite","svg"],"options_hinting":"Y","options_vertical_metrics":"Y","add_space_glyphs":"Y","add_hyphen_glyphs":"Y","fallback":"none","options_subset":"basic","subset_custom":"","subset_custom_range":"","css_format":"fontspring","filename_suffix":"webfont","emsquare":"2048","spacing_adjustment":"0","ps_hinter":"default","gasp_setting":"xy"} -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/stylesheet.css: -------------------------------------------------------------------------------- 1 | /* Generated by Font Squirrel (http://www.fontsquirrel.com) on June 11, 2011 */ 2 | 3 | 4 | 5 | @font-face { 6 | font-family: 'BorisBlackBloxxRegular'; 7 | src: url('borisblackbloxx-webfont.eot'); 8 | src: url('borisblackbloxx-webfont.eot?#iefix') format('embedded-opentype'), 9 | url('borisblackbloxx-webfont.woff') format('woff'), 10 | url('borisblackbloxx-webfont.ttf') format('truetype'), 11 | url('borisblackbloxx-webfont.svg#BorisBlackBloxxRegular') format('svg'); 12 | font-weight: normal; 13 | font-style: normal; 14 | 15 | } 16 | 17 | -------------------------------------------------------------------------------- /Fonts/Insolent/stylesheet.css: -------------------------------------------------------------------------------- 1 | @font-face {font-family: 'Insolent-Italic';src: url('webfonts/eot/style_173184.eot');src: url('webfonts/eot/style_173184.eot?#iefix') format('embedded-opentype'),url('webfonts/woff/style_173184.woff') format('woff'),url('webfonts/ttf/style_173184.ttf') format('truetype'),url('webfonts/svg/style_173184.svg#Insolent-Italic') format('svg');} 2 | @font-face {font-family: 'Insolent';src: url('webfonts/eot/style_173185.eot');src: url('webfonts/eot/style_173185.eot?#iefix') format('embedded-opentype'),url('webfonts/woff/style_173185.woff') format('woff'),url('webfonts/ttf/style_173185.ttf') format('truetype'),url('webfonts/svg/style_173185.svg#Insolent') format('svg');} 3 | -------------------------------------------------------------------------------- /Examples/CookieExample.js: -------------------------------------------------------------------------------- 1 | // 2 | // CookieExample.js 3 | // Tangle 4 | // 5 | // Created by Bret Victor on 6/10/11. 6 | // (c) 2011 Bret Victor. MIT open-source license. 7 | // 8 | 9 | window.addEvent('domready', function () { 10 | 11 | var model = { 12 | initialize: function () { 13 | this.cookies = 3; 14 | this.caloriesPerCookie = 50; 15 | this.caloriesPerDay = 2100; 16 | }, 17 | update: function () { 18 | this.calories = this.cookies * this.caloriesPerCookie; 19 | this.dailyPercent = 100 * this.calories / this.caloriesPerDay; 20 | } 21 | }; 22 | 23 | for (var i = 1; ; i++) { 24 | var id = "cookieExample" + ((i > 1) ? i : ""); 25 | var element = document.getElementById(id); 26 | if (!element) { break; } 27 | new Tangle(element,model); 28 | } 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /TangleKit/TangleKit.css: -------------------------------------------------------------------------------- 1 | /* 2 | * TangleKit.css 3 | * Tangle 0.1.0 4 | * 5 | * Created by Bret Victor on 6/10/11. 6 | * (c) 2011 Bret Victor. MIT open-source license. 7 | * 8 | */ 9 | 10 | 11 | /* cursor */ 12 | 13 | .TKCursorDragHorizontal { 14 | cursor: pointer; 15 | cursor: move; 16 | cursor: col-resize; 17 | } 18 | 19 | 20 | /* TKToggle */ 21 | 22 | .TKToggle { 23 | color: #46f; 24 | border-bottom: 1px dashed #46f; 25 | cursor: pointer; 26 | } 27 | 28 | 29 | /* TKAdjustableNumber */ 30 | 31 | .TKAdjustableNumber { 32 | position:relative; 33 | color: #46f; 34 | border-bottom: 1px dashed #46f; 35 | } 36 | 37 | .TKAdjustableNumberHover { 38 | } 39 | 40 | .TKAdjustableNumberDown { 41 | color: #00c; 42 | border-bottom: 1px dashed #00c; 43 | } 44 | 45 | .TKAdjustableNumberHelp { 46 | position:absolute; 47 | color: #00f; 48 | font: 9px "Helvetica-Neue", "Arial", sans-serif; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/specimen_files/easytabs.js: -------------------------------------------------------------------------------- 1 | (function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:"fast",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId="#"+this.id;if(param.defaultContent==''){param.defaultContent=1;} 2 | if(typeof param.defaultContent=="number") 3 | {var defaultTab=$(thisId+" .tabs li:eq("+(param.defaultContent-1)+") a").attr('href').substr(1);}else{var defaultTab=param.defaultContent;} 4 | $(thisId+" .tabs li a").each(function(){var tabToHide=$(this).attr('href').substr(1);$("#"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+" .easytabs-tab-content").hide();} 5 | function changeContent(tabId){hideAll();$(thisId+" .tabs li").removeClass(param.activeClass);$(thisId+" .tabs li a[href=#"+tabId+"]").closest('li').addClass(param.activeClass);if(param.fadeSpeed!="none") 6 | {$(thisId+" #"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+" #"+tabId).show();}} 7 | $(thisId+" .tabs li").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery); -------------------------------------------------------------------------------- /TangleTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tangle document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 36 | 37 | 38 | 39 | 40 |

This is a simple reactive document.

41 | 42 |

43 | When you eat cookies, you 44 | will consume calories. 45 |

46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Examples/ParkExample.js: -------------------------------------------------------------------------------- 1 | // 2 | // ParkExample.js 3 | // Tangle 4 | // 5 | // Created by Bret Victor on 3/10/11. 6 | // (c) 2011 Bret Victor. MIT open-source license. 7 | // 8 | 9 | window.addEvent('domready', function () { 10 | 11 | new Tangle(document.getElementById("parkExample"), { 12 | 13 | initialize: function () { 14 | this.parkCount = 278; 15 | this.oldAdmission = 12; 16 | this.registeredVehicleCount = 28e6; // http://www.yesforstateparks.com/get-the-facts/fact-sheets/general-fact-sheet 17 | this.taxpayerCount = 13657632; // http://trac.syr.edu/tracirs/findings/aboutTP/states/California/counties/06000/06000main.html 18 | this.oldVisitorCount = 75e6; // http://parks.ca.gov/pages/712/files/budget%20fact%20sheet%20w-graphics%20-%2001-14-08.pdf 19 | this.oldBudget = 400e6; // this is not really correct, it ignores revenue, but I couldn't find any revenue data 20 | this.oldClosedParkCount = 150; 21 | 22 | this.percentOfAdmissionConvertedToRevenue = 0.1; // total BS, couldn't find real data, just trying to make the numbers work 23 | this.percentInStateVistors = 85; 24 | this.percentVehicleOwners = 95; 25 | 26 | this.tax = 18; 27 | this.percentCompliance = 100; 28 | this.isTaxPerVehicle = true; 29 | this.newAdmission = 0; 30 | this.newAdmissionAppliesToEveryone = false; 31 | }, 32 | 33 | update: function () { 34 | var taxCount = this.isTaxPerVehicle ? this.registeredVehicleCount : this.taxpayerCount; 35 | this.taxCollected = this.tax * this.percentCompliance/100 * taxCount; 36 | 37 | var fractionOfVisitorsEligibleForNewAdmission = this.newAdmissionAppliesToEveryone ? 1 : 38 | (this.percentInStateVistors/100 * (this.isTaxPerVehicle ? (this.percentVehicleOwners/100) : 1)); 39 | var averageAdmission = this.oldAdmission + fractionOfVisitorsEligibleForNewAdmission * (this.newAdmission - this.oldAdmission); 40 | 41 | // fake demand curve 42 | this.newVisitorCount = this.oldVisitorCount * Math.max(0.2, 1 + 0.5*Math.atan(1 - averageAdmission/this.oldAdmission)); 43 | 44 | var oldRevenue = this.oldVisitorCount * this.oldAdmission * this.percentOfAdmissionConvertedToRevenue; 45 | var newRevenue = this.newVisitorCount * averageAdmission * this.percentOfAdmissionConvertedToRevenue; 46 | 47 | this.deltaRevenue = newRevenue - oldRevenue; 48 | this.deltaBudget = this.taxCollected + this.deltaRevenue; 49 | this.deltaVisitorCount = this.newVisitorCount - this.oldVisitorCount; 50 | this.relativeVisitorCount = Math.abs(this.deltaVisitorCount / this.oldVisitorCount); 51 | 52 | this.budget = this.oldBudget + this.deltaBudget; 53 | 54 | var maintainanceBudget = 600e6; 55 | var repairBudget = 750e6; 56 | var maxBudget = 1000e6; 57 | 58 | if (this.budget < maintainanceBudget) { 59 | this.scenarioIndex = 0; 60 | this.closedParkCount = this.oldClosedParkCount * (maintainanceBudget - this.budget) / (maintainanceBudget - this.oldBudget); 61 | this.closedParkCount = Math.round(this.closedParkCount); 62 | } 63 | else if (this.budget < repairBudget) { 64 | this.scenarioIndex = 1; 65 | } 66 | else if (this.budget < maxBudget) { 67 | this.scenarioIndex = 2; 68 | this.restorationTime = Math.round(10 - 9 * (this.budget - repairBudget) / (maxBudget - repairBudget)); 69 | } 70 | else { 71 | this.scenarioIndex = 3; 72 | this.surplus = this.budget - maxBudget; 73 | } 74 | }, 75 | }); 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /download.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tangle: Download 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 | 43 |

Download Tangle

44 | 45 |

Tangle-0.1.0.zip — released June 14, 2011

46 | 47 |

Visit the project on GitHub.

48 | 49 |

Join the brand new Tangle Talk discussion group!

50 | 51 | 52 |

What's In The Box

53 | 54 |

Tangle.js is a lightweight library that provides a simple API for tangling up the values in your document. Tangle.js has no dependencies, and works with any JavaScript framework, or none at all.

55 | 56 |

TangleKit is an optional collection of UI components that let your readers adjust values and visualize the results. You can grab whichever components you want, use them, extend them, modify them, or just learn from them and make your own. TangleKit also includes (and depends on) a few helpful libraries, such as MooTools, sprintf, and BVTouchable.

57 | 58 |

Tangle Template is a simple sample document that you can start from, to get tangling right away.

59 | 60 |

Get Involved

61 | 62 |

This is an alpha release. That means that the API is subject to change, and such changes may break your code. TangleKit is currently a hodge-podge, and almost certainly will change.

63 | 64 |

But don't let that stop you. Make things with Tangle! Here's how you can help:

65 | 66 | 76 | 77 | 78 | 79 | 80 |
81 | 82 | 83 | 84 | 85 | 86 | 99 | 100 | 101 |
102 | 103 | -------------------------------------------------------------------------------- /Fonts/Insolent/webfonts/svg/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MyFonts Webfonts Demo for iOS devices 5 | 6 | 7 | 33 | 34 | 35 | 36 | 37 | 38 |

MyFonts Webfonts Demo for iOS devices

39 | 40 | 41 |

0. Insolent Italic 42 | (VersionID 431385, otf) 43 |

44 | 45 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

46 | 47 |

An, partus ancillae sitne in fructu habendus, disseretur inter principes civitatis, P. Scaevolam M'.que Manilium, ab iisque M. Brutus dissentiet -- quod et acutum genus est et ad usus civium non inutile, nosque ea scripta reliquaque eiusdem generis et legimus libenter et legemus --, haec, quae vitam omnem continent, neglegentur? nam, ut sint illa vendibiliora, haec uberiora certe sunt. quamquam id quidem licebit iis existimare, qui legerint. nos autem hanc omnem quaestionem de finibus bonorum et malorum fere a nobis explicatam esse his litteris arbitramur, in quibus, quantum potuimus, non modo quid nobis probaretur, sed etiam quid a singulis philosophiae disciplinis diceretur, persecuti sumus.

48 | 49 |

Id qui in una virtute ponunt et splendore nominis capti quid natura postulet non intellegunt, errore maximo, si Epicurum audire voluerint, liberabuntur: istae enim vestrae eximiae pulchraeque virtutes nisi voluptatem efficerent, quis eas aut laudabilis aut expetendas arbitraretur? ut enim medicorum scientiam non ipsius artis, sed bonae valetudinis causa probamus, et gubernatoris ars, quia bene navigandi rationem habet, utilitate, non arte laudatur, sic sapientia, quae ars vivendi putanda est, non expeteretur, si nihil efficeret; nunc expetitur, quod est tamquam artifex conquirendae et comparandae voluptatis.

50 | 51 | 52 | 53 |

1. Insolent 54 | (VersionID 431386, otf) 55 |

56 | 57 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

58 | 59 |

An, partus ancillae sitne in fructu habendus, disseretur inter principes civitatis, P. Scaevolam M'.que Manilium, ab iisque M. Brutus dissentiet -- quod et acutum genus est et ad usus civium non inutile, nosque ea scripta reliquaque eiusdem generis et legimus libenter et legemus --, haec, quae vitam omnem continent, neglegentur? nam, ut sint illa vendibiliora, haec uberiora certe sunt. quamquam id quidem licebit iis existimare, qui legerint. nos autem hanc omnem quaestionem de finibus bonorum et malorum fere a nobis explicatam esse his litteris arbitramur, in quibus, quantum potuimus, non modo quid nobis probaretur, sed etiam quid a singulis philosophiae disciplinis diceretur, persecuti sumus.

60 | 61 |

Id qui in una virtute ponunt et splendore nominis capti quid natura postulet non intellegunt, errore maximo, si Epicurum audire voluerint, liberabuntur: istae enim vestrae eximiae pulchraeque virtutes nisi voluptatem efficerent, quis eas aut laudabilis aut expetendas arbitraretur? ut enim medicorum scientiam non ipsius artis, sed bonae valetudinis causa probamus, et gubernatoris ars, quia bene navigandi rationem habet, utilitate, non arte laudatur, sic sapientia, quae ars vivendi putanda est, non expeteretur, si nihil efficeret; nunc expetitur, quod est tamquam artifex conquirendae et comparandae voluptatis.

62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/specimen_files/grid_12-825-55-15.css: -------------------------------------------------------------------------------- 1 | /*Notes about grid: 2 | Columns: 12 3 | Grid Width: 825px 4 | Column Width: 55px 5 | Gutter Width: 15px 6 | -------------------------------*/ 7 | 8 | 9 | 10 | .section {margin-bottom: 18px; 11 | } 12 | .section:after {content: ".";display: block;height: 0;clear: both;visibility: hidden;} 13 | .section {*zoom: 1;} 14 | 15 | .section .firstcolumn, 16 | .section .firstcol {margin-left: 0;} 17 | 18 | 19 | /* Border on left hand side of a column. */ 20 | .border { 21 | padding-left: 7px; 22 | margin-left: 7px; 23 | border-left: 1px solid #eee; 24 | } 25 | 26 | /* Border with more whitespace, spans one column. */ 27 | .colborder { 28 | padding-left: 42px; 29 | margin-left: 42px; 30 | border-left: 1px solid #eee; 31 | } 32 | 33 | 34 | 35 | /* The Grid Classes */ 36 | .grid1, .grid1_2cols, .grid1_3cols, .grid1_4cols, .grid2, .grid2_3cols, .grid2_4cols, .grid3, .grid3_2cols, .grid3_4cols, .grid4, .grid4_3cols, .grid5, .grid5_2cols, .grid5_3cols, .grid5_4cols, .grid6, .grid6_4cols, .grid7, .grid7_2cols, .grid7_3cols, .grid7_4cols, .grid8, .grid8_3cols, .grid9, .grid9_2cols, .grid9_4cols, .grid10, .grid10_3cols, .grid10_4cols, .grid11, .grid11_2cols, .grid11_3cols, .grid11_4cols, .grid12 37 | {margin-left: 15px;float: left;display: inline; overflow: hidden;} 38 | 39 | 40 | .width1, .grid1, .span-1 {width: 55px;} 41 | .width1_2cols,.grid1_2cols {width: 20px;} 42 | .width1_3cols,.grid1_3cols {width: 8px;} 43 | .width1_4cols,.grid1_4cols {width: 2px;} 44 | .input_width1 {width: 49px;} 45 | 46 | .width2, .grid2, .span-2 {width: 125px;} 47 | .width2_3cols,.grid2_3cols {width: 31px;} 48 | .width2_4cols,.grid2_4cols {width: 20px;} 49 | .input_width2 {width: 119px;} 50 | 51 | .width3, .grid3, .span-3 {width: 195px;} 52 | .width3_2cols,.grid3_2cols {width: 90px;} 53 | .width3_4cols,.grid3_4cols {width: 37px;} 54 | .input_width3 {width: 189px;} 55 | 56 | .width4, .grid4, .span-4 {width: 265px;} 57 | .width4_3cols,.grid4_3cols {width: 78px;} 58 | .input_width4 {width: 259px;} 59 | 60 | .width5, .grid5, .span-5 {width: 335px;} 61 | .width5_2cols,.grid5_2cols {width: 160px;} 62 | .width5_3cols,.grid5_3cols {width: 101px;} 63 | .width5_4cols,.grid5_4cols {width: 72px;} 64 | .input_width5 {width: 329px;} 65 | 66 | .width6, .grid6, .span-6 {width: 405px;} 67 | .width6_4cols,.grid6_4cols {width: 90px;} 68 | .input_width6 {width: 399px;} 69 | 70 | .width7, .grid7, .span-7 {width: 475px;} 71 | .width7_2cols,.grid7_2cols {width: 230px;} 72 | .width7_3cols,.grid7_3cols {width: 148px;} 73 | .width7_4cols,.grid7_4cols {width: 107px;} 74 | .input_width7 {width: 469px;} 75 | 76 | .width8, .grid8, .span-8 {width: 545px;} 77 | .width8_3cols,.grid8_3cols {width: 171px;} 78 | .input_width8 {width: 539px;} 79 | 80 | .width9, .grid9, .span-9 {width: 615px;} 81 | .width9_2cols,.grid9_2cols {width: 300px;} 82 | .width9_4cols,.grid9_4cols {width: 142px;} 83 | .input_width9 {width: 609px;} 84 | 85 | .width10, .grid10, .span-10 {width: 685px;} 86 | .width10_3cols,.grid10_3cols {width: 218px;} 87 | .width10_4cols,.grid10_4cols {width: 160px;} 88 | .input_width10 {width: 679px;} 89 | 90 | .width11, .grid11, .span-11 {width: 755px;} 91 | .width11_2cols,.grid11_2cols {width: 370px;} 92 | .width11_3cols,.grid11_3cols {width: 241px;} 93 | .width11_4cols,.grid11_4cols {width: 177px;} 94 | .input_width11 {width: 749px;} 95 | 96 | .width12, .grid12, .span-12 {width: 825px;} 97 | .input_width12 {width: 819px;} 98 | 99 | /* Subdivided grid spaces */ 100 | .emptycols_left1, .prepend-1 {padding-left: 70px;} 101 | .emptycols_right1, .append-1 {padding-right: 70px;} 102 | .emptycols_left2, .prepend-2 {padding-left: 140px;} 103 | .emptycols_right2, .append-2 {padding-right: 140px;} 104 | .emptycols_left3, .prepend-3 {padding-left: 210px;} 105 | .emptycols_right3, .append-3 {padding-right: 210px;} 106 | .emptycols_left4, .prepend-4 {padding-left: 280px;} 107 | .emptycols_right4, .append-4 {padding-right: 280px;} 108 | .emptycols_left5, .prepend-5 {padding-left: 350px;} 109 | .emptycols_right5, .append-5 {padding-right: 350px;} 110 | .emptycols_left6, .prepend-6 {padding-left: 420px;} 111 | .emptycols_right6, .append-6 {padding-right: 420px;} 112 | .emptycols_left7, .prepend-7 {padding-left: 490px;} 113 | .emptycols_right7, .append-7 {padding-right: 490px;} 114 | .emptycols_left8, .prepend-8 {padding-left: 560px;} 115 | .emptycols_right8, .append-8 {padding-right: 560px;} 116 | .emptycols_left9, .prepend-9 {padding-left: 630px;} 117 | .emptycols_right9, .append-9 {padding-right: 630px;} 118 | .emptycols_left10, .prepend-10 {padding-left: 700px;} 119 | .emptycols_right10, .append-10 {padding-right: 700px;} 120 | .emptycols_left11, .prepend-11 {padding-left: 770px;} 121 | .emptycols_right11, .append-11 {padding-right: 770px;} 122 | .pull-1 {margin-left: -70px;} 123 | .push-1 {margin-right: -70px;margin-left: 18px;float: right;} 124 | .pull-2 {margin-left: -140px;} 125 | .push-2 {margin-right: -140px;margin-left: 18px;float: right;} 126 | .pull-3 {margin-left: -210px;} 127 | .push-3 {margin-right: -210px;margin-left: 18px;float: right;} 128 | .pull-4 {margin-left: -280px;} 129 | .push-4 {margin-right: -280px;margin-left: 18px;float: right;} -------------------------------------------------------------------------------- /TangleKit/BVTouchable.js: -------------------------------------------------------------------------------- 1 | // 2 | // BVTouchable.js 3 | // ExplorableExplanations 4 | // 5 | // Created by Bret Victor on 3/10/11. 6 | // (c) 2011 Bret Victor. MIT open-source license. 7 | // 8 | 9 | (function () { 10 | 11 | var BVTouchable = this.BVTouchable = new Class ({ 12 | 13 | initialize: function (el, delegate) { 14 | this.element = el; 15 | this.delegate = delegate; 16 | this.setTouchable(true); 17 | }, 18 | 19 | 20 | //---------------------------------------------------------------------------------- 21 | // 22 | // touches 23 | // 24 | 25 | setTouchable: function (isTouchable) { 26 | if (this.touchable === isTouchable) { return; } 27 | this.touchable = isTouchable; 28 | this.element.style.pointerEvents = (this.touchable || this.hoverable) ? "auto" : "none"; 29 | 30 | if (isTouchable) { 31 | if (!this._mouseBound) { 32 | this._mouseBound = { 33 | mouseDown: this._mouseDown.bind(this), 34 | mouseMove: this._mouseMove.bind(this), 35 | mouseUp: this._mouseUp.bind(this), 36 | touchStart: this._touchStart.bind(this), 37 | touchMove: this._touchMove.bind(this), 38 | touchEnd: this._touchEnd.bind(this), 39 | touchCancel: this._touchCancel.bind(this) 40 | }; 41 | } 42 | this.element.addEvent("mousedown", this._mouseBound.mouseDown); 43 | this.element.addEvent("touchstart", this._mouseBound.touchStart); 44 | } 45 | else { 46 | this.element.removeEvents("mousedown"); 47 | this.element.removeEvents("touchstart"); 48 | } 49 | }, 50 | 51 | touchDidGoDown: function (touches) { this.delegate.touchDidGoDown(touches); }, 52 | touchDidMove: function (touches) { this.delegate.touchDidMove(touches); }, 53 | touchDidGoUp: function (touches) { this.delegate.touchDidGoUp(touches); }, 54 | 55 | _mouseDown: function (event) { 56 | event.stop(); 57 | this.element.getDocument().addEvents({ 58 | mousemove: this._mouseBound.mouseMove, 59 | mouseup: this._mouseBound.mouseUp 60 | }); 61 | 62 | this.touches = new BVTouches(event); 63 | this.touchDidGoDown(this.touches); 64 | }, 65 | 66 | _mouseMove: function (event) { 67 | event.stop(); 68 | this.touches._updateWithEvent(event); 69 | this.touchDidMove(this.touches); 70 | }, 71 | 72 | _mouseUp: function (event) { 73 | event.stop(); 74 | this.touches._goUpWithEvent(event); 75 | this.touchDidGoUp(this.touches); 76 | 77 | delete this.touches; 78 | this.element.getDocument().removeEvents({ 79 | mousemove: this._mouseBound.mouseMove, 80 | mouseup: this._mouseBound.mouseUp 81 | }); 82 | }, 83 | 84 | _touchStart: function (event) { 85 | event.stop(); 86 | if (this.touches || event.length > 1) { this._touchCancel(event); return; } // only-single touch for now 87 | 88 | this.element.getDocument().addEvents({ 89 | touchmove: this._mouseBound.touchMove, 90 | touchend: this._mouseBound.touchEnd, 91 | touchcancel: this._mouseBound.touchCancel 92 | }); 93 | 94 | this.touches = new BVTouches(event); 95 | this.touchDidGoDown(this.touches); 96 | }, 97 | 98 | _touchMove: function (event) { 99 | event.stop(); 100 | if (!this.touches) { return; } 101 | 102 | this.touches._updateWithEvent(event); 103 | this.touchDidMove(this.touches); 104 | }, 105 | 106 | _touchEnd: function (event) { 107 | event.stop(); 108 | if (!this.touches) { return; } 109 | 110 | this.touches._goUpWithEvent(event); 111 | this.touchDidGoUp(this.touches); 112 | 113 | delete this.touches; 114 | this.element.getDocument().removeEvents({ 115 | touchmove: this._mouseBound.touchMove, 116 | touchend: this._mouseBound.touchEnd, 117 | touchcancel: this._mouseBound.touchCancel 118 | }); 119 | }, 120 | 121 | _touchCancel: function (event) { 122 | this._touchEnd(event); 123 | } 124 | 125 | }); 126 | 127 | 128 | //==================================================================================== 129 | // 130 | // BVTouches 131 | // 132 | 133 | var BVTouches = this.BVTouches = new Class({ 134 | 135 | initialize: function (event) { 136 | this.globalPoint = { x:event.page.x, y:-event.page.y }; 137 | this.translation = { x:0, y:0 }; 138 | this.deltaTranslation = { x:0, y:0 }; 139 | this.velocity = { x:0, y:0 }; 140 | this.count = 1; 141 | this.event = event; 142 | this.timestamp = event.event.timeStamp; 143 | this.downTimestamp = this.timestamp; 144 | }, 145 | 146 | _updateWithEvent: function (event, isRemoving) { 147 | this.event = event; 148 | if (!isRemoving) { 149 | var dx = event.page.x - this.globalPoint.x; // todo, transform to local coordinate space? 150 | var dy = -event.page.y - this.globalPoint.y; 151 | this.translation.x += dx; 152 | this.translation.y += dy; 153 | this.deltaTranslation.x += dx; 154 | this.deltaTranslation.y += dy; 155 | this.globalPoint.x = event.page.x; 156 | this.globalPoint.y = -event.page.y; 157 | } 158 | 159 | var timestamp = event.event.timeStamp; 160 | var dt = timestamp - this.timestamp; 161 | var isSamePoint = isRemoving || (dx === 0 && dy === 0); 162 | var isStopped = (isSamePoint && dt > 150); 163 | 164 | this.velocity.x = isStopped ? 0 : (isSamePoint || dt === 0) ? this.velocity.x : (dx / dt * 1000); 165 | this.velocity.y = isStopped ? 0 : (isSamePoint || dt === 0) ? this.velocity.y : (dy / dt * 1000); 166 | this.timestamp = timestamp; 167 | }, 168 | 169 | _goUpWithEvent: function (event) { 170 | this._updateWithEvent(event, true); 171 | this.count = 0; 172 | 173 | var didMove = Math.abs(this.translation.x) > 10 || Math.abs(this.translation.y) > 10; 174 | var wasMoving = Math.abs(this.velocity.x) > 400 || Math.abs(this.velocity.y) > 400; 175 | this.wasTap = !didMove && !wasMoving && (this.getTimeSinceGoingDown() < 300); 176 | }, 177 | 178 | getTimeSinceGoingDown: function () { 179 | return this.timestamp - this.downTimestamp; 180 | }, 181 | 182 | resetDeltaTranslation: function () { 183 | this.deltaTranslation.x = 0; 184 | this.deltaTranslation.y = 0; 185 | } 186 | 187 | }); 188 | 189 | 190 | //==================================================================================== 191 | 192 | })(); 193 | -------------------------------------------------------------------------------- /TangleKit/sprintf.js: -------------------------------------------------------------------------------- 1 | /** 2 | sprintf() for JavaScript 0.7-beta1 3 | http://www.diveintojavascript.com/projects/javascript-sprintf 4 | 5 | Copyright (c) Alexandru Marasteanu 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * Neither the name of sprintf() for JavaScript nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | 31 | Changelog: 32 | 2010.09.06 - 0.7-beta1 33 | - features: vsprintf, support for named placeholders 34 | - enhancements: format cache, reduced global namespace pollution 35 | 36 | 2010.05.22 - 0.6: 37 | - reverted to 0.4 and fixed the bug regarding the sign of the number 0 38 | Note: 39 | Thanks to Raphael Pigulla (http://www.n3rd.org/) 40 | who warned me about a bug in 0.5, I discovered that the last update was 41 | a regress. I appologize for that. 42 | 43 | 2010.05.09 - 0.5: 44 | - bug fix: 0 is now preceeded with a + sign 45 | - bug fix: the sign was not at the right position on padded results (Kamal Abdali) 46 | - switched from GPL to BSD license 47 | 48 | 2007.10.21 - 0.4: 49 | - unit test and patch (David Baird) 50 | 51 | 2007.09.17 - 0.3: 52 | - bug fix: no longer throws exception on empty paramenters (Hans Pufal) 53 | 54 | 2007.09.11 - 0.2: 55 | - feature: added argument swapping 56 | 57 | 2007.04.03 - 0.1: 58 | - initial release 59 | **/ 60 | 61 | var sprintf = (function() { 62 | function get_type(variable) { 63 | return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase(); 64 | } 65 | function str_repeat(input, multiplier) { 66 | for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */} 67 | return output.join(''); 68 | } 69 | 70 | var str_format = function() { 71 | if (!str_format.cache.hasOwnProperty(arguments[0])) { 72 | str_format.cache[arguments[0]] = str_format.parse(arguments[0]); 73 | } 74 | return str_format.format.call(null, str_format.cache[arguments[0]], arguments); 75 | }; 76 | 77 | str_format.format = function(parse_tree, argv) { 78 | var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length; 79 | for (i = 0; i < tree_length; i++) { 80 | node_type = get_type(parse_tree[i]); 81 | if (node_type === 'string') { 82 | output.push(parse_tree[i]); 83 | } 84 | else if (node_type === 'array') { 85 | match = parse_tree[i]; // convenience purposes only 86 | if (match[2]) { // keyword argument 87 | arg = argv[cursor]; 88 | for (k = 0; k < match[2].length; k++) { 89 | if (!arg.hasOwnProperty(match[2][k])) { 90 | throw(sprintf('[sprintf] property "%s" does not exist', match[2][k])); 91 | } 92 | arg = arg[match[2][k]]; 93 | } 94 | } 95 | else if (match[1]) { // positional argument (explicit) 96 | arg = argv[match[1]]; 97 | } 98 | else { // positional argument (implicit) 99 | arg = argv[cursor++]; 100 | } 101 | 102 | if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) { 103 | throw(sprintf('[sprintf] expecting number but found %s', get_type(arg))); 104 | } 105 | switch (match[8]) { 106 | case 'b': arg = arg.toString(2); break; 107 | case 'c': arg = String.fromCharCode(arg); break; 108 | case 'd': arg = parseInt(arg, 10); break; 109 | case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break; 110 | case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break; 111 | case 'o': arg = arg.toString(8); break; 112 | case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break; 113 | case 'u': arg = Math.abs(arg); break; 114 | case 'x': arg = arg.toString(16); break; 115 | case 'X': arg = arg.toString(16).toUpperCase(); break; 116 | } 117 | arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg); 118 | pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' '; 119 | pad_length = match[6] - String(arg).length; 120 | pad = match[6] ? str_repeat(pad_character, pad_length) : ''; 121 | output.push(match[5] ? arg + pad : pad + arg); 122 | } 123 | } 124 | return output.join(''); 125 | }; 126 | 127 | str_format.cache = {}; 128 | 129 | str_format.parse = function(fmt) { 130 | var _fmt = fmt, match = [], parse_tree = [], arg_names = 0; 131 | while (_fmt) { 132 | if ((match = /^[^\x25]+/.exec(_fmt)) !== null) { 133 | parse_tree.push(match[0]); 134 | } 135 | else if ((match = /^\x25{2}/.exec(_fmt)) !== null) { 136 | parse_tree.push('%'); 137 | } 138 | else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) { 139 | if (match[2]) { 140 | arg_names |= 1; 141 | var field_list = [], replacement_field = match[2], field_match = []; 142 | if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { 143 | field_list.push(field_match[1]); 144 | while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') { 145 | if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) { 146 | field_list.push(field_match[1]); 147 | } 148 | else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) { 149 | field_list.push(field_match[1]); 150 | } 151 | else { 152 | throw('[sprintf] huh?'); 153 | } 154 | } 155 | } 156 | else { 157 | throw('[sprintf] huh?'); 158 | } 159 | match[2] = field_list; 160 | } 161 | else { 162 | arg_names |= 2; 163 | } 164 | if (arg_names === 3) { 165 | throw('[sprintf] mixing positional and named placeholders is not (yet) supported'); 166 | } 167 | parse_tree.push(match); 168 | } 169 | else { 170 | throw('[sprintf] huh?'); 171 | } 172 | _fmt = _fmt.substring(match[0].length); 173 | } 174 | return parse_tree; 175 | }; 176 | 177 | return str_format; 178 | })(); 179 | 180 | var vsprintf = function(fmt, argv) { 181 | argv.unshift(fmt); 182 | return sprintf.apply(null, argv); 183 | }; 184 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | * { margin: 0; padding: 0; } 2 | 3 | html { 4 | } 5 | 6 | body { 7 | background: #eee0c7; 8 | font: 14px "Lato", "Helvetica", "Arial", sans-serif; 9 | line-height:1.4; 10 | color: #553f19; 11 | width: 100%; 12 | margin-top:0; 13 | } 14 | 15 | .small { 16 | font-size:13px; 17 | } 18 | 19 | #everything { 20 | width: 818px; 21 | background-image: url(Images/BodyBackground.png); 22 | margin-top: 0; 23 | margin-left: auto; 24 | margin-right: auto; 25 | height:100%; 26 | } 27 | 28 | #header { 29 | background-image: url(Images/Header.png); 30 | margin-left:9px; 31 | margin-right:9px; 32 | width:800px; 33 | height:78px; 34 | } 35 | 36 | #header { 37 | position: relative; 38 | } 39 | 40 | #header h1 { 41 | font: 46px "BorisBlackBloxxRegular", "Helvetica", sans-serif; 42 | color: #fff; 43 | position:absolute; 44 | left: 35px; 45 | top: -8px; 46 | } 47 | 48 | #header h1 a { 49 | color: #fff; 50 | text-decoration: none; 51 | } 52 | 53 | #header h1 a:hover { 54 | color: #fad595; 55 | } 56 | 57 | #header h3 { 58 | font: 12px "Lato", "Helvetica", "Arial", sans-serif; 59 | color: #aa8d5a; 60 | position:absolute; 61 | left: 42px; 62 | top: 54px; 63 | } 64 | 65 | .menu { 66 | position: absolute; 67 | left: 200px; 68 | width: 556px; 69 | font: 12px "Insolent", "Helvetica", sans-serif; 70 | text-align:right; 71 | } 72 | 73 | #header .menu { 74 | top: 56px; 75 | } 76 | 77 | .menu a { 78 | text-transform: uppercase; 79 | text-decoration: none; 80 | color: #fff; 81 | } 82 | 83 | .menu a:hover { 84 | color: #fad595; 85 | text-decoration: underline; 86 | } 87 | 88 | #footer { 89 | background-image: url(Images/FooterBackground.png); 90 | margin-left:9px; 91 | margin-right:9px; 92 | width:800px; 93 | height:40px; 94 | position:relative; 95 | color:#fff; 96 | font: 11px "Insolent", "Helvetica", sans-serif; 97 | } 98 | 99 | #footerShadow { 100 | position:absolute; 101 | background-image: url(Images/FooterTop.png); 102 | width:800px; 103 | height:36px; 104 | } 105 | 106 | #footer .menu { 107 | top: 15px; 108 | } 109 | 110 | #footer .author { 111 | font: 12px "Lato", "Helvetica", "Arial", sans-serif; 112 | color: #aa8d5a; 113 | position:absolute; 114 | left:45px; 115 | top:13px; 116 | } 117 | 118 | #footer .author a { 119 | color: inherit; 120 | } 121 | 122 | #body { 123 | margin-left:51px; 124 | margin-right:51px; 125 | margin-top:20px; 126 | margin-bottom:25px; 127 | } 128 | 129 | .example { 130 | font: 16px "Times New Roman", "Times", serif; 131 | width:735px; 132 | margin-left:-10px; 133 | } 134 | 135 | .example p { 136 | margin:0; 137 | color:#444; 138 | } 139 | 140 | .example code { 141 | font-size:12px; 142 | color:#888; 143 | } 144 | 145 | .example code b { 146 | font-weight:bold; 147 | color:#222; 148 | } 149 | 150 | .exampleTop { 151 | background-image: url(Images/ExampleTop.png); 152 | width:735px; 153 | height:9px; 154 | } 155 | 156 | .exampleCenter { 157 | background-image: url(Images/ExampleCenter.png); 158 | width:655px; 159 | padding-top:10px; 160 | padding-left:40px; 161 | padding-right:40px; 162 | padding-bottom:8px; 163 | } 164 | 165 | .exampleBottom { 166 | background-image: url(Images/ExampleBottom.png); 167 | width:735px; 168 | height:25px; 169 | } 170 | 171 | p { 172 | margin-top:15px; 173 | margin-bottom:15px; 174 | } 175 | 176 | .small p { 177 | margin-top:10px; 178 | margin-bottom:10px; 179 | } 180 | 181 | a { 182 | color: #c25106; 183 | text-decoration: none; 184 | } 185 | 186 | a:hover { 187 | color: #a24106; 188 | text-decoration: underline; 189 | } 190 | 191 | ul { 192 | list-style-type: none; 193 | margin-top:5px; 194 | } 195 | 196 | ul.dotted { 197 | list-style-type: disc; 198 | margin-left:24px; 199 | } 200 | 201 | li { 202 | margin-top:2px; 203 | } 204 | 205 | ul.dotted li { 206 | margin-top:6px; 207 | } 208 | 209 | h2 { 210 | font: 26px "Insolent", "Helvetica", sans-serif; 211 | margin-top:32px; 212 | margin-bottom:-8px; 213 | } 214 | 215 | h4 { 216 | font: 18px "Insolent", "Helvetica", sans-serif; 217 | margin-top:26px; 218 | margin-bottom:-6px; 219 | } 220 | 221 | h6 { 222 | font: 15px "Insolent", "Helvetica", sans-serif; 223 | margin-top:26px; 224 | margin-bottom:-7px; 225 | } 226 | 227 | strong { 228 | font-weight: bold; 229 | color: #e00; 230 | } 231 | 232 | .small pre { 233 | background-color:rgba(255,255,255,0.18); 234 | padding-left:12px; 235 | margin-left:0px; 236 | margin-top:12px; 237 | margin-bottom:12px; 238 | font-size: 13px; 239 | } 240 | 241 | 242 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 243 | /* park example */ 244 | 245 | #parkExample { 246 | position:relative; 247 | font: 14px "Times New Roman", "Times", serif; 248 | line-height: 1.4; 249 | color: #111; 250 | margin-left:0px; 251 | width:604px; 252 | } 253 | 254 | #parkExample h5 { 255 | font: 16px "Times New Roman", "Times", serif; 256 | font-weight: bold; 257 | line-height: 1.4; 258 | color: #111; 259 | margin-top:10px; 260 | margin-bottom:10px; 261 | } 262 | 263 | #parkExample h6 { 264 | font: 14px "Times New Roman", "Times", serif; 265 | font-weight: normal; 266 | font-style: italic; 267 | line-height: 1.4; 268 | color: #111; 269 | margin-top:13px; 270 | margin-bottom:-5px; 271 | } 272 | 273 | #parkExample p { 274 | margin-top:12px; 275 | margin-bottom:12px; 276 | } 277 | 278 | #parkExample .TKAdjustableNumber { 279 | } 280 | 281 | #parkExample .TKAdjustableNumberHover { 282 | } 283 | 284 | #parkExample .TKToggle { 285 | } 286 | 287 | #parkExample .TKToggle:hover { 288 | } 289 | 290 | 291 | /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 292 | /* filter example */ 293 | 294 | #filterExample { 295 | position:relative; 296 | font: 13px "Times New Roman", "Times", serif; 297 | color: #111; 298 | margin-left:0px; 299 | margin-top:20px; 300 | margin-bottom:20px; 301 | width:598px; 302 | } 303 | 304 | #filterExample p { 305 | margin-top:15px; 306 | margin-bottom:15px; 307 | } 308 | 309 | #filterExample i { 310 | font: 13px "Times New Roman", "Times", serif; 311 | font-style: italic; 312 | } 313 | 314 | .filterSidebar { 315 | position:absolute; 316 | left:420px; 317 | width:170px; 318 | font-size:12px; 319 | color:#aaa; 320 | } 321 | 322 | .filterIndent { 323 | position:relative; 324 | margin-left: 40px; 325 | margin-bottom:25px; 326 | } 327 | 328 | .filterDynamicLabel { 329 | position:absolute; 330 | width:40px; 331 | height:14px; 332 | font: 11px "Times New Roman", "Times", serif; 333 | color: #00f; 334 | background-color:rgba(200,0,0,0.3); 335 | background-color:#fff; 336 | text-align:center; 337 | display:none; 338 | } 339 | 340 | .filterDynamicCoef { 341 | position:absolute; 342 | font: 13px "Times New Roman", "Times", serif; 343 | color: #00f; 344 | background-color:rgba(200,0,0,0.3); 345 | background-color:#fff; 346 | text-align:left; 347 | display:none; 348 | } 349 | 350 | .FilterStepPlot { 351 | position:absolute; 352 | display:none; 353 | } 354 | 355 | .FilterTimePlot { 356 | position:absolute; 357 | display:none; 358 | } 359 | 360 | .FilterPolePlot { 361 | position:absolute; 362 | display:none; 363 | } 364 | 365 | .filterFreqPlot { 366 | position:relative; 367 | width:256px; 368 | height:128px; 369 | border-left:1px solid #222; 370 | border-bottom:1px solid #222; 371 | } 372 | 373 | .filterUnstable { 374 | position:absolute; 375 | left:13px; 376 | top:40px; 377 | color:#f00; 378 | } 379 | 380 | .filterParams { 381 | position:absolute; 382 | color: #00f; 383 | padding-top: 2px; 384 | padding-bottom: 2px; 385 | padding-left: 6px; 386 | padding-right: 6px; 387 | pointer-events:none; 388 | } 389 | 390 | .FilterKnobHelp { 391 | position:absolute; 392 | display:none; 393 | left:0px; 394 | top:-13px; 395 | color: #b0b0b0; 396 | font: 11px "Times New Roman", "Times", serif; 397 | font-style: italic; 398 | pointer-events:none; 399 | } 400 | 401 | .cursorDrag { 402 | cursor: pointer; 403 | cursor: move; 404 | } 405 | 406 | -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/specimen_files/specimen_stylesheet.css: -------------------------------------------------------------------------------- 1 | @import url('grid_12-825-55-15.css'); 2 | 3 | /* 4 | CSS Reset by Eric Meyer - Released under Public Domain 5 | http://meyerweb.com/eric/tools/css/reset/ 6 | */ 7 | html, body, div, span, applet, object, iframe, 8 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 9 | a, abbr, acronym, address, big, cite, code, 10 | del, dfn, em, font, img, ins, kbd, q, s, samp, 11 | small, strike, strong, sub, sup, tt, var, 12 | b, u, i, center, dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, table, 14 | caption, tbody, tfoot, thead, tr, th, td 15 | {margin: 0;padding: 0;border: 0;outline: 0; 16 | font-size: 100%;vertical-align: baseline; 17 | background: transparent;} 18 | body {line-height: 1;} 19 | ol, ul {list-style: none;} 20 | blockquote, q {quotes: none;} 21 | blockquote:before, blockquote:after, 22 | q:before, q:after {content: ''; content: none;} 23 | :focus {outline: 0;} 24 | ins {text-decoration: none;} 25 | del {text-decoration: line-through;} 26 | table {border-collapse: collapse;border-spacing: 0;} 27 | 28 | 29 | 30 | 31 | body { 32 | color: #000; 33 | background-color: #dcdcdc; 34 | } 35 | 36 | a { 37 | text-decoration: none; 38 | color: #1883ba; 39 | } 40 | 41 | h1{ 42 | font-size: 32px; 43 | font-weight: normal; 44 | font-style: normal; 45 | margin-bottom: 18px; 46 | } 47 | 48 | h2{ 49 | font-size: 18px; 50 | } 51 | 52 | #container { 53 | width: 865px; 54 | margin: 0px auto; 55 | } 56 | 57 | 58 | #header { 59 | padding: 20px; 60 | font-size: 36px; 61 | background-color: #000; 62 | color: #fff; 63 | } 64 | 65 | #header span { 66 | color: #666; 67 | } 68 | #main_content { 69 | background-color: #fff; 70 | padding: 60px 20px 20px; 71 | } 72 | 73 | 74 | #footer p { 75 | margin: 0; 76 | padding-top: 10px; 77 | padding-bottom: 50px; 78 | color: #333; 79 | font: 10px Arial, sans-serif; 80 | } 81 | 82 | .tabs { 83 | width: 100%; 84 | height: 31px; 85 | background-color: #444; 86 | } 87 | .tabs li { 88 | float: left; 89 | margin: 0; 90 | overflow: hidden; 91 | background-color: #444; 92 | } 93 | .tabs li a { 94 | display: block; 95 | color: #fff; 96 | text-decoration: none; 97 | font: bold 11px/11px 'Arial'; 98 | text-transform: uppercase; 99 | padding: 10px 15px; 100 | border-right: 1px solid #fff; 101 | } 102 | 103 | .tabs li a:hover { 104 | background-color: #00b3ff; 105 | 106 | } 107 | 108 | .tabs li.active a { 109 | color: #000; 110 | background-color: #fff; 111 | } 112 | 113 | 114 | 115 | div.huge { 116 | 117 | font-size: 300px; 118 | line-height: 1em; 119 | padding: 0; 120 | letter-spacing: -.02em; 121 | overflow: hidden; 122 | } 123 | div.glyph_range { 124 | font-size: 72px; 125 | line-height: 1.1em; 126 | } 127 | 128 | .size10{ font-size: 10px; } 129 | .size11{ font-size: 11px; } 130 | .size12{ font-size: 12px; } 131 | .size13{ font-size: 13px; } 132 | .size14{ font-size: 14px; } 133 | .size16{ font-size: 16px; } 134 | .size18{ font-size: 18px; } 135 | .size20{ font-size: 20px; } 136 | .size24{ font-size: 24px; } 137 | .size30{ font-size: 30px; } 138 | .size36{ font-size: 36px; } 139 | .size48{ font-size: 48px; } 140 | .size60{ font-size: 60px; } 141 | .size72{ font-size: 72px; } 142 | .size90{ font-size: 90px; } 143 | 144 | 145 | .psample_row1 { height: 120px;} 146 | .psample_row1 { height: 120px;} 147 | .psample_row2 { height: 160px;} 148 | .psample_row3 { height: 160px;} 149 | .psample_row4 { height: 160px;} 150 | 151 | .psample { 152 | overflow: hidden; 153 | position: relative; 154 | } 155 | .psample p { 156 | line-height: 1.3em; 157 | display: block; 158 | overflow: hidden; 159 | margin: 0; 160 | } 161 | 162 | .psample span { 163 | margin-right: .5em; 164 | } 165 | 166 | .white_blend { 167 | width: 100%; 168 | height: 61px; 169 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==); 170 | position: absolute; 171 | bottom: 0; 172 | } 173 | .black_blend { 174 | width: 100%; 175 | height: 61px; 176 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC); 177 | position: absolute; 178 | bottom: 0; 179 | } 180 | .fullreverse { 181 | background: #000 !important; 182 | color: #fff !important; 183 | margin-left: -20px; 184 | padding-left: 20px; 185 | margin-right: -20px; 186 | padding-right: 20px; 187 | padding: 20px; 188 | margin-bottom:0; 189 | } 190 | 191 | 192 | .sample_table td { 193 | padding-top: 3px; 194 | padding-bottom:5px; 195 | padding-left: 5px; 196 | vertical-align: middle; 197 | line-height: 1.2em; 198 | } 199 | 200 | .sample_table td:first-child { 201 | background-color: #eee; 202 | text-align: right; 203 | padding-right: 5px; 204 | padding-left: 0; 205 | padding: 5px; 206 | font: 11px/12px "Courier New", Courier, mono; 207 | } 208 | 209 | code { 210 | white-space: pre; 211 | background-color: #eee; 212 | display: block; 213 | padding: 10px; 214 | margin-bottom: 18px; 215 | overflow: auto; 216 | } 217 | 218 | 219 | .bottom,.last {margin-bottom:0 !important; padding-bottom:0 !important;} 220 | 221 | .box { 222 | padding: 18px; 223 | margin-bottom: 18px; 224 | background: #eee; 225 | } 226 | 227 | .reverse,.reversed { background: #000 !important;color: #fff !important; border: none !important;} 228 | 229 | #bodycomparison { 230 | position: relative; 231 | overflow: hidden; 232 | font-size: 72px; 233 | height: 90px; 234 | white-space: nowrap; 235 | } 236 | 237 | #bodycomparison div{ 238 | font-size: 72px; 239 | line-height: 90px; 240 | display: inline; 241 | margin: 0 15px 0 0; 242 | padding: 0; 243 | } 244 | 245 | #bodycomparison div span{ 246 | font: 10px Arial; 247 | position: absolute; 248 | left: 0; 249 | } 250 | #xheight { 251 | float: none; 252 | position: absolute; 253 | color: #d9f3ff; 254 | font-size: 72px; 255 | line-height: 90px; 256 | } 257 | 258 | .fontbody { 259 | position: relative; 260 | } 261 | .arialbody{ 262 | font-family: Arial; 263 | position: relative; 264 | } 265 | .verdanabody{ 266 | font-family: Verdana; 267 | position: relative; 268 | } 269 | .georgiabody{ 270 | font-family: Georgia; 271 | position: relative; 272 | } 273 | 274 | /* @group Layout page 275 | */ 276 | 277 | #layout h1 { 278 | font-size: 36px; 279 | line-height: 42px; 280 | font-weight: normal; 281 | font-style: normal; 282 | } 283 | 284 | #layout h2 { 285 | font-size: 24px; 286 | line-height: 23px; 287 | font-weight: normal; 288 | font-style: normal; 289 | } 290 | 291 | #layout h3 { 292 | font-size: 22px; 293 | line-height: 1.4em; 294 | margin-top: 1em; 295 | font-weight: normal; 296 | font-style: normal; 297 | } 298 | 299 | 300 | #layout p.byline { 301 | font-size: 12px; 302 | margin-top: 18px; 303 | line-height: 12px; 304 | margin-bottom: 0; 305 | } 306 | #layout p { 307 | font-size: 14px; 308 | line-height: 21px; 309 | margin-bottom: .5em; 310 | } 311 | 312 | #layout p.large{ 313 | font-size: 18px; 314 | line-height: 26px; 315 | } 316 | 317 | #layout .sidebar p{ 318 | font-size: 12px; 319 | line-height: 1.4em; 320 | } 321 | 322 | #layout p.caption { 323 | font-size: 10px; 324 | margin-top: -16px; 325 | margin-bottom: 18px; 326 | } 327 | 328 | /* @end */ 329 | 330 | /* @group Glyphs */ 331 | 332 | #glyph_chart div{ 333 | background-color: #d9f3ff; 334 | color: black; 335 | float: left; 336 | font-size: 36px; 337 | height: 1.2em; 338 | line-height: 1.2em; 339 | margin-bottom: 1px; 340 | margin-right: 1px; 341 | text-align: center; 342 | width: 1.2em; 343 | position: relative; 344 | padding: .6em .2em .2em; 345 | } 346 | 347 | #glyph_chart div p { 348 | position: absolute; 349 | left: 0; 350 | top: 0; 351 | display: block; 352 | text-align: center; 353 | font: bold 9px Arial, sans-serif; 354 | background-color: #3a768f; 355 | width: 100%; 356 | color: #fff; 357 | padding: 2px 0; 358 | } 359 | 360 | 361 | #glyphs h1 { 362 | font-family: Arial, sans-serif; 363 | } 364 | /* @end */ 365 | 366 | /* @group Installing */ 367 | 368 | #installing { 369 | font: 13px Arial, sans-serif; 370 | } 371 | 372 | #installing p, 373 | #glyphs p{ 374 | line-height: 1.2em; 375 | margin-bottom: 18px; 376 | font: 13px Arial, sans-serif; 377 | } 378 | 379 | 380 | 381 | #installing h3{ 382 | font-size: 15px; 383 | margin-top: 18px; 384 | } 385 | 386 | /* @end */ 387 | 388 | #rendering h1 { 389 | font-family: Arial, sans-serif; 390 | } 391 | .render_table td { 392 | font: 11px "Courier New", Courier, mono; 393 | vertical-align: middle; 394 | } 395 | 396 | 397 | -------------------------------------------------------------------------------- /TangleKit/TangleKit.js: -------------------------------------------------------------------------------- 1 | // 2 | // TangleKit.js 3 | // Tangle 0.1.0 4 | // 5 | // Created by Bret Victor on 6/10/11. 6 | // (c) 2011 Bret Victor. MIT open-source license. 7 | // 8 | 9 | 10 | (function () { 11 | 12 | 13 | //---------------------------------------------------------- 14 | // 15 | // TKIf 16 | // 17 | // Shows the element if value is true (non-zero), hides if false. 18 | // 19 | // Attributes: data-invert (optional): show if false instead. 20 | 21 | Tangle.classes.TKIf = { 22 | 23 | initialize: function (element, options, tangle, variable) { 24 | this.isInverted = !!options.invert; 25 | }, 26 | 27 | update: function (element, value) { 28 | if (this.isInverted) { value = !value; } 29 | if (value) { element.style.removeProperty("display"); } 30 | else { element.style.display = "none" }; 31 | } 32 | }; 33 | 34 | 35 | //---------------------------------------------------------- 36 | // 37 | // TKSwitch 38 | // 39 | // Shows the element's nth child if value is n. 40 | // 41 | // False or true values will show the first or second child respectively. 42 | 43 | Tangle.classes.TKSwitch = { 44 | 45 | update: function (element, value) { 46 | element.getChildren().each( function (child, index) { 47 | if (index != value) { child.style.display = "none"; } 48 | else { child.style.removeProperty("display"); } 49 | }); 50 | } 51 | }; 52 | 53 | 54 | //---------------------------------------------------------- 55 | // 56 | // TKSwitchPositiveNegative 57 | // 58 | // Shows the element's first child if value is positive or zero. 59 | // Shows the element's second child if value is negative. 60 | 61 | Tangle.classes.TKSwitchPositiveNegative = { 62 | 63 | update: function (element, value) { 64 | Tangle.classes.TKSwitch.update(element, value < 0); 65 | } 66 | }; 67 | 68 | 69 | //---------------------------------------------------------- 70 | // 71 | // TKToggle 72 | // 73 | // Click to toggle value between 0 and 1. 74 | 75 | Tangle.classes.TKToggle = { 76 | 77 | initialize: function (element, options, tangle, variable) { 78 | element.addEvent("click", function (event) { 79 | var isActive = tangle.getValue(variable); 80 | tangle.setValue(variable, isActive ? 0 : 1); 81 | }); 82 | } 83 | }; 84 | 85 | 86 | //---------------------------------------------------------- 87 | // 88 | // TKNumberField 89 | // 90 | // An input box where a number can be typed in. 91 | // 92 | // Attributes: data-size (optional): width of the box in characters 93 | 94 | Tangle.classes.TKNumberField = { 95 | 96 | initialize: function (element, options, tangle, variable) { 97 | this.input = new Element("input", { 98 | type: "text", 99 | "class":"TKNumberFieldInput", 100 | size: options.size || 6 101 | }).inject(element, "top"); 102 | 103 | var inputChanged = (function () { 104 | var value = this.getValue(); 105 | tangle.setValue(variable, value); 106 | }).bind(this); 107 | 108 | this.input.addEvent("keyup", inputChanged); 109 | this.input.addEvent("blur", inputChanged); 110 | this.input.addEvent("change", inputChanged); 111 | }, 112 | 113 | getValue: function () { 114 | var value = parseFloat(this.input.get("value")); 115 | return isNaN(value) ? 0 : value; 116 | }, 117 | 118 | update: function (element, value) { 119 | var currentValue = this.getValue(); 120 | if (value !== currentValue) { this.input.set("value", "" + value); } 121 | } 122 | }; 123 | 124 | 125 | //---------------------------------------------------------- 126 | // 127 | // TKAdjustableNumber 128 | // 129 | // Drag a number to adjust. 130 | // 131 | // Attributes: data-min (optional): minimum value 132 | // data-max (optional): maximum value 133 | // data-step (optional): granularity of adjustment (can be fractional) 134 | 135 | var isAnyAdjustableNumberDragging = false; // hack for dragging one value over another one 136 | 137 | Tangle.classes.TKAdjustableNumber = { 138 | 139 | initialize: function (element, options, tangle, variable) { 140 | this.element = element; 141 | this.tangle = tangle; 142 | this.variable = variable; 143 | 144 | this.min = (options.min !== undefined) ? parseFloat(options.min) : 0; 145 | this.max = (options.max !== undefined) ? parseFloat(options.max) : 1e100; 146 | this.step = (options.step !== undefined) ? parseFloat(options.step) : 1; 147 | 148 | this.initializeHover(); 149 | this.initializeHelp(); 150 | this.initializeDrag(); 151 | }, 152 | 153 | 154 | // hover 155 | 156 | initializeHover: function () { 157 | this.isHovering = false; 158 | this.element.addEvent("mouseenter", (function () { this.isHovering = true; this.updateRolloverEffects(); }).bind(this)); 159 | this.element.addEvent("mouseleave", (function () { this.isHovering = false; this.updateRolloverEffects(); }).bind(this)); 160 | }, 161 | 162 | updateRolloverEffects: function () { 163 | this.updateStyle(); 164 | this.updateCursor(); 165 | this.updateHelp(); 166 | }, 167 | 168 | isActive: function () { 169 | return this.isDragging || (this.isHovering && !isAnyAdjustableNumberDragging); 170 | }, 171 | 172 | updateStyle: function () { 173 | if (this.isDragging) { this.element.addClass("TKAdjustableNumberDown"); } 174 | else { this.element.removeClass("TKAdjustableNumberDown"); } 175 | 176 | if (!this.isDragging && this.isActive()) { this.element.addClass("TKAdjustableNumberHover"); } 177 | else { this.element.removeClass("TKAdjustableNumberHover"); } 178 | }, 179 | 180 | updateCursor: function () { 181 | var body = document.getElement("body"); 182 | if (this.isActive()) { body.addClass("TKCursorDragHorizontal"); } 183 | else { body.removeClass("TKCursorDragHorizontal"); } 184 | }, 185 | 186 | 187 | // help 188 | 189 | initializeHelp: function () { 190 | this.helpElement = (new Element("div", { "class": "TKAdjustableNumberHelp" })).inject(this.element, "top"); 191 | this.helpElement.setStyle("display", "none"); 192 | this.helpElement.set("text", "drag"); 193 | }, 194 | 195 | updateHelp: function () { 196 | var size = this.element.getSize(); 197 | var top = -size.y + 7; 198 | var left = Math.round(0.5 * (size.x - 20)); 199 | var display = (this.isHovering && !isAnyAdjustableNumberDragging) ? "block" : "none"; 200 | this.helpElement.setStyles({ left:left, top:top, display:display }); 201 | }, 202 | 203 | 204 | // drag 205 | 206 | initializeDrag: function () { 207 | this.isDragging = false; 208 | new BVTouchable(this.element, this); 209 | }, 210 | 211 | touchDidGoDown: function (touches) { 212 | this.valueAtMouseDown = this.tangle.getValue(this.variable); 213 | this.isDragging = true; 214 | isAnyAdjustableNumberDragging = true; 215 | this.updateRolloverEffects(); 216 | this.updateStyle(); 217 | }, 218 | 219 | touchDidMove: function (touches) { 220 | var value = this.valueAtMouseDown + touches.translation.x / 5 * this.step; 221 | value = ((value / this.step).round() * this.step).limit(this.min, this.max); 222 | this.tangle.setValue(this.variable, value); 223 | this.updateHelp(); 224 | }, 225 | 226 | touchDidGoUp: function (touches) { 227 | this.isDragging = false; 228 | isAnyAdjustableNumberDragging = false; 229 | this.updateRolloverEffects(); 230 | this.updateStyle(); 231 | this.helpElement.setStyle("display", touches.wasTap ? "block" : "none"); 232 | } 233 | }; 234 | 235 | 236 | 237 | 238 | //---------------------------------------------------------- 239 | // 240 | // formats 241 | // 242 | // Most of these are left over from older versions of Tangle, 243 | // before parameters and printf were available. They should 244 | // be redesigned. 245 | // 246 | 247 | function formatValueWithPrecision (value,precision) { 248 | if (Math.abs(value) >= 100) { precision--; } 249 | if (Math.abs(value) >= 10) { precision--; } 250 | return "" + value.round(Math.max(precision,0)); 251 | } 252 | 253 | Tangle.formats.p3 = function (value) { 254 | return formatValueWithPrecision(value,3); 255 | }; 256 | 257 | Tangle.formats.neg_p3 = function (value) { 258 | return formatValueWithPrecision(-value,3); 259 | }; 260 | 261 | Tangle.formats.p2 = function (value) { 262 | return formatValueWithPrecision(value,2); 263 | }; 264 | 265 | Tangle.formats.e6 = function (value) { 266 | return "" + (value * 1e-6).round(); 267 | }; 268 | 269 | Tangle.formats.abs_e6 = function (value) { 270 | return "" + (Math.abs(value) * 1e-6).round(); 271 | }; 272 | 273 | Tangle.formats.freq = function (value) { 274 | if (value < 100) { return "" + value.round(1) + " Hz"; } 275 | if (value < 1000) { return "" + value.round(0) + " Hz"; } 276 | return "" + (value / 1000).round(2) + " KHz"; 277 | }; 278 | 279 | Tangle.formats.dollars = function (value) { 280 | return "$" + value.round(0); 281 | }; 282 | 283 | Tangle.formats.free = function (value) { 284 | return value ? ("$" + value.round(0)) : "free"; 285 | }; 286 | 287 | Tangle.formats.percent = function (value) { 288 | return "" + (100 * value).round(0) + "%"; 289 | }; 290 | 291 | 292 | 293 | //---------------------------------------------------------- 294 | 295 | })(); 296 | 297 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tangle: a JavaScript library for reactive documents 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 |

Tangle is a JavaScript library for creating reactive documents. Your readers can interactively explore 60 | possibilities, play with parameters, and see the document update immediately. Tangle is super-simple and easy to learn.

61 | 62 |

This is a simple reactive document.

63 | 64 |
65 |
66 |
67 |

When you eat cookies, you consume calories.

68 |
69 |
70 |
71 | 72 |

This is the HTML for that example.

73 | 74 |
75 |
76 |
77 | When you eat <span data-var="cookies" class="TKAdjustableNumber"> cookies</span>,
78 | you consume <span data-var="calories"> calories</span>.
79 |
80 |
81 |
82 | 83 |

And this is the JavaScript.

84 | 85 |
86 |
87 |
88 | var tangle = new Tangle(document, {
89 |     initialize: function () { this.cookies = 3; },
90 |     update:     function () { this.calories = this.cookies * 50; }
91 | });
92 |
93 |
94 |
95 | 96 |

Write your document with HTML and CSS, as you normally would. Use special HTML attributes to indicate variables. Write a little JavaScript to specify how your variables are calculated. Tangle ties it all together.

97 | 98 |

*   *   *

99 | 100 |

Try out some examples.

101 | 102 | 103 | 104 | 105 | 106 |
107 |
108 |
109 |
110 | 111 |
Proposition 21: Vehicle License Fee for State Parks
112 | 113 |
The way it is now:
114 | 115 |

California has state parks, including state beaches and historic parks. 116 | The current $ million budget is insufficient to maintain these parks, 117 | and parks will be shut down at least part-time. 118 | Most parks charge per vehicle for admission.

119 | 120 |
What Prop 21 would do:
121 | 122 |

Proposes to charge car owners an extra $18 on their annual registration bill, to go into the state park fund. Cars that pay the charge would have free park admission.

123 | 124 |
Analysis:
125 | 126 |

Suppose that an extra 127 | was charged to 128 | % of 129 | California taxpayersvehicle registrations. 130 | Park admission would be for 131 | those who paid the chargeeveryone. 132 |

133 | 134 |

This would collect an extralose 135 | $ million 136 | ($ million from the tax, 137 | plusminus 138 | $ million 139 | additionallost revenue from admission) 140 | for a total state park budget of $ million. 141 | 142 | 143 | This is not sufficient to maintain the parks, and 144 | parks would be shut down at least part-time. 145 | This is sufficient to maintain the parks in their current state, but not fund 146 | a program to bring safety and cleanliness up to acceptable standards. 147 | This is sufficient to maintain the parks in their current state, plus fund a program to 148 | bring safety and cleanliness up to acceptable standards over the next 149 | years. 150 | This is sufficient to maintain the parks and bring safety and cleanliness up to acceptable standards, 151 | leaving a $ million per year surplus. 152 | 153 |

154 | 155 |

Park attendance would 156 | risefall by 157 | , to 158 | million visits each year.

159 |
160 |
161 |
162 |
163 | 164 | 165 | 166 | 167 | 168 | 169 |
170 |
171 |
172 |
173 | 174 |

Below is a simplified digital adaptation of the analog state variable filter.

175 | 176 |

This topology is particularly useful for embedded audio processing, because Fc (cutoff frequency) and Q (resonance) are controlled by independent coefficients, kf and kq. (With most filters, the coefficients are functions of both parameters, which precludes pre-calculated lookup tables.)

177 | 178 |
179 | 180 | 181 | 182 |
183 |
184 |
185 | 186 | 187 |
188 | 189 |

The coefficients and transfer function are:

190 | 191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | 199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | 209 | 211 | 212 | 213 |
214 | 215 | 216 |

Some example frequency responses:

217 | 218 |
219 |
220 | 221 |
222 |
223 | Fc =
224 | Q = 225 |
226 |
Unstable
227 |
228 |
229 | 230 |
231 |
232 | Fc =
233 | Q = 234 |
235 |
Unstable
236 |
237 |
238 | 239 |
240 |
241 |
242 |
243 | 244 | 245 | 246 | 247 | 248 |

For a more extensive example, see Ten Brighter Ideas.

249 |

For the motivation and philosophy behind reactive documents, see Explorable Explanations.

250 |

Or learn how to get started with Tangle.

251 | 252 |
253 | 254 | 255 | 256 | 257 | 258 | 271 | 272 | 273 |
274 | 275 | -------------------------------------------------------------------------------- /Tangle.js: -------------------------------------------------------------------------------- 1 | // 2 | // Tangle.js 3 | // Tangle 0.1.0 4 | // 5 | // Created by Bret Victor on 5/2/10. 6 | // (c) 2011 Bret Victor. MIT open-source license. 7 | // 8 | // ------ model ------ 9 | // 10 | // var tangle = new Tangle(rootElement, model); 11 | // tangle.setModel(model); 12 | // 13 | // ------ variables ------ 14 | // 15 | // var value = tangle.getValue(variableName); 16 | // tangle.setValue(variableName, value); 17 | // tangle.setValues({ variableName:value, variableName:value }); 18 | // 19 | // ------ UI components ------ 20 | // 21 | // Tangle.classes.myClass = { 22 | // initialize: function (element, options, tangle, variable) { ... }, 23 | // update: function (element, value) { ... } 24 | // }; 25 | // Tangle.formats.myFormat = function (value) { return "..."; }; 26 | // 27 | 28 | var Tangle = this.Tangle = function (rootElement, modelClass) { 29 | 30 | var tangle = this; 31 | tangle.element = rootElement; 32 | tangle.setModel = setModel; 33 | tangle.getValue = getValue; 34 | tangle.setValue = setValue; 35 | tangle.setValues = setValues; 36 | 37 | var _model; 38 | var _nextSetterID = 0; 39 | var _setterInfosByVariableName = {}; // { varName: { setterID:7, setter:function (v) { } }, ... } 40 | var _varargConstructorsByArgCount = []; 41 | 42 | 43 | //---------------------------------------------------------- 44 | // 45 | // construct 46 | 47 | initializeElements(); 48 | setModel(modelClass); 49 | return tangle; 50 | 51 | 52 | //---------------------------------------------------------- 53 | // 54 | // elements 55 | 56 | function initializeElements() { 57 | var elements = rootElement.getElementsByTagName("*"); 58 | var interestingElements = []; 59 | 60 | // build a list of elements with class or data-var attributes 61 | 62 | for (var i = 0, length = elements.length; i < length; i++) { 63 | var element = elements[i]; 64 | if (element.getAttribute("class") || element.getAttribute("data-var")) { 65 | interestingElements.push(element); 66 | } 67 | } 68 | 69 | // initialize interesting elements in this list. (Can't traverse "elements" 70 | // directly, because elements is "live", and views that change the node tree 71 | // will change elements mid-traversal.) 72 | 73 | for (var i = 0, length = interestingElements.length; i < length; i++) { 74 | var element = interestingElements[i]; 75 | 76 | var varNames = null; 77 | var varAttribute = element.getAttribute("data-var"); 78 | if (varAttribute) { varNames = varAttribute.split(" "); } 79 | 80 | var views = null; 81 | var classAttribute = element.getAttribute("class"); 82 | if (classAttribute) { 83 | var classNames = classAttribute.split(" "); 84 | views = getViewsForElement(element, classNames, varNames); 85 | } 86 | 87 | if (!varNames) { continue; } 88 | 89 | var didAddSetter = false; 90 | if (views) { 91 | for (var j = 0; j < views.length; j++) { 92 | if (!views[j].update) { continue; } 93 | addViewSettersForElement(element, varNames, views[j]); 94 | didAddSetter = true; 95 | } 96 | } 97 | 98 | if (!didAddSetter) { 99 | var formatAttribute = element.getAttribute("data-format"); 100 | var formatter = getFormatterForFormat(formatAttribute, varNames); 101 | addFormatSettersForElement(element, varNames, formatter); 102 | } 103 | } 104 | } 105 | 106 | function getViewsForElement(element, classNames, varNames) { // initialize classes 107 | var views = null; 108 | 109 | for (var i = 0, length = classNames.length; i < length; i++) { 110 | var clas = Tangle.classes[classNames[i]]; 111 | if (!clas) { continue; } 112 | 113 | var options = getOptionsForElement(element); 114 | var args = [ element, options, tangle ]; 115 | if (varNames) { args = args.concat(varNames); } 116 | 117 | var view = constructClass(clas, args); 118 | 119 | if (!views) { views = []; } 120 | views.push(view); 121 | } 122 | 123 | return views; 124 | } 125 | 126 | function getOptionsForElement(element) { // might use dataset someday 127 | var options = {}; 128 | 129 | var attributes = element.attributes; 130 | var regexp = /^data-[\w\-]+$/; 131 | 132 | for (var i = 0, length = attributes.length; i < length; i++) { 133 | var attr = attributes[i]; 134 | var attrName = attr.name; 135 | if (!attrName || !regexp.test(attrName)) { continue; } 136 | 137 | options[attrName.substr(5)] = attr.value; 138 | } 139 | 140 | return options; 141 | } 142 | 143 | function constructClass(clas, args) { 144 | if (typeof clas !== "function") { // class is prototype object 145 | var View = function () { }; 146 | View.prototype = clas; 147 | var view = new View(); 148 | if (view.initialize) { view.initialize.apply(view,args); } 149 | return view; 150 | } 151 | else { // class is constructor function, which we need to "new" with varargs (but no built-in way to do so) 152 | var ctor = _varargConstructorsByArgCount[args.length]; 153 | if (!ctor) { 154 | var ctorArgs = []; 155 | for (var i = 0; i < args.length; i++) { ctorArgs.push("args[" + i + "]"); } 156 | var ctorString = "(function (clas,args) { return new clas(" + ctorArgs.join(",") + "); })"; 157 | ctor = eval(ctorString); // nasty 158 | _varargConstructorsByArgCount[args.length] = ctor; // but cached 159 | } 160 | return ctor(clas,args); 161 | } 162 | } 163 | 164 | 165 | //---------------------------------------------------------- 166 | // 167 | // formatters 168 | 169 | function getFormatterForFormat(formatAttribute, varNames) { 170 | if (!formatAttribute) { formatAttribute = "default"; } 171 | 172 | var formatter = getFormatterForCustomFormat(formatAttribute, varNames); 173 | if (!formatter) { formatter = getFormatterForSprintfFormat(formatAttribute, varNames); } 174 | if (!formatter) { log("Tangle: unknown format: " + formatAttribute); formatter = getFormatterForFormat(null,varNames); } 175 | 176 | return formatter; 177 | } 178 | 179 | function getFormatterForCustomFormat(formatAttribute, varNames) { 180 | var components = formatAttribute.split(" "); 181 | var formatName = components[0]; 182 | if (!formatName) { return null; } 183 | 184 | var format = Tangle.formats[formatName]; 185 | if (!format) { return null; } 186 | 187 | var formatter; 188 | var params = components.slice(1); 189 | 190 | if (varNames.length <= 1 && params.length === 0) { // one variable, no params 191 | formatter = format; 192 | } 193 | else if (varNames.length <= 1) { // one variable with params 194 | formatter = function (value) { 195 | var args = [ value ].concat(params); 196 | return format.apply(null, args); 197 | }; 198 | } 199 | else { // multiple variables 200 | formatter = function () { 201 | var values = getValuesForVariables(varNames); 202 | var args = values.concat(params); 203 | return format.apply(null, args); 204 | }; 205 | } 206 | return formatter; 207 | } 208 | 209 | function getFormatterForSprintfFormat(formatAttribute, varNames) { 210 | if (!sprintf || !formatAttribute.test(/\%/)) { return null; } 211 | 212 | var formatter; 213 | if (varNames.length <= 1) { // one variable 214 | formatter = function (value) { 215 | return sprintf(formatAttribute, value); 216 | }; 217 | } 218 | else { 219 | formatter = function (value) { // multiple variables 220 | var values = getValuesForVariables(varNames); 221 | var args = [ formatAttribute ].concat(values); 222 | return sprintf.apply(null, args); 223 | }; 224 | } 225 | return formatter; 226 | } 227 | 228 | 229 | //---------------------------------------------------------- 230 | // 231 | // setters 232 | 233 | function addViewSettersForElement(element, varNames, view) { // element has a class with an update method 234 | var setter; 235 | if (varNames.length <= 1) { 236 | setter = function (value) { view.update(element, value); }; 237 | } 238 | else { 239 | setter = function () { 240 | var values = getValuesForVariables(varNames); 241 | var args = [ element ].concat(values); 242 | view.update.apply(view,args); 243 | }; 244 | } 245 | 246 | addSetterForVariables(setter, varNames); 247 | } 248 | 249 | function addFormatSettersForElement(element, varNames, formatter) { // tangle is injecting a formatted value itself 250 | var span = null; 251 | var setter = function (value) { 252 | if (!span) { 253 | span = document.createElement("span"); 254 | element.insertBefore(span, element.firstChild); 255 | } 256 | span.innerHTML = formatter(value); 257 | }; 258 | 259 | addSetterForVariables(setter, varNames); 260 | } 261 | 262 | function addSetterForVariables(setter, varNames) { 263 | var setterInfo = { setterID:_nextSetterID, setter:setter }; 264 | _nextSetterID++; 265 | 266 | for (var i = 0; i < varNames.length; i++) { 267 | var varName = varNames[i]; 268 | if (!_setterInfosByVariableName[varName]) { _setterInfosByVariableName[varName] = []; } 269 | _setterInfosByVariableName[varName].push(setterInfo); 270 | } 271 | } 272 | 273 | function applySettersForVariables(varNames) { 274 | var appliedSetterIDs = {}; // remember setterIDs that we've applied, so we don't call setters twice 275 | 276 | for (var i = 0, ilength = varNames.length; i < ilength; i++) { 277 | var varName = varNames[i]; 278 | var setterInfos = _setterInfosByVariableName[varName]; 279 | if (!setterInfos) { continue; } 280 | 281 | var value = _model[varName]; 282 | 283 | for (var j = 0, jlength = setterInfos.length; j < jlength; j++) { 284 | var setterInfo = setterInfos[j]; 285 | if (setterInfo.setterID in appliedSetterIDs) { continue; } // if we've already applied this setter, move on 286 | appliedSetterIDs[setterInfo.setterID] = true; 287 | 288 | setterInfo.setter(value); 289 | } 290 | } 291 | } 292 | 293 | 294 | //---------------------------------------------------------- 295 | // 296 | // variables 297 | 298 | function getValue(varName) { 299 | var value = _model[varName]; 300 | if (value === undefined) { log("Tangle: unknown variable: " + varName); return 0; } 301 | return value; 302 | } 303 | 304 | function setValue(varName, value) { 305 | var obj = {}; 306 | obj[varName] = value; 307 | setValues(obj); 308 | } 309 | 310 | function setValues(obj) { 311 | var changedVarNames = []; 312 | 313 | for (var varName in obj) { 314 | var value = obj[varName]; 315 | var oldValue = _model[varName]; 316 | if (oldValue === undefined) { log("Tangle: setting unknown variable: " + varName); continue; } 317 | if (oldValue === value) { continue; } // don't update if new value is the same 318 | 319 | _model[varName] = value; 320 | changedVarNames.push(varName); 321 | } 322 | 323 | if (changedVarNames.length) { 324 | applySettersForVariables(changedVarNames); 325 | updateModel(); 326 | } 327 | } 328 | 329 | function getValuesForVariables(varNames) { 330 | var values = []; 331 | for (var i = 0, length = varNames.length; i < length; i++) { 332 | values.push(getValue(varNames[i])); 333 | } 334 | return values; 335 | } 336 | 337 | 338 | //---------------------------------------------------------- 339 | // 340 | // model 341 | 342 | function setModel(modelClass) { 343 | var ModelClass = function () { }; 344 | ModelClass.prototype = modelClass; 345 | _model = new ModelClass; 346 | 347 | updateModel(true); // initialize and update 348 | } 349 | 350 | function updateModel(shouldInitialize) { 351 | var ShadowModel = function () {}; // make a shadow object, so we can see exactly which properties changed 352 | ShadowModel.prototype = _model; 353 | var shadowModel = new ShadowModel; 354 | 355 | if (shouldInitialize) { shadowModel.initialize(); } 356 | shadowModel.update(); 357 | 358 | var changedVarNames = []; 359 | for (var varName in shadowModel) { 360 | if (!shadowModel.hasOwnProperty(varName)) { continue; } 361 | if (_model[varName] === shadowModel[varName]) { continue; } 362 | 363 | _model[varName] = shadowModel[varName]; 364 | changedVarNames.push(varName); 365 | } 366 | 367 | applySettersForVariables(changedVarNames); 368 | } 369 | 370 | 371 | //---------------------------------------------------------- 372 | // 373 | // debug 374 | 375 | function log (msg) { 376 | if (window.console) { window.console.log(msg); } 377 | } 378 | 379 | }; // end of Tangle 380 | 381 | 382 | //---------------------------------------------------------- 383 | // 384 | // components 385 | 386 | Tangle.classes = {}; 387 | Tangle.formats = {}; 388 | 389 | Tangle.formats["default"] = function (value) { return "" + value; }; 390 | 391 | -------------------------------------------------------------------------------- /guide.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tangle: Getting Started 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 | 35 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 | 56 |

Getting Started

57 | 58 |

Tangle lets you write documents that change.

59 | 60 |

Let's say you're writing an article about dieting, and want to discuss the calorie content of various foods. You might write the following.

61 | 62 |
63 |
64 |
65 |

When you eat 3 cookies, you consume 150 calories. That's 7% of your recommended daily calories.

66 |
67 |
68 |
69 | 70 |

That's a true statement, but it only applies directly to those few readers who eat 3 cookies a day. Most of your readers eat more or less. What if the reader could adjust the statement to see information about their own situation? What if readers could explore alternative scenarios as well?

71 | 72 |
73 |
74 |
75 |

When you eat cookies, you consume calories. That's % of your recommended daily calories.

76 |
77 |
78 |
79 | 80 |

Let's see how to write this example.

81 | 82 | 83 |

Variables

84 | 85 |

We start by identifying the variables — those parts of the statement that need to change. In this case, there are three varying numbers in the statement, so we need three variables.

86 | 87 |
88 |
89 |
90 |

When you eat 3 cookies, you consume 150 calories. That's 7% of your recommended daily calories.

91 |
92 |
93 |
94 | 95 |

We'll call these variables cookies, calories, and dailyPercent respectively.

96 | 97 | 98 |

HTML

99 | 100 |

Now, let's look at the HTML for the statement.

101 | 102 |
103 | <p>When you eat 3 cookies, you consume <b>150 calories</b>.
104 |    That's 7% of your recommended daily calories.</p>
105 | 
106 | 107 | 108 |
data-var
109 | 110 |

Let's start with the "150 calories" part. Notice that it's inside <b> tags, to make it bold. We'll make two simple changes:

111 | 112 |
113 | <p>When you eat 3 cookies, you consume <b data-var="calories"> calories</b>.
114 |    That's 7% of your recommended daily calories.</p>
115 | 
116 | 117 |

First, we added the data-var="calories" attribute to the <b> tag. This tells Tangle to insert the value of the "calories" variable inside the tag. So, if the calories variable were 500, it would appear as "500 calories".

118 | 119 |

Second, we removed the "150" from inside the tag, because Tangle is going to insert the correct number itself.

120 | 121 | 122 |
span
123 | 124 |

Now, let's look at the "7%" part. It's not inside a tag, so we need to put it inside one. The <span> tag is typically used for this purpose. Unlike <b>, it has no intrinsic meaning of its own — it's simply a generic container that we can attach style and variable information to.

125 | 126 |
127 | <p>When you eat 3 cookies, you consume <b data-var="calories"> calories</b>.
128 |    That's <span data-var="dailyPercent">%</span> of your recommended daily calories.</p>
129 | 
130 | 131 |

Tangle will insert the value of the "dailyPercent" variable at the start of the span.

132 | 133 | 134 |
class
135 | 136 |

Now, we'll look at the "3 cookies" part. This part isn't just dynamic — it's interactive. We want the reader to be able to adjust the variable by dragging on the number. For this, we use a class. A Tangle class can describe how the reader interacts with an HTML element, or how a variable should be displayed.

137 | 138 |

We'll use the class "TKAdjustableNumber", which comes with TangleKit. (The "TK" prefix refers to "TangleKit".) TangleKit is a collection of basic classes for you to use. Once you start tangling more heavily, you might want to start making your own classes.

139 | 140 |
141 | <p>When you eat <span class="TKAdjustableNumber" data-var="cookies"> cookies</span>,
142 |    you consume <b data-var="calories"> calories</b>. That's 
143 |    <span data-var="dailyPercent">%</span> of your recommended daily calories.</p>
144 | 
145 | 146 | 147 |
id
148 | 149 |

Finally, we need to make sure that we're able to find this statement from JavaScript. The easiest way to do so is to add an id attribute to the tag that contains it. In this case, our statement is inside a <p> paragraph tag, and we can name it like so:

150 | 151 |
152 | <p id="calorieCalculator">
153 |   When you eat <span class="TKAdjustableNumber" data-var="cookies"> cookies</span>,
154 |   you consume <b data-var="calories"> calories</b>. That's 
155 |   <span data-var="dailyPercent">%</span> of your recommended daily calories.</p>
156 | 
157 | 158 |

That's all we need to do with the HTML. Next, we'll tell Tangle how to calculate those variables.

159 | 160 |

JavaScript

161 | 162 |

In JavaScript, we need to "create a tangle" for all of the HTML elements and variables involved in this particular statement. We do so with new Tangle, like so:

163 | 164 |
165 | var tangle = new Tangle(rootElement, model);
166 | 
167 | 168 |

But first, we need the rootElement and the model.

169 | 170 | 171 |
rootElement
172 | 173 |

The rootElement refers to the element that contains our statement. In this case, we put an id attribute on the surrounding paragraph, so we can find it easily:

174 | 175 |
176 | var rootElement = document.getElementById("calorieCalculator");
177 | 
178 | 179 | 180 |
model
181 | 182 |

The model tells Tangle how to initialize and update the variables. It has this form:

183 | 184 |
185 | var model = {
186 |     initialize: function () { ... },
187 |     update: function () { ... }
188 | };
189 | 
190 | 191 |

The initialize method runs when the tangle is first created. This is where you set the initial values of independent variables. We want the statement initially to read, "When you eat 3 cookies...", so we need the cookies variable to start at 3.

192 | 193 |
194 | var model = {
195 |     initialize: function () {
196 |         this.cookies = 3;
197 |     },
198 |     update: function () { ... }
199 | };
200 | 
201 | 202 |

The update method runs whenever a variable changes. This is where you describe how to calculate "derived variables" — variables that depend on others. The calories variable can be calculated from cookies: 203 | 204 |

205 | var model = {
206 |     initialize: function () {
207 |         this.cookies = 3;
208 |     },
209 |     update: function () {
210 |         this.calories = this.cookies * 50;
211 |     }
212 | };
213 | 
214 | 215 |

and the dailyPercent variable can be calculated from calories.

216 | 217 |
218 | var model = {
219 |     initialize: function () {
220 |         this.cookies = 3;
221 |     },
222 |     update: function () {
223 |         this.calories = this.cookies * 50;
224 |         this.dailyPercent = 100 * this.calories / 2100;
225 |     }
226 | };
227 | 
228 | 229 |

This works fine, but it's a little hard to read. Someone perusing this code might not immediately understand what the "50" and "2100" are all about. Let's give them more descriptive names.

230 | 231 |
232 | var model = {
233 |     initialize: function () {
234 |         this.cookies = 3;
235 |         this.caloriesPerCookie = 50;
236 |         this.dailyCalories = 2100;
237 |     },
238 |     update: function () {
239 |         this.calories = this.cookies * this.caloriesPerCookie;
240 |         this.dailyPercent = 100 * this.calories / this.dailyCalories;
241 |     }
242 | };
243 | 
244 | 245 |

The code is now much easier to follow. And later, if we want caloriesPerCookie or dailyCalories to be adjustable, we can do so directly in the HTML, without touching the JavaScript.

246 | 247 | 248 |
new Tangle
249 | 250 |

We can now create the tangle. Instead of writing out the rootElement and model separately, we typically just define them inline, like so:

251 | 252 |
253 | var tangle = new Tangle (document.getElementById("calorieCalculator"), {
254 |     initialize: function () {
255 |         this.cookies = 3;
256 |         this.caloriesPerCookie = 50;
257 |         this.dailyCalories = 2100;
258 |     },
259 |     update: function () {
260 |         this.calories = this.cookies * this.caloriesPerCookie;
261 |         this.dailyPercent = 100 * this.calories / this.dailyCalories;
262 |     }
263 | });
264 | 
265 | 266 |

That's all there is to the JavaScript.

267 | 268 | 269 |

Formats and Classes

270 | 271 |

We're almost done. But when we try it out, we get this:

272 | 273 |
274 |
275 |
276 |

When you eat cookies, you consume calories. That's % of your recommended daily calories.

277 |
278 |
279 |
280 | 281 |

Our dailyPercent is perhaps a little more precise than necessary. To tell Tangle how to properly display that variable, we can specify a format.

282 | 283 | 284 |
Formats
285 | 286 |

In the HTML, we add a data-format attribute:

287 | 288 |
289 | That's <span data-var="dailyPercent" data-format="%.0f">%</span>
290 | of your recommended daily calories.
291 | 
292 | 293 |

%.0f is known as a printf-style format, which is a standard language for describing how numbers should be printed. Here, we specified that we wanted to round off the number, with no digits after the decimal point.

294 | 295 |

It's possible to create your own format functions and use those as well, but in this case, the printf format works fine.

296 | 297 |
298 |
299 |
300 |

When you eat cookies, you consume calories. That's % of your recommended daily calories.

301 |
302 |
303 |
304 | 305 | 306 |
Classes
307 | 308 |

Currently, we can adjust the cookies variable by dragging it, thanks to the TKAdjustableNumber class that we specified:

309 | 310 |
311 | <span data-var="cookies" class="TKAdjustableNumber">
312 | 
313 | 314 |

Perhaps you don't like dragging, and would rather let your readers type in a number instead. We can simply switch the class to TKNumberField:

315 | 316 |
317 | <span data-var="cookies" class="TKNumberField">
318 | 
319 | 320 |

And our statement becomes:

321 | 322 |
323 |
324 |
325 |

When you eat cookies, you consume calories. That's % of your recommended daily calories.

326 |
327 |
328 |
329 | 330 |

TangleKit contains a growing selection of classes, and you can define your own classes easily.

331 | 332 | 333 |

Possibilities

334 | 335 |

We just worked through a very simple example, to give you a taste of what you can tangle. But there's much more you can do.

336 | 337 |
    338 |
  • Create rich data visualizations, such as charts and graphs, using Tangle classes.
  • 339 |
  • Create dynamic data displays, such as stock tickers and weather reports, using Tangle to maintain a clean separation between back-end data and front-end views.
  • 340 |
  • Pass around strings, arrays, and other objects in variables, for more data-heavy applications.
  • 341 |
  • Create controls and views that use multiple variables at once.
  • 342 |
343 | 344 |

You might want to look over the API reference, or just download Tangle and dive right in.

345 | 346 | 347 |
348 | 349 | 350 | 351 | 352 | 353 | 366 | 367 | 368 |
369 | 370 | -------------------------------------------------------------------------------- /Examples/FilterExample.js: -------------------------------------------------------------------------------- 1 | // 2 | // FilterExample.js 3 | // Tangle 4 | // 5 | // Created by Bret Victor on 3/10/11. 6 | // (c) 2011 Bret Victor. MIT open-source license. 7 | // 8 | 9 | 10 | (function () { 11 | 12 | 13 | window.addEvent('domready', function () { 14 | 15 | var container = document.getElementById("filterExample"); 16 | var tangle = new Tangle(container, { 17 | 18 | initialize: function () { 19 | this.fs = 44100; this.index = 1; 20 | this.fc1 = 2000; this.q1 = 0.8; 21 | this.fc2 = 1200; this.q2 = 3.5; 22 | }, 23 | 24 | update: function () { 25 | var i = this.index; 26 | var fc = this['fc' + i]; 27 | var q = this['q' + i]; 28 | 29 | // filter coefficients 30 | 31 | this.kf = 2 * Math.sin(Math.PI * fc / this.fs); 32 | this.kq = 1 / q; 33 | 34 | // transfer function coefficients 35 | 36 | this.b0 = this.kf * this.kf; 37 | this.a1 = -2 + this.kf * (this.kf + this.kq); 38 | this.a1neg = -this.a1; 39 | this.a2 = 1 - (this.kf * this.kq); 40 | 41 | // solve for poles in terms of z^-1 42 | 43 | var a1 = this.a1; 44 | var a2 = this.a2; 45 | 46 | var root1Real, root1Imag, root2Real, root2Imag; 47 | var real = -a1 / (2 * a2); 48 | var disc = a1*a1 - 4*a2; 49 | 50 | if (a2 == 0) { 51 | root1Real = root2Real = -1 / a1; 52 | root1Imag = root2Imag = 0; 53 | } 54 | else if (disc < 0) { 55 | root1Real = root2Real = real; 56 | root1Imag = Math.sqrt(-disc) / (2 * a2); 57 | root2Imag = -root1Imag; 58 | } 59 | else { 60 | root1Real = real + Math.sqrt(disc) / (2 * a2); 61 | root2Real = real - Math.sqrt(disc) / (2 * a2); 62 | root1Imag = root2Imag = 0; 63 | } 64 | 65 | // take recipricol to get z 66 | 67 | this.pole1Real = root1Real / (root1Real * root1Real + root1Imag * root1Imag); 68 | this.pole1Imag = -root1Imag / (root1Real * root1Real + root1Imag * root1Imag); 69 | this.pole2Real = root2Real / (root2Real * root2Real + root2Imag * root2Imag); 70 | this.pole2Imag = -root2Imag / (root2Real * root2Real + root2Imag * root2Imag); 71 | 72 | // stable 73 | 74 | this.pole1Inside = (this.pole1Real * this.pole1Real + this.pole1Imag * this.pole1Imag) < 1; 75 | this.pole2Inside = (this.pole2Real * this.pole2Real + this.pole2Imag * this.pole2Imag) < 1; 76 | this.unstable = !this.pole1Inside || !this.pole2Inside; 77 | 78 | // update indexed variables 79 | 80 | this['kf' + i] = this.kf; 81 | this['kq' + i] = this.kq; 82 | this['unstable' + i] = this.unstable; 83 | }, 84 | }); 85 | 86 | tangle.setValue("index", 2); // initialize both kf1 and kf2, etc. 87 | }); 88 | 89 | 90 | 91 | //---------------------------------------------------------- 92 | // 93 | // Two-pole no-zero lowpass with (mostly) independent Fc and Q 94 | // controls. Efficient implementation, but can be unstable at 95 | // higher frequencies. 96 | // 97 | // A simplified digital adaptation of the analog state variable 98 | // filter, described in Hal Chamberlin's "Musical Applications 99 | // of Microprocessors." 100 | // 101 | // Kf^2 * z^-1 102 | // H(z) = -------------------------------------------- 103 | // 1 - (2 - Kf*(Kf+Kq))*z^-1 + (1 - Kf*Kq)*z^-2 104 | // 105 | // Kq = 1/Q (Q > 0.5) 106 | // 107 | // Kf = 2 * sin(pi*Fc/Fs) (Approximately. It becomes exact 108 | // as Q approaches infinity.) 109 | // 110 | // Kf is approximately 2*pi * Fc/Fs for smallish Fc. 111 | // 112 | // Topology: [bp] [lp] 113 | // 114 | // in --->(+)--(kf)--->(+)----.---(kf)--(+)--->[z^-1]---> out 115 | // ^ ^ v ^ | 116 | // (+)<--(-kq)---'--[z^-1] '--------| 117 | // ^ | 118 | // '----(-1)------------------------------' 119 | 120 | function chamberlinResponse (kf,kq,N,x) { 121 | if (!N) { N = 512; } 122 | 123 | var output = []; 124 | var lp = 0, bp = 0, input = 1; 125 | 126 | for (var i = 0; i < N; i++) { 127 | bp += kf * (input - lp - kq*bp); 128 | lp += kf * bp; 129 | output[i] = lp; 130 | input = x; 131 | } 132 | 133 | return output; 134 | } 135 | 136 | function chamberlinImpulseResponse (kf,kq,N) { 137 | return chamberlinResponse(kf,kq,N,0); 138 | } 139 | 140 | function chamberlinStepResponse (kf,kq,N) { 141 | return chamberlinResponse(kf,kq,N,1); 142 | } 143 | 144 | 145 | 146 | //---------------------------------------------------------- 147 | // 148 | // FilterKnob 149 | // 150 | 151 | Tangle.classes.FilterKnob = { 152 | 153 | initialize: function (el, options, tangle, xParameter, yParameter) { 154 | var index = xParameter.substr(xParameter.length - 1); 155 | var xBounds = { min:20, max:20000 }; 156 | var yBounds = { min:0.01, max:10 }; 157 | 158 | 159 | // log-scaled Q 160 | 161 | var qLogScaleBase = 24; 162 | 163 | function getQForY (y) { 164 | return (yBounds.max - yBounds.min) * (Math.pow(qLogScaleBase, -y/canvasHeight) - 1/qLogScaleBase) + yBounds.min; 165 | } 166 | 167 | function getYForQ (q) { 168 | return -canvasHeight * Math.log((q - yBounds.min) / (yBounds.max - yBounds.min) + 1/qLogScaleBase) / Math.log(qLogScaleBase) 169 | } 170 | 171 | 172 | // view 173 | 174 | el.setStyles({position:"absolute", left:0, top:0}); 175 | 176 | var canvasEl = el.getParent().getElement("canvas"); 177 | var canvasWidth = canvasEl.get("width"); 178 | var canvasHeight = canvasEl.get("height"); 179 | 180 | var lineStyle = "position:absolute; display:block; border-left:1px dotted #00f; pointer-events:none; width:1px; height:" + canvasHeight + "px;"; 181 | var lineEl = new Element("div", { style:lineStyle }); 182 | el.grab(lineEl, "bottom"); 183 | 184 | var knobStyle = "position:absolute; display:none; "; 185 | var knobWidth = 36, knobHeight = 36; 186 | var knobEl = new Element("img", { style:knobStyle, src:"Images/FilterParamsKnob.png", width:knobWidth, height:knobHeight }); 187 | el.grab(knobEl, "bottom"); 188 | 189 | var helpEl = new Element("div", { "class": "FilterKnobHelp" }); 190 | helpEl.set("text", "drag"); 191 | el.grab(helpEl, "bottom"); 192 | 193 | var knobX, knobY; 194 | 195 | this.update = function (el, xValue, yValue) { 196 | var freq = xValue / tangle.getValue("fs"); 197 | knobX = Math.round(Tangle.classes.FilterFreqPlot.getXForNormalizedFrequency(freq, canvasWidth)); 198 | knobY = Math.round(getYForQ(yValue)); 199 | knobEl.setStyles( { left:knobX - knobWidth/2, top:knobY - knobHeight/2 } ); 200 | lineEl.setStyles( { left:knobX }); 201 | helpEl.setStyles( { left:knobX - knobWidth/2 - 22, top:knobY - knobHeight/2 + 8 } ); 202 | }; 203 | 204 | 205 | // rollover effects 206 | 207 | var isShowing = false; 208 | var isHovering = false; 209 | 210 | canvasEl.addEvent("mouseenter", function () { isShowing = true; updateRolloverEffects(); }); 211 | canvasEl.addEvent("mouseleave", function () { isShowing = false; updateRolloverEffects(); }); 212 | knobEl.addEvent("mouseenter", function () { isHovering = true; updateRolloverEffects(); }); 213 | knobEl.addEvent("mouseleave", function () { isHovering = false; updateRolloverEffects(); }); 214 | 215 | function updateRolloverEffects () { 216 | updateCursor(); 217 | var isShowingKnob = (isShowing || isHovering || isDragging); 218 | knobEl.setStyle("display", isShowingKnob ? "block" : "none"); 219 | helpEl.setStyle("display", (isShowingKnob && !didDrag) ? "block" : "none"); 220 | } 221 | 222 | function updateCursor () { 223 | var body = document.getElement("body"); 224 | if (isHovering || isDragging) { body.addClass("cursorDrag"); } 225 | else { body.removeClass("cursorDrag"); } 226 | } 227 | 228 | function updateDynamicLabelsShowing () { 229 | tangle.element.getElements(".showOnDrag").each( function (hideEl) { 230 | hideEl.setStyle("display", isDragging ? "block" : "none"); 231 | }); 232 | tangle.element.getElement(".filterSidebar").setStyle("display", isDragging ? "none" : "block"); 233 | } 234 | 235 | 236 | // drag 237 | 238 | var isDragging = false; 239 | var didDrag = false; 240 | var knobXAtMouseDown, knobYAtMouseDown; 241 | 242 | new BVTouchable(knobEl, { 243 | 244 | touchDidGoDown: function (touches) { 245 | knobXAtMouseDown = knobX; 246 | knobYAtMouseDown = knobY; 247 | isDragging = true; 248 | didDrag = true; 249 | knobEl.set("src", "Images/FilterParamsKnobDrag.png"); 250 | updateRolloverEffects(); 251 | updateDynamicLabelsShowing(); 252 | tangle.setValue("index", index); 253 | }, 254 | 255 | touchDidMove: function (touches) { 256 | var obj = { }; 257 | 258 | var newX = knobXAtMouseDown + touches.translation.x; 259 | var fc = Tangle.classes.FilterFreqPlot.getNormalizedFrequencyForX(newX, canvasWidth) * tangle.getValue("fs"); 260 | obj[xParameter] = fc.limit(xBounds.min, xBounds.max); 261 | 262 | var newY = knobYAtMouseDown - touches.translation.y; 263 | var q = getQForY(newY); 264 | obj[yParameter] = q.limit(yBounds.min, yBounds.max); 265 | 266 | tangle.setValues(obj); 267 | }, 268 | 269 | touchDidGoUp: function (touches) { 270 | isDragging = false; 271 | knobEl.set("src", "Images/FilterParamsKnob.png"); 272 | helpEl.setStyle("display", "none"); 273 | updateRolloverEffects(); 274 | updateDynamicLabelsShowing(); 275 | } 276 | }); 277 | } 278 | }; 279 | 280 | 281 | 282 | //---------------------------------------------------------- 283 | // 284 | // FilterFreqPlot 285 | // 286 | 287 | Tangle.classes.FilterFreqPlot = { 288 | 289 | initialize: function (el, options, tangle) { 290 | this.tangle = tangle; 291 | }, 292 | 293 | update: function (el, kf, kq) { 294 | var canvasWidth = el.get("width"); 295 | var canvasHeight = el.get("height"); 296 | var ctx = el.getContext("2d"); 297 | 298 | var fs = this.tangle.getValue("fs"); 299 | var unstable = this.tangle.getValue("unstable"); 300 | 301 | var N = 2048; 302 | var impulseResponse = chamberlinImpulseResponse(kf,kq,N); 303 | 304 | var fft = new RFFT(N, fs); 305 | fft.forward(impulseResponse); 306 | var values = fft.spectrum; 307 | 308 | var maxValue = 0; 309 | for (var i = 0; i < N; i++) { maxValue = Math.max(maxValue, values[i]); } 310 | maxValue = values[0]; 311 | 312 | ctx.fillStyle = "#fff"; 313 | ctx.fillRect(0, 0, canvasWidth, canvasHeight); 314 | 315 | ctx.fillStyle = unstable ? "#f00" : "#555"; 316 | for (var x = 0; x < canvasWidth; x++) { 317 | var base = 100; 318 | 319 | var i = N * this.getNormalizedFrequencyForX(x, canvasWidth); // log-scale x 320 | var fracI = i - Math.floor(i); 321 | var lowV = values[Math.floor(i)]; 322 | var highV = values[Math.ceil(i)]; 323 | 324 | var value = lowV + fracI * (highV - lowV); 325 | var y = (value > 0) ? Math.max(0, canvasHeight/2 + 32*Math.log(value/maxValue)) : 0; // log-scale y 326 | ctx.fillRect(x, canvasHeight - y, 1, y); 327 | } 328 | }, 329 | 330 | getFrequencyLogScaleBase: function () { return 100; }, 331 | 332 | getNormalizedFrequencyForX: function (x, canvasWidth) { 333 | var base = this.getFrequencyLogScaleBase(); 334 | return 0.5 * (Math.pow(base, x/canvasWidth - 1) - 1/base); 335 | }, 336 | 337 | getXForNormalizedFrequency: function (freq, canvasWidth) { 338 | var base = this.getFrequencyLogScaleBase(); 339 | return ((Math.log((freq * 2) + 1/base) / Math.log(base)) + 1) * canvasWidth; 340 | } 341 | }; 342 | 343 | 344 | 345 | //---------------------------------------------------------- 346 | // 347 | // FilterTimePlot 348 | // 349 | 350 | Tangle.classes.FilterTimePlot = { 351 | 352 | initialize: function (el, options, tangle) { 353 | this.tangle = tangle; 354 | }, 355 | 356 | update: function (el, kf, kq) { 357 | var canvasWidth = el.get("width"); 358 | var canvasHeight = el.get("height"); 359 | var ctx = el.getContext("2d"); 360 | 361 | var fs = this.tangle.getValue("fs"); 362 | var unstable = this.tangle.getValue("unstable"); 363 | var widthBeforeStep = this.getWidthBeforeStep(); 364 | 365 | var N = 256; 366 | var values = chamberlinStepResponse(kf,kq,N); 367 | 368 | ctx.fillStyle = "#fff"; 369 | ctx.fillRect(0, 0, canvasWidth, canvasHeight); 370 | 371 | ctx.strokeStyle = unstable ? "#f00" : "#00f"; 372 | ctx.lineWidth = 2; 373 | ctx.beginPath(); 374 | 375 | ctx.moveTo(0, canvasHeight-1); 376 | ctx.lineTo(widthBeforeStep, canvasHeight-1); 377 | 378 | for (var x = widthBeforeStep; x < canvasWidth; x++) { 379 | var i = x - widthBeforeStep; 380 | var fracI = i - Math.floor(i); 381 | var lowV = values[Math.floor(i)]; 382 | var highV = values[Math.ceil(i)]; 383 | var value = lowV + fracI * (highV - lowV); 384 | var y = value * canvasHeight/2; 385 | ctx.lineTo(x, canvasHeight - y); 386 | } 387 | 388 | ctx.stroke(); 389 | }, 390 | 391 | getWidthBeforeStep: function () { return 16; } 392 | }; 393 | 394 | 395 | //---------------------------------------------------------- 396 | // 397 | // FilterStepPlot 398 | // 399 | 400 | Tangle.classes.FilterStepPlot = { 401 | 402 | initialize: function (el) { 403 | var canvasWidth = el.get("width"); 404 | var canvasHeight = el.get("height"); 405 | var ctx = el.getContext("2d"); 406 | var widthBeforeStep = Tangle.classes.FilterTimePlot.getWidthBeforeStep(); 407 | 408 | ctx.fillStyle = "#fff"; 409 | ctx.fillRect(0, 0, canvasWidth, canvasHeight); 410 | 411 | ctx.strokeStyle = "#00f"; 412 | ctx.lineWidth = 2; 413 | ctx.beginPath(); 414 | 415 | ctx.moveTo(0,canvasHeight-1); 416 | ctx.lineTo(widthBeforeStep,canvasHeight-1); 417 | ctx.lineTo(widthBeforeStep,canvasHeight/2); 418 | ctx.lineTo(canvasWidth,canvasHeight/2); 419 | ctx.stroke(); 420 | } 421 | }; 422 | 423 | 424 | //---------------------------------------------------------- 425 | // 426 | // FilterPolePlot 427 | // 428 | 429 | Tangle.classes.FilterPolePlot = { 430 | 431 | initialize: function (el, options, tangle) { 432 | this.tangle = tangle; 433 | }, 434 | 435 | update: function (el, pole1Real, pole1Imag, pole2Real, pole2Imag) { 436 | var pole1Inside = this.tangle.getValue("pole1Inside"); 437 | var pole2Inside = this.tangle.getValue("pole2Inside"); 438 | 439 | var canvasWidth = el.get("width"); 440 | var canvasHeight = el.get("height"); 441 | var ctx = el.getContext("2d"); 442 | var unitRadius = canvasWidth * 1/4; 443 | 444 | // draw arena 445 | 446 | ctx.fillStyle = "#fff"; 447 | ctx.fillRect(0, 0, canvasWidth, canvasHeight); 448 | 449 | ctx.fillStyle = "#f4f4f4"; 450 | ctx.beginPath(); 451 | ctx.arc(canvasWidth/2, canvasHeight/2, unitRadius, 0, Math.PI * 2, false); 452 | ctx.fill(); 453 | 454 | ctx.strokeStyle = "#fff"; 455 | ctx.lineWidth = 2; 456 | ctx.beginPath(); 457 | ctx.moveTo(canvasWidth/2 - unitRadius, canvasHeight/2); 458 | ctx.lineTo(canvasWidth/2 + unitRadius, canvasHeight/2); 459 | ctx.stroke(); 460 | ctx.beginPath(); 461 | ctx.moveTo(canvasWidth/2, canvasHeight/2 - unitRadius); 462 | ctx.lineTo(canvasWidth/2, canvasHeight/2 + unitRadius); 463 | ctx.stroke(); 464 | 465 | // draw poles 466 | 467 | ctx.strokeStyle = pole1Inside ? "#00f" : "#f00"; 468 | drawCrossAtPoint(canvasWidth/2 + unitRadius * pole1Real, 469 | canvasHeight/2 + unitRadius * pole1Imag); 470 | 471 | ctx.strokeStyle = pole2Inside ? "#00f" : "#f00"; 472 | drawCrossAtPoint(canvasWidth/2 + unitRadius * pole2Real, 473 | canvasHeight/2 + unitRadius * pole2Imag); 474 | 475 | function drawCrossAtPoint(x,y) { 476 | var crossRadius = 3; 477 | ctx.lineWidth = 1; 478 | ctx.beginPath(); 479 | ctx.moveTo(x - crossRadius, y - crossRadius); 480 | ctx.lineTo(x + crossRadius, y + crossRadius); 481 | ctx.stroke(); 482 | ctx.beginPath(); 483 | ctx.moveTo(x - crossRadius, y + crossRadius); 484 | ctx.lineTo(x + crossRadius, y - crossRadius); 485 | ctx.stroke(); 486 | } 487 | } 488 | 489 | }; 490 | 491 | 492 | })(); 493 | 494 | -------------------------------------------------------------------------------- /reference.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tangle: API Reference 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |

Contents

43 | 44 | 51 | 52 | 53 | 54 | 55 | 56 |

*   *   *

57 | 58 |

HTML attributes

59 | 60 | 66 | 67 |

class

68 | 69 |
 70 | I want <span class="TKAdjustableNumber" data-var="cookies"></span> cookies.
 71 | 
72 | 73 |

The class attribute normally specifies a CSS class which gives an element a style. With Tangle, it can also specify a JavaScript class which gives an element a behavior. It might turn an element into a control that adjusts a variable, or a view that displays a variable. It is common to define a class in both CSS and Tangle, for appearance and behavior respectively.

74 | 75 |

A number of basic classes are included with TangleKit. For details on how to create and plug in your own classes, see Tangle.classes.

76 | 77 |

An element can have any number of classes, like so:

78 | 79 |
 80 | <span class="MyClass MyOtherClass" data-var="cookies">
 81 | 
82 | 83 |

Any of the classes can be defined in CSS, JavaScript, both, or neither. You might use one class for the visual presentation of a variable, and another to provide interactive manipulation.

84 | 85 | 86 |

data-var

87 | 88 |
 89 | I ate <span data-var="cookies"></span> cookies.
 90 | 
91 | 92 |

The data-var attribute specifies a variable. You typically use it to display the variable's value in some way, or to specify which variable a class should adjust.

93 | 94 |

The data-var attribute can go on any element. If the element has a class, the variable name will be passed to the class's initialize method, and the variable's value will be passed to the class's update method whenever it changes.

95 | 96 |

If none of the element's classes has an update method, Tangle will insert the variable's value into the element (before any existing content), and update it whenever it changes. The value will be formatted with the data-format attribute, if present. Tangle won't insert anything if any class has an update method, because it assumes that class is handling the display of the variable.

97 | 98 |

An element can have multiple variables, like so:

99 | 100 |
101 | <span class="MyClass" data-var="cookies tacos">
102 | 
103 | 104 |

All variable names will be passed to MyClass's initialize method, and all values will be passed to MyClass's update method. This is useful for two-dimensional controls that adjust two variables at once, or views that depend on multiple variables. See Tangle.classes for details.

105 | 106 | 107 |

data-format

108 | 109 |
110 | That cookie costs $<span data-var="cost" data-format="%.2f"></span>.
111 | 
112 | 113 |

The variable is presented with the given format. You can specify a printf-style format, or define your own format functions. To use printf-style formats, you must include a sprintf library, such as the one that is provided with TangleKit. For details on defining custom formats, see Tangle.formats.

114 | 115 |

If you specify multiple variables in the data-var attribute, all values will be passed to the format:

116 | 117 |
118 | The cookies cost $<span data-var="cost quantity" data-format="%.2f per %d"></span>.
119 | 
120 | 121 |

If you are using a custom format, you can pass parameters to it, separated by spaces. See Tangle.formats for details.

122 | 123 |
124 | <span data-var="cookies" data-format="myFormat parameter1 parameter2">
125 | 
126 | 127 | 128 |

other data attributes

129 | 130 |

Classes may make use of other attributes with the data- prefix. For example, specifying the minimum and maximum values for a TKAdjustableNumber: 131 | 132 |

133 | <span class="TKAdjustableNumber" data-var="cookies" data-min="0" data-max="20">
134 | 
135 | 136 |

These additional attributes are passed to the class's initialize method, via the options parameter.

137 | 138 |

For details on what attributes a class accepts, see the documentation for the individual class.

139 | 140 | 141 | 142 | 143 | 144 |

*   *   *

145 | 146 |

JavaScript API

147 | 148 | 155 | 156 |

new Tangle

157 | 158 |
159 | var tangle = new Tangle (rootElement, model);
160 | 
161 | 162 |
163 | var tangle = new Tangle (document.getElementById("calorieCalculator"), {
164 |     initialize: function () { this.cookies = 4; },
165 |     update:     function () { this.calories = this.cookies * 50; }
166 | });
167 | 
168 | 169 |

Creates a new tangle. Tangle looks under rootElement for elements with classes or variables. Classes are initialized, and the model is set as described below.

170 | 171 |

You may create as many tangles as you like, with different rootElements or models. Each one keeps track of its own variables and classes. Typically you will create a tangle for each "interactive widget" in your document.

172 | 173 | 174 |

setModel

175 | 176 |
177 | tangle.setModel(newModel);
178 | 
179 | 180 |
181 | tangle.setModel({
182 |     initialize: function () { this.cookies = 4; },
183 |     update:     function () { this.calories = this.cookies * 50; }
184 | });
185 | 
186 | 187 |

This method changes and resets the current model.

188 | 189 |

The model should be an object with initialize and update methods. When the model is set, the initialize method is called to set the initial values of variables, and the update method is called to calculate variables derived from those. When a variable is changed subsequently via tangle.setValue, the update method will be called again to recalculate derived variables.

190 | 191 |

You may also define any methods that you want to use internally:

192 | 193 |
194 | {  initialize: function () { this.cookies = 4; },
195 |    update:     function () { this.calories = this.cookies * this.getCalPerCookie(); },
196 |    getCalPerCookie: function () { ... }
197 | }
198 | 
199 | 200 | 201 |

getValue

202 | 203 |
204 | var value = tangle.getValue(variable);
205 | 
206 | 207 |
208 | var numberOfCookies = tangle.getValue("cookies");
209 | 
210 | 211 |

Returns the current value of a variable.

212 | 213 | 214 |

setValue

215 | 216 |
217 | tangle.setValue(variable, newValue);
218 | 
219 | 220 |
221 | tangle.setValue("cookies", 17);
222 | 
223 | 224 |

Sets a new value for a variable. If the new value is different than the old one, the model is updated.

225 | 226 |

A variable is typically a number, but can be of any type. If you are using a reference type such as an Array, be aware that you cannot update the model by mutating the array. Instead, pass a new or copied array to tangle.setValue.

227 | 228 | 229 |

setValues

230 | 231 |
232 | tangle.setValues({ variableName:value, variableName:value });
233 | 
234 | 235 |
236 | tangle.setValues({ cookies:17, cupcakes:12 });
237 | 
238 | 239 |

Sets values for multiple variables at once.

240 | 241 | 242 | 243 | 244 | 245 |

*   *   *

246 | 247 |

Tangle.classes

248 | 249 |
250 | Tangle.classes.MyClass = {
251 |     initialize: function (element, options, tangle, variable) { ... },  // optional
252 |     update: function (element, value) { ... }   // optional
253 | };
254 | 
255 | 256 |

A Tangle class lets you associate some JavaScript code with an HTML element. It's typically used to define a control that adjusts a variable, or a view that displays a variable, although more creative uses are possible.

257 | 258 |

If your HTML contains:

259 | 260 |
261 | <span class="MyClass" data-var="cookies">
262 | 
263 | 264 |

then when the tangle is created, MyClass will be instantiated for that element, and if there is an initialize method, it will be called, and passed the name of the variable. If there is an update method, it will be called when the specified variable is initialized or changed, and passed the value of the variable.

265 | 266 | 267 |

initialize

268 | 269 |

The initialize method is a good place to add event listeners to the element, to set up an interactive control. The following example implements a control that increments the variable when it is clicked.

270 | 271 |
272 | Tangle.classes.ClickableNumber = {
273 |     initialize: function (element, options, tangle, variable) {
274 |         element.onclick = function () {
275 |             var value = tangle.getValue(variable);
276 |             tangle.setValue(variable, value + 1);
277 |         };
278 |     }
279 | };
280 | 
281 | 282 |

The options parameter is described below.

283 | 284 | 285 |

update

286 | 287 |

The update method is for updating the element's appearance or contents when the variable changes. The following example resizes the element's width when the variable changes, perhaps for a bar in a bar chart.

288 | 289 |
290 | Tangle.classes.StretchyBar = {
291 |     update: function (element, value) {
292 |         element.style.width = "" + value + "px";
293 |     }
294 | };
295 | 
296 | 297 | 298 |

other methods

299 | 300 |

You may also define any methods that you want to use internally.

301 | 302 |
303 | Tangle.classes.StretchyBar = {
304 |     update: function (element, value) {
305 |         var width = value * this.getStretchiness();
306 |         element.style.width = "" + width + "px";
307 |     },
308 |     getStretchiness: function () {
309 |         ...
310 |     }
311 | };
312 | 
313 | 314 | 315 |

CSS

316 | 317 |

Because these are CSS classes as well as Tangle classes, you can often specify the element's style in a CSS file, and only deal with behavior in JavaScript.

318 | 319 |
320 | .ClickableNumber {
321 |     color: #46f;
322 |     border-bottom: 1px dashed #46f;
323 |     cursor: pointer;
324 | }
325 | 
326 | 327 | 328 |

framework classes

329 | 330 |

If Tangle.classes.MyClass is a function instead of an object, Tangle will call it as a constructor, instead of directly invoking an initialize method. So, if you're using a framework such as MooTools that has its own notion of what a "class" is, you can use its classes directly, and take advantage of inheritance and other features.

331 | 332 |
333 | Tangle.classes.MyClass = new Class({  // assuming we're using MooTools
334 |     Extends: MyBaseClass,
335 |     initialize: function (element, options, tangle, variable) {
336 |        this.parent(element, options, tangle, variable);
337 |        ...
338 |     }
339 | });
340 | 
341 | 342 | 343 |

multiple variables

344 | 345 |

An HTML element can specify any number of variables, and they will all be passed to initialize and update. For example, if your class expects two variables:

346 | 347 |
348 | <span class="MyClass" data-var="cookies cupcakes">
349 | 
350 | 351 |

you would define it like so:

352 | 353 |
354 | Tangle.classes.MyClass = {
355 |     initialize: function (element, options, tangle, variable1, variable2) { ... },
356 |     update: function (element, value1, value2) { ... }
357 | };
358 | 
359 | 360 |

This is useful for two-dimensional controls that adjust two variables at once, or views that display multiple variables.

361 | 362 | 363 |

options

364 | 365 |

The initialize method is passed an options parameter:

366 | 367 |
368 | Tangle.classes.MyClass = {
369 |     initialize: function (element, options, tangle, variable) { ... }
370 | };
371 | 
372 | 373 |

The options parameter is an object containing the data- attributes of the elements. (The "data-" prefix is removed.) If your HTML is:

374 | 375 |
376 | <span class="MyClass" data-min="1" data-max="10">
377 | 
378 | 379 |

then options will be an object like so:

380 | 381 |
382 | { min:"1", max:"10" }
383 | 
384 | 385 | 386 | 387 | 388 | 389 | 390 |

*   *   *

391 | 392 |

Tangle.formats

393 | 394 |
395 | Tangle.formats.myFormat = function (value) { return "..."; }
396 | 
397 | 398 |

A Tangle format defines how to present a value. If your HTML is:

399 | 400 |
401 | <span data-var="fractionOfCookiesRemaining" data-format="percent">
402 | 
403 | 404 |

then when the variable is updated, the following function will be called to turn the value into a string.

405 | 406 |
407 | Tangle.formats.percent = function (value) {     // formats 0.42 as "42%"
408 |     return "" + Math.round(value * 100) + "%";
409 | };
410 | 
411 | 412 |

You can often use printf-style formats instead, but Tangle.formats is available for your custom formatting needs. To use printf-style formats, you must include a sprintf library, such as the one that is provided with TangleKit.

413 | 414 |

multiple variables

415 | 416 |

If the data-var attribute specifies multiple variables, all values will be passed to the function.

417 | 418 |
419 | <span data-var="cookies cupcakes" data-format="myFormat">
420 | 
421 | 422 |
423 | Tangle.formats.myFormat = function (value1, value2) { return "..."; }
424 | 
425 | 426 |

parameters

427 | 428 |

You can specify parameters in the data-format attribute, separated by spaces. These strings will be passed to the function, after the variable values.

429 | 430 |
431 | <span data-var="cookies" data-format="myFormat huge">
432 | 
433 | 434 |
435 | Tangle.formats.myFormat = function (value, size) { return "..."; }
436 | 
437 | 438 | 439 | 440 | 441 | 442 | 443 |

*   *   *

444 | 445 |

TangleKit

446 | 447 |

TangleKit is an optional collection of Tangle classes and formats, for adjusting variables and visualizing values. You can grab whichever components you want, use them, extend them, modify them, or just learn from them and make your own.

448 | 449 |

TangleKit is currently pretty rudimentary, and not yet documented here. You can look through TangleKit.js for what's currently available. Perhaps you'd even like to contribute to it.

450 | 451 | 452 |
453 | 454 | 455 | 456 | 457 | 458 | 471 | 472 | 473 |
474 | 475 | -------------------------------------------------------------------------------- /Fonts/BorisBlackBloxx/BorisBlackBloxxRegular-demo.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | BorisBlackBloxx Regular Specimen 19 | 20 | 21 | 26 | 27 | 28 | 29 |
30 | 32 | 39 | 40 |
41 | 42 | 43 |
44 | 45 |
46 |
47 |
AaBb
48 |
49 |
50 | 51 |
52 |
A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
53 |
54 |
55 |
56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
73 | 74 |
75 | 76 |
77 | 78 | 79 | 80 |
81 | 82 | 83 |
84 |
body
body
body
body
85 |
86 | bodyBorisBlackBloxx Regular 87 |
88 |
89 | bodyArial 90 |
91 |
92 | bodyVerdana 93 |
94 |
95 | bodyGeorgia 96 |
97 | 98 | 99 | 100 |
101 | 102 | 103 |
104 | 105 |
106 |

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

107 | 108 |
109 |
110 |

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

111 | 112 |
113 |
114 |

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

115 | 116 |
117 |
118 |

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

119 | 120 |
121 |
122 | 123 |
124 |
125 |
126 |

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

127 | 128 |
129 |
130 |

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

131 | 132 |
133 |
134 |

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

135 | 136 |
137 | 138 |
139 | 140 |
141 | 142 |
143 |
144 |

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

145 |
146 |
147 |

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

148 |
149 | 150 |
151 | 152 |
153 | 154 |
155 |
156 |

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

157 |
158 |
159 | 160 |
161 | 162 | 163 | 164 |
165 |
166 |

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

167 | 168 |
169 |
170 |

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

171 | 172 |
173 |
174 |

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

175 | 176 |
177 |
178 |

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

179 | 180 |
181 |
182 | 183 |
184 | 185 |
186 |
187 |

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

188 | 189 |
190 |
191 |

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

192 | 193 |
194 |
195 |

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

196 | 197 |
198 |
199 | 200 |
201 | 202 |
203 |
204 |

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

205 |
206 |
207 |

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

208 |
209 | 210 |
211 | 212 |
213 | 214 |
215 |
216 |

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

217 |
218 |
219 | 220 |
221 | 222 | 223 | 224 | 225 |
226 | 227 |
228 | 229 |
230 | 231 |
232 |

Lorem Ipsum Dolor

233 |

Etiam porta sem malesuada magna mollis euismod

234 | 235 | 236 |
237 |
238 |
239 |
240 |

Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

241 | 242 | 243 |

Pellentesque ornare sem

244 | 245 |

Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

246 | 247 |

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

248 | 249 |

Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

250 | 251 |

Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

252 | 253 |

Cras mattis consectetur

254 | 255 |

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

256 | 257 |

Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

258 |
259 | 260 | 277 |
278 | 279 |
280 | 281 | 282 |
283 |
284 |
285 |

ClearType (GDI)

286 | 287 |

288 |
289 |
290 |
291 | 292 | 293 | 294 | 295 |
296 |
297 |
298 | 299 |

Language Support

300 |

The subset of BorisBlackBloxx Regular in this kit supports the following languages:
301 | 302 | Albanian, Danish, Dutch, English, Faroese, French, German, Icelandic, Italian, Malagasy, Norwegian, Spanish, Swedish

303 |

Glyph Chart

304 |

The subset of BorisBlackBloxx Regular in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.

305 |
306 | 307 |

&#32;

308 |

&#33;

!
309 |

&#34;

"
310 |

&#35;

#
311 |

&#36;

$
312 |

&#37;

%
313 |

&#38;

&
314 |

&#39;

'
315 |

&#40;

(
316 |

&#41;

)
317 |

&#42;

*
318 |

&#43;

+
319 |

&#44;

,
320 |

&#45;

-
321 |

&#46;

.
322 |

&#47;

/
323 |

&#48;

0
324 |

&#49;

1
325 |

&#50;

2
326 |

&#51;

3
327 |

&#52;

4
328 |

&#53;

5
329 |

&#54;

6
330 |

&#55;

7
331 |

&#56;

8
332 |

&#57;

9
333 |

&#58;

:
334 |

&#59;

;
335 |

&#60;

<
336 |

&#61;

=
337 |

&#62;

>
338 |

&#63;

?
339 |

&#64;

@
340 |

&#65;

A
341 |

&#66;

B
342 |

&#67;

C
343 |

&#68;

D
344 |

&#69;

E
345 |

&#70;

F
346 |

&#71;

G
347 |

&#72;

H
348 |

&#73;

I
349 |

&#74;

J
350 |

&#75;

K
351 |

&#76;

L
352 |

&#77;

M
353 |

&#78;

N
354 |

&#79;

O
355 |

&#80;

P
356 |

&#81;

Q
357 |

&#82;

R
358 |

&#83;

S
359 |

&#84;

T
360 |

&#85;

U
361 |

&#86;

V
362 |

&#87;

W
363 |

&#88;

X
364 |

&#89;

Y
365 |

&#90;

Z
366 |

&#91;

[
367 |

&#92;

\
368 |

&#93;

]
369 |

&#94;

^
370 |

&#95;

_
371 |

&#96;

`
372 |

&#97;

a
373 |

&#98;

b
374 |

&#99;

c
375 |

&#100;

d
376 |

&#101;

e
377 |

&#102;

f
378 |

&#103;

g
379 |

&#104;

h
380 |

&#105;

i
381 |

&#106;

j
382 |

&#107;

k
383 |

&#108;

l
384 |

&#109;

m
385 |

&#110;

n
386 |

&#111;

o
387 |

&#112;

p
388 |

&#113;

q
389 |

&#114;

r
390 |

&#115;

s
391 |

&#116;

t
392 |

&#117;

u
393 |

&#118;

v
394 |

&#119;

w
395 |

&#120;

x
396 |

&#121;

y
397 |

&#122;

z
398 |

&#123;

{
399 |

&#124;

|
400 |

&#125;

}
401 |

&#126;

~
402 |

&#160;

 
403 |

&#161;

¡
404 |

&#162;

¢
405 |

&#163;

£
406 |

&#165;

¥
407 |

&#166;

¦
408 |

&#167;

§
409 |

&#168;

¨
410 |

&#169;

©
411 |

&#170;

ª
412 |

&#171;

«
413 |

&#172;

¬
414 |

&#173;

­
415 |

&#174;

®
416 |

&#175;

¯
417 |

&#176;

°
418 |

&#177;

±
419 |

&#178;

²
420 |

&#179;

³
421 |

&#180;

´
422 |

&#181;

µ
423 |

&#182;

424 |

&#183;

·
425 |

&#184;

¸
426 |

&#185;

¹
427 |

&#186;

º
428 |

&#187;

»
429 |

&#188;

¼
430 |

&#189;

½
431 |

&#190;

¾
432 |

&#191;

¿
433 |

&#192;

À
434 |

&#193;

Á
435 |

&#194;

Â
436 |

&#195;

Ã
437 |

&#196;

Ä
438 |

&#197;

Å
439 |

&#198;

Æ
440 |

&#199;

Ç
441 |

&#200;

È
442 |

&#201;

É
443 |

&#202;

Ê
444 |

&#203;

Ë
445 |

&#204;

Ì
446 |

&#205;

Í
447 |

&#206;

Î
448 |

&#207;

Ï
449 |

&#208;

Ð
450 |

&#209;

Ñ
451 |

&#210;

Ò
452 |

&#211;

Ó
453 |

&#212;

Ô
454 |

&#213;

Õ
455 |

&#214;

Ö
456 |

&#215;

×
457 |

&#216;

Ø
458 |

&#217;

Ù
459 |

&#218;

Ú
460 |

&#219;

Û
461 |

&#220;

Ü
462 |

&#221;

Ý
463 |

&#222;

Þ
464 |

&#223;

ß
465 |

&#224;

à
466 |

&#225;

á
467 |

&#226;

â
468 |

&#227;

ã
469 |

&#228;

ä
470 |

&#229;

å
471 |

&#230;

æ
472 |

&#231;

ç
473 |

&#232;

è
474 |

&#233;

é
475 |

&#234;

ê
476 |

&#235;

ë
477 |

&#236;

ì
478 |

&#237;

í
479 |

&#238;

î
480 |

&#239;

ï
481 |

&#240;

ð
482 |

&#241;

ñ
483 |

&#242;

ò
484 |

&#243;

ó
485 |

&#244;

ô
486 |

&#245;

õ
487 |

&#246;

ö
488 |

&#247;

÷
489 |

&#248;

ø
490 |

&#249;

ù
491 |

&#250;

ú
492 |

&#251;

û
493 |

&#252;

ü
494 |

&#253;

ý
495 |

&#254;

þ
496 |

&#255;

ÿ
497 |

&#338;

Œ
498 |

&#339;

œ
499 |

&#376;

Ÿ
500 |

&#710;

ˆ
501 |

&#732;

˜
502 |

&#8211;

503 |

&#8212;

504 |

&#8216;

505 |

&#8217;

506 |

&#8218;

507 |

&#8220;

508 |

&#8221;

509 |

&#8222;

510 |

&#8226;

511 |

&#8230;

512 |

&#8249;

513 |

&#8250;

514 |

&#8364;

515 |

&#8482;

516 |

&#57344;

517 |

&#64257;

518 |

&#64258;

519 |

&#64259;

520 |

&#64260;

521 |
522 |
523 | 524 | 525 |
526 |
527 | 528 | 529 |
530 | 531 |
532 | 533 |
534 |
535 |
536 |

Installing Webfonts

537 | 538 |

Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

539 | 540 |

1. Upload your webfonts

541 |

You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

542 | 543 |

2. Include the webfont stylesheet

544 |

A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

545 | 546 | 547 | 548 | @font-face{ 549 | font-family: 'MyWebFont'; 550 | src: url('WebFont.eot'); 551 | src: url('WebFont.eot?#iefix') format('embedded-opentype'), 552 | url('WebFont.woff') format('woff'), 553 | url('WebFont.ttf') format('truetype'), 554 | url('WebFont.svg#webfont') format('svg'); 555 | } 556 | 557 | 558 |

We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

559 | <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> 560 | 561 |

3. Modify your own stylesheet

562 |

To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

563 | p { font-family: 'WebFont', Arial, sans-serif; } 564 | 565 |

4. Test

566 |

Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

567 |
568 | 569 | 595 |
596 | 597 |
598 | 599 |
600 | 603 |
604 | 605 | 606 | --------------------------------------------------------------------------------