فهرست منبع

add all the talks from the old site

jmelesky 8 سال پیش
والد
کامیت
e9fc9f38a2
100فایلهای تغییر یافته به همراه33790 افزوده شده و 0 حذف شده
  1. BIN
      talks/bayes_theorem/Bayes'_theorem Files/0000c57867374dbd1b7837882c5f7e5c.png
  2. BIN
      talks/bayes_theorem/Bayes'_theorem Files/01b89e228d286117e3f007b0bc3b67c0.png
  3. BIN
      talks/bayes_theorem/Bayes'_theorem Files/07cf5446f48e526e1abcdb471e60089a.png
  4. BIN
      talks/bayes_theorem/Bayes'_theorem Files/1e3f8174c43010f28197df71869b7076.png
  5. BIN
      talks/bayes_theorem/Bayes'_theorem Files/2203d39e737f922023985141bee96496.png
  6. BIN
      talks/bayes_theorem/Bayes'_theorem Files/246afc51d522fc2830a4e2ab6a52459d.png
  7. BIN
      talks/bayes_theorem/Bayes'_theorem Files/26afd73f8c17f310707120691ccc4a35.png
  8. BIN
      talks/bayes_theorem/Bayes'_theorem Files/2bd02825f98ac442e1819e108ab10de5.png
  9. BIN
      talks/bayes_theorem/Bayes'_theorem Files/32146e2a0949cb320bfd837305e6c133.png
  10. BIN
      talks/bayes_theorem/Bayes'_theorem Files/33e1933b9dc0649e63bef6a4767d9736.png
  11. BIN
      talks/bayes_theorem/Bayes'_theorem Files/36334bfa49453b8bb0f43149502314b0.png
  12. BIN
      talks/bayes_theorem/Bayes'_theorem Files/3ba2b020b036f8920da08f092979ea9e.png
  13. BIN
      talks/bayes_theorem/Bayes'_theorem Files/3e294d8052aa226062a1a1158be30079.png
  14. BIN
      talks/bayes_theorem/Bayes'_theorem Files/402214a6b5fb1babe545afc206a96d92.png
  15. BIN
      talks/bayes_theorem/Bayes'_theorem Files/6956beed6709f29c47056603dd448e37.png
  16. BIN
      talks/bayes_theorem/Bayes'_theorem Files/6aa65315e6ae2da1b417789e43b4eb72.png
  17. BIN
      talks/bayes_theorem/Bayes'_theorem Files/81be09df6b27e9b3d3ee7c8e4e6ced85.png
  18. BIN
      talks/bayes_theorem/Bayes'_theorem Files/81dbc60f7af3163c790e7891c072ed53.png
  19. BIN
      talks/bayes_theorem/Bayes'_theorem Files/863ce6b9dadd7cc5db96f03ebfdf9cd8.png
  20. BIN
      talks/bayes_theorem/Bayes'_theorem Files/8b6c81124815aad54c91c42b3261165d.png
  21. BIN
      talks/bayes_theorem/Bayes'_theorem Files/908da7e26f756ae79f5ac0bfce87c5ce.png
  22. BIN
      talks/bayes_theorem/Bayes'_theorem Files/9c1f50e099b3ae7e1ece4cb35ca21356.png
  23. BIN
      talks/bayes_theorem/Bayes'_theorem Files/a6df7814a46fc4c9670cc510b72bb318.png
  24. 149 0
      talks/bayes_theorem/Bayes'_theorem Files/ajax.js
  25. BIN
      talks/bayes_theorem/Bayes'_theorem Files/b337da08b983c9e9c6f741d856b4b72c.png
  26. BIN
      talks/bayes_theorem/Bayes'_theorem Files/b3866293e4211c8404d4e4a86bcf156d.png
  27. BIN
      talks/bayes_theorem/Bayes'_theorem Files/b9474d45d82accf03434710c10871795.png
  28. BIN
      talks/bayes_theorem/Bayes'_theorem Files/bcb58d4f262347072d35dea4a65977dd.png
  29. BIN
      talks/bayes_theorem/Bayes'_theorem Files/c4451387126a4c6a51739382cacb9e99.png
  30. BIN
      talks/bayes_theorem/Bayes'_theorem Files/c5932ce24d2e109050d0846a541a70b1.png
  31. 290 0
      talks/bayes_theorem/Bayes'_theorem Files/commonPrint.css
  32. BIN
      talks/bayes_theorem/Bayes'_theorem Files/daa4b03e8ea857a6957e4a1d0b8ef6b1.png
  33. BIN
      talks/bayes_theorem/Bayes'_theorem Files/dc58f3b5ce30cd67b381fb124e19c384.png
  34. BIN
      talks/bayes_theorem/Bayes'_theorem Files/df2df78c50ec6f12e78abfc669dad68e.png
  35. BIN
      talks/bayes_theorem/Bayes'_theorem Files/efaf8fda8a92eeb2d8cf70468c20ed5a.png
  36. BIN
      talks/bayes_theorem/Bayes'_theorem Files/f9c94dd8c87f868043f2ff054a4444b3.png
  37. BIN
      talks/bayes_theorem/Bayes'_theorem Files/f9fdf7c75c7f4ddae1140dd2a9e73714.png
  38. 1334 0
      talks/bayes_theorem/Bayes'_theorem Files/handheld.css
  39. 680 0
      talks/bayes_theorem/Bayes'_theorem Files/index.php
  40. 1637 0
      talks/bayes_theorem/Bayes'_theorem Files/main.css
  41. BIN
      talks/bayes_theorem/Bayes'_theorem Files/poweredby_mediawiki_88x31.png
  42. BIN
      talks/bayes_theorem/Bayes'_theorem Files/wiki-en.png
  43. 1246 0
      talks/bayes_theorem/Bayes'_theorem Files/wikibits.js
  44. BIN
      talks/bayes_theorem/Bayes'_theorem Files/wikimedia-button.png
  45. 478 0
      talks/bayes_theorem/Bayes'_theorem.html
  46. 1488 0
      talks/bayes_theorem/index.html
  47. 7 0
      talks/cant-c-a-thing/bookmarks
  48. 22 0
      talks/cant-c-a-thing/code/gen_matrix.rb
  49. 226 0
      talks/cant-c-a-thing/code/matrix.rb
  50. 85 0
      talks/cant-c-a-thing/code/multiwrapper.c
  51. 222 0
      talks/cant-c-a-thing/code/ocaml_arrays.ml
  52. 227 0
      talks/cant-c-a-thing/code/ocaml_lists.ml
  53. 84 0
      talks/cant-c-a-thing/code/ocamlmatrix.c
  54. 28 0
      talks/cant-c-a-thing/code/ocamlmatrix_old.c
  55. 31 0
      talks/cant-c-a-thing/code/wrapped_ocaml.ml
  56. BIN
      talks/cant-c-a-thing/img/dhh.jpg
  57. 621 0
      talks/cant-c-a-thing/index.html
  58. 494 0
      talks/cant-c-a-thing/index.txt
  59. 136 0
      talks/cant-c-a-thing/scripts/builder.js
  60. 965 0
      talks/cant-c-a-thing/scripts/controls.js
  61. 974 0
      talks/cant-c-a-thing/scripts/dragdrop.js
  62. 1122 0
      talks/cant-c-a-thing/scripts/effects.js
  63. 3408 0
      talks/cant-c-a-thing/scripts/jquery-1.2.3.js
  64. 12 0
      talks/cant-c-a-thing/scripts/jquery-1.2.3.min.js
  65. 4170 0
      talks/cant-c-a-thing/scripts/prototype.js
  66. 58 0
      talks/cant-c-a-thing/scripts/scriptaculous.js
  67. 275 0
      talks/cant-c-a-thing/scripts/slider.js
  68. 99 0
      talks/cant-c-a-thing/scripts/slideshow.js
  69. 55 0
      talks/cant-c-a-thing/scripts/sound.js
  70. 568 0
      talks/cant-c-a-thing/scripts/unittest.js
  71. BIN
      talks/federate_fdw/federate_fdw.pdf
  72. 320 0
      talks/locking_war_stories/index.html
  73. 150 0
      talks/locking_war_stories/index.txt
  74. 136 0
      talks/locking_war_stories/scripts/builder.js
  75. 965 0
      talks/locking_war_stories/scripts/controls.js
  76. 974 0
      talks/locking_war_stories/scripts/dragdrop.js
  77. 1122 0
      talks/locking_war_stories/scripts/effects.js
  78. 3408 0
      talks/locking_war_stories/scripts/jquery-1.2.3.js
  79. 12 0
      talks/locking_war_stories/scripts/jquery-1.2.3.min.js
  80. 4170 0
      talks/locking_war_stories/scripts/prototype.js
  81. 58 0
      talks/locking_war_stories/scripts/scriptaculous.js
  82. 275 0
      talks/locking_war_stories/scripts/slider.js
  83. 99 0
      talks/locking_war_stories/scripts/slideshow.js
  84. 55 0
      talks/locking_war_stories/scripts/sound.js
  85. 568 0
      talks/locking_war_stories/scripts/unittest.js
  86. BIN
      talks/ml-primer/.DS_Store
  87. BIN
      talks/ml-primer/._.DS_Store
  88. 287 0
      talks/ml-primer/index.html
  89. BIN
      talks/ml-primer/media/badline1.png
  90. BIN
      talks/ml-primer/media/badline2.png
  91. BIN
      talks/ml-primer/media/badset1.png
  92. BIN
      talks/ml-primer/media/badset2.png
  93. BIN
      talks/ml-primer/media/badset3.png
  94. BIN
      talks/ml-primer/media/basedata.png
  95. BIN
      talks/ml-primer/media/bordervectors.png
  96. BIN
      talks/ml-primer/media/cleansep.png
  97. BIN
      talks/ml-primer/media/dsc01228-02-h.jpg
  98. BIN
      talks/ml-primer/media/supportvectors.png
  99. BIN
      talks/ml-primer/scripts/.DS_Store
  100. BIN
      talks/ml-primer/scripts/._.DS_Store

BIN
talks/bayes_theorem/Bayes'_theorem Files/0000c57867374dbd1b7837882c5f7e5c.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/01b89e228d286117e3f007b0bc3b67c0.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/07cf5446f48e526e1abcdb471e60089a.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/1e3f8174c43010f28197df71869b7076.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/2203d39e737f922023985141bee96496.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/246afc51d522fc2830a4e2ab6a52459d.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/26afd73f8c17f310707120691ccc4a35.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/2bd02825f98ac442e1819e108ab10de5.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/32146e2a0949cb320bfd837305e6c133.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/33e1933b9dc0649e63bef6a4767d9736.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/36334bfa49453b8bb0f43149502314b0.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/3ba2b020b036f8920da08f092979ea9e.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/3e294d8052aa226062a1a1158be30079.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/402214a6b5fb1babe545afc206a96d92.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/6956beed6709f29c47056603dd448e37.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/6aa65315e6ae2da1b417789e43b4eb72.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/81be09df6b27e9b3d3ee7c8e4e6ced85.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/81dbc60f7af3163c790e7891c072ed53.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/863ce6b9dadd7cc5db96f03ebfdf9cd8.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/8b6c81124815aad54c91c42b3261165d.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/908da7e26f756ae79f5ac0bfce87c5ce.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/9c1f50e099b3ae7e1ece4cb35ca21356.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/a6df7814a46fc4c9670cc510b72bb318.png


+ 149 - 0
talks/bayes_theorem/Bayes'_theorem Files/ajax.js

@@ -0,0 +1,149 @@
+// remote scripting library
+// (c) copyright 2005 modernmethod, inc
+var sajax_debug_mode = false;
+var sajax_request_type = "GET";
+
+/**
+* if sajax_debug_mode is true, this function outputs given the message into 
+* the element with id = sajax_debug; if no such element exists in the document, 
+* it is injected.
+*/
+function sajax_debug(text) {
+	if (!sajax_debug_mode) return false;
+
+	var e= document.getElementById('sajax_debug');
+
+	if (!e) {
+		e= document.createElement("p");
+		e.className= 'sajax_debug';
+		e.id= 'sajax_debug';
+
+		var b= document.getElementsByTagName("body")[0];
+
+		if (b.firstChild) b.insertBefore(e, b.firstChild);
+		else b.appendChild(e);
+	}
+
+	var m= document.createElement("div");
+	m.appendChild( document.createTextNode( text ) );
+
+	e.appendChild( m );
+
+	return true;
+}
+
+/**
+* compatibility wrapper for creating a new XMLHttpRequest object.
+*/
+function sajax_init_object() {
+	sajax_debug("sajax_init_object() called..")
+	var A;
+	try {
+		A=new ActiveXObject("Msxml2.XMLHTTP");
+	} catch (e) {
+		try {
+			A=new ActiveXObject("Microsoft.XMLHTTP");
+		} catch (oc) {
+			A=null;
+		}
+	}
+	if(!A && typeof XMLHttpRequest != "undefined")
+		A = new XMLHttpRequest();
+	if (!A)
+		sajax_debug("Could not create connection object.");
+
+	return A;
+}
+
+/**
+* Perform an ajax call to mediawiki. Calls are handeled by AjaxDispatcher.php
+*   func_name - the name of the function to call. Must be registered in $wgAjaxExportList
+*   args - an array of arguments to that function
+*   target - the target that will handle the result of the call. If this is a function,
+*            if will be called with the XMLHttpRequest as a parameter; if it's an input
+*            element, its value will be set to the resultText; if it's another type of
+*            element, its innerHTML will be set to the resultText.
+*
+* Example:
+*    sajax_do_call('doFoo', [1, 2, 3], document.getElementById("showFoo"));
+*
+* This will call the doFoo function via MediaWiki's AjaxDispatcher, with
+* (1, 2, 3) as the parameter list, and will show the result in the element
+* with id = showFoo
+*/
+function sajax_do_call(func_name, args, target) {
+	var i, x, n;
+	var uri;
+	var post_data;
+	uri = wgServer + wgScriptPath + "/index.php?action=ajax";
+	if (sajax_request_type == "GET") {
+		if (uri.indexOf("?") == -1)
+			uri = uri + "?rs=" + encodeURIComponent(func_name);
+		else
+			uri = uri + "&rs=" + encodeURIComponent(func_name);
+		for (i = 0; i < args.length; i++)
+			uri = uri + "&rsargs[]=" + encodeURIComponent(args[i]);
+		//uri = uri + "&rsrnd=" + new Date().getTime();
+		post_data = null;
+	} else {
+		post_data = "rs=" + encodeURIComponent(func_name);
+		for (i = 0; i < args.length; i++)
+			post_data = post_data + "&rsargs[]=" + encodeURIComponent(args[i]);
+	}
+	x = sajax_init_object();
+	if (!x) {
+		alert("AJAX not supported");
+		return false;
+	}
+
+	try {
+		x.open(sajax_request_type, uri, true);
+	} catch (e) {
+		if (window.location.hostname == "localhost") {
+			alert("Your browser blocks XMLHttpRequest to 'localhost', try using a real hostname for development/testing.");
+		}
+		throw e;
+	}
+	if (sajax_request_type == "POST") {
+		x.setRequestHeader("Method", "POST " + uri + " HTTP/1.1");
+		x.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+	}
+	x.setRequestHeader("Pragma", "cache=yes");
+	x.setRequestHeader("Cache-Control", "no-transform");
+	x.onreadystatechange = function() {
+		if (x.readyState != 4)
+			return;
+
+		sajax_debug("received (" + x.status + " " + x.statusText + ") " + x.responseText);
+
+		//if (x.status != 200)
+		//	alert("Error: " + x.status + " " + x.statusText + ": " + x.responseText);
+		//else
+
+		if ( typeof( target ) == 'function' ) {
+			target( x );
+		}
+		else if ( typeof( target ) == 'object' ) {
+			if ( target.tagName == 'INPUT' ) {
+				if (x.status == 200) target.value= x.responseText;
+				//else alert("Error: " + x.status + " " + x.statusText + " (" + x.responseText + ")");
+			}
+			else {
+				if (x.status == 200) target.innerHTML = x.responseText;
+				else target.innerHTML= "<div class='error'>Error: " + x.status + " " + x.statusText + " (" + x.responseText + ")</div>";
+			}
+		}
+		else {
+			alert("bad target for sajax_do_call: not a function or object: " + target);
+		}
+
+		return;
+	}
+
+	sajax_debug(func_name + " uri = " + uri + " / post = " + post_data);
+	x.send(post_data);
+	sajax_debug(func_name + " waiting..");
+	delete x;
+
+	return true;
+}

BIN
talks/bayes_theorem/Bayes'_theorem Files/b337da08b983c9e9c6f741d856b4b72c.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/b3866293e4211c8404d4e4a86bcf156d.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/b9474d45d82accf03434710c10871795.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/bcb58d4f262347072d35dea4a65977dd.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/c4451387126a4c6a51739382cacb9e99.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/c5932ce24d2e109050d0846a541a70b1.png


+ 290 - 0
talks/bayes_theorem/Bayes'_theorem Files/commonPrint.css

@@ -0,0 +1,290 @@
+/*
+** MediaWiki Print style sheet for CSS2-capable browsers.
+** Copyright Gabriel Wicke, http://www.aulinx.de/
+**
+** Derived from the plone (http://plone.org/) styles
+** Copyright Alexander Limi
+*/
+
+/* Thanks to A List Apart (http://alistapart.com/) for useful extras */
+a.stub,
+a.new{ color:#ba0000; text-decoration:none; }
+
+#toc { 
+    /*border:1px solid #2f6fab;*/
+    border:1px solid #aaaaaa;
+    background-color:#f9f9f9;
+    padding:5px;
+}
+.tocindent {
+	margin-left: 2em;
+}
+.tocline {
+	margin-bottom: 0px;
+}
+
+/* images */
+div.floatright { 
+    float: right;
+    clear: right;
+    margin: 0;
+    position:relative;
+    border: 0.5em solid White;
+    border-width: 0.5em 0 0.8em 1.4em;
+}
+div.floatright p { font-style: italic;} 
+div.floatleft { 
+    float: left; 
+    margin: 0.3em 0.5em 0.5em 0;
+    position:relative;
+    border: 0.5em solid White;
+    border-width: 0.5em 1.4em 0.8em 0;
+}
+div.floatleft p { font-style: italic; } 
+/* thumbnails */
+div.thumb {
+    margin-bottom: 0.5em;
+    border-style: solid; border-color: White;
+    width: auto;
+    overflow: hidden;
+}
+div.thumb div {
+    border:1px solid #cccccc;
+    padding: 3px !important;
+    background-color:#f9f9f9;
+    font-size: 94%;
+    text-align: center;
+}
+div.thumb div a img {
+    border:1px solid #cccccc;
+}
+div.thumb div div.thumbcaption {
+    border: none;
+    padding: 0.3em 0 0.1em 0;
+}
+div.magnify { display: none; }
+div.tright {
+    float: right;
+    clear: right;
+    border-width: 0.5em 0 0.8em 1.4em;
+}
+div.tleft {
+    float: left;
+    margin-right:0.5em;
+    border-width: 0.5em 1.4em 0.8em 0;
+}
+
+/* table standards */
+table.rimage {
+    float:right; 
+    width:1pt; 
+    position:relative;
+    margin-left:1em; 
+    margin-bottom:1em;
+    text-align:center;
+}
+
+body {
+    background: White;
+    /*font-size: 11pt !important;*/
+    color: Black;
+    margin: 0;
+    padding: 0;
+}
+
+.noprint,
+div#jump-to-nav,
+div.top,
+div#column-one,
+#colophon,
+.editsection,
+.toctoggle,
+.tochidden,
+div#f-poweredbyico,
+div#f-copyrightico,
+li#viewcount,
+li#about,
+li#disclaimer,
+li#privacy {
+    /* Hides all the elements irrelevant for printing */
+    display: none;
+}
+
+ul { 
+    list-style-type: square;
+}
+
+#content {
+    background: none;
+    border: none ! important;
+    padding: 0 ! important;
+    margin: 0 ! important;
+}
+#footer {
+	background : white;
+	color : black;
+    border-top: 1px solid black;
+}
+
+h1, h2, h3, h4, h5, h6
+{
+	font-weight: bold;
+}
+
+p, .documentDescription {
+    margin: 1em 0 ! important;
+    line-height: 1.2em;
+}
+
+.tocindent p {
+	margin: 0 0 0 0 ! important;
+}
+
+pre {
+    border: 1pt dashed black;
+    white-space: pre;
+    font-size: 8pt;
+    overflow: auto;
+    padding: 1em 0;
+	background : white;
+	color : black;
+}
+
+table.listing,
+table.listing td {
+    border: 1pt solid black;
+    border-collapse: collapse;
+}
+
+a {
+    color: Black !important;
+    background: none !important;
+    padding: 0 !important;
+}
+
+a:link, a:visited {
+    color: #520;
+    background: transparent;
+    text-decoration: underline;
+}
+
+#content a.external.text:after, #content a.external.autonumber:after {
+    /* Expand URLs for printing */
+    content: " (" attr(href) ") ";
+}
+
+#globalWrapper {
+    width: 100% !important;
+    min-width: 0 !important;
+}
+
+#content {
+	background : white;
+	color : black;
+}
+
+#column-content {
+    margin: 0 !important;
+}
+
+#column-content #content {
+    padding: 1em;
+    margin: 0 !important;
+}
+/* MSIE/Win doesn't understand 'inherit' */
+a, a.external, a.new, a.stub {
+	color: black ! important;
+	text-decoration: none ! important;
+}
+
+/* Continue ... */
+a, a.external, a.new, a.stub {
+	color: inherit ! important;
+	text-decoration: inherit ! important;
+}
+
+img { border: none; }
+img.tex { vertical-align: middle; }
+span.texhtml { font-family: serif; }
+
+div.townBox {
+    position:relative;
+    float:right;
+    background:White;
+    margin-left:1em;
+    border: 1px solid gray;
+    padding:0.3em;
+    width: 200px;
+    overflow: hidden;
+    clear: right;
+}
+div.townBox dl {
+    padding: 0;
+    margin: 0 0 0.3em 0; 
+    font-size: 96%;
+}
+div.townBox dl dt {
+    background: none;
+    margin: 0.4em 0 0 0;
+}
+div.townBox dl dd {
+    margin: 0.1em 0 0 1.1em;
+    background-color: #f3f3f3;
+}
+
+#siteNotice { display: none; }
+
+table.gallery {
+        border:  1px solid #cccccc;
+        margin:  2px;
+        padding: 2px;
+        background-color:#ffffff;
+}
+
+table.gallery tr { 
+        vertical-align:top;
+}
+
+div.gallerybox {
+        border: 1px solid #cccccc;
+        margin: 2px;
+        background-color:#f9f9f9;
+        width:  150px;
+}
+
+div.gallerybox div.thumb {
+        text-align: center;
+        border: 1px solid #cccccc;
+        margin: 2px;
+}       
+
+div.gallerytext {
+        font-size: 94%;
+        padding: 2px 4px;
+}       
+
+/*
+** Diff rendering
+*/
+table.diff { background:white; }
+td.diff-otitle { background:#ffffff; }
+td.diff-ntitle { background:#ffffff; }
+td.diff-addedline {
+    background:#ccffcc;
+    font-size: smaller;
+    border: solid 2px black;
+}
+td.diff-deletedline {
+    background:#ffffaa;
+    font-size: smaller;
+    border: dotted 2px black;
+}
+td.diff-context {
+    background:#eeeeee;
+    font-size: smaller;
+}
+.diffchange {
+    color: silver;
+    font-weight: bold;
+    text-decoration: underline;
+}

BIN
talks/bayes_theorem/Bayes'_theorem Files/daa4b03e8ea857a6957e4a1d0b8ef6b1.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/dc58f3b5ce30cd67b381fb124e19c384.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/df2df78c50ec6f12e78abfc669dad68e.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/efaf8fda8a92eeb2d8cf70468c20ed5a.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/f9c94dd8c87f868043f2ff054a4444b3.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/f9fdf7c75c7f4ddae1140dd2a9e73714.png


+ 1334 - 0
talks/bayes_theorem/Bayes'_theorem Files/handheld.css

@@ -0,0 +1,1334 @@
+/*
+** MediaWiki 'monobook' style sheet for CSS2-capable browsers.
+** Copyright Gabriel Wicke - http://wikidev.net/
+** License: GPL (http://www.gnu.org/copyleft/gpl.html)
+**
+** Loosely based on http://www.positioniseverything.net/ordered-floats.html by Big John
+** and the Plone 2.0 styles, see http://plone.org/ (Alexander Limi,Joe Geldart & Tom Croucher,
+** Michael Zeltner and Geir Bækholt)
+** All you guys rock :)
+*/
+
+/**
+ * Stylesheet for handhelds.  All rules not marked media-specific are shared
+ * with main.css and should be updated in tandem.  The rules can't be in the
+ * same file because old browsers like IE5 won't obey @media rules.
+ *
+ * Rules that are handheld-specific are given @media rules in case old browsers
+ * don't recognize the media attribute and load this file anyway.
+ */
+
+#content {
+	background: white;
+	color: black;
+	border: 1px solid #aaa;
+	border-right: none;
+	line-height: 1.5em;
+}
+/* the left column width is specified in class .portlet */
+
+/* Font size:
+** We take advantage of keyword scaling- browsers won't go below 9px
+** More at http://www.w3.org/2003/07/30-font-size
+** http://style.cleverchimp.com/font_size_intervals/altintervals.html
+*/
+
+body {
+	font: x-small sans-serif;
+	background: #f9f9f9 url(headbg.jpg) 0 0 no-repeat;
+	color: black;
+	margin: 0;
+	padding: 0;
+}
+
+/* scale back up to a sane default */
+#globalWrapper {
+	font-size: 127%;
+	width: 100%;
+	margin: 0;
+	padding: 0;
+}
+.visualClear {
+	clear: both;
+}
+
+/* general styles */
+
+table {
+	font-size: 100%;
+	color: black;
+	/* we don't want the bottom borders of <h2>s to be visible through
+	   floated tables */
+	background-color: white;
+}
+a {
+	text-decoration: none;
+	color: #002bb8;
+	background: none;
+}
+a:visited {
+	color: #5a3696;
+}
+a:active {
+	color: #faa700;
+}
+a:hover {
+	text-decoration: underline;
+}
+a.stub {
+	color: #772233;
+}
+a.new, #p-personal a.new {
+	color: #ba0000;
+}
+a.new:visited, #p-personal a.new:visited {
+	color: #a55858;
+}
+
+img {
+	border: none;
+	vertical-align: middle;
+}
+p img {
+	margin: 0;
+}
+
+hr {
+	height: 1px;
+	color: #aaa;
+	background-color: #aaa;
+	border: 0;
+	margin: .2em 0 .2em 0;
+}
+
+h1, h2, h3, h4, h5, h6 {
+	color: black;
+	background: none;
+	font-weight: normal;
+	margin: 0;
+	padding-top: .5em;
+	padding-bottom: .17em;
+	border-bottom: 1px solid #aaa;
+}
+h1 { font-size: 188%; }
+h1 .editsection { font-size: 53%; }
+h2 { font-size: 150%; }
+h2 .editsection { font-size: 67%; }
+h3, h4, h5, h6 {
+	border-bottom: none;
+	font-weight: bold;
+}
+h3 { font-size: 132%; }
+h3 .editsection { font-size: 76%; font-weight: normal; }
+h4 { font-size: 116%; }
+h4 .editsection { font-size: 86%; font-weight: normal; }
+h5 { font-size: 100%; }
+h5 .editsection { font-weight: normal; }
+h6 { font-size: 80%;  }
+h6 .editsection { font-size: 125%; font-weight: normal; }
+
+.editsection {
+	float: right;
+	margin-left: 5px;
+}
+
+ul {
+	line-height: 1.5em;
+	list-style-type: square;
+	margin: .3em 0 0 1.5em;
+	padding: 0;
+	list-style-image: url(bullet.gif);
+}
+ol {
+	line-height: 1.5em;
+	margin: .3em 0 0 3.2em;
+	padding: 0;
+	list-style-image: none;
+}
+li {
+	margin-bottom: .1em;
+}
+dt {
+	font-weight: bold;
+	margin-bottom: .1em;
+}
+dl {
+	margin-top: .2em;
+	margin-bottom: .5em;
+}
+dd {
+	line-height: 1.5em;
+	margin-left: 2em;
+	margin-bottom: .1em;
+}
+
+fieldset {
+	border: 1px solid #2f6fab;
+	margin: 1em 0 1em 0;
+	padding: 0 1em 1em;
+	line-height: 1.5em;
+}
+legend {
+	padding: .5em;
+	font-size: 95%;
+}
+form {
+	border: none;
+	margin: 0;
+}
+
+textarea {
+	width: 100%;
+	padding: .1em;
+}
+
+input.historysubmit {
+	padding: 0 .3em .3em .3em !important;
+	font-size: 94%;
+	cursor: pointer;
+	height: 1.7em !important;
+	margin-left: 1.6em;
+}
+select {
+	vertical-align: top;
+}
+abbr, acronym, .explain {
+	border-bottom: 1px dotted black;
+	color: black;
+	background: none;
+	cursor: help;
+}
+q {
+	font-family: Times, "Times New Roman", serif;
+	font-style: italic;
+}
+/* disabled for now
+blockquote {
+	font-family: Times, "Times New Roman", serif;
+	font-style: italic;
+}*/
+code {
+	background-color: #f9f9f9;
+}
+pre {
+	padding: 1em;
+	border: 1px dashed #2f6fab;
+	color: black;
+	background-color: #f9f9f9;
+	line-height: 1.1em;
+}
+
+/*
+** the main content area
+*/
+
+#contentSub, #contentSub2 {
+	font-size: 84%;
+	line-height: 1.2em;
+	margin: 0 0 1.4em 1em;
+	color: #7d7d7d;
+	width: auto;
+}
+span.subpages {
+	display: block;
+}
+
+/* Some space under the headers in the content area */
+#bodyContent h1, #bodyContent h2 {
+	margin-bottom: .6em;
+}
+#bodyContent h3, #bodyContent h4, #bodyContent h5 {
+	margin-bottom: .3em;
+}
+.firstHeading {
+	margin-bottom: .1em;
+}
+
+/* user notification thing */
+.usermessage {
+	background-color: #ffce7b;
+	border: 1px solid #ffa500;
+	color: black;
+	font-weight: bold;
+	margin: 2em 0 1em;
+	padding: .5em 1em;
+	vertical-align: middle;
+}
+#siteNotice {
+	text-align: center;
+	font-size: 95%;
+	padding: 0 .9em;
+}
+#siteNotice p {
+	margin: 0;
+	padding: 0;
+}
+.error {
+	color: red;
+	font-size: larger;
+}
+.errorbox, .successbox {
+	font-size: larger;
+	border: 2px solid;
+	padding: .5em 1em;
+	float: left;
+	margin-bottom: 2em;
+	color: #000;
+}
+.errorbox {
+	border-color: red;
+	background-color: #fff2f2;
+}
+.successbox {
+	border-color: green;
+	background-color: #dfd;
+}
+.errorbox h2, .successbox h2 {
+	font-size: 1em;
+	font-weight: bold;
+	display: inline;
+	margin: 0 .5em 0 0;
+	border: none;
+}
+
+#catlinks {
+	border: 1px solid #aaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	margin-top: 1em;
+	clear: both;
+}
+/* currently unused, intended to be used by a metadata box
+in the bottom-right corner of the content area */
+.documentDescription {
+	/* The summary text describing the document */
+	font-weight: bold;
+	display: block;
+	margin: 1em 0;
+	line-height: 1.5em;
+}
+.documentByLine {
+	text-align: right;
+	font-size: 90%;
+	clear: both;
+	font-weight: normal;
+	color: #76797c;
+}
+
+/* emulate center */
+.center {
+	width: 100%;
+	text-align: center;
+}
+*.center * {
+	margin-left: auto;
+	margin-right: auto;
+}
+/* small for tables and similar */
+.small, .small * {
+	font-size: 94%;
+}
+table.small {
+	font-size: 100%;
+}
+
+/*
+** content styles
+*/
+
+#toc,
+.toc,
+.mw-warning {
+	border: 1px solid #aaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	font-size: 95%;
+}
+#toc h2,
+.toc h2 {
+	display: inline;
+	border: none;
+	padding: 0;
+	font-size: 100%;
+	font-weight: bold;
+}
+#toc #toctitle,
+.toc #toctitle,
+#toc .toctitle,
+.toc .toctitle {
+	text-align: center;
+}
+#toc ul,
+.toc ul {
+	list-style-type: none;
+	list-style-image: none;
+	margin-left: 0;
+	padding-left: 0;
+	text-align: left;
+}
+#toc ul ul,
+.toc ul ul {
+	margin: 0 0 0 2em;
+}
+#toc .toctoggle,
+.toc .toctoggle {
+	font-size: 94%;
+}
+
+.mw-warning {
+	margin-left: 50px;
+	margin-right: 50px;
+	text-align: center;
+}
+
+/* images */
+div.floatright, table.floatright {
+	clear: right;
+	float: right;
+	position: relative;
+	margin: 0 0 .5em .5em;
+	border: 0;
+/*
+	border: .5em solid white;
+	border-width: .5em 0 .8em 1.4em;
+*/
+}
+div.floatright p { font-style: italic; }
+div.floatleft, table.floatleft {
+	float: left;
+	clear: left;
+	position: relative;
+	margin: 0 .5em .5em 0;
+	border: 0;
+/*
+	margin: .3em .5em .5em 0;
+	border: .5em solid white;
+	border-width: .5em 1.4em .8em 0;
+*/
+}
+div.floatleft p { font-style: italic; }
+/* thumbnails */
+div.thumb {
+	margin-bottom: .5em;
+	border-style: solid;
+	border-color: white;
+	width: auto;
+}
+div.thumbinner {
+	border: 1px solid #ccc;
+	padding: 3px !important;
+	background-color: #f9f9f9;
+	font-size: 94%;
+	text-align: center;
+	overflow: hidden;
+}
+html .thumbimage {
+	border: 1px solid #ccc;
+}
+html .thumbcaption {
+	border: none;
+	text-align: left;
+	line-height: 1.4em;
+	padding: 3px !important;
+	font-size: 94%;
+}
+div.magnify {
+	float: right;
+	border: none !important;
+	background: none !important;
+}
+div.magnify a, div.magnify img {
+	display: block;
+	border: none !important;
+	background: none !important;
+}
+div.tright {
+	clear: right;
+	float: right;
+	border-width: .5em 0 .8em 1.4em;
+}
+div.tleft {
+	float: left;
+	clear: left;
+	margin-right: .5em;
+	border-width: .5em 1.4em .8em 0;
+}
+
+.hiddenStructure {
+	display: none;
+	speak: none;
+}
+img.tex {
+	vertical-align: middle;
+}
+span.texhtml {
+	font-family: serif;
+}
+
+/*
+** classes for special content elements like town boxes
+** intended to be referenced directly from the wiki src
+*/
+
+/*
+** User styles
+*/
+/* table standards */
+table.rimage {
+	float: right;
+	position: relative;
+	margin-left: 1em;
+	margin-bottom: 1em;
+	text-align: center;
+}
+.toccolours {
+	border: 1px solid #aaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	font-size: 95%;
+}
+div.townBox {
+	position: relative;
+	float: right;
+	background: white;
+	margin-left: 1em;
+	border: 1px solid gray;
+	padding: .3em;
+	width: 200px;
+	overflow: hidden;
+	clear: right;
+}
+div.townBox dl {
+	padding: 0;
+	margin: 0 0 .3em;
+	font-size: 96%;
+}
+div.townBox dl dt {
+	background: none;
+	margin: .4em 0 0;
+}
+div.townBox dl dd {
+	margin: .1em 0 0 1.1em;
+	background-color: #f3f3f3;
+}
+
+/*
+** edit views etc
+*/
+.special li {
+	line-height: 1.4em;
+	margin: 0;
+	padding: 0;
+}
+
+/* Page history styling */
+/* the auto-generated edit comments */
+.autocomment {
+	color: gray;
+}
+#pagehistory span.user {
+	margin-left: 1.4em;
+	margin-right: .4em;
+}
+#pagehistory span.minor {
+	font-weight: bold;
+}
+#pagehistory li {
+	border: 1px solid white;
+}
+#pagehistory li.selected {
+	background-color: #f9f9f9;
+	border: 1px dashed #aaa;
+}
+
+/*
+** Diff rendering
+*/
+table.diff, td.diff-otitle, td.diff-ntitle {
+	background-color: white;
+}
+td.diff-addedline {
+	background: #cfc;
+	font-size: smaller;
+}
+td.diff-deletedline {
+	background: #ffa;
+	font-size: smaller;
+}
+td.diff-context {
+	background: #eee;
+	font-size: smaller;
+}
+.diffchange {
+	color: red;
+	font-weight: bold;
+}
+
+/*
+** keep the whitespace in front of the ^=, hides rule from konqueror
+** this is css3, the validator doesn't like it when validating as css2
+*/
+#bodyContent a.external,
+#bodyContent a[href ^="gopher://"] {
+	background: url(external.png) center right no-repeat;
+	padding-right: 13px;
+}
+#bodyContent a[href ^="https://"],
+.link-https {
+	background: url(lock_icon.gif) center right no-repeat;
+	padding-right: 16px;
+}
+#bodyContent a[href ^="mailto:"],
+.link-mailto {
+	background: url(mail_icon.gif) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a[href ^="news://"] {
+	background: url(news_icon.png) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a[href ^="ftp://"],
+.link-ftp {
+	background: url(file_icon.gif) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a[href ^="irc://"],
+.link-irc {
+	background: url(discussionitem_icon.gif) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a.external[href $=".ogg"], #bodyContent a.external[href $=".OGG"],
+#bodyContent a.external[href $=".mid"], #bodyContent a.external[href $=".MID"],
+#bodyContent a.external[href $=".midi"], #bodyContent a.external[href $=".MIDI"],
+#bodyContent a.external[href $=".mp3"], #bodyContent a.external[href $=".MP3"],
+#bodyContent a.external[href $=".wav"], #bodyContent a.external[href $=".WAV"],
+#bodyContent a.external[href $=".wma"], #bodyContent a.external[href $=".WMA"],
+.link-audio {
+	background: url("audio.png") center right no-repeat;
+	padding-right: 13px;
+}
+#bodyContent a.external[href $=".ogm"], #bodyContent a.external[href $=".OGM"],
+#bodyContent a.external[href $=".avi"], #bodyContent a.external[href $=".AVI"],
+#bodyContent a.external[href $=".mpeg"], #bodyContent a.external[href $=".MPEG"],
+#bodyContent a.external[href $=".mpg"], #bodyContent a.external[href $=".MPG"],
+.link-video {
+	background: url("video.png") center right no-repeat;
+	padding-right: 13px;
+}
+#bodyContent a.external[href $=".pdf"], #bodyContent a.external[href $=".PDF"],
+#bodyContent a.external[href *=".pdf#"], #bodyContent a.external[href *=".PDF#"],
+#bodyContent a.external[href *=".pdf?"], #bodyContent a.external[href *=".PDF?"],
+.link-document {
+	background: url("document.png") center right no-repeat;
+	padding-right: 12px;
+}
+
+/* disable interwiki styling */
+#bodyContent a.extiw,
+#bodyContent a.extiw:active {
+	color: #36b;
+	background: none;
+	padding: 0;
+}
+#bodyContent a.external {
+	color: #36b;
+}
+/* this can be used in the content area to switch off
+special external link styling */
+#bodyContent .plainlinks a {
+	background: none !important;
+	padding: 0 !important;
+}
+/*
+** Structural Elements
+*/
+
+/*
+** general portlet styles (elements in the quickbar)
+*/
+.portlet {
+	border: none;
+	margin: 0 0 .5em;
+	padding: 0;
+	float: none;
+	width: 11.6em;
+	overflow: hidden;
+}
+.portlet h4 {
+	font-size: 95%;
+	font-weight: normal;
+	white-space: nowrap;
+}
+.portlet h5 {
+	background: transparent;
+	padding: 0 1em 0 .5em;
+	display: inline;
+	height: 1em;
+	text-transform: lowercase;
+	font-size: 91%;
+	font-weight: normal;
+	white-space: nowrap;
+}
+.portlet h6 {
+	background: #ffae2e;
+	border: 1px solid #2f6fab;
+	border-style: solid solid none solid;
+	padding: 0 1em 0 1em;
+	text-transform: lowercase;
+	display: block;
+	font-size: 1em;
+	height: 1.2em;
+	font-weight: normal;
+	white-space: nowrap;
+}
+.pBody {
+	font-size: 95%;
+	background-color: white;
+	color: black;
+	border-collapse: collapse;
+	border: 1px solid #aaa;
+	padding: 0 .8em .3em .5em;
+}
+.portlet h1,
+.portlet h2,
+.portlet h3,
+.portlet h4 {
+	margin: 0;
+	padding: 0;
+}
+.portlet ul {
+	line-height: 1.5em;
+	list-style-type: square;
+	list-style-image: url(bullet.gif);
+	font-size: 95%;
+}
+.portlet li {
+	padding: 0;
+	margin: 0;
+}
+
+/*
+** Logo properties
+*/
+
+@media handheld {
+	#p-logo { display: none }
+}
+
+/*
+** the navigation portlet
+*/
+
+#p-navigation .pBody {
+	padding-right: 0;
+}
+
+#p-navigation li.active a, #p-navigation li.active a:hover {
+	text-decoration: none;
+	font-weight: bold;
+}
+
+
+/*
+** Search portlet
+*/
+input.searchButton {
+	margin-top: 1px;
+	font-size: 95%;
+}
+#searchGoButton {
+	padding-left: .5em;
+	padding-right: .5em;
+	font-weight: bold;
+}
+#searchInput {
+	width: 10.9em;
+	margin: 0;
+	font-size: 95%;
+}
+#p-search .pBody {
+	padding: .5em .4em .4em .4em;
+	text-align: center;
+}
+
+/*
+** the personal toolbar
+*/
+#p-personal ul {
+	text-transform: lowercase;
+}
+#p-personal li.active {
+	font-weight: bold;
+}
+/*
+** the page-related actions- page/talk, edit etc
+*/
+#p-cactions .hiddenStructure {
+	display: none;
+}
+#p-cactions li a {
+	text-transform: lowercase;
+}
+
+/* TODO: #t-iscite is only used by the Cite extension, come up with some
+ * system which allows extensions to add to this file on the fly
+ */
+#t-ispermalink, #t-iscite {
+	color: #999;
+}
+/*
+** footer
+*/
+#footer {
+	background-color: white;
+	border-top: 1px solid #fabd23;
+	border-bottom: 1px solid #fabd23;
+	margin: .6em 0 1em 0;
+	padding: .4em 0 1.2em 0;
+	text-align: center;
+	font-size: 90%;
+}
+#footer li {
+	display: inline;
+	margin: 0 1.3em;
+}
+/* hide from incapable browsers */
+head:first-child+body #footer li { white-space: nowrap; }
+#f-poweredbyico, #f-copyrightico {
+	margin: 0 8px;
+	position: relative;
+	top: -2px; /* Bump it up just a tad */
+}
+#f-poweredbyico {
+	float: right;
+	height: 1%;
+}
+#f-copyrightico {
+	float: left;
+	height: 1%;
+}
+
+/* js pref toc */
+#preftoc {
+	margin: 0;
+	padding: 0;
+	width: 100%;
+	clear: both;
+}
+#preftoc li {
+	background-color: #f0f0f0;
+	color: #000;
+}
+#preftoc li.selected {
+	font-weight: bold;
+	background-color: #f9f9f9;
+	border: 1px solid #aaa;
+	border-bottom: none;
+	cursor: default;
+	top: 1px;
+	padding-top: 2px;
+	margin-right: -3px;
+}
+#preftoc > li.selected {
+	top: 2px;
+}
+#preftoc a,
+#preftoc a:active {
+	display: block;
+	color: #000;
+	padding: 0 .7em;
+	position: relative;
+	text-decoration: none;
+}
+#preftoc li.selected a {
+	cursor: default;
+	text-decoration: none;
+}
+#prefcontrol {
+	padding-top: 2em;
+	clear: both;
+}
+#preferences {
+	margin: 0;
+	border: 1px solid #aaa;
+	clear: both;
+	padding: 1.5em;
+	background-color: #F9F9F9;
+}
+.prefsection {
+	border: none;
+	padding: 0;
+	margin: 0;
+}
+.prefsection fieldset {
+	border: 1px solid #aaa;
+	float: left;
+	margin-right: 2em;
+}
+.prefsection legend {
+	font-weight: bold;   
+}
+.prefsection table, .prefsection legend {
+	background-color: #F9F9F9;
+}
+div.prefsectiontip {
+	font-size: x-small;
+	padding: .2em 2em;
+}
+.btnSavePrefs {
+	font-weight: bold;
+	padding-left: .3em;
+	padding-right: .3em;
+}
+
+.preferences-login {
+	clear: both;
+	margin-bottom: 1.5em;
+}
+
+.prefcache {
+	font-size: 90%;
+	margin-top: 2em;
+}
+
+div#userloginForm form,
+div#userlogin form#userlogin2 {
+	margin: 0 3em 1em 0;
+	border: 1px solid #aaa;
+	clear: both;
+	padding: 1.5em 2em;
+	background-color: #f9f9f9;
+	float: left;
+}
+
+div#userloginForm table,
+div#userlogin form#userlogin2 table {
+	background-color: #f9f9f9;
+}
+
+div#userloginForm h2,
+div#userlogin form#userlogin2 h2 {
+	padding-top: 0;
+}
+
+div#userlogin .captcha {
+	border: 1px solid #bbb;
+	padding: 1.5em 2em;
+	background-color: white;
+}
+
+
+#userloginprompt, #languagelinks {
+	font-size: 85%;
+}
+
+#login-sectiontip {
+	font-size: 85%;
+	line-height: 1.2;
+	padding-top: 2em;
+}
+
+#userlogin .loginText, #userlogin .loginPassword {
+	width: 12em;
+}
+
+#userloginlink a, #wpLoginattempt, #wpCreateaccount {
+	font-weight: bold;
+}
+
+/* more IE fixes */
+/* float/negative margin brokenness */
+* html #footer {margin-top: 0;}
+* html #column-content {
+	display: inline;
+	margin-bottom: 0;
+}
+* html div.editsection { font-size: smaller; }
+#pagehistory li.selected { position: relative; }
+
+/* Mac IE 5.0 fix; floated content turns invisible */
+* > html #column-content {
+	float: none;
+}
+* > html #column-one {
+	position: absolute;
+	left: 0;
+	top: 0;
+}
+* > html #footer {
+	margin-left: 13.2em;
+}
+.redirectText {
+	font-size: 150%;
+	margin: 5px;
+}
+
+.printfooter {
+	display: none;
+}
+
+.not-patrolled {
+	background-color: #ffa;
+}
+div.patrollink {
+	font-size: 75%;
+	text-align: right;
+}
+span.newpage, span.minor, span.searchmatch, span.bot {
+	font-weight: bold;
+}
+span.unpatrolled {
+	font-weight: bold;
+	color: red;
+}
+
+span.searchmatch {
+	color: red;
+}
+.sharedUploadNotice {
+	font-style: italic;
+}
+
+span.updatedmarker {
+	color: black;
+	background-color: #0f0;
+}
+
+table.gallery {
+	border: 1px solid #ccc;
+	margin: 2px;
+	padding: 2px;
+	background-color: white;
+}
+
+table.gallery tr {
+	vertical-align: top;
+}
+
+table.gallery td {
+	vertical-align: top;
+	background-color: #f9f9f9;
+	border: solid 2px white;
+}
+
+/* Keep this temporarily so that cached pages will display right */
+table.gallery td.galleryheader {
+    text-align: center;
+	font-weight: bold;
+}
+table.gallery caption {
+	font-weight: bold;
+}
+
+div.gallerybox {
+	margin: 2px;
+	width:  150px;
+}
+
+div.gallerybox div.thumb {
+	text-align: center;
+	border: 1px solid #ccc;
+	margin: 2px;
+}
+
+div.gallerytext {
+	overflow: hidden;
+	font-size: 94%;
+	padding: 2px 4px;
+}
+
+span.comment {
+	font-style: italic;
+}
+
+span.changedby {
+	font-size: 95%;
+}
+
+.previewnote {
+	text-indent: 3em;
+	color: #c00;
+	border-bottom: 1px solid #aaa;
+	padding-bottom: 1em;
+	margin-bottom: 1em;
+}
+
+.previewnote p {
+	margin: 0;
+	padding: 0;
+}
+
+.editExternally {
+	border: 1px solid gray;
+	background-color: #ffffff;
+	padding: 3px;
+	margin-top: 0.5em;
+	float: left;
+	font-size: small;
+	text-align: center;
+}
+.editExternallyHelp {
+	font-style: italic;
+	color: gray;
+}
+
+li span.deleted, span.history-deleted {
+	text-decoration: line-through;
+	color: #888;
+	font-style: italic;
+}
+
+.toggle {
+	margin-left: 2em;
+	text-indent: -2em;
+}
+
+/* Classes for EXIF data display */
+table.mw_metadata {
+	font-size: 0.8em;
+	margin-left: 0.5em;
+	margin-bottom: 0.5em;
+	width: 300px;
+}
+
+table.mw_metadata caption {
+	font-weight: bold;
+}
+
+table.mw_metadata th {
+	font-weight: normal;
+}
+
+table.mw_metadata td {
+	padding: 0.1em;
+}
+
+table.mw_metadata {
+	border: none;
+	border-collapse: collapse;
+}
+
+table.mw_metadata td, table.mw_metadata th {
+	text-align: center;
+	border: 1px solid #aaaaaa;
+	padding-left: 0.1em;
+	padding-right: 0.1em;
+}
+
+table.mw_metadata th {
+	background-color: #f9f9f9;
+}
+
+table.mw_metadata td {
+	background-color: #fcfcfc;
+}
+
+table.collapsed tr.collapsable {
+	display: none;
+}
+
+
+/* filetoc */
+ul#filetoc {
+	text-align: center;
+	border: 1px solid #aaaaaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	font-size: 95%;
+	margin-bottom: 0.5em;
+	margin-left: 0;
+	margin-right: 0;
+}
+
+#filetoc li {
+	display: inline;
+	list-style-type: none;
+	padding-right: 2em;
+}
+
+input#wpSummary {
+	width: 80%;
+}
+
+/* @bug 1714 */
+input#wpSave, input#wpDiff {
+	margin-right: 0.33em;
+}
+
+#editform .editOptions {
+	display: inline;
+}
+
+#wpSave {
+	font-weight: bold;
+}
+
+/* Classes for article validation */
+
+table.revisionform_default {
+	border: 1px solid #000000;
+}
+
+table.revisionform_focus {
+	border: 1px solid #000000;
+	background-color:#00BBFF;
+}
+
+tr.revision_tr_default {
+	background-color:#EEEEEE;
+}
+
+tr.revision_tr_first {
+	background-color:#DDDDDD;
+}
+
+p.revision_saved {
+	color: green;
+	font-weight:bold;
+}
+
+#mw_trackbacks {
+	border: solid 1px #bbbbff;
+	background-color: #eeeeff;
+	padding: 0.2em;
+}
+
+
+/* Allmessages table */
+
+#allmessagestable th {
+	background-color: #b2b2ff;
+}
+
+#allmessagestable tr.orig {
+	background-color: #ffe2e2;
+}
+
+#allmessagestable tr.new {
+	background-color: #e2ffe2;
+}
+
+#allmessagestable tr.def {
+	background-color: #f0f0ff;
+}
+
+
+/* noarticletext */
+div.noarticletext {
+	border: 1px solid #ccc;
+	background: #fff;
+	padding: .2em 1em;
+	color: #000;
+}
+
+div#searchTargetContainer {
+	left:       10px;
+	top:        10px;
+	width:      90%;
+	background: white;
+}
+
+div#searchTarget {
+	padding:    3px;
+	margin:     5px;
+	background: #F0F0F0;
+	border:     solid 1px blue;
+}
+
+div#searchTarget ul li {
+	list-style: none;
+}
+
+div#searchTarget ul li:before {
+	color: orange;
+	content: "\00BB \0020";
+}
+
+div.multipageimagenavbox {
+   border: solid 1px silver;
+   padding: 4px;
+   margin: 1em;
+   -moz-border-radius: 6px;
+   background: #f0f0f0;
+}
+
+div.multipageimagenavbox div.thumb {
+   border: none;
+   margin-left: 2em;
+   margin-right: 2em;
+}
+
+div.multipageimagenavbox hr {
+   margin: 6px;
+}
+
+table.multipageimage td {
+   text-align: center;
+}
+
+/** Special:Version */
+
+table#sv-ext, table#sv-hooks {
+	margin: 1em;
+	padding:0em;
+}
+
+#sv-ext td, #sv-hooks td,
+#sv-ext th, #sv-hooks th {
+	border: 1px solid #A0A0A0;
+	padding: 0 0.15em 0 0.15em;
+}
+#sv-ext th, #sv-hooks th {
+	background-color: #F0F0F0;
+	color: black;
+	padding: 0 0.15em 0 0.15em;
+}
+tr.sv-space{
+	height: 0.8em;
+	border:none;
+}
+tr.sv-space td { display: none; }
+
+/*
+  Table pager (e.g. Special:Imagelist)
+  - remove underlines from the navigation link
+  - collapse borders
+  - set the borders to outsets (similar to Special:Allmessages)
+  - remove line wrapping for all td and th, set background color
+  - restore line wrapping for the last two table cells (description and size)
+*/
+.TablePager_nav a { text-decoration: none; }
+.TablePager { border-collapse: collapse; }
+.TablePager, .TablePager td, .TablePager th { 
+	border: 0.15em solid #777777;
+	padding: 0 0.15em 0 0.15em;
+}
+.TablePager th { background-color: #eeeeff }
+.TablePager td { background-color: #ffffff }
+.TablePager tr:hover td { background-color: #eeeeff }
+
+.imagelist td, .imagelist th { white-space: nowrap }
+.imagelist .TablePager_col_links { background-color: #eeeeff }
+.imagelist .TablePager_col_img_description { white-space: normal }
+.imagelist th.TablePager_sort { background-color: #ccccff }
+
+.templatesUsed { margin-top: 1.5em; }
+
+.mw-summary-preview {
+	margin: 0.1em 0;
+}
+@media handheld {
+	.nonessential {
+		/* Kill big bulky stuff that will clog up the screen */
+		display: none;
+	}
+}
+
+/**
+ * Here is some stuff that's ACTUALLY COMMON TO ALL SKINS.
+ * When the day comes, it can be moved to a *real* common.css.
+ */
+.mw-plusminus-null { color: #aaa; }
+.texvc { direction: ltr; unicode-bidi: embed; }
+/* Stop floats from intruding into edit area in previews */
+#toolbar, #wpTextbox1 { clear: both; }

+ 680 - 0
talks/bayes_theorem/Bayes'_theorem Files/index.php

@@ -0,0 +1,680 @@
+/* generated javascript */
+var skin = 'monobook';
+var stylepath = '/skins-1.5';
+
+/* MediaWiki:Common.js */
+/** Tooltips and access keys ***************************************************
+  *
+  *  Description: Adds tooltips and access keys to links part of the MediaWiki
+  *               interface.
+  *  Maintainers: [[User:Gwicke|Gwicke]]?, [[User:Simetrical|Simetrical]]?, [[User:Ruud Koot|Ruud Koot]]
+  */
+ 
+ ta = new Object();
+ ta["n-mainpage"]            = new Array("z","Visit the main page"); 
+ ta["n-Main-page"]           = new Array("z","Visit the main page"); 
+ ta["n-Featured-content"]    = new Array("","Featured content — the best of Wikipedia"); 
+ ta["n-help"]                = new Array("","The place to find out about Wikipedia"); 
+ ta["n-contact"]             = new Array("","How to contact Wikipedia");
+ ta["n-sitesupport"]         = new Array("","Help keep Wikipedia running"); 
+ ta["t-print"]               = new Array("","Printable version of this page");
+ ta["t-permalink"]           = new Array("","Permanent link to this version of the page");
+ ta["t-cite"]                = new Array("","Cite this Wikipedia article");
+ ta["ca-nstab-project"]      = new Array("c","View the project page");
+ ta["n-Contents"]            = new Array("","Guides to browsing Wikipedia"); 
+
+ /** Import module *************************************************************
+  *
+  *  Description: Includes a raw wiki page as javascript or CSS, 
+  *               used for including user made modules.
+  *  Maintainers: [[User:AzaToth]]
+  */
+ importedScripts = {}; // object keeping track of included scripts, so a script ain't included twice
+ function importScript( page ) {
+     if( importedScripts[page] ) {
+         return;
+     }
+     importedScripts[page] = true;
+     var url = wgScriptPath
+             + '/index.php?title='
+             + encodeURIComponent( page.replace( ' ', '_' ) )
+             + '&action=raw&ctype=text/javascript';
+     var scriptElem = document.createElement( 'script' );
+     scriptElem.setAttribute( 'src' , url );
+     scriptElem.setAttribute( 'type' , 'text/javascript' );
+     document.getElementsByTagName( 'head' )[0].appendChild( scriptElem );
+ }
+ 
+ function importStylesheet( page ) {
+     var sheet = '@import "'
+               + wgScriptPath
+               + '/index.php?title='
+               + encodeURIComponent( page.replace( ' ', '_' ) )
+               + '&action=raw&ctype=text/css";'
+     var styleElem = document.createElement( 'style' );
+     styleElem.setAttribute( 'type' , 'text/css' );
+     styleElem.appendChild( document.createTextNode( sheet ) );
+     document.getElementsByTagName( 'head' )[0].appendChild( styleElem );
+ }
+
+ /* Test if an element has a certain class **************************************
+  *
+  * Description: Uses regular expressions and caching for better performance.
+  * Maintainers: [[User:Mike Dillon]], [[User:R. Koot]], [[User:SG]]
+  */
+ 
+ var hasClass = (function () {
+     var reCache = {};
+     return function (element, className) {
+         return (reCache[className] ? reCache[className] : (reCache[className] = new RegExp("(?:\\s|^)" + className + "(?:\\s|$)"))).test(element.className);
+     };
+ })();
+
+ /** Internet Explorer bug fix **************************************************
+  *
+  *  Description: UNDOCUMENTED
+  *  Maintainers: [[User:Tom-]]?
+  */
+ 
+ if (window.showModalDialog && document.compatMode && document.compatMode == "CSS1Compat")
+ {
+   var oldWidth;
+   var docEl = document.documentElement;
+ 
+   function fixIEScroll()
+   {
+     if (!oldWidth || docEl.clientWidth > oldWidth)
+       doFixIEScroll();
+     else
+       setTimeout(doFixIEScroll, 1);
+   
+     oldWidth = docEl.clientWidth;
+   }
+ 
+   function doFixIEScroll() {
+     docEl.style.overflowX = (docEl.scrollWidth - docEl.clientWidth < 4) ? "hidden" : "";
+   }
+ 
+   document.attachEvent("onreadystatechange", fixIEScroll);
+   attachEvent("onresize", fixIEScroll);
+ }
+
+ /** Interwiki links to featured articles ***************************************
+  *
+  *  Description: Highlights interwiki links to featured articles (or
+  *               equivalents) by changing the bullet before the interwiki link
+  *               into a star.
+  *  Maintainers: [[User:R. Koot]]
+  */
+ 
+ function LinkFA() 
+ {
+     if ( document.getElementById( "p-lang" ) ) {
+         var InterwikiLinks = document.getElementById( "p-lang" ).getElementsByTagName( "li" );
+ 
+         for ( var i = 0; i < InterwikiLinks.length; i++ ) {
+             if ( document.getElementById( InterwikiLinks[i].className + "-fa" ) ) {
+                 InterwikiLinks[i].className += " FA"
+                 InterwikiLinks[i].title = "This is a featured article in another language.";
+             }
+         }
+     }
+ }
+ 
+ addOnloadHook( LinkFA );
+
+ /** Collapsible tables *********************************************************
+  *
+  *  Description: Allows tables to be collapsed, showing only the header. See
+  *               [[Wikipedia:NavFrame]].
+  *  Maintainers: [[User:R. Koot]]
+  */
+ 
+ var autoCollapse = 2;
+ var collapseCaption = "hide";
+ var expandCaption = "show";
+ 
+ function collapseTable( tableIndex )
+ {
+     var Button = document.getElementById( "collapseButton" + tableIndex );
+     var Table = document.getElementById( "collapsibleTable" + tableIndex );
+ 
+     if ( !Table || !Button ) {
+         return false;
+     }
+ 
+     var Rows = Table.getElementsByTagName( "tr" ); 
+ 
+     if ( Button.firstChild.data == collapseCaption ) {
+         for ( var i = 1; i < Rows.length; i++ ) {
+             Rows[i].style.display = "none";
+         }
+         Button.firstChild.data = expandCaption;
+     } else {
+         for ( var i = 1; i < Rows.length; i++ ) {
+             Rows[i].style.display = Rows[0].style.display;
+         }
+         Button.firstChild.data = collapseCaption;
+     }
+ }
+ 
+ function createCollapseButtons()
+ {
+     var tableIndex = 0;
+     var NavigationBoxes = new Object();
+     var Tables = document.getElementsByTagName( "table" );
+ 
+     for ( var i = 0; i < Tables.length; i++ ) {
+         if ( hasClass( Tables[i], "collapsible" ) ) {
+             NavigationBoxes[ tableIndex ] = Tables[i];
+             Tables[i].setAttribute( "id", "collapsibleTable" + tableIndex );
+ 
+             var Button     = document.createElement( "span" );
+             var ButtonLink = document.createElement( "a" );
+             var ButtonText = document.createTextNode( collapseCaption );
+ 
+             Button.style.styleFloat = "right";
+             Button.style.cssFloat = "right";
+             Button.style.fontWeight = "normal";
+             Button.style.textAlign = "right";
+             Button.style.width = "6em";
+ 
+             ButtonLink.setAttribute( "id", "collapseButton" + tableIndex );
+             ButtonLink.setAttribute( "href", "javascript:collapseTable(" + tableIndex + ");" );
+             ButtonLink.appendChild( ButtonText );
+ 
+             Button.appendChild( document.createTextNode( "[" ) );
+             Button.appendChild( ButtonLink );
+             Button.appendChild( document.createTextNode( "]" ) );
+ 
+             var Header = Tables[i].getElementsByTagName( "tr" )[0].getElementsByTagName( "th" )[0];
+             /* only add button and increment count if there is a header row to work with */
+             if (Header) {
+                 Header.insertBefore( Button, Header.childNodes[0] );
+                 tableIndex++;
+             }
+         }
+     }
+ 
+     for ( var i = 0;  i < tableIndex; i++ ) {
+         if ( hasClass( NavigationBoxes[i], "collapsed" ) || ( tableIndex >= autoCollapse && hasClass( NavigationBoxes[i], "autocollapse" ) ) ) {
+             collapseTable( i );
+         }
+     }
+ }
+ 
+ addOnloadHook( createCollapseButtons );
+
+ /** Dynamic Navigation Bars (experimental) *************************************
+  *
+  *  Description: See [[Wikipedia:NavFrame]].
+  *  Maintainers: UNMAINTAINED
+  */
+ 
+  // set up the words in your language
+  var NavigationBarHide = '[' + collapseCaption + ']';
+  var NavigationBarShow = '[' + expandCaption + ']';
+  
+  // set up max count of Navigation Bars on page,
+  // if there are more, all will be hidden
+  // NavigationBarShowDefault = 0; // all bars will be hidden
+  // NavigationBarShowDefault = 1; // on pages with more than 1 bar all bars will be hidden
+  var NavigationBarShowDefault = autoCollapse;
+  
+  
+  // shows and hides content and picture (if available) of navigation bars
+  // Parameters:
+  //     indexNavigationBar: the index of navigation bar to be toggled
+  function toggleNavigationBar(indexNavigationBar)
+  {
+     var NavToggle = document.getElementById("NavToggle" + indexNavigationBar);
+     var NavFrame = document.getElementById("NavFrame" + indexNavigationBar);
+  
+     if (!NavFrame || !NavToggle) {
+         return false;
+     }
+  
+     // if shown now
+     if (NavToggle.firstChild.data == NavigationBarHide) {
+         for (
+                 var NavChild = NavFrame.firstChild;
+                 NavChild != null;
+                 NavChild = NavChild.nextSibling
+             ) {
+             if ( hasClass( NavChild, 'NavPic' ) ) {
+                 NavChild.style.display = 'none';
+             }
+             if ( hasClass( NavChild, 'NavContent') ) {
+                 NavChild.style.display = 'none';
+             }
+         }
+     NavToggle.firstChild.data = NavigationBarShow;
+  
+     // if hidden now
+     } else if (NavToggle.firstChild.data == NavigationBarShow) {
+         for (
+                 var NavChild = NavFrame.firstChild;
+                 NavChild != null;
+                 NavChild = NavChild.nextSibling
+             ) {
+             if (hasClass(NavChild, 'NavPic')) {
+                 NavChild.style.display = 'block';
+             }
+             if (hasClass(NavChild, 'NavContent')) {
+                 NavChild.style.display = 'block';
+             }
+         }
+     NavToggle.firstChild.data = NavigationBarHide;
+     }
+  }
+  
+  // adds show/hide-button to navigation bars
+  function createNavigationBarToggleButton()
+  {
+     var indexNavigationBar = 0;
+     // iterate over all < div >-elements 
+     var divs = document.getElementsByTagName("div");
+     for(
+             var i=0; 
+             NavFrame = divs[i]; 
+             i++
+         ) {
+         // if found a navigation bar
+         if (hasClass(NavFrame, "NavFrame")) {
+  
+             indexNavigationBar++;
+             var NavToggle = document.createElement("a");
+             NavToggle.className = 'NavToggle';
+             NavToggle.setAttribute('id', 'NavToggle' + indexNavigationBar);
+             NavToggle.setAttribute('href', 'javascript:toggleNavigationBar(' + indexNavigationBar + ');');
+             
+             var NavToggleText = document.createTextNode(NavigationBarHide);
+             NavToggle.appendChild(NavToggleText);
+             // Find the NavHead and attach the toggle link (Must be this complicated because Moz's firstChild handling is borked)
+             for(
+               var j=0; 
+               j < NavFrame.childNodes.length; 
+               j++
+             ) {
+               if (hasClass(NavFrame.childNodes[j], "NavHead")) {
+                 NavFrame.childNodes[j].appendChild(NavToggle);
+               }
+             }
+             NavFrame.setAttribute('id', 'NavFrame' + indexNavigationBar);
+         }
+     }
+     // if more Navigation Bars found than Default: hide all
+     if (NavigationBarShowDefault < indexNavigationBar) {
+         for(
+                 var i=1; 
+                 i<=indexNavigationBar; 
+                 i++
+         ) {
+             toggleNavigationBar(i);
+         }
+     }
+   
+  }
+  
+  addOnloadHook( createNavigationBarToggleButton );
+
+ /** Main Page layout fixes *********************************************************
+  *
+  *  Description:        Various layout fixes for the main page, including an
+  *                      additional link to the complete list of languages available
+  *                      and the renaming of the 'Article' to to 'Main Page'.
+  *  Maintainers:        [[User:AzaToth]], [[User:R. Koot]]
+  */
+ 
+ function mainPageRenameNamespaceTab() {
+     try {
+         var Node = document.getElementById( 'ca-nstab-main' ).firstChild;
+         if ( Node.textContent ) {      // Per DOM Level 3
+             Node.textContent = 'Main Page';
+         } else if ( Node.innerText ) { // IE doesn't handle .textContent
+             Node.innerText = 'Main Page';
+         } else {                       // Fallback
+             Node.replaceChild( Node.firstChild, document.createTextNode( 'Main Page' ) ); 
+         }
+     } catch(e) {
+         // bailing out!
+     }
+ }
+ 
+ function mainPageAppendCompleteListLink() {
+     try {
+         var node = document.getElementById( "p-lang" )
+                            .getElementsByTagName('div')[0]
+                            .getElementsByTagName('ul')[0];
+ 
+         var aNode = document.createElement( 'a' );
+         var liNode = document.createElement( 'li' );
+ 
+         aNode.appendChild( document.createTextNode( 'Complete list' ) );
+         aNode.setAttribute( 'href' , 'http://meta.wikimedia.org/wiki/List_of_Wikipedias' );
+         liNode.appendChild( aNode );
+         liNode.className = 'interwiki-completelist';
+         node.appendChild( liNode );
+      } catch(e) {
+        // lets just ignore what's happened
+        return;
+     }
+ }
+ 
+ if ( wgTitle == 'Main Page' && ( wgNamespaceNumber == 0 || wgNamespaceNumber == 1 ) ) {
+        addOnloadHook( mainPageRenameNamespaceTab );
+ }
+ 
+ if ( wgTitle == 'Main Page' && wgNamespaceNumber == 0 ) {
+        addOnloadHook( mainPageAppendCompleteListLink );
+ }
+
+ /** Extra toolbar options ****************************************************** <nowiki>
+  *
+  *  Description: UNDOCUMENTED
+  *  Maintainers: [[User:MarkS]]?, [[User:Voice of All]], [[User:R. Koot]]
+  */
+ 
+ //This is a modified copy of a script by User:MarkS for extra features added by User:Voice of All.
+ // This is based on the original code on Wikipedia:Tools/Editing tools
+ // To disable this script, add <code>mwCustomEditButtons = [];<code> to [[Special:Mypage/monobook.js]]
+ 
+ if (mwCustomEditButtons) {
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/c/c8/Button_redirect.png",
+     "speedTip": "Redirect",
+     "tagOpen": "#REDIRECT [[",
+     "tagClose": "]]",
+     "sampleText": "Insert text"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/c/c9/Button_strike.png",
+     "speedTip": "Strike",
+     "tagOpen": "<s>",
+     "tagClose": "</s>",
+     "sampleText": "Strike-through text"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/1/13/Button_enter.png",
+     "speedTip": "Line break",
+     "tagOpen": "<br />",
+     "tagClose": "",
+     "sampleText": ""};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/8/80/Button_upper_letter.png",
+     "speedTip": "Superscript",
+     "tagOpen": "<sup>",
+     "tagClose": "</sup>",
+     "sampleText": "Superscript text"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/7/70/Button_lower_letter.png",
+     "speedTip": "Subscript",
+     "tagOpen": "<sub>",
+     "tagClose": "</sub>",
+     "sampleText": "Subscript text"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/5/58/Button_small.png",
+     "speedTip": "Small",
+     "tagOpen": "<small>",
+     "tagClose": "</small>",
+     "sampleText": "Small Text"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/3/34/Button_hide_comment.png",
+     "speedTip": "Insert hidden Comment",
+     "tagOpen": "<!-- ",
+     "tagClose": " -->",
+     "sampleText": "Comment"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/1/12/Button_gallery.png",
+     "speedTip": "Insert a picture gallery",
+     "tagOpen": "\n<gallery>\n",
+     "tagClose": "\n</gallery>",
+     "sampleText": "Image:Example.jpg|Caption1\nImage:Example.jpg|Caption2"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/f/fd/Button_blockquote.png",
+     "speedTip": "Insert block of quoted text",
+     "tagOpen": "<blockquote>\n",
+     "tagClose": "\n</blockquote>",
+     "sampleText": "Block quote"};
+ 
+   mwCustomEditButtons[mwCustomEditButtons.length] = {
+     "imageFile": "http://upload.wikimedia.org/wikipedia/en/6/60/Button_insert_table.png",
+     "speedTip": "Insert a table",
+     "tagOpen": '{| class="wikitable"\n|-\n',
+     "tagClose": "\n|}",
+     "sampleText": "! header 1\n! header 2\n! header 3\n|-\n| row 1, cell 1\n| row 1, cell 2\n| row 1, cell 3\n|-\n| row 2, cell 1\n| row 2, cell 2\n| row 2, cell 3"};
+ }
+ 
+ /*</nowiki>*/
+
+ /** pageview counter ***********************************************************
+  *
+  *  Description: Please talk to de:User:LeonWeber before changing anything or 
+  *               if there are any issues with that.
+  *  Maintainers: [[:de:User:LeonWeber]]?
+  */
+  
+  // this should be adjusted to a good value.
+  // BE CAREFUL, you will break zedler if it's too low!
+  // And then DaB. will kill Leon :-(
+  var disable_counter = 0;
+  var counter_factor = 6000;
+  
+  function pgcounter_setup()
+  {
+  	if(disable_counter == 0)
+  	{
+  		var url = window.location.href;
+  		if(Math.floor(Math.random()*counter_factor)==42)  // the probability thing
+   		{
+   			if(wgIsArticle==true || wgArticleId==0) // do not count history pages etc.
+  			{
+  				var pgcountNs = wgCanonicalNamespace;
+  				if(wgCanonicalNamespace=="")
+  				{
+  					pgcountNs = "0";
+  				}
+   				var cnt_url = "http://pgcount.wikimedia.de/index.png?ns=" + pgcountNs + "&title=" + encodeURI(wgTitle) + "&factor=" + counter_factor +"&wiki=enwiki";
+  				var img = new Image(); 
+  				img.src = cnt_url;
+  			}
+  		}
+  	}
+  }
+  // Do not use aOnloadFunctions[aOnloadFunctions.length] = pgcounter_setup;, some browsers don't like that.
+  pgcounter_setup();
+
+ /** "Technical restrictions" title fix *****************************************
+  *
+  *  Description:
+  *  Maintainers: [[User:Interiot]], [[User:Mets501]]
+  */
+ 
+ // For pages that have something like Template:Lowercase, replace the title, but only if it is cut-and-pasteable as a valid wikilink.
+ //	(for instance [[iPod]]'s title is updated.  <nowiki>But [[C#]] is not an equivalent wikilink, so [[C Sharp]] doesn't have its main title changed)</nowiki>
+ //
+ // The function looks for a banner like this: <nowiki>
+ // <div id="RealTitleBanner">    <!-- div that gets hidden -->
+ //   <span id="RealTitle">title</span>
+ // </div>
+ // </nowiki>An element with id=DisableRealTitle disables the function.
+ var disableRealTitle = 0;		// users can disable this by making this true from their monobook.js
+ if (wgIsArticle) {			// don't display the RealTitle when editing, since it is apparently inconsistent (doesn't show when editing sections, doesn't show when not previewing)
+     addOnloadHook(function() {
+ 	try {
+ 		var realTitleBanner = document.getElementById("RealTitleBanner");
+ 		if (realTitleBanner && !document.getElementById("DisableRealTitle") && !disableRealTitle) {
+ 			var realTitle = document.getElementById("RealTitle");
+ 			if (realTitle) {
+ 				var realTitleHTML = realTitle.innerHTML;
+ 				realTitleText = pickUpText(realTitle);
+ 
+ 				var isPasteable = 0;
+ 				//var containsHTML = /</.test(realTitleHTML);	// contains ANY HTML
+ 				var containsTooMuchHTML = /</.test( realTitleHTML.replace(/<\/?(sub|sup|small|big)>/gi, "") ); // contains HTML that will be ignored when cut-n-pasted as a wikilink
+ 				// calculate whether the title is pasteable
+ 				var verifyTitle = realTitleText.replace(/^ +/, "");		// trim left spaces
+ 				verifyTitle = verifyTitle.charAt(0).toUpperCase() + verifyTitle.substring(1, verifyTitle.length);	// uppercase first character
+ 
+ 				// if the namespace prefix is there, remove it on our verification copy.  If it isn't there, add it to the original realValue copy.
+ 				if (wgNamespaceNumber != 0) {
+ 					if (wgCanonicalNamespace == verifyTitle.substr(0, wgCanonicalNamespace.length).replace(/ /g, "_") && verifyTitle.charAt(wgCanonicalNamespace.length) == ":") {
+ 						verifyTitle = verifyTitle.substr(wgCanonicalNamespace.length + 1);
+ 					} else {
+ 						realTitleText = wgCanonicalNamespace.replace(/_/g, " ") + ":" + realTitleText;
+ 						realTitleHTML = wgCanonicalNamespace.replace(/_/g, " ") + ":" + realTitleHTML;
+ 					}
+ 				}
+ 
+ 				// verify whether wgTitle matches
+ 				verifyTitle = verifyTitle.replace(/^ +/, "").replace(/ +$/, "");		// trim left and right spaces
+ 				verifyTitle = verifyTitle.replace(/_/g, " ");		// underscores to spaces
+ 				verifyTitle = verifyTitle.charAt(0).toUpperCase() + verifyTitle.substring(1, verifyTitle.length);	// uppercase first character
+ 				isPasteable = (verifyTitle == wgTitle);
+ 
+ 				var h1 = document.getElementsByTagName("h1")[0];
+ 				if (h1 && isPasteable) {
+ 					h1.innerHTML = containsTooMuchHTML ? realTitleText : realTitleHTML;
+ 					if (!containsTooMuchHTML)
+ 						realTitleBanner.style.display = "none";
+ 				}
+ 				document.title = realTitleText + " - Wikipedia, the free encyclopedia";
+ 			}
+ 		}
+ 	} catch (e) {
+ 		/* Something went wrong. */
+ 	}
+     });
+ }
+ 
+ 
+ // similar to innerHTML, but only returns the text portions of the insides, excludes HTML
+ function pickUpText(aParentElement) {
+   var str = "";
+ 
+   function pickUpTextInternal(aElement) {
+     var child = aElement.firstChild;
+     while (child) {
+       if (child.nodeType == 1)		// ELEMENT_NODE 
+         pickUpTextInternal(child);
+       else if (child.nodeType == 3)	// TEXT_NODE
+         str += child.nodeValue;
+ 
+       child = child.nextSibling;
+     }
+   }
+ 
+   pickUpTextInternal(aParentElement);
+ 
+   return str;
+ }
+
+ //fix edit summary prompt for undo
+ //this code fixes the fact that the undo function combined with the "no edit summary prompter" causes problems if leaving the
+ //edit summary unchanged
+ //this was added by [[User:Deskana]], code by [[User:Tra]]
+ addOnloadHook(function () {
+   if (document.location.search.indexOf("undo=") != -1
+   && document.getElementsByName('wpAutoSummary')[0]) {
+     document.getElementsByName('wpAutoSummary')[0].value='';
+   }
+ })
+
+ /** Add dismiss button to watchlist-message *************************************
+  *
+  *  Description: Hide the watchlist message for one week.
+  *  Maintainers: [[User:Ruud Koot|Ruud Koot]]
+  */
+ 
+ function addDismissButton() {
+    var watchlistMessage = document.getElementById("watchlist-message");
+    if ( watchlistMessage == null ) return;
+ 
+    if ( document.cookie.indexOf( "hidewatchlistmessage=yes" ) != -1 ) {
+        watchlistMessage.style.display = "none";
+    }
+ 
+    var Button     = document.createElement( "span" );
+    var ButtonLink = document.createElement( "a" );
+    var ButtonText = document.createTextNode( "dismiss" );
+ 
+    ButtonLink.setAttribute( "id", "dismissButton" );
+    ButtonLink.setAttribute( "href", "javascript:dismissWatchlistMessage();" );
+    ButtonLink.setAttribute( "title", "Hide this message for one week" );
+    ButtonLink.appendChild( ButtonText );
+ 
+    Button.appendChild( document.createTextNode( "[" ) );
+    Button.appendChild( ButtonLink );
+    Button.appendChild( document.createTextNode( "]" ) );
+ 
+    watchlistMessage.appendChild( Button );
+ }
+ 
+ function dismissWatchlistMessage() {
+     var e = new Date();
+     e.setTime( e.getTime() + (7*24*60*60*1000) );
+     document.cookie = "hidewatchlistmessage=yes; expires=" + e.toGMTString() + "; path=/";
+     var watchlistMessage = document.getElementById("watchlist-message");
+     watchlistMessage.style.display = "none";
+ }
+ 
+ addOnloadHook( addDismissButton );
+
+ /** Numeric sorting ***************************************************
+  *
+  *  Description: Fixes a bug (part of [[bugzilla:8115]])
+  *  in http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/skins/common/wikibits.js
+  *  regarding [[Help:Sorting|table sorting]]:
+  *  it allows sorting of numbers with more than one comma (thousands separator).
+  *  Maintainer: [[User:Patrick|Patrick]]
+  */
+ function ts_parseFloat(num) {
+ 	if (!num) return 0;
+ 	num = parseFloat(num.replace(/,/g, ""));
+ 	return (isNaN(num) ? 0 : num);
+ }
+
+
+ /** Main Page deletion image *******************************************************
+   *
+   *  Description: If the Main Page does not exist (i.e. it's been deleted) then insert an image
+   *               instead of showing the "page does not exist" text.
+   *  Created by: [[User:Mark]], with invaluable help from [[User:Pathoschild]]
+   */
+ 
+ function MainPageDeletedImage() {
+   try {
+ 
+     //If the article does not exist and it is the Main Page, proceed
+     if ( document.getElementById( "noarticletext" ) && wgTitle == 'Main Page' ) {
+ 
+       // Insert a protected commons image at the end of the document explaining it.
+       var contentbox = document.getElementById('content');
+       var newimg = document.createElement('img');
+       newimg.setAttribute('src','http://upload.wikimedia.org/wikipedia/commons/9/99/WikipediaTechnical.png');
+       contentbox.appendChild(newimg);
+ 
+       // Hide the article-does-not-exist text
+       var NoArticleMessage = document.getElementById('noarticletext');
+       NoArticleMessage.style.display="none";
+ 
+       // Hide the edit button
+       var EditThisPageButton = document.getElementById('ca-edit');
+       EditThisPageButton.style.display="none";
+     }
+   } catch(e) {
+       // In case it does not work, do nothing
+       return;
+   }
+ }
+ 
+ addOnloadHook( MainPageDeletedImage );
+
+/* MediaWiki:Monobook.js (deprecated; migrate to Common.js!) */
+/* Deprecated by [[MediaWiki:Common.js]] */

+ 1637 - 0
talks/bayes_theorem/Bayes'_theorem Files/main.css

@@ -0,0 +1,1637 @@
+/*
+** MediaWiki 'monobook' style sheet for CSS2-capable browsers.
+** Copyright Gabriel Wicke - http://wikidev.net/
+** License: GPL (http://www.gnu.org/copyleft/gpl.html)
+**
+** Loosely based on http://www.positioniseverything.net/ordered-floats.html by Big John
+** and the Plone 2.0 styles, see http://plone.org/ (Alexander Limi,Joe Geldart & Tom Croucher,
+** Michael Zeltner and Geir Bækholt)
+** All you guys rock :)
+*/
+
+/**
+ * Stylesheet for screen/projection.  All rules not marked media-specific are
+ * shared with handheld.css and should be updated in tandem.  The rules can't
+ * be in the same file because old browsers like IE5 won't obey @media rules.
+ *
+ * Rules that are screen/projection-specific are marked with commented-out
+ * @media rules and indentation.
+ */
+
+/* @media screen, projection { */
+	#column-content {
+		width: 100%;
+		float: right;
+		margin: 0 0 .6em -12.2em;
+		padding: 0;
+	}
+	#content {
+		margin: 2.8em 0 0 12.2em;
+		padding: 0 1em 1.5em 1em;
+		position: relative;
+		z-index: 2;
+	}
+	#column-one {
+		padding-top: 160px;
+	}
+/* } */
+#content {
+	background: white;
+	color: black;
+	border: 1px solid #aaa;
+	border-right: none;
+	line-height: 1.5em;
+}
+/* the left column width is specified in class .portlet */
+
+/* Font size:
+** We take advantage of keyword scaling- browsers won't go below 9px
+** More at http://www.w3.org/2003/07/30-font-size
+** http://style.cleverchimp.com/font_size_intervals/altintervals.html
+*/
+
+body {
+	font: x-small sans-serif;
+	background: #f9f9f9 url(headbg.jpg) 0 0 no-repeat;
+	color: black;
+	margin: 0;
+	padding: 0;
+}
+
+/* scale back up to a sane default */
+#globalWrapper {
+	font-size: 127%;
+	width: 100%;
+	margin: 0;
+	padding: 0;
+}
+.visualClear {
+	clear: both;
+}
+
+/* general styles */
+
+table {
+	font-size: 100%;
+	color: black;
+	/* we don't want the bottom borders of <h2>s to be visible through
+	   floated tables */
+	background-color: white;
+}
+a {
+	text-decoration: none;
+	color: #002bb8;
+	background: none;
+}
+a:visited {
+	color: #5a3696;
+}
+a:active {
+	color: #faa700;
+}
+a:hover {
+	text-decoration: underline;
+}
+a.stub {
+	color: #772233;
+}
+a.new, #p-personal a.new {
+	color: #ba0000;
+}
+a.new:visited, #p-personal a.new:visited {
+	color: #a55858;
+}
+
+img {
+	border: none;
+	vertical-align: middle;
+}
+/* @media screen, projection { */
+	p {
+		margin: .4em 0 .5em 0;
+		line-height: 1.5em;
+	}
+/* } */
+p img {
+	margin: 0;
+}
+
+hr {
+	height: 1px;
+	color: #aaa;
+	background-color: #aaa;
+	border: 0;
+	margin: .2em 0 .2em 0;
+}
+
+h1, h2, h3, h4, h5, h6 {
+	color: black;
+	background: none;
+	font-weight: normal;
+	margin: 0;
+	padding-top: .5em;
+	padding-bottom: .17em;
+	border-bottom: 1px solid #aaa;
+}
+h1 { font-size: 188%; }
+h1 .editsection { font-size: 53%; }
+h2 { font-size: 150%; }
+h2 .editsection { font-size: 67%; }
+h3, h4, h5, h6 {
+	border-bottom: none;
+	font-weight: bold;
+}
+h3 { font-size: 132%; }
+h3 .editsection { font-size: 76%; font-weight: normal; }
+h4 { font-size: 116%; }
+h4 .editsection { font-size: 86%; font-weight: normal; }
+h5 { font-size: 100%; }
+h5 .editsection { font-weight: normal; }
+h6 { font-size: 80%;  }
+h6 .editsection { font-size: 125%; font-weight: normal; }
+
+.editsection {
+	float: right;
+	margin-left: 5px;
+}
+
+ul {
+	line-height: 1.5em;
+	list-style-type: square;
+	margin: .3em 0 0 1.5em;
+	padding: 0;
+	list-style-image: url(bullet.gif);
+}
+ol {
+	line-height: 1.5em;
+	margin: .3em 0 0 3.2em;
+	padding: 0;
+	list-style-image: none;
+}
+li {
+	margin-bottom: .1em;
+}
+dt {
+	font-weight: bold;
+	margin-bottom: .1em;
+}
+dl {
+	margin-top: .2em;
+	margin-bottom: .5em;
+}
+dd {
+	line-height: 1.5em;
+	margin-left: 2em;
+	margin-bottom: .1em;
+}
+
+fieldset {
+	border: 1px solid #2f6fab;
+	margin: 1em 0 1em 0;
+	padding: 0 1em 1em;
+	line-height: 1.5em;
+}
+legend {
+	padding: .5em;
+	font-size: 95%;
+}
+form {
+	border: none;
+	margin: 0;
+}
+
+textarea {
+	width: 100%;
+	padding: .1em;
+}
+
+input.historysubmit {
+	padding: 0 .3em .3em .3em !important;
+	font-size: 94%;
+	cursor: pointer;
+	height: 1.7em !important;
+	margin-left: 1.6em;
+}
+select {
+	vertical-align: top;
+}
+abbr, acronym, .explain {
+	border-bottom: 1px dotted black;
+	color: black;
+	background: none;
+	cursor: help;
+}
+q {
+	font-family: Times, "Times New Roman", serif;
+	font-style: italic;
+}
+/* disabled for now
+blockquote {
+	font-family: Times, "Times New Roman", serif;
+	font-style: italic;
+}*/
+code {
+	background-color: #f9f9f9;
+}
+pre {
+	padding: 1em;
+	border: 1px dashed #2f6fab;
+	color: black;
+	background-color: #f9f9f9;
+	line-height: 1.1em;
+}
+
+/*
+** the main content area
+*/
+
+/* @media screen, projection { */
+	#siteSub {
+		display: none;
+	}
+	#jump-to-nav {
+		display: none;
+	}
+/* } */
+
+#contentSub, #contentSub2 {
+	font-size: 84%;
+	line-height: 1.2em;
+	margin: 0 0 1.4em 1em;
+	color: #7d7d7d;
+	width: auto;
+}
+span.subpages {
+	display: block;
+}
+
+/* Some space under the headers in the content area */
+#bodyContent h1, #bodyContent h2 {
+	margin-bottom: .6em;
+}
+#bodyContent h3, #bodyContent h4, #bodyContent h5 {
+	margin-bottom: .3em;
+}
+.firstHeading {
+	margin-bottom: .1em;
+}
+
+/* user notification thing */
+.usermessage {
+	background-color: #ffce7b;
+	border: 1px solid #ffa500;
+	color: black;
+	font-weight: bold;
+	margin: 2em 0 1em;
+	padding: .5em 1em;
+	vertical-align: middle;
+}
+#siteNotice {
+	text-align: center;
+	font-size: 95%;
+	padding: 0 .9em;
+}
+#siteNotice p {
+	margin: 0;
+	padding: 0;
+}
+.error {
+	color: red;
+	font-size: larger;
+}
+.errorbox, .successbox {
+	font-size: larger;
+	border: 2px solid;
+	padding: .5em 1em;
+	float: left;
+	margin-bottom: 2em;
+	color: #000;
+}
+.errorbox {
+	border-color: red;
+	background-color: #fff2f2;
+}
+.successbox {
+	border-color: green;
+	background-color: #dfd;
+}
+.errorbox h2, .successbox h2 {
+	font-size: 1em;
+	font-weight: bold;
+	display: inline;
+	margin: 0 .5em 0 0;
+	border: none;
+}
+
+#catlinks {
+	border: 1px solid #aaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	margin-top: 1em;
+	clear: both;
+}
+/* currently unused, intended to be used by a metadata box
+in the bottom-right corner of the content area */
+.documentDescription {
+	/* The summary text describing the document */
+	font-weight: bold;
+	display: block;
+	margin: 1em 0;
+	line-height: 1.5em;
+}
+.documentByLine {
+	text-align: right;
+	font-size: 90%;
+	clear: both;
+	font-weight: normal;
+	color: #76797c;
+}
+
+/* emulate center */
+.center {
+	width: 100%;
+	text-align: center;
+}
+*.center * {
+	margin-left: auto;
+	margin-right: auto;
+}
+/* small for tables and similar */
+.small, .small * {
+	font-size: 94%;
+}
+table.small {
+	font-size: 100%;
+}
+
+/*
+** content styles
+*/
+
+#toc,
+.toc,
+.mw-warning {
+	border: 1px solid #aaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	font-size: 95%;
+}
+#toc h2,
+.toc h2 {
+	display: inline;
+	border: none;
+	padding: 0;
+	font-size: 100%;
+	font-weight: bold;
+}
+#toc #toctitle,
+.toc #toctitle,
+#toc .toctitle,
+.toc .toctitle {
+	text-align: center;
+}
+#toc ul,
+.toc ul {
+	list-style-type: none;
+	list-style-image: none;
+	margin-left: 0;
+	padding-left: 0;
+	text-align: left;
+}
+#toc ul ul,
+.toc ul ul {
+	margin: 0 0 0 2em;
+}
+#toc .toctoggle,
+.toc .toctoggle {
+	font-size: 94%;
+}
+
+.mw-warning {
+	margin-left: 50px;
+	margin-right: 50px;
+	text-align: center;
+}
+
+/* images */
+div.floatright, table.floatright {
+	clear: right;
+	float: right;
+	position: relative;
+	margin: 0 0 .5em .5em;
+	border: 0;
+/*
+	border: .5em solid white;
+	border-width: .5em 0 .8em 1.4em;
+*/
+}
+div.floatright p { font-style: italic; }
+div.floatleft, table.floatleft {
+	float: left;
+	clear: left;
+	position: relative;
+	margin: 0 .5em .5em 0;
+	border: 0;
+/*
+	margin: .3em .5em .5em 0;
+	border: .5em solid white;
+	border-width: .5em 1.4em .8em 0;
+*/
+}
+div.floatleft p { font-style: italic; }
+/* thumbnails */
+div.thumb {
+	margin-bottom: .5em;
+	border-style: solid;
+	border-color: white;
+	width: auto;
+}
+div.thumbinner {
+	border: 1px solid #ccc;
+	padding: 3px !important;
+	background-color: #f9f9f9;
+	font-size: 94%;
+	text-align: center;
+	overflow: hidden;
+}
+html .thumbimage {
+	border: 1px solid #ccc;
+}
+html .thumbcaption {
+	border: none;
+	text-align: left;
+	line-height: 1.4em;
+	padding: 3px !important;
+	font-size: 94%;
+}
+div.magnify {
+	float: right;
+	border: none !important;
+	background: none !important;
+}
+div.magnify a, div.magnify img {
+	display: block;
+	border: none !important;
+	background: none !important;
+}
+div.tright {
+	clear: right;
+	float: right;
+	border-width: .5em 0 .8em 1.4em;
+}
+div.tleft {
+	float: left;
+	clear: left;
+	margin-right: .5em;
+	border-width: .5em 1.4em .8em 0;
+}
+
+.hiddenStructure {
+	display: none;
+	speak: none;
+}
+img.tex {
+	vertical-align: middle;
+}
+span.texhtml {
+	font-family: serif;
+}
+
+/* Have a checkered background on images on the description pages and in galleries
+   to make transparency visible
+
+#file img, .gallerybox .thumb img {
+	background: url(Checker-16x16.png) repeat;
+}
+*/
+
+/*
+** classes for special content elements like town boxes
+** intended to be referenced directly from the wiki src
+*/
+
+/*
+** User styles
+*/
+/* table standards */
+table.rimage {
+	float: right;
+	position: relative;
+	margin-left: 1em;
+	margin-bottom: 1em;
+	text-align: center;
+}
+.toccolours {
+	border: 1px solid #aaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	font-size: 95%;
+}
+div.townBox {
+	position: relative;
+	float: right;
+	background: white;
+	margin-left: 1em;
+	border: 1px solid gray;
+	padding: .3em;
+	width: 200px;
+	overflow: hidden;
+	clear: right;
+}
+div.townBox dl {
+	padding: 0;
+	margin: 0 0 .3em;
+	font-size: 96%;
+}
+div.townBox dl dt {
+	background: none;
+	margin: .4em 0 0;
+}
+div.townBox dl dd {
+	margin: .1em 0 0 1.1em;
+	background-color: #f3f3f3;
+}
+
+/*
+** edit views etc
+*/
+.special li {
+	line-height: 1.4em;
+	margin: 0;
+	padding: 0;
+}
+
+/* Page history styling */
+/* the auto-generated edit comments */
+.autocomment {
+	color: gray;
+}
+#pagehistory span.user {
+	margin-left: 1.4em;
+	margin-right: .4em;
+}
+#pagehistory span.minor {
+	font-weight: bold;
+}
+#pagehistory li {
+	border: 1px solid white;
+}
+#pagehistory li.selected {
+	background-color: #f9f9f9;
+	border: 1px dashed #aaa;
+}
+
+/*
+** Diff rendering
+*/
+table.diff, td.diff-otitle, td.diff-ntitle {
+	background-color: white;
+}
+td.diff-addedline {
+	background: #cfc;
+	font-size: smaller;
+}
+td.diff-deletedline {
+	background: #ffa;
+	font-size: smaller;
+}
+td.diff-context {
+	background: #eee;
+	font-size: smaller;
+}
+.diffchange {
+	color: red;
+	font-weight: bold;
+	text-decoration: none;
+}
+
+/*
+** keep the whitespace in front of the ^=, hides rule from konqueror
+** this is css3, the validator doesn't like it when validating as css2
+*/
+#bodyContent a.external,
+#bodyContent a[href ^="gopher://"] {
+	background: url(external.png) center right no-repeat;
+	padding-right: 13px;
+}
+#bodyContent a[href ^="https://"],
+.link-https {
+	background: url(lock_icon.gif) center right no-repeat;
+	padding-right: 16px;
+}
+#bodyContent a[href ^="mailto:"],
+.link-mailto {
+	background: url(mail_icon.gif) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a[href ^="news://"] {
+	background: url(news_icon.png) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a[href ^="ftp://"],
+.link-ftp {
+	background: url(file_icon.gif) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a[href ^="irc://"],
+.link-irc {
+	background: url(discussionitem_icon.gif) center right no-repeat;
+	padding-right: 18px;
+}
+#bodyContent a.external[href $=".ogg"], #bodyContent a.external[href $=".OGG"],
+#bodyContent a.external[href $=".mid"], #bodyContent a.external[href $=".MID"],
+#bodyContent a.external[href $=".midi"], #bodyContent a.external[href $=".MIDI"],
+#bodyContent a.external[href $=".mp3"], #bodyContent a.external[href $=".MP3"],
+#bodyContent a.external[href $=".wav"], #bodyContent a.external[href $=".WAV"],
+#bodyContent a.external[href $=".wma"], #bodyContent a.external[href $=".WMA"],
+.link-audio {
+	background: url("audio.png") center right no-repeat;
+	padding-right: 13px;
+}
+#bodyContent a.external[href $=".ogm"], #bodyContent a.external[href $=".OGM"],
+#bodyContent a.external[href $=".avi"], #bodyContent a.external[href $=".AVI"],
+#bodyContent a.external[href $=".mpeg"], #bodyContent a.external[href $=".MPEG"],
+#bodyContent a.external[href $=".mpg"], #bodyContent a.external[href $=".MPG"],
+.link-video {
+	background: url("video.png") center right no-repeat;
+	padding-right: 13px;
+}
+#bodyContent a.external[href $=".pdf"], #bodyContent a.external[href $=".PDF"],
+#bodyContent a.external[href *=".pdf#"], #bodyContent a.external[href *=".PDF#"],
+#bodyContent a.external[href *=".pdf?"], #bodyContent a.external[href *=".PDF?"],
+.link-document {
+	background: url("document.png") center right no-repeat;
+	padding-right: 12px;
+}
+
+/* disable interwiki styling */
+#bodyContent a.extiw,
+#bodyContent a.extiw:active {
+	color: #36b;
+	background: none;
+	padding: 0;
+}
+#bodyContent a.external {
+	color: #36b;
+}
+/* this can be used in the content area to switch off
+special external link styling */
+#bodyContent .plainlinks a {
+	background: none !important;
+	padding: 0 !important;
+}
+/*
+** Structural Elements
+*/
+
+/*
+** general portlet styles (elements in the quickbar)
+*/
+.portlet {
+	border: none;
+	margin: 0 0 .5em;
+	padding: 0;
+	float: none;
+	width: 11.6em;
+	overflow: hidden;
+}
+.portlet h4 {
+	font-size: 95%;
+	font-weight: normal;
+	white-space: nowrap;
+}
+.portlet h5 {
+	background: transparent;
+	padding: 0 1em 0 .5em;
+	display: inline;
+	height: 1em;
+	text-transform: lowercase;
+	font-size: 91%;
+	font-weight: normal;
+	white-space: nowrap;
+}
+.portlet h6 {
+	background: #ffae2e;
+	border: 1px solid #2f6fab;
+	border-style: solid solid none solid;
+	padding: 0 1em 0 1em;
+	text-transform: lowercase;
+	display: block;
+	font-size: 1em;
+	height: 1.2em;
+	font-weight: normal;
+	white-space: nowrap;
+}
+.pBody {
+	font-size: 95%;
+	background-color: white;
+	color: black;
+	border-collapse: collapse;
+	border: 1px solid #aaa;
+	padding: 0 .8em .3em .5em;
+}
+.portlet h1,
+.portlet h2,
+.portlet h3,
+.portlet h4 {
+	margin: 0;
+	padding: 0;
+}
+.portlet ul {
+	line-height: 1.5em;
+	list-style-type: square;
+	list-style-image: url(bullet.gif);
+	font-size: 95%;
+}
+.portlet li {
+	padding: 0;
+	margin: 0;
+}
+
+/*
+** Logo properties
+*/
+
+/* @media screen, projection { */
+	#p-logo {
+		top: 0;
+		left: 0;
+		position: absolute; /*needed to use z-index */
+		z-index: 3;
+		height: 155px;
+		width: 12em;
+		overflow: visible;
+	}
+	#p-logo h5 {
+		display: none;
+	}
+	#p-logo a,
+	#p-logo a:hover {
+		display: block;
+		height: 155px;
+		width: 12.2em;
+		background-repeat: no-repeat;
+		background-position: 35% 50% !important;
+		text-decoration: none;
+	}
+/* } */
+/*
+** the navigation portlet
+*/
+
+/* @media screen, projection { */
+	#p-navigation {
+		position: relative;
+		z-index: 3;
+	}
+	#p-navigation a {
+		display: block;
+	}
+	#p-navigation li.active a, #p-navigation li.active a:hover {
+		display: inline;
+	}
+/* } */
+
+#p-navigation .pBody {
+	padding-right: 0;
+}
+
+#p-navigation li.active a, #p-navigation li.active a:hover {
+	text-decoration: none;
+	font-weight: bold;
+}
+
+
+/*
+** Search portlet
+*/
+/* @media screen, projection { */
+	#p-search {
+		position: relative;
+		z-index: 3;
+	}
+/* } */
+input.searchButton {
+	margin-top: 1px;
+	font-size: 95%;
+}
+#searchGoButton {
+	padding-left: .5em;
+	padding-right: .5em;
+	font-weight: bold;
+}
+#searchInput {
+	width: 10.9em;
+	margin: 0;
+	font-size: 95%;
+}
+#p-search .pBody {
+	padding: .5em .4em .4em .4em;
+	text-align: center;
+}
+
+/*
+** the personal toolbar
+*/
+/* @media screen, projection { */
+	#p-personal {
+		position: absolute;
+		left: 0;
+		top: 0;
+		z-index: 0;
+	}
+	#p-personal {
+		width: 100%;
+		white-space: nowrap;
+		padding: 0;
+		margin: 0;
+		border: none;
+		background: none;
+		overflow: visible;
+		line-height: 1.2em;
+	}
+	#p-personal h5 {
+		display: none;
+	}
+	#p-personal .portlet,
+	#p-personal .pBody {
+		z-index: 0;
+		padding: 0;
+		margin: 0;
+		border: none;
+		overflow: visible;
+		background: none;
+	}
+/* this is the ul contained in the portlet */
+	#p-personal ul {
+		border: none;
+		line-height: 1.4em;
+		color: #2f6fab;
+		padding: 0 2em 0 3em;
+		margin: 0;
+		text-align: right;
+		list-style: none;
+		z-index: 0;
+		background: none;
+		cursor: default;
+	}
+	#p-personal li {
+		z-index: 0;
+		border: none;
+		padding: 0;
+		display: inline;
+		color: #2f6fab;
+		margin-left: 1em;
+		line-height: 1.2em;
+		background: none;
+	}
+	#p-personal li a {
+		text-decoration: none;
+		color: #005896;
+		padding-bottom: .2em;
+		background: none;
+	}
+	#p-personal li a:hover {
+		background-color: white;
+		padding-bottom: .2em;
+		text-decoration: none;
+	}
+	#p-personal li.active a:hover {
+		background-color: transparent;
+	}
+	/* the icon in front of the user name, single quotes
+	in bg url to hide it from iemac */
+	li#pt-userpage,
+	li#pt-anonuserpage,
+	li#pt-login {
+		background: url(user.gif) top left no-repeat;
+		padding-left: 20px;
+		text-transform: none;
+	}
+/* } */
+#p-personal ul {
+	text-transform: lowercase;
+}
+#p-personal li.active {
+	font-weight: bold;
+}
+/*
+** the page-related actions- page/talk, edit etc
+*/
+/* @media screen, projection { */
+	#p-cactions {
+		position: absolute;
+		top: 1.3em;
+		left: 11.5em;
+		margin: 0;
+		white-space: nowrap;
+		width: 76%;
+		line-height: 1.1em;
+		overflow: visible;
+		background: none;
+		border-collapse: collapse;
+		padding-left: 1em;
+		list-style: none;
+		font-size: 95%;
+	}
+	#p-cactions ul {
+		list-style: none;
+	}
+	#p-cactions li {
+		display: inline;
+		border: 1px solid #aaa;
+		border-bottom: none;
+		padding: 0 0 .1em 0;
+		margin: 0 .3em 0 0;
+		overflow: visible;
+		background: white;
+	}
+	#p-cactions li.selected {
+		border-color: #fabd23;
+		padding: 0 0 .2em 0;
+		font-weight: bold;
+	}
+	#p-cactions li a {
+		background-color: #fbfbfb;
+		color: #002bb8;
+		border: none;
+		padding: 0 .8em .3em;
+		position: relative;
+		z-index: 0;
+		margin: 0;
+		text-decoration: none;
+	}
+	#p-cactions li.selected a {
+		z-index: 3;
+		padding: 0 1em .2em!important;
+		background-color: white;
+	}
+	#p-cactions .new a {
+		color: #ba0000;
+	}
+	#p-cactions li a:hover {
+		z-index: 3;
+		text-decoration: none;
+		background-color: white;
+	}
+	#p-cactions h5 {
+		display: none;
+	}
+	#p-cactions li.istalk {
+		margin-right: 0;
+	}
+	#p-cactions li.istalk a {
+		padding-right: .5em;
+	}
+	#p-cactions #ca-addsection a {
+		padding-left: .4em;
+		padding-right: .4em;
+	}
+	/* offsets to distinguish the tab groups */
+	li#ca-talk {
+		margin-right: 1.6em;
+	}
+	li#ca-watch, li#ca-unwatch, li#ca-varlang-0, li#ca-print {
+		margin-left: 1.6em;
+	}
+	#p-cactions .pBody {
+		font-size: 1em;
+		background-color: transparent;
+		color: inherit;
+		border-collapse: inherit;
+		border: 0;
+		padding: 0;
+	}
+/* } */
+#p-cactions .hiddenStructure {
+	display: none;
+}
+#p-cactions li a {
+	text-transform: lowercase;
+}
+
+/*
+** the remaining portlets
+*/
+/* @media screen, projection { */
+	#p-tbx,
+	#p-lang {
+		position: relative;
+		z-index: 3;
+	}
+/* } */
+
+/* TODO: #t-iscite is only used by the Cite extension, come up with some
+ * system which allows extensions to add to this file on the fly
+ */
+#t-ispermalink, #t-iscite {
+	color: #999;
+}
+/*
+** footer
+*/
+#footer {
+	background-color: white;
+	border-top: 1px solid #fabd23;
+	border-bottom: 1px solid #fabd23;
+	margin: .6em 0 1em 0;
+	padding: .4em 0 1.2em 0;
+	text-align: center;
+	font-size: 90%;
+}
+#footer li {
+	display: inline;
+	margin: 0 1.3em;
+}
+#f-poweredbyico, #f-copyrightico {
+	margin: 0 8px;
+	position: relative;
+	top: -2px; /* Bump it up just a tad */
+}
+#f-poweredbyico {
+	float: right;
+	height: 1%;
+}
+#f-copyrightico {
+	float: left;
+	height: 1%;
+}
+
+/* js pref toc */
+#preftoc {
+	margin: 0;
+	padding: 0;
+	width: 100%;
+	clear: both;
+}
+#preftoc li {
+	background-color: #f0f0f0;
+	color: #000;
+}
+/* @media screen, projection { */
+	#preftoc li {
+		margin: 1px -2px 1px 2px;
+		float: left;
+		padding: 2px 0 3px 0;
+		border: 1px solid #fff;
+		border-right-color: #716f64;
+		border-bottom: 0;
+		position: relative;
+		white-space: nowrap;
+		list-style-type: none;
+		list-style-image: none;
+		z-index: 3;
+	}
+/* } */
+#preftoc li.selected {
+	font-weight: bold;
+	background-color: #f9f9f9;
+	border: 1px solid #aaa;
+	border-bottom: none;
+	cursor: default;
+	top: 1px;
+	padding-top: 2px;
+	margin-right: -3px;
+}
+#preftoc > li.selected {
+	top: 2px;
+}
+#preftoc a,
+#preftoc a:active {
+	display: block;
+	color: #000;
+	padding: 0 .7em;
+	position: relative;
+	text-decoration: none;
+}
+#preftoc li.selected a {
+	cursor: default;
+	text-decoration: none;
+}
+#prefcontrol {
+	padding-top: 2em;
+	clear: both;
+}
+#preferences {
+	margin: 0;
+	border: 1px solid #aaa;
+	clear: both;
+	padding: 1.5em;
+	background-color: #F9F9F9;
+}
+.prefsection {
+	border: none;
+	padding: 0;
+	margin: 0;
+}
+.prefsection fieldset {
+	border: 1px solid #aaa;
+	float: left;
+	margin-right: 2em;
+}
+.prefsection legend {
+	font-weight: bold;   
+}
+.prefsection table, .prefsection legend {
+	background-color: #F9F9F9;
+}
+/* @media screen, projection { */
+	.mainLegend {
+		display: none;
+	}
+/* } */
+div.prefsectiontip {
+	font-size: x-small;
+	padding: .2em 2em;
+	color: #666;
+}
+.btnSavePrefs {
+	font-weight: bold;
+	padding-left: .3em;
+	padding-right: .3em;
+}
+
+.preferences-login {
+	clear: both;
+	margin-bottom: 1.5em;
+}
+
+.prefcache {
+	font-size: 90%;
+	margin-top: 2em;
+}
+
+div#userloginForm form,
+div#userlogin form#userlogin2 {
+	margin: 0 3em 1em 0;
+	border: 1px solid #aaa;
+	clear: both;
+	padding: 1.5em 2em;
+	background-color: #f9f9f9;
+	float: left;
+}
+
+div#userloginForm table,
+div#userlogin form#userlogin2 table {
+	background-color: #f9f9f9;
+}
+
+div#userloginForm h2,
+div#userlogin form#userlogin2 h2 {
+	padding-top: 0;
+}
+
+div#userlogin .captcha,
+div#userloginForm .captcha {
+	border: 1px solid #bbb;
+	padding: 1.5em 2em;
+	background-color: white;
+}
+
+
+#userloginprompt, #languagelinks {
+	font-size: 85%;
+}
+
+#login-sectiontip {
+	font-size: 85%;
+	line-height: 1.2;
+	padding-top: 2em;
+}
+
+#userlogin .loginText, #userlogin .loginPassword {
+	width: 12em;
+}
+
+#userloginlink a, #wpLoginattempt, #wpCreateaccount {
+	font-weight: bold;
+}
+
+/* @media screen, projection { */
+	/*
+	** IE/Mac fixes, hope to find a validating way to move this
+	** to a separate stylesheet. This would work but doesn't validate:
+	** @import("IEMacFixes.css");
+	*/
+	/* tabs: border on the a, not the div */
+	* > html #p-cactions li { border: none; }
+	* > html #p-cactions li a {
+		border: 1px solid #aaa;
+		border-bottom: none;
+	}
+	* > html #p-cactions li.selected a { border-color: #fabd23; }
+	/* footer icons need a fixed width */
+	* > html #f-poweredbyico,
+	* > html #f-copyrightico { width: 88px; }
+	* > html #bodyContent,
+	* > html #bodyContent pre {
+		overflow-x: auto;
+		width: 100%;
+		padding-bottom: 25px;
+	}
+/* } */
+
+/* more IE fixes */
+/* float/negative margin brokenness */
+* html #footer {margin-top: 0;}
+* html #column-content {
+	display: inline;
+	margin-bottom: 0;
+}
+* html div.editsection { font-size: smaller; }
+#pagehistory li.selected { position: relative; }
+
+/* Mac IE 5.0 fix; floated content turns invisible */
+* > html #column-content {
+	float: none;
+}
+* > html #column-one {
+	position: absolute;
+	left: 0;
+	top: 0;
+}
+* > html #footer {
+	margin-left: 13.2em;
+}
+.redirectText {
+	font-size: 150%;
+	margin: 5px;
+}
+
+.printfooter {
+	display: none;
+}
+
+.not-patrolled {
+	background-color: #ffa;
+}
+div.patrollink {
+	font-size: 75%;
+	text-align: right;
+}
+span.newpage, span.minor, span.searchmatch, span.bot {
+	font-weight: bold;
+}
+span.unpatrolled {
+	font-weight: bold;
+	color: red;
+}
+
+span.searchmatch {
+	color: red;
+}
+.sharedUploadNotice {
+	font-style: italic;
+}
+
+span.updatedmarker {
+	color: black;
+	background-color: #0f0;
+}
+
+table.gallery {
+	border: 1px solid #ccc;
+	margin: 2px;
+	padding: 2px;
+	background-color: white;
+}
+
+table.gallery tr {
+	vertical-align: top;
+}
+
+table.gallery td {
+	vertical-align: top;
+	background-color: #f9f9f9;
+	border: solid 2px white;
+}
+/* Keep this temporarily so that cached pages will display right */
+table.gallery td.galleryheader {
+    text-align: center;
+	font-weight: bold;
+}
+table.gallery caption {
+	font-weight: bold;
+}
+
+div.gallerybox {
+	margin: 2px;
+}
+
+div.gallerybox div.thumb {
+	text-align: center;
+	border: 1px solid #ccc;
+	margin: 2px;
+}
+
+div.gallerytext {
+	overflow: hidden;
+	font-size: 94%;
+	padding: 2px 4px;
+}
+
+span.comment {
+	font-style: italic;
+}
+
+span.changedby {
+	font-size: 95%;
+}
+
+.previewnote {
+	text-indent: 3em;
+	color: #c00;
+	border-bottom: 1px solid #aaa;
+	padding-bottom: 1em;
+	margin-bottom: 1em;
+}
+
+.previewnote p {
+	margin: 0;
+	padding: 0;
+}
+
+.editExternally {
+	border: 1px solid gray;
+	background-color: #ffffff;
+	padding: 3px;
+	margin-top: 0.5em;
+	float: left;
+	font-size: small;
+	text-align: center;
+}
+.editExternallyHelp {
+	font-style: italic;
+	color: gray;
+}
+
+li span.deleted, span.history-deleted {
+	text-decoration: line-through;
+	color: #888;
+	font-style: italic;
+}
+
+.toggle {
+	margin-left: 2em;
+	text-indent: -2em;
+}
+
+/* Classes for EXIF data display */
+table.mw_metadata {
+	font-size: 0.8em;
+	margin-left: 0.5em;
+	margin-bottom: 0.5em;
+	width: 300px;
+}
+
+table.mw_metadata caption {
+	font-weight: bold;
+}
+
+table.mw_metadata th {
+	font-weight: normal;
+}
+
+table.mw_metadata td {
+	padding: 0.1em;
+}
+
+table.mw_metadata {
+	border: none;
+	border-collapse: collapse;
+}
+
+table.mw_metadata td, table.mw_metadata th {
+	text-align: center;
+	border: 1px solid #aaaaaa;
+	padding-left: 0.1em;
+	padding-right: 0.1em;
+}
+
+table.mw_metadata th {
+	background-color: #f9f9f9;
+}
+
+table.mw_metadata td {
+	background-color: #fcfcfc;
+}
+
+table.collapsed tr.collapsable {
+	display: none;
+}
+
+
+/* filetoc */
+ul#filetoc {
+	text-align: center;
+	border: 1px solid #aaaaaa;
+	background-color: #f9f9f9;
+	padding: 5px;
+	font-size: 95%;
+	margin-bottom: 0.5em;
+	margin-left: 0;
+	margin-right: 0;
+}
+
+#filetoc li {
+	display: inline;
+	list-style-type: none;
+	padding-right: 2em;
+}
+
+input#wpSummary {
+	width: 80%;
+}
+
+/* @bug 1714 */
+input#wpSave, input#wpDiff {
+	margin-right: 0.33em;
+}
+
+#editform .editOptions {
+	display: inline;
+}
+
+#wpSave {
+	font-weight: bold;
+}
+
+/* Classes for article validation */
+
+table.revisionform_default {
+	border: 1px solid #000000;
+}
+
+table.revisionform_focus {
+	border: 1px solid #000000;
+	background-color:#00BBFF;
+}
+
+tr.revision_tr_default {
+	background-color:#EEEEEE;
+}
+
+tr.revision_tr_first {
+	background-color:#DDDDDD;
+}
+
+p.revision_saved {
+	color: green;
+	font-weight:bold;
+}
+
+#mw_trackbacks {
+	border: solid 1px #bbbbff;
+	background-color: #eeeeff;
+	padding: 0.2em;
+}
+
+
+/* Allmessages table */
+
+#allmessagestable th {
+	background-color: #b2b2ff;
+}
+
+#allmessagestable tr.orig {
+	background-color: #ffe2e2;
+}
+
+#allmessagestable tr.new {
+	background-color: #e2ffe2;
+}
+
+#allmessagestable tr.def {
+	background-color: #f0f0ff;
+}
+
+
+/* noarticletext */
+div.noarticletext {
+	border: 1px solid #ccc;
+	background: #fff;
+	padding: .2em 1em;
+	color: #000;
+}
+
+div#searchTargetContainer {
+	left:       10px;
+	top:        10px;
+	width:      90%;
+	background: white;
+}
+
+div#searchTarget {
+	padding:    3px;
+	margin:     5px;
+	background: #F0F0F0;
+	border:     solid 1px blue;
+}
+
+div#searchTarget ul li {
+	list-style: none;
+}
+
+div#searchTarget ul li:before {
+	color: orange;
+	content: "\00BB \0020";
+}
+
+div.multipageimagenavbox {
+   border: solid 1px silver;
+   padding: 4px;
+   margin: 1em;
+   -moz-border-radius: 6px;
+   background: #f0f0f0;
+}
+
+div.multipageimagenavbox div.thumb {
+   border: none;
+   margin-left: 2em;
+   margin-right: 2em;
+}
+
+div.multipageimagenavbox hr {
+   margin: 6px;
+}
+
+table.multipageimage td {
+   text-align: center;
+}
+
+/** Special:Version */
+
+table#sv-ext, table#sv-hooks {
+	margin: 1em;
+	padding:0em;
+}
+
+#sv-ext td, #sv-hooks td,
+#sv-ext th, #sv-hooks th {
+	border: 1px solid #A0A0A0;
+	padding: 0 0.15em 0 0.15em;
+}
+#sv-ext th, #sv-hooks th {
+	background-color: #F0F0F0;
+	color: black;
+	padding: 0 0.15em 0 0.15em;
+}
+tr.sv-space{
+	height: 0.8em;
+	border:none;
+}
+tr.sv-space td { display: none; }
+
+/*
+  Table pager (e.g. Special:Imagelist)
+  - remove underlines from the navigation link
+  - collapse borders
+  - set the borders to outsets (similar to Special:Allmessages)
+  - remove line wrapping for all td and th, set background color
+  - restore line wrapping for the last two table cells (description and size)
+*/
+.TablePager_nav a { text-decoration: none; }
+.TablePager { border-collapse: collapse; }
+.TablePager, .TablePager td, .TablePager th { 
+	border: 1px solid #aaaaaa;
+	padding: 0 0.15em 0 0.15em;
+}
+.TablePager th { background-color: #eeeeff }
+.TablePager td { background-color: #ffffff }
+.TablePager tr:hover td { background-color: #eeeeff }
+
+.imagelist td, .imagelist th { white-space: nowrap }
+.imagelist .TablePager_col_links { background-color: #eeeeff }
+.imagelist .TablePager_col_img_description { white-space: normal }
+.imagelist th.TablePager_sort { background-color: #ccccff }
+
+.templatesUsed { margin-top: 1.5em; }
+
+.mw-summary-preview {
+	margin: 0.1em 0;
+}
+
+/* Convenience links on Special:Ipblocklist */
+p.mw-ipb-conveniencelinks {
+	font-size: 90%;
+	float: right;
+}
+
+/**
+ * Here is some stuff that's ACTUALLY COMMON TO ALL SKINS.
+ * When the day comes, it can be moved to a *real* common.css.
+ */
+.mw-plusminus-null { color: #aaa; }
+.texvc { direction: ltr; unicode-bidi: embed; }
+/* Stop floats from intruding into edit area in previews */
+#toolbar, #wpTextbox1 { clear: both; }
+
+.MediaTransformError {
+	background-color: #ccc;
+	padding: 0.1em;
+}
+.MediaTransformError td {
+	text-align: center;
+	vertical-align: middle;
+	font-size: 90%;
+}

BIN
talks/bayes_theorem/Bayes'_theorem Files/poweredby_mediawiki_88x31.png


BIN
talks/bayes_theorem/Bayes'_theorem Files/wiki-en.png


+ 1246 - 0
talks/bayes_theorem/Bayes'_theorem Files/wikibits.js

@@ -0,0 +1,1246 @@
+// MediaWiki JavaScript support functions
+
+var clientPC = navigator.userAgent.toLowerCase(); // Get client info
+var is_gecko = ((clientPC.indexOf('gecko')!=-1) && (clientPC.indexOf('spoofer')==-1)
+                && (clientPC.indexOf('khtml') == -1) && (clientPC.indexOf('netscape/7.0')==-1));
+var is_safari = ((clientPC.indexOf('applewebkit')!=-1) && (clientPC.indexOf('spoofer')==-1));
+var is_khtml = (navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled ));
+// For accesskeys
+var is_ff2_win = (clientPC.indexOf('firefox/2')!=-1 || clientPC.indexOf('minefield/3')!=-1) && clientPC.indexOf('windows')!=-1;
+var is_ff2_x11 = (clientPC.indexOf('firefox/2')!=-1 || clientPC.indexOf('minefield/3')!=-1) && clientPC.indexOf('x11')!=-1;
+if (clientPC.indexOf('opera') != -1) {
+	var is_opera = true;
+	var is_opera_preseven = (window.opera && !document.childNodes);
+	var is_opera_seven = (window.opera && document.childNodes);
+}
+
+// Global external objects used by this script.
+/*extern ta, stylepath, skin */
+
+// add any onload functions in this hook (please don't hard-code any events in the xhtml source)
+var doneOnloadHook;
+
+if (!window.onloadFuncts) {
+	var onloadFuncts = [];
+}
+
+function addOnloadHook(hookFunct) {
+	// Allows add-on scripts to add onload functions
+	onloadFuncts[onloadFuncts.length] = hookFunct;
+}
+
+function hookEvent(hookName, hookFunct) {
+	if (window.addEventListener) {
+		window.addEventListener(hookName, hookFunct, false);
+	} else if (window.attachEvent) {
+		window.attachEvent("on" + hookName, hookFunct);
+	}
+}
+
+// document.write special stylesheet links
+if (typeof stylepath != 'undefined' && typeof skin != 'undefined') {
+	if (is_opera_preseven) {
+		document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/Opera6Fixes.css">');
+	} else if (is_opera_seven) {
+		document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/Opera7Fixes.css">');
+	} else if (is_khtml) {
+		document.write('<link rel="stylesheet" type="text/css" href="'+stylepath+'/'+skin+'/KHTMLFixes.css">');
+	}
+}
+
+if (wgBreakFrames) {
+	// Un-trap us from framesets
+	if (window.top != window) {
+		window.top.location = window.location;
+	}
+}
+
+// for enhanced RecentChanges
+function toggleVisibility(_levelId, _otherId, _linkId) {
+	var thisLevel = document.getElementById(_levelId);
+	var otherLevel = document.getElementById(_otherId);
+	var linkLevel = document.getElementById(_linkId);
+	if (thisLevel.style.display == 'none') {
+		thisLevel.style.display = 'block';
+		otherLevel.style.display = 'none';
+		linkLevel.style.display = 'inline';
+	} else {
+		thisLevel.style.display = 'none';
+		otherLevel.style.display = 'inline';
+		linkLevel.style.display = 'none';
+	}
+}
+
+function historyRadios(parent) {
+	var inputs = parent.getElementsByTagName('input');
+	var radios = [];
+	for (var i = 0; i < inputs.length; i++) {
+		if (inputs[i].name == "diff" || inputs[i].name == "oldid") {
+			radios[radios.length] = inputs[i];
+		}
+	}
+	return radios;
+}
+
+// check selection and tweak visibility/class onclick
+function diffcheck() {
+	var dli = false; // the li where the diff radio is checked
+	var oli = false; // the li where the oldid radio is checked
+	var hf = document.getElementById('pagehistory');
+	if (!hf) {
+		return true;
+	}
+	var lis = hf.getElementsByTagName('li');
+	for (var i=0;i<lis.length;i++) {
+		var inputs = historyRadios(lis[i]);
+		if (inputs[1] && inputs[0]) {
+			if (inputs[1].checked || inputs[0].checked) { // this row has a checked radio button
+				if (inputs[1].checked && inputs[0].checked && inputs[0].value == inputs[1].value) {
+					return false;
+				}
+				if (oli) { // it's the second checked radio
+					if (inputs[1].checked) {
+						oli.className = "selected";
+						return false;
+					}
+				} else if (inputs[0].checked) {
+					return false;
+				}
+				if (inputs[0].checked) {
+					dli = lis[i];
+				}
+				if (!oli) {
+					inputs[0].style.visibility = 'hidden';
+				}
+				if (dli) {
+					inputs[1].style.visibility = 'hidden';
+				}
+				lis[i].className = "selected";
+				oli = lis[i];
+			}  else { // no radio is checked in this row
+				if (!oli) {
+					inputs[0].style.visibility = 'hidden';
+				} else {
+					inputs[0].style.visibility = 'visible';
+				}
+				if (dli) {
+					inputs[1].style.visibility = 'hidden';
+				} else {
+					inputs[1].style.visibility = 'visible';
+				}
+				lis[i].className = "";
+			}
+		}
+	}
+	return true;
+}
+
+// page history stuff
+// attach event handlers to the input elements on history page
+function histrowinit() {
+	var hf = document.getElementById('pagehistory');
+	if (!hf) {
+		return;
+	}
+	var lis = hf.getElementsByTagName('li');
+	for (var i = 0; i < lis.length; i++) {
+		var inputs = historyRadios(lis[i]);
+		if (inputs[0] && inputs[1]) {
+			inputs[0].onclick = diffcheck;
+			inputs[1].onclick = diffcheck;
+		}
+	}
+	diffcheck();
+}
+
+// generate toc from prefs form, fold sections
+// XXX: needs testing on IE/Mac and safari
+// more comments to follow
+function tabbedprefs() {
+	var prefform = document.getElementById('preferences');
+	if (!prefform || !document.createElement) {
+		return;
+	}
+	if (prefform.nodeName.toLowerCase() == 'a') {
+		return; // Occasional IE problem
+	}
+	prefform.className = prefform.className + 'jsprefs';
+	var sections = [];
+	var children = prefform.childNodes;
+	var seci = 0;
+	for (var i = 0; i < children.length; i++) {
+		if (children[i].nodeName.toLowerCase() == 'fieldset') {
+			children[i].id = 'prefsection-' + seci;
+			children[i].className = 'prefsection';
+			if (is_opera || is_khtml) {
+				children[i].className = 'prefsection operaprefsection';
+			}
+			var legends = children[i].getElementsByTagName('legend');
+			sections[seci] = {};
+			legends[0].className = 'mainLegend';
+			if (legends[0] && legends[0].firstChild.nodeValue) {
+				sections[seci].text = legends[0].firstChild.nodeValue;
+			} else {
+				sections[seci].text = '# ' + seci;
+			}
+			sections[seci].secid = children[i].id;
+			seci++;
+			if (sections.length != 1) {
+				children[i].style.display = 'none';
+			} else {
+				var selectedid = children[i].id;
+			}
+		}
+	}
+	var toc = document.createElement('ul');
+	toc.id = 'preftoc';
+	toc.selectedid = selectedid;
+	for (i = 0; i < sections.length; i++) {
+		var li = document.createElement('li');
+		if (i === 0) {
+			li.className = 'selected';
+		}
+		var a = document.createElement('a');
+		a.href = '#' + sections[i].secid;
+		a.onmousedown = a.onclick = uncoversection;
+		a.appendChild(document.createTextNode(sections[i].text));
+		a.secid = sections[i].secid;
+		li.appendChild(a);
+		toc.appendChild(li);
+	}
+	prefform.parentNode.insertBefore(toc, prefform.parentNode.childNodes[0]);
+	document.getElementById('prefsubmit').id = 'prefcontrol';
+}
+
+function uncoversection() {
+	var oldsecid = this.parentNode.parentNode.selectedid;
+	var newsec = document.getElementById(this.secid);
+	if (oldsecid != this.secid) {
+		var ul = document.getElementById('preftoc');
+		document.getElementById(oldsecid).style.display = 'none';
+		newsec.style.display = 'block';
+		ul.selectedid = this.secid;
+		var lis = ul.getElementsByTagName('li');
+		for (var i = 0; i< lis.length; i++) {
+			lis[i].className = '';
+		}
+		this.parentNode.className = 'selected';
+	}
+	return false;
+}
+
+// Timezone stuff
+// tz in format [+-]HHMM
+function checkTimezone(tz, msg) {
+	var localclock = new Date();
+	// returns negative offset from GMT in minutes
+	var tzRaw = localclock.getTimezoneOffset();
+	var tzHour = Math.floor( Math.abs(tzRaw) / 60);
+	var tzMin = Math.abs(tzRaw) % 60;
+	var tzString = ((tzRaw >= 0) ? "-" : "+") + ((tzHour < 10) ? "0" : "") + tzHour + ((tzMin < 10) ? "0" : "") + tzMin;
+	if (tz != tzString) {
+		var junk = msg.split('$1');
+		document.write(junk[0] + "UTC" + tzString + junk[1]);
+	}
+}
+
+function unhidetzbutton() {
+	var tzb = document.getElementById('guesstimezonebutton');
+	if (tzb) {
+		tzb.style.display = 'inline';
+	}
+}
+
+// in [-]HH:MM format...
+// won't yet work with non-even tzs
+function fetchTimezone() {
+	// FIXME: work around Safari bug
+	var localclock = new Date();
+	// returns negative offset from GMT in minutes
+	var tzRaw = localclock.getTimezoneOffset();
+	var tzHour = Math.floor( Math.abs(tzRaw) / 60);
+	var tzMin = Math.abs(tzRaw) % 60;
+	var tzString = ((tzRaw >= 0) ? "-" : "") + ((tzHour < 10) ? "0" : "") + tzHour +
+		":" + ((tzMin < 10) ? "0" : "") + tzMin;
+	return tzString;
+}
+
+function guessTimezone(box) {
+	document.getElementsByName("wpHourDiff")[0].value = fetchTimezone();
+}
+
+function showTocToggle() {
+	if (document.createTextNode) {
+		// Uses DOM calls to avoid document.write + XHTML issues
+
+		var linkHolder = document.getElementById('toctitle');
+		if (!linkHolder) {
+			return;
+		}
+
+		var outerSpan = document.createElement('span');
+		outerSpan.className = 'toctoggle';
+
+		var toggleLink = document.createElement('a');
+		toggleLink.id = 'togglelink';
+		toggleLink.className = 'internal';
+		toggleLink.href = 'javascript:toggleToc()';
+		toggleLink.appendChild(document.createTextNode(tocHideText));
+
+		outerSpan.appendChild(document.createTextNode('['));
+		outerSpan.appendChild(toggleLink);
+		outerSpan.appendChild(document.createTextNode(']'));
+
+		linkHolder.appendChild(document.createTextNode(' '));
+		linkHolder.appendChild(outerSpan);
+
+		var cookiePos = document.cookie.indexOf("hidetoc=");
+		if (cookiePos > -1 && document.cookie.charAt(cookiePos + 8) == 1) {
+			toggleToc();
+		}
+	}
+}
+
+function changeText(el, newText) {
+	// Safari work around
+	if (el.innerText) {
+		el.innerText = newText;
+	} else if (el.firstChild && el.firstChild.nodeValue) {
+		el.firstChild.nodeValue = newText;
+	}
+}
+
+function toggleToc() {
+	var toc = document.getElementById('toc').getElementsByTagName('ul')[0];
+	var toggleLink = document.getElementById('togglelink');
+
+	if (toc && toggleLink && toc.style.display == 'none') {
+		changeText(toggleLink, tocHideText);
+		toc.style.display = 'block';
+		document.cookie = "hidetoc=0";
+	} else {
+		changeText(toggleLink, tocShowText);
+		toc.style.display = 'none';
+		document.cookie = "hidetoc=1";
+	}
+}
+
+var mwEditButtons = [];
+var mwCustomEditButtons = []; // eg to add in MediaWiki:Common.js
+
+// this function generates the actual toolbar buttons with localized text
+// we use it to avoid creating the toolbar where javascript is not enabled
+function addButton(imageFile, speedTip, tagOpen, tagClose, sampleText, imageId) {
+	// Don't generate buttons for browsers which don't fully
+	// support it.
+	mwEditButtons[mwEditButtons.length] =
+		{"imageId": imageId,
+		 "imageFile": imageFile,
+		 "speedTip": speedTip,
+		 "tagOpen": tagOpen,
+		 "tagClose": tagClose,
+		 "sampleText": sampleText};
+}
+
+// this function generates the actual toolbar buttons with localized text
+// we use it to avoid creating the toolbar where javascript is not enabled
+function mwInsertEditButton(parent, item) {
+	var image = document.createElement("img");
+	image.width = 23;
+	image.height = 22;
+	image.className = "mw-toolbar-editbutton";
+	if (item.imageId) image.id = item.imageId;
+	image.src = item.imageFile;
+	image.border = 0;
+	image.alt = item.speedTip;
+	image.title = item.speedTip;
+	image.style.cursor = "pointer";
+	image.onclick = function() {
+		insertTags(item.tagOpen, item.tagClose, item.sampleText);
+		return false;
+	};
+
+	parent.appendChild(image);
+	return true;
+}
+
+function mwSetupToolbar() {
+	var toolbar = document.getElementById('toolbar');
+	if (!toolbar) { return false; }
+
+	var textbox = document.getElementById('wpTextbox1');
+	if (!textbox) { return false; }
+
+	// Don't generate buttons for browsers which don't fully
+	// support it.
+	if (!document.selection && textbox.selectionStart === null) {
+		return false;
+	}
+
+	for (var i = 0; i < mwEditButtons.length; i++) {
+		mwInsertEditButton(toolbar, mwEditButtons[i]);
+	}
+	for (var i = 0; i < mwCustomEditButtons.length; i++) {
+		mwInsertEditButton(toolbar, mwCustomEditButtons[i]);
+	}
+	return true;
+}
+
+function escapeQuotes(text) {
+	var re = new RegExp("'","g");
+	text = text.replace(re,"\\'");
+	re = new RegExp("\\n","g");
+	text = text.replace(re,"\\n");
+	return escapeQuotesHTML(text);
+}
+
+function escapeQuotesHTML(text) {
+	var re = new RegExp('&',"g");
+	text = text.replace(re,"&amp;");
+	re = new RegExp('"',"g");
+	text = text.replace(re,"&quot;");
+	re = new RegExp('<',"g");
+	text = text.replace(re,"&lt;");
+	re = new RegExp('>',"g");
+	text = text.replace(re,"&gt;");
+	return text;
+}
+
+// apply tagOpen/tagClose to selection in textarea,
+// use sampleText instead of selection if there is none
+// copied and adapted from phpBB
+function insertTags(tagOpen, tagClose, sampleText) {
+	var txtarea;
+	if (document.editform) {
+		txtarea = document.editform.wpTextbox1;
+	} else {
+		// some alternate form? take the first one we can find
+		var areas = document.getElementsByTagName('textarea');
+		txtarea = areas[0];
+	}
+
+	// IE
+	if (document.selection  && !is_gecko) {
+		var theSelection = document.selection.createRange().text;
+		if (!theSelection) {
+			theSelection=sampleText;
+		}
+		txtarea.focus();
+		if (theSelection.charAt(theSelection.length - 1) == " ") { // exclude ending space char, if any
+			theSelection = theSelection.substring(0, theSelection.length - 1);
+			document.selection.createRange().text = tagOpen + theSelection + tagClose + " ";
+		} else {
+			document.selection.createRange().text = tagOpen + theSelection + tagClose;
+		}
+
+	// Mozilla
+	} else if(txtarea.selectionStart || txtarea.selectionStart == '0') {
+		var replaced = false;
+		var startPos = txtarea.selectionStart;
+		var endPos = txtarea.selectionEnd;
+		if (endPos-startPos) {
+			replaced = true;
+		}
+		var scrollTop = txtarea.scrollTop;
+		var myText = (txtarea.value).substring(startPos, endPos);
+		if (!myText) {
+			myText=sampleText;
+		}
+		var subst;
+		if (myText.charAt(myText.length - 1) == " ") { // exclude ending space char, if any
+			subst = tagOpen + myText.substring(0, (myText.length - 1)) + tagClose + " ";
+		} else {
+			subst = tagOpen + myText + tagClose;
+		}
+		txtarea.value = txtarea.value.substring(0, startPos) + subst +
+			txtarea.value.substring(endPos, txtarea.value.length);
+		txtarea.focus();
+		//set new selection
+		if (replaced) {
+			var cPos = startPos+(tagOpen.length+myText.length+tagClose.length);
+			txtarea.selectionStart = cPos;
+			txtarea.selectionEnd = cPos;
+		} else {
+			txtarea.selectionStart = startPos+tagOpen.length;
+			txtarea.selectionEnd = startPos+tagOpen.length+myText.length;
+		}
+		txtarea.scrollTop = scrollTop;
+
+	// All other browsers get no toolbar.
+	// There was previously support for a crippled "help"
+	// bar, but that caused more problems than it solved.
+	}
+	// reposition cursor if possible
+	if (txtarea.createTextRange) {
+		txtarea.caretPos = document.selection.createRange().duplicate();
+	}
+}
+
+
+/**
+ * Set the accesskey prefix based on browser detection.
+ */
+var tooltipAccessKeyPrefix = 'alt-';
+if (is_opera) {
+	tooltipAccessKeyPrefix = 'shift-esc-';
+} else if (is_safari
+	   || navigator.userAgent.toLowerCase().indexOf('mac') != -1
+	   || navigator.userAgent.toLowerCase().indexOf('konqueror') != -1 ) {
+	tooltipAccessKeyPrefix = 'ctrl-';
+} else if (is_ff2_x11 || is_ff2_win) {
+	tooltipAccessKeyPrefix = 'alt-shift-';
+}
+var tooltipAccessKeyRegexp = /\[(ctrl-)?(alt-)?(shift-)?(esc-)?.\]$/;
+
+/**
+ * Add the appropriate prefix to the accesskey shown in the tooltip.
+ * If the nodeList parameter is given, only those nodes are updated;
+ * otherwise, all the nodes that will probably have accesskeys by
+ * default are updated.
+ *
+ * @param Array nodeList -- list of elements to update
+ */
+function updateTooltipAccessKeys( nodeList ) {
+	if ( !nodeList ) {
+		// skins without a "column-one" element don't seem to have links with accesskeys either
+		var columnOne = document.getElementById("column-one");
+		if ( columnOne )
+			updateTooltipAccessKeys( columnOne.getElementsByTagName("a") );
+		// these are rare enough that no such optimization is needed
+		updateTooltipAccessKeys( document.getElementsByTagName("input") );
+		updateTooltipAccessKeys( document.getElementsByTagName("label") );
+		return;
+	}
+
+	for ( var i = 0; i < nodeList.length; i++ ) {
+		var element = nodeList[i];
+		var tip = element.getAttribute("title");
+		var key = element.getAttribute("accesskey");
+		if ( key && tooltipAccessKeyRegexp.exec(tip) ) {
+			tip = tip.replace(tooltipAccessKeyRegexp,
+					  "["+tooltipAccessKeyPrefix+key+"]");
+			element.setAttribute("title", tip );
+		}
+	}
+}
+
+/**
+ * Add a link to one of the portlet menus on the page, including:
+ *
+ * p-cactions: Content actions (shown as tabs above the main content in Monobook)
+ * p-personal: Personal tools (shown at the top right of the page in Monobook)
+ * p-navigation: Navigation
+ * p-tb: Toolbox
+ *
+ * This function exists for the convenience of custom JS authors.  All
+ * but the first three parameters are optional, though providing at
+ * least an id and a tooltip is recommended.
+ *
+ * By default the new link will be added to the end of the list.  To
+ * add the link before a given existing item, pass the DOM node of
+ * that item (easily obtained with document.getElementById()) as the
+ * nextnode parameter; to add the link _after_ an existing item, pass
+ * the node's nextSibling instead.
+ *
+ * @param String portlet -- id of the target portlet ("p-cactions", "p-personal", "p-navigation" or "p-tb")
+ * @param String href -- link URL
+ * @param String text -- link text (will be automatically lowercased by CSS for p-cactions in Monobook)
+ * @param String id -- id of the new item, should be unique and preferably have the appropriate prefix ("ca-", "pt-", "n-" or "t-")
+ * @param String tooltip -- text to show when hovering over the link, without accesskey suffix
+ * @param String accesskey -- accesskey to activate this link (one character, try to avoid conflicts)
+ * @param Node nextnode -- the DOM node before which the new item should be added, should be another item in the same list
+ *
+ * @return Node -- the DOM node of the new item (an LI element) or null
+ */
+function addPortletLink(portlet, href, text, id, tooltip, accesskey, nextnode) {
+	var node = document.getElementById(portlet);
+	if ( !node ) return null;
+	node = node.getElementsByTagName( "ul" )[0];
+	if ( !node ) return null;
+
+	var link = document.createElement( "a" );
+	link.appendChild( document.createTextNode( text ) );
+	link.href = href;
+
+	var item = document.createElement( "li" );
+	item.appendChild( link );
+	if ( id ) item.id = id;
+
+	if ( accesskey ) {
+		link.setAttribute( "accesskey", accesskey );
+		tooltip += " ["+accesskey+"]";
+	}
+	if ( tooltip ) {
+		link.setAttribute( "title", tooltip );
+	}
+	if ( accesskey && tooltip ) {
+		updateTooltipAccessKeys( new Array( link ) );
+	}
+
+	if ( nextnode && nextnode.parentNode == node )
+		node.insertBefore( item, nextnode );
+	else
+		node.appendChild( item );  // IE compatibility (?)
+
+	return item;
+}
+
+
+/**
+ * Set up accesskeys/tooltips from the deprecated ta array.  If doId
+ * is specified, only set up for that id.  Note that this function is
+ * deprecated and will not be supported indefinitely -- use
+ * updateTooltipAccessKey() instead.
+ *
+ * @param mixed doId string or null
+ */
+function akeytt( doId ) {
+	// A lot of user scripts (and some of the code below) break if
+	// ta isn't defined, so we make sure it is.  Explictly using
+	// window.ta avoids a "ta is not defined" error.
+	if (!window.ta) window.ta = new Array;
+
+	// Make a local, possibly restricted, copy to avoid clobbering
+	// the original.
+	var ta;
+	if ( doId ) {
+		ta = new Array;
+		ta[doId] = window.ta[doId];
+	} else {
+		ta = window.ta;
+	}
+
+	// Now deal with evil deprecated ta
+	var watchCheckboxExists = document.getElementById( 'wpWatchthis' ) ? true : false;
+	for (var id in ta) {
+		var n = document.getElementById(id);
+		if (n) {
+			var a = null;
+			var ak = '';
+			// Are we putting accesskey in it
+			if (ta[id][0].length > 0) {
+				// Is this object a object? If not assume it's the next child.
+
+				if (n.nodeName.toLowerCase() == "a") {
+					a = n;
+				} else {
+					a = n.childNodes[0];
+				}
+			 	// Don't add an accesskey for the watch tab if the watch
+			 	// checkbox is also available.
+				if (a && ((id != 'ca-watch' && id != 'ca-unwatch') || !watchCheckboxExists)) {
+					a.accessKey = ta[id][0];
+					ak = ' ['+tooltipAccessKeyPrefix+ta[id][0]+']';
+				}
+			} else {
+				// We don't care what type the object is when assigning tooltip
+				a = n;
+				ak = '';
+			}
+
+			if (a) {
+				a.title = ta[id][1]+ak;
+			}
+		}
+	}
+}
+
+function setupRightClickEdit() {
+	if (document.getElementsByTagName) {
+		var spans = document.getElementsByTagName('span');
+		for (var i = 0; i < spans.length; i++) {
+			var el = spans[i];
+			if(el.className == 'editsection') {
+				addRightClickEditHandler(el);
+			}
+		}
+	}
+}
+
+function addRightClickEditHandler(el) {
+	for (var i = 0; i < el.childNodes.length; i++) {
+		var link = el.childNodes[i];
+		if (link.nodeType == 1 && link.nodeName.toLowerCase() == 'a') {
+			var editHref = link.getAttribute('href');
+			// find the enclosing (parent) header
+			var prev = el.parentNode;
+			if (prev && prev.nodeType == 1 &&
+			prev.nodeName.match(/^[Hh][1-6]$/)) {
+				prev.oncontextmenu = function(e) {
+					if (!e) { e = window.event; }
+					// e is now the event in all browsers
+					var targ;
+					if (e.target) { targ = e.target; }
+					else if (e.srcElement) { targ = e.srcElement; }
+					if (targ.nodeType == 3) { // defeat Safari bug
+						targ = targ.parentNode;
+					}
+					// targ is now the target element
+
+					// We don't want to deprive the noble reader of a context menu
+					// for the section edit link, do we?  (Might want to extend this
+					// to all <a>'s?)
+					if (targ.nodeName.toLowerCase() != 'a'
+					|| targ.parentNode.className != 'editsection') {
+						document.location = editHref;
+						return false;
+					}
+					return true;
+				};
+			}
+		}
+	}
+}
+
+var checkboxes;
+var lastCheckbox;
+
+function setupCheckboxShiftClick() {
+	checkboxes = [];
+	lastCheckbox = null;
+	var inputs = document.getElementsByTagName('input');
+	addCheckboxClickHandlers(inputs);
+}
+
+function addCheckboxClickHandlers(inputs, start) {
+	if ( !start) start = 0;
+
+	var finish = start + 250;
+	if ( finish > inputs.length )
+		finish = inputs.length;
+
+	for ( var i = start; i < finish; i++ ) {
+		var cb = inputs[i];
+		if ( !cb.type || cb.type.toLowerCase() != 'checkbox' )
+			continue;
+		cb.index = checkboxes.push(cb) - 1;
+		cb.onmouseup = checkboxMouseupHandler;
+	}
+
+	if ( finish < inputs.length ) {
+		setTimeout( function () {
+			addCheckboxClickHandlers(inputs, finish);
+		}, 200 );
+	}
+}
+
+function checkboxMouseupHandler(e) {
+	if (typeof e == 'undefined') {
+		e = window.event;
+	}
+	if ( !e.shiftKey || lastCheckbox === null ) {
+		lastCheckbox = this.index;
+		return true;
+	}
+	var endState = !this.checked;
+	if ( is_opera ) { // opera has already toggled the checkbox by this point
+		endState = !endState;
+	}
+	var start, finish;
+	if ( this.index < lastCheckbox ) {
+		start = this.index + 1;
+		finish = lastCheckbox;
+	} else {
+		start = lastCheckbox;
+		finish = this.index - 1;
+	}
+	for (var i = start; i <= finish; ++i ) {
+		checkboxes[i].checked = endState;
+	}
+	lastCheckbox = this.index;
+	return true;
+}
+
+function toggle_element_activation(ida,idb) {
+	if (!document.getElementById) {
+		return;
+	}
+	document.getElementById(ida).disabled=true;
+	document.getElementById(idb).disabled=false;
+}
+
+function toggle_element_check(ida,idb) {
+	if (!document.getElementById) {
+		return;
+	}
+	document.getElementById(ida).checked=true;
+	document.getElementById(idb).checked=false;
+}
+
+function fillDestFilename(id) {
+	if (!document.getElementById) {
+		return;
+	}
+	var path = document.getElementById(id).value;
+	// Find trailing part
+	var slash = path.lastIndexOf('/');
+	var backslash = path.lastIndexOf('\\');
+	var fname;
+	if (slash == -1 && backslash == -1) {
+		fname = path;
+	} else if (slash > backslash) {
+		fname = path.substring(slash+1, 10000);
+	} else {
+		fname = path.substring(backslash+1, 10000);
+	}
+
+	// Capitalise first letter and replace spaces by underscores
+	fname = fname.charAt(0).toUpperCase().concat(fname.substring(1,10000)).replace(/ /g, '_');
+
+	// Output result
+	var destFile = document.getElementById('wpDestFile');
+	if (destFile) {
+		destFile.value = fname;
+	}
+}
+
+function scrollEditBox() {
+	var editBoxEl = document.getElementById("wpTextbox1");
+	var scrollTopEl = document.getElementById("wpScrolltop");
+	var editFormEl = document.getElementById("editform");
+
+	if (editBoxEl && scrollTopEl) {
+		if (scrollTopEl.value) { editBoxEl.scrollTop = scrollTopEl.value; }
+		editFormEl.onsubmit = function() {
+			document.getElementById("wpScrolltop").value = document.getElementById("wpTextbox1").scrollTop;
+		};
+	}
+}
+
+hookEvent("load", scrollEditBox);
+
+var allmessages_nodelist = false;
+var allmessages_modified = false;
+var allmessages_timeout = false;
+var allmessages_running = false;
+
+function allmessagesmodified() {
+	allmessages_modified = !allmessages_modified;
+	allmessagesfilter();
+}
+
+function allmessagesfilter() {
+	if ( allmessages_timeout )
+		window.clearTimeout( allmessages_timeout );
+
+	if ( !allmessages_running )
+		allmessages_timeout = window.setTimeout( 'allmessagesfilter_do();', 500 );
+}
+
+function allmessagesfilter_do() {
+	if ( !allmessages_nodelist )
+		return;
+
+	var text = document.getElementById('allmessagesinput').value;
+	var nodef = allmessages_modified;
+
+	allmessages_running = true;
+
+	for ( var name in allmessages_nodelist ) {
+		var nodes = allmessages_nodelist[name];
+		var display = ( name.indexOf( text ) == -1 ? 'none' : '' );
+
+		for ( var i = 0; i < nodes.length; i++)
+			nodes[i].style.display =
+				( nodes[i].className == "def" && nodef
+				  ? 'none' : display );
+	}
+
+	if ( text != document.getElementById('allmessagesinput').value ||
+	     nodef != allmessages_modified )
+		allmessagesfilter_do();  // repeat
+
+	allmessages_running = false;
+}
+
+function allmessagesfilter_init() {
+	if ( allmessages_nodelist )
+		return;
+
+	var nodelist = new Array();
+	var templist = new Array();
+
+	var table = document.getElementById('allmessagestable');
+	if ( !table ) return;
+
+	var rows = document.getElementsByTagName('tr');
+	for ( var i = 0; i < rows.length; i++ ) {
+		var id = rows[i].getAttribute('id')
+		if ( id && id.substring(0,16) != 'sp-allmessages-r' ) continue;
+		templist[ id ] = rows[i];
+	}
+
+	var spans = table.getElementsByTagName('span');
+	for ( var i = 0; i < spans.length; i++ ) {
+		var id = spans[i].getAttribute('id')
+		if ( id && id.substring(0,17) != 'sp-allmessages-i-' ) continue;
+		if ( !spans[i].firstChild || spans[i].firstChild.nodeType != 3 ) continue;
+
+		var nodes = new Array();
+		var row1 = templist[ id.replace('i', 'r1') ];
+		var row2 = templist[ id.replace('i', 'r2') ];
+
+		if ( row1 ) nodes[nodes.length] = row1;
+		if ( row2 ) nodes[nodes.length] = row2;
+		nodelist[ spans[i].firstChild.nodeValue ] = nodes;
+	}
+
+	var k = document.getElementById('allmessagesfilter');
+	if (k) { k.style.display = ''; }
+
+	allmessages_nodelist = nodelist;
+}
+
+hookEvent( "load", allmessagesfilter_init );
+
+/*
+	Written by Jonathan Snook, http://www.snook.ca/jonathan
+	Add-ons by Robert Nyman, http://www.robertnyman.com
+	Author says "The credit comment is all it takes, no license. Go crazy with it!:-)"
+	From http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
+*/
+function getElementsByClassName(oElm, strTagName, oClassNames){
+	var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
+	var arrReturnElements = new Array();
+	var arrRegExpClassNames = new Array();
+	if(typeof oClassNames == "object"){
+		for(var i=0; i<oClassNames.length; i++){
+			arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames[i].replace(/\-/g, "\\-") + "(\\s|$)"));
+		}
+	}
+	else{
+		arrRegExpClassNames.push(new RegExp("(^|\\s)" + oClassNames.replace(/\-/g, "\\-") + "(\\s|$)"));
+	}
+	var oElement;
+	var bMatchesAll;
+	for(var j=0; j<arrElements.length; j++){
+		oElement = arrElements[j];
+		bMatchesAll = true;
+		for(var k=0; k<arrRegExpClassNames.length; k++){
+			if(!arrRegExpClassNames[k].test(oElement.className)){
+				bMatchesAll = false;
+				break;
+			}
+		}
+		if(bMatchesAll){
+			arrReturnElements.push(oElement);
+		}
+	}
+	return (arrReturnElements)
+}
+
+function redirectToFragment(fragment) {
+	var match = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
+	if (match) {
+		var webKitVersion = parseInt(match[1]);
+		if (webKitVersion < 420) {
+			// Released Safari w/ WebKit 418.9.1 messes up horribly
+			// Nightlies of 420+ are ok
+			return;
+		}
+	}
+	if (is_gecko) {
+		// Mozilla needs to wait until after load, otherwise the window doesn't scroll
+		addOnloadHook(function () {
+			if (window.location.hash == "")
+				window.location.hash = fragment;
+		});
+	} else {
+		if (window.location.hash == "")
+			window.location.hash = fragment;
+	}
+}
+
+/*
+ * Table sorting script  by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
+ * Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
+ * Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .
+ *
+ * Copyright (c) 1997-2006 Stuart Langridge, Joost de Valk.
+ *
+ * @todo don't break on colspans/rowspans (bug 8028)
+ * @todo language-specific digit grouping/decimals (bug 8063)
+ * @todo support all accepted date formats (bug 8226)
+ */
+
+var ts_image_path = stylepath+"/common/images/";
+var ts_image_up = "sort_up.gif";
+var ts_image_down = "sort_down.gif";
+var ts_image_none = "sort_none.gif";
+var ts_europeandate = wgContentLanguage != "en"; // The non-American-inclined can change to "true"
+var ts_alternate_row_colors = true;
+var SORT_COLUMN_INDEX;
+
+function sortables_init() {
+	var idnum = 0;
+	// Find all tables with class sortable and make them sortable
+	var tables = getElementsByClassName(document, "table", "sortable");
+	for (var ti = 0; ti < tables.length ; ti++) {
+		if (!tables[ti].id) {
+			tables[ti].setAttribute('id','sortable_table_id_'+idnum);
+			++idnum;
+		}
+		ts_makeSortable(tables[ti]);
+	}
+}
+
+function ts_makeSortable(table) {
+	var firstRow;
+	if (table.rows && table.rows.length > 0) {
+		if (table.tHead && table.tHead.rows.length > 0) {
+			firstRow = table.tHead.rows[table.tHead.rows.length-1];
+		} else {
+			firstRow = table.rows[0];
+		}
+	}
+	if (!firstRow) return;
+
+	// We have a first row: assume it's the header, and make its contents clickable links
+	for (var i = 0; i < firstRow.cells.length; i++) {
+		var cell = firstRow.cells[i];
+		if ((" "+cell.className+" ").indexOf(" unsortable ") == -1) {
+			cell.innerHTML += '&nbsp;&nbsp;<a href="#" class="sortheader" onclick="ts_resortTable(this);return false;"><span class="sortarrow"><img src="'+ ts_image_path + ts_image_none + '" alt="&darr;"/></span></a>';
+		}
+	}
+	if (ts_alternate_row_colors) {
+		ts_alternate(table);
+	}
+}
+
+function ts_getInnerText(el) {
+	if (typeof el == "string") return el;
+	if (typeof el == "undefined") { return el };
+	if (el.innerText) return el.innerText;	// Not needed but it is faster
+	var str = "";
+
+	var cs = el.childNodes;
+	var l = cs.length;
+	for (var i = 0; i < l; i++) {
+		switch (cs[i].nodeType) {
+			case 1: //ELEMENT_NODE
+				str += ts_getInnerText(cs[i]);
+				break;
+			case 3:	//TEXT_NODE
+				str += cs[i].nodeValue;
+				break;
+		}
+	}
+	return str;
+}
+
+function ts_resortTable(lnk) {
+	// get the span
+	var span = lnk.getElementsByTagName('span')[0];
+
+	var td = lnk.parentNode;
+	var tr = td.parentNode;
+	var column = td.cellIndex;
+
+	var table = tr.parentNode;
+	while (table && !(table.tagName && table.tagName.toLowerCase() == 'table'))
+		table = table.parentNode;
+	if (!table) return;
+
+	// Work out a type for the column
+	if (table.rows.length <= 1) return;
+
+	// Skip the first row if that's where the headings are
+	var rowStart = (table.tHead && table.tHead.rows.length > 0 ? 0 : 1);
+
+	var itm = "";
+	for (var i = rowStart; i < table.rows.length; i++) {
+		if (table.rows[i].cells.length > column) {
+			itm = ts_getInnerText(table.rows[i].cells[column]);
+			itm = itm.replace(/^[\s\xa0]+/, "").replace(/[\s\xa0]+$/, "");
+			if (itm != "") break;
+		}
+	}
+
+	sortfn = ts_sort_caseinsensitive;
+	if (itm.match(/^\d\d[\/. -][a-zA-Z]{3}[\/. -]\d\d\d\d$/))
+		sortfn = ts_sort_date;
+	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d\d\d$/))
+		sortfn = ts_sort_date;
+	if (itm.match(/^\d\d[\/.-]\d\d[\/.-]\d\d$/))
+		sortfn = ts_sort_date;
+	if (itm.match(/^[\u00a3$\u20ac]/)) // pound dollar euro
+		sortfn = ts_sort_currency;
+	if (itm.match(/^[\d.,]+\%?$/))
+		sortfn = ts_sort_numeric;
+
+	var reverse = (span.getAttribute("sortdir") == 'down');
+
+	var newRows = new Array();
+	for (var j = rowStart; j < table.rows.length; j++) {
+		var row = table.rows[j];
+		var keyText = ts_getInnerText(row.cells[column]);
+		var oldIndex = (reverse ? -j : j);
+
+		newRows[newRows.length] = new Array(row, keyText, oldIndex);
+	}
+
+	newRows.sort(sortfn);
+
+	var arrowHTML;
+	if (reverse) {
+			arrowHTML = '<img src="'+ ts_image_path + ts_image_down + '" alt="&darr;"/>';
+			newRows.reverse();
+			span.setAttribute('sortdir','up');
+	} else {
+			arrowHTML = '<img src="'+ ts_image_path + ts_image_up + '" alt="&uarr;"/>';
+			span.setAttribute('sortdir','down');
+	}
+
+	// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
+	// don't do sortbottom rows
+	for (var i = 0; i < newRows.length; i++) {
+		if ((" "+newRows[i][0].className+" ").indexOf(" sortbottom ") == -1)
+			table.tBodies[0].appendChild(newRows[i][0]);
+	}
+	// do sortbottom rows only
+	for (var i = 0; i < newRows.length; i++) {
+		if ((" "+newRows[i][0].className+" ").indexOf(" sortbottom ") != -1)
+			table.tBodies[0].appendChild(newRows[i][0]);
+	}
+
+	// Delete any other arrows there may be showing
+	var spans = getElementsByClassName(tr, "span", "sortarrow");
+	for (var i = 0; i < spans.length; i++) {
+		spans[i].innerHTML = '<img src="'+ ts_image_path + ts_image_none + '" alt="&darr;"/>';
+	}
+	span.innerHTML = arrowHTML;
+
+	ts_alternate(table);		
+}
+
+function ts_dateToSortKey(date) {	
+	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
+	if (date.length == 11) {
+		switch (date.substr(3,3).toLowerCase()) {
+			case "jan": var month = "01"; break;
+			case "feb": var month = "02"; break;
+			case "mar": var month = "03"; break;
+			case "apr": var month = "04"; break;
+			case "may": var month = "05"; break;
+			case "jun": var month = "06"; break;
+			case "jul": var month = "07"; break;
+			case "aug": var month = "08"; break;
+			case "sep": var month = "09"; break;
+			case "oct": var month = "10"; break;
+			case "nov": var month = "11"; break;
+			case "dec": var month = "12"; break;
+			// default: var month = "00";
+		}
+		return date.substr(7,4)+month+date.substr(0,2);
+	} else if (date.length == 10) {
+		if (ts_europeandate == false) {
+			return date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
+		} else {
+			return date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
+		}
+	} else if (date.length == 8) {
+		yr = date.substr(6,2);
+		if (parseInt(yr) < 50) { 
+			yr = '20'+yr; 
+		} else { 
+			yr = '19'+yr; 
+		}
+		if (ts_europeandate == true) {
+			return yr+date.substr(3,2)+date.substr(0,2);
+		} else {
+			return yr+date.substr(0,2)+date.substr(3,2);
+		}
+	}
+	return "00000000";
+}
+
+function ts_parseFloat(num) {
+	if (!num) return 0;
+	num = parseFloat(num.replace(/,/, ""));
+	return (isNaN(num) ? 0 : num);
+}
+
+function ts_sort_date(a,b) {
+	var aa = ts_dateToSortKey(a[1]);
+	var bb = ts_dateToSortKey(b[1]);
+	return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
+}
+
+function ts_sort_currency(a,b) {
+	var aa = ts_parseFloat(a[1].replace(/[^0-9.]/g,''));
+	var bb = ts_parseFloat(b[1].replace(/[^0-9.]/g,''));
+	return (aa != bb ? aa - bb : a[2] - b[2]);
+}
+
+function ts_sort_numeric(a,b) {
+	var aa = ts_parseFloat(a[1]);
+	var bb = ts_parseFloat(b[1]);
+	return (aa != bb ? aa - bb : a[2] - b[2]);
+}
+
+function ts_sort_caseinsensitive(a,b) {
+	var aa = a[1].toLowerCase();
+	var bb = b[1].toLowerCase();
+	return (aa < bb ? -1 : aa > bb ? 1 : a[2] - b[2]);
+}
+
+function ts_sort_default(a,b) {
+	return (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : a[2] - b[2]);
+}
+
+function ts_alternate(table) {
+	// Take object table and get all it's tbodies.
+	var tableBodies = table.getElementsByTagName("tbody");
+	// Loop through these tbodies
+	for (var i = 0; i < tableBodies.length; i++) {
+		// Take the tbody, and get all it's rows
+		var tableRows = tableBodies[i].getElementsByTagName("tr");
+		// Loop through these rows
+		// Start at 1 because we want to leave the heading row untouched
+		for (var j = 0; j < tableRows.length; j++) {
+			// Check if j is even, and apply classes for both possible results
+			var oldClasses = tableRows[j].className.split(" ");
+			var newClassName = "";
+			for (var k = 0; k < oldClasses.length; k++) {
+				if (oldClasses[k] != "" && oldClasses[k] != "even" && oldClasses[k] != "odd")
+					newClassName += oldClasses[k] + " ";
+			}
+			tableRows[j].className = newClassName + (j % 2 == 0 ? "even" : "odd");
+		}
+	}
+}
+
+/*
+ * End of table sorting code
+ */
+
+function runOnloadHook() {
+	// don't run anything below this for non-dom browsers
+	if (doneOnloadHook || !(document.getElementById && document.getElementsByTagName)) {
+		return;
+	}
+
+	// set this before running any hooks, since any errors below
+	// might cause the function to terminate prematurely
+	doneOnloadHook = true;
+
+	histrowinit();
+	unhidetzbutton();
+	tabbedprefs();
+	updateTooltipAccessKeys( null );
+	akeytt( null );
+	scrollEditBox();
+	setupCheckboxShiftClick();
+	sortables_init();
+
+	// Run any added-on functions
+	for (var i = 0; i < onloadFuncts.length; i++) {
+		onloadFuncts[i]();
+	}
+}
+
+//note: all skins should call runOnloadHook() at the end of html output,
+//      so the below should be redundant. It's there just in case.
+hookEvent("load", runOnloadHook);
+
+hookEvent("load", mwSetupToolbar);

BIN
talks/bayes_theorem/Bayes'_theorem Files/wikimedia-button.png


+ 478 - 0
talks/bayes_theorem/Bayes'_theorem.html

@@ -0,0 +1,478 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" dir="ltr" lang="en"><head>
+
+	
+		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+				<meta name="keywords" content="Bayes' theorem,1702,1761,1763,1774,A posteriori,Absolutely continuous,Abuse of notation,Athanasios Papoulis,Bayesian inference,Bayesian network">
+		<link rel="shortcut icon" href="http://en.wikipedia.org/favicon.ico">
+		<link rel="search" type="application/opensearchdescription+xml" href="http://en.wikipedia.org/w/opensearch_desc.php" title="Wikipedia (English)">
+		<link rel="copyright" href="http://www.gnu.org/copyleft/fdl.html"><title>Bayes' theorem - Wikipedia, the free encyclopedia</title>
+
+		<link rel="stylesheet" type="text/css" media="screen,projection" href="Bayes%27_theorem%20Files/main.css">
+		<link rel="stylesheet" type="text/css" media="print" href="Bayes%27_theorem%20Files/commonPrint.css">
+		<link rel="stylesheet" type="text/css" media="handheld" href="Bayes%27_theorem%20Files/handheld.css"><!--[if lt IE 5.5000]><style type="text/css">@import "/skins-1.5/monobook/IE50Fixes.css?64";</style><![endif]--><!--[if IE 5.5000]><style type="text/css">@import "/skins-1.5/monobook/IE55Fixes.css?64";</style><![endif]--><!--[if IE 6]><style type="text/css">@import "/skins-1.5/monobook/IE60Fixes.css?64";</style><![endif]--><!--[if IE 7]><style type="text/css">@import "/skins-1.5/monobook/IE70Fixes.css?64";</style><![endif]--><!--[if lt IE 7]><script type="text/javascript" src="/skins-1.5/common/IEFixes.js?64"></script>
+		<meta http-equiv="imagetoolbar" content="no" /><![endif]-->
+		
+		
+		
+		
+		
+		
+		<script type="text/javascript">/*<![CDATA[*/
+var skin = "monobook";
+var stylepath = "/skins-1.5";
+var wgArticlePath = "/wiki/$1";
+var wgScriptPath = "/w";
+var wgServer = "http://en.wikipedia.org";
+var wgCanonicalNamespace = "";
+var wgCanonicalSpecialPageName = false;
+var wgNamespaceNumber = 0;
+var wgPageName = "Bayes\'_theorem";
+var wgTitle = "Bayes\' theorem";
+var wgAction = "view";
+var wgArticleId = "49569";
+var wgIsArticle = true;
+var wgUserName = null;
+var wgUserGroups = null;
+var wgUserLanguage = "en";
+var wgContentLanguage = "en";
+var wgBreakFrames = false;
+var wgCurRevisionId = "128360312";
+/*]]>*/</script>
+                
+		<script type="text/javascript" src="Bayes%27_theorem%20Files/wikibits.js"><!-- wikibits js --></script>
+		<script type="text/javascript" src="Bayes%27_theorem%20Files/index.php"><!-- site js --></script>
+		<style type="text/css">/*<![CDATA[*/
+@import "/w/index.php?title=MediaWiki:Common.css&usemsgcache=yes&action=raw&ctype=text/css&smaxage=2678400";
+@import "/w/index.php?title=MediaWiki:Monobook.css&usemsgcache=yes&action=raw&ctype=text/css&smaxage=2678400";
+@import "/w/index.php?title=-&action=raw&gen=css&maxage=2678400";
+/*]]>*/</style><!-- Head Scripts -->
+		
+		<script type="text/javascript" src="Bayes%27_theorem%20Files/ajax.js"></script></head><body class="mediawiki ns-0 ltr page-Bayes_theorem">
+	<div id="globalWrapper">
+		<div id="column-content">
+	<div id="content">
+		<a name="top" id="top"></a>
+		<div id="siteNotice"><script type="text/javascript" language="JavaScript">
+<!--
+document.writeln("\x3cdiv style=\"position:absolute; z-index:100; right:100px; top:-0px;\" class=\"metadata\" id=\"donate\"\x3e\n\x3cdiv style=\"text-align:right; font-size:80%\"\x3e\x3ci\x3eYour \x3cb\x3e\x3ca href=\"http://wikimediafoundation.org/wiki/Fundraising\" class=\"extiw\" title=\"wikimedia:Fundraising\"\x3econtinued donations\x3c/a\x3e\x3c/b\x3e keep Wikipedia running!\x3c/i\x3e\n\x3c/div\x3e\x3c/div\x3e\n");
+-->
+</script><div style="position: absolute; z-index: 100; right: 100px; top: 0px;" class="metadata" id="donate">
+<div style="text-align: right; font-size: 80%;"><i>Your <b><a href="http://wikimediafoundation.org/wiki/Fundraising" class="extiw" title="wikimedia:Fundraising">continued donations</a></b> keep Wikipedia running!</i>
+</div></div>
+
+</div>		<h1 class="firstHeading">Bayes' theorem</h1>
+		<div id="bodyContent">
+			<h3 id="siteSub">From Wikipedia, the free encyclopedia</h3>
+			<div id="contentSub"></div>
+									<div id="jump-to-nav">Jump to: <a href="#column-one">navigation</a>, <a href="#searchInput">search</a></div>			<!-- start content -->
+			<p><b>Bayes' theorem</b> (also known as <b>Bayes' rule</b> or <b>Bayes' law</b>) is a result in <a href="http://en.wikipedia.org/wiki/Probability_theory" title="Probability theory">probability theory</a>, which relates the <a href="http://en.wikipedia.org/wiki/Conditional_probability" title="Conditional probability">conditional</a> and <a href="http://en.wikipedia.org/wiki/Conditional_probability" title="Conditional probability">marginal</a> <a href="http://en.wikipedia.org/wiki/Probability_distribution" title="Probability distribution">probability distributions</a> of <a href="http://en.wikipedia.org/wiki/Random_variable" title="Random variable">random variables</a>. In some interpretations of <a href="http://en.wikipedia.org/wiki/Probability" title="Probability">probability</a>, Bayes' theorem tells how to update or revise beliefs in light of new evidence <i><a href="http://en.wikipedia.org/wiki/A_posteriori" title="A posteriori">a posteriori</a></i>.</p>
+<p>The probability of an <a href="http://en.wikipedia.org/wiki/Event_%28probability_theory%29" title="Event (probability theory)">event</a> <i>A</i> conditional on another event <i>B</i> is generally different from the probability of <i>B</i> conditional on <i>A</i>. However, there is a definite relationship between the two, and Bayes' theorem is the statement of that relationship.</p>
+<p>As a formal <a href="http://en.wikipedia.org/wiki/Theorem" title="Theorem">theorem</a>, Bayes' theorem is valid in all interpretations of probability. However, <a href="http://en.wikipedia.org/wiki/Frequentist" title="Frequentist">frequentist</a> and <a href="http://en.wikipedia.org/wiki/Bayesian_probability" title="Bayesian probability">Bayesian</a>
+interpretations disagree about the kinds of things to which
+probabilities should be assigned in applications: frequentists assign
+probabilities to random events according to their frequencies of
+occurrence or to subsets of populations as proportions of the whole;
+Bayesians assign probabilities to propositions that are uncertain. A
+consequence is that Bayesians have more frequent occasion to use Bayes'
+theorem. The articles on <a href="http://en.wikipedia.org/wiki/Bayesian_probability" title="Bayesian probability">Bayesian probability</a> and <a href="http://en.wikipedia.org/wiki/Frequency_probability" title="Frequency probability">frequentist probability</a> discuss these debates at greater length.</p>
+<table id="toc" class="toc" summary="Contents">
+<tbody><tr>
+<td>
+<div id="toctitle">
+<h2>Contents</h2>
+ <span class="toctoggle">[<a href="javascript:toggleToc()" class="internal" id="togglelink">hide</a>]</span></div>
+<ul>
+<li class="toclevel-1"><a href="#Statement_of_Bayes.27_theorem"><span class="tocnumber">1</span> <span class="toctext">Statement of Bayes' theorem</span></a></li>
+<li class="toclevel-1"><a href="#Derivation_from_conditional_probabilities"><span class="tocnumber">2</span> <span class="toctext">Derivation from conditional probabilities</span></a></li>
+<li class="toclevel-1"><a href="#Alternative_forms_of_Bayes.27_theorem"><span class="tocnumber">3</span> <span class="toctext">Alternative forms of Bayes' theorem</span></a>
+<ul>
+<li class="toclevel-2"><a href="#Bayes.27_theorem_in_terms_of_odds_and_likelihood_ratio"><span class="tocnumber">3.1</span> <span class="toctext">Bayes' theorem in terms of odds and likelihood ratio</span></a></li>
+<li class="toclevel-2"><a href="#Bayes.27_theorem_for_probability_densities"><span class="tocnumber">3.2</span> <span class="toctext">Bayes' theorem for probability densities</span></a></li>
+<li class="toclevel-2"><a href="#Abstract_Bayes.27_theorem"><span class="tocnumber">3.3</span> <span class="toctext">Abstract Bayes' theorem</span></a></li>
+<li class="toclevel-2"><a href="#Extensions_of_Bayes.27_theorem"><span class="tocnumber">3.4</span> <span class="toctext">Extensions of Bayes' theorem</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1"><a href="#Examples"><span class="tocnumber">4</span> <span class="toctext">Examples</span></a>
+<ul>
+<li class="toclevel-2"><a href="#Example_.231:__Conditional_probabilities"><span class="tocnumber">4.1</span> <span class="toctext">Example #1: Conditional probabilities</span></a>
+<ul>
+<li class="toclevel-3"><a href="#Tables_of_occurrences_and_relative_frequencies"><span class="tocnumber">4.1.1</span> <span class="toctext">Tables of occurrences and relative frequencies</span></a></li>
+</ul>
+</li>
+<li class="toclevel-2"><a href="#Example_.232:__Drug_testing"><span class="tocnumber">4.2</span> <span class="toctext">Example #2: Drug testing</span></a></li>
+<li class="toclevel-2"><a href="#Example_.233:__Bayesian_inference"><span class="tocnumber">4.3</span> <span class="toctext">Example #3: Bayesian inference</span></a></li>
+<li class="toclevel-2"><a href="#Example_.234:_The_Monty_Hall_problem"><span class="tocnumber">4.4</span> <span class="toctext">Example #4: The Monty Hall problem</span></a></li>
+</ul>
+</li>
+<li class="toclevel-1"><a href="#Historical_remarks"><span class="tocnumber">5</span> <span class="toctext">Historical remarks</span></a></li>
+<li class="toclevel-1"><a href="#See_also"><span class="tocnumber">6</span> <span class="toctext">See also</span></a></li>
+<li class="toclevel-1"><a href="#References"><span class="tocnumber">7</span> <span class="toctext">References</span></a>
+<ul>
+<li class="toclevel-2"><a href="#Versions_of_the_essay"><span class="tocnumber">7.1</span> <span class="toctext">Versions of the essay</span></a></li>
+<li class="toclevel-2"><a href="#Commentaries"><span class="tocnumber">7.2</span> <span class="toctext">Commentaries</span></a></li>
+<li class="toclevel-2"><a href="#Additional_material"><span class="tocnumber">7.3</span> <span class="toctext">Additional material</span></a></li>
+</ul>
+</li>
+</ul>
+</td>
+</tr>
+</tbody></table>
+<script type="text/javascript">
+//<![CDATA[
+ if (window.showTocToggle) { var tocShowText = "show"; var tocHideText = "hide"; showTocToggle(); } 
+//]]>
+</script>
+<p><a name="Statement_of_Bayes.27_theorem" id="Statement_of_Bayes.27_theorem"></a></p>
+<h2><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=1" title="Edit section: Statement of Bayes' theorem">edit</a>]</span> <span class="mw-headline">Statement of Bayes' theorem</span></h2>
+<p>Bayes' theorem relates the conditional and marginal probabilities of <a href="http://en.wikipedia.org/wiki/Stochastic_process" title="Stochastic process">stochastic</a> <a href="http://en.wikipedia.org/wiki/Event_%28probability_theory%29" title="Event (probability theory)">events</a> <i>A</i> and <i>B</i>:</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/246afc51d522fc2830a4e2ab6a52459d.png" alt="\begin{align}   P(A|B)  &amp;  =       \frac{P(B | A)\, P(A)}{P(B)}  \\             &amp;  \propto L(A | B)\, P(A)   \end{align}"></dd>
+</dl>
+<p>where <i>L</i>(<i>A</i>|<i>B</i>) is the <a href="http://en.wikipedia.org/wiki/Likelihood_function" title="Likelihood function">likelihood</a> of <i>A</i> given fixed <i>B</i>. Although in this case the relationship <span class="texhtml"><i>P</i>(<i>B</i> | <i>A</i>) = <i>L</i>(<i>A</i> | <i>B</i>)</span>, in other cases <a href="http://en.wikipedia.org/wiki/Likelihood_function" title="Likelihood function">likelihood</a> <i>L</i> can be multiplied by a constant factor, so that it is proportional to, but does not equal <a href="http://en.wikipedia.org/wiki/Probability" title="Probability">probability</a> <i>P</i>.</p>
+<p>Each term in Bayes' theorem has a conventional name:</p>
+<ul>
+<li>P(<i>A</i>) is the <a href="http://en.wikipedia.org/wiki/Prior_probability" title="Prior probability">prior probability</a> or <a href="http://en.wikipedia.org/wiki/Marginal_probability" title="Marginal probability">marginal probability</a> of <i>A</i>. It is "prior" in the sense that it does not take into account any information about <i>B</i>.</li>
+<li>P(<i>A</i>|<i>B</i>) is the <a href="http://en.wikipedia.org/wiki/Conditional_probability" title="Conditional probability">conditional probability</a> of <i>A</i>, given <i>B</i>. It is also called the <a href="http://en.wikipedia.org/wiki/Posterior_probability" title="Posterior probability">posterior probability</a> because it is derived from or depends upon the specified value of <i>B</i>.</li>
+<li>P(<i>B</i>|<i>A</i>) is the conditional probability of <i>B</i> given <i>A</i>.</li>
+<li>P(<i>B</i>) is the prior or marginal probability of <i>B</i>, and acts as a <a href="http://en.wikipedia.org/wiki/Normalizing_constant" title="Normalizing constant">normalizing constant</a>.</li>
+</ul>
+<p>With this terminology, the theorem may be paraphrased as</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/3e294d8052aa226062a1a1158be30079.png" alt="\mbox{posterior} = \frac{\mbox{likelihood} \times \mbox{prior}} {\mbox{normalizing constant}}"></dd>
+</dl>
+<p>In words: the posterior probability is proportional to the product of the prior probability and the likelihood.</p>
+<p>In addition, the ratio P(<i>B</i>|<i>A</i>)/P(<i>B</i>) is sometimes called the standardised likelihood, so the theorem may also be paraphrased as</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/9c1f50e099b3ae7e1ece4cb35ca21356.png" alt="\mbox{posterior} = {\mbox{standardised likelihood} \times \mbox{prior} }.\,"></dd>
+</dl>
+<p><a name="Derivation_from_conditional_probabilities" id="Derivation_from_conditional_probabilities"></a></p>
+<h2><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=2" title="Edit section: Derivation from conditional probabilities">edit</a>]</span> <span class="mw-headline">Derivation from conditional probabilities</span></h2>
+<p>To derive the theorem, we start from the definition of <a href="http://en.wikipedia.org/wiki/Conditional_probability" title="Conditional probability">conditional probability</a>. The probability of event <i>A</i> given event <i>B</i> is</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/a6df7814a46fc4c9670cc510b72bb318.png" alt="P(A|B)=\frac{P(A \cap B)}{P(B)}."></dd>
+</dl>
+<p>Likewise, the probability of event <i>B</i> given event <i>A</i> is</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/8b6c81124815aad54c91c42b3261165d.png" alt="P(B|A) = \frac{P(A \cap B)}{P(A)}. \!"></dd>
+</dl>
+<p>Rearranging and combining these two equations, we find</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/efaf8fda8a92eeb2d8cf70468c20ed5a.png" alt="P(A|B)\, P(B) = P(A \cap B) = P(B|A)\, P(A). \!"></dd>
+</dl>
+<p>This <a href="http://en.wikipedia.org/wiki/Lemma_%28mathematics%29" title="Lemma (mathematics)">lemma</a> is sometimes called the product rule for probabilities. Dividing both sides by Pr(<i>B</i>), providing that it is non-zero, we obtain Bayes' theorem:</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/f9fdf7c75c7f4ddae1140dd2a9e73714.png" alt="P(A|B) = \frac{P(B|A)\,P(A)}{P(B)}. \!"></dd>
+</dl>
+<p><a name="Alternative_forms_of_Bayes.27_theorem" id="Alternative_forms_of_Bayes.27_theorem"></a></p>
+<h2><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=3" title="Edit section: Alternative forms of Bayes' theorem">edit</a>]</span> <span class="mw-headline">Alternative forms of Bayes' theorem</span></h2>
+<p>Bayes' theorem is often embellished by noting that</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/07cf5446f48e526e1abcdb471e60089a.png" alt="P(B) = P(A\cap B) + P(A^C\cap B) = P(B|A) P(A) + P(B|A^C) P(A^C)\,"></dd>
+</dl>
+<p>where <i>A</i><sup><i>C</i></sup> is the <a href="http://en.wikipedia.org/wiki/Complement_%28set_theory%29#Absolute_complement" title="Complement (set theory)">complementary</a> event of <i>A</i> (often called "not A"). So the theorem can be restated as</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/b337da08b983c9e9c6f741d856b4b72c.png" alt="P(A|B) = \frac{P(B | A)\, P(A)}{P(B|A) P(A) + P(B|A^C) P(A^C)}.  \!"></dd>
+</dl>
+<p>More generally, where {<i>A</i><sub><i>i</i></sub>} forms a <a href="http://en.wikipedia.org/wiki/Partition_of_a_set" title="Partition of a set">partition</a> of the event space,</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/daa4b03e8ea857a6957e4a1d0b8ef6b1.png" alt="P(A_i|B) = \frac{P(B | A_i)\, P(A_i)}{\sum_j P(B|A_j)\,P(A_j)} , \!"></dd>
+</dl>
+<p>for any <i>A</i><sub><i>i</i></sub> in the partition.</p>
+<p>See also the <a href="http://en.wikipedia.org/wiki/Law_of_total_probability" title="Law of total probability">law of total probability</a>.</p>
+<p><a name="Bayes.27_theorem_in_terms_of_odds_and_likelihood_ratio" id="Bayes.27_theorem_in_terms_of_odds_and_likelihood_ratio"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=4" title="Edit section: Bayes' theorem in terms of odds and likelihood ratio">edit</a>]</span> <span class="mw-headline">Bayes' theorem in terms of odds and likelihood ratio</span></h3>
+<p>Bayes' theorem can also be written neatly in terms of a <a href="http://en.wikipedia.org/wiki/Likelihood_function" title="Likelihood function">likelihood</a> ratio Λ and <a href="http://en.wikipedia.org/wiki/Odds" title="Odds">odds</a> <i>O</i> as</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/6aa65315e6ae2da1b417789e43b4eb72.png" alt="O(A|B)=O(A) \cdot \Lambda (A|B)"></dd>
+</dl>
+<p>where <img class="tex" src="Bayes%27_theorem%20Files/81be09df6b27e9b3d3ee7c8e4e6ced85.png" alt="O(A|B)=\frac{P(A|B)}{P(A^C|B)} \!"> are the <i>odds</i> of <i>A</i> given <i>B</i>,</p>
+<p>and <img class="tex" src="Bayes%27_theorem%20Files/c4451387126a4c6a51739382cacb9e99.png" alt="O(A)=\frac{P(A)}{P(A^C)} \!"> are the odds of <i>A</i> by itself,</p>
+<p>while <img class="tex" src="Bayes%27_theorem%20Files/32146e2a0949cb320bfd837305e6c133.png" alt="\Lambda (A|B) = \frac{L(A|B)}{L(A^C|B)} = \frac{P(B|A)}{P(B|A^C)} \!"> is the likelihood ratio.</p>
+<p><a name="Bayes.27_theorem_for_probability_densities" id="Bayes.27_theorem_for_probability_densities"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=5" title="Edit section: Bayes' theorem for probability densities">edit</a>]</span> <span class="mw-headline">Bayes' theorem for probability densities</span></h3>
+<p>There is also a version of Bayes' theorem for continuous
+distributions. It is somewhat harder to derive, since probability
+densities, strictly speaking, are not probabilities, so Bayes' theorem
+has to be established by a limit process; see Papoulis (citation
+below), Section 7.3 for an elementary derivation. Bayes's theorem for
+probability densities is formally similar to the theorem for
+probabilities:</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/402214a6b5fb1babe545afc206a96d92.png" alt="f(x|y) = \frac{f(x,y)}{f(y)} = \frac{f(y|x)\,f(x)}{f(y)} \!"></dd>
+</dl>
+<p>and there is an analogous statement of the law of total probability:</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/908da7e26f756ae79f5ac0bfce87c5ce.png" alt="f(x|y) = \frac{f(y|x)\,f(x)}{\int_{-\infty}^{\infty} f(y|x)\,f(x)\,dx}. \!"></dd>
+</dl>
+<p>As in the discrete case, the terms have standard names. <i>f</i>(<i>x</i>, <i>y</i>) is the joint distribution of <i>X</i> and <i>Y</i>, <i>f</i>(<i>x</i>|<i>y</i>) is the posterior distribution of <i>X</i> given <i>Y</i>=<i>y</i>, <i>f</i>(<i>y</i>|<i>x</i>) = <i>L</i>(<i>x</i>|<i>y</i>) is (as a function of <i>x</i>) the likelihood function of <i>X</i> given <i>Y</i>=<i>y</i>, and <i>f</i>(<i>x</i>) and <i>f</i>(<i>y</i>) are the marginal distributions of <i>X</i> and <i>Y</i> respectively, with <i>f</i>(<i>x</i>) being the prior distribution of <i>X</i>.</p>
+<p>Here we have indulged in a conventional <a href="http://en.wikipedia.org/wiki/Abuse_of_notation" title="Abuse of notation">abuse of notation</a>, using <i>f</i>
+for each one of these terms, although each one is really a different
+function; the functions are distinguished by the names of their
+arguments.</p>
+<p><a name="Abstract_Bayes.27_theorem" id="Abstract_Bayes.27_theorem"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=6" title="Edit section: Abstract Bayes' theorem">edit</a>]</span> <span class="mw-headline">Abstract Bayes' theorem</span></h3>
+<p>Given two <a href="http://en.wikipedia.org/wiki/Absolutely_continuous" title="Absolutely continuous">absolutely continuous</a> probability measures <span class="texhtml"><i>P</i>˜<i>Q</i></span> on the probability space <img class="tex" src="Bayes%27_theorem%20Files/b9474d45d82accf03434710c10871795.png" alt="(\Omega, \mathcal{F})"> and a sigma-algebra <img class="tex" src="Bayes%27_theorem%20Files/6956beed6709f29c47056603dd448e37.png" alt="\mathcal{G} \subset \mathcal{F}">, the abstract Bayes theorem for a <img class="tex" src="Bayes%27_theorem%20Files/26afd73f8c17f310707120691ccc4a35.png" alt="\mathcal{F}">-measurable random variable <span class="texhtml"><i>X</i></span> becomes</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/bcb58d4f262347072d35dea4a65977dd.png" alt="E_P[X|\mathcal{G}] = \frac{E_Q[\frac{dP}{dQ} X |\mathcal{G}]}{E_Q[\frac{dP}{dQ}|\mathcal{G}]}">.</dd>
+</dl>
+<p>This formulation is used in <a href="http://en.wikipedia.org/wiki/Kalman_filtering" title="Kalman filtering">Kalman filtering</a> to find <a href="http://en.wikipedia.org/wiki/Zakai_equation" title="Zakai equation">Zakai equations</a>. It is also used in <a href="http://en.wikipedia.org/wiki/Financial_mathematics" title="Financial mathematics">financial mathematics</a> for change of <a href="http://en.wikipedia.org/wiki/Numeraire" title="Numeraire">numeraire</a> techniques.</p>
+<p><a name="Extensions_of_Bayes.27_theorem" id="Extensions_of_Bayes.27_theorem"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=7" title="Edit section: Extensions of Bayes' theorem">edit</a>]</span> <span class="mw-headline">Extensions of Bayes' theorem</span></h3>
+<p>Theorems analogous to Bayes' theorem hold in problems with more than two variables. For example:</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/01b89e228d286117e3f007b0bc3b67c0.png" alt="P(A|B,C) = \frac{P(A) \, P(B|A) \, P(C|A,B)}{P(B) \, P(C|B)}"></dd>
+</dl>
+<p>This can be derived in several steps from Bayes' theorem and the definition of conditional probability:</p>
+<dl>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/0000c57867374dbd1b7837882c5f7e5c.png" alt="P(A|B,C) = \frac{P(A,B,C)}{P(B,C)} = \frac{P(A,B,C)}{P(B) \, P(C|B)} ="></dd>
+<dd><img class="tex" src="Bayes%27_theorem%20Files/2203d39e737f922023985141bee96496.png" alt="= \frac{P(C|A,B) \, P(A,B)}{P(B) \, P(C|B)} = \frac{P(A) \, P(B|A) \, P(C|A,B)}{P(B) \, P(C|B)} ."></dd>
+</dl>
+<p>A general strategy is to work with a decomposition of the <a href="http://en.wikipedia.org/wiki/Joint_probability" title="Joint probability">joint probability</a>,
+and to marginalize (integrate) over the variables that are not of
+interest. Depending on the form of the decomposition, it may be
+possible to prove that some integrals must be 1, and thus they fall out
+of the decomposition; exploiting this property can reduce the
+computations very substantially. A <a href="http://en.wikipedia.org/wiki/Bayesian_network" title="Bayesian network">Bayesian network</a>, for example, specifies a factorization of a <a href="http://en.wikipedia.org/wiki/Joint_distribution" title="Joint distribution">joint distribution</a>
+of several variables in which the conditional probability of any one
+variable given the remaining ones takes a particularly simple form (see
+<a href="http://en.wikipedia.org/wiki/Markov_blanket" title="Markov blanket">Markov blanket</a>).</p>
+<p><a name="Examples" id="Examples"></a></p>
+<h2><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=8" title="Edit section: Examples">edit</a>]</span> <span class="mw-headline">Stochastic Hoody-hoo</span></h2>
+<p><a name="Example_.231:__Conditional_probabilities" id="Example_.231:__Conditional_probabilities"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=9" title="Edit section: Example #1:  Conditional probabilities">edit</a>]</span> <span class="mw-headline">Blahbitty Bloohbitty Blee</span></h3>
+<p>Suppose there are blah blah blitty blah. Yada yada, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah yada yada ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. Yada yada, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah yada yada ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. Yada yada, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah yada yada ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee.</p>
+
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=9" title="Edit section: Example #2:  Conditional probabilities">edit</a>]</span> <span class="mw-headline">You are getting very sleepy...</span></h3>
+
+<p>Then you can yada yada bloopy blah. Yada yada, yada yada bleepity Blee, yada blee blah yaditty yada ya. Blahbloopy yada yada blech blah, blechitty blah yada yada ying yang yong. Foo bar baz, yada yada yaddity yada ya, yada yada blahbloopy yada yada bloohbloopy blee. Yada yada, yada yada bleepity Blee, yada blee blah yaditty yada ya. Blahbloopy yada yada blech blah, blechitty blah yada yada ying yang yong. Foo bar baz, yada yada yaddity yada ya, yada yada blahbloopy yada yada bloohbloopy blee. Yada yada, yada yada bleepity Blee, yada blee blah yaditty yada ya. Blahbloopy yada yada blech blah, blechitty blah yada yada ying yang yong. Foo bar baz, yada yada yaddity yada ya, yada yada blahbloopy yada yada bloohbloopy blee.</p>
+
+
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=9" title="Edit section: Example #2:  Conditional probabilities">edit</a>]</span> <span class="mw-headline">Verrrrry Sleeeeeepy.......</span></h3>
+
+<p>We find that blah blah blitty blah. blah blah, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. blah blah, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. blah blah, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. blah blah, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee. blah blah, blah blah bleepity Blee, yada blee blah yaditty yada ya. Blahbitty blah blah blech blah, blechitty blah blah blah ying yang yong. Foo bar baz, blah blah yaddity yada ya, blah blah blahbitty blah blah bloohbitty blee.</p>
+
+
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=9" title="Edit section: Example #2:  Conditional probabilities">edit</a>]</span> <span class="mw-headline">Your eyelids are sooooo heavy.......</span></h3>
+
+<p>Blah blah blah</p>
+
+<p>Yada yada yada</p>
+
+<p>Blah blah</p>
+
+<p>Yada yada</p>
+
+<p>Blah</p>
+
+<p>Yada</p>
+
+<p>blah</p>
+
+<p>yada</p>
+
+<p>blah</p>
+
+<p>blah</p>
+
+<p>blah</p>
+
+<p>blah</p>
+
+<p>blah</p>
+
+<p>blah</p>
+
+<p>Zzzzz.............</p>
+
+<p><a name="See_also" id="See_also"></a></p>
+<h2><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=15" title="Edit section: See also">edit</a>]</span> <span class="mw-headline">See also</span></h2>
+<div class="references-small" style="-moz-column-count: 2;">
+<ul>
+<li><a href="http://en.wikipedia.org/wiki/Bayesian_inference" title="Bayesian inference">Bayesian inference</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Bayesian_spam_filtering" title="Bayesian spam filtering">Bayesian spam filtering</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Bogofilter" title="Bogofilter">Bogofilter</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Conjugate_prior" title="Conjugate prior">Conjugate prior</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Empirical_Bayes_method" title="Empirical Bayes method">Empirical Bayes method</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Monty_Hall_problem" title="Monty Hall problem">Monty Hall problem</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Occam%27s_razor" title="Occam's razor">Occam's razor</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Prosecutor%27s_fallacy" title="Prosecutor's fallacy">Prosecutor's fallacy</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Raven_paradox" title="Raven paradox">Raven paradox</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Recursive_Bayesian_estimation" title="Recursive Bayesian estimation">Recursive Bayesian estimation</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Revising_opinions_in_statistics" title="Revising opinions in statistics">Revising opinions in statistics</a></li>
+<li><a href="http://en.wikipedia.org/wiki/Sequential_bayesian_filtering" title="Sequential bayesian filtering">Sequential bayesian filtering</a></li>
+</ul>
+</div>
+<p><a name="References" id="References"></a></p>
+<h2><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=16" title="Edit section: References">edit</a>]</span> <span class="mw-headline">References</span></h2>
+<ol class="references"></ol>
+<p><a name="Versions_of_the_essay" id="Versions_of_the_essay"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=17" title="Edit section: Versions of the essay">edit</a>]</span> <span class="mw-headline">Versions of the essay</span></h3>
+<ul>
+<li>Thomas Bayes (1763), "An Essay towards solving a Problem in the
+Doctrine of Chances. By the late Rev. Mr. Bayes, F. R. S. communicated
+by Mr. Price, in a letter to John Canton, A. M. F. R. S.", <i><a href="http://en.wikipedia.org/wiki/Philosophical_Transactions" title="Philosophical Transactions">Philosophical Transactions</a>, Giving Some Account of the Present Undertakings, Studies and Labours of the Ingenious in Many Considerable Parts of the World</i> 53:370–418.</li>
+<li>Thomas Bayes (1763/1958) "Studies in the History of Probability and
+Statistics: IX. Thomas Bayes' Essay Towards Solving a Problem in the
+Doctrine of Chances", <i><a href="http://en.wikipedia.org/wiki/Biometrika" title="Biometrika">Biometrika</a></i> 45:296–315. <i>(Bayes' essay in modernized notation)</i></li>
+<li>Thomas Bayes <a href="http://www.stat.ucla.edu/history/essay.pdf" class="external text" title="http://www.stat.ucla.edu/history/essay.pdf" rel="nofollow">"An essay towards solving a Problem in the Doctrine of Chances"</a>. <i>(Bayes' essay in the original notation)</i></li>
+</ul>
+<p><a name="Commentaries" id="Commentaries"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=18" title="Edit section: Commentaries">edit</a>]</span> <span class="mw-headline">Commentaries</span></h3>
+<ul>
+<li><a href="http://en.wikipedia.org/wiki/George_Alfred_Barnard" title="George Alfred Barnard">G. A. Barnard</a>
+(1958) "Studies in the History of Probability and Statistics: IX.
+Thomas Bayes' Essay Towards Solving a Problem in the Doctrine of
+Chances", <i>Biometrika</i> 45:293–295. <i>(biographical remarks)</i></li>
+<li>Daniel Covarrubias. <a href="http://www.stat.rice.edu/%7Eblairc/seminar/Files/danTalk.pdf" class="external text" title="http://www.stat.rice.edu/~blairc/seminar/Files/danTalk.pdf" rel="nofollow">"An Essay Towards Solving a Problem in the Doctrine of Chances"</a>. <i>(an outline and exposition of Bayes' essay)</i></li>
+<li>Stephen M. Stigler (1982). "Thomas Bayes' Bayesian Inference," <i>Journal of the Royal Statistical Society</i>, Series A, 145:250–258. <i>(Stigler argues for a revised interpretation of the essay; recommended)</i></li>
+<li><a href="http://en.wikipedia.org/wiki/Isaac_Todhunter" title="Isaac Todhunter">Isaac Todhunter</a> (1865). <i>A History of the Mathematical Theory of Probability from the time of Pascal to that of Laplace</i>, Macmillan. Reprinted 1949, 1956 by Chelsea and 2001 by Thoemmes.</li>
+</ul>
+<p><a name="Additional_material" id="Additional_material"></a></p>
+<h3><span class="editsection">[<a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit&amp;section=19" title="Edit section: Additional material">edit</a>]</span> <span class="mw-headline">Additional material</span></h3>
+<ul>
+<li>Pierre-Simon Laplace (1774). "Mémoire sur la Probabilité des Causes par les Événements", <i>Savants Étranges</i> 6:621–656; also <i>Œuvres</i> 8:27–65.</li>
+<li>Pierre-Simon Laplace (1774/1986). "Memoir on the Probability of the Causes of Events", <i>Statistical Science</i> 1(3):364–378.</li>
+<li>Stephen M. Stigler (1986). "Laplace's 1774 memoir on inverse probability", <i>Statistical Science</i> 1(3):359–378.</li>
+<li>Stephen M. Stigler (1983). "Who Discovered Bayes' Theorem?" <i>The American Statistician</i> 37(4):290–296.</li>
+<li>Jeff Miller et al. <a href="http://members.aol.com/jeff570/mathword.html" class="external text" title="http://members.aol.com/jeff570/mathword.html" rel="nofollow">Earliest Known Uses of Some of the Words of Mathematics (B)</a>. (<i>very informative; recommended</i>)</li>
+<li><a href="http://en.wikipedia.org/wiki/Athanasios_Papoulis" title="Athanasios Papoulis">Athanasios Papoulis</a> (1984). <i>Probability, Random Variables, and Stochastic Processes</i>, second edition. New York: McGraw-Hill.</li>
+<li>James Joyce (2003). <a href="http://plato.stanford.edu/entries/Bayes-theorem/" class="external text" title="http://plato.stanford.edu/entries/Bayes-theorem/" rel="nofollow">"Bayes' Theorem"</a>, <i><a href="http://en.wikipedia.org/wiki/Stanford_Encyclopedia_of_Philosophy" title="Stanford Encyclopedia of Philosophy">Stanford Encyclopedia of Philosophy</a></i>.</li>
+<li><a href="http://www.inference.phy.cam.ac.uk/mackay/itila/" class="external text" title="http://www.inference.phy.cam.ac.uk/mackay/itila/" rel="nofollow">The on-line textbook: Information Theory, Inference, and Learning Algorithms</a>, by <a href="http://en.wikipedia.org/wiki/David_J.C._MacKay" title="David J.C. MacKay">David J.C. MacKay</a> provides an up to date overview of the use of Bayes' theorem in information theory and machine learning.</li>
+<li><a href="http://plato.stanford.edu/entries/bayes-theorem/" class="external text" title="http://plato.stanford.edu/entries/bayes-theorem/" rel="nofollow">Stanford Encyclopedia of Philosophy: Bayes' Theorem</a> provides a comprehensive introduction to Bayes' theorem.</li>
+<li><a href="http://en.wikipedia.org/wiki/Eric_W._Weisstein" title="Eric W. Weisstein">Eric W. Weisstein</a>, <i><a href="http://mathworld.wolfram.com/BayesTheorem.html" class="external text" title="http://mathworld.wolfram.com/BayesTheorem.html" rel="nofollow">Bayes' Theorem</a></i> at <a href="http://en.wikipedia.org/wiki/MathWorld" title="MathWorld">MathWorld</a>.</li>
+<li><i><a href="http://planetmath.org/encyclopedia/BayesTheorem.html" class="external text" title="http://planetmath.org/encyclopedia/BayesTheorem.html" rel="nofollow">Bayes' theorem</a></i> at <a href="http://en.wikipedia.org/wiki/PlanetMath" title="PlanetMath">PlanetMath</a>.</li>
+<li>Yudkowsky, Eliezer S. (2003), "<a href="http://yudkowsky.net/bayes/bayes.html" class="external text" title="http://yudkowsky.net/bayes/bayes.html" rel="nofollow">An Intuitive Explanation of Bayesian Reasoning</a>"</li>
+</ul>
+
+<!-- 
+Pre-expand include size: 1117 bytes
+Post-expand include size: 200 bytes
+Template argument size: 73 bytes
+Maximum: 2048000 bytes
+-->
+
+<!-- Saved in parser cache with key enwiki:pcache:idhash:49569-0!1!0!default!!en!2 and timestamp 20070505042040 -->
+<div class="printfooter">
+Retrieved from "<a href="http://en.wikipedia.org/wiki/Bayes%27_theorem">http://en.wikipedia.org/wiki/Bayes%27_theorem</a>"</div>
+			<div id="catlinks"><p class="catlinks"><a href="http://en.wikipedia.org/wiki/Special:Categories" title="Special:Categories">Categories</a>: <span dir="ltr"><a href="http://en.wikipedia.org/wiki/Category:Probability_theory" title="Category:Probability theory">Probability theory</a></span> | <span dir="ltr"><a href="http://en.wikipedia.org/wiki/Category:Mathematical_theorems" title="Category:Mathematical theorems">Mathematical theorems</a></span></p></div>			<!-- end content -->
+			<div class="visualClear"></div>
+		</div>
+	</div>
+		</div>
+		<div id="column-one">
+	<div id="p-cactions" class="portlet">
+		<h5>Views</h5>
+		<div class="pBody">
+			<ul>
+					 <li id="ca-nstab-main" class="selected"><a href="http://en.wikipedia.org/wiki/Bayes%27_theorem" title="View the content page [ctrl-c]" accesskey="c">Article</a></li>
+					 <li id="ca-talk"><a href="http://en.wikipedia.org/wiki/Talk:Bayes%27_theorem" title="Discussion about the content page [ctrl-t]" accesskey="t">Discussion</a></li>
+					 <li id="ca-edit"><a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=edit" title="You can edit this page. Please use the preview button before saving. [ctrl-e]" accesskey="e">Edit this page</a></li>
+					 <li id="ca-history"><a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;action=history" title="Past versions of this page. [ctrl-h]" accesskey="h">History</a></li>
+				</ul>
+		</div>
+	</div>
+	<div class="portlet" id="p-personal">
+		<h5>Personal tools</h5>
+		<div class="pBody">
+			<ul>
+				<li id="pt-login"><a href="http://en.wikipedia.org/w/index.php?title=Special:Userlogin&amp;returnto=Bayes%27_theorem" title="You are encouraged to log in, it is not mandatory however. [ctrl-o]" accesskey="o">Sign in / create account</a></li>
+			</ul>
+		</div>
+	</div>
+	<div class="portlet" id="p-logo">
+		<a style="background-image: url(Bayes%27_theorem%20Files/wiki-en.png);" href="http://en.wikipedia.org/wiki/Main_Page" title="Visit the Main Page [ctrl-z]" accesskey="z"></a>
+	</div>
+	<script type="text/javascript"> if (window.isMSIE55) fixalpha(); </script>
+		<div class="portlet" id="p-navigation">
+		<h5>Navigation</h5>
+		<div class="pBody">
+			<ul>
+				<li id="n-Main-page"><a title="Visit the main page [ctrl-z]" accesskey="z" href="http://en.wikipedia.org/wiki/Main_Page">Main page</a></li>
+				<li title="Guides to browsing Wikipedia" id="n-Contents"><a href="http://en.wikipedia.org/wiki/Wikipedia:Contents">Contents</a></li>
+				<li title="Featured content — the best of Wikipedia" id="n-Featured-content"><a href="http://en.wikipedia.org/wiki/Wikipedia:Featured_content">Featured content</a></li>
+				<li id="n-currentevents"><a href="http://en.wikipedia.org/wiki/Portal:Current_events" title="Find background information on current events">Current events</a></li>
+				<li id="n-randompage"><a href="http://en.wikipedia.org/wiki/Special:Random" title="Load a random page [ctrl-x]" accesskey="x">Random article</a></li>
+			</ul>
+		</div>
+	</div>
+		<div class="portlet" id="p-interaction">
+		<h5>interaction</h5>
+		<div class="pBody">
+			<ul>
+				<li id="n-About-Wikipedia"><a href="http://en.wikipedia.org/wiki/Wikipedia:About">About Wikipedia</a></li>
+				<li id="n-portal"><a href="http://en.wikipedia.org/wiki/Wikipedia:Community_Portal" title="About the project, what you can do, where to find things">Community portal</a></li>
+				<li id="n-recentchanges"><a href="http://en.wikipedia.org/wiki/Special:Recentchanges" title="The list of recent changes in the wiki. [ctrl-r]" accesskey="r">Recent changes</a></li>
+				<li title="How to contact Wikipedia" id="n-contact"><a href="http://en.wikipedia.org/wiki/Wikipedia:Contact_us">Contact us</a></li>
+				<li title="Help keep Wikipedia running" id="n-sitesupport"><a href="http://wikimediafoundation.org/wiki/Fundraising" title="Support us">Make a donation</a></li>
+				<li title="The place to find out about Wikipedia" id="n-help"><a href="http://en.wikipedia.org/wiki/Help:Contents" title="The place to find out.">Help</a></li>
+			</ul>
+		</div>
+	</div>
+		<div id="p-search" class="portlet">
+		<h5><label for="searchInput">Search</label></h5>
+		<div id="searchBody" class="pBody">
+			<form action="/wiki/Special:Search" id="searchform"><div>
+				<input id="searchInput" name="search" title="Search Wikipedia [ctrl-f]" accesskey="f" value="" type="text">
+				<input name="go" class="searchButton" id="searchGoButton" value="Go" type="submit"> 
+				<input name="fulltext" class="searchButton" id="mw-searchButton" value="Search" type="submit">
+			</div></form>
+		</div>
+	</div>
+	<div class="portlet" id="p-tb">
+		<h5>Toolbox</h5>
+		<div class="pBody">
+			<ul>
+				<li id="t-whatlinkshere"><a href="http://en.wikipedia.org/wiki/Special:Whatlinkshere/Bayes%27_theorem" title="List of all wiki pages that link here [ctrl-j]" accesskey="j">What links here</a></li>
+				<li id="t-recentchangeslinked"><a href="http://en.wikipedia.org/wiki/Special:Recentchangeslinked/Bayes%27_theorem" title="Recent changes in pages linked from this page [ctrl-k]" accesskey="k">Related changes</a></li>
+<li id="t-upload"><a href="http://en.wikipedia.org/wiki/Special:Upload" title="Upload images or media files [ctrl-u]" accesskey="u">Upload file</a></li>
+<li id="t-specialpages"><a href="http://en.wikipedia.org/wiki/Special:Specialpages" title="List of all special pages [ctrl-q]" accesskey="q">Special pages</a></li>
+				<li title="Printable version of this page" id="t-print"><a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;printable=yes">Printable version</a></li>				<li title="Permanent link to this version of the page" id="t-permalink"><a href="http://en.wikipedia.org/w/index.php?title=Bayes%27_theorem&amp;oldid=128360312">Permanent link</a></li><li title="Cite this Wikipedia article" id="t-cite"><a href="http://en.wikipedia.org/w/index.php?title=Special:Cite&amp;page=Bayes%27_theorem&amp;id=128360312">Cite this article</a></li>			</ul>
+		</div>
+	</div>
+	<div id="p-lang" class="portlet">
+		<h5>In other languages</h5>
+		<div class="pBody">
+			<ul>
+				<li class="interwiki-ar"><a href="http://ar.wikipedia.org/wiki/%D9%85%D8%A8%D8%B1%D9%87%D9%86%D8%A9_%D8%A8%D8%A7%D9%8A%D8%B2">العربية</a></li>
+				<li class="interwiki-ca"><a href="http://ca.wikipedia.org/wiki/Teorema_de_Bayes">Català</a></li>
+				<li class="interwiki-de"><a href="http://de.wikipedia.org/wiki/Bayestheorem">Deutsch</a></li>
+				<li class="interwiki-es"><a href="http://es.wikipedia.org/wiki/Teorema_de_Bayes">Español</a></li>
+				<li class="interwiki-fr"><a href="http://fr.wikipedia.org/wiki/Th%C3%A9or%C3%A8me_de_Bayes">Français</a></li>
+				<li class="interwiki-it"><a href="http://it.wikipedia.org/wiki/Teorema_di_Bayes">Italiano</a></li>
+				<li class="interwiki-he"><a href="http://he.wikipedia.org/wiki/%D7%97%D7%95%D7%A7_%D7%91%D7%99%D7%99%D7%A1">עברית</a></li>
+				<li class="interwiki-lt"><a href="http://lt.wikipedia.org/wiki/Bajeso_teorema">Lietuvių</a></li>
+				<li class="interwiki-nl"><a href="http://nl.wikipedia.org/wiki/Theorema_van_Bayes">Nederlands</a></li>
+				<li class="interwiki-ja"><a href="http://ja.wikipedia.org/wiki/%E3%83%99%E3%82%A4%E3%82%BA%E3%81%AE%E5%AE%9A%E7%90%86">日本語</a></li>
+				<li class="interwiki-pl"><a href="http://pl.wikipedia.org/wiki/Twierdzenie_Bayesa">Polski</a></li>
+				<li class="interwiki-pt"><a href="http://pt.wikipedia.org/wiki/Teorema_de_Bayes">Português</a></li>
+				<li class="interwiki-ro"><a href="http://ro.wikipedia.org/wiki/Teorema_lui_Bayes">Română</a></li>
+				<li class="interwiki-ru"><a href="http://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%91%D0%B0%D0%B9%D0%B5%D1%81%D0%B0">Русский</a></li>
+				<li class="interwiki-sr"><a href="http://sr.wikipedia.org/wiki/%D0%91%D0%B0%D1%98%D0%B5%D1%81%D0%BE%D0%B2%D0%B0_%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0">Српски / Srpski</a></li>
+				<li class="interwiki-su"><a href="http://su.wikipedia.org/wiki/T%C3%A9or%C3%A9ma_Bayes">Basa Sunda</a></li>
+				<li class="interwiki-fi"><a href="http://fi.wikipedia.org/wiki/Bayesin_teoreema">Suomi</a></li>
+				<li class="interwiki-sv"><a href="http://sv.wikipedia.org/wiki/Bayes_sats">Svenska</a></li>
+				<li class="interwiki-vi"><a href="http://vi.wikipedia.org/wiki/%C4%90%E1%BB%8Bnh_l%C3%BD_Bayes">Tiếng Việt</a></li>
+				<li class="interwiki-zh"><a href="http://zh.wikipedia.org/wiki/%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%AE%9A%E7%90%86">中文</a></li>
+			</ul>
+		</div>
+	</div>
+		</div><!-- end of the left (by default at least) column -->
+			<div class="visualClear"></div>
+			<div id="footer">
+				<div id="f-poweredbyico"><a href="http://www.mediawiki.org/"><img src="Bayes%27_theorem%20Files/poweredby_mediawiki_88x31.png" alt="Powered by MediaWiki"></a></div>
+				<div id="f-copyrightico"><a href="http://wikimediafoundation.org/"><img src="Bayes%27_theorem%20Files/wikimedia-button.png" alt="Wikimedia Foundation" border="0"></a></div>
+			<ul id="f-list">
+				<li id="lastmod"> This page was last modified 03:56, 5 May 2007.</li>
+				<li id="copyright">All text is available under the terms of the <a class="internal" href="http://en.wikipedia.org/wiki/Wikipedia:Text_of_the_GNU_Free_Documentation_License" title="Wikipedia:Text of the GNU Free Documentation License">GNU Free Documentation License</a>. (See <b><a class="internal" href="http://en.wikipedia.org/wiki/Wikipedia:Copyrights" title="Wikipedia:Copyrights">Copyrights</a></b> for details.) <br> Wikipedia® is a registered trademark of the <a href="http://www.wikimediafoundation.org/">Wikimedia Foundation, Inc</a>., a US-registered <a class="internal" href="http://en.wikipedia.org/wiki/501%28c%29#501.28c.29.283.29" title="501(c)(3)">501(c)(3)</a> <a href="http://wikimediafoundation.org/wiki/Deductibility_of_donations">tax-deductible</a> <a class="internal" href="http://en.wikipedia.org/wiki/Non-profit_organization" title="Non-profit organization">nonprofit</a> <a href="http://en.wikipedia.org/wiki/Charitable_organization" title="Charitable organization">charity</a>.<br></li>
+				<li id="privacy"><a href="http://wikimediafoundation.org/wiki/Privacy_policy" title="wikimedia:Privacy policy">Privacy policy</a></li>
+				<li id="about"><a href="http://en.wikipedia.org/wiki/Wikipedia:About" title="Wikipedia:About">About Wikipedia</a></li>
+				<li id="disclaimer"><a href="http://en.wikipedia.org/wiki/Wikipedia:General_disclaimer" title="Wikipedia:General disclaimer">Disclaimers</a></li>
+			</ul>
+		</div>
+		
+	
+		<script type="text/javascript">if (window.runOnloadHook) runOnloadHook();</script>
+</div>
+<!-- Served by srv83 in 0.079 secs. -->
+</body></html>

+ 1488 - 0
talks/bayes_theorem/index.html

@@ -0,0 +1,1488 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<!-- configuration parameters -->
+<meta name="defaultView" content="slideshow" />
+<meta name="controlVis" content="hidden" />
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta name="generator" content="pandoc" />
+<style type="text/css" media="projection" id="slideProj">
+/* Do not edit or override these styles! The system will likely break if you do. */
+
+div#header, div#footer, div#controls, .slide {position: absolute;}
+html>body div#header, html>body div#footer, 
+  html>body div#controls, html>body .slide {position: fixed;}
+.handout {display: none;}
+.layout {display: block;}
+.slide, .hideme, .incremental {visibility: hidden;}
+#slide0 {visibility: visible;}
+
+/* The following styles size, place, and layer the slide components.
+   Edit these if you want to change the overall slide layout.
+   The commented lines can be uncommented (and modified, if necessary) 
+    to help you with the rearrangement process. */
+
+/* target = 1024x768 */
+
+div#header, div#footer, .slide {width: 100%; top: 0; left: 0;}
+div#header {top: 0; height: 3em; z-index: 1;}
+div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;}
+.slide {top: 0; width: 92%; padding: 3.5em 4% 4%; z-index: 2;  list-style: none;}
+div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;}
+div#controls form {position: absolute; bottom: 0; right: 0; width: 100%;
+  margin: 0;}
+#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; z-index: 10;}
+html>body #currentSlide {position: fixed;}
+
+/*
+div#header {background: #FCC;}
+div#footer {background: #CCF;}
+div#controls {background: #BBD;}
+div#currentSlide {background: #FFC;}
+*/
+
+/* Following are the presentation styles -- edit away! */
+
+body {background: #FFF url(bodybg.gif) -16px 0 no-repeat; color: #000; font-size: 2em;}
+:link, :visited {text-decoration: none; color: #00C;}
+#controls :active {color: #88A !important;}
+#controls :focus {outline: 1px dotted #227;}
+h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;}
+ul, pre {margin: 0; line-height: 1em;}
+html, body {margin: 0; padding: 0;}
+
+blockquote, q {font-style: italic;}
+blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em; text-align: center; font-size: 1em;}
+blockquote p {margin: 0;}
+blockquote i {font-style: normal;}
+blockquote b {display: block; margin-top: 0.5em; font-weight: normal; font-size: smaller; font-style: normal;}
+blockquote b i {font-style: italic;}
+
+kbd {font-weight: bold; font-size: 1em;}
+sup {font-size: smaller; line-height: 1px;}
+
+.slide code {padding: 2px 0.25em; font-weight: bold; color: #533;}
+.slide code.bad, code del {color: red;}
+.slide code.old {color: silver;}
+.slide pre {padding: 0; margin: 0.25em 0 0.5em 0.5em; color: #533; font-size: 90%;}
+.slide pre code {display: block;}
+.slide ul {margin-left: 5%; margin-right: 7%; list-style: disc;}
+.slide li {margin-top: 0.75em; margin-right: 0;}
+.slide ul ul {line-height: 1;}
+.slide ul ul li {margin: .2em; font-size: 85%; list-style: square;}
+.slide img.leader {display: block; margin: 0 auto;}
+
+div#header, div#footer {background: #005; color: #AAB;
+  font-family: Verdana, Helvetica, sans-serif;}
+div#header {background: #005 url(bodybg.gif) -16px 0 no-repeat;
+  line-height: 1px;}
+div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;}
+#footer h1, #footer h2 {display: block; padding: 0 1em;}
+#footer h2 {font-style: italic;}
+
+div.long {font-size: 0.75em;}
+.slide h1 {position: absolute; top: 0.7em; left: 87px; z-index: 1;
+  margin: 0; padding: 0.3em 0 0 50px; white-space: nowrap;
+  font: bold 150%/1em Helvetica, sans-serif; text-transform: capitalize;
+  color: #DDE; background: #005;}
+.slide h3 {font-size: 130%;}
+h1 abbr {font-variant: small-caps;}
+
+div#controls {position: absolute; left: 50%; bottom: 0;
+  width: 50%;
+  text-align: right; font: bold 0.9em Verdana, Helvetica, sans-serif;}
+html>body div#controls {position: fixed; padding: 0 0 1em 0;
+  top: auto;}
+div#controls form {position: absolute; bottom: 0; right: 0; width: 100%;
+  margin: 0; padding: 0;}
+#controls #navLinks a {padding: 0; margin: 0 0.5em; 
+  background: #005; border: none; color: #779; 
+  cursor: pointer;}
+#controls #navList {height: 1em;}
+#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; background: #DDD; color: #227;}
+
+#currentSlide {text-align: center; font-size: 0.5em; color: #449;}
+
+#slide0 {padding-top: 3.5em; font-size: 90%;}
+#slide0 h1 {position: static; margin: 1em 0 0; padding: 0;
+   font: bold 2em Helvetica, sans-serif; white-space: normal;
+   color: #000; background: transparent;}
+#slide0 h2 {font: bold italic 1em Helvetica, sans-serif; margin: 0.25em;}
+#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;}
+#slide0 h4 {margin-top: 0; font-size: 1em;}
+
+ul.urls {list-style: none; display: inline; margin: 0;}
+.urls li {display: inline; margin: 0;}
+.note {display: none;}
+.external {border-bottom: 1px dotted gray;}
+html>body .external {border-bottom: none;}
+.external:after {content: " \274F"; font-size: smaller; color: #77B;}
+
+.incremental, .incremental *, .incremental *:after {color: #DDE; visibility: visible;}
+img.incremental {visibility: hidden;}
+.slide .current {color: #B02;}
+
+
+/* diagnostics
+
+li:after {content: " [" attr(class) "]"; color: #F88;}
+ */
+</style>
+<style type="text/css" media="projection" id="operaFix">
+/* DO NOT CHANGE THESE unless you really want to break Opera Show */
+.slide {
+	visibility: visible !important;
+	position: static !important;
+	page-break-before: always;
+}
+#slide0 {page-break-before: avoid;}
+
+</style>
+<style type="text/css" media="screen" id="outlineStyle">
+/* don't change this unless you want the layout stuff to show up in the outline view! */
+
+.layout div, #footer *, #controlForm * {display: none;}
+#footer, #controls, #controlForm, #navLinks, #toggle {
+  display: block; visibility: visible; margin: 0; padding: 0;}
+#toggle {float: right; padding: 0.5em;}
+html>body #toggle {position: fixed; top: 0; right: 0;}
+
+/* making the outline look pretty-ish */
+
+#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;}
+#slide0 h1 {padding-top: 1.5em;}
+.slide h1 {margin: 1.5em 0 0; padding-top: 0.25em;
+  border-top: 1px solid #888; border-bottom: 1px solid #AAA;}
+#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;}
+
+</style>
+<style type="text/css" media="print" id="slidePrint">
+/* The following rule is necessary to have all slides appear in print! DO NOT REMOVE IT! */
+.slide, ul {page-break-inside: avoid; visibility: visible !important;}
+h1 {page-break-after: avoid;}
+
+body {font-size: 12pt; background: white;}
+* {color: black;}
+
+#slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;}
+#slide0 h3 {margin: 0; padding: 0;}
+#slide0 h4 {margin: 0 0 0.5em; padding: 0;}
+#slide0 {margin-bottom: 3em;}
+
+h1 {border-top: 2pt solid gray; border-bottom: 1px dotted silver;}
+.extra {background: transparent !important;}
+div.extra, pre.extra, .example {font-size: 10pt; color: #333;}
+ul.extra a {font-weight: bold;}
+p.example {display: none;}
+
+#header {display: none;}
+#footer h1 {margin: 0; border-bottom: 1px solid; color: gray; font-style: italic;}
+#footer h2, #controls {display: none;}
+
+/* The following rule keeps the layout stuff out of print.  Remove at your own risk! */
+.layout, .layout * {display: none !important;}
+
+</style>
+<script type="text/javascript">
+// S5 v1.1 slides.js -- released into the Public Domain
+//
+// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for information 
+// about all the wonderful and talented contributors to this code!
+
+var undef;
+var slideCSS = '';
+var snum = 0;
+var smax = 1;
+var incpos = 0;
+var number = undef;
+var s5mode = true;
+var defaultView = 'slideshow';
+var controlVis = 'visible';
+
+var isIE = navigator.appName == 'Microsoft Internet Explorer' && navigator.userAgent.indexOf('Opera') < 1 ? 1 : 0;
+var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0;
+var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0;
+
+function hasClass(object, className) {
+	if (!object.className) return false;
+	return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1);
+}
+
+function hasValue(object, value) {
+	if (!object) return false;
+	return (object.search('(^|\\s)' + value + '(\\s|$)') != -1);
+}
+
+function removeClass(object,className) {
+	if (!object) return;
+	object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2);
+}
+
+function addClass(object,className) {
+	if (!object || hasClass(object, className)) return;
+	if (object.className) {
+		object.className += ' '+className;
+	} else {
+		object.className = className;
+	}
+}
+
+function GetElementsWithClassName(elementName,className) {
+	var allElements = document.getElementsByTagName(elementName);
+	var elemColl = new Array();
+	for (var i = 0; i< allElements.length; i++) {
+		if (hasClass(allElements[i], className)) {
+			elemColl[elemColl.length] = allElements[i];
+		}
+	}
+	return elemColl;
+}
+
+function isParentOrSelf(element, id) {
+	if (element == null || element.nodeName=='BODY') return false;
+	else if (element.id == id) return true;
+	else return isParentOrSelf(element.parentNode, id);
+}
+
+function nodeValue(node) {
+	var result = "";
+	if (node.nodeType == 1) {
+		var children = node.childNodes;
+		for (var i = 0; i < children.length; ++i) {
+			result += nodeValue(children[i]);
+		}		
+	}
+	else if (node.nodeType == 3) {
+		result = node.nodeValue;
+	}
+	return(result);
+}
+
+function slideLabel() {
+	var slideColl = GetElementsWithClassName('*','slide');
+	var list = document.getElementById('jumplist');
+	smax = slideColl.length;
+	for (var n = 0; n < smax; n++) {
+		var obj = slideColl[n];
+
+		var did = 'slide' + n.toString();
+		obj.setAttribute('id',did);
+		if (isOp) continue;
+
+		var otext = '';
+		var menu = obj.firstChild;
+		if (!menu) continue; // to cope with empty slides
+		while (menu && menu.nodeType == 3) {
+			menu = menu.nextSibling;
+		}
+	 	if (!menu) continue; // to cope with slides with only text nodes
+
+		var menunodes = menu.childNodes;
+		for (var o = 0; o < menunodes.length; o++) {
+			otext += nodeValue(menunodes[o]);
+		}
+		list.options[list.length] = new Option(n + ' : '  + otext, n);
+	}
+}
+
+function currentSlide() {
+	var cs;
+	if (document.getElementById) {
+		cs = document.getElementById('currentSlide');
+	} else {
+		cs = document.currentSlide;
+	}
+	cs.innerHTML = '<span id="csHere">' + snum + '<\/span> ' + 
+		'<span id="csSep">\/<\/span> ' + 
+		'<span id="csTotal">' + (smax-1) + '<\/span>';
+	if (snum == 0) {
+		cs.style.visibility = 'hidden';
+	} else {
+		cs.style.visibility = 'visible';
+	}
+}
+
+function go(step) {
+	if (document.getElementById('slideProj').disabled || step == 0) return;
+	var jl = document.getElementById('jumplist');
+	var cid = 'slide' + snum;
+	var ce = document.getElementById(cid);
+	if (incrementals[snum].length > 0) {
+		for (var i = 0; i < incrementals[snum].length; i++) {
+			removeClass(incrementals[snum][i], 'current');
+			removeClass(incrementals[snum][i], 'incremental');
+		}
+	}
+	if (step != 'j') {
+		snum += step;
+		lmax = smax - 1;
+		if (snum > lmax) snum = lmax;
+		if (snum < 0) snum = 0;
+	} else
+		snum = parseInt(jl.value);
+	var nid = 'slide' + snum;
+	var ne = document.getElementById(nid);
+	if (!ne) {
+		ne = document.getElementById('slide0');
+		snum = 0;
+	}
+	if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;}
+	if (incrementals[snum].length > 0 && incpos == 0) {
+		for (var i = 0; i < incrementals[snum].length; i++) {
+			if (hasClass(incrementals[snum][i], 'current'))
+				incpos = i + 1;
+			else
+				addClass(incrementals[snum][i], 'incremental');
+		}
+	}
+	if (incrementals[snum].length > 0 && incpos > 0)
+		addClass(incrementals[snum][incpos - 1], 'current');
+	ce.style.visibility = 'hidden';
+	ne.style.visibility = 'visible';
+	jl.selectedIndex = snum;
+	currentSlide();
+	number = 0;
+}
+
+function goTo(target) {
+	if (target >= smax || target == snum) return;
+	go(target - snum);
+}
+
+function subgo(step) {
+	if (step > 0) {
+		removeClass(incrementals[snum][incpos - 1],'current');
+		removeClass(incrementals[snum][incpos], 'incremental');
+		addClass(incrementals[snum][incpos],'current');
+		incpos++;
+	} else {
+		incpos--;
+		removeClass(incrementals[snum][incpos],'current');
+		addClass(incrementals[snum][incpos], 'incremental');
+		addClass(incrementals[snum][incpos - 1],'current');
+	}
+}
+
+function toggle() {
+	var slideColl = GetElementsWithClassName('*','slide');
+	var slides = document.getElementById('slideProj');
+	var outline = document.getElementById('outlineStyle');
+	if (!slides.disabled) {
+		slides.disabled = true;
+		outline.disabled = false;
+		s5mode = false;
+		fontSize('1em');
+		for (var n = 0; n < smax; n++) {
+			var slide = slideColl[n];
+			slide.style.visibility = 'visible';
+		}
+	} else {
+		slides.disabled = false;
+		outline.disabled = true;
+		s5mode = true;
+		fontScale();
+		for (var n = 0; n < smax; n++) {
+			var slide = slideColl[n];
+			slide.style.visibility = 'hidden';
+		}
+		slideColl[snum].style.visibility = 'visible';
+	}
+}
+
+function showHide(action) {
+	var obj = GetElementsWithClassName('*','hideme')[0];
+	switch (action) {
+	case 's': obj.style.visibility = 'visible'; break;
+	case 'h': obj.style.visibility = 'hidden'; break;
+	case 'k':
+		if (obj.style.visibility != 'visible') {
+			obj.style.visibility = 'visible';
+		} else {
+			obj.style.visibility = 'hidden';
+		}
+	break;
+	}
+}
+
+// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/)
+function keys(key) {
+	if (!key) {
+		key = event;
+		key.which = key.keyCode;
+	}
+	if (key.which == 84) {
+		toggle();
+		return;
+	}
+	if (s5mode) {
+		switch (key.which) {
+			case 10: // return
+			case 13: // enter
+				if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return;
+				if (key.target && isParentOrSelf(key.target, 'controls')) return;
+				if(number != undef) {
+					goTo(number);
+					break;
+				}
+			case 32: // spacebar
+			case 34: // page down
+			case 39: // rightkey
+			case 40: // downkey
+				if(number != undef) {
+					go(number);
+				} else if (!incrementals[snum] || incpos >= incrementals[snum].length) {
+					go(1);
+				} else {
+					subgo(1);
+				}
+				break;
+			case 33: // page up
+			case 37: // leftkey
+			case 38: // upkey
+				if(number != undef) {
+					go(-1 * number);
+				} else if (!incrementals[snum] || incpos <= 0) {
+					go(-1);
+				} else {
+					subgo(-1);
+				}
+				break;
+			case 36: // home
+				goTo(0);
+				break;
+			case 35: // end
+				goTo(smax-1);
+				break;
+			case 67: // c
+				showHide('k');
+				break;
+		}
+		if (key.which < 48 || key.which > 57) {
+			number = undef;
+		} else {
+			if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return;
+			if (key.target && isParentOrSelf(key.target, 'controls')) return;
+			number = (((number != undef) ? number : 0) * 10) + (key.which - 48);
+		}
+	}
+	return false;
+}
+
+function clicker(e) {
+	number = undef;
+	var target;
+	if (window.event) {
+		target = window.event.srcElement;
+		e = window.event;
+	} else target = e.target;
+	if (target.getAttribute('href') != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target,'object')) return true;
+	if (!e.which || e.which == 1) {
+		if (!incrementals[snum] || incpos >= incrementals[snum].length) {
+			go(1);
+		} else {
+			subgo(1);
+		}
+	}
+}
+
+function findSlide(hash) {
+	var target = null;
+	var slides = GetElementsWithClassName('*','slide');
+	for (var i = 0; i < slides.length; i++) {
+		var targetSlide = slides[i];
+		if ( (targetSlide.name && targetSlide.name == hash)
+		 || (targetSlide.id && targetSlide.id == hash) ) {
+			target = targetSlide;
+			break;
+		}
+	}
+	while(target != null && target.nodeName != 'BODY') {
+		if (hasClass(target, 'slide')) {
+			return parseInt(target.id.slice(5));
+		}
+		target = target.parentNode;
+	}
+	return null;
+}
+
+function slideJump() {
+	if (window.location.hash == null) return;
+	var sregex = /^#slide(\d+)$/;
+	var matches = sregex.exec(window.location.hash);
+	var dest = null;
+	if (matches != null) {
+		dest = parseInt(matches[1]);
+	} else {
+		dest = findSlide(window.location.hash.slice(1));
+	}
+	if (dest != null)
+		go(dest - snum);
+}
+
+function fixLinks() {
+	var thisUri = window.location.href;
+	thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length);
+	var aelements = document.getElementsByTagName('A');
+	for (var i = 0; i < aelements.length; i++) {
+		var a = aelements[i].href;
+		var slideID = a.match('\#slide[0-9]{1,2}');
+		if ((slideID) && (slideID[0].slice(0,1) == '#')) {
+			var dest = findSlide(slideID[0].slice(1));
+			if (dest != null) {
+				if (aelements[i].addEventListener) {
+					aelements[i].addEventListener("click", new Function("e",
+						"if (document.getElementById('slideProj').disabled) return;" +
+						"go("+dest+" - snum); " +
+						"if (e.preventDefault) e.preventDefault();"), true);
+				} else if (aelements[i].attachEvent) {
+					aelements[i].attachEvent("onclick", new Function("",
+						"if (document.getElementById('slideProj').disabled) return;" +
+						"go("+dest+" - snum); " +
+						"event.returnValue = false;"));
+				}
+			}
+		}
+	}
+}
+
+function externalLinks() {
+	if (!document.getElementsByTagName) return;
+	var anchors = document.getElementsByTagName('a');
+	for (var i=0; i<anchors.length; i++) {
+		var anchor = anchors[i];
+		if (anchor.getAttribute('href') && hasValue(anchor.rel, 'external')) {
+			anchor.target = '_blank';
+			addClass(anchor,'external');
+		}
+	}
+}
+
+function createControls() {
+	var controlsDiv = document.getElementById("controls");
+	if (!controlsDiv) return;
+	var hider = ' onmouseover="showHide(\'s\');" onmouseout="showHide(\'h\');"';
+	var hideDiv, hideList = '';
+	if (controlVis == 'hidden') {
+		hideDiv = hider;
+	} else {
+		hideList = hider;
+	}
+	controlsDiv.innerHTML = '<form action="#" id="controlForm"' + hideDiv + '>' +
+	'<div id="navLinks">' +
+	'<a accesskey="t" id="toggle" href="javascript:toggle();">&#216;<\/a>' +
+	'<a accesskey="z" id="prev" href="javascript:go(-1);">&laquo;<\/a>' +
+	'<a accesskey="x" id="next" href="javascript:go(1);">&raquo;<\/a>' +
+	'<div id="navList"' + hideList + '><select id="jumplist" onchange="go(\'j\');"><\/select><\/div>' +
+	'<\/div><\/form>';
+	if (controlVis == 'hidden') {
+		var hidden = document.getElementById('navLinks');
+	} else {
+		var hidden = document.getElementById('jumplist');
+	}
+	addClass(hidden,'hideme');
+}
+
+function fontScale() {  // causes layout problems in FireFox that get fixed if browser's Reload is used; same may be true of other Gecko-based browsers
+	if (!s5mode) return false;
+	var vScale = 22;  // both yield 32 (after rounding) at 1024x768
+	var hScale = 32;  // perhaps should auto-calculate based on theme's declared value?
+	if (window.innerHeight) {
+		var vSize = window.innerHeight;
+		var hSize = window.innerWidth;
+	} else if (document.documentElement.clientHeight) {
+		var vSize = document.documentElement.clientHeight;
+		var hSize = document.documentElement.clientWidth;
+	} else if (document.body.clientHeight) {
+		var vSize = document.body.clientHeight;
+		var hSize = document.body.clientWidth;
+	} else {
+		var vSize = 700;  // assuming 1024x768, minus chrome and such
+		var hSize = 1024; // these do not account for kiosk mode or Opera Show
+	}
+	var newSize = Math.min(Math.round(vSize/vScale),Math.round(hSize/hScale));
+	fontSize(newSize + 'px');
+	if (isGe) {  // hack to counter incremental reflow bugs
+		var obj = document.getElementsByTagName('body')[0];
+		obj.style.display = 'none';
+		obj.style.display = 'block';
+	}
+}
+
+function fontSize(value) {
+	if (!(s5ss = document.getElementById('s5ss'))) {
+		if (!isIE) {
+			document.getElementsByTagName('head')[0].appendChild(s5ss = document.createElement('style'));
+			s5ss.setAttribute('media','screen, projection');
+			s5ss.setAttribute('id','s5ss');
+		} else {
+			document.createStyleSheet();
+			document.s5ss = document.styleSheets[document.styleSheets.length - 1];
+		}
+	}
+	if (!isIE) {
+		while (s5ss.lastChild) s5ss.removeChild(s5ss.lastChild);
+		s5ss.appendChild(document.createTextNode('body {font-size: ' + value + ' !important;}'));
+	} else {
+		document.s5ss.addRule('body','font-size: ' + value + ' !important;');
+	}
+}
+
+function notOperaFix() {
+	slideCSS = document.getElementById('slideProj').href;
+	var slides = document.getElementById('slideProj');
+	var outline = document.getElementById('outlineStyle');
+	slides.setAttribute('media','screen');
+	outline.disabled = true;
+	if (isGe) {
+		slides.setAttribute('href','null');   // Gecko fix
+		slides.setAttribute('href',slideCSS); // Gecko fix
+	}
+	if (isIE && document.styleSheets && document.styleSheets[0]) {
+		document.styleSheets[0].addRule('img', 'behavior: url(ui/default/iepngfix.htc)');
+		document.styleSheets[0].addRule('div', 'behavior: url(ui/default/iepngfix.htc)');
+		document.styleSheets[0].addRule('.slide', 'behavior: url(ui/default/iepngfix.htc)');
+	}
+}
+
+function getIncrementals(obj) {
+	var incrementals = new Array();
+	if (!obj) 
+		return incrementals;
+	var children = obj.childNodes;
+	for (var i = 0; i < children.length; i++) {
+		var child = children[i];
+		if (hasClass(child, 'incremental')) {
+			if (child.nodeName == 'OL' || child.nodeName == 'UL') {
+				removeClass(child, 'incremental');
+				for (var j = 0; j < child.childNodes.length; j++) {
+					if (child.childNodes[j].nodeType == 1) {
+						addClass(child.childNodes[j], 'incremental');
+					}
+				}
+			} else {
+				incrementals[incrementals.length] = child;
+				removeClass(child,'incremental');
+			}
+		}
+		if (hasClass(child, 'show-first')) {
+			if (child.nodeName == 'OL' || child.nodeName == 'UL') {
+				removeClass(child, 'show-first');
+				if (child.childNodes[isGe].nodeType == 1) {
+					removeClass(child.childNodes[isGe], 'incremental');
+				}
+			} else {
+				incrementals[incrementals.length] = child;
+			}
+		}
+		incrementals = incrementals.concat(getIncrementals(child));
+	}
+	return incrementals;
+}
+
+function createIncrementals() {
+	var incrementals = new Array();
+	for (var i = 0; i < smax; i++) {
+		incrementals[i] = getIncrementals(document.getElementById('slide'+i));
+	}
+	return incrementals;
+}
+
+function defaultCheck() {
+	var allMetas = document.getElementsByTagName('meta');
+	for (var i = 0; i< allMetas.length; i++) {
+		if (allMetas[i].name == 'defaultView') {
+			defaultView = allMetas[i].content;
+		}
+		if (allMetas[i].name == 'controlVis') {
+			controlVis = allMetas[i].content;
+		}
+	}
+}
+
+// Key trap fix, new function body for trap()
+function trap(e) {
+	if (!e) {
+		e = event;
+		e.which = e.keyCode;
+	}
+	try {
+		modifierKey = e.ctrlKey || e.altKey || e.metaKey;
+	}
+	catch(e) {
+		modifierKey = false;
+	}
+	return modifierKey || e.which == 0;
+}
+
+function startup() {
+	defaultCheck();
+	if (!isOp) 
+		createControls();
+	slideLabel();
+	fixLinks();
+	externalLinks();
+	fontScale();
+	if (!isOp) {
+		notOperaFix();
+		incrementals = createIncrementals();
+		slideJump();
+		if (defaultView == 'outline') {
+			toggle();
+		}
+		document.onkeyup = keys;
+		document.onkeypress = trap;
+		document.onclick = clicker;
+	}
+}
+
+window.onload = startup;
+window.onresize = function(){setTimeout('fontScale()', 50);}</script>
+
+<meta name="author" content="John Melesky" />
+<meta name="date" content="2007-05-10" />
+<title>Bayes' Theorem</title>
+</head>
+<body>
+<div class="layout">
+<div id="controls"></div>
+<div id="currentSlide"></div>
+<div id="header"></div>
+<div id="footer">
+
+<h1>2007-05-10</h1>
+<h2>Bayes' Theorem</h2>
+</div>
+</div>
+
+<div class="presentation">
+
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<h3>John Melesky</h3>
+<h4>2007-05-10</h4>
+</div>
+
+<div class="slide">
+
+<h1>What is Bayes' Theorem?</h1>
+<p>
+  It's a method for determining conditional probabilities.
+</p>
+<p>
+  Duh.
+</p>
+<p>
+  See? <a target="new" href="Bayes%27_theorem.html">Wikipedia says so</a>.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Um, dude, whut?</h1>
+</div>
+
+<div class="slide">
+
+<h1>Um, dude, whut?</h1>
+<p>
+  Seriously, i don't know, either.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>An example</h1>
+</div>
+
+<div class="slide">
+
+<h1>An example</h1>
+<p>
+  1000 Python programmers walk into a conference.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>An example</h1>
+<p>
+  1000 Python programmers walk into a conference.
+</p>
+<p>
+  10 of them (1%) harbour a dark secret.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>A test!</h1>
+</div>
+
+<div class="slide">
+
+<h1>A test!</h1>
+<p>
+  &quot;Do you prefer list comprehensions to 'map'?&quot;
+</p>
+</div>
+
+<div class="slide">
+
+<h1>A test!</h1>
+<p>
+  &quot;Do you prefer list comprehensions to 'map'?&quot;
+</p>
+<p>
+  How accurate is it?
+</p>
+<ul class="incremental">
+  <li>If you harbour the dark secret, it will return positive 90% of
+      the time</li>
+  <li>If you don't harbour that secret, it will return positive only
+      10% of the time</li>
+</ul>
+</div>
+
+<div class="slide">
+
+<h1>A question</h1>
+<p>
+  Joe takes the test, and it returns positive.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>A question</h1>
+<p>
+  Joe takes the test, and it returns positive.
+</p>
+<p>
+  How likely is he to harbour this dark secret?
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The answer</h1>
+<p>
+  90% !
+</p>
+<p>
+  Duh.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The answer</h1>
+<p>
+  Probably not what you think.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The answer: Bayes' Theorem</h1>
+<p>
+  Bayes' Theorem tells us how to compute this.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The answer: Bayes' Theorem</h1>
+<p>
+  Bayes' Theorem tells us how to compute this.
+</p>
+<p>
+  But we don't know Bayes' Theorem yet, so let's puzzle through it.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The question</h1>
+<p>
+  Joe takes the test, and it returns positive.
+</p>
+<p>
+  How likely is he to harbour this dark secret?
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The question, step-by-step</h1>
+<p>
+  If we test <em>everyone</em>, how many people will get positive
+  results?
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The question, step-by-step</h1>
+<p>
+  If we test <em>everyone</em>, how many people will get positive
+  results?
+</p>
+<p>
+  And of those, how many will actually be evil?
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The question, step-by-step</h1>
+<p>
+  If we test <em>everyone</em>, how many people will get positive
+  results?
+</p>
+<p>
+  And of those, how many will actually be evil?
+</p>
+<p>
+  We can use those two numbers to figure out how likely Joe is to be
+  evil.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The population</h1>
+<p>
+  1000 Python programmers
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The population</h1>
+<p>
+  1000 Python programmers
+</p>
+<p>
+  10% harbour the secret
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The population</h1>
+<p>
+  1000 Python programmers
+</p>
+<ul class="incremental">
+  <li>990 Pure and stout of heart</li>
+  <li>10 Dark-hearted functional bastards</li>
+</ul>
+</div>
+
+<div class="slide">
+
+<h1>The bad population</h1>
+<p>
+  10 Dark souls
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The bad population</h1>
+<p>
+  10 Dark souls
+</p>
+<p>
+  ... with 90% positive test results ...
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The bad population</h1>
+<p>
+  10 Dark souls
+</p>
+<p>
+  ... with 90% positive test results ...
+</p>
+<p>
+  ... means that if we administer the test to everyone, we'll
+  identify 9 functional bastards.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The bad population</h1>
+<p>
+  10 Dark souls
+</p>
+<p>
+  ... with 90% positive test results ...
+</p>
+<p>
+  ... means that if we administer the test to everyone, we'll
+  identify 9 functional bastards.
+</p>
+<p>
+  ... oh, and 1 will get through.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The good population</h1>
+<p>
+  990 Stout hearts
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The good population</h1>
+<p>
+  990 Stout hearts
+</p>
+<p>
+  ... with a mere 10% positive test results ...
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The good population</h1>
+<p>
+  990 Stout hearts
+</p>
+<p>
+  ... with a mere 10% positive test results ...
+</p>
+<p>
+  ... means that if we administer the test to everyone, we'll clear
+  891 pure pythonic souls.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The good population</h1>
+<p>
+  990 Stout hearts
+</p>
+<p>
+  ... with a mere 10% positive test results ...
+</p>
+<p>
+  ... means that if we administer the test to everyone, we'll clear
+  891 pure pythonic souls.
+</p>
+<p>
+  ... <em>BUT</em> 99 will be falsely misrepresented!
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The total population</h1>
+<p>
+  1000 Python programmers
+</p>
+<ul class="incremental">
+  <li><p>
+        10 evildoers
+      </p>
+      <ul class="incremental">
+        <li><p>
+              9 rightly identified
+            </p></li>
+        <li><p>
+              1 slips through the cracks
+            </p></li>
+      </ul></li>
+  <li><p>
+        990 true and stout-hearted pythonists
+      </p>
+      <ul class="incremental">
+        <li><p>
+              99 falsely accused
+            </p></li>
+        <li><p>
+              891 cleared by the test
+            </p></li>
+      </ul></li>
+</ul>
+</div>
+
+<div class="slide">
+
+<h1>The positive-test population</h1>
+<p>
+  108 people will test positive (99 + 9)
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The positive-test population</h1>
+<p>
+  108 people will test positive (99 + 9)
+</p>
+<p>
+  9 of which are actual wrong-headed fools.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The positive-test population</h1>
+<p>
+  108 people will test positive (99 + 9)
+</p>
+<p>
+  9 of which are actual wrong-headed fools.
+</p>
+<p>
+  Which means that 8.33% (9 / 108) of those with a positive test
+  result will be nasty, worthless functional programmers.
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The positive-test population</h1>
+<p>
+  108 people will test positive (99 + 9)
+</p>
+<p>
+  9 of which are actual wrong-headed fools.
+</p>
+<p>
+  Which means that 8.33% (9 / 108) of those with a positive test
+  result will be nasty, worthless functional programmers.
+</p>
+<p>
+  Which is a far cry from 90%
+</p>
+</div>
+
+<div class="slide">
+
+<h1>The total population</h1>
+<p>
+  1000 Python programmers
+</p>
+<p>
+  1% incidence of dark secret, 90% positive test rate for those who
+  harbour dark secret, 10% positive test rate for those who are
+  innocent.
+</p>
+<ul>
+  <li><p>
+        1% - 10 functional programmers
+      </p>
+      <ul>
+        <li><p>
+              .9% - 9 rightly identified
+            </p></li>
+        <li><p>
+              .1% - 1 slips through the cracks
+            </p></li>
+      </ul></li>
+  <li><p>
+        99% - 990 true pythonists
+      </p>
+      <ul>
+        <li><p>
+              9.9% - 99 falsely accused
+            </p></li>
+        <li><p>
+              89.1% - 891 cleared by the test
+            </p></li>
+      </ul></li>
+</ul>
+</div>
+
+<div class="slide">
+
+<h1>The total population</h1>
+<p>
+  1000 Python programmers
+</p>
+<p>
+  1% incidence of dark secret, 95% positive test rate for those who
+  harbour dark secret, 10% positive test rate for those who are
+  innocent.
+</p>
+<ul>
+  <li><p>
+        1% - 10 functional programmers
+      </p>
+      <ul>
+        <li><p>
+              .95% - 9.5 rightly identified
+            </p></li>
+        <li><p>
+              .05% - 0.5 slips through the cracks
+            </p></li>
+      </ul></li>
+  <li><p>
+        99% - 990 true pythonists
+      </p>
+      <ul>
+        <li><p>
+              9.9% - 99 falsely accused
+            </p></li>
+        <li><p>
+              89.1% - 891 cleared by the test
+            </p></li>
+      </ul></li>
+</ul>
+</div>
+
+<div class="slide">
+
+<h1>The total population</h1>
+<p>
+  1000 Python programmers
+</p>
+<p>
+  1% incidence of dark secret, 95% positive test rate for those who
+  harbour dark secret, 2% positive test rate for those who are
+  innocent.
+</p>
+<ul>
+  <li><p>
+        1% - 10 functional programmers
+      </p>
+      <ul>
+        <li><p>
+              .95% - 9.5 rightly identified
+            </p></li>
+        <li><p>
+              .05% - 0.5 slips through the cracks
+            </p></li>
+      </ul></li>
+  <li><p>
+        99% - 990 true pythonists
+      </p>
+      <ul>
+        <li><p>
+              1.98% - 19.8 falsely accused
+            </p></li>
+        <li><p>
+              97.02% - 970.2 cleared by the test
+            </p></li>
+      </ul></li>
+</ul>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  probability(positive test result, given guilt) * probability(guilt)
+</p>
+<p>
+  over
+</p>
+<p>
+  probability(positive test result)
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  probability(positive test result, given guilt) * 1%
+</p>
+<p>
+  over
+</p>
+<p>
+  probability(positive test result)
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  90% * 1%
+</p>
+<p>
+  over
+</p>
+<p>
+  probability(positive test result)
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  90% * 1%
+</p>
+<p>
+  over
+</p>
+<p>
+  hmmm....
+</p>
+</div>
+
+<div class="slide">
+
+<h1>probability(positive test result)</h1>
+<p>
+  population with positive test result
+</p>
+<p>
+  over
+</p>
+<p>
+  total population
+</p>
+</div>
+
+<div class="slide">
+
+<h1>probability(positive test result)</h1>
+<p>
+  population of true positives + population of false positives
+</p>
+<p>
+  over
+</p>
+<p>
+  total population
+</p>
+</div>
+
+<div class="slide">
+
+<h1>probability(positive test result)</h1>
+<p>
+  9 + population of false positives
+</p>
+<p>
+  over
+</p>
+<p>
+  total population
+</p>
+</div>
+
+<div class="slide">
+
+<h1>probability(positive test result)</h1>
+<p>
+  9 + 99
+</p>
+<p>
+  over
+</p>
+<p>
+  total population
+</p>
+</div>
+
+<div class="slide">
+
+<h1>probability(positive test result)</h1>
+<p>
+  9 + 99
+</p>
+<p>
+  over
+</p>
+<p>
+  1000
+</p>
+</div>
+
+<div class="slide">
+
+<h1>probability(positive test result)</h1>
+<p>
+  (9 + 108) / 1000 = 10.8%
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  90% * 1%
+</p>
+<p>
+  over
+</p>
+<p>
+  10.8%
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  90% * 1% / 10.8% =
+</p>
+<p>
+  8.33%
+</p>
+</div>
+
+<div class="slide">
+
+<h1>Bayes' Theorem</h1>
+<p>
+  probability(guilt, given a positive test result) =
+</p>
+<p>
+  90% * 1% / 10.8% =
+</p>
+<p>
+  8.33%
+</p>
+<p>
+  And it's just that simple
+</p>
+</div>
+
+<div class="slide">
+
+<h1>But....</h1>
+<p>
+  What's this got to do with spam?
+</p>
+</div>
+
+</div>
+
+</body>
+</html>

+ 7 - 0
talks/cant-c-a-thing/bookmarks

@@ -0,0 +1,7 @@
+http://www.biais.org/blog/index.php/2007/06/25/50-how-to-write-caml-extensions-for-python
+http://www.mega-nerd.com/erikd/Blog/CodeHacking/Ocaml/index.html
+http://www.ocaml-tutorial.org/calling_c_libraries
+http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html
+
+
+http://www.linux-nantes.org/~fmonnier/OCaml/ocaml-wrapping-c.php

+ 22 - 0
talks/cant-c-a-thing/code/gen_matrix.rb

@@ -0,0 +1,22 @@
+#!/usr/bin/env ruby
+
+matrix = []
+
+100.times {
+  column = []
+
+  100.times {
+    column.push(rand(1000))
+
+  }
+
+  matrix.push(column)
+}
+
+
+puts "[ "
+print matrix.collect {|r|
+  "[ " + r.join(", ") + "],\n"
+}
+puts "]"
+

+ 226 - 0
talks/cant-c-a-thing/code/matrix.rb

@@ -0,0 +1,226 @@
+#!/usr/bin/env ruby
+
+def matrix_mul(a,b)
+  rows = (0 .. a.size - 1).collect {
+    |i|
+    (0 .. b[0].size - 1).collect {
+      |j|
+      vij = 0
+      0.upto(a[i].size - 1) do
+        |k|
+        vij += a[i][k] * b[k][j]
+      end
+      vij
+    }
+  }
+  return rows
+end
+
+
+matrix1 = [ 
+[ 13, 458, 667, 794, 474, 190, 590, 164, 418, 713, 461, 72, 808, 252, 398, 668, 675, 392, 658, 285, 465, 759, 931, 318, 252, 137, 161, 961, 719, 710, 638, 475, 874, 832, 38, 775, 127, 232, 360, 53, 940, 62, 834, 369, 202, 101, 776, 475, 614, 112, 878, 94, 933, 957, 406, 604, 754, 746, 686, 798, 137, 978, 644, 884, 243, 949, 886, 430, 897, 838, 532, 691, 752, 321, 769, 303, 796, 355, 712, 761, 899, 191, 196, 890, 354, 641, 645, 756, 456, 422, 420, 681, 502, 386, 475, 342, 263, 387, 903, 585],
+[ 471, 29, 715, 919, 269, 703, 485, 770, 409, 254, 766, 399, 174, 697, 871, 663, 380, 843, 374, 861, 376, 161, 177, 496, 590, 688, 857, 47, 148, 889, 542, 711, 837, 884, 50, 40, 243, 409, 225, 214, 348, 695, 524, 82, 976, 235, 500, 85, 660, 982, 477, 200, 673, 702, 258, 697, 475, 580, 895, 399, 241, 308, 930, 974, 975, 961, 934, 114, 972, 556, 734, 407, 792, 470, 457, 682, 210, 70, 318, 884, 711, 682, 318, 154, 932, 336, 876, 988, 780, 498, 570, 997, 380, 738, 643, 576, 500, 417, 951, 917],
+[ 832, 881, 641, 275, 630, 836, 447, 177, 398, 30, 183, 520, 1, 999, 941, 108, 465, 609, 893, 320, 359, 422, 443, 319, 446, 263, 2, 670, 464, 407, 855, 133, 107, 287, 627, 742, 782, 850, 897, 311, 641, 142, 467, 219, 832, 465, 172, 979, 819, 633, 122, 970, 242, 412, 560, 644, 724, 404, 705, 89, 197, 43, 173, 888, 518, 104, 758, 204, 445, 31, 837, 708, 511, 140, 909, 371, 382, 295, 262, 144, 617, 943, 209, 216, 485, 541, 77, 737, 708, 787, 696, 5, 332, 954, 519, 374, 488, 84, 176, 365],
+[ 574, 23, 755, 550, 81, 403, 35, 616, 646, 26, 489, 502, 115, 469, 766, 975, 981, 710, 689, 386, 420, 405, 326, 65, 384, 845, 869, 556, 845, 222, 99, 770, 290, 199, 545, 105, 510, 728, 668, 66, 248, 426, 171, 283, 488, 514, 769, 673, 190, 819, 110, 94, 868, 49, 837, 36, 170, 10, 686, 475, 105, 215, 209, 579, 271, 959, 46, 48, 408, 948, 184, 534, 672, 325, 79, 301, 59, 232, 251, 53, 763, 78, 29, 570, 942, 216, 524, 357, 528, 893, 652, 545, 250, 14, 137, 158, 332, 710, 517, 625],
+[ 818, 612, 346, 27, 59, 585, 113, 883, 534, 331, 320, 641, 671, 432, 58, 181, 961, 434, 290, 417, 643, 816, 835, 135, 924, 927, 182, 838, 457, 625, 528, 642, 360, 651, 208, 28, 117, 174, 272, 243, 57, 923, 809, 156, 935, 979, 780, 844, 976, 674, 108, 117, 254, 434, 897, 630, 117, 241, 712, 234, 203, 870, 56, 828, 399, 909, 606, 402, 936, 581, 155, 691, 359, 771, 41, 589, 773, 622, 502, 182, 486, 583, 68, 291, 669, 981, 65, 976, 418, 278, 899, 573, 399, 45, 513, 261, 540, 411, 29, 511],
+[ 666, 292, 522, 214, 224, 159, 941, 805, 847, 174, 206, 116, 557, 570, 181, 749, 523, 791, 180, 802, 802, 690, 726, 798, 967, 398, 528, 528, 53, 719, 67, 201, 100, 995, 945, 252, 644, 328, 15, 60, 934, 849, 239, 242, 169, 131, 468, 54, 750, 27, 700, 704, 89, 810, 951, 599, 959, 279, 578, 364, 512, 269, 478, 570, 735, 792, 614, 198, 671, 140, 553, 311, 813, 741, 354, 272, 743, 829, 376, 464, 294, 111, 61, 791, 774, 754, 659, 180, 273, 959, 379, 113, 147, 279, 749, 728, 727, 937, 25, 257],
+[ 740, 239, 730, 571, 449, 192, 418, 178, 562, 591, 474, 832, 119, 113, 669, 259, 565, 309, 512, 529, 761, 353, 568, 734, 0, 883, 607, 455, 447, 5, 229, 350, 432, 4, 30, 879, 861, 445, 153, 120, 713, 810, 715, 584, 255, 953, 352, 938, 396, 729, 738, 459, 413, 367, 370, 917, 360, 375, 802, 497, 971, 250, 140, 731, 627, 507, 907, 694, 678, 36, 221, 42, 977, 71, 908, 51, 448, 530, 959, 427, 1, 442, 849, 703, 313, 754, 830, 311, 995, 947, 51, 791, 250, 79, 611, 302, 375, 846, 562, 141],
+[ 524, 47, 289, 556, 90, 800, 78, 576, 605, 104, 882, 770, 481, 84, 379, 340, 314, 304, 994, 948, 504, 887, 249, 246, 744, 729, 176, 573, 458, 443, 601, 963, 531, 671, 631, 16, 258, 290, 706, 23, 578, 812, 79, 85, 863, 503, 369, 26, 751, 294, 933, 231, 776, 913, 904, 603, 891, 113, 985, 470, 384, 234, 601, 919, 21, 570, 399, 464, 150, 46, 825, 250, 559, 244, 14, 767, 59, 964, 996, 644, 730, 802, 570, 963, 883, 89, 279, 826, 407, 140, 665, 611, 63, 741, 617, 842, 792, 557, 982, 154],
+[ 104, 977, 251, 143, 854, 149, 416, 480, 898, 704, 578, 132, 977, 511, 15, 172, 678, 862, 400, 293, 157, 407, 344, 540, 138, 752, 66, 308, 427, 641, 608, 369, 406, 182, 549, 858, 985, 752, 219, 235, 532, 103, 618, 522, 470, 156, 704, 129, 45, 133, 478, 26, 814, 987, 479, 117, 488, 414, 475, 53, 870, 232, 166, 22, 978, 181, 529, 480, 376, 25, 842, 153, 141, 602, 147, 199, 408, 673, 196, 576, 733, 488, 498, 521, 93, 360, 836, 534, 220, 599, 982, 461, 797, 269, 872, 46, 710, 367, 739, 41],
+[ 944, 815, 471, 676, 226, 348, 413, 145, 474, 762, 642, 335, 664, 964, 219, 614, 456, 704, 155, 810, 23, 460, 648, 876, 448, 842, 292, 833, 576, 837, 185, 343, 957, 219, 746, 482, 174, 812, 583, 501, 336, 309, 518, 483, 367, 8, 440, 690, 619, 316, 548, 833, 923, 677, 574, 132, 967, 271, 202, 111, 374, 962, 450, 749, 129, 563, 948, 778, 502, 943, 863, 935, 312, 731, 142, 726, 510, 953, 798, 766, 934, 26, 94, 776, 289, 972, 821, 503, 540, 203, 783, 333, 503, 781, 874, 309, 502, 644, 947, 219],
+[ 689, 803, 790, 729, 230, 755, 567, 824, 402, 897, 482, 519, 429, 148, 825, 631, 265, 443, 895, 638, 204, 878, 718, 953, 367, 52, 601, 503, 254, 108, 718, 360, 21, 116, 746, 527, 348, 660, 684, 36, 560, 306, 30, 498, 326, 932, 348, 674, 509, 881, 167, 443, 360, 208, 46, 924, 959, 574, 482, 620, 561, 158, 497, 33, 786, 723, 528, 931, 166, 935, 424, 624, 576, 523, 458, 733, 472, 750, 252, 257, 778, 541, 478, 673, 343, 253, 498, 778, 380, 320, 630, 315, 764, 266, 740, 940, 941, 217, 30, 825],
+[ 833, 651, 195, 221, 32, 666, 999, 967, 651, 934, 414, 224, 184, 573, 33, 975, 223, 768, 330, 38, 94, 464, 358, 119, 900, 880, 141, 835, 345, 520, 45, 302, 609, 251, 998, 38, 862, 643, 535, 811, 525, 284, 540, 529, 124, 760, 176, 81, 590, 93, 378, 947, 195, 93, 949, 470, 405, 971, 106, 16, 367, 242, 193, 217, 550, 178, 368, 806, 220, 296, 245, 604, 806, 241, 437, 866, 480, 411, 233, 321, 579, 46, 107, 706, 313, 322, 183, 959, 274, 868, 426, 156, 684, 458, 220, 750, 708, 750, 188, 343],
+[ 708, 130, 155, 703, 348, 471, 382, 659, 917, 59, 96, 572, 599, 435, 821, 14, 863, 747, 495, 360, 680, 737, 20, 49, 639, 398, 769, 625, 159, 599, 377, 875, 982, 181, 466, 7, 29, 197, 979, 707, 169, 204, 895, 104, 505, 120, 780, 233, 875, 390, 503, 12, 361, 994, 211, 3, 627, 747, 277, 496, 17, 15, 607, 929, 1, 463, 595, 422, 344, 924, 709, 939, 461, 629, 44, 678, 156, 816, 421, 853, 376, 172, 857, 777, 293, 89, 203, 729, 110, 770, 289, 795, 251, 729, 302, 102, 545, 642, 468, 373],
+[ 950, 758, 247, 434, 855, 261, 874, 334, 356, 805, 220, 109, 525, 566, 533, 947, 941, 654, 95, 511, 939, 546, 568, 815, 89, 260, 272, 792, 891, 367, 608, 91, 689, 636, 446, 488, 74, 508, 257, 13, 408, 273, 789, 984, 749, 965, 583, 920, 703, 762, 723, 124, 689, 249, 885, 39, 829, 261, 748, 415, 665, 481, 817, 525, 808, 3, 626, 481, 434, 112, 946, 135, 708, 340, 949, 288, 526, 523, 417, 959, 668, 87, 106, 586, 430, 151, 966, 824, 997, 343, 235, 676, 944, 147, 720, 909, 394, 769, 618, 263],
+[ 799, 8, 776, 287, 629, 611, 860, 539, 22, 707, 685, 40, 548, 156, 23, 104, 751, 759, 392, 941, 520, 193, 978, 686, 396, 123, 151, 513, 570, 944, 516, 339, 195, 96, 656, 821, 273, 128, 695, 937, 230, 714, 936, 220, 783, 375, 489, 512, 173, 358, 702, 989, 438, 938, 772, 149, 587, 453, 373, 6, 270, 237, 169, 344, 288, 520, 166, 872, 67, 334, 409, 508, 729, 149, 540, 916, 519, 541, 115, 58, 580, 311, 115, 648, 640, 727, 373, 837, 350, 739, 610, 325, 643, 348, 917, 815, 990, 416, 636, 837],
+[ 726, 669, 723, 348, 924, 410, 280, 192, 682, 371, 132, 487, 106, 951, 298, 983, 724, 8, 507, 451, 219, 966, 979, 704, 156, 237, 930, 29, 899, 488, 812, 209, 701, 934, 807, 159, 55, 767, 700, 485, 173, 352, 60, 947, 19, 140, 793, 856, 949, 48, 331, 458, 785, 238, 463, 691, 765, 461, 133, 114, 406, 125, 643, 108, 725, 435, 807, 25, 797, 734, 618, 75, 728, 3, 452, 109, 202, 59, 949, 437, 673, 129, 230, 150, 788, 58, 542, 171, 393, 146, 436, 496, 310, 927, 813, 107, 496, 132, 959, 400],
+[ 177, 48, 86, 428, 979, 703, 678, 260, 714, 25, 547, 712, 192, 853, 723, 465, 795, 490, 931, 912, 272, 819, 531, 754, 148, 492, 906, 112, 187, 652, 872, 468, 179, 703, 595, 54, 537, 948, 553, 36, 951, 568, 749, 651, 154, 528, 27, 256, 676, 430, 147, 838, 91, 747, 256, 246, 983, 690, 483, 525, 157, 673, 871, 771, 465, 693, 143, 365, 540, 710, 782, 174, 890, 793, 207, 174, 638, 905, 577, 225, 628, 224, 195, 560, 860, 613, 68, 588, 415, 805, 873, 782, 885, 650, 772, 297, 449, 307, 398, 777],
+[ 422, 178, 282, 640, 756, 342, 94, 657, 563, 336, 87, 763, 710, 889, 632, 757, 76, 954, 651, 728, 824, 195, 621, 648, 386, 493, 941, 970, 816, 198, 450, 809, 760, 418, 558, 695, 331, 200, 1, 596, 954, 428, 440, 41, 531, 682, 117, 33, 498, 389, 161, 298, 199, 462, 273, 780, 289, 613, 764, 646, 813, 514, 508, 311, 52, 896, 816, 434, 488, 79, 806, 349, 592, 20, 424, 916, 172, 837, 574, 937, 374, 213, 668, 358, 714, 851, 782, 617, 336, 132, 914, 202, 104, 224, 676, 674, 349, 795, 308, 794],
+[ 16, 483, 140, 120, 643, 768, 463, 61, 37, 58, 347, 586, 746, 588, 850, 964, 29, 443, 949, 960, 219, 994, 861, 870, 517, 338, 529, 353, 229, 635, 34, 763, 207, 278, 633, 43, 403, 926, 545, 544, 620, 385, 755, 88, 620, 219, 169, 438, 632, 953, 694, 327, 448, 729, 927, 644, 289, 643, 792, 738, 712, 871, 799, 513, 217, 764, 14, 676, 989, 463, 892, 918, 5, 350, 240, 156, 17, 850, 282, 127, 470, 109, 82, 624, 940, 428, 555, 116, 572, 566, 731, 617, 370, 943, 347, 885, 947, 321, 913, 102],
+[ 952, 592, 850, 993, 3, 924, 234, 709, 905, 727, 716, 595, 889, 404, 538, 72, 238, 382, 919, 35, 39, 768, 834, 270, 160, 512, 956, 175, 579, 719, 70, 626, 321, 923, 251, 873, 261, 189, 105, 139, 151, 386, 808, 899, 901, 97, 53, 415, 163, 536, 564, 176, 160, 386, 511, 749, 868, 179, 881, 601, 270, 232, 782, 799, 21, 488, 712, 127, 95, 315, 572, 320, 871, 458, 184, 621, 481, 219, 327, 442, 196, 409, 471, 959, 487, 200, 527, 272, 433, 657, 650, 409, 788, 291, 832, 689, 36, 0, 481, 400],
+[ 924, 734, 63, 557, 915, 936, 250, 720, 481, 315, 153, 122, 21, 727, 371, 8, 225, 826, 827, 344, 235, 867, 629, 747, 213, 786, 851, 780, 526, 56, 233, 204, 123, 49, 864, 403, 62, 270, 980, 732, 165, 40, 590, 522, 862, 214, 496, 166, 366, 946, 76, 703, 744, 541, 227, 293, 983, 841, 442, 455, 670, 45, 564, 905, 117, 692, 903, 540, 154, 73, 136, 932, 969, 563, 514, 969, 50, 322, 447, 729, 285, 262, 663, 83, 963, 456, 745, 699, 33, 875, 590, 469, 541, 846, 136, 700, 283, 869, 974, 224],
+[ 239, 879, 691, 695, 216, 457, 353, 264, 211, 712, 911, 679, 421, 540, 492, 10, 796, 828, 84, 992, 596, 906, 500, 340, 425, 452, 801, 930, 74, 718, 338, 297, 277, 474, 455, 739, 439, 692, 384, 776, 760, 888, 175, 977, 381, 614, 613, 229, 632, 56, 959, 997, 155, 427, 408, 52, 122, 295, 651, 164, 395, 638, 713, 142, 200, 912, 189, 71, 673, 938, 26, 516, 244, 566, 378, 896, 542, 591, 97, 320, 978, 881, 228, 615, 728, 704, 776, 962, 473, 274, 915, 977, 687, 131, 232, 19, 537, 796, 186, 317],
+[ 386, 376, 759, 333, 495, 987, 100, 685, 405, 463, 662, 37, 219, 586, 480, 110, 918, 751, 796, 530, 38, 384, 30, 25, 432, 114, 49, 174, 119, 883, 701, 789, 132, 75, 456, 147, 425, 769, 582, 89, 732, 417, 462, 723, 278, 595, 5, 897, 600, 809, 797, 359, 242, 450, 175, 194, 920, 718, 38, 242, 671, 915, 870, 724, 247, 328, 354, 30, 997, 534, 523, 659, 758, 755, 208, 281, 235, 688, 360, 55, 193, 644, 797, 925, 40, 902, 155, 573, 584, 997, 665, 896, 808, 161, 827, 47, 631, 868, 761, 574],
+[ 347, 127, 476, 519, 358, 390, 895, 21, 811, 806, 246, 53, 934, 542, 250, 694, 855, 290, 294, 657, 969, 896, 427, 598, 703, 724, 108, 242, 202, 44, 222, 99, 169, 821, 338, 631, 937, 412, 487, 581, 824, 873, 636, 555, 529, 866, 156, 502, 381, 524, 693, 242, 380, 3, 423, 820, 380, 740, 273, 907, 53, 361, 743, 889, 746, 297, 585, 374, 650, 916, 191, 807, 128, 258, 16, 299, 972, 581, 573, 419, 762, 618, 484, 24, 908, 744, 686, 114, 417, 166, 802, 745, 577, 734, 647, 689, 979, 552, 783, 914],
+[ 492, 272, 626, 991, 522, 426, 989, 162, 420, 452, 25, 886, 910, 824, 236, 597, 295, 908, 525, 243, 140, 895, 281, 728, 953, 540, 307, 510, 133, 89, 321, 547, 417, 284, 759, 913, 796, 646, 675, 454, 747, 15, 173, 467, 543, 543, 331, 83, 965, 432, 178, 876, 238, 713, 365, 204, 620, 722, 51, 443, 203, 942, 759, 337, 482, 231, 363, 570, 866, 142, 816, 104, 312, 477, 315, 478, 557, 289, 713, 644, 597, 261, 290, 227, 452, 762, 882, 500, 997, 306, 920, 258, 340, 703, 502, 940, 172, 153, 356, 800],
+[ 370, 977, 232, 299, 448, 891, 102, 428, 597, 70, 828, 395, 629, 183, 65, 539, 803, 148, 942, 824, 644, 698, 880, 949, 793, 669, 20, 435, 757, 243, 860, 802, 814, 318, 870, 0, 884, 653, 386, 520, 882, 722, 323, 259, 856, 279, 830, 577, 395, 518, 967, 332, 2, 920, 374, 42, 398, 109, 739, 511, 691, 536, 934, 748, 755, 7, 55, 126, 661, 23, 687, 199, 153, 884, 32, 254, 887, 480, 448, 232, 230, 149, 610, 711, 534, 567, 567, 190, 391, 459, 707, 292, 398, 349, 402, 733, 998, 794, 601, 712],
+[ 285, 622, 412, 377, 604, 165, 215, 583, 140, 830, 2, 949, 438, 866, 642, 542, 412, 880, 869, 306, 251, 832, 604, 413, 9, 342, 79, 147, 747, 982, 275, 240, 394, 77, 52, 596, 22, 446, 385, 528, 109, 52, 514, 384, 383, 267, 157, 113, 835, 23, 471, 948, 203, 387, 169, 81, 738, 218, 189, 24, 626, 398, 483, 392, 573, 993, 983, 720, 666, 23, 344, 698, 726, 911, 587, 332, 918, 696, 606, 930, 92, 946, 851, 305, 919, 795, 842, 47, 721, 972, 942, 538, 917, 465, 887, 66, 117, 432, 522, 680],
+[ 186, 183, 101, 655, 548, 888, 405, 282, 835, 795, 530, 847, 257, 70, 571, 926, 342, 352, 419, 228, 116, 343, 287, 397, 25, 185, 310, 615, 993, 594, 668, 889, 381, 714, 407, 874, 908, 525, 938, 878, 868, 87, 583, 926, 782, 440, 213, 964, 846, 536, 66, 655, 472, 184, 664, 540, 124, 306, 276, 336, 548, 884, 597, 410, 633, 756, 2, 366, 660, 836, 218, 5, 362, 589, 266, 75, 27, 723, 773, 691, 891, 380, 678, 571, 341, 348, 306, 901, 302, 59, 791, 23, 932, 172, 540, 712, 607, 473, 230, 640],
+[ 821, 438, 975, 442, 709, 268, 695, 469, 686, 121, 393, 979, 83, 868, 156, 75, 716, 379, 204, 235, 886, 270, 266, 245, 628, 678, 785, 459, 723, 597, 99, 301, 901, 192, 707, 692, 69, 242, 175, 901, 754, 481, 679, 333, 560, 91, 359, 741, 900, 894, 552, 432, 839, 866, 744, 246, 823, 529, 193, 828, 839, 744, 494, 778, 244, 373, 90, 111, 828, 388, 83, 914, 367, 159, 503, 176, 266, 525, 23, 725, 984, 562, 100, 720, 466, 914, 87, 611, 107, 726, 727, 87, 803, 79, 689, 173, 602, 822, 143, 994],
+[ 391, 754, 164, 791, 822, 431, 156, 633, 9, 898, 762, 50, 270, 90, 928, 691, 382, 524, 238, 795, 548, 131, 886, 893, 48, 36, 973, 668, 675, 593, 489, 121, 639, 772, 613, 756, 881, 632, 298, 703, 341, 92, 705, 190, 734, 859, 19, 302, 156, 203, 383, 732, 40, 191, 893, 66, 980, 759, 175, 69, 778, 659, 102, 510, 50, 338, 529, 100, 102, 613, 151, 453, 833, 492, 626, 506, 836, 155, 658, 466, 637, 906, 502, 239, 306, 665, 245, 580, 213, 43, 373, 769, 539, 70, 733, 53, 242, 943, 432, 129],
+[ 958, 780, 435, 245, 741, 922, 433, 828, 14, 309, 151, 786, 237, 507, 163, 306, 362, 807, 940, 83, 4, 82, 855, 950, 616, 639, 401, 15, 675, 213, 114, 143, 957, 998, 388, 422, 407, 852, 285, 179, 910, 538, 379, 947, 537, 757, 136, 659, 339, 310, 323, 371, 92, 75, 52, 496, 155, 522, 491, 985, 150, 534, 674, 440, 668, 287, 454, 367, 70, 740, 208, 923, 522, 328, 78, 630, 874, 741, 157, 121, 340, 727, 835, 525, 373, 120, 365, 612, 668, 436, 498, 435, 227, 457, 130, 783, 698, 674, 750, 358],
+[ 752, 748, 639, 811, 169, 386, 755, 923, 610, 406, 430, 579, 122, 550, 503, 27, 937, 908, 867, 19, 466, 550, 394, 175, 115, 718, 128, 456, 614, 116, 543, 733, 993, 813, 847, 644, 305, 608, 331, 436, 475, 910, 998, 420, 728, 223, 987, 691, 163, 122, 568, 961, 981, 276, 616, 405, 684, 120, 259, 420, 29, 817, 833, 549, 197, 350, 479, 755, 34, 776, 733, 529, 155, 738, 104, 714, 908, 833, 518, 641, 798, 538, 794, 183, 94, 454, 603, 188, 493, 138, 66, 759, 104, 884, 837, 241, 546, 894, 666, 354],
+[ 543, 651, 287, 308, 110, 188, 968, 950, 593, 484, 126, 983, 274, 647, 386, 510, 604, 649, 125, 994, 379, 309, 632, 503, 812, 358, 513, 145, 569, 213, 519, 638, 40, 311, 578, 761, 22, 785, 168, 543, 973, 809, 789, 388, 41, 969, 464, 624, 514, 166, 328, 912, 706, 540, 657, 735, 367, 776, 25, 106, 449, 758, 897, 869, 369, 748, 89, 680, 439, 144, 480, 488, 130, 204, 479, 581, 573, 167, 497, 962, 385, 339, 597, 807, 851, 463, 706, 481, 408, 761, 688, 14, 432, 854, 981, 890, 222, 792, 616, 395],
+[ 830, 656, 853, 928, 408, 355, 638, 646, 180, 301, 783, 257, 149, 571, 443, 522, 179, 321, 926, 223, 557, 238, 18, 165, 805, 410, 203, 567, 549, 201, 841, 511, 342, 41, 80, 561, 759, 481, 704, 506, 645, 316, 59, 984, 337, 786, 831, 474, 971, 90, 293, 531, 568, 846, 588, 160, 308, 837, 683, 105, 24, 791, 859, 228, 280, 604, 627, 223, 781, 709, 376, 777, 886, 73, 268, 177, 101, 354, 817, 75, 181, 927, 26, 633, 505, 660, 361, 165, 153, 516, 606, 925, 311, 169, 130, 213, 381, 989, 772, 427],
+[ 702, 358, 467, 189, 170, 5, 768, 288, 103, 3, 155, 754, 312, 179, 645, 54, 750, 589, 618, 950, 417, 586, 382, 539, 970, 987, 684, 516, 307, 777, 779, 734, 891, 508, 596, 278, 816, 106, 320, 526, 33, 245, 543, 900, 854, 475, 207, 482, 377, 992, 440, 939, 446, 799, 70, 491, 624, 557, 787, 991, 859, 658, 39, 802, 952, 898, 829, 848, 287, 268, 57, 279, 746, 899, 782, 487, 368, 715, 945, 93, 198, 510, 859, 941, 815, 96, 831, 822, 937, 414, 197, 592, 919, 478, 507, 701, 919, 794, 301, 548],
+[ 176, 43, 493, 300, 286, 807, 706, 313, 629, 560, 329, 558, 722, 315, 638, 12, 684, 144, 657, 107, 456, 690, 371, 526, 917, 699, 962, 792, 640, 755, 136, 734, 348, 601, 823, 73, 384, 240, 186, 187, 929, 718, 349, 981, 74, 290, 703, 846, 509, 906, 652, 762, 872, 556, 928, 461, 334, 619, 114, 719, 828, 449, 596, 616, 92, 278, 403, 770, 284, 525, 649, 102, 316, 493, 691, 726, 413, 283, 671, 906, 935, 988, 409, 476, 276, 890, 572, 500, 111, 559, 126, 666, 754, 601, 144, 445, 912, 145, 645, 367],
+[ 557, 336, 787, 429, 932, 959, 980, 109, 975, 72, 248, 841, 668, 586, 504, 672, 428, 582, 815, 290, 343, 860, 270, 872, 413, 498, 656, 138, 550, 204, 981, 782, 605, 231, 368, 519, 175, 639, 382, 307, 413, 609, 0, 240, 725, 226, 163, 503, 825, 869, 535, 362, 712, 980, 178, 995, 292, 251, 542, 175, 546, 157, 172, 743, 625, 16, 664, 911, 42, 464, 801, 313, 508, 998, 964, 284, 894, 852, 577, 992, 780, 88, 39, 504, 302, 336, 432, 888, 724, 435, 783, 595, 110, 155, 997, 618, 269, 451, 358, 750],
+[ 195, 674, 151, 665, 281, 824, 780, 877, 977, 655, 329, 780, 789, 810, 381, 972, 731, 496, 341, 301, 50, 936, 36, 334, 758, 730, 735, 330, 107, 847, 973, 256, 416, 686, 947, 377, 580, 38, 667, 366, 670, 107, 416, 687, 425, 16, 894, 269, 57, 439, 369, 998, 388, 632, 643, 718, 733, 17, 46, 362, 165, 111, 925, 25, 224, 948, 576, 425, 505, 872, 72, 782, 639, 874, 318, 848, 591, 459, 340, 943, 139, 220, 790, 325, 219, 657, 45, 781, 825, 15, 174, 773, 374, 525, 257, 437, 82, 395, 241, 580],
+[ 522, 210, 868, 79, 613, 801, 455, 715, 266, 198, 240, 957, 860, 523, 69, 908, 664, 393, 647, 61, 211, 462, 584, 262, 353, 997, 467, 983, 192, 568, 647, 315, 214, 516, 147, 576, 992, 552, 997, 116, 670, 277, 817, 310, 286, 603, 614, 192, 894, 841, 411, 971, 403, 510, 673, 905, 146, 444, 850, 802, 239, 330, 732, 791, 407, 418, 98, 314, 662, 226, 89, 187, 811, 45, 698, 937, 110, 819, 664, 298, 373, 471, 932, 26, 787, 430, 811, 424, 135, 593, 55, 863, 588, 596, 773, 81, 15, 397, 998, 73],
+[ 902, 102, 761, 737, 520, 943, 576, 22, 694, 901, 702, 791, 333, 53, 726, 686, 175, 637, 374, 953, 737, 217, 641, 204, 397, 583, 658, 900, 432, 303, 6, 810, 417, 264, 752, 828, 221, 843, 971, 29, 915, 779, 686, 974, 204, 727, 799, 439, 497, 146, 29, 737, 883, 410, 195, 783, 705, 91, 856, 761, 639, 622, 575, 528, 533, 826, 764, 983, 673, 749, 884, 145, 419, 912, 716, 183, 361, 398, 217, 66, 254, 853, 914, 867, 736, 538, 74, 29, 457, 562, 39, 442, 416, 212, 594, 13, 137, 159, 798, 200],
+[ 806, 464, 233, 550, 895, 229, 19, 6, 462, 976, 896, 643, 318, 620, 109, 758, 4, 618, 498, 751, 114, 471, 965, 354, 309, 372, 488, 991, 369, 15, 464, 110, 641, 89, 921, 852, 86, 136, 387, 281, 294, 142, 704, 432, 188, 197, 842, 169, 504, 173, 299, 927, 764, 104, 472, 468, 497, 303, 886, 486, 68, 794, 41, 448, 206, 550, 669, 863, 35, 461, 505, 282, 551, 972, 58, 939, 56, 134, 701, 288, 286, 109, 427, 996, 605, 919, 754, 443, 690, 31, 30, 395, 793, 128, 332, 393, 901, 755, 63, 806],
+[ 360, 305, 726, 670, 640, 860, 408, 962, 803, 189, 524, 424, 327, 959, 401, 975, 456, 65, 73, 20, 948, 928, 206, 629, 280, 283, 633, 66, 288, 398, 431, 396, 583, 544, 201, 417, 793, 934, 239, 166, 890, 76, 288, 543, 614, 275, 500, 664, 723, 878, 419, 121, 653, 180, 393, 283, 9, 347, 416, 381, 782, 733, 337, 506, 567, 334, 802, 560, 323, 70, 861, 74, 331, 880, 180, 546, 875, 202, 378, 659, 246, 84, 160, 174, 777, 805, 680, 387, 360, 781, 77, 673, 646, 297, 631, 780, 805, 642, 986, 168],
+[ 438, 909, 393, 147, 68, 794, 310, 353, 785, 259, 443, 618, 838, 482, 31, 53, 183, 222, 874, 134, 363, 687, 807, 870, 534, 327, 195, 442, 715, 619, 458, 790, 459, 45, 163, 579, 834, 631, 743, 759, 210, 262, 182, 323, 633, 820, 162, 502, 546, 442, 771, 519, 186, 154, 663, 80, 295, 638, 810, 435, 392, 280, 64, 700, 34, 960, 43, 280, 648, 939, 372, 818, 63, 276, 622, 992, 210, 32, 340, 543, 112, 7, 747, 148, 751, 542, 269, 973, 559, 842, 986, 358, 621, 757, 821, 573, 313, 404, 364, 235],
+[ 995, 102, 157, 156, 393, 342, 601, 521, 188, 365, 398, 572, 435, 444, 482, 147, 697, 225, 78, 684, 788, 902, 715, 434, 410, 234, 189, 838, 84, 809, 249, 612, 973, 845, 708, 532, 310, 423, 115, 918, 224, 318, 96, 677, 663, 437, 964, 598, 256, 762, 185, 388, 881, 681, 274, 661, 87, 487, 957, 488, 718, 156, 305, 929, 694, 681, 873, 193, 948, 586, 449, 837, 666, 963, 612, 16, 11, 14, 80, 351, 882, 778, 348, 588, 268, 322, 857, 808, 66, 485, 932, 872, 131, 130, 542, 861, 637, 652, 925, 678],
+[ 399, 883, 908, 844, 371, 977, 645, 68, 142, 437, 102, 642, 713, 580, 220, 644, 573, 860, 644, 961, 855, 399, 631, 548, 516, 48, 419, 387, 445, 0, 139, 15, 949, 137, 66, 682, 583, 210, 82, 238, 887, 327, 754, 392, 458, 241, 591, 77, 283, 543, 497, 71, 297, 571, 897, 553, 788, 668, 687, 742, 724, 656, 563, 824, 672, 501, 297, 230, 867, 399, 174, 863, 736, 281, 859, 564, 64, 576, 780, 706, 83, 863, 416, 818, 882, 825, 554, 522, 846, 100, 619, 409, 858, 106, 604, 358, 84, 469, 907, 945],
+[ 479, 776, 514, 556, 70, 130, 285, 164, 743, 683, 949, 848, 944, 465, 532, 668, 423, 448, 377, 321, 126, 183, 670, 420, 623, 718, 663, 314, 346, 963, 261, 806, 532, 700, 279, 854, 560, 826, 670, 627, 55, 401, 600, 489, 988, 131, 854, 810, 432, 620, 401, 602, 644, 128, 16, 285, 851, 267, 757, 599, 640, 576, 367, 286, 841, 547, 953, 240, 422, 287, 866, 268, 508, 448, 218, 691, 225, 387, 896, 661, 792, 592, 512, 663, 879, 107, 939, 597, 442, 86, 622, 180, 920, 124, 779, 768, 842, 186, 510, 940],
+[ 92, 218, 621, 633, 308, 71, 148, 12, 914, 219, 487, 357, 172, 917, 410, 357, 790, 253, 588, 338, 476, 899, 347, 804, 221, 382, 438, 618, 739, 118, 785, 672, 565, 288, 439, 701, 870, 771, 178, 968, 279, 461, 37, 443, 536, 346, 454, 284, 105, 315, 260, 619, 794, 182, 339, 611, 722, 300, 650, 90, 344, 540, 959, 296, 565, 162, 535, 920, 155, 352, 979, 410, 419, 686, 133, 537, 528, 263, 257, 155, 457, 647, 288, 629, 540, 87, 715, 498, 727, 642, 965, 513, 242, 382, 188, 279, 711, 925, 797, 271],
+[ 345, 580, 999, 63, 651, 525, 771, 954, 946, 536, 360, 649, 170, 743, 628, 171, 103, 71, 425, 209, 292, 528, 319, 501, 420, 666, 430, 50, 49, 824, 402, 975, 198, 798, 948, 975, 523, 729, 300, 115, 950, 722, 999, 325, 727, 763, 290, 977, 927, 498, 305, 558, 800, 346, 732, 828, 559, 352, 986, 94, 774, 216, 572, 258, 632, 639, 789, 847, 686, 76, 911, 227, 714, 914, 764, 263, 203, 867, 154, 815, 121, 533, 113, 833, 137, 491, 473, 434, 971, 183, 797, 546, 366, 673, 109, 914, 117, 250, 922, 973],
+[ 983, 801, 354, 120, 648, 917, 306, 406, 995, 22, 512, 442, 767, 755, 194, 412, 329, 941, 382, 489, 305, 904, 573, 189, 197, 25, 109, 693, 54, 127, 418, 872, 68, 649, 876, 595, 8, 635, 31, 14, 151, 755, 382, 986, 584, 493, 202, 854, 836, 116, 840, 869, 910, 634, 911, 659, 396, 910, 788, 172, 289, 20, 398, 897, 765, 249, 779, 710, 553, 235, 955, 626, 201, 639, 788, 149, 856, 214, 126, 296, 74, 674, 303, 130, 416, 736, 410, 725, 175, 753, 632, 376, 270, 114, 294, 282, 59, 793, 495, 757],
+[ 787, 11, 493, 807, 254, 33, 848, 99, 676, 476, 862, 990, 627, 308, 709, 188, 459, 327, 95, 835, 782, 191, 864, 618, 310, 917, 401, 925, 184, 306, 830, 439, 7, 501, 58, 874, 268, 482, 27, 56, 183, 874, 167, 101, 467, 41, 508, 64, 133, 292, 491, 517, 49, 673, 525, 862, 224, 789, 42, 810, 906, 637, 305, 636, 91, 663, 759, 437, 632, 706, 590, 371, 87, 263, 926, 346, 594, 645, 399, 981, 571, 228, 24, 438, 947, 670, 348, 496, 435, 581, 300, 637, 757, 695, 317, 883, 706, 226, 210, 445],
+[ 958, 461, 56, 421, 682, 860, 811, 823, 348, 292, 232, 942, 221, 490, 150, 805, 386, 379, 576, 527, 295, 934, 316, 679, 298, 676, 215, 142, 223, 879, 1, 524, 375, 928, 180, 402, 912, 341, 83, 228, 785, 505, 485, 376, 538, 897, 44, 468, 378, 145, 355, 589, 498, 502, 100, 239, 608, 789, 738, 571, 762, 831, 107, 905, 856, 209, 744, 53, 334, 951, 313, 924, 943, 528, 738, 56, 283, 456, 696, 478, 722, 649, 360, 147, 121, 522, 699, 662, 227, 79, 147, 267, 572, 756, 705, 891, 171, 595, 544, 130],
+[ 220, 901, 493, 966, 413, 676, 45, 552, 315, 309, 745, 997, 181, 231, 608, 761, 782, 280, 377, 707, 520, 711, 888, 966, 787, 812, 924, 909, 486, 704, 409, 222, 176, 780, 409, 943, 251, 151, 910, 552, 192, 388, 700, 690, 326, 453, 552, 421, 803, 602, 465, 698, 462, 763, 340, 400, 951, 757, 890, 270, 861, 860, 983, 749, 317, 922, 467, 305, 873, 229, 186, 365, 770, 270, 197, 42, 742, 539, 809, 425, 622, 43, 256, 457, 45, 233, 798, 152, 975, 968, 912, 732, 206, 53, 235, 669, 897, 393, 489, 269],
+[ 504, 536, 873, 35, 906, 508, 877, 548, 363, 549, 963, 945, 817, 68, 106, 302, 741, 237, 15, 403, 679, 193, 475, 571, 459, 737, 280, 255, 5, 766, 63, 306, 521, 61, 33, 334, 581, 372, 861, 187, 591, 852, 149, 137, 138, 353, 961, 192, 931, 633, 299, 558, 944, 821, 176, 25, 983, 14, 728, 287, 898, 311, 520, 963, 505, 210, 611, 856, 129, 686, 870, 361, 432, 822, 946, 518, 554, 58, 775, 988, 514, 637, 603, 81, 708, 684, 759, 115, 262, 68, 472, 78, 133, 36, 256, 576, 930, 711, 823, 424],
+[ 353, 407, 869, 653, 839, 201, 930, 469, 413, 980, 216, 990, 332, 247, 825, 504, 351, 329, 7, 656, 766, 87, 335, 780, 420, 185, 440, 848, 573, 458, 662, 289, 390, 824, 831, 604, 618, 569, 272, 947, 37, 865, 402, 442, 484, 869, 621, 448, 73, 862, 154, 180, 437, 148, 817, 139, 79, 252, 603, 962, 530, 143, 519, 292, 661, 77, 851, 193, 706, 901, 710, 595, 905, 753, 771, 924, 278, 913, 161, 504, 619, 146, 42, 903, 133, 740, 805, 594, 93, 605, 76, 751, 785, 519, 416, 855, 593, 27, 948, 715],
+[ 436, 944, 136, 790, 478, 878, 349, 862, 428, 542, 404, 958, 576, 185, 364, 719, 776, 427, 509, 415, 647, 393, 801, 473, 699, 945, 966, 261, 731, 738, 883, 180, 267, 543, 369, 336, 2, 678, 768, 999, 482, 559, 965, 743, 230, 282, 173, 413, 873, 516, 442, 599, 456, 623, 355, 87, 492, 294, 694, 910, 910, 479, 250, 679, 975, 425, 946, 57, 461, 690, 82, 847, 707, 480, 17, 658, 143, 984, 585, 786, 368, 787, 107, 715, 411, 75, 475, 846, 633, 469, 23, 635, 645, 766, 818, 723, 197, 560, 6, 869],
+[ 529, 1, 434, 535, 110, 21, 521, 976, 468, 45, 988, 859, 426, 359, 861, 430, 256, 965, 908, 323, 559, 548, 386, 41, 484, 161, 816, 619, 12, 348, 659, 845, 183, 354, 849, 11, 69, 816, 677, 305, 395, 990, 641, 936, 976, 559, 810, 218, 309, 167, 747, 747, 288, 945, 702, 411, 609, 571, 789, 308, 33, 687, 52, 228, 274, 152, 704, 114, 422, 694, 922, 856, 754, 492, 425, 94, 944, 371, 96, 509, 640, 554, 387, 481, 76, 202, 437, 279, 513, 91, 529, 46, 346, 65, 136, 314, 733, 184, 826, 29],
+[ 96, 760, 913, 599, 849, 413, 885, 848, 867, 877, 518, 17, 17, 716, 312, 333, 56, 231, 611, 761, 35, 913, 993, 847, 516, 584, 671, 889, 85, 971, 701, 855, 266, 516, 683, 485, 803, 698, 183, 494, 209, 444, 381, 714, 840, 733, 258, 757, 813, 581, 182, 700, 390, 918, 281, 194, 150, 818, 350, 33, 764, 815, 203, 51, 683, 608, 828, 563, 48, 835, 580, 770, 697, 870, 331, 373, 738, 265, 137, 895, 860, 753, 314, 575, 43, 560, 374, 696, 341, 840, 398, 821, 658, 917, 738, 846, 847, 937, 203, 500],
+[ 652, 815, 235, 964, 317, 475, 299, 195, 815, 941, 199, 15, 277, 877, 683, 958, 713, 974, 379, 866, 789, 143, 556, 291, 202, 413, 868, 768, 572, 705, 525, 674, 906, 898, 814, 729, 730, 859, 515, 132, 978, 220, 460, 351, 293, 64, 579, 857, 626, 535, 751, 368, 629, 437, 209, 550, 123, 299, 593, 881, 150, 708, 389, 179, 223, 324, 196, 893, 362, 108, 160, 354, 522, 667, 876, 404, 410, 308, 156, 586, 969, 757, 311, 639, 342, 751, 702, 588, 311, 885, 707, 364, 488, 722, 315, 127, 70, 900, 927, 465],
+[ 344, 464, 498, 130, 440, 992, 394, 695, 899, 821, 18, 732, 215, 438, 3, 135, 369, 433, 763, 541, 398, 751, 959, 766, 541, 672, 668, 47, 297, 632, 144, 722, 807, 940, 207, 582, 401, 755, 864, 425, 483, 233, 889, 780, 249, 844, 10, 304, 302, 194, 188, 740, 180, 749, 997, 591, 370, 926, 873, 49, 314, 603, 284, 403, 416, 147, 667, 608, 987, 54, 152, 928, 28, 389, 338, 30, 213, 390, 65, 631, 317, 219, 709, 298, 803, 88, 655, 933, 102, 530, 994, 988, 650, 445, 345, 152, 528, 898, 178, 596],
+[ 400, 844, 91, 574, 583, 793, 852, 997, 477, 28, 359, 268, 361, 904, 447, 194, 353, 534, 475, 428, 866, 399, 438, 296, 141, 784, 284, 329, 752, 146, 202, 144, 893, 38, 89, 490, 422, 25, 631, 480, 242, 93, 814, 753, 518, 657, 985, 555, 912, 351, 822, 620, 165, 190, 439, 22, 978, 449, 381, 612, 200, 814, 128, 7, 984, 567, 249, 567, 242, 563, 962, 55, 698, 998, 985, 437, 820, 20, 330, 910, 361, 349, 23, 41, 209, 175, 14, 442, 507, 664, 444, 523, 242, 783, 8, 695, 74, 273, 699, 73],
+[ 342, 441, 468, 889, 653, 509, 303, 147, 686, 236, 894, 696, 648, 36, 578, 980, 122, 766, 602, 251, 812, 627, 447, 434, 818, 828, 808, 48, 814, 384, 581, 389, 782, 656, 725, 986, 455, 729, 568, 878, 226, 154, 967, 165, 405, 52, 253, 6, 405, 936, 169, 269, 539, 960, 536, 658, 20, 619, 993, 79, 415, 554, 354, 630, 277, 221, 134, 420, 180, 495, 603, 582, 807, 51, 115, 624, 586, 658, 822, 472, 895, 648, 246, 538, 987, 107, 703, 455, 95, 270, 93, 999, 373, 21, 383, 174, 504, 522, 494, 446],
+[ 499, 522, 97, 291, 10, 370, 278, 82, 316, 156, 359, 931, 506, 767, 237, 345, 23, 916, 274, 782, 885, 566, 525, 603, 659, 768, 696, 344, 663, 706, 394, 490, 936, 518, 127, 914, 650, 757, 830, 578, 139, 128, 915, 675, 704, 76, 862, 542, 435, 404, 155, 239, 471, 306, 428, 186, 421, 578, 997, 900, 905, 455, 574, 168, 853, 360, 853, 857, 421, 75, 405, 631, 719, 664, 183, 91, 565, 623, 961, 101, 302, 755, 960, 625, 884, 165, 399, 389, 835, 688, 296, 356, 923, 250, 638, 854, 38, 222, 872, 372],
+[ 694, 678, 352, 71, 197, 336, 610, 161, 796, 657, 16, 223, 914, 857, 34, 876, 146, 268, 948, 545, 858, 613, 479, 795, 413, 812, 779, 463, 577, 477, 994, 617, 577, 453, 987, 804, 576, 605, 968, 810, 662, 681, 866, 31, 575, 162, 707, 505, 417, 937, 645, 739, 956, 217, 635, 794, 48, 806, 163, 572, 907, 110, 648, 619, 3, 434, 272, 938, 492, 986, 91, 892, 102, 268, 725, 540, 403, 805, 940, 87, 250, 161, 238, 781, 583, 767, 556, 951, 678, 382, 414, 257, 313, 411, 752, 128, 309, 265, 627, 820],
+[ 345, 397, 269, 50, 594, 928, 501, 732, 14, 474, 144, 16, 55, 264, 421, 676, 71, 793, 795, 370, 128, 600, 89, 758, 646, 880, 762, 0, 679, 844, 401, 606, 168, 994, 21, 71, 540, 183, 245, 900, 295, 198, 794, 720, 642, 453, 44, 673, 760, 98, 797, 339, 50, 22, 658, 561, 924, 799, 936, 997, 641, 398, 32, 714, 154, 811, 801, 193, 626, 646, 642, 394, 270, 660, 266, 466, 732, 440, 915, 324, 745, 343, 685, 418, 202, 260, 996, 536, 156, 119, 202, 170, 741, 947, 883, 424, 856, 900, 524, 753],
+[ 147, 529, 528, 714, 427, 418, 575, 657, 63, 695, 149, 724, 605, 99, 574, 948, 886, 159, 465, 836, 483, 330, 804, 447, 133, 214, 680, 517, 162, 710, 15, 29, 729, 627, 845, 307, 572, 642, 708, 111, 519, 551, 415, 634, 379, 272, 400, 871, 729, 489, 377, 796, 514, 337, 816, 455, 850, 438, 753, 705, 126, 345, 793, 607, 875, 279, 770, 406, 421, 41, 101, 713, 701, 865, 526, 461, 130, 98, 634, 354, 238, 281, 964, 524, 924, 972, 392, 797, 594, 388, 822, 436, 801, 287, 311, 401, 821, 501, 938, 654],
+[ 376, 371, 406, 978, 954, 794, 384, 994, 770, 670, 281, 274, 955, 836, 178, 132, 651, 471, 728, 429, 881, 970, 28, 259, 852, 897, 106, 375, 805, 129, 395, 745, 706, 885, 615, 517, 194, 564, 244, 357, 576, 944, 188, 682, 528, 673, 118, 39, 480, 374, 255, 66, 676, 494, 874, 610, 128, 547, 505, 784, 293, 182, 186, 938, 568, 378, 817, 28, 128, 962, 953, 583, 728, 521, 681, 939, 284, 52, 77, 130, 788, 984, 877, 611, 608, 997, 304, 473, 413, 535, 580, 606, 12, 397, 79, 36, 954, 46, 663, 421],
+[ 951, 659, 998, 554, 567, 889, 275, 285, 514, 492, 274, 923, 400, 170, 438, 824, 745, 447, 868, 807, 13, 695, 533, 130, 816, 794, 72, 879, 547, 557, 129, 962, 942, 440, 643, 803, 722, 611, 127, 431, 864, 86, 553, 81, 78, 653, 236, 14, 970, 587, 205, 42, 548, 74, 389, 275, 62, 834, 933, 173, 578, 702, 512, 423, 962, 51, 926, 80, 228, 526, 331, 554, 681, 109, 67, 90, 148, 586, 511, 576, 339, 590, 962, 905, 364, 729, 3, 362, 46, 871, 159, 434, 96, 625, 180, 999, 373, 979, 118, 246],
+[ 476, 285, 124, 996, 479, 320, 826, 304, 230, 580, 96, 50, 194, 263, 457, 4, 100, 182, 22, 235, 112, 129, 24, 991, 105, 933, 298, 89, 813, 414, 264, 99, 323, 374, 62, 955, 321, 256, 866, 489, 606, 805, 759, 521, 286, 346, 613, 44, 503, 324, 669, 720, 323, 926, 185, 203, 130, 834, 448, 698, 240, 912, 343, 285, 225, 597, 451, 831, 175, 976, 504, 931, 135, 168, 747, 927, 471, 636, 265, 765, 266, 728, 918, 798, 775, 954, 245, 28, 159, 633, 835, 580, 490, 80, 749, 838, 788, 331, 964, 998],
+[ 106, 377, 538, 81, 704, 880, 743, 661, 900, 300, 753, 827, 918, 251, 993, 803, 50, 815, 662, 520, 756, 92, 304, 673, 554, 40, 868, 732, 481, 742, 689, 597, 481, 638, 566, 984, 418, 403, 431, 599, 932, 899, 917, 509, 74, 418, 216, 939, 312, 438, 572, 440, 775, 796, 747, 142, 407, 226, 951, 862, 4, 353, 469, 530, 237, 170, 640, 514, 432, 927, 552, 15, 428, 671, 806, 541, 594, 539, 53, 729, 701, 778, 624, 386, 749, 227, 87, 642, 68, 524, 435, 91, 688, 494, 378, 164, 464, 70, 78, 73],
+[ 445, 32, 479, 771, 252, 849, 126, 433, 634, 465, 860, 355, 999, 663, 987, 451, 290, 895, 238, 764, 157, 865, 518, 701, 372, 936, 570, 176, 900, 607, 686, 710, 524, 346, 359, 433, 763, 832, 103, 973, 795, 922, 467, 140, 603, 7, 876, 686, 681, 921, 641, 680, 916, 396, 465, 992, 759, 424, 204, 587, 728, 167, 458, 408, 995, 3, 378, 589, 809, 866, 313, 331, 396, 301, 968, 265, 930, 51, 533, 340, 539, 300, 579, 902, 444, 832, 465, 250, 948, 806, 805, 185, 961, 582, 712, 721, 541, 975, 714, 940],
+[ 382, 165, 944, 167, 9, 292, 885, 983, 865, 16, 543, 385, 879, 563, 154, 161, 564, 365, 825, 25, 186, 147, 627, 563, 486, 695, 826, 402, 244, 108, 299, 487, 323, 496, 696, 260, 782, 951, 735, 351, 429, 933, 660, 647, 528, 32, 363, 662, 762, 767, 310, 891, 643, 379, 282, 149, 917, 326, 680, 541, 479, 490, 782, 630, 607, 395, 687, 285, 691, 507, 320, 154, 855, 220, 219, 771, 811, 667, 482, 508, 57, 624, 860, 286, 807, 254, 37, 110, 344, 769, 668, 65, 512, 815, 59, 909, 151, 266, 112, 765],
+[ 126, 267, 336, 350, 819, 322, 862, 547, 730, 497, 678, 237, 867, 199, 128, 900, 665, 555, 227, 746, 130, 796, 308, 669, 802, 420, 736, 496, 949, 751, 785, 37, 918, 389, 233, 465, 685, 958, 879, 821, 916, 681, 646, 333, 412, 381, 230, 851, 339, 564, 16, 396, 201, 169, 280, 661, 622, 589, 929, 112, 958, 282, 19, 588, 309, 214, 32, 110, 336, 145, 46, 953, 888, 307, 960, 830, 508, 258, 987, 17, 320, 396, 384, 965, 287, 309, 641, 452, 456, 162, 8, 493, 84, 79, 407, 239, 555, 105, 689, 8],
+[ 339, 32, 682, 209, 73, 135, 733, 71, 304, 819, 376, 913, 301, 696, 181, 47, 168, 454, 985, 207, 891, 469, 287, 639, 455, 994, 496, 508, 188, 257, 738, 442, 329, 654, 70, 408, 982, 855, 977, 354, 577, 348, 628, 933, 859, 74, 505, 964, 436, 884, 272, 370, 805, 696, 72, 950, 502, 254, 872, 199, 203, 361, 200, 869, 879, 652, 460, 928, 549, 286, 857, 768, 968, 48, 515, 632, 830, 288, 71, 874, 477, 531, 720, 738, 254, 802, 205, 824, 929, 522, 430, 12, 298, 568, 652, 457, 458, 407, 428, 933],
+[ 579, 840, 571, 445, 666, 467, 796, 198, 783, 39, 56, 920, 397, 495, 751, 855, 404, 511, 968, 788, 965, 137, 507, 630, 949, 472, 841, 195, 772, 101, 652, 242, 376, 326, 495, 801, 220, 473, 109, 746, 915, 362, 460, 269, 635, 427, 184, 821, 664, 9, 190, 125, 435, 327, 261, 835, 80, 732, 784, 63, 297, 689, 497, 728, 953, 346, 867, 443, 981, 808, 979, 868, 137, 570, 964, 456, 331, 525, 702, 630, 811, 60, 766, 489, 507, 1, 762, 75, 306, 957, 143, 576, 424, 928, 442, 567, 170, 910, 325, 3],
+[ 638, 927, 498, 668, 641, 288, 781, 73, 86, 947, 190, 175, 945, 946, 180, 450, 197, 590, 503, 292, 588, 399, 192, 620, 275, 294, 690, 714, 590, 827, 986, 871, 301, 834, 32, 240, 344, 853, 615, 522, 364, 617, 319, 578, 920, 433, 32, 182, 727, 234, 813, 188, 965, 630, 625, 136, 464, 74, 138, 750, 762, 164, 822, 233, 895, 68, 665, 592, 526, 450, 287, 6, 117, 465, 211, 166, 379, 572, 117, 235, 62, 33, 416, 816, 755, 175, 665, 110, 334, 59, 341, 947, 45, 129, 175, 871, 334, 140, 739, 342],
+[ 175, 997, 699, 604, 787, 270, 696, 608, 544, 923, 843, 973, 379, 378, 308, 919, 990, 144, 99, 148, 480, 925, 561, 432, 732, 484, 78, 232, 999, 492, 41, 943, 705, 642, 245, 472, 81, 645, 706, 398, 850, 233, 939, 809, 63, 799, 600, 793, 339, 329, 134, 701, 907, 157, 403, 724, 43, 233, 588, 150, 399, 868, 437, 960, 354, 875, 489, 325, 734, 617, 282, 611, 218, 187, 654, 684, 99, 756, 865, 668, 817, 721, 609, 366, 711, 948, 179, 287, 410, 488, 268, 317, 208, 755, 74, 515, 140, 561, 879, 557],
+[ 913, 32, 439, 17, 851, 939, 738, 930, 67, 865, 920, 377, 0, 151, 530, 815, 141, 795, 711, 478, 631, 891, 816, 276, 780, 375, 491, 689, 1, 949, 693, 372, 379, 147, 930, 377, 154, 797, 317, 760, 699, 909, 702, 257, 660, 838, 614, 853, 369, 92, 977, 431, 958, 928, 94, 126, 228, 35, 523, 13, 1, 757, 376, 317, 935, 63, 354, 972, 437, 524, 743, 327, 48, 749, 796, 457, 966, 291, 73, 793, 302, 163, 955, 685, 911, 978, 221, 261, 282, 918, 136, 540, 556, 80, 399, 568, 444, 400, 847, 759],
+[ 832, 685, 786, 622, 862, 855, 796, 721, 64, 497, 849, 247, 396, 453, 746, 146, 38, 928, 252, 422, 848, 923, 300, 366, 64, 546, 4, 53, 224, 420, 382, 414, 599, 609, 524, 323, 554, 805, 120, 536, 326, 994, 778, 42, 31, 543, 371, 975, 627, 879, 163, 709, 386, 317, 501, 649, 267, 4, 899, 306, 433, 246, 641, 201, 17, 601, 910, 379, 751, 800, 58, 486, 71, 126, 270, 773, 985, 624, 945, 997, 183, 546, 368, 638, 611, 9, 489, 889, 754, 878, 626, 623, 713, 898, 232, 962, 156, 893, 618, 960],
+[ 767, 32, 809, 532, 706, 675, 868, 645, 184, 865, 647, 415, 214, 585, 864, 236, 840, 90, 153, 704, 228, 491, 294, 562, 176, 403, 695, 42, 598, 359, 156, 368, 723, 268, 788, 579, 362, 910, 548, 595, 475, 768, 761, 100, 247, 917, 929, 34, 836, 961, 233, 245, 830, 901, 25, 283, 851, 454, 899, 713, 660, 141, 253, 937, 895, 268, 890, 440, 0, 918, 403, 39, 193, 492, 15, 578, 70, 196, 110, 684, 686, 111, 718, 753, 421, 155, 626, 110, 98, 14, 85, 264, 978, 721, 366, 999, 763, 226, 147, 123],
+[ 530, 640, 608, 55, 368, 63, 850, 645, 261, 546, 523, 812, 572, 991, 73, 244, 940, 92, 40, 994, 841, 911, 317, 488, 156, 651, 808, 908, 856, 380, 530, 869, 406, 947, 375, 879, 689, 601, 412, 664, 295, 194, 958, 291, 975, 229, 763, 756, 992, 395, 555, 227, 564, 261, 797, 512, 665, 222, 579, 487, 523, 45, 469, 530, 668, 726, 93, 634, 133, 587, 356, 241, 921, 275, 963, 280, 596, 755, 881, 565, 580, 771, 964, 139, 568, 287, 571, 495, 535, 971, 684, 103, 340, 492, 175, 548, 433, 111, 380, 782],
+[ 278, 234, 678, 37, 87, 23, 534, 544, 295, 324, 995, 24, 224, 663, 991, 985, 419, 327, 808, 864, 63, 986, 115, 363, 53, 12, 506, 780, 143, 579, 617, 301, 668, 919, 674, 42, 841, 165, 592, 981, 249, 896, 120, 914, 62, 559, 383, 8, 942, 320, 995, 475, 865, 102, 61, 928, 188, 131, 201, 509, 14, 276, 818, 262, 665, 565, 648, 520, 155, 26, 973, 611, 84, 820, 826, 969, 597, 632, 894, 508, 140, 174, 969, 128, 630, 322, 376, 826, 934, 321, 820, 9, 292, 597, 877, 528, 383, 629, 167, 981],
+[ 705, 778, 774, 752, 636, 716, 3, 310, 428, 231, 151, 115, 419, 840, 297, 957, 277, 243, 403, 390, 921, 180, 513, 79, 836, 349, 654, 150, 423, 127, 544, 211, 66, 943, 302, 495, 12, 110, 409, 464, 815, 690, 13, 501, 744, 916, 774, 657, 44, 466, 556, 349, 675, 759, 383, 11, 517, 450, 803, 261, 897, 690, 39, 494, 47, 87, 569, 979, 96, 679, 113, 35, 232, 975, 630, 58, 100, 515, 600, 197, 176, 894, 78, 634, 590, 409, 694, 944, 258, 294, 430, 885, 784, 980, 605, 365, 63, 404, 617, 80],
+[ 108, 779, 607, 49, 384, 492, 585, 647, 223, 760, 632, 221, 353, 992, 970, 440, 920, 956, 623, 764, 198, 125, 883, 55, 475, 749, 202, 957, 940, 203, 796, 65, 888, 486, 659, 890, 924, 68, 174, 649, 581, 911, 140, 863, 186, 826, 664, 764, 608, 830, 539, 531, 922, 780, 297, 208, 813, 15, 764, 28, 88, 180, 506, 499, 730, 535, 261, 923, 509, 814, 931, 433, 995, 275, 740, 655, 148, 692, 80, 339, 389, 551, 994, 179, 335, 54, 357, 737, 878, 539, 872, 907, 837, 423, 355, 852, 592, 973, 888, 400],
+[ 963, 523, 230, 25, 32, 421, 761, 289, 497, 136, 430, 47, 472, 370, 906, 984, 438, 19, 782, 354, 584, 941, 251, 400, 964, 560, 875, 875, 466, 184, 787, 378, 21, 599, 566, 155, 521, 844, 82, 405, 490, 569, 862, 871, 520, 74, 330, 548, 851, 509, 593, 289, 976, 111, 224, 757, 874, 280, 464, 869, 628, 878, 46, 795, 915, 787, 396, 772, 958, 132, 786, 943, 134, 726, 645, 22, 125, 239, 282, 362, 7, 145, 863, 182, 118, 459, 842, 638, 709, 743, 48, 391, 672, 753, 873, 638, 335, 942, 266, 981],
+[ 845, 783, 25, 670, 930, 50, 50, 245, 395, 681, 580, 441, 915, 592, 47, 710, 353, 899, 26, 367, 563, 955, 524, 665, 520, 517, 389, 881, 369, 25, 251, 879, 22, 373, 368, 269, 0, 48, 724, 399, 819, 781, 933, 369, 822, 552, 873, 215, 329, 152, 243, 943, 308, 643, 263, 960, 348, 379, 178, 194, 264, 583, 365, 63, 220, 521, 842, 415, 946, 703, 721, 199, 664, 937, 110, 703, 903, 271, 705, 643, 176, 104, 821, 496, 102, 142, 747, 538, 392, 515, 830, 191, 52, 318, 6, 471, 911, 945, 288, 868],
+[ 744, 863, 56, 970, 520, 556, 984, 397, 781, 886, 430, 73, 763, 356, 57, 979, 954, 320, 975, 983, 757, 879, 691, 12, 615, 308, 725, 905, 924, 884, 46, 863, 289, 201, 950, 31, 95, 908, 970, 431, 21, 772, 373, 292, 558, 887, 66, 886, 194, 919, 644, 224, 166, 803, 274, 115, 153, 670, 353, 343, 487, 989, 597, 182, 349, 666, 846, 610, 357, 457, 966, 913, 782, 90, 593, 663, 635, 331, 138, 603, 161, 128, 718, 855, 648, 49, 526, 946, 775, 161, 59, 109, 691, 346, 887, 70, 39, 733, 949, 483],
+[ 24, 241, 989, 625, 686, 511, 818, 726, 132, 278, 852, 916, 199, 722, 75, 366, 86, 273, 518, 698, 842, 280, 959, 532, 985, 395, 399, 324, 355, 480, 273, 782, 27, 313, 541, 671, 778, 876, 411, 719, 915, 712, 873, 662, 686, 913, 428, 117, 787, 210, 89, 508, 432, 807, 253, 858, 220, 139, 712, 927, 503, 724, 974, 895, 974, 944, 551, 386, 892, 490, 646, 530, 772, 591, 530, 937, 66, 773, 855, 520, 403, 534, 730, 310, 113, 515, 338, 771, 524, 343, 6, 555, 421, 194, 892, 744, 523, 885, 713, 421],
+[ 623, 848, 337, 335, 658, 580, 447, 454, 27, 518, 685, 610, 931, 81, 878, 349, 258, 762, 66, 574, 949, 825, 391, 981, 75, 315, 491, 551, 239, 44, 253, 492, 230, 509, 177, 930, 749, 796, 502, 420, 999, 741, 555, 686, 595, 41, 139, 453, 26, 538, 480, 373, 473, 553, 344, 180, 847, 437, 700, 171, 542, 62, 379, 883, 544, 254, 350, 900, 883, 273, 134, 385, 420, 505, 265, 347, 211, 916, 814, 888, 401, 690, 471, 883, 817, 94, 551, 989, 393, 885, 240, 470, 245, 468, 298, 195, 389, 227, 303, 195],
+[ 634, 752, 95, 625, 808, 285, 689, 17, 245, 323, 650, 302, 902, 658, 892, 415, 150, 595, 286, 904, 140, 435, 880, 76, 574, 844, 286, 148, 618, 936, 883, 819, 866, 175, 823, 655, 823, 921, 260, 435, 664, 656, 644, 523, 30, 129, 416, 829, 67, 803, 498, 209, 865, 857, 917, 314, 355, 689, 332, 305, 822, 882, 618, 10, 908, 544, 234, 843, 916, 103, 511, 759, 47, 576, 224, 531, 502, 913, 558, 376, 889, 222, 525, 321, 991, 829, 714, 502, 526, 388, 866, 847, 181, 761, 105, 279, 901, 218, 314, 214],
+[ 196, 966, 786, 349, 289, 745, 925, 375, 287, 178, 759, 471, 857, 901, 385, 162, 701, 468, 441, 890, 314, 789, 307, 588, 243, 925, 397, 854, 38, 288, 377, 55, 193, 56, 36, 251, 543, 342, 776, 777, 278, 542, 136, 329, 547, 558, 456, 806, 667, 260, 279, 456, 357, 893, 272, 890, 318, 842, 64, 738, 233, 27, 697, 898, 790, 570, 330, 588, 328, 938, 179, 89, 684, 892, 377, 80, 274, 650, 564, 711, 823, 302, 752, 620, 220, 553, 231, 471, 360, 704, 815, 948, 147, 790, 817, 286, 891, 357, 76, 836],
+[ 646, 913, 594, 812, 515, 997, 178, 539, 982, 320, 941, 704, 495, 644, 413, 700, 48, 527, 80, 12, 833, 523, 277, 930, 873, 637, 833, 605, 829, 399, 780, 879, 694, 607, 323, 926, 456, 846, 946, 510, 409, 754, 958, 87, 898, 553, 117, 605, 157, 845, 951, 647, 774, 175, 517, 528, 669, 356, 95, 990, 64, 710, 176, 117, 501, 78, 468, 460, 752, 235, 711, 216, 772, 529, 708, 732, 711, 159, 938, 399, 434, 646, 315, 946, 510, 493, 461, 430, 929, 999, 578, 666, 736, 70, 121, 230, 745, 421, 559, 554],
+[ 485, 982, 731, 576, 582, 599, 423, 558, 600, 304, 620, 903, 558, 521, 964, 835, 367, 718, 120, 229, 283, 207, 384, 512, 111, 633, 395, 290, 922, 159, 125, 507, 114, 19, 142, 377, 677, 552, 910, 354, 705, 603, 534, 125, 24, 504, 423, 123, 104, 241, 835, 201, 679, 604, 105, 903, 973, 653, 740, 798, 169, 320, 24, 485, 988, 973, 638, 573, 465, 315, 279, 378, 454, 972, 388, 493, 877, 577, 510, 112, 805, 854, 543, 29, 245, 937, 834, 663, 720, 830, 456, 493, 280, 459, 739, 653, 550, 963, 515, 542],
+[ 843, 689, 938, 870, 693, 557, 422, 505, 786, 954, 32, 604, 514, 626, 314, 378, 93, 771, 162, 751, 177, 374, 70, 733, 857, 894, 685, 850, 149, 302, 942, 276, 22, 924, 494, 687, 143, 397, 863, 980, 400, 531, 427, 890, 245, 553, 934, 376, 267, 67, 732, 986, 834, 321, 138, 698, 867, 943, 842, 497, 335, 128, 301, 887, 951, 734, 860, 388, 792, 49, 510, 882, 772, 92, 127, 240, 218, 34, 625, 233, 730, 837, 30, 250, 334, 302, 848, 292, 341, 215, 441, 90, 563, 97, 952, 359, 923, 77, 246, 746],
+[ 738, 804, 858, 41, 236, 917, 28, 741, 332, 918, 643, 516, 248, 45, 336, 791, 410, 718, 220, 788, 73, 163, 24, 804, 304, 321, 909, 296, 399, 204, 541, 510, 557, 370, 549, 826, 365, 922, 719, 932, 354, 947, 786, 610, 117, 259, 245, 28, 745, 119, 856, 640, 759, 977, 31, 780, 33, 420, 559, 329, 791, 942, 502, 976, 770, 0, 899, 29, 37, 539, 474, 758, 314, 667, 653, 4, 426, 854, 766, 499, 336, 686, 221, 348, 126, 827, 508, 674, 595, 420, 329, 149, 24, 701, 226, 483, 168, 581, 515, 473],
+[ 39, 495, 591, 930, 891, 242, 577, 124, 734, 976, 452, 304, 578, 850, 137, 418, 23, 701, 232, 116, 566, 149, 217, 556, 481, 818, 239, 740, 770, 434, 205, 318, 350, 793, 143, 203, 182, 573, 744, 278, 490, 611, 195, 862, 440, 59, 485, 675, 442, 159, 356, 549, 306, 348, 761, 710, 815, 319, 711, 438, 89, 248, 732, 127, 261, 777, 91, 510, 138, 790, 803, 410, 434, 880, 232, 939, 397, 543, 552, 451, 871, 615, 511, 381, 577, 662, 779, 591, 822, 656, 168, 684, 635, 428, 570, 789, 978, 134, 555, 999],
+[ 209, 692, 95, 73, 279, 954, 27, 966, 530, 504, 459, 896, 151, 761, 196, 899, 339, 477, 811, 348, 506, 34, 45, 773, 511, 665, 514, 468, 646, 237, 951, 984, 89, 101, 134, 231, 988, 399, 624, 423, 96, 713, 591, 504, 206, 110, 949, 199, 871, 812, 91, 704, 405, 254, 697, 696, 726, 566, 845, 367, 365, 519, 130, 518, 485, 375, 744, 556, 112, 482, 689, 851, 452, 507, 577, 80, 794, 331, 458, 981, 331, 358, 825, 351, 322, 57, 166, 189, 658, 948, 504, 843, 599, 425, 79, 7, 838, 705, 108, 302],
+[ 411, 81, 71, 3, 826, 621, 360, 513, 933, 922, 983, 980, 552, 759, 494, 143, 792, 798, 0, 144, 141, 216, 502, 115, 702, 815, 686, 247, 155, 376, 116, 774, 179, 567, 15, 528, 497, 923, 486, 291, 902, 188, 109, 112, 567, 608, 954, 506, 958, 975, 734, 903, 494, 612, 922, 2, 571, 759, 544, 519, 963, 970, 992, 401, 327, 506, 800, 665, 472, 232, 791, 499, 223, 488, 204, 547, 879, 939, 232, 189, 770, 858, 158, 224, 902, 885, 813, 611, 169, 117, 155, 396, 756, 607, 719, 119, 543, 84, 687, 109],
+[ 109, 823, 891, 623, 598, 710, 764, 130, 206, 801, 635, 893, 574, 753, 293, 299, 888, 56, 264, 505, 842, 765, 885, 153, 139, 614, 266, 141, 903, 254, 889, 838, 821, 232, 233, 666, 757, 357, 753, 83, 576, 67, 191, 597, 664, 917, 704, 642, 394, 529, 604, 372, 612, 906, 371, 353, 486, 180, 906, 926, 203, 676, 222, 715, 674, 433, 435, 307, 103, 544, 59, 845, 859, 74, 681, 68, 944, 810, 616, 955, 943, 381, 653, 462, 897, 471, 345, 497, 149, 101, 956, 78, 664, 111, 145, 663, 425, 579, 837, 653],
+[ 833, 626, 238, 515, 430, 387, 764, 687, 225, 828, 195, 167, 409, 612, 583, 839, 707, 126, 462, 77, 956, 633, 473, 794, 132, 82, 134, 157, 799, 454, 808, 436, 658, 514, 890, 901, 209, 778, 986, 362, 976, 726, 472, 952, 881, 55, 374, 697, 222, 350, 82, 43, 346, 336, 318, 221, 909, 793, 107, 742, 607, 481, 621, 106, 358, 772, 593, 952, 688, 955, 884, 149, 246, 35, 522, 656, 877, 882, 362, 505, 80, 936, 367, 389, 380, 452, 296, 520, 374, 651, 264, 516, 818, 37, 791, 847, 883, 228, 857, 785],
+[ 92, 714, 922, 792, 693, 891, 323, 115, 163, 493, 451, 42, 791, 717, 965, 702, 393, 949, 391, 620, 411, 446, 551, 361, 896, 635, 683, 323, 898, 497, 884, 633, 422, 133, 228, 772, 353, 735, 599, 975, 305, 985, 764, 710, 370, 358, 190, 24, 84, 875, 760, 278, 442, 391, 990, 267, 729, 163, 970, 120, 597, 426, 919, 480, 983, 862, 160, 325, 680, 314, 357, 89, 533, 790, 669, 307, 665, 491, 425, 852, 704, 865, 916, 26, 869, 153, 639, 522, 19, 408, 527, 96, 721, 995, 707, 601, 215, 352, 623, 224]
+]
+
+matrix2 = [ 
+[ 441, 279, 954, 649, 952, 840, 914, 480, 981, 841, 875, 520, 253, 404, 301, 768, 97, 276, 25, 760, 968, 548, 7, 730, 141, 549, 980, 249, 810, 203, 543, 770, 158, 200, 288, 866, 188, 789, 827, 105, 987, 989, 97, 898, 40, 460, 88, 342, 404, 551, 594, 867, 543, 856, 373, 250, 781, 267, 260, 510, 555, 319, 576, 447, 288, 451, 986, 17, 406, 423, 795, 21, 19, 563, 162, 527, 26, 355, 901, 337, 109, 79, 930, 988, 778, 330, 682, 728, 95, 982, 810, 10, 44, 660, 323, 233, 869, 858, 661, 923],
+[ 483, 792, 816, 997, 368, 441, 223, 514, 282, 453, 322, 195, 828, 326, 600, 188, 191, 697, 212, 626, 580, 349, 874, 817, 670, 659, 721, 958, 532, 820, 663, 636, 122, 542, 66, 636, 432, 207, 138, 543, 461, 625, 166, 234, 603, 32, 468, 183, 977, 483, 108, 233, 53, 248, 544, 955, 243, 20, 21, 115, 259, 892, 60, 888, 744, 262, 770, 958, 814, 173, 986, 978, 90, 729, 53, 913, 290, 19, 840, 570, 91, 707, 256, 419, 663, 513, 894, 441, 97, 283, 783, 880, 559, 671, 53, 148, 583, 810, 31, 157],
+[ 55, 886, 77, 224, 564, 107, 116, 313, 912, 282, 367, 641, 354, 150, 118, 453, 181, 176, 567, 848, 651, 697, 442, 601, 917, 467, 900, 265, 832, 572, 436, 505, 555, 688, 414, 673, 830, 688, 606, 940, 215, 651, 414, 957, 337, 630, 731, 996, 787, 995, 371, 973, 401, 296, 389, 722, 315, 675, 410, 831, 676, 454, 535, 349, 810, 151, 9, 57, 290, 681, 340, 924, 493, 147, 160, 427, 998, 156, 813, 269, 368, 558, 182, 949, 485, 823, 269, 551, 504, 174, 902, 652, 874, 343, 780, 814, 202, 504, 382, 313],
+[ 437, 847, 409, 389, 903, 707, 570, 580, 7, 374, 819, 73, 312, 886, 322, 878, 997, 381, 817, 311, 121, 785, 78, 16, 550, 90, 254, 879, 37, 634, 650, 8, 446, 317, 715, 918, 883, 195, 139, 928, 239, 900, 358, 670, 37, 91, 291, 207, 304, 36, 624, 300, 357, 285, 569, 143, 908, 924, 298, 739, 352, 141, 592, 45, 309, 633, 99, 785, 17, 712, 324, 686, 324, 484, 181, 675, 431, 796, 72, 928, 854, 13, 160, 937, 684, 34, 409, 652, 49, 103, 10, 639, 940, 590, 20, 467, 405, 172, 146, 703],
+[ 441, 951, 730, 441, 425, 934, 284, 683, 888, 603, 134, 826, 676, 915, 414, 703, 83, 127, 556, 621, 248, 965, 789, 740, 633, 575, 617, 522, 729, 855, 187, 722, 714, 841, 25, 443, 136, 53, 120, 898, 852, 589, 719, 93, 58, 826, 536, 565, 816, 483, 884, 598, 837, 102, 556, 878, 878, 194, 748, 952, 487, 516, 210, 195, 828, 25, 171, 125, 616, 143, 472, 574, 326, 549, 633, 20, 806, 766, 728, 170, 24, 412, 408, 49, 847, 75, 554, 1, 507, 740, 908, 753, 388, 935, 235, 280, 863, 631, 127, 983],
+[ 694, 755, 12, 698, 999, 873, 725, 673, 590, 272, 818, 772, 159, 716, 116, 746, 417, 202, 655, 721, 383, 67, 186, 507, 160, 107, 257, 44, 243, 145, 836, 109, 883, 713, 190, 425, 59, 641, 465, 8, 986, 877, 521, 254, 493, 241, 587, 332, 436, 150, 24, 671, 604, 339, 423, 242, 213, 785, 225, 300, 288, 567, 641, 490, 580, 472, 532, 421, 264, 538, 539, 550, 996, 173, 717, 282, 864, 18, 458, 569, 365, 483, 146, 450, 613, 123, 123, 824, 721, 595, 30, 6, 628, 717, 849, 523, 652, 486, 28, 155],
+[ 195, 174, 617, 548, 536, 29, 513, 869, 469, 223, 220, 588, 182, 491, 849, 690, 16, 453, 154, 452, 557, 528, 750, 3, 654, 759, 746, 314, 686, 724, 242, 395, 808, 693, 652, 772, 335, 507, 33, 859, 104, 920, 508, 970, 620, 193, 398, 809, 664, 372, 924, 382, 172, 379, 455, 980, 264, 824, 913, 559, 919, 369, 712, 48, 251, 278, 197, 918, 777, 826, 521, 968, 50, 840, 935, 680, 393, 499, 217, 912, 773, 210, 181, 558, 405, 837, 856, 861, 920, 226, 561, 332, 54, 658, 169, 958, 827, 739, 138, 36],
+[ 774, 538, 230, 497, 912, 402, 83, 912, 435, 698, 117, 591, 384, 131, 977, 40, 950, 30, 173, 398, 310, 374, 858, 789, 717, 844, 545, 696, 732, 574, 948, 697, 196, 273, 987, 303, 846, 631, 149, 887, 475, 514, 561, 708, 69, 96, 721, 87, 185, 165, 958, 983, 603, 85, 582, 734, 757, 566, 495, 612, 340, 161, 8, 991, 579, 309, 184, 922, 655, 5, 609, 328, 894, 668, 971, 177, 800, 397, 180, 346, 144, 116, 401, 937, 84, 72, 627, 563, 865, 694, 381, 149, 82, 894, 907, 583, 441, 81, 84, 452],
+[ 136, 956, 39, 194, 120, 362, 799, 99, 819, 108, 290, 182, 688, 294, 959, 204, 889, 444, 203, 357, 353, 149, 529, 222, 876, 75, 536, 271, 596, 623, 224, 438, 824, 565, 257, 117, 770, 775, 557, 381, 353, 417, 713, 905, 729, 581, 370, 378, 200, 382, 199, 990, 108, 576, 614, 87, 179, 590, 317, 261, 834, 44, 766, 936, 960, 751, 472, 514, 685, 693, 31, 243, 311, 263, 998, 917, 583, 193, 757, 224, 276, 662, 92, 79, 23, 120, 613, 823, 334, 974, 96, 674, 395, 174, 167, 403, 780, 374, 387, 102],
+[ 778, 196, 562, 870, 74, 279, 202, 149, 786, 738, 917, 719, 716, 493, 327, 588, 436, 356, 381, 164, 946, 161, 21, 783, 108, 377, 389, 2, 745, 597, 305, 456, 347, 454, 900, 943, 846, 815, 143, 540, 192, 997, 430, 339, 550, 146, 485, 750, 342, 4, 100, 776, 14, 68, 635, 318, 871, 6, 293, 932, 245, 227, 80, 471, 56, 181, 718, 850, 567, 796, 183, 903, 166, 984, 382, 561, 333, 670, 339, 159, 587, 126, 675, 373, 139, 453, 508, 735, 463, 354, 426, 824, 276, 479, 717, 202, 598, 810, 820, 203],
+[ 31, 876, 240, 137, 950, 855, 487, 753, 554, 31, 28, 20, 924, 404, 119, 710, 161, 271, 677, 459, 866, 686, 503, 697, 254, 355, 702, 960, 448, 635, 293, 606, 618, 311, 417, 184, 706, 249, 141, 895, 421, 662, 550, 16, 587, 981, 166, 834, 39, 174, 403, 163, 764, 179, 5, 935, 887, 670, 598, 402, 637, 309, 478, 934, 746, 194, 713, 207, 826, 602, 484, 981, 561, 978, 300, 284, 266, 939, 952, 514, 553, 387, 975, 380, 909, 20, 382, 458, 430, 608, 911, 934, 767, 475, 804, 14, 113, 241, 200, 413],
+[ 162, 469, 576, 680, 795, 354, 91, 136, 450, 764, 173, 925, 989, 494, 158, 7, 499, 483, 936, 439, 253, 465, 339, 431, 504, 966, 381, 581, 983, 794, 788, 758, 361, 405, 623, 808, 90, 6, 213, 38, 509, 418, 50, 207, 817, 735, 656, 682, 942, 833, 285, 673, 397, 163, 764, 928, 235, 668, 752, 902, 574, 918, 676, 821, 623, 340, 513, 175, 187, 50, 73, 899, 412, 555, 410, 747, 686, 281, 733, 484, 690, 791, 391, 187, 875, 480, 408, 835, 648, 526, 673, 916, 551, 466, 601, 325, 304, 248, 237, 358],
+[ 221, 754, 971, 735, 372, 83, 236, 950, 708, 563, 411, 583, 930, 361, 603, 709, 731, 521, 310, 980, 990, 943, 473, 986, 200, 792, 62, 937, 34, 503, 514, 842, 305, 933, 115, 731, 660, 132, 570, 48, 337, 610, 641, 6, 222, 746, 616, 410, 145, 655, 930, 866, 833, 158, 287, 893, 965, 397, 602, 773, 833, 109, 626, 587, 273, 870, 277, 589, 850, 287, 809, 363, 955, 319, 766, 897, 45, 121, 441, 621, 970, 902, 892, 965, 639, 788, 634, 692, 727, 573, 298, 898, 299, 973, 937, 282, 48, 645, 153, 572],
+[ 978, 396, 738, 290, 850, 165, 953, 155, 523, 907, 438, 118, 676, 144, 402, 149, 402, 588, 773, 419, 745, 317, 647, 294, 856, 781, 656, 630, 220, 249, 258, 219, 638, 794, 862, 902, 750, 469, 282, 533, 528, 214, 899, 328, 129, 875, 92, 978, 968, 569, 151, 602, 339, 165, 374, 154, 80, 537, 526, 281, 735, 462, 762, 862, 906, 331, 409, 99, 99, 935, 172, 174, 717, 703, 457, 634, 112, 371, 335, 701, 976, 991, 817, 182, 814, 336, 377, 613, 348, 361, 906, 107, 918, 328, 3, 41, 579, 378, 116, 810],
+[ 262, 1, 167, 365, 518, 949, 869, 250, 482, 407, 606, 319, 448, 594, 697, 376, 859, 224, 245, 915, 516, 96, 694, 728, 302, 526, 809, 879, 970, 660, 712, 478, 529, 194, 385, 881, 595, 287, 70, 613, 847, 978, 322, 18, 408, 488, 701, 157, 546, 950, 368, 423, 562, 457, 804, 525, 60, 813, 385, 432, 370, 879, 81, 226, 742, 419, 965, 323, 739, 486, 864, 63, 140, 670, 216, 898, 111, 24, 465, 582, 129, 383, 392, 62, 436, 465, 497, 112, 599, 853, 395, 681, 792, 278, 305, 203, 917, 947, 519, 683],
+[ 24, 828, 335, 457, 672, 58, 979, 236, 589, 660, 280, 196, 213, 200, 968, 740, 711, 99, 190, 657, 507, 242, 804, 102, 561, 999, 890, 372, 698, 619, 96, 78, 741, 349, 209, 837, 550, 61, 142, 54, 272, 316, 973, 168, 71, 892, 893, 785, 857, 161, 62, 554, 190, 696, 30, 55, 748, 395, 55, 119, 511, 979, 776, 412, 65, 3, 435, 551, 356, 350, 988, 240, 260, 209, 948, 592, 690, 826, 433, 997, 356, 211, 890, 792, 541, 476, 529, 935, 697, 777, 108, 396, 648, 131, 167, 254, 510, 969, 543, 495],
+[ 422, 112, 540, 74, 165, 34, 293, 110, 483, 634, 185, 895, 84, 150, 781, 224, 991, 115, 807, 242, 340, 512, 109, 217, 255, 913, 898, 206, 975, 756, 98, 456, 695, 686, 50, 720, 610, 201, 94, 742, 186, 110, 582, 114, 889, 159, 955, 163, 388, 685, 688, 874, 537, 390, 950, 155, 430, 664, 947, 222, 772, 244, 714, 94, 497, 718, 608, 658, 449, 766, 875, 784, 734, 263, 993, 34, 269, 978, 790, 993, 277, 143, 529, 918, 919, 558, 638, 134, 822, 105, 766, 28, 828, 461, 518, 289, 453, 680, 102, 597],
+[ 333, 538, 184, 445, 751, 50, 370, 58, 373, 357, 839, 956, 956, 720, 580, 510, 586, 968, 626, 188, 750, 518, 548, 563, 422, 316, 723, 867, 849, 755, 726, 143, 46, 457, 55, 891, 592, 18, 465, 787, 836, 333, 948, 760, 928, 903, 875, 98, 696, 1, 909, 956, 244, 547, 270, 854, 272, 136, 763, 194, 348, 130, 499, 157, 272, 873, 679, 935, 620, 687, 889, 110, 12, 184, 344, 442, 184, 542, 692, 518, 477, 434, 344, 536, 100, 816, 153, 939, 70, 803, 970, 332, 489, 732, 761, 820, 256, 846, 966, 955],
+[ 696, 801, 249, 667, 447, 127, 422, 648, 524, 215, 126, 448, 445, 693, 604, 193, 114, 811, 500, 461, 277, 928, 802, 181, 259, 647, 906, 126, 361, 549, 657, 968, 510, 735, 696, 471, 864, 264, 266, 626, 506, 410, 669, 179, 768, 743, 659, 235, 112, 893, 962, 694, 467, 197, 568, 300, 476, 572, 590, 312, 30, 158, 38, 684, 219, 98, 321, 40, 839, 64, 796, 888, 109, 577, 363, 813, 758, 428, 23, 546, 742, 704, 857, 451, 5, 221, 155, 396, 697, 713, 110, 513, 479, 109, 932, 244, 879, 139, 564, 838],
+[ 927, 542, 126, 712, 97, 84, 571, 887, 775, 648, 411, 884, 980, 873, 876, 391, 836, 867, 971, 277, 905, 309, 377, 932, 350, 779, 838, 638, 88, 397, 256, 826, 401, 134, 735, 665, 827, 720, 432, 616, 475, 767, 842, 792, 582, 515, 740, 231, 253, 664, 216, 604, 683, 306, 316, 419, 192, 978, 428, 737, 711, 484, 786, 906, 87, 703, 824, 611, 307, 153, 251, 708, 526, 815, 710, 339, 290, 732, 712, 280, 567, 219, 2, 865, 392, 598, 700, 475, 101, 941, 28, 445, 388, 104, 948, 815, 639, 942, 619, 176],
+[ 388, 828, 511, 554, 262, 928, 333, 381, 551, 618, 696, 383, 62, 972, 313, 962, 601, 261, 401, 536, 836, 565, 206, 457, 671, 30, 899, 566, 763, 840, 40, 98, 0, 926, 944, 344, 821, 418, 131, 849, 696, 841, 457, 517, 566, 371, 323, 109, 552, 527, 169, 963, 487, 279, 424, 30, 19, 512, 717, 412, 945, 751, 848, 89, 725, 87, 395, 530, 245, 678, 978, 939, 753, 297, 440, 879, 25, 915, 375, 582, 856, 729, 790, 667, 408, 628, 497, 875, 11, 442, 778, 425, 987, 71, 717, 988, 378, 88, 125, 170],
+[ 138, 973, 776, 541, 679, 589, 474, 139, 827, 963, 527, 327, 720, 908, 156, 507, 163, 695, 413, 997, 437, 675, 997, 694, 829, 582, 842, 242, 312, 109, 206, 246, 540, 896, 212, 419, 475, 671, 35, 539, 19, 682, 278, 371, 413, 916, 772, 121, 516, 774, 78, 910, 809, 80, 631, 607, 401, 229, 359, 281, 189, 891, 215, 199, 382, 702, 836, 395, 176, 348, 591, 510, 941, 610, 7, 369, 515, 534, 964, 176, 965, 548, 251, 726, 661, 318, 647, 313, 747, 649, 830, 761, 610, 206, 202, 265, 656, 679, 503, 987],
+[ 960, 534, 850, 481, 839, 206, 353, 760, 316, 16, 919, 798, 693, 977, 576, 310, 715, 704, 600, 533, 597, 163, 205, 525, 934, 309, 286, 239, 158, 149, 389, 579, 656, 896, 577, 74, 618, 330, 644, 890, 688, 885, 851, 562, 640, 762, 460, 628, 283, 54, 450, 854, 183, 49, 815, 466, 114, 718, 263, 849, 474, 31, 561, 153, 373, 902, 154, 732, 683, 594, 79, 246, 717, 872, 6, 396, 703, 322, 525, 123, 498, 677, 24, 848, 849, 449, 780, 81, 616, 83, 428, 259, 854, 560, 41, 242, 945, 569, 447, 529],
+[ 953, 751, 515, 618, 258, 198, 129, 898, 610, 211, 348, 409, 962, 177, 240, 719, 790, 18, 129, 172, 121, 503, 114, 594, 562, 781, 172, 462, 190, 827, 529, 276, 361, 165, 739, 447, 712, 3, 798, 229, 287, 7, 858, 72, 536, 504, 267, 363, 223, 723, 194, 105, 628, 221, 911, 368, 198, 578, 240, 418, 587, 892, 909, 470, 35, 217, 221, 821, 932, 76, 753, 938, 172, 696, 144, 84, 375, 36, 299, 165, 995, 282, 511, 469, 917, 36, 828, 830, 950, 460, 792, 9, 293, 482, 290, 310, 630, 268, 358, 625],
+[ 203, 54, 356, 983, 226, 436, 950, 167, 688, 150, 546, 975, 710, 327, 339, 521, 712, 318, 533, 137, 855, 716, 641, 662, 344, 878, 890, 900, 826, 124, 221, 305, 909, 397, 523, 216, 581, 250, 58, 143, 176, 812, 695, 645, 603, 764, 774, 79, 947, 280, 860, 605, 312, 957, 752, 276, 353, 859, 245, 324, 297, 236, 387, 625, 312, 120, 769, 839, 236, 204, 29, 275, 753, 256, 920, 255, 512, 176, 916, 292, 787, 773, 169, 134, 773, 586, 686, 440, 635, 481, 626, 225, 282, 45, 65, 278, 494, 105, 754, 374],
+[ 171, 86, 547, 990, 449, 64, 908, 200, 207, 306, 640, 124, 648, 872, 319, 284, 438, 596, 356, 458, 364, 419, 542, 372, 508, 308, 578, 637, 649, 800, 291, 830, 501, 103, 19, 5, 841, 4, 345, 2, 338, 584, 481, 894, 771, 719, 690, 847, 382, 929, 311, 477, 9, 882, 682, 617, 586, 812, 323, 11, 135, 877, 933, 57, 10, 746, 570, 323, 419, 873, 572, 368, 56, 876, 410, 841, 372, 403, 74, 624, 98, 863, 294, 535, 732, 107, 490, 731, 991, 903, 351, 882, 303, 870, 749, 437, 37, 9, 566, 472],
+[ 595, 343, 596, 193, 549, 195, 378, 187, 414, 599, 508, 781, 316, 422, 773, 349, 238, 945, 939, 138, 745, 513, 42, 37, 2, 775, 54, 691, 558, 977, 363, 922, 4, 581, 56, 9, 299, 956, 949, 935, 567, 583, 288, 219, 605, 813, 409, 528, 105, 115, 600, 528, 174, 364, 562, 555, 65, 166, 578, 494, 386, 396, 147, 315, 221, 548, 930, 31, 545, 176, 83, 721, 356, 306, 270, 158, 158, 485, 999, 295, 959, 845, 590, 236, 772, 559, 783, 537, 716, 182, 791, 103, 580, 646, 677, 247, 132, 839, 775, 488],
+[ 781, 240, 951, 639, 806, 23, 471, 805, 755, 373, 584, 276, 400, 163, 860, 544, 755, 647, 69, 22, 667, 167, 758, 55, 432, 576, 109, 477, 531, 932, 428, 402, 360, 385, 630, 473, 130, 383, 465, 445, 634, 266, 205, 535, 911, 723, 35, 764, 747, 338, 920, 52, 355, 335, 698, 367, 45, 432, 356, 446, 912, 226, 702, 976, 532, 239, 691, 719, 941, 898, 108, 909, 612, 184, 336, 314, 880, 413, 861, 79, 432, 349, 543, 712, 165, 475, 712, 10, 588, 29, 997, 922, 953, 502, 841, 828, 653, 279, 628, 792],
+[ 218, 938, 972, 686, 331, 346, 69, 683, 752, 956, 83, 503, 973, 916, 965, 207, 344, 664, 268, 634, 93, 316, 970, 218, 943, 838, 584, 533, 258, 859, 662, 908, 68, 654, 352, 71, 243, 913, 494, 337, 810, 515, 781, 9, 922, 56, 809, 501, 131, 685, 276, 598, 188, 927, 182, 545, 857, 20, 48, 2, 248, 448, 256, 830, 135, 360, 450, 942, 344, 386, 527, 270, 424, 63, 51, 612, 124, 582, 528, 152, 868, 772, 223, 505, 268, 441, 28, 563, 607, 555, 229, 45, 303, 172, 261, 123, 854, 623, 303, 35],
+[ 374, 739, 676, 781, 317, 377, 785, 393, 783, 801, 983, 75, 366, 464, 473, 472, 163, 798, 637, 602, 528, 505, 837, 265, 710, 936, 522, 908, 257, 934, 631, 696, 467, 619, 653, 17, 546, 379, 988, 829, 656, 52, 380, 877, 260, 418, 447, 237, 584, 490, 977, 682, 817, 670, 709, 80, 386, 19, 408, 977, 649, 983, 36, 995, 213, 587, 953, 693, 360, 744, 949, 557, 210, 746, 476, 828, 138, 886, 250, 124, 413, 175, 484, 708, 425, 799, 24, 543, 506, 570, 678, 1, 401, 974, 57, 130, 216, 216, 237, 153],
+[ 937, 536, 860, 128, 362, 493, 214, 872, 854, 358, 741, 951, 270, 177, 147, 608, 699, 391, 170, 364, 506, 157, 886, 196, 671, 231, 321, 487, 430, 888, 607, 778, 872, 379, 280, 107, 525, 946, 693, 978, 886, 395, 549, 884, 470, 331, 228, 712, 566, 914, 636, 268, 956, 542, 195, 691, 933, 337, 993, 113, 747, 597, 537, 311, 783, 385, 684, 869, 793, 626, 541, 662, 572, 947, 523, 908, 83, 233, 169, 860, 545, 904, 965, 657, 402, 628, 270, 793, 102, 132, 360, 216, 259, 237, 645, 466, 163, 489, 360, 708],
+[ 896, 428, 769, 716, 697, 610, 262, 464, 346, 501, 961, 953, 502, 932, 627, 98, 785, 822, 489, 894, 1, 831, 296, 483, 260, 591, 987, 567, 785, 266, 622, 252, 89, 719, 413, 264, 726, 973, 358, 132, 147, 691, 443, 991, 522, 417, 100, 922, 522, 966, 681, 691, 548, 273, 47, 825, 183, 29, 151, 561, 8, 715, 615, 467, 6, 953, 962, 344, 110, 258, 800, 874, 183, 71, 420, 567, 733, 444, 786, 839, 857, 646, 91, 196, 956, 759, 949, 545, 93, 13, 184, 837, 634, 203, 532, 700, 109, 505, 378, 850],
+[ 215, 708, 799, 796, 456, 187, 971, 343, 135, 411, 369, 364, 254, 297, 83, 499, 258, 565, 151, 27, 680, 845, 373, 402, 346, 134, 872, 559, 471, 802, 342, 775, 248, 964, 103, 102, 545, 322, 278, 802, 858, 662, 202, 829, 474, 590, 527, 499, 985, 934, 441, 730, 56, 409, 442, 622, 92, 713, 730, 129, 170, 445, 855, 685, 546, 196, 690, 771, 606, 143, 884, 432, 931, 181, 509, 853, 221, 243, 266, 624, 451, 845, 64, 648, 711, 245, 41, 632, 939, 947, 671, 13, 121, 603, 164, 211, 678, 749, 772, 328],
+[ 9, 307, 259, 990, 737, 363, 38, 587, 677, 656, 643, 809, 963, 263, 226, 798, 708, 236, 42, 251, 307, 34, 846, 924, 760, 559, 403, 926, 899, 515, 192, 597, 788, 509, 61, 519, 299, 701, 207, 902, 262, 987, 669, 231, 367, 593, 551, 626, 996, 71, 483, 949, 62, 783, 971, 880, 65, 310, 912, 932, 983, 826, 649, 13, 480, 83, 628, 450, 470, 52, 294, 255, 422, 256, 253, 465, 384, 207, 61, 63, 190, 194, 809, 39, 400, 99, 704, 245, 223, 254, 539, 787, 447, 30, 271, 484, 691, 262, 595, 83],
+[ 579, 750, 97, 495, 662, 984, 177, 220, 940, 166, 952, 193, 460, 816, 375, 979, 658, 22, 748, 843, 188, 604, 106, 423, 696, 628, 164, 169, 522, 543, 522, 830, 361, 737, 599, 255, 736, 222, 667, 865, 121, 639, 221, 158, 234, 629, 46, 659, 955, 311, 567, 473, 640, 705, 320, 807, 339, 83, 748, 373, 506, 589, 775, 696, 857, 121, 141, 485, 180, 14, 807, 74, 813, 855, 156, 899, 602, 412, 592, 402, 128, 382, 350, 586, 400, 639, 462, 558, 901, 230, 493, 640, 631, 808, 168, 219, 96, 99, 947, 334],
+[ 482, 440, 368, 861, 131, 880, 527, 106, 587, 252, 366, 5, 999, 963, 787, 431, 39, 934, 626, 564, 323, 383, 609, 824, 724, 722, 971, 669, 262, 6, 283, 598, 823, 722, 87, 251, 492, 782, 231, 192, 450, 903, 195, 885, 809, 596, 887, 610, 192, 779, 52, 427, 567, 785, 19, 498, 154, 429, 8, 752, 671, 797, 691, 885, 982, 124, 696, 871, 763, 691, 22, 410, 275, 16, 435, 356, 558, 956, 88, 742, 884, 778, 626, 129, 636, 866, 543, 130, 977, 88, 808, 26, 560, 685, 710, 138, 151, 427, 440, 211],
+[ 328, 906, 697, 29, 55, 68, 959, 288, 817, 207, 752, 492, 136, 662, 122, 545, 474, 972, 50, 910, 316, 540, 126, 642, 537, 297, 526, 358, 210, 189, 452, 689, 16, 342, 557, 43, 204, 155, 69, 500, 598, 138, 188, 860, 199, 984, 960, 665, 161, 673, 791, 517, 221, 310, 266, 805, 92, 217, 355, 70, 544, 989, 920, 798, 180, 31, 806, 957, 509, 860, 764, 116, 371, 321, 195, 985, 673, 223, 4, 502, 78, 635, 563, 357, 173, 43, 388, 428, 935, 330, 484, 352, 967, 14, 27, 938, 170, 653, 706, 29],
+[ 847, 382, 568, 628, 735, 967, 242, 714, 803, 898, 484, 976, 233, 202, 218, 85, 138, 267, 744, 362, 922, 591, 47, 668, 906, 799, 670, 272, 956, 857, 948, 325, 345, 486, 355, 18, 927, 502, 490, 908, 484, 452, 908, 170, 360, 179, 833, 230, 203, 432, 402, 130, 935, 874, 347, 960, 193, 886, 896, 407, 504, 86, 391, 990, 873, 615, 72, 51, 634, 737, 248, 795, 303, 472, 895, 804, 225, 358, 205, 493, 57, 575, 870, 354, 888, 164, 770, 122, 810, 189, 922, 335, 332, 447, 124, 197, 643, 33, 551, 683],
+[ 43, 761, 664, 547, 393, 684, 116, 499, 853, 989, 735, 240, 258, 926, 978, 353, 378, 639, 287, 413, 196, 165, 944, 772, 180, 935, 181, 897, 791, 103, 47, 666, 976, 878, 977, 13, 455, 590, 215, 777, 55, 770, 57, 421, 393, 808, 932, 366, 456, 303, 977, 211, 19, 944, 882, 421, 314, 592, 891, 878, 855, 160, 186, 507, 841, 673, 666, 123, 440, 447, 870, 57, 710, 480, 637, 737, 909, 324, 780, 128, 347, 243, 432, 620, 354, 955, 360, 619, 878, 757, 505, 643, 54, 298, 875, 928, 744, 472, 273, 221],
+[ 391, 920, 604, 17, 118, 417, 316, 482, 101, 631, 941, 612, 188, 443, 263, 511, 140, 70, 319, 404, 632, 817, 150, 414, 573, 557, 210, 172, 892, 612, 952, 762, 196, 207, 323, 172, 368, 444, 321, 34, 337, 215, 887, 645, 874, 316, 321, 438, 452, 995, 37, 627, 186, 710, 706, 816, 609, 188, 346, 170, 547, 706, 895, 896, 866, 811, 373, 351, 403, 870, 196, 831, 978, 341, 26, 432, 80, 619, 456, 740, 366, 327, 179, 702, 114, 327, 218, 762, 442, 340, 123, 986, 521, 723, 298, 457, 28, 646, 606, 479],
+[ 265, 626, 62, 191, 564, 34, 879, 324, 702, 322, 670, 962, 230, 85, 857, 313, 57, 521, 356, 968, 609, 245, 164, 696, 656, 895, 542, 315, 709, 240, 276, 871, 550, 770, 700, 377, 962, 431, 429, 841, 820, 705, 516, 383, 451, 843, 307, 481, 105, 990, 998, 2, 663, 111, 931, 527, 946, 290, 38, 70, 877, 251, 963, 269, 549, 887, 398, 419, 214, 216, 314, 603, 804, 613, 985, 820, 24, 203, 564, 668, 322, 910, 866, 283, 651, 819, 112, 620, 536, 172, 145, 803, 904, 510, 259, 501, 402, 958, 244, 861],
+[ 463, 424, 437, 281, 945, 866, 801, 977, 701, 360, 263, 125, 250, 57, 175, 785, 454, 77, 293, 403, 401, 107, 138, 266, 710, 177, 294, 840, 592, 345, 852, 492, 478, 801, 199, 750, 238, 950, 746, 917, 192, 21, 108, 31, 31, 665, 392, 195, 642, 452, 532, 609, 709, 824, 568, 618, 144, 455, 268, 758, 682, 963, 891, 705, 877, 710, 428, 660, 532, 260, 481, 921, 194, 540, 463, 946, 393, 461, 209, 944, 255, 898, 228, 659, 136, 213, 937, 440, 765, 352, 149, 841, 356, 328, 290, 130, 20, 782, 261, 823],
+[ 372, 534, 678, 476, 344, 493, 56, 171, 926, 202, 998, 573, 943, 961, 478, 273, 131, 73, 47, 920, 490, 348, 61, 762, 34, 361, 334, 383, 774, 249, 661, 194, 137, 761, 126, 796, 800, 208, 525, 92, 868, 104, 546, 321, 456, 501, 535, 791, 995, 38, 922, 843, 304, 570, 382, 470, 704, 165, 691, 533, 612, 595, 896, 948, 477, 341, 345, 370, 136, 677, 183, 114, 309, 578, 500, 267, 960, 800, 613, 607, 135, 399, 979, 982, 808, 910, 292, 192, 414, 432, 485, 714, 153, 849, 752, 805, 656, 896, 426, 483],
+[ 796, 938, 942, 484, 15, 345, 212, 464, 604, 133, 320, 308, 400, 796, 229, 549, 551, 244, 458, 464, 424, 63, 816, 879, 191, 404, 401, 259, 645, 216, 692, 356, 644, 596, 85, 899, 696, 248, 687, 703, 715, 593, 803, 798, 856, 388, 20, 946, 586, 8, 488, 276, 352, 513, 270, 143, 808, 288, 885, 538, 453, 842, 739, 503, 948, 756, 427, 309, 493, 522, 722, 335, 242, 646, 301, 428, 708, 299, 392, 850, 540, 999, 977, 456, 748, 996, 481, 88, 499, 460, 484, 433, 369, 917, 60, 579, 717, 670, 161, 966],
+[ 115, 863, 547, 152, 738, 93, 342, 420, 717, 628, 832, 395, 973, 547, 621, 757, 986, 778, 604, 270, 808, 277, 606, 128, 781, 795, 314, 101, 245, 377, 474, 992, 597, 393, 444, 612, 865, 120, 45, 787, 646, 670, 748, 654, 368, 544, 773, 156, 738, 864, 937, 881, 130, 120, 697, 565, 764, 682, 464, 297, 886, 726, 53, 968, 455, 318, 338, 988, 512, 788, 423, 105, 679, 308, 591, 586, 901, 261, 351, 548, 98, 818, 589, 543, 703, 999, 955, 310, 749, 554, 926, 732, 771, 173, 431, 165, 862, 662, 155, 139],
+[ 607, 327, 200, 135, 948, 235, 469, 59, 49, 31, 507, 818, 558, 225, 674, 787, 460, 990, 469, 996, 415, 280, 153, 934, 125, 275, 853, 71, 616, 146, 943, 40, 470, 822, 652, 73, 969, 701, 667, 268, 950, 171, 413, 385, 398, 286, 713, 946, 975, 749, 956, 958, 752, 416, 898, 631, 885, 417, 216, 392, 412, 510, 309, 345, 83, 48, 449, 854, 512, 808, 40, 521, 484, 761, 625, 40, 939, 239, 810, 69, 414, 169, 311, 0, 34, 348, 138, 883, 23, 746, 973, 158, 806, 749, 796, 691, 279, 784, 589, 736],
+[ 212, 167, 41, 134, 286, 403, 775, 953, 461, 528, 368, 395, 860, 341, 746, 582, 259, 512, 500, 614, 808, 624, 178, 831, 391, 275, 609, 472, 412, 63, 629, 31, 887, 615, 240, 544, 709, 845, 233, 400, 293, 920, 462, 509, 753, 43, 224, 844, 946, 559, 151, 76, 873, 224, 907, 713, 606, 277, 639, 124, 674, 641, 713, 477, 948, 259, 528, 851, 443, 202, 174, 336, 379, 219, 610, 174, 819, 538, 796, 603, 733, 226, 907, 542, 252, 562, 631, 688, 518, 242, 40, 57, 264, 67, 443, 583, 892, 542, 591, 292],
+[ 893, 830, 368, 620, 531, 303, 337, 311, 371, 26, 827, 929, 746, 489, 290, 540, 657, 787, 89, 128, 271, 339, 484, 899, 887, 459, 129, 973, 881, 593, 666, 731, 784, 268, 841, 99, 103, 803, 34, 680, 583, 393, 94, 141, 612, 313, 149, 198, 914, 353, 526, 2, 479, 377, 562, 379, 508, 982, 61, 863, 22, 482, 237, 418, 258, 608, 222, 151, 187, 233, 952, 828, 442, 981, 119, 585, 723, 419, 282, 74, 786, 683, 517, 364, 861, 560, 496, 966, 143, 60, 293, 856, 924, 463, 956, 356, 25, 96, 7, 809],
+[ 507, 739, 898, 679, 89, 695, 429, 665, 357, 661, 714, 991, 408, 970, 344, 374, 277, 170, 136, 237, 139, 667, 726, 258, 761, 425, 756, 207, 15, 403, 244, 879, 978, 821, 802, 215, 768, 779, 513, 451, 641, 581, 993, 10, 107, 692, 757, 385, 724, 637, 94, 871, 408, 791, 201, 59, 909, 154, 714, 230, 419, 651, 674, 467, 710, 524, 257, 349, 470, 890, 85, 962, 67, 105, 172, 968, 226, 373, 896, 516, 54, 553, 33, 873, 787, 863, 515, 778, 98, 960, 985, 125, 459, 625, 806, 195, 796, 913, 475, 718],
+[ 450, 911, 444, 773, 583, 620, 569, 531, 860, 810, 214, 121, 336, 115, 286, 183, 240, 940, 103, 96, 273, 933, 972, 435, 704, 139, 500, 724, 14, 860, 954, 577, 451, 239, 286, 431, 439, 567, 550, 420, 144, 793, 316, 447, 2, 512, 762, 8, 374, 775, 263, 978, 675, 331, 617, 271, 925, 419, 577, 127, 179, 178, 741, 937, 942, 604, 129, 693, 19, 174, 541, 774, 569, 210, 146, 704, 639, 762, 734, 875, 145, 477, 636, 997, 652, 267, 204, 854, 856, 238, 12, 854, 206, 446, 362, 175, 834, 966, 43, 80],
+[ 425, 226, 297, 921, 399, 369, 134, 655, 377, 609, 188, 979, 455, 612, 639, 724, 369, 785, 951, 344, 777, 879, 410, 296, 698, 487, 666, 41, 37, 420, 920, 791, 196, 461, 212, 658, 41, 309, 442, 374, 458, 986, 854, 233, 901, 206, 138, 586, 705, 182, 561, 478, 500, 896, 569, 830, 9, 249, 501, 52, 912, 964, 259, 53, 491, 573, 733, 82, 971, 937, 782, 125, 302, 55, 414, 439, 181, 869, 76, 565, 182, 451, 571, 51, 131, 743, 813, 813, 395, 756, 76, 473, 928, 538, 230, 110, 139, 666, 402, 576],
+[ 321, 295, 868, 382, 715, 356, 106, 688, 460, 547, 859, 215, 587, 738, 250, 657, 468, 288, 882, 878, 924, 902, 354, 828, 140, 187, 752, 665, 438, 311, 399, 18, 605, 314, 30, 502, 294, 516, 443, 403, 798, 539, 578, 959, 302, 692, 44, 521, 527, 685, 505, 310, 377, 371, 877, 26, 121, 909, 754, 851, 419, 682, 248, 958, 529, 945, 649, 603, 544, 607, 631, 557, 823, 777, 576, 369, 983, 952, 65, 584, 52, 371, 988, 597, 982, 28, 249, 783, 169, 167, 893, 32, 171, 831, 618, 166, 920, 864, 399, 215],
+[ 707, 383, 785, 550, 566, 663, 922, 906, 812, 7, 341, 795, 414, 588, 131, 350, 623, 668, 910, 473, 361, 63, 418, 665, 155, 64, 179, 224, 356, 306, 492, 794, 770, 702, 959, 393, 656, 355, 491, 869, 185, 273, 761, 778, 270, 849, 882, 857, 972, 980, 330, 850, 162, 556, 807, 113, 848, 431, 811, 448, 372, 56, 651, 854, 233, 88, 283, 914, 581, 942, 501, 848, 197, 967, 702, 743, 679, 278, 501, 219, 386, 933, 598, 709, 649, 703, 453, 841, 256, 452, 249, 437, 986, 54, 50, 867, 600, 387, 975, 77],
+[ 68, 273, 716, 190, 641, 951, 502, 848, 672, 176, 206, 31, 577, 923, 105, 216, 355, 89, 356, 70, 22, 698, 639, 293, 348, 78, 308, 106, 678, 479, 239, 694, 557, 317, 402, 555, 235, 176, 409, 832, 511, 46, 873, 23, 278, 679, 381, 872, 423, 265, 623, 295, 698, 401, 692, 670, 256, 198, 612, 541, 307, 126, 26, 696, 610, 404, 345, 409, 481, 828, 461, 977, 108, 61, 489, 630, 748, 259, 721, 469, 430, 298, 569, 706, 203, 284, 969, 868, 772, 916, 398, 990, 509, 616, 871, 801, 535, 743, 994, 760],
+[ 416, 913, 216, 683, 588, 820, 97, 673, 951, 49, 756, 362, 359, 62, 552, 598, 625, 938, 223, 87, 31, 76, 73, 623, 428, 894, 102, 487, 329, 669, 976, 105, 937, 659, 561, 82, 313, 707, 491, 68, 185, 698, 522, 106, 411, 135, 100, 616, 52, 904, 456, 644, 762, 227, 239, 502, 781, 905, 862, 409, 757, 315, 597, 864, 155, 883, 231, 223, 314, 972, 780, 98, 441, 670, 40, 552, 455, 769, 559, 64, 135, 31, 698, 686, 302, 945, 920, 974, 131, 762, 111, 603, 607, 693, 790, 681, 794, 909, 137, 713],
+[ 557, 779, 321, 762, 28, 946, 726, 213, 136, 309, 855, 707, 737, 472, 53, 2, 604, 252, 865, 632, 442, 101, 922, 482, 230, 299, 601, 296, 841, 553, 884, 50, 907, 380, 816, 875, 981, 838, 636, 268, 894, 857, 26, 591, 815, 908, 854, 474, 125, 411, 625, 785, 305, 434, 715, 852, 165, 334, 177, 101, 134, 534, 906, 502, 488, 683, 629, 204, 957, 543, 357, 172, 576, 95, 887, 147, 432, 813, 260, 369, 421, 852, 177, 441, 788, 698, 621, 163, 39, 566, 351, 394, 359, 152, 170, 195, 20, 626, 643, 904],
+[ 419, 837, 684, 761, 385, 68, 997, 373, 222, 279, 412, 591, 808, 410, 455, 9, 478, 987, 145, 506, 365, 580, 50, 612, 867, 46, 435, 583, 934, 598, 238, 737, 240, 543, 526, 471, 807, 484, 701, 687, 685, 938, 407, 185, 604, 741, 568, 148, 368, 619, 238, 949, 501, 113, 216, 726, 477, 374, 851, 309, 992, 459, 33, 365, 824, 690, 754, 716, 408, 610, 391, 66, 362, 131, 863, 962, 225, 952, 928, 564, 404, 920, 167, 19, 262, 329, 705, 389, 131, 247, 824, 962, 918, 547, 707, 9, 368, 574, 639, 145],
+[ 693, 917, 254, 863, 543, 631, 996, 599, 757, 952, 520, 803, 487, 670, 677, 258, 54, 973, 418, 659, 509, 7, 347, 280, 300, 418, 852, 179, 954, 541, 524, 490, 101, 11, 281, 596, 184, 689, 265, 857, 653, 204, 503, 356, 190, 796, 735, 358, 80, 810, 909, 378, 846, 947, 338, 963, 67, 965, 122, 820, 506, 260, 689, 855, 488, 602, 84, 164, 930, 595, 488, 577, 639, 678, 208, 535, 673, 283, 912, 716, 407, 145, 953, 67, 217, 48, 68, 948, 982, 79, 65, 14, 527, 689, 61, 914, 299, 455, 614, 349],
+[ 639, 49, 212, 859, 318, 60, 534, 845, 617, 611, 43, 845, 616, 132, 583, 680, 619, 763, 607, 705, 382, 677, 739, 613, 561, 651, 51, 449, 684, 976, 154, 17, 721, 303, 518, 905, 225, 100, 496, 277, 852, 201, 63, 273, 182, 990, 710, 686, 771, 862, 851, 105, 944, 527, 807, 964, 216, 251, 394, 649, 296, 775, 967, 302, 757, 191, 843, 88, 975, 419, 333, 493, 898, 425, 862, 722, 119, 555, 797, 85, 738, 942, 692, 648, 860, 525, 653, 356, 972, 537, 878, 30, 111, 141, 111, 648, 463, 793, 63, 12],
+[ 876, 388, 206, 833, 480, 472, 796, 445, 32, 590, 449, 93, 657, 252, 644, 617, 665, 2, 400, 253, 587, 161, 99, 980, 427, 216, 760, 245, 293, 922, 487, 894, 608, 389, 610, 6, 477, 907, 928, 95, 681, 127, 951, 234, 819, 356, 171, 697, 273, 391, 761, 674, 530, 993, 130, 475, 838, 539, 507, 546, 405, 999, 771, 999, 242, 33, 165, 232, 427, 974, 827, 577, 322, 968, 681, 826, 489, 622, 140, 854, 424, 526, 579, 948, 105, 264, 53, 289, 256, 203, 373, 567, 101, 951, 877, 342, 502, 575, 396, 834],
+[ 656, 536, 51, 232, 965, 16, 498, 579, 975, 694, 570, 238, 342, 125, 513, 199, 538, 737, 862, 860, 80, 316, 769, 11, 940, 630, 849, 243, 689, 539, 17, 658, 678, 939, 981, 762, 559, 221, 699, 553, 180, 384, 423, 588, 544, 186, 26, 54, 595, 738, 701, 197, 978, 647, 631, 943, 478, 368, 682, 206, 885, 525, 788, 908, 102, 961, 53, 98, 294, 849, 520, 357, 38, 889, 804, 361, 871, 856, 303, 198, 437, 227, 110, 229, 66, 266, 576, 256, 844, 298, 168, 186, 351, 432, 166, 876, 537, 733, 324, 154],
+[ 219, 878, 913, 50, 734, 116, 135, 419, 454, 939, 215, 703, 666, 716, 427, 71, 447, 496, 94, 419, 72, 911, 610, 161, 118, 985, 557, 223, 631, 365, 66, 905, 171, 937, 393, 573, 366, 221, 793, 851, 986, 250, 273, 175, 766, 425, 905, 101, 580, 531, 467, 86, 127, 465, 51, 730, 357, 813, 502, 968, 638, 296, 438, 865, 724, 848, 731, 232, 156, 635, 510, 246, 523, 34, 321, 539, 553, 926, 757, 625, 707, 330, 270, 183, 802, 823, 102, 773, 426, 607, 28, 83, 838, 705, 884, 370, 628, 907, 849, 857],
+[ 981, 135, 455, 228, 880, 843, 799, 993, 874, 568, 419, 983, 25, 530, 41, 733, 968, 443, 908, 120, 263, 382, 137, 583, 277, 182, 503, 279, 985, 208, 622, 291, 180, 92, 117, 324, 201, 360, 975, 399, 152, 737, 49, 624, 733, 310, 227, 245, 939, 160, 222, 110, 848, 15, 548, 561, 59, 772, 872, 673, 406, 664, 789, 967, 974, 809, 651, 620, 931, 39, 472, 993, 740, 330, 381, 316, 555, 750, 223, 704, 708, 340, 66, 518, 320, 281, 810, 307, 869, 962, 592, 785, 180, 295, 983, 971, 441, 190, 578, 226],
+[ 517, 314, 778, 104, 936, 330, 684, 500, 75, 200, 423, 581, 931, 231, 326, 712, 529, 126, 233, 877, 318, 821, 664, 874, 947, 553, 223, 147, 41, 84, 609, 287, 948, 134, 103, 795, 148, 121, 751, 503, 541, 458, 690, 334, 858, 409, 574, 72, 51, 269, 535, 226, 128, 944, 174, 481, 482, 258, 320, 620, 548, 864, 575, 31, 430, 81, 895, 510, 572, 852, 120, 217, 11, 642, 64, 572, 617, 659, 52, 417, 768, 609, 857, 858, 504, 561, 312, 922, 485, 225, 276, 185, 545, 783, 86, 212, 378, 885, 288, 669],
+[ 836, 341, 191, 759, 752, 283, 946, 946, 259, 568, 524, 257, 297, 145, 950, 408, 248, 938, 133, 837, 452, 876, 672, 147, 618, 486, 816, 733, 877, 856, 901, 351, 318, 352, 547, 968, 838, 287, 335, 291, 104, 522, 940, 394, 794, 293, 381, 260, 962, 473, 176, 281, 50, 306, 841, 170, 586, 454, 810, 669, 800, 725, 294, 99, 563, 636, 663, 33, 78, 318, 873, 893, 43, 278, 641, 843, 925, 948, 22, 132, 23, 101, 608, 87, 337, 779, 290, 247, 960, 696, 852, 49, 360, 175, 197, 559, 336, 568, 719, 509],
+[ 430, 8, 161, 969, 96, 214, 496, 333, 252, 169, 680, 199, 463, 36, 602, 39, 815, 92, 982, 177, 766, 894, 400, 17, 126, 896, 519, 396, 397, 509, 195, 201, 776, 967, 334, 717, 452, 405, 300, 144, 373, 938, 508, 770, 828, 151, 947, 88, 746, 45, 260, 899, 981, 893, 767, 312, 806, 441, 336, 697, 720, 494, 350, 845, 24, 198, 327, 485, 256, 384, 986, 369, 921, 760, 412, 82, 481, 13, 83, 271, 855, 432, 273, 28, 713, 653, 786, 867, 155, 441, 110, 276, 431, 303, 231, 200, 281, 365, 415, 658],
+[ 519, 228, 523, 208, 330, 431, 823, 41, 820, 593, 289, 95, 166, 534, 544, 942, 577, 106, 561, 963, 229, 31, 326, 922, 753, 727, 874, 12, 481, 352, 209, 342, 279, 873, 825, 804, 398, 860, 798, 792, 471, 347, 975, 786, 866, 750, 795, 21, 446, 55, 909, 160, 874, 46, 886, 259, 305, 780, 966, 231, 752, 11, 171, 649, 997, 796, 629, 476, 937, 745, 958, 637, 751, 917, 682, 348, 629, 847, 73, 180, 453, 309, 47, 339, 668, 441, 415, 713, 438, 58, 315, 933, 671, 983, 654, 516, 431, 310, 171, 463],
+[ 297, 985, 225, 627, 993, 722, 826, 92, 113, 995, 498, 714, 803, 816, 462, 331, 229, 508, 294, 607, 942, 583, 480, 855, 884, 102, 821, 944, 238, 75, 653, 888, 347, 912, 968, 796, 119, 640, 455, 740, 6, 464, 319, 455, 540, 51, 85, 184, 862, 710, 830, 544, 406, 352, 766, 453, 632, 443, 214, 965, 677, 35, 216, 6, 579, 700, 623, 297, 313, 578, 365, 320, 506, 569, 317, 554, 564, 37, 645, 976, 864, 360, 874, 204, 636, 33, 951, 143, 740, 356, 961, 560, 802, 24, 14, 497, 324, 916, 123, 51],
+[ 324, 98, 895, 105, 896, 664, 708, 779, 648, 989, 824, 971, 939, 274, 408, 102, 949, 143, 261, 214, 114, 646, 351, 864, 23, 920, 992, 501, 279, 375, 395, 237, 436, 936, 578, 669, 42, 234, 857, 811, 790, 918, 284, 87, 640, 927, 51, 413, 257, 697, 248, 368, 719, 258, 705, 72, 100, 914, 606, 982, 692, 283, 24, 675, 204, 319, 964, 613, 842, 588, 335, 676, 439, 221, 723, 909, 489, 557, 502, 827, 410, 793, 256, 811, 273, 673, 765, 522, 796, 68, 681, 700, 984, 307, 801, 798, 129, 395, 679, 424],
+[ 986, 596, 799, 236, 629, 385, 96, 790, 283, 403, 299, 416, 379, 276, 263, 635, 133, 382, 666, 483, 478, 439, 939, 664, 307, 933, 447, 863, 564, 102, 401, 413, 171, 166, 24, 242, 622, 795, 595, 64, 563, 47, 584, 944, 970, 735, 751, 497, 270, 627, 143, 756, 983, 584, 464, 857, 22, 46, 89, 204, 302, 569, 892, 660, 630, 880, 897, 458, 32, 390, 772, 255, 491, 862, 336, 453, 597, 832, 189, 245, 304, 92, 691, 712, 454, 707, 783, 219, 891, 723, 0, 892, 374, 907, 29, 466, 839, 682, 608, 404],
+[ 878, 417, 23, 356, 116, 345, 772, 551, 265, 230, 59, 902, 319, 748, 747, 233, 80, 7, 22, 536, 413, 695, 593, 447, 364, 262, 210, 44, 136, 286, 183, 985, 744, 765, 140, 18, 286, 649, 554, 387, 971, 78, 398, 626, 32, 234, 377, 754, 796, 109, 919, 503, 761, 578, 121, 456, 478, 384, 517, 646, 714, 98, 242, 351, 4, 943, 587, 618, 202, 676, 995, 906, 247, 851, 625, 543, 133, 665, 695, 939, 530, 664, 963, 344, 974, 819, 269, 427, 899, 663, 919, 660, 135, 809, 31, 541, 382, 200, 914, 687],
+[ 367, 774, 980, 429, 808, 997, 404, 441, 843, 177, 792, 509, 966, 135, 905, 578, 718, 433, 865, 29, 342, 724, 202, 138, 803, 71, 267, 233, 685, 866, 551, 928, 299, 211, 606, 960, 981, 350, 264, 477, 408, 381, 756, 158, 741, 238, 278, 814, 608, 714, 599, 638, 370, 516, 975, 291, 527, 745, 395, 751, 213, 618, 934, 837, 607, 568, 565, 745, 943, 299, 741, 634, 586, 17, 478, 159, 99, 761, 523, 857, 785, 983, 843, 330, 418, 755, 376, 804, 267, 644, 328, 435, 361, 651, 744, 646, 590, 727, 881, 958],
+[ 984, 569, 207, 203, 88, 750, 706, 3, 37, 600, 497, 448, 173, 758, 860, 295, 533, 491, 609, 510, 95, 200, 89, 31, 35, 430, 700, 667, 26, 848, 623, 802, 270, 398, 213, 217, 115, 159, 296, 226, 753, 151, 911, 24, 792, 70, 378, 752, 791, 300, 975, 768, 471, 29, 999, 417, 12, 317, 771, 888, 519, 607, 722, 305, 594, 840, 812, 351, 75, 811, 259, 598, 306, 536, 7, 953, 13, 839, 301, 196, 957, 858, 909, 858, 937, 693, 908, 774, 281, 448, 978, 989, 367, 228, 381, 724, 608, 174, 423, 453],
+[ 324, 606, 751, 83, 280, 223, 529, 775, 22, 841, 251, 871, 223, 506, 791, 652, 601, 818, 992, 720, 928, 159, 432, 758, 184, 348, 911, 266, 792, 307, 315, 297, 433, 797, 93, 417, 887, 841, 948, 915, 159, 198, 826, 465, 50, 908, 342, 815, 233, 560, 662, 262, 64, 415, 13, 358, 79, 707, 438, 250, 707, 266, 22, 714, 648, 424, 278, 403, 404, 732, 925, 350, 659, 553, 216, 159, 485, 297, 899, 980, 785, 928, 93, 310, 690, 612, 933, 949, 544, 580, 828, 495, 608, 124, 6, 685, 698, 549, 798, 882],
+[ 273, 637, 805, 744, 8, 352, 248, 321, 510, 782, 857, 412, 33, 869, 49, 205, 93, 814, 735, 213, 268, 597, 760, 983, 300, 595, 807, 980, 577, 667, 357, 956, 71, 359, 177, 775, 892, 248, 270, 704, 829, 575, 345, 168, 514, 829, 541, 926, 138, 428, 80, 774, 164, 700, 411, 253, 704, 194, 158, 472, 72, 218, 90, 492, 490, 35, 118, 335, 202, 604, 335, 205, 877, 487, 750, 193, 844, 438, 575, 433, 96, 408, 687, 662, 268, 949, 176, 28, 225, 731, 756, 431, 610, 504, 667, 122, 555, 769, 48, 763],
+[ 127, 134, 16, 708, 757, 899, 491, 149, 93, 834, 174, 632, 403, 711, 300, 642, 524, 826, 9, 855, 957, 921, 353, 734, 723, 974, 908, 974, 368, 90, 132, 567, 511, 786, 340, 914, 233, 591, 766, 436, 451, 888, 453, 817, 599, 762, 989, 74, 908, 496, 377, 854, 331, 39, 216, 383, 166, 447, 224, 803, 459, 761, 722, 752, 99, 312, 579, 680, 642, 201, 286, 78, 194, 191, 786, 890, 438, 898, 14, 745, 268, 6, 589, 490, 774, 626, 320, 508, 275, 831, 52, 564, 763, 134, 915, 397, 936, 729, 365, 792],
+[ 500, 96, 73, 766, 6, 294, 166, 847, 64, 864, 692, 289, 478, 204, 760, 455, 556, 874, 413, 808, 192, 901, 45, 327, 749, 817, 635, 308, 63, 420, 271, 649, 54, 694, 926, 638, 76, 408, 978, 353, 618, 318, 505, 708, 27, 800, 268, 691, 473, 445, 511, 482, 613, 309, 349, 446, 850, 900, 694, 203, 82, 102, 856, 202, 995, 646, 565, 558, 51, 825, 576, 797, 909, 785, 541, 470, 242, 858, 182, 419, 887, 764, 903, 224, 792, 955, 535, 14, 887, 787, 102, 719, 911, 404, 982, 906, 59, 104, 663, 14],
+[ 272, 54, 357, 257, 980, 63, 783, 845, 111, 921, 353, 914, 385, 372, 535, 272, 101, 557, 947, 570, 405, 355, 176, 744, 356, 163, 602, 841, 427, 160, 104, 925, 242, 756, 425, 231, 145, 909, 69, 157, 891, 214, 33, 120, 174, 394, 700, 478, 8, 757, 649, 652, 346, 805, 130, 811, 596, 962, 140, 350, 225, 686, 895, 603, 657, 359, 756, 879, 404, 212, 757, 857, 88, 571, 569, 940, 454, 956, 383, 531, 127, 980, 265, 72, 327, 651, 903, 732, 721, 723, 107, 489, 93, 843, 557, 82, 955, 93, 607, 488],
+[ 365, 251, 463, 814, 818, 350, 53, 955, 600, 754, 947, 652, 616, 348, 152, 823, 329, 31, 815, 792, 130, 470, 371, 562, 152, 670, 800, 498, 539, 684, 69, 273, 335, 535, 240, 682, 49, 979, 222, 638, 448, 71, 230, 689, 784, 647, 650, 400, 673, 344, 824, 950, 483, 497, 284, 746, 734, 305, 631, 90, 827, 853, 859, 422, 57, 938, 39, 92, 651, 818, 156, 704, 253, 671, 206, 836, 180, 278, 111, 690, 876, 636, 581, 776, 410, 183, 242, 688, 904, 15, 835, 616, 166, 173, 740, 56, 245, 750, 59, 487],
+[ 26, 48, 944, 320, 209, 976, 293, 670, 349, 98, 991, 375, 319, 231, 466, 933, 240, 227, 68, 67, 61, 709, 928, 155, 373, 212, 239, 564, 472, 781, 479, 630, 706, 810, 603, 684, 560, 753, 98, 43, 973, 869, 810, 920, 570, 314, 50, 564, 189, 40, 853, 656, 371, 678, 459, 502, 428, 684, 490, 608, 524, 609, 499, 677, 169, 952, 891, 599, 44, 190, 990, 826, 57, 758, 283, 108, 862, 333, 694, 594, 872, 281, 50, 583, 702, 875, 443, 157, 674, 855, 597, 458, 93, 399, 911, 440, 110, 159, 206, 982],
+[ 565, 541, 575, 259, 427, 408, 94, 900, 264, 190, 789, 740, 817, 54, 474, 656, 693, 392, 148, 899, 756, 463, 485, 752, 611, 425, 503, 739, 93, 172, 730, 601, 223, 551, 244, 18, 715, 493, 104, 572, 373, 472, 551, 324, 246, 574, 41, 760, 546, 560, 908, 193, 937, 720, 391, 700, 418, 500, 754, 413, 747, 819, 928, 801, 741, 954, 611, 688, 792, 960, 491, 444, 920, 16, 320, 100, 588, 455, 17, 606, 865, 177, 987, 568, 812, 400, 48, 159, 972, 477, 504, 310, 81, 663, 807, 517, 749, 442, 374, 758],
+[ 952, 772, 319, 516, 456, 461, 812, 22, 62, 732, 28, 386, 681, 368, 837, 619, 130, 50, 189, 365, 301, 669, 128, 467, 735, 739, 374, 874, 535, 218, 525, 754, 951, 463, 504, 241, 738, 480, 435, 182, 498, 879, 742, 300, 916, 76, 593, 363, 637, 526, 250, 838, 231, 176, 723, 356, 30, 15, 167, 102, 289, 155, 778, 816, 868, 654, 430, 530, 789, 796, 561, 873, 267, 838, 258, 274, 155, 262, 520, 680, 558, 373, 665, 504, 522, 209, 19, 182, 489, 892, 252, 12, 854, 88, 169, 658, 728, 860, 158, 763],
+[ 657, 767, 984, 360, 119, 134, 626, 496, 46, 300, 378, 261, 968, 188, 769, 438, 896, 256, 370, 455, 399, 16, 718, 3, 201, 921, 395, 583, 295, 100, 158, 533, 86, 950, 759, 988, 820, 178, 787, 37, 843, 434, 431, 673, 114, 486, 671, 9, 780, 794, 758, 653, 532, 142, 572, 462, 648, 453, 161, 235, 364, 780, 429, 75, 147, 154, 250, 805, 60, 834, 416, 690, 442, 7, 732, 914, 102, 190, 382, 246, 762, 437, 865, 77, 294, 603, 180, 733, 143, 566, 222, 237, 138, 134, 288, 974, 750, 60, 877, 735],
+[ 515, 643, 907, 708, 984, 722, 461, 360, 888, 961, 689, 490, 673, 749, 19, 202, 789, 108, 165, 892, 125, 669, 840, 38, 375, 739, 691, 785, 284, 833, 338, 712, 139, 364, 843, 935, 359, 934, 91, 292, 239, 603, 501, 808, 863, 808, 894, 925, 129, 606, 354, 609, 380, 608, 894, 967, 210, 127, 796, 614, 938, 963, 591, 528, 412, 918, 847, 969, 474, 815, 820, 629, 653, 372, 528, 752, 817, 68, 211, 483, 370, 803, 488, 259, 456, 235, 409, 626, 759, 694, 589, 746, 334, 207, 727, 692, 581, 48, 24, 490],
+[ 615, 138, 479, 250, 430, 821, 771, 908, 333, 409, 622, 903, 676, 991, 378, 191, 45, 296, 335, 992, 107, 78, 676, 779, 950, 797, 138, 529, 579, 364, 627, 509, 908, 702, 559, 657, 320, 599, 995, 335, 902, 941, 954, 935, 949, 960, 220, 669, 568, 700, 271, 122, 127, 563, 447, 730, 364, 771, 328, 206, 434, 602, 87, 875, 391, 369, 113, 797, 457, 444, 174, 623, 439, 76, 825, 381, 893, 262, 498, 365, 880, 67, 106, 552, 679, 790, 487, 745, 421, 488, 52, 606, 470, 923, 94, 814, 420, 402, 367, 471],
+[ 777, 989, 575, 0, 530, 976, 506, 845, 22, 697, 362, 558, 206, 753, 176, 309, 905, 76, 796, 176, 812, 615, 440, 147, 294, 897, 727, 76, 961, 305, 778, 912, 738, 882, 265, 333, 417, 840, 775, 200, 700, 931, 21, 872, 123, 236, 81, 310, 537, 585, 873, 112, 419, 99, 568, 538, 364, 997, 919, 794, 972, 181, 519, 511, 463, 505, 341, 524, 866, 36, 150, 941, 489, 993, 41, 276, 927, 548, 21, 817, 750, 327, 310, 417, 134, 219, 739, 847, 105, 587, 268, 601, 108, 846, 428, 762, 975, 626, 977, 78],
+[ 557, 127, 559, 473, 783, 542, 610, 967, 839, 515, 34, 586, 414, 725, 726, 206, 741, 62, 84, 675, 64, 579, 194, 346, 892, 840, 929, 742, 569, 380, 985, 550, 148, 835, 579, 708, 37, 843, 110, 806, 657, 98, 368, 923, 384, 957, 551, 107, 878, 316, 664, 507, 205, 358, 643, 570, 250, 796, 905, 724, 846, 80, 295, 130, 850, 877, 890, 438, 805, 627, 850, 984, 771, 464, 287, 987, 29, 822, 541, 185, 430, 556, 430, 304, 931, 36, 961, 215, 359, 726, 949, 178, 159, 840, 955, 218, 537, 383, 671, 767],
+[ 969, 580, 785, 75, 176, 805, 666, 451, 185, 210, 700, 19, 710, 764, 419, 497, 891, 389, 442, 22, 708, 810, 435, 265, 324, 575, 887, 332, 718, 405, 570, 813, 647, 317, 136, 511, 683, 353, 806, 753, 889, 275, 716, 170, 518, 289, 893, 711, 596, 140, 437, 743, 585, 818, 869, 605, 69, 524, 515, 136, 140, 640, 732, 458, 895, 982, 122, 365, 501, 707, 319, 128, 223, 161, 904, 677, 60, 175, 60, 258, 237, 54, 808, 357, 415, 883, 203, 333, 357, 93, 734, 767, 803, 873, 409, 198, 950, 833, 727, 674],
+[ 868, 632, 904, 179, 869, 730, 66, 530, 460, 868, 383, 844, 406, 624, 814, 948, 180, 371, 701, 576, 813, 217, 853, 625, 167, 593, 352, 761, 666, 779, 367, 519, 75, 959, 465, 845, 435, 713, 332, 824, 95, 65, 973, 294, 275, 52, 444, 624, 843, 479, 789, 382, 812, 184, 935, 198, 658, 870, 851, 776, 365, 42, 426, 815, 423, 491, 117, 787, 195, 65, 486, 90, 842, 721, 673, 356, 586, 305, 543, 335, 383, 169, 814, 65, 384, 404, 978, 234, 296, 36, 204, 165, 98, 639, 851, 376, 984, 316, 894, 319],
+[ 424, 678, 569, 558, 109, 430, 549, 864, 866, 282, 282, 29, 487, 905, 257, 145, 310, 490, 521, 69, 673, 924, 961, 394, 448, 100, 293, 630, 583, 691, 93, 750, 926, 729, 568, 724, 169, 27, 793, 896, 521, 75, 783, 112, 311, 551, 314, 349, 860, 512, 94, 341, 752, 929, 363, 15, 569, 588, 782, 415, 889, 356, 138, 530, 801, 666, 624, 380, 827, 996, 623, 900, 899, 296, 7, 264, 17, 793, 315, 143, 249, 33, 181, 801, 382, 555, 956, 281, 556, 591, 606, 82, 950, 202, 997, 390, 380, 185, 575, 111],
+[ 885, 502, 515, 779, 714, 172, 651, 461, 623, 261, 576, 79, 702, 791, 332, 781, 522, 869, 570, 185, 762, 565, 624, 360, 533, 518, 710, 286, 961, 531, 549, 801, 925, 485, 68, 442, 563, 124, 485, 537, 896, 196, 75, 800, 641, 19, 112, 922, 367, 831, 34, 299, 911, 941, 521, 751, 688, 373, 258, 770, 703, 189, 741, 241, 310, 977, 36, 963, 760, 417, 577, 702, 284, 6, 980, 70, 164, 750, 762, 918, 837, 949, 448, 437, 675, 485, 505, 424, 743, 291, 350, 780, 663, 266, 675, 968, 303, 911, 865, 85],
+[ 802, 219, 197, 645, 524, 741, 149, 322, 937, 768, 999, 571, 728, 119, 708, 622, 201, 31, 312, 404, 865, 9, 410, 623, 728, 867, 896, 134, 71, 970, 363, 826, 602, 281, 279, 933, 692, 202, 376, 146, 12, 460, 560, 334, 318, 786, 952, 967, 950, 107, 200, 961, 212, 905, 721, 423, 301, 462, 522, 725, 458, 825, 146, 135, 387, 823, 727, 896, 969, 239, 882, 191, 83, 75, 920, 692, 419, 412, 684, 967, 369, 363, 739, 597, 188, 830, 721, 345, 128, 784, 247, 658, 100, 711, 190, 138, 199, 200, 482, 91],
+[ 378, 644, 233, 989, 350, 591, 387, 967, 611, 876, 891, 421, 907, 709, 719, 246, 554, 493, 665, 860, 298, 584, 309, 711, 551, 493, 907, 60, 838, 780, 396, 28, 206, 41, 224, 237, 592, 629, 985, 323, 925, 438, 863, 57, 393, 762, 626, 916, 102, 446, 351, 842, 517, 79, 358, 991, 323, 798, 106, 992, 382, 973, 769, 10, 241, 713, 224, 644, 527, 62, 912, 936, 266, 973, 238, 625, 388, 935, 683, 421, 725, 73, 358, 958, 836, 645, 3, 902, 324, 377, 497, 849, 953, 301, 187, 377, 354, 215, 718, 354],
+[ 718, 561, 503, 907, 558, 310, 147, 559, 180, 841, 753, 527, 228, 803, 738, 652, 875, 440, 952, 651, 198, 810, 221, 692, 275, 589, 340, 76, 304, 538, 81, 978, 414, 514, 269, 272, 274, 324, 708, 24, 35, 456, 432, 904, 814, 852, 211, 283, 593, 403, 289, 855, 317, 203, 285, 844, 925, 252, 785, 9, 195, 2, 779, 629, 526, 297, 594, 545, 555, 996, 810, 292, 378, 711, 581, 40, 155, 469, 894, 42, 144, 735, 462, 68, 439, 804, 599, 453, 532, 382, 234, 922, 46, 560, 663, 579, 151, 619, 512, 962],
+[ 267, 158, 607, 622, 133, 230, 262, 182, 837, 528, 301, 325, 570, 523, 141, 982, 854, 650, 331, 564, 308, 932, 164, 498, 642, 981, 99, 961, 264, 955, 827, 840, 58, 223, 353, 657, 68, 686, 659, 991, 635, 111, 525, 795, 838, 600, 94, 607, 488, 279, 886, 713, 203, 107, 657, 292, 731, 519, 467, 339, 669, 425, 161, 644, 939, 55, 727, 812, 797, 839, 213, 145, 833, 584, 804, 639, 168, 211, 842, 253, 972, 73, 4, 997, 955, 445, 352, 313, 231, 722, 88, 974, 229, 109, 350, 348, 436, 469, 453, 412],
+[ 786, 313, 406, 848, 778, 604, 373, 685, 607, 81, 297, 278, 399, 898, 552, 18, 103, 680, 334, 426, 76, 823, 981, 117, 852, 140, 402, 233, 10, 133, 457, 185, 785, 211, 79, 65, 253, 155, 392, 492, 164, 967, 344, 288, 348, 436, 505, 928, 856, 107, 229, 957, 468, 543, 783, 976, 946, 96, 6, 905, 752, 526, 123, 883, 106, 579, 512, 770, 23, 181, 915, 832, 509, 156, 96, 423, 630, 686, 3, 209, 211, 994, 67, 809, 159, 752, 820, 658, 842, 571, 796, 418, 957, 572, 994, 340, 947, 152, 288, 722],
+[ 549, 983, 337, 805, 677, 278, 888, 137, 204, 131, 165, 377, 738, 967, 752, 852, 463, 768, 605, 28, 318, 640, 737, 751, 9, 212, 433, 479, 714, 121, 781, 718, 949, 78, 665, 284, 635, 250, 446, 263, 72, 163, 849, 933, 831, 660, 471, 151, 307, 801, 49, 711, 191, 238, 672, 789, 756, 531, 946, 701, 880, 711, 355, 729, 864, 904, 934, 64, 583, 153, 400, 375, 62, 84, 494, 71, 103, 689, 713, 857, 291, 557, 245, 712, 291, 183, 18, 743, 305, 827, 400, 758, 832, 62, 168, 375, 758, 38, 496, 870],
+[ 792, 798, 284, 431, 306, 812, 79, 589, 745, 589, 358, 150, 280, 422, 386, 238, 984, 563, 609, 157, 326, 475, 487, 851, 260, 321, 754, 148, 114, 404, 891, 369, 755, 798, 400, 352, 213, 745, 75, 464, 95, 503, 63, 709, 997, 661, 343, 900, 69, 847, 422, 352, 315, 561, 988, 720, 59, 892, 208, 463, 23, 291, 755, 183, 924, 954, 650, 342, 926, 812, 516, 236, 799, 865, 253, 542, 216, 751, 610, 296, 773, 530, 803, 441, 15, 479, 588, 470, 118, 238, 17, 611, 890, 136, 566, 147, 714, 202, 480, 358],
+[ 532, 644, 973, 437, 354, 856, 647, 464, 324, 737, 166, 662, 606, 543, 22, 279, 954, 103, 498, 894, 534, 579, 451, 582, 301, 11, 292, 795, 560, 592, 945, 895, 463, 96, 22, 623, 26, 406, 61, 848, 467, 161, 512, 95, 690, 942, 41, 754, 641, 653, 811, 647, 784, 87, 507, 439, 79, 815, 446, 811, 948, 296, 685, 217, 485, 752, 398, 140, 962, 730, 292, 708, 638, 4, 798, 697, 256, 419, 472, 270, 729, 470, 586, 717, 441, 848, 991, 836, 50, 695, 696, 437, 153, 659, 841, 457, 933, 690, 591, 724],
+[ 396, 316, 856, 63, 63, 734, 58, 251, 299, 371, 17, 429, 490, 74, 864, 936, 850, 335, 173, 185, 391, 573, 183, 899, 203, 951, 641, 181, 395, 206, 414, 296, 194, 256, 634, 337, 148, 803, 373, 525, 379, 632, 410, 485, 168, 996, 427, 124, 264, 895, 620, 630, 761, 927, 855, 254, 798, 677, 610, 784, 741, 847, 793, 775, 547, 950, 354, 501, 264, 633, 721, 813, 411, 685, 556, 828, 512, 73, 684, 836, 73, 362, 810, 438, 984, 788, 438, 517, 53, 103, 487, 446, 923, 407, 685, 973, 927, 988, 662, 643]
+]
+
+matrix3 = matrix_mul(matrix1, matrix2)

+ 85 - 0
talks/cant-c-a-thing/code/multiwrapper.c

@@ -0,0 +1,85 @@
+#include "ruby.h"
+
+#include "caml/callback.h"
+     
+void initcaml(void) {
+  char *dummy_argv[] = {0};
+
+  caml_startup(dummy_argv);
+}
+
+
+VALUE OcamlMatrix = Qnil; // set up the Ruby namespace
+    
+void Init_ocamlmatrix();
+
+VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height);
+void method_assign(VALUE self, VALUE x, VALUE y, VALUE item);
+VALUE method_retrieve(VALUE self, VALUE x, VALUE y);
+VALUE method_matrix_mul(VALUE self, VALUE b);
+
+void Init_ocamlmatrix() {
+  OcamlMatrix = rb_define_module("OcamlMatrix");
+  rb_define_singleton_method(OcamlMatrix, "w_new_matrix", singleton_matrix_new, 2);
+  rb_define_method(OcamlMatrix, "w_matrix_assign", method_assign, 3);
+  rb_define_method(OcamlMatrix, "w_matrix_retrieve", method_retrieve, 2);
+  rb_define_method(OcamlMatrix, "w_matrix_mul", method_matrix_mul, 1);
+
+  initcaml();
+}
+
+
+VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+  ocaml_m = ocaml_new_matrix(Val_long(NUM2LONG(width)), Val_long(NUM2LONG(height)));
+
+  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+  rb_obj_call_init(my_m, 0, NULL);
+
+  CAMLreturnT (VALUE, my_m);
+}
+
+
+void method_assign(VALUE self, VALUE x, VALUE y, VALUE item) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+
+  Data_Get_Struct(self, value, ocaml_m);
+
+  ocaml_matrix_assign(ocaml_m, Val_long(NUM2LONG(x)), Val_long(NUM2LONG(y)), Val_long(NUM2LONG(item)));
+
+  CAMLreturn0;
+}
+
+
+VALUE method_retrieve(VALUE self, VALUE x, VALUE y) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+
+  Data_Get_Struct(self, value, ocaml_m);
+
+  VALUE retval = LONG2NUM(Long_val(ocaml_matrix_retrieve(ocaml_m,
+                                                         Val_long(NUM2LONG(x)),
+                                                         Val_long(NUM2LONG(y)))
+                                   ));
+
+  CAMLreturnT (VALUE, retval);
+}
+
+
+VALUE method_matrix_mul(VALUE self, VALUE b) {
+  CAMLparam0 ();
+  CAMLlocal3 (ocaml_m1, ocaml_m2, ocaml_m3);
+
+  Data_Get_Struct(self, value, ocaml_m1);
+  Data_Get_Struct(b, value, ocaml_m2);
+
+  ocaml_m3 = ocaml_matrix_mul(ocaml_m1, ocaml_m2);
+
+  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+  rb_obj_call_init(my_m, 0, NULL);
+
+  CAMLreturnT (VALUE, my_m);
+}
+

+ 222 - 0
talks/cant-c-a-thing/code/ocaml_arrays.ml

@@ -0,0 +1,222 @@
+let matrix_mul x y =
+  let x0 = Array.length x
+  and y0 = Array.length y in
+  let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+  let z = Array.make_matrix x0 y1 0 in
+  for i = 0 to x0-1 do
+    for j = 0 to y1-1 do
+      for k = 0 to y0-1 do
+        z.(i).(j) <- z.(i).(j) + x.(i).(k) * y.(k).(j)
+      done
+    done
+  done;
+  z
+
+let matrix1 = [| 
+[| 13; 458; 667; 794; 474; 190; 590; 164; 418; 713; 461; 72; 808; 252; 398; 668; 675; 392; 658; 285; 465; 759; 931; 318; 252; 137; 161; 961; 719; 710; 638; 475; 874; 832; 38; 775; 127; 232; 360; 53; 940; 62; 834; 369; 202; 101; 776; 475; 614; 112; 878; 94; 933; 957; 406; 604; 754; 746; 686; 798; 137; 978; 644; 884; 243; 949; 886; 430; 897; 838; 532; 691; 752; 321; 769; 303; 796; 355; 712; 761; 899; 191; 196; 890; 354; 641; 645; 756; 456; 422; 420; 681; 502; 386; 475; 342; 263; 387; 903; 585|];
+[| 471; 29; 715; 919; 269; 703; 485; 770; 409; 254; 766; 399; 174; 697; 871; 663; 380; 843; 374; 861; 376; 161; 177; 496; 590; 688; 857; 47; 148; 889; 542; 711; 837; 884; 50; 40; 243; 409; 225; 214; 348; 695; 524; 82; 976; 235; 500; 85; 660; 982; 477; 200; 673; 702; 258; 697; 475; 580; 895; 399; 241; 308; 930; 974; 975; 961; 934; 114; 972; 556; 734; 407; 792; 470; 457; 682; 210; 70; 318; 884; 711; 682; 318; 154; 932; 336; 876; 988; 780; 498; 570; 997; 380; 738; 643; 576; 500; 417; 951; 917|];
+[| 832; 881; 641; 275; 630; 836; 447; 177; 398; 30; 183; 520; 1; 999; 941; 108; 465; 609; 893; 320; 359; 422; 443; 319; 446; 263; 2; 670; 464; 407; 855; 133; 107; 287; 627; 742; 782; 850; 897; 311; 641; 142; 467; 219; 832; 465; 172; 979; 819; 633; 122; 970; 242; 412; 560; 644; 724; 404; 705; 89; 197; 43; 173; 888; 518; 104; 758; 204; 445; 31; 837; 708; 511; 140; 909; 371; 382; 295; 262; 144; 617; 943; 209; 216; 485; 541; 77; 737; 708; 787; 696; 5; 332; 954; 519; 374; 488; 84; 176; 365|];
+[| 574; 23; 755; 550; 81; 403; 35; 616; 646; 26; 489; 502; 115; 469; 766; 975; 981; 710; 689; 386; 420; 405; 326; 65; 384; 845; 869; 556; 845; 222; 99; 770; 290; 199; 545; 105; 510; 728; 668; 66; 248; 426; 171; 283; 488; 514; 769; 673; 190; 819; 110; 94; 868; 49; 837; 36; 170; 10; 686; 475; 105; 215; 209; 579; 271; 959; 46; 48; 408; 948; 184; 534; 672; 325; 79; 301; 59; 232; 251; 53; 763; 78; 29; 570; 942; 216; 524; 357; 528; 893; 652; 545; 250; 14; 137; 158; 332; 710; 517; 625|];
+[| 818; 612; 346; 27; 59; 585; 113; 883; 534; 331; 320; 641; 671; 432; 58; 181; 961; 434; 290; 417; 643; 816; 835; 135; 924; 927; 182; 838; 457; 625; 528; 642; 360; 651; 208; 28; 117; 174; 272; 243; 57; 923; 809; 156; 935; 979; 780; 844; 976; 674; 108; 117; 254; 434; 897; 630; 117; 241; 712; 234; 203; 870; 56; 828; 399; 909; 606; 402; 936; 581; 155; 691; 359; 771; 41; 589; 773; 622; 502; 182; 486; 583; 68; 291; 669; 981; 65; 976; 418; 278; 899; 573; 399; 45; 513; 261; 540; 411; 29; 511|];
+[| 666; 292; 522; 214; 224; 159; 941; 805; 847; 174; 206; 116; 557; 570; 181; 749; 523; 791; 180; 802; 802; 690; 726; 798; 967; 398; 528; 528; 53; 719; 67; 201; 100; 995; 945; 252; 644; 328; 15; 60; 934; 849; 239; 242; 169; 131; 468; 54; 750; 27; 700; 704; 89; 810; 951; 599; 959; 279; 578; 364; 512; 269; 478; 570; 735; 792; 614; 198; 671; 140; 553; 311; 813; 741; 354; 272; 743; 829; 376; 464; 294; 111; 61; 791; 774; 754; 659; 180; 273; 959; 379; 113; 147; 279; 749; 728; 727; 937; 25; 257|];
+[| 740; 239; 730; 571; 449; 192; 418; 178; 562; 591; 474; 832; 119; 113; 669; 259; 565; 309; 512; 529; 761; 353; 568; 734; 0; 883; 607; 455; 447; 5; 229; 350; 432; 4; 30; 879; 861; 445; 153; 120; 713; 810; 715; 584; 255; 953; 352; 938; 396; 729; 738; 459; 413; 367; 370; 917; 360; 375; 802; 497; 971; 250; 140; 731; 627; 507; 907; 694; 678; 36; 221; 42; 977; 71; 908; 51; 448; 530; 959; 427; 1; 442; 849; 703; 313; 754; 830; 311; 995; 947; 51; 791; 250; 79; 611; 302; 375; 846; 562; 141|];
+[| 524; 47; 289; 556; 90; 800; 78; 576; 605; 104; 882; 770; 481; 84; 379; 340; 314; 304; 994; 948; 504; 887; 249; 246; 744; 729; 176; 573; 458; 443; 601; 963; 531; 671; 631; 16; 258; 290; 706; 23; 578; 812; 79; 85; 863; 503; 369; 26; 751; 294; 933; 231; 776; 913; 904; 603; 891; 113; 985; 470; 384; 234; 601; 919; 21; 570; 399; 464; 150; 46; 825; 250; 559; 244; 14; 767; 59; 964; 996; 644; 730; 802; 570; 963; 883; 89; 279; 826; 407; 140; 665; 611; 63; 741; 617; 842; 792; 557; 982; 154|];
+[| 104; 977; 251; 143; 854; 149; 416; 480; 898; 704; 578; 132; 977; 511; 15; 172; 678; 862; 400; 293; 157; 407; 344; 540; 138; 752; 66; 308; 427; 641; 608; 369; 406; 182; 549; 858; 985; 752; 219; 235; 532; 103; 618; 522; 470; 156; 704; 129; 45; 133; 478; 26; 814; 987; 479; 117; 488; 414; 475; 53; 870; 232; 166; 22; 978; 181; 529; 480; 376; 25; 842; 153; 141; 602; 147; 199; 408; 673; 196; 576; 733; 488; 498; 521; 93; 360; 836; 534; 220; 599; 982; 461; 797; 269; 872; 46; 710; 367; 739; 41|];
+[| 944; 815; 471; 676; 226; 348; 413; 145; 474; 762; 642; 335; 664; 964; 219; 614; 456; 704; 155; 810; 23; 460; 648; 876; 448; 842; 292; 833; 576; 837; 185; 343; 957; 219; 746; 482; 174; 812; 583; 501; 336; 309; 518; 483; 367; 8; 440; 690; 619; 316; 548; 833; 923; 677; 574; 132; 967; 271; 202; 111; 374; 962; 450; 749; 129; 563; 948; 778; 502; 943; 863; 935; 312; 731; 142; 726; 510; 953; 798; 766; 934; 26; 94; 776; 289; 972; 821; 503; 540; 203; 783; 333; 503; 781; 874; 309; 502; 644; 947; 219|];
+[| 689; 803; 790; 729; 230; 755; 567; 824; 402; 897; 482; 519; 429; 148; 825; 631; 265; 443; 895; 638; 204; 878; 718; 953; 367; 52; 601; 503; 254; 108; 718; 360; 21; 116; 746; 527; 348; 660; 684; 36; 560; 306; 30; 498; 326; 932; 348; 674; 509; 881; 167; 443; 360; 208; 46; 924; 959; 574; 482; 620; 561; 158; 497; 33; 786; 723; 528; 931; 166; 935; 424; 624; 576; 523; 458; 733; 472; 750; 252; 257; 778; 541; 478; 673; 343; 253; 498; 778; 380; 320; 630; 315; 764; 266; 740; 940; 941; 217; 30; 825|];
+[| 833; 651; 195; 221; 32; 666; 999; 967; 651; 934; 414; 224; 184; 573; 33; 975; 223; 768; 330; 38; 94; 464; 358; 119; 900; 880; 141; 835; 345; 520; 45; 302; 609; 251; 998; 38; 862; 643; 535; 811; 525; 284; 540; 529; 124; 760; 176; 81; 590; 93; 378; 947; 195; 93; 949; 470; 405; 971; 106; 16; 367; 242; 193; 217; 550; 178; 368; 806; 220; 296; 245; 604; 806; 241; 437; 866; 480; 411; 233; 321; 579; 46; 107; 706; 313; 322; 183; 959; 274; 868; 426; 156; 684; 458; 220; 750; 708; 750; 188; 343|];
+[| 708; 130; 155; 703; 348; 471; 382; 659; 917; 59; 96; 572; 599; 435; 821; 14; 863; 747; 495; 360; 680; 737; 20; 49; 639; 398; 769; 625; 159; 599; 377; 875; 982; 181; 466; 7; 29; 197; 979; 707; 169; 204; 895; 104; 505; 120; 780; 233; 875; 390; 503; 12; 361; 994; 211; 3; 627; 747; 277; 496; 17; 15; 607; 929; 1; 463; 595; 422; 344; 924; 709; 939; 461; 629; 44; 678; 156; 816; 421; 853; 376; 172; 857; 777; 293; 89; 203; 729; 110; 770; 289; 795; 251; 729; 302; 102; 545; 642; 468; 373|];
+[| 950; 758; 247; 434; 855; 261; 874; 334; 356; 805; 220; 109; 525; 566; 533; 947; 941; 654; 95; 511; 939; 546; 568; 815; 89; 260; 272; 792; 891; 367; 608; 91; 689; 636; 446; 488; 74; 508; 257; 13; 408; 273; 789; 984; 749; 965; 583; 920; 703; 762; 723; 124; 689; 249; 885; 39; 829; 261; 748; 415; 665; 481; 817; 525; 808; 3; 626; 481; 434; 112; 946; 135; 708; 340; 949; 288; 526; 523; 417; 959; 668; 87; 106; 586; 430; 151; 966; 824; 997; 343; 235; 676; 944; 147; 720; 909; 394; 769; 618; 263|];
+[| 799; 8; 776; 287; 629; 611; 860; 539; 22; 707; 685; 40; 548; 156; 23; 104; 751; 759; 392; 941; 520; 193; 978; 686; 396; 123; 151; 513; 570; 944; 516; 339; 195; 96; 656; 821; 273; 128; 695; 937; 230; 714; 936; 220; 783; 375; 489; 512; 173; 358; 702; 989; 438; 938; 772; 149; 587; 453; 373; 6; 270; 237; 169; 344; 288; 520; 166; 872; 67; 334; 409; 508; 729; 149; 540; 916; 519; 541; 115; 58; 580; 311; 115; 648; 640; 727; 373; 837; 350; 739; 610; 325; 643; 348; 917; 815; 990; 416; 636; 837|];
+[| 726; 669; 723; 348; 924; 410; 280; 192; 682; 371; 132; 487; 106; 951; 298; 983; 724; 8; 507; 451; 219; 966; 979; 704; 156; 237; 930; 29; 899; 488; 812; 209; 701; 934; 807; 159; 55; 767; 700; 485; 173; 352; 60; 947; 19; 140; 793; 856; 949; 48; 331; 458; 785; 238; 463; 691; 765; 461; 133; 114; 406; 125; 643; 108; 725; 435; 807; 25; 797; 734; 618; 75; 728; 3; 452; 109; 202; 59; 949; 437; 673; 129; 230; 150; 788; 58; 542; 171; 393; 146; 436; 496; 310; 927; 813; 107; 496; 132; 959; 400|];
+[| 177; 48; 86; 428; 979; 703; 678; 260; 714; 25; 547; 712; 192; 853; 723; 465; 795; 490; 931; 912; 272; 819; 531; 754; 148; 492; 906; 112; 187; 652; 872; 468; 179; 703; 595; 54; 537; 948; 553; 36; 951; 568; 749; 651; 154; 528; 27; 256; 676; 430; 147; 838; 91; 747; 256; 246; 983; 690; 483; 525; 157; 673; 871; 771; 465; 693; 143; 365; 540; 710; 782; 174; 890; 793; 207; 174; 638; 905; 577; 225; 628; 224; 195; 560; 860; 613; 68; 588; 415; 805; 873; 782; 885; 650; 772; 297; 449; 307; 398; 777|];
+[| 422; 178; 282; 640; 756; 342; 94; 657; 563; 336; 87; 763; 710; 889; 632; 757; 76; 954; 651; 728; 824; 195; 621; 648; 386; 493; 941; 970; 816; 198; 450; 809; 760; 418; 558; 695; 331; 200; 1; 596; 954; 428; 440; 41; 531; 682; 117; 33; 498; 389; 161; 298; 199; 462; 273; 780; 289; 613; 764; 646; 813; 514; 508; 311; 52; 896; 816; 434; 488; 79; 806; 349; 592; 20; 424; 916; 172; 837; 574; 937; 374; 213; 668; 358; 714; 851; 782; 617; 336; 132; 914; 202; 104; 224; 676; 674; 349; 795; 308; 794|];
+[| 16; 483; 140; 120; 643; 768; 463; 61; 37; 58; 347; 586; 746; 588; 850; 964; 29; 443; 949; 960; 219; 994; 861; 870; 517; 338; 529; 353; 229; 635; 34; 763; 207; 278; 633; 43; 403; 926; 545; 544; 620; 385; 755; 88; 620; 219; 169; 438; 632; 953; 694; 327; 448; 729; 927; 644; 289; 643; 792; 738; 712; 871; 799; 513; 217; 764; 14; 676; 989; 463; 892; 918; 5; 350; 240; 156; 17; 850; 282; 127; 470; 109; 82; 624; 940; 428; 555; 116; 572; 566; 731; 617; 370; 943; 347; 885; 947; 321; 913; 102|];
+[| 952; 592; 850; 993; 3; 924; 234; 709; 905; 727; 716; 595; 889; 404; 538; 72; 238; 382; 919; 35; 39; 768; 834; 270; 160; 512; 956; 175; 579; 719; 70; 626; 321; 923; 251; 873; 261; 189; 105; 139; 151; 386; 808; 899; 901; 97; 53; 415; 163; 536; 564; 176; 160; 386; 511; 749; 868; 179; 881; 601; 270; 232; 782; 799; 21; 488; 712; 127; 95; 315; 572; 320; 871; 458; 184; 621; 481; 219; 327; 442; 196; 409; 471; 959; 487; 200; 527; 272; 433; 657; 650; 409; 788; 291; 832; 689; 36; 0; 481; 400|];
+[| 924; 734; 63; 557; 915; 936; 250; 720; 481; 315; 153; 122; 21; 727; 371; 8; 225; 826; 827; 344; 235; 867; 629; 747; 213; 786; 851; 780; 526; 56; 233; 204; 123; 49; 864; 403; 62; 270; 980; 732; 165; 40; 590; 522; 862; 214; 496; 166; 366; 946; 76; 703; 744; 541; 227; 293; 983; 841; 442; 455; 670; 45; 564; 905; 117; 692; 903; 540; 154; 73; 136; 932; 969; 563; 514; 969; 50; 322; 447; 729; 285; 262; 663; 83; 963; 456; 745; 699; 33; 875; 590; 469; 541; 846; 136; 700; 283; 869; 974; 224|];
+[| 239; 879; 691; 695; 216; 457; 353; 264; 211; 712; 911; 679; 421; 540; 492; 10; 796; 828; 84; 992; 596; 906; 500; 340; 425; 452; 801; 930; 74; 718; 338; 297; 277; 474; 455; 739; 439; 692; 384; 776; 760; 888; 175; 977; 381; 614; 613; 229; 632; 56; 959; 997; 155; 427; 408; 52; 122; 295; 651; 164; 395; 638; 713; 142; 200; 912; 189; 71; 673; 938; 26; 516; 244; 566; 378; 896; 542; 591; 97; 320; 978; 881; 228; 615; 728; 704; 776; 962; 473; 274; 915; 977; 687; 131; 232; 19; 537; 796; 186; 317|];
+[| 386; 376; 759; 333; 495; 987; 100; 685; 405; 463; 662; 37; 219; 586; 480; 110; 918; 751; 796; 530; 38; 384; 30; 25; 432; 114; 49; 174; 119; 883; 701; 789; 132; 75; 456; 147; 425; 769; 582; 89; 732; 417; 462; 723; 278; 595; 5; 897; 600; 809; 797; 359; 242; 450; 175; 194; 920; 718; 38; 242; 671; 915; 870; 724; 247; 328; 354; 30; 997; 534; 523; 659; 758; 755; 208; 281; 235; 688; 360; 55; 193; 644; 797; 925; 40; 902; 155; 573; 584; 997; 665; 896; 808; 161; 827; 47; 631; 868; 761; 574|];
+[| 347; 127; 476; 519; 358; 390; 895; 21; 811; 806; 246; 53; 934; 542; 250; 694; 855; 290; 294; 657; 969; 896; 427; 598; 703; 724; 108; 242; 202; 44; 222; 99; 169; 821; 338; 631; 937; 412; 487; 581; 824; 873; 636; 555; 529; 866; 156; 502; 381; 524; 693; 242; 380; 3; 423; 820; 380; 740; 273; 907; 53; 361; 743; 889; 746; 297; 585; 374; 650; 916; 191; 807; 128; 258; 16; 299; 972; 581; 573; 419; 762; 618; 484; 24; 908; 744; 686; 114; 417; 166; 802; 745; 577; 734; 647; 689; 979; 552; 783; 914|];
+[| 492; 272; 626; 991; 522; 426; 989; 162; 420; 452; 25; 886; 910; 824; 236; 597; 295; 908; 525; 243; 140; 895; 281; 728; 953; 540; 307; 510; 133; 89; 321; 547; 417; 284; 759; 913; 796; 646; 675; 454; 747; 15; 173; 467; 543; 543; 331; 83; 965; 432; 178; 876; 238; 713; 365; 204; 620; 722; 51; 443; 203; 942; 759; 337; 482; 231; 363; 570; 866; 142; 816; 104; 312; 477; 315; 478; 557; 289; 713; 644; 597; 261; 290; 227; 452; 762; 882; 500; 997; 306; 920; 258; 340; 703; 502; 940; 172; 153; 356; 800|];
+[| 370; 977; 232; 299; 448; 891; 102; 428; 597; 70; 828; 395; 629; 183; 65; 539; 803; 148; 942; 824; 644; 698; 880; 949; 793; 669; 20; 435; 757; 243; 860; 802; 814; 318; 870; 0; 884; 653; 386; 520; 882; 722; 323; 259; 856; 279; 830; 577; 395; 518; 967; 332; 2; 920; 374; 42; 398; 109; 739; 511; 691; 536; 934; 748; 755; 7; 55; 126; 661; 23; 687; 199; 153; 884; 32; 254; 887; 480; 448; 232; 230; 149; 610; 711; 534; 567; 567; 190; 391; 459; 707; 292; 398; 349; 402; 733; 998; 794; 601; 712|];
+[| 285; 622; 412; 377; 604; 165; 215; 583; 140; 830; 2; 949; 438; 866; 642; 542; 412; 880; 869; 306; 251; 832; 604; 413; 9; 342; 79; 147; 747; 982; 275; 240; 394; 77; 52; 596; 22; 446; 385; 528; 109; 52; 514; 384; 383; 267; 157; 113; 835; 23; 471; 948; 203; 387; 169; 81; 738; 218; 189; 24; 626; 398; 483; 392; 573; 993; 983; 720; 666; 23; 344; 698; 726; 911; 587; 332; 918; 696; 606; 930; 92; 946; 851; 305; 919; 795; 842; 47; 721; 972; 942; 538; 917; 465; 887; 66; 117; 432; 522; 680|];
+[| 186; 183; 101; 655; 548; 888; 405; 282; 835; 795; 530; 847; 257; 70; 571; 926; 342; 352; 419; 228; 116; 343; 287; 397; 25; 185; 310; 615; 993; 594; 668; 889; 381; 714; 407; 874; 908; 525; 938; 878; 868; 87; 583; 926; 782; 440; 213; 964; 846; 536; 66; 655; 472; 184; 664; 540; 124; 306; 276; 336; 548; 884; 597; 410; 633; 756; 2; 366; 660; 836; 218; 5; 362; 589; 266; 75; 27; 723; 773; 691; 891; 380; 678; 571; 341; 348; 306; 901; 302; 59; 791; 23; 932; 172; 540; 712; 607; 473; 230; 640|];
+[| 821; 438; 975; 442; 709; 268; 695; 469; 686; 121; 393; 979; 83; 868; 156; 75; 716; 379; 204; 235; 886; 270; 266; 245; 628; 678; 785; 459; 723; 597; 99; 301; 901; 192; 707; 692; 69; 242; 175; 901; 754; 481; 679; 333; 560; 91; 359; 741; 900; 894; 552; 432; 839; 866; 744; 246; 823; 529; 193; 828; 839; 744; 494; 778; 244; 373; 90; 111; 828; 388; 83; 914; 367; 159; 503; 176; 266; 525; 23; 725; 984; 562; 100; 720; 466; 914; 87; 611; 107; 726; 727; 87; 803; 79; 689; 173; 602; 822; 143; 994|];
+[| 391; 754; 164; 791; 822; 431; 156; 633; 9; 898; 762; 50; 270; 90; 928; 691; 382; 524; 238; 795; 548; 131; 886; 893; 48; 36; 973; 668; 675; 593; 489; 121; 639; 772; 613; 756; 881; 632; 298; 703; 341; 92; 705; 190; 734; 859; 19; 302; 156; 203; 383; 732; 40; 191; 893; 66; 980; 759; 175; 69; 778; 659; 102; 510; 50; 338; 529; 100; 102; 613; 151; 453; 833; 492; 626; 506; 836; 155; 658; 466; 637; 906; 502; 239; 306; 665; 245; 580; 213; 43; 373; 769; 539; 70; 733; 53; 242; 943; 432; 129|];
+[| 958; 780; 435; 245; 741; 922; 433; 828; 14; 309; 151; 786; 237; 507; 163; 306; 362; 807; 940; 83; 4; 82; 855; 950; 616; 639; 401; 15; 675; 213; 114; 143; 957; 998; 388; 422; 407; 852; 285; 179; 910; 538; 379; 947; 537; 757; 136; 659; 339; 310; 323; 371; 92; 75; 52; 496; 155; 522; 491; 985; 150; 534; 674; 440; 668; 287; 454; 367; 70; 740; 208; 923; 522; 328; 78; 630; 874; 741; 157; 121; 340; 727; 835; 525; 373; 120; 365; 612; 668; 436; 498; 435; 227; 457; 130; 783; 698; 674; 750; 358|];
+[| 752; 748; 639; 811; 169; 386; 755; 923; 610; 406; 430; 579; 122; 550; 503; 27; 937; 908; 867; 19; 466; 550; 394; 175; 115; 718; 128; 456; 614; 116; 543; 733; 993; 813; 847; 644; 305; 608; 331; 436; 475; 910; 998; 420; 728; 223; 987; 691; 163; 122; 568; 961; 981; 276; 616; 405; 684; 120; 259; 420; 29; 817; 833; 549; 197; 350; 479; 755; 34; 776; 733; 529; 155; 738; 104; 714; 908; 833; 518; 641; 798; 538; 794; 183; 94; 454; 603; 188; 493; 138; 66; 759; 104; 884; 837; 241; 546; 894; 666; 354|];
+[| 543; 651; 287; 308; 110; 188; 968; 950; 593; 484; 126; 983; 274; 647; 386; 510; 604; 649; 125; 994; 379; 309; 632; 503; 812; 358; 513; 145; 569; 213; 519; 638; 40; 311; 578; 761; 22; 785; 168; 543; 973; 809; 789; 388; 41; 969; 464; 624; 514; 166; 328; 912; 706; 540; 657; 735; 367; 776; 25; 106; 449; 758; 897; 869; 369; 748; 89; 680; 439; 144; 480; 488; 130; 204; 479; 581; 573; 167; 497; 962; 385; 339; 597; 807; 851; 463; 706; 481; 408; 761; 688; 14; 432; 854; 981; 890; 222; 792; 616; 395|];
+[| 830; 656; 853; 928; 408; 355; 638; 646; 180; 301; 783; 257; 149; 571; 443; 522; 179; 321; 926; 223; 557; 238; 18; 165; 805; 410; 203; 567; 549; 201; 841; 511; 342; 41; 80; 561; 759; 481; 704; 506; 645; 316; 59; 984; 337; 786; 831; 474; 971; 90; 293; 531; 568; 846; 588; 160; 308; 837; 683; 105; 24; 791; 859; 228; 280; 604; 627; 223; 781; 709; 376; 777; 886; 73; 268; 177; 101; 354; 817; 75; 181; 927; 26; 633; 505; 660; 361; 165; 153; 516; 606; 925; 311; 169; 130; 213; 381; 989; 772; 427|];
+[| 702; 358; 467; 189; 170; 5; 768; 288; 103; 3; 155; 754; 312; 179; 645; 54; 750; 589; 618; 950; 417; 586; 382; 539; 970; 987; 684; 516; 307; 777; 779; 734; 891; 508; 596; 278; 816; 106; 320; 526; 33; 245; 543; 900; 854; 475; 207; 482; 377; 992; 440; 939; 446; 799; 70; 491; 624; 557; 787; 991; 859; 658; 39; 802; 952; 898; 829; 848; 287; 268; 57; 279; 746; 899; 782; 487; 368; 715; 945; 93; 198; 510; 859; 941; 815; 96; 831; 822; 937; 414; 197; 592; 919; 478; 507; 701; 919; 794; 301; 548|];
+[| 176; 43; 493; 300; 286; 807; 706; 313; 629; 560; 329; 558; 722; 315; 638; 12; 684; 144; 657; 107; 456; 690; 371; 526; 917; 699; 962; 792; 640; 755; 136; 734; 348; 601; 823; 73; 384; 240; 186; 187; 929; 718; 349; 981; 74; 290; 703; 846; 509; 906; 652; 762; 872; 556; 928; 461; 334; 619; 114; 719; 828; 449; 596; 616; 92; 278; 403; 770; 284; 525; 649; 102; 316; 493; 691; 726; 413; 283; 671; 906; 935; 988; 409; 476; 276; 890; 572; 500; 111; 559; 126; 666; 754; 601; 144; 445; 912; 145; 645; 367|];
+[| 557; 336; 787; 429; 932; 959; 980; 109; 975; 72; 248; 841; 668; 586; 504; 672; 428; 582; 815; 290; 343; 860; 270; 872; 413; 498; 656; 138; 550; 204; 981; 782; 605; 231; 368; 519; 175; 639; 382; 307; 413; 609; 0; 240; 725; 226; 163; 503; 825; 869; 535; 362; 712; 980; 178; 995; 292; 251; 542; 175; 546; 157; 172; 743; 625; 16; 664; 911; 42; 464; 801; 313; 508; 998; 964; 284; 894; 852; 577; 992; 780; 88; 39; 504; 302; 336; 432; 888; 724; 435; 783; 595; 110; 155; 997; 618; 269; 451; 358; 750|];
+[| 195; 674; 151; 665; 281; 824; 780; 877; 977; 655; 329; 780; 789; 810; 381; 972; 731; 496; 341; 301; 50; 936; 36; 334; 758; 730; 735; 330; 107; 847; 973; 256; 416; 686; 947; 377; 580; 38; 667; 366; 670; 107; 416; 687; 425; 16; 894; 269; 57; 439; 369; 998; 388; 632; 643; 718; 733; 17; 46; 362; 165; 111; 925; 25; 224; 948; 576; 425; 505; 872; 72; 782; 639; 874; 318; 848; 591; 459; 340; 943; 139; 220; 790; 325; 219; 657; 45; 781; 825; 15; 174; 773; 374; 525; 257; 437; 82; 395; 241; 580|];
+[| 522; 210; 868; 79; 613; 801; 455; 715; 266; 198; 240; 957; 860; 523; 69; 908; 664; 393; 647; 61; 211; 462; 584; 262; 353; 997; 467; 983; 192; 568; 647; 315; 214; 516; 147; 576; 992; 552; 997; 116; 670; 277; 817; 310; 286; 603; 614; 192; 894; 841; 411; 971; 403; 510; 673; 905; 146; 444; 850; 802; 239; 330; 732; 791; 407; 418; 98; 314; 662; 226; 89; 187; 811; 45; 698; 937; 110; 819; 664; 298; 373; 471; 932; 26; 787; 430; 811; 424; 135; 593; 55; 863; 588; 596; 773; 81; 15; 397; 998; 73|];
+[| 902; 102; 761; 737; 520; 943; 576; 22; 694; 901; 702; 791; 333; 53; 726; 686; 175; 637; 374; 953; 737; 217; 641; 204; 397; 583; 658; 900; 432; 303; 6; 810; 417; 264; 752; 828; 221; 843; 971; 29; 915; 779; 686; 974; 204; 727; 799; 439; 497; 146; 29; 737; 883; 410; 195; 783; 705; 91; 856; 761; 639; 622; 575; 528; 533; 826; 764; 983; 673; 749; 884; 145; 419; 912; 716; 183; 361; 398; 217; 66; 254; 853; 914; 867; 736; 538; 74; 29; 457; 562; 39; 442; 416; 212; 594; 13; 137; 159; 798; 200|];
+[| 806; 464; 233; 550; 895; 229; 19; 6; 462; 976; 896; 643; 318; 620; 109; 758; 4; 618; 498; 751; 114; 471; 965; 354; 309; 372; 488; 991; 369; 15; 464; 110; 641; 89; 921; 852; 86; 136; 387; 281; 294; 142; 704; 432; 188; 197; 842; 169; 504; 173; 299; 927; 764; 104; 472; 468; 497; 303; 886; 486; 68; 794; 41; 448; 206; 550; 669; 863; 35; 461; 505; 282; 551; 972; 58; 939; 56; 134; 701; 288; 286; 109; 427; 996; 605; 919; 754; 443; 690; 31; 30; 395; 793; 128; 332; 393; 901; 755; 63; 806|];
+[| 360; 305; 726; 670; 640; 860; 408; 962; 803; 189; 524; 424; 327; 959; 401; 975; 456; 65; 73; 20; 948; 928; 206; 629; 280; 283; 633; 66; 288; 398; 431; 396; 583; 544; 201; 417; 793; 934; 239; 166; 890; 76; 288; 543; 614; 275; 500; 664; 723; 878; 419; 121; 653; 180; 393; 283; 9; 347; 416; 381; 782; 733; 337; 506; 567; 334; 802; 560; 323; 70; 861; 74; 331; 880; 180; 546; 875; 202; 378; 659; 246; 84; 160; 174; 777; 805; 680; 387; 360; 781; 77; 673; 646; 297; 631; 780; 805; 642; 986; 168|];
+[| 438; 909; 393; 147; 68; 794; 310; 353; 785; 259; 443; 618; 838; 482; 31; 53; 183; 222; 874; 134; 363; 687; 807; 870; 534; 327; 195; 442; 715; 619; 458; 790; 459; 45; 163; 579; 834; 631; 743; 759; 210; 262; 182; 323; 633; 820; 162; 502; 546; 442; 771; 519; 186; 154; 663; 80; 295; 638; 810; 435; 392; 280; 64; 700; 34; 960; 43; 280; 648; 939; 372; 818; 63; 276; 622; 992; 210; 32; 340; 543; 112; 7; 747; 148; 751; 542; 269; 973; 559; 842; 986; 358; 621; 757; 821; 573; 313; 404; 364; 235|];
+[| 995; 102; 157; 156; 393; 342; 601; 521; 188; 365; 398; 572; 435; 444; 482; 147; 697; 225; 78; 684; 788; 902; 715; 434; 410; 234; 189; 838; 84; 809; 249; 612; 973; 845; 708; 532; 310; 423; 115; 918; 224; 318; 96; 677; 663; 437; 964; 598; 256; 762; 185; 388; 881; 681; 274; 661; 87; 487; 957; 488; 718; 156; 305; 929; 694; 681; 873; 193; 948; 586; 449; 837; 666; 963; 612; 16; 11; 14; 80; 351; 882; 778; 348; 588; 268; 322; 857; 808; 66; 485; 932; 872; 131; 130; 542; 861; 637; 652; 925; 678|];
+[| 399; 883; 908; 844; 371; 977; 645; 68; 142; 437; 102; 642; 713; 580; 220; 644; 573; 860; 644; 961; 855; 399; 631; 548; 516; 48; 419; 387; 445; 0; 139; 15; 949; 137; 66; 682; 583; 210; 82; 238; 887; 327; 754; 392; 458; 241; 591; 77; 283; 543; 497; 71; 297; 571; 897; 553; 788; 668; 687; 742; 724; 656; 563; 824; 672; 501; 297; 230; 867; 399; 174; 863; 736; 281; 859; 564; 64; 576; 780; 706; 83; 863; 416; 818; 882; 825; 554; 522; 846; 100; 619; 409; 858; 106; 604; 358; 84; 469; 907; 945|];
+[| 479; 776; 514; 556; 70; 130; 285; 164; 743; 683; 949; 848; 944; 465; 532; 668; 423; 448; 377; 321; 126; 183; 670; 420; 623; 718; 663; 314; 346; 963; 261; 806; 532; 700; 279; 854; 560; 826; 670; 627; 55; 401; 600; 489; 988; 131; 854; 810; 432; 620; 401; 602; 644; 128; 16; 285; 851; 267; 757; 599; 640; 576; 367; 286; 841; 547; 953; 240; 422; 287; 866; 268; 508; 448; 218; 691; 225; 387; 896; 661; 792; 592; 512; 663; 879; 107; 939; 597; 442; 86; 622; 180; 920; 124; 779; 768; 842; 186; 510; 940|];
+[| 92; 218; 621; 633; 308; 71; 148; 12; 914; 219; 487; 357; 172; 917; 410; 357; 790; 253; 588; 338; 476; 899; 347; 804; 221; 382; 438; 618; 739; 118; 785; 672; 565; 288; 439; 701; 870; 771; 178; 968; 279; 461; 37; 443; 536; 346; 454; 284; 105; 315; 260; 619; 794; 182; 339; 611; 722; 300; 650; 90; 344; 540; 959; 296; 565; 162; 535; 920; 155; 352; 979; 410; 419; 686; 133; 537; 528; 263; 257; 155; 457; 647; 288; 629; 540; 87; 715; 498; 727; 642; 965; 513; 242; 382; 188; 279; 711; 925; 797; 271|];
+[| 345; 580; 999; 63; 651; 525; 771; 954; 946; 536; 360; 649; 170; 743; 628; 171; 103; 71; 425; 209; 292; 528; 319; 501; 420; 666; 430; 50; 49; 824; 402; 975; 198; 798; 948; 975; 523; 729; 300; 115; 950; 722; 999; 325; 727; 763; 290; 977; 927; 498; 305; 558; 800; 346; 732; 828; 559; 352; 986; 94; 774; 216; 572; 258; 632; 639; 789; 847; 686; 76; 911; 227; 714; 914; 764; 263; 203; 867; 154; 815; 121; 533; 113; 833; 137; 491; 473; 434; 971; 183; 797; 546; 366; 673; 109; 914; 117; 250; 922; 973|];
+[| 983; 801; 354; 120; 648; 917; 306; 406; 995; 22; 512; 442; 767; 755; 194; 412; 329; 941; 382; 489; 305; 904; 573; 189; 197; 25; 109; 693; 54; 127; 418; 872; 68; 649; 876; 595; 8; 635; 31; 14; 151; 755; 382; 986; 584; 493; 202; 854; 836; 116; 840; 869; 910; 634; 911; 659; 396; 910; 788; 172; 289; 20; 398; 897; 765; 249; 779; 710; 553; 235; 955; 626; 201; 639; 788; 149; 856; 214; 126; 296; 74; 674; 303; 130; 416; 736; 410; 725; 175; 753; 632; 376; 270; 114; 294; 282; 59; 793; 495; 757|];
+[| 787; 11; 493; 807; 254; 33; 848; 99; 676; 476; 862; 990; 627; 308; 709; 188; 459; 327; 95; 835; 782; 191; 864; 618; 310; 917; 401; 925; 184; 306; 830; 439; 7; 501; 58; 874; 268; 482; 27; 56; 183; 874; 167; 101; 467; 41; 508; 64; 133; 292; 491; 517; 49; 673; 525; 862; 224; 789; 42; 810; 906; 637; 305; 636; 91; 663; 759; 437; 632; 706; 590; 371; 87; 263; 926; 346; 594; 645; 399; 981; 571; 228; 24; 438; 947; 670; 348; 496; 435; 581; 300; 637; 757; 695; 317; 883; 706; 226; 210; 445|];
+[| 958; 461; 56; 421; 682; 860; 811; 823; 348; 292; 232; 942; 221; 490; 150; 805; 386; 379; 576; 527; 295; 934; 316; 679; 298; 676; 215; 142; 223; 879; 1; 524; 375; 928; 180; 402; 912; 341; 83; 228; 785; 505; 485; 376; 538; 897; 44; 468; 378; 145; 355; 589; 498; 502; 100; 239; 608; 789; 738; 571; 762; 831; 107; 905; 856; 209; 744; 53; 334; 951; 313; 924; 943; 528; 738; 56; 283; 456; 696; 478; 722; 649; 360; 147; 121; 522; 699; 662; 227; 79; 147; 267; 572; 756; 705; 891; 171; 595; 544; 130|];
+[| 220; 901; 493; 966; 413; 676; 45; 552; 315; 309; 745; 997; 181; 231; 608; 761; 782; 280; 377; 707; 520; 711; 888; 966; 787; 812; 924; 909; 486; 704; 409; 222; 176; 780; 409; 943; 251; 151; 910; 552; 192; 388; 700; 690; 326; 453; 552; 421; 803; 602; 465; 698; 462; 763; 340; 400; 951; 757; 890; 270; 861; 860; 983; 749; 317; 922; 467; 305; 873; 229; 186; 365; 770; 270; 197; 42; 742; 539; 809; 425; 622; 43; 256; 457; 45; 233; 798; 152; 975; 968; 912; 732; 206; 53; 235; 669; 897; 393; 489; 269|];
+[| 504; 536; 873; 35; 906; 508; 877; 548; 363; 549; 963; 945; 817; 68; 106; 302; 741; 237; 15; 403; 679; 193; 475; 571; 459; 737; 280; 255; 5; 766; 63; 306; 521; 61; 33; 334; 581; 372; 861; 187; 591; 852; 149; 137; 138; 353; 961; 192; 931; 633; 299; 558; 944; 821; 176; 25; 983; 14; 728; 287; 898; 311; 520; 963; 505; 210; 611; 856; 129; 686; 870; 361; 432; 822; 946; 518; 554; 58; 775; 988; 514; 637; 603; 81; 708; 684; 759; 115; 262; 68; 472; 78; 133; 36; 256; 576; 930; 711; 823; 424|];
+[| 353; 407; 869; 653; 839; 201; 930; 469; 413; 980; 216; 990; 332; 247; 825; 504; 351; 329; 7; 656; 766; 87; 335; 780; 420; 185; 440; 848; 573; 458; 662; 289; 390; 824; 831; 604; 618; 569; 272; 947; 37; 865; 402; 442; 484; 869; 621; 448; 73; 862; 154; 180; 437; 148; 817; 139; 79; 252; 603; 962; 530; 143; 519; 292; 661; 77; 851; 193; 706; 901; 710; 595; 905; 753; 771; 924; 278; 913; 161; 504; 619; 146; 42; 903; 133; 740; 805; 594; 93; 605; 76; 751; 785; 519; 416; 855; 593; 27; 948; 715|];
+[| 436; 944; 136; 790; 478; 878; 349; 862; 428; 542; 404; 958; 576; 185; 364; 719; 776; 427; 509; 415; 647; 393; 801; 473; 699; 945; 966; 261; 731; 738; 883; 180; 267; 543; 369; 336; 2; 678; 768; 999; 482; 559; 965; 743; 230; 282; 173; 413; 873; 516; 442; 599; 456; 623; 355; 87; 492; 294; 694; 910; 910; 479; 250; 679; 975; 425; 946; 57; 461; 690; 82; 847; 707; 480; 17; 658; 143; 984; 585; 786; 368; 787; 107; 715; 411; 75; 475; 846; 633; 469; 23; 635; 645; 766; 818; 723; 197; 560; 6; 869|];
+[| 529; 1; 434; 535; 110; 21; 521; 976; 468; 45; 988; 859; 426; 359; 861; 430; 256; 965; 908; 323; 559; 548; 386; 41; 484; 161; 816; 619; 12; 348; 659; 845; 183; 354; 849; 11; 69; 816; 677; 305; 395; 990; 641; 936; 976; 559; 810; 218; 309; 167; 747; 747; 288; 945; 702; 411; 609; 571; 789; 308; 33; 687; 52; 228; 274; 152; 704; 114; 422; 694; 922; 856; 754; 492; 425; 94; 944; 371; 96; 509; 640; 554; 387; 481; 76; 202; 437; 279; 513; 91; 529; 46; 346; 65; 136; 314; 733; 184; 826; 29|];
+[| 96; 760; 913; 599; 849; 413; 885; 848; 867; 877; 518; 17; 17; 716; 312; 333; 56; 231; 611; 761; 35; 913; 993; 847; 516; 584; 671; 889; 85; 971; 701; 855; 266; 516; 683; 485; 803; 698; 183; 494; 209; 444; 381; 714; 840; 733; 258; 757; 813; 581; 182; 700; 390; 918; 281; 194; 150; 818; 350; 33; 764; 815; 203; 51; 683; 608; 828; 563; 48; 835; 580; 770; 697; 870; 331; 373; 738; 265; 137; 895; 860; 753; 314; 575; 43; 560; 374; 696; 341; 840; 398; 821; 658; 917; 738; 846; 847; 937; 203; 500|];
+[| 652; 815; 235; 964; 317; 475; 299; 195; 815; 941; 199; 15; 277; 877; 683; 958; 713; 974; 379; 866; 789; 143; 556; 291; 202; 413; 868; 768; 572; 705; 525; 674; 906; 898; 814; 729; 730; 859; 515; 132; 978; 220; 460; 351; 293; 64; 579; 857; 626; 535; 751; 368; 629; 437; 209; 550; 123; 299; 593; 881; 150; 708; 389; 179; 223; 324; 196; 893; 362; 108; 160; 354; 522; 667; 876; 404; 410; 308; 156; 586; 969; 757; 311; 639; 342; 751; 702; 588; 311; 885; 707; 364; 488; 722; 315; 127; 70; 900; 927; 465|];
+[| 344; 464; 498; 130; 440; 992; 394; 695; 899; 821; 18; 732; 215; 438; 3; 135; 369; 433; 763; 541; 398; 751; 959; 766; 541; 672; 668; 47; 297; 632; 144; 722; 807; 940; 207; 582; 401; 755; 864; 425; 483; 233; 889; 780; 249; 844; 10; 304; 302; 194; 188; 740; 180; 749; 997; 591; 370; 926; 873; 49; 314; 603; 284; 403; 416; 147; 667; 608; 987; 54; 152; 928; 28; 389; 338; 30; 213; 390; 65; 631; 317; 219; 709; 298; 803; 88; 655; 933; 102; 530; 994; 988; 650; 445; 345; 152; 528; 898; 178; 596|];
+[| 400; 844; 91; 574; 583; 793; 852; 997; 477; 28; 359; 268; 361; 904; 447; 194; 353; 534; 475; 428; 866; 399; 438; 296; 141; 784; 284; 329; 752; 146; 202; 144; 893; 38; 89; 490; 422; 25; 631; 480; 242; 93; 814; 753; 518; 657; 985; 555; 912; 351; 822; 620; 165; 190; 439; 22; 978; 449; 381; 612; 200; 814; 128; 7; 984; 567; 249; 567; 242; 563; 962; 55; 698; 998; 985; 437; 820; 20; 330; 910; 361; 349; 23; 41; 209; 175; 14; 442; 507; 664; 444; 523; 242; 783; 8; 695; 74; 273; 699; 73|];
+[| 342; 441; 468; 889; 653; 509; 303; 147; 686; 236; 894; 696; 648; 36; 578; 980; 122; 766; 602; 251; 812; 627; 447; 434; 818; 828; 808; 48; 814; 384; 581; 389; 782; 656; 725; 986; 455; 729; 568; 878; 226; 154; 967; 165; 405; 52; 253; 6; 405; 936; 169; 269; 539; 960; 536; 658; 20; 619; 993; 79; 415; 554; 354; 630; 277; 221; 134; 420; 180; 495; 603; 582; 807; 51; 115; 624; 586; 658; 822; 472; 895; 648; 246; 538; 987; 107; 703; 455; 95; 270; 93; 999; 373; 21; 383; 174; 504; 522; 494; 446|];
+[| 499; 522; 97; 291; 10; 370; 278; 82; 316; 156; 359; 931; 506; 767; 237; 345; 23; 916; 274; 782; 885; 566; 525; 603; 659; 768; 696; 344; 663; 706; 394; 490; 936; 518; 127; 914; 650; 757; 830; 578; 139; 128; 915; 675; 704; 76; 862; 542; 435; 404; 155; 239; 471; 306; 428; 186; 421; 578; 997; 900; 905; 455; 574; 168; 853; 360; 853; 857; 421; 75; 405; 631; 719; 664; 183; 91; 565; 623; 961; 101; 302; 755; 960; 625; 884; 165; 399; 389; 835; 688; 296; 356; 923; 250; 638; 854; 38; 222; 872; 372|];
+[| 694; 678; 352; 71; 197; 336; 610; 161; 796; 657; 16; 223; 914; 857; 34; 876; 146; 268; 948; 545; 858; 613; 479; 795; 413; 812; 779; 463; 577; 477; 994; 617; 577; 453; 987; 804; 576; 605; 968; 810; 662; 681; 866; 31; 575; 162; 707; 505; 417; 937; 645; 739; 956; 217; 635; 794; 48; 806; 163; 572; 907; 110; 648; 619; 3; 434; 272; 938; 492; 986; 91; 892; 102; 268; 725; 540; 403; 805; 940; 87; 250; 161; 238; 781; 583; 767; 556; 951; 678; 382; 414; 257; 313; 411; 752; 128; 309; 265; 627; 820|];
+[| 345; 397; 269; 50; 594; 928; 501; 732; 14; 474; 144; 16; 55; 264; 421; 676; 71; 793; 795; 370; 128; 600; 89; 758; 646; 880; 762; 0; 679; 844; 401; 606; 168; 994; 21; 71; 540; 183; 245; 900; 295; 198; 794; 720; 642; 453; 44; 673; 760; 98; 797; 339; 50; 22; 658; 561; 924; 799; 936; 997; 641; 398; 32; 714; 154; 811; 801; 193; 626; 646; 642; 394; 270; 660; 266; 466; 732; 440; 915; 324; 745; 343; 685; 418; 202; 260; 996; 536; 156; 119; 202; 170; 741; 947; 883; 424; 856; 900; 524; 753|];
+[| 147; 529; 528; 714; 427; 418; 575; 657; 63; 695; 149; 724; 605; 99; 574; 948; 886; 159; 465; 836; 483; 330; 804; 447; 133; 214; 680; 517; 162; 710; 15; 29; 729; 627; 845; 307; 572; 642; 708; 111; 519; 551; 415; 634; 379; 272; 400; 871; 729; 489; 377; 796; 514; 337; 816; 455; 850; 438; 753; 705; 126; 345; 793; 607; 875; 279; 770; 406; 421; 41; 101; 713; 701; 865; 526; 461; 130; 98; 634; 354; 238; 281; 964; 524; 924; 972; 392; 797; 594; 388; 822; 436; 801; 287; 311; 401; 821; 501; 938; 654|];
+[| 376; 371; 406; 978; 954; 794; 384; 994; 770; 670; 281; 274; 955; 836; 178; 132; 651; 471; 728; 429; 881; 970; 28; 259; 852; 897; 106; 375; 805; 129; 395; 745; 706; 885; 615; 517; 194; 564; 244; 357; 576; 944; 188; 682; 528; 673; 118; 39; 480; 374; 255; 66; 676; 494; 874; 610; 128; 547; 505; 784; 293; 182; 186; 938; 568; 378; 817; 28; 128; 962; 953; 583; 728; 521; 681; 939; 284; 52; 77; 130; 788; 984; 877; 611; 608; 997; 304; 473; 413; 535; 580; 606; 12; 397; 79; 36; 954; 46; 663; 421|];
+[| 951; 659; 998; 554; 567; 889; 275; 285; 514; 492; 274; 923; 400; 170; 438; 824; 745; 447; 868; 807; 13; 695; 533; 130; 816; 794; 72; 879; 547; 557; 129; 962; 942; 440; 643; 803; 722; 611; 127; 431; 864; 86; 553; 81; 78; 653; 236; 14; 970; 587; 205; 42; 548; 74; 389; 275; 62; 834; 933; 173; 578; 702; 512; 423; 962; 51; 926; 80; 228; 526; 331; 554; 681; 109; 67; 90; 148; 586; 511; 576; 339; 590; 962; 905; 364; 729; 3; 362; 46; 871; 159; 434; 96; 625; 180; 999; 373; 979; 118; 246|];
+[| 476; 285; 124; 996; 479; 320; 826; 304; 230; 580; 96; 50; 194; 263; 457; 4; 100; 182; 22; 235; 112; 129; 24; 991; 105; 933; 298; 89; 813; 414; 264; 99; 323; 374; 62; 955; 321; 256; 866; 489; 606; 805; 759; 521; 286; 346; 613; 44; 503; 324; 669; 720; 323; 926; 185; 203; 130; 834; 448; 698; 240; 912; 343; 285; 225; 597; 451; 831; 175; 976; 504; 931; 135; 168; 747; 927; 471; 636; 265; 765; 266; 728; 918; 798; 775; 954; 245; 28; 159; 633; 835; 580; 490; 80; 749; 838; 788; 331; 964; 998|];
+[| 106; 377; 538; 81; 704; 880; 743; 661; 900; 300; 753; 827; 918; 251; 993; 803; 50; 815; 662; 520; 756; 92; 304; 673; 554; 40; 868; 732; 481; 742; 689; 597; 481; 638; 566; 984; 418; 403; 431; 599; 932; 899; 917; 509; 74; 418; 216; 939; 312; 438; 572; 440; 775; 796; 747; 142; 407; 226; 951; 862; 4; 353; 469; 530; 237; 170; 640; 514; 432; 927; 552; 15; 428; 671; 806; 541; 594; 539; 53; 729; 701; 778; 624; 386; 749; 227; 87; 642; 68; 524; 435; 91; 688; 494; 378; 164; 464; 70; 78; 73|];
+[| 445; 32; 479; 771; 252; 849; 126; 433; 634; 465; 860; 355; 999; 663; 987; 451; 290; 895; 238; 764; 157; 865; 518; 701; 372; 936; 570; 176; 900; 607; 686; 710; 524; 346; 359; 433; 763; 832; 103; 973; 795; 922; 467; 140; 603; 7; 876; 686; 681; 921; 641; 680; 916; 396; 465; 992; 759; 424; 204; 587; 728; 167; 458; 408; 995; 3; 378; 589; 809; 866; 313; 331; 396; 301; 968; 265; 930; 51; 533; 340; 539; 300; 579; 902; 444; 832; 465; 250; 948; 806; 805; 185; 961; 582; 712; 721; 541; 975; 714; 940|];
+[| 382; 165; 944; 167; 9; 292; 885; 983; 865; 16; 543; 385; 879; 563; 154; 161; 564; 365; 825; 25; 186; 147; 627; 563; 486; 695; 826; 402; 244; 108; 299; 487; 323; 496; 696; 260; 782; 951; 735; 351; 429; 933; 660; 647; 528; 32; 363; 662; 762; 767; 310; 891; 643; 379; 282; 149; 917; 326; 680; 541; 479; 490; 782; 630; 607; 395; 687; 285; 691; 507; 320; 154; 855; 220; 219; 771; 811; 667; 482; 508; 57; 624; 860; 286; 807; 254; 37; 110; 344; 769; 668; 65; 512; 815; 59; 909; 151; 266; 112; 765|];
+[| 126; 267; 336; 350; 819; 322; 862; 547; 730; 497; 678; 237; 867; 199; 128; 900; 665; 555; 227; 746; 130; 796; 308; 669; 802; 420; 736; 496; 949; 751; 785; 37; 918; 389; 233; 465; 685; 958; 879; 821; 916; 681; 646; 333; 412; 381; 230; 851; 339; 564; 16; 396; 201; 169; 280; 661; 622; 589; 929; 112; 958; 282; 19; 588; 309; 214; 32; 110; 336; 145; 46; 953; 888; 307; 960; 830; 508; 258; 987; 17; 320; 396; 384; 965; 287; 309; 641; 452; 456; 162; 8; 493; 84; 79; 407; 239; 555; 105; 689; 8|];
+[| 339; 32; 682; 209; 73; 135; 733; 71; 304; 819; 376; 913; 301; 696; 181; 47; 168; 454; 985; 207; 891; 469; 287; 639; 455; 994; 496; 508; 188; 257; 738; 442; 329; 654; 70; 408; 982; 855; 977; 354; 577; 348; 628; 933; 859; 74; 505; 964; 436; 884; 272; 370; 805; 696; 72; 950; 502; 254; 872; 199; 203; 361; 200; 869; 879; 652; 460; 928; 549; 286; 857; 768; 968; 48; 515; 632; 830; 288; 71; 874; 477; 531; 720; 738; 254; 802; 205; 824; 929; 522; 430; 12; 298; 568; 652; 457; 458; 407; 428; 933|];
+[| 579; 840; 571; 445; 666; 467; 796; 198; 783; 39; 56; 920; 397; 495; 751; 855; 404; 511; 968; 788; 965; 137; 507; 630; 949; 472; 841; 195; 772; 101; 652; 242; 376; 326; 495; 801; 220; 473; 109; 746; 915; 362; 460; 269; 635; 427; 184; 821; 664; 9; 190; 125; 435; 327; 261; 835; 80; 732; 784; 63; 297; 689; 497; 728; 953; 346; 867; 443; 981; 808; 979; 868; 137; 570; 964; 456; 331; 525; 702; 630; 811; 60; 766; 489; 507; 1; 762; 75; 306; 957; 143; 576; 424; 928; 442; 567; 170; 910; 325; 3|];
+[| 638; 927; 498; 668; 641; 288; 781; 73; 86; 947; 190; 175; 945; 946; 180; 450; 197; 590; 503; 292; 588; 399; 192; 620; 275; 294; 690; 714; 590; 827; 986; 871; 301; 834; 32; 240; 344; 853; 615; 522; 364; 617; 319; 578; 920; 433; 32; 182; 727; 234; 813; 188; 965; 630; 625; 136; 464; 74; 138; 750; 762; 164; 822; 233; 895; 68; 665; 592; 526; 450; 287; 6; 117; 465; 211; 166; 379; 572; 117; 235; 62; 33; 416; 816; 755; 175; 665; 110; 334; 59; 341; 947; 45; 129; 175; 871; 334; 140; 739; 342|];
+[| 175; 997; 699; 604; 787; 270; 696; 608; 544; 923; 843; 973; 379; 378; 308; 919; 990; 144; 99; 148; 480; 925; 561; 432; 732; 484; 78; 232; 999; 492; 41; 943; 705; 642; 245; 472; 81; 645; 706; 398; 850; 233; 939; 809; 63; 799; 600; 793; 339; 329; 134; 701; 907; 157; 403; 724; 43; 233; 588; 150; 399; 868; 437; 960; 354; 875; 489; 325; 734; 617; 282; 611; 218; 187; 654; 684; 99; 756; 865; 668; 817; 721; 609; 366; 711; 948; 179; 287; 410; 488; 268; 317; 208; 755; 74; 515; 140; 561; 879; 557|];
+[| 913; 32; 439; 17; 851; 939; 738; 930; 67; 865; 920; 377; 0; 151; 530; 815; 141; 795; 711; 478; 631; 891; 816; 276; 780; 375; 491; 689; 1; 949; 693; 372; 379; 147; 930; 377; 154; 797; 317; 760; 699; 909; 702; 257; 660; 838; 614; 853; 369; 92; 977; 431; 958; 928; 94; 126; 228; 35; 523; 13; 1; 757; 376; 317; 935; 63; 354; 972; 437; 524; 743; 327; 48; 749; 796; 457; 966; 291; 73; 793; 302; 163; 955; 685; 911; 978; 221; 261; 282; 918; 136; 540; 556; 80; 399; 568; 444; 400; 847; 759|];
+[| 832; 685; 786; 622; 862; 855; 796; 721; 64; 497; 849; 247; 396; 453; 746; 146; 38; 928; 252; 422; 848; 923; 300; 366; 64; 546; 4; 53; 224; 420; 382; 414; 599; 609; 524; 323; 554; 805; 120; 536; 326; 994; 778; 42; 31; 543; 371; 975; 627; 879; 163; 709; 386; 317; 501; 649; 267; 4; 899; 306; 433; 246; 641; 201; 17; 601; 910; 379; 751; 800; 58; 486; 71; 126; 270; 773; 985; 624; 945; 997; 183; 546; 368; 638; 611; 9; 489; 889; 754; 878; 626; 623; 713; 898; 232; 962; 156; 893; 618; 960|];
+[| 767; 32; 809; 532; 706; 675; 868; 645; 184; 865; 647; 415; 214; 585; 864; 236; 840; 90; 153; 704; 228; 491; 294; 562; 176; 403; 695; 42; 598; 359; 156; 368; 723; 268; 788; 579; 362; 910; 548; 595; 475; 768; 761; 100; 247; 917; 929; 34; 836; 961; 233; 245; 830; 901; 25; 283; 851; 454; 899; 713; 660; 141; 253; 937; 895; 268; 890; 440; 0; 918; 403; 39; 193; 492; 15; 578; 70; 196; 110; 684; 686; 111; 718; 753; 421; 155; 626; 110; 98; 14; 85; 264; 978; 721; 366; 999; 763; 226; 147; 123|];
+[| 530; 640; 608; 55; 368; 63; 850; 645; 261; 546; 523; 812; 572; 991; 73; 244; 940; 92; 40; 994; 841; 911; 317; 488; 156; 651; 808; 908; 856; 380; 530; 869; 406; 947; 375; 879; 689; 601; 412; 664; 295; 194; 958; 291; 975; 229; 763; 756; 992; 395; 555; 227; 564; 261; 797; 512; 665; 222; 579; 487; 523; 45; 469; 530; 668; 726; 93; 634; 133; 587; 356; 241; 921; 275; 963; 280; 596; 755; 881; 565; 580; 771; 964; 139; 568; 287; 571; 495; 535; 971; 684; 103; 340; 492; 175; 548; 433; 111; 380; 782|];
+[| 278; 234; 678; 37; 87; 23; 534; 544; 295; 324; 995; 24; 224; 663; 991; 985; 419; 327; 808; 864; 63; 986; 115; 363; 53; 12; 506; 780; 143; 579; 617; 301; 668; 919; 674; 42; 841; 165; 592; 981; 249; 896; 120; 914; 62; 559; 383; 8; 942; 320; 995; 475; 865; 102; 61; 928; 188; 131; 201; 509; 14; 276; 818; 262; 665; 565; 648; 520; 155; 26; 973; 611; 84; 820; 826; 969; 597; 632; 894; 508; 140; 174; 969; 128; 630; 322; 376; 826; 934; 321; 820; 9; 292; 597; 877; 528; 383; 629; 167; 981|];
+[| 705; 778; 774; 752; 636; 716; 3; 310; 428; 231; 151; 115; 419; 840; 297; 957; 277; 243; 403; 390; 921; 180; 513; 79; 836; 349; 654; 150; 423; 127; 544; 211; 66; 943; 302; 495; 12; 110; 409; 464; 815; 690; 13; 501; 744; 916; 774; 657; 44; 466; 556; 349; 675; 759; 383; 11; 517; 450; 803; 261; 897; 690; 39; 494; 47; 87; 569; 979; 96; 679; 113; 35; 232; 975; 630; 58; 100; 515; 600; 197; 176; 894; 78; 634; 590; 409; 694; 944; 258; 294; 430; 885; 784; 980; 605; 365; 63; 404; 617; 80|];
+[| 108; 779; 607; 49; 384; 492; 585; 647; 223; 760; 632; 221; 353; 992; 970; 440; 920; 956; 623; 764; 198; 125; 883; 55; 475; 749; 202; 957; 940; 203; 796; 65; 888; 486; 659; 890; 924; 68; 174; 649; 581; 911; 140; 863; 186; 826; 664; 764; 608; 830; 539; 531; 922; 780; 297; 208; 813; 15; 764; 28; 88; 180; 506; 499; 730; 535; 261; 923; 509; 814; 931; 433; 995; 275; 740; 655; 148; 692; 80; 339; 389; 551; 994; 179; 335; 54; 357; 737; 878; 539; 872; 907; 837; 423; 355; 852; 592; 973; 888; 400|];
+[| 963; 523; 230; 25; 32; 421; 761; 289; 497; 136; 430; 47; 472; 370; 906; 984; 438; 19; 782; 354; 584; 941; 251; 400; 964; 560; 875; 875; 466; 184; 787; 378; 21; 599; 566; 155; 521; 844; 82; 405; 490; 569; 862; 871; 520; 74; 330; 548; 851; 509; 593; 289; 976; 111; 224; 757; 874; 280; 464; 869; 628; 878; 46; 795; 915; 787; 396; 772; 958; 132; 786; 943; 134; 726; 645; 22; 125; 239; 282; 362; 7; 145; 863; 182; 118; 459; 842; 638; 709; 743; 48; 391; 672; 753; 873; 638; 335; 942; 266; 981|];
+[| 845; 783; 25; 670; 930; 50; 50; 245; 395; 681; 580; 441; 915; 592; 47; 710; 353; 899; 26; 367; 563; 955; 524; 665; 520; 517; 389; 881; 369; 25; 251; 879; 22; 373; 368; 269; 0; 48; 724; 399; 819; 781; 933; 369; 822; 552; 873; 215; 329; 152; 243; 943; 308; 643; 263; 960; 348; 379; 178; 194; 264; 583; 365; 63; 220; 521; 842; 415; 946; 703; 721; 199; 664; 937; 110; 703; 903; 271; 705; 643; 176; 104; 821; 496; 102; 142; 747; 538; 392; 515; 830; 191; 52; 318; 6; 471; 911; 945; 288; 868|];
+[| 744; 863; 56; 970; 520; 556; 984; 397; 781; 886; 430; 73; 763; 356; 57; 979; 954; 320; 975; 983; 757; 879; 691; 12; 615; 308; 725; 905; 924; 884; 46; 863; 289; 201; 950; 31; 95; 908; 970; 431; 21; 772; 373; 292; 558; 887; 66; 886; 194; 919; 644; 224; 166; 803; 274; 115; 153; 670; 353; 343; 487; 989; 597; 182; 349; 666; 846; 610; 357; 457; 966; 913; 782; 90; 593; 663; 635; 331; 138; 603; 161; 128; 718; 855; 648; 49; 526; 946; 775; 161; 59; 109; 691; 346; 887; 70; 39; 733; 949; 483|];
+[| 24; 241; 989; 625; 686; 511; 818; 726; 132; 278; 852; 916; 199; 722; 75; 366; 86; 273; 518; 698; 842; 280; 959; 532; 985; 395; 399; 324; 355; 480; 273; 782; 27; 313; 541; 671; 778; 876; 411; 719; 915; 712; 873; 662; 686; 913; 428; 117; 787; 210; 89; 508; 432; 807; 253; 858; 220; 139; 712; 927; 503; 724; 974; 895; 974; 944; 551; 386; 892; 490; 646; 530; 772; 591; 530; 937; 66; 773; 855; 520; 403; 534; 730; 310; 113; 515; 338; 771; 524; 343; 6; 555; 421; 194; 892; 744; 523; 885; 713; 421|];
+[| 623; 848; 337; 335; 658; 580; 447; 454; 27; 518; 685; 610; 931; 81; 878; 349; 258; 762; 66; 574; 949; 825; 391; 981; 75; 315; 491; 551; 239; 44; 253; 492; 230; 509; 177; 930; 749; 796; 502; 420; 999; 741; 555; 686; 595; 41; 139; 453; 26; 538; 480; 373; 473; 553; 344; 180; 847; 437; 700; 171; 542; 62; 379; 883; 544; 254; 350; 900; 883; 273; 134; 385; 420; 505; 265; 347; 211; 916; 814; 888; 401; 690; 471; 883; 817; 94; 551; 989; 393; 885; 240; 470; 245; 468; 298; 195; 389; 227; 303; 195|];
+[| 634; 752; 95; 625; 808; 285; 689; 17; 245; 323; 650; 302; 902; 658; 892; 415; 150; 595; 286; 904; 140; 435; 880; 76; 574; 844; 286; 148; 618; 936; 883; 819; 866; 175; 823; 655; 823; 921; 260; 435; 664; 656; 644; 523; 30; 129; 416; 829; 67; 803; 498; 209; 865; 857; 917; 314; 355; 689; 332; 305; 822; 882; 618; 10; 908; 544; 234; 843; 916; 103; 511; 759; 47; 576; 224; 531; 502; 913; 558; 376; 889; 222; 525; 321; 991; 829; 714; 502; 526; 388; 866; 847; 181; 761; 105; 279; 901; 218; 314; 214|];
+[| 196; 966; 786; 349; 289; 745; 925; 375; 287; 178; 759; 471; 857; 901; 385; 162; 701; 468; 441; 890; 314; 789; 307; 588; 243; 925; 397; 854; 38; 288; 377; 55; 193; 56; 36; 251; 543; 342; 776; 777; 278; 542; 136; 329; 547; 558; 456; 806; 667; 260; 279; 456; 357; 893; 272; 890; 318; 842; 64; 738; 233; 27; 697; 898; 790; 570; 330; 588; 328; 938; 179; 89; 684; 892; 377; 80; 274; 650; 564; 711; 823; 302; 752; 620; 220; 553; 231; 471; 360; 704; 815; 948; 147; 790; 817; 286; 891; 357; 76; 836|];
+[| 646; 913; 594; 812; 515; 997; 178; 539; 982; 320; 941; 704; 495; 644; 413; 700; 48; 527; 80; 12; 833; 523; 277; 930; 873; 637; 833; 605; 829; 399; 780; 879; 694; 607; 323; 926; 456; 846; 946; 510; 409; 754; 958; 87; 898; 553; 117; 605; 157; 845; 951; 647; 774; 175; 517; 528; 669; 356; 95; 990; 64; 710; 176; 117; 501; 78; 468; 460; 752; 235; 711; 216; 772; 529; 708; 732; 711; 159; 938; 399; 434; 646; 315; 946; 510; 493; 461; 430; 929; 999; 578; 666; 736; 70; 121; 230; 745; 421; 559; 554|];
+[| 485; 982; 731; 576; 582; 599; 423; 558; 600; 304; 620; 903; 558; 521; 964; 835; 367; 718; 120; 229; 283; 207; 384; 512; 111; 633; 395; 290; 922; 159; 125; 507; 114; 19; 142; 377; 677; 552; 910; 354; 705; 603; 534; 125; 24; 504; 423; 123; 104; 241; 835; 201; 679; 604; 105; 903; 973; 653; 740; 798; 169; 320; 24; 485; 988; 973; 638; 573; 465; 315; 279; 378; 454; 972; 388; 493; 877; 577; 510; 112; 805; 854; 543; 29; 245; 937; 834; 663; 720; 830; 456; 493; 280; 459; 739; 653; 550; 963; 515; 542|];
+[| 843; 689; 938; 870; 693; 557; 422; 505; 786; 954; 32; 604; 514; 626; 314; 378; 93; 771; 162; 751; 177; 374; 70; 733; 857; 894; 685; 850; 149; 302; 942; 276; 22; 924; 494; 687; 143; 397; 863; 980; 400; 531; 427; 890; 245; 553; 934; 376; 267; 67; 732; 986; 834; 321; 138; 698; 867; 943; 842; 497; 335; 128; 301; 887; 951; 734; 860; 388; 792; 49; 510; 882; 772; 92; 127; 240; 218; 34; 625; 233; 730; 837; 30; 250; 334; 302; 848; 292; 341; 215; 441; 90; 563; 97; 952; 359; 923; 77; 246; 746|];
+[| 738; 804; 858; 41; 236; 917; 28; 741; 332; 918; 643; 516; 248; 45; 336; 791; 410; 718; 220; 788; 73; 163; 24; 804; 304; 321; 909; 296; 399; 204; 541; 510; 557; 370; 549; 826; 365; 922; 719; 932; 354; 947; 786; 610; 117; 259; 245; 28; 745; 119; 856; 640; 759; 977; 31; 780; 33; 420; 559; 329; 791; 942; 502; 976; 770; 0; 899; 29; 37; 539; 474; 758; 314; 667; 653; 4; 426; 854; 766; 499; 336; 686; 221; 348; 126; 827; 508; 674; 595; 420; 329; 149; 24; 701; 226; 483; 168; 581; 515; 473|];
+[| 39; 495; 591; 930; 891; 242; 577; 124; 734; 976; 452; 304; 578; 850; 137; 418; 23; 701; 232; 116; 566; 149; 217; 556; 481; 818; 239; 740; 770; 434; 205; 318; 350; 793; 143; 203; 182; 573; 744; 278; 490; 611; 195; 862; 440; 59; 485; 675; 442; 159; 356; 549; 306; 348; 761; 710; 815; 319; 711; 438; 89; 248; 732; 127; 261; 777; 91; 510; 138; 790; 803; 410; 434; 880; 232; 939; 397; 543; 552; 451; 871; 615; 511; 381; 577; 662; 779; 591; 822; 656; 168; 684; 635; 428; 570; 789; 978; 134; 555; 999|];
+[| 209; 692; 95; 73; 279; 954; 27; 966; 530; 504; 459; 896; 151; 761; 196; 899; 339; 477; 811; 348; 506; 34; 45; 773; 511; 665; 514; 468; 646; 237; 951; 984; 89; 101; 134; 231; 988; 399; 624; 423; 96; 713; 591; 504; 206; 110; 949; 199; 871; 812; 91; 704; 405; 254; 697; 696; 726; 566; 845; 367; 365; 519; 130; 518; 485; 375; 744; 556; 112; 482; 689; 851; 452; 507; 577; 80; 794; 331; 458; 981; 331; 358; 825; 351; 322; 57; 166; 189; 658; 948; 504; 843; 599; 425; 79; 7; 838; 705; 108; 302|];
+[| 411; 81; 71; 3; 826; 621; 360; 513; 933; 922; 983; 980; 552; 759; 494; 143; 792; 798; 0; 144; 141; 216; 502; 115; 702; 815; 686; 247; 155; 376; 116; 774; 179; 567; 15; 528; 497; 923; 486; 291; 902; 188; 109; 112; 567; 608; 954; 506; 958; 975; 734; 903; 494; 612; 922; 2; 571; 759; 544; 519; 963; 970; 992; 401; 327; 506; 800; 665; 472; 232; 791; 499; 223; 488; 204; 547; 879; 939; 232; 189; 770; 858; 158; 224; 902; 885; 813; 611; 169; 117; 155; 396; 756; 607; 719; 119; 543; 84; 687; 109|];
+[| 109; 823; 891; 623; 598; 710; 764; 130; 206; 801; 635; 893; 574; 753; 293; 299; 888; 56; 264; 505; 842; 765; 885; 153; 139; 614; 266; 141; 903; 254; 889; 838; 821; 232; 233; 666; 757; 357; 753; 83; 576; 67; 191; 597; 664; 917; 704; 642; 394; 529; 604; 372; 612; 906; 371; 353; 486; 180; 906; 926; 203; 676; 222; 715; 674; 433; 435; 307; 103; 544; 59; 845; 859; 74; 681; 68; 944; 810; 616; 955; 943; 381; 653; 462; 897; 471; 345; 497; 149; 101; 956; 78; 664; 111; 145; 663; 425; 579; 837; 653|];
+[| 833; 626; 238; 515; 430; 387; 764; 687; 225; 828; 195; 167; 409; 612; 583; 839; 707; 126; 462; 77; 956; 633; 473; 794; 132; 82; 134; 157; 799; 454; 808; 436; 658; 514; 890; 901; 209; 778; 986; 362; 976; 726; 472; 952; 881; 55; 374; 697; 222; 350; 82; 43; 346; 336; 318; 221; 909; 793; 107; 742; 607; 481; 621; 106; 358; 772; 593; 952; 688; 955; 884; 149; 246; 35; 522; 656; 877; 882; 362; 505; 80; 936; 367; 389; 380; 452; 296; 520; 374; 651; 264; 516; 818; 37; 791; 847; 883; 228; 857; 785|];
+[| 92; 714; 922; 792; 693; 891; 323; 115; 163; 493; 451; 42; 791; 717; 965; 702; 393; 949; 391; 620; 411; 446; 551; 361; 896; 635; 683; 323; 898; 497; 884; 633; 422; 133; 228; 772; 353; 735; 599; 975; 305; 985; 764; 710; 370; 358; 190; 24; 84; 875; 760; 278; 442; 391; 990; 267; 729; 163; 970; 120; 597; 426; 919; 480; 983; 862; 160; 325; 680; 314; 357; 89; 533; 790; 669; 307; 665; 491; 425; 852; 704; 865; 916; 26; 869; 153; 639; 522; 19; 408; 527; 96; 721; 995; 707; 601; 215; 352; 623; 224|]
+|];;
+
+let matrix2 = [| 
+[| 441; 279; 954; 649; 952; 840; 914; 480; 981; 841; 875; 520; 253; 404; 301; 768; 97; 276; 25; 760; 968; 548; 7; 730; 141; 549; 980; 249; 810; 203; 543; 770; 158; 200; 288; 866; 188; 789; 827; 105; 987; 989; 97; 898; 40; 460; 88; 342; 404; 551; 594; 867; 543; 856; 373; 250; 781; 267; 260; 510; 555; 319; 576; 447; 288; 451; 986; 17; 406; 423; 795; 21; 19; 563; 162; 527; 26; 355; 901; 337; 109; 79; 930; 988; 778; 330; 682; 728; 95; 982; 810; 10; 44; 660; 323; 233; 869; 858; 661; 923|];
+[| 483; 792; 816; 997; 368; 441; 223; 514; 282; 453; 322; 195; 828; 326; 600; 188; 191; 697; 212; 626; 580; 349; 874; 817; 670; 659; 721; 958; 532; 820; 663; 636; 122; 542; 66; 636; 432; 207; 138; 543; 461; 625; 166; 234; 603; 32; 468; 183; 977; 483; 108; 233; 53; 248; 544; 955; 243; 20; 21; 115; 259; 892; 60; 888; 744; 262; 770; 958; 814; 173; 986; 978; 90; 729; 53; 913; 290; 19; 840; 570; 91; 707; 256; 419; 663; 513; 894; 441; 97; 283; 783; 880; 559; 671; 53; 148; 583; 810; 31; 157|];
+[| 55; 886; 77; 224; 564; 107; 116; 313; 912; 282; 367; 641; 354; 150; 118; 453; 181; 176; 567; 848; 651; 697; 442; 601; 917; 467; 900; 265; 832; 572; 436; 505; 555; 688; 414; 673; 830; 688; 606; 940; 215; 651; 414; 957; 337; 630; 731; 996; 787; 995; 371; 973; 401; 296; 389; 722; 315; 675; 410; 831; 676; 454; 535; 349; 810; 151; 9; 57; 290; 681; 340; 924; 493; 147; 160; 427; 998; 156; 813; 269; 368; 558; 182; 949; 485; 823; 269; 551; 504; 174; 902; 652; 874; 343; 780; 814; 202; 504; 382; 313|];
+[| 437; 847; 409; 389; 903; 707; 570; 580; 7; 374; 819; 73; 312; 886; 322; 878; 997; 381; 817; 311; 121; 785; 78; 16; 550; 90; 254; 879; 37; 634; 650; 8; 446; 317; 715; 918; 883; 195; 139; 928; 239; 900; 358; 670; 37; 91; 291; 207; 304; 36; 624; 300; 357; 285; 569; 143; 908; 924; 298; 739; 352; 141; 592; 45; 309; 633; 99; 785; 17; 712; 324; 686; 324; 484; 181; 675; 431; 796; 72; 928; 854; 13; 160; 937; 684; 34; 409; 652; 49; 103; 10; 639; 940; 590; 20; 467; 405; 172; 146; 703|];
+[| 441; 951; 730; 441; 425; 934; 284; 683; 888; 603; 134; 826; 676; 915; 414; 703; 83; 127; 556; 621; 248; 965; 789; 740; 633; 575; 617; 522; 729; 855; 187; 722; 714; 841; 25; 443; 136; 53; 120; 898; 852; 589; 719; 93; 58; 826; 536; 565; 816; 483; 884; 598; 837; 102; 556; 878; 878; 194; 748; 952; 487; 516; 210; 195; 828; 25; 171; 125; 616; 143; 472; 574; 326; 549; 633; 20; 806; 766; 728; 170; 24; 412; 408; 49; 847; 75; 554; 1; 507; 740; 908; 753; 388; 935; 235; 280; 863; 631; 127; 983|];
+[| 694; 755; 12; 698; 999; 873; 725; 673; 590; 272; 818; 772; 159; 716; 116; 746; 417; 202; 655; 721; 383; 67; 186; 507; 160; 107; 257; 44; 243; 145; 836; 109; 883; 713; 190; 425; 59; 641; 465; 8; 986; 877; 521; 254; 493; 241; 587; 332; 436; 150; 24; 671; 604; 339; 423; 242; 213; 785; 225; 300; 288; 567; 641; 490; 580; 472; 532; 421; 264; 538; 539; 550; 996; 173; 717; 282; 864; 18; 458; 569; 365; 483; 146; 450; 613; 123; 123; 824; 721; 595; 30; 6; 628; 717; 849; 523; 652; 486; 28; 155|];
+[| 195; 174; 617; 548; 536; 29; 513; 869; 469; 223; 220; 588; 182; 491; 849; 690; 16; 453; 154; 452; 557; 528; 750; 3; 654; 759; 746; 314; 686; 724; 242; 395; 808; 693; 652; 772; 335; 507; 33; 859; 104; 920; 508; 970; 620; 193; 398; 809; 664; 372; 924; 382; 172; 379; 455; 980; 264; 824; 913; 559; 919; 369; 712; 48; 251; 278; 197; 918; 777; 826; 521; 968; 50; 840; 935; 680; 393; 499; 217; 912; 773; 210; 181; 558; 405; 837; 856; 861; 920; 226; 561; 332; 54; 658; 169; 958; 827; 739; 138; 36|];
+[| 774; 538; 230; 497; 912; 402; 83; 912; 435; 698; 117; 591; 384; 131; 977; 40; 950; 30; 173; 398; 310; 374; 858; 789; 717; 844; 545; 696; 732; 574; 948; 697; 196; 273; 987; 303; 846; 631; 149; 887; 475; 514; 561; 708; 69; 96; 721; 87; 185; 165; 958; 983; 603; 85; 582; 734; 757; 566; 495; 612; 340; 161; 8; 991; 579; 309; 184; 922; 655; 5; 609; 328; 894; 668; 971; 177; 800; 397; 180; 346; 144; 116; 401; 937; 84; 72; 627; 563; 865; 694; 381; 149; 82; 894; 907; 583; 441; 81; 84; 452|];
+[| 136; 956; 39; 194; 120; 362; 799; 99; 819; 108; 290; 182; 688; 294; 959; 204; 889; 444; 203; 357; 353; 149; 529; 222; 876; 75; 536; 271; 596; 623; 224; 438; 824; 565; 257; 117; 770; 775; 557; 381; 353; 417; 713; 905; 729; 581; 370; 378; 200; 382; 199; 990; 108; 576; 614; 87; 179; 590; 317; 261; 834; 44; 766; 936; 960; 751; 472; 514; 685; 693; 31; 243; 311; 263; 998; 917; 583; 193; 757; 224; 276; 662; 92; 79; 23; 120; 613; 823; 334; 974; 96; 674; 395; 174; 167; 403; 780; 374; 387; 102|];
+[| 778; 196; 562; 870; 74; 279; 202; 149; 786; 738; 917; 719; 716; 493; 327; 588; 436; 356; 381; 164; 946; 161; 21; 783; 108; 377; 389; 2; 745; 597; 305; 456; 347; 454; 900; 943; 846; 815; 143; 540; 192; 997; 430; 339; 550; 146; 485; 750; 342; 4; 100; 776; 14; 68; 635; 318; 871; 6; 293; 932; 245; 227; 80; 471; 56; 181; 718; 850; 567; 796; 183; 903; 166; 984; 382; 561; 333; 670; 339; 159; 587; 126; 675; 373; 139; 453; 508; 735; 463; 354; 426; 824; 276; 479; 717; 202; 598; 810; 820; 203|];
+[| 31; 876; 240; 137; 950; 855; 487; 753; 554; 31; 28; 20; 924; 404; 119; 710; 161; 271; 677; 459; 866; 686; 503; 697; 254; 355; 702; 960; 448; 635; 293; 606; 618; 311; 417; 184; 706; 249; 141; 895; 421; 662; 550; 16; 587; 981; 166; 834; 39; 174; 403; 163; 764; 179; 5; 935; 887; 670; 598; 402; 637; 309; 478; 934; 746; 194; 713; 207; 826; 602; 484; 981; 561; 978; 300; 284; 266; 939; 952; 514; 553; 387; 975; 380; 909; 20; 382; 458; 430; 608; 911; 934; 767; 475; 804; 14; 113; 241; 200; 413|];
+[| 162; 469; 576; 680; 795; 354; 91; 136; 450; 764; 173; 925; 989; 494; 158; 7; 499; 483; 936; 439; 253; 465; 339; 431; 504; 966; 381; 581; 983; 794; 788; 758; 361; 405; 623; 808; 90; 6; 213; 38; 509; 418; 50; 207; 817; 735; 656; 682; 942; 833; 285; 673; 397; 163; 764; 928; 235; 668; 752; 902; 574; 918; 676; 821; 623; 340; 513; 175; 187; 50; 73; 899; 412; 555; 410; 747; 686; 281; 733; 484; 690; 791; 391; 187; 875; 480; 408; 835; 648; 526; 673; 916; 551; 466; 601; 325; 304; 248; 237; 358|];
+[| 221; 754; 971; 735; 372; 83; 236; 950; 708; 563; 411; 583; 930; 361; 603; 709; 731; 521; 310; 980; 990; 943; 473; 986; 200; 792; 62; 937; 34; 503; 514; 842; 305; 933; 115; 731; 660; 132; 570; 48; 337; 610; 641; 6; 222; 746; 616; 410; 145; 655; 930; 866; 833; 158; 287; 893; 965; 397; 602; 773; 833; 109; 626; 587; 273; 870; 277; 589; 850; 287; 809; 363; 955; 319; 766; 897; 45; 121; 441; 621; 970; 902; 892; 965; 639; 788; 634; 692; 727; 573; 298; 898; 299; 973; 937; 282; 48; 645; 153; 572|];
+[| 978; 396; 738; 290; 850; 165; 953; 155; 523; 907; 438; 118; 676; 144; 402; 149; 402; 588; 773; 419; 745; 317; 647; 294; 856; 781; 656; 630; 220; 249; 258; 219; 638; 794; 862; 902; 750; 469; 282; 533; 528; 214; 899; 328; 129; 875; 92; 978; 968; 569; 151; 602; 339; 165; 374; 154; 80; 537; 526; 281; 735; 462; 762; 862; 906; 331; 409; 99; 99; 935; 172; 174; 717; 703; 457; 634; 112; 371; 335; 701; 976; 991; 817; 182; 814; 336; 377; 613; 348; 361; 906; 107; 918; 328; 3; 41; 579; 378; 116; 810|];
+[| 262; 1; 167; 365; 518; 949; 869; 250; 482; 407; 606; 319; 448; 594; 697; 376; 859; 224; 245; 915; 516; 96; 694; 728; 302; 526; 809; 879; 970; 660; 712; 478; 529; 194; 385; 881; 595; 287; 70; 613; 847; 978; 322; 18; 408; 488; 701; 157; 546; 950; 368; 423; 562; 457; 804; 525; 60; 813; 385; 432; 370; 879; 81; 226; 742; 419; 965; 323; 739; 486; 864; 63; 140; 670; 216; 898; 111; 24; 465; 582; 129; 383; 392; 62; 436; 465; 497; 112; 599; 853; 395; 681; 792; 278; 305; 203; 917; 947; 519; 683|];
+[| 24; 828; 335; 457; 672; 58; 979; 236; 589; 660; 280; 196; 213; 200; 968; 740; 711; 99; 190; 657; 507; 242; 804; 102; 561; 999; 890; 372; 698; 619; 96; 78; 741; 349; 209; 837; 550; 61; 142; 54; 272; 316; 973; 168; 71; 892; 893; 785; 857; 161; 62; 554; 190; 696; 30; 55; 748; 395; 55; 119; 511; 979; 776; 412; 65; 3; 435; 551; 356; 350; 988; 240; 260; 209; 948; 592; 690; 826; 433; 997; 356; 211; 890; 792; 541; 476; 529; 935; 697; 777; 108; 396; 648; 131; 167; 254; 510; 969; 543; 495|];
+[| 422; 112; 540; 74; 165; 34; 293; 110; 483; 634; 185; 895; 84; 150; 781; 224; 991; 115; 807; 242; 340; 512; 109; 217; 255; 913; 898; 206; 975; 756; 98; 456; 695; 686; 50; 720; 610; 201; 94; 742; 186; 110; 582; 114; 889; 159; 955; 163; 388; 685; 688; 874; 537; 390; 950; 155; 430; 664; 947; 222; 772; 244; 714; 94; 497; 718; 608; 658; 449; 766; 875; 784; 734; 263; 993; 34; 269; 978; 790; 993; 277; 143; 529; 918; 919; 558; 638; 134; 822; 105; 766; 28; 828; 461; 518; 289; 453; 680; 102; 597|];
+[| 333; 538; 184; 445; 751; 50; 370; 58; 373; 357; 839; 956; 956; 720; 580; 510; 586; 968; 626; 188; 750; 518; 548; 563; 422; 316; 723; 867; 849; 755; 726; 143; 46; 457; 55; 891; 592; 18; 465; 787; 836; 333; 948; 760; 928; 903; 875; 98; 696; 1; 909; 956; 244; 547; 270; 854; 272; 136; 763; 194; 348; 130; 499; 157; 272; 873; 679; 935; 620; 687; 889; 110; 12; 184; 344; 442; 184; 542; 692; 518; 477; 434; 344; 536; 100; 816; 153; 939; 70; 803; 970; 332; 489; 732; 761; 820; 256; 846; 966; 955|];
+[| 696; 801; 249; 667; 447; 127; 422; 648; 524; 215; 126; 448; 445; 693; 604; 193; 114; 811; 500; 461; 277; 928; 802; 181; 259; 647; 906; 126; 361; 549; 657; 968; 510; 735; 696; 471; 864; 264; 266; 626; 506; 410; 669; 179; 768; 743; 659; 235; 112; 893; 962; 694; 467; 197; 568; 300; 476; 572; 590; 312; 30; 158; 38; 684; 219; 98; 321; 40; 839; 64; 796; 888; 109; 577; 363; 813; 758; 428; 23; 546; 742; 704; 857; 451; 5; 221; 155; 396; 697; 713; 110; 513; 479; 109; 932; 244; 879; 139; 564; 838|];
+[| 927; 542; 126; 712; 97; 84; 571; 887; 775; 648; 411; 884; 980; 873; 876; 391; 836; 867; 971; 277; 905; 309; 377; 932; 350; 779; 838; 638; 88; 397; 256; 826; 401; 134; 735; 665; 827; 720; 432; 616; 475; 767; 842; 792; 582; 515; 740; 231; 253; 664; 216; 604; 683; 306; 316; 419; 192; 978; 428; 737; 711; 484; 786; 906; 87; 703; 824; 611; 307; 153; 251; 708; 526; 815; 710; 339; 290; 732; 712; 280; 567; 219; 2; 865; 392; 598; 700; 475; 101; 941; 28; 445; 388; 104; 948; 815; 639; 942; 619; 176|];
+[| 388; 828; 511; 554; 262; 928; 333; 381; 551; 618; 696; 383; 62; 972; 313; 962; 601; 261; 401; 536; 836; 565; 206; 457; 671; 30; 899; 566; 763; 840; 40; 98; 0; 926; 944; 344; 821; 418; 131; 849; 696; 841; 457; 517; 566; 371; 323; 109; 552; 527; 169; 963; 487; 279; 424; 30; 19; 512; 717; 412; 945; 751; 848; 89; 725; 87; 395; 530; 245; 678; 978; 939; 753; 297; 440; 879; 25; 915; 375; 582; 856; 729; 790; 667; 408; 628; 497; 875; 11; 442; 778; 425; 987; 71; 717; 988; 378; 88; 125; 170|];
+[| 138; 973; 776; 541; 679; 589; 474; 139; 827; 963; 527; 327; 720; 908; 156; 507; 163; 695; 413; 997; 437; 675; 997; 694; 829; 582; 842; 242; 312; 109; 206; 246; 540; 896; 212; 419; 475; 671; 35; 539; 19; 682; 278; 371; 413; 916; 772; 121; 516; 774; 78; 910; 809; 80; 631; 607; 401; 229; 359; 281; 189; 891; 215; 199; 382; 702; 836; 395; 176; 348; 591; 510; 941; 610; 7; 369; 515; 534; 964; 176; 965; 548; 251; 726; 661; 318; 647; 313; 747; 649; 830; 761; 610; 206; 202; 265; 656; 679; 503; 987|];
+[| 960; 534; 850; 481; 839; 206; 353; 760; 316; 16; 919; 798; 693; 977; 576; 310; 715; 704; 600; 533; 597; 163; 205; 525; 934; 309; 286; 239; 158; 149; 389; 579; 656; 896; 577; 74; 618; 330; 644; 890; 688; 885; 851; 562; 640; 762; 460; 628; 283; 54; 450; 854; 183; 49; 815; 466; 114; 718; 263; 849; 474; 31; 561; 153; 373; 902; 154; 732; 683; 594; 79; 246; 717; 872; 6; 396; 703; 322; 525; 123; 498; 677; 24; 848; 849; 449; 780; 81; 616; 83; 428; 259; 854; 560; 41; 242; 945; 569; 447; 529|];
+[| 953; 751; 515; 618; 258; 198; 129; 898; 610; 211; 348; 409; 962; 177; 240; 719; 790; 18; 129; 172; 121; 503; 114; 594; 562; 781; 172; 462; 190; 827; 529; 276; 361; 165; 739; 447; 712; 3; 798; 229; 287; 7; 858; 72; 536; 504; 267; 363; 223; 723; 194; 105; 628; 221; 911; 368; 198; 578; 240; 418; 587; 892; 909; 470; 35; 217; 221; 821; 932; 76; 753; 938; 172; 696; 144; 84; 375; 36; 299; 165; 995; 282; 511; 469; 917; 36; 828; 830; 950; 460; 792; 9; 293; 482; 290; 310; 630; 268; 358; 625|];
+[| 203; 54; 356; 983; 226; 436; 950; 167; 688; 150; 546; 975; 710; 327; 339; 521; 712; 318; 533; 137; 855; 716; 641; 662; 344; 878; 890; 900; 826; 124; 221; 305; 909; 397; 523; 216; 581; 250; 58; 143; 176; 812; 695; 645; 603; 764; 774; 79; 947; 280; 860; 605; 312; 957; 752; 276; 353; 859; 245; 324; 297; 236; 387; 625; 312; 120; 769; 839; 236; 204; 29; 275; 753; 256; 920; 255; 512; 176; 916; 292; 787; 773; 169; 134; 773; 586; 686; 440; 635; 481; 626; 225; 282; 45; 65; 278; 494; 105; 754; 374|];
+[| 171; 86; 547; 990; 449; 64; 908; 200; 207; 306; 640; 124; 648; 872; 319; 284; 438; 596; 356; 458; 364; 419; 542; 372; 508; 308; 578; 637; 649; 800; 291; 830; 501; 103; 19; 5; 841; 4; 345; 2; 338; 584; 481; 894; 771; 719; 690; 847; 382; 929; 311; 477; 9; 882; 682; 617; 586; 812; 323; 11; 135; 877; 933; 57; 10; 746; 570; 323; 419; 873; 572; 368; 56; 876; 410; 841; 372; 403; 74; 624; 98; 863; 294; 535; 732; 107; 490; 731; 991; 903; 351; 882; 303; 870; 749; 437; 37; 9; 566; 472|];
+[| 595; 343; 596; 193; 549; 195; 378; 187; 414; 599; 508; 781; 316; 422; 773; 349; 238; 945; 939; 138; 745; 513; 42; 37; 2; 775; 54; 691; 558; 977; 363; 922; 4; 581; 56; 9; 299; 956; 949; 935; 567; 583; 288; 219; 605; 813; 409; 528; 105; 115; 600; 528; 174; 364; 562; 555; 65; 166; 578; 494; 386; 396; 147; 315; 221; 548; 930; 31; 545; 176; 83; 721; 356; 306; 270; 158; 158; 485; 999; 295; 959; 845; 590; 236; 772; 559; 783; 537; 716; 182; 791; 103; 580; 646; 677; 247; 132; 839; 775; 488|];
+[| 781; 240; 951; 639; 806; 23; 471; 805; 755; 373; 584; 276; 400; 163; 860; 544; 755; 647; 69; 22; 667; 167; 758; 55; 432; 576; 109; 477; 531; 932; 428; 402; 360; 385; 630; 473; 130; 383; 465; 445; 634; 266; 205; 535; 911; 723; 35; 764; 747; 338; 920; 52; 355; 335; 698; 367; 45; 432; 356; 446; 912; 226; 702; 976; 532; 239; 691; 719; 941; 898; 108; 909; 612; 184; 336; 314; 880; 413; 861; 79; 432; 349; 543; 712; 165; 475; 712; 10; 588; 29; 997; 922; 953; 502; 841; 828; 653; 279; 628; 792|];
+[| 218; 938; 972; 686; 331; 346; 69; 683; 752; 956; 83; 503; 973; 916; 965; 207; 344; 664; 268; 634; 93; 316; 970; 218; 943; 838; 584; 533; 258; 859; 662; 908; 68; 654; 352; 71; 243; 913; 494; 337; 810; 515; 781; 9; 922; 56; 809; 501; 131; 685; 276; 598; 188; 927; 182; 545; 857; 20; 48; 2; 248; 448; 256; 830; 135; 360; 450; 942; 344; 386; 527; 270; 424; 63; 51; 612; 124; 582; 528; 152; 868; 772; 223; 505; 268; 441; 28; 563; 607; 555; 229; 45; 303; 172; 261; 123; 854; 623; 303; 35|];
+[| 374; 739; 676; 781; 317; 377; 785; 393; 783; 801; 983; 75; 366; 464; 473; 472; 163; 798; 637; 602; 528; 505; 837; 265; 710; 936; 522; 908; 257; 934; 631; 696; 467; 619; 653; 17; 546; 379; 988; 829; 656; 52; 380; 877; 260; 418; 447; 237; 584; 490; 977; 682; 817; 670; 709; 80; 386; 19; 408; 977; 649; 983; 36; 995; 213; 587; 953; 693; 360; 744; 949; 557; 210; 746; 476; 828; 138; 886; 250; 124; 413; 175; 484; 708; 425; 799; 24; 543; 506; 570; 678; 1; 401; 974; 57; 130; 216; 216; 237; 153|];
+[| 937; 536; 860; 128; 362; 493; 214; 872; 854; 358; 741; 951; 270; 177; 147; 608; 699; 391; 170; 364; 506; 157; 886; 196; 671; 231; 321; 487; 430; 888; 607; 778; 872; 379; 280; 107; 525; 946; 693; 978; 886; 395; 549; 884; 470; 331; 228; 712; 566; 914; 636; 268; 956; 542; 195; 691; 933; 337; 993; 113; 747; 597; 537; 311; 783; 385; 684; 869; 793; 626; 541; 662; 572; 947; 523; 908; 83; 233; 169; 860; 545; 904; 965; 657; 402; 628; 270; 793; 102; 132; 360; 216; 259; 237; 645; 466; 163; 489; 360; 708|];
+[| 896; 428; 769; 716; 697; 610; 262; 464; 346; 501; 961; 953; 502; 932; 627; 98; 785; 822; 489; 894; 1; 831; 296; 483; 260; 591; 987; 567; 785; 266; 622; 252; 89; 719; 413; 264; 726; 973; 358; 132; 147; 691; 443; 991; 522; 417; 100; 922; 522; 966; 681; 691; 548; 273; 47; 825; 183; 29; 151; 561; 8; 715; 615; 467; 6; 953; 962; 344; 110; 258; 800; 874; 183; 71; 420; 567; 733; 444; 786; 839; 857; 646; 91; 196; 956; 759; 949; 545; 93; 13; 184; 837; 634; 203; 532; 700; 109; 505; 378; 850|];
+[| 215; 708; 799; 796; 456; 187; 971; 343; 135; 411; 369; 364; 254; 297; 83; 499; 258; 565; 151; 27; 680; 845; 373; 402; 346; 134; 872; 559; 471; 802; 342; 775; 248; 964; 103; 102; 545; 322; 278; 802; 858; 662; 202; 829; 474; 590; 527; 499; 985; 934; 441; 730; 56; 409; 442; 622; 92; 713; 730; 129; 170; 445; 855; 685; 546; 196; 690; 771; 606; 143; 884; 432; 931; 181; 509; 853; 221; 243; 266; 624; 451; 845; 64; 648; 711; 245; 41; 632; 939; 947; 671; 13; 121; 603; 164; 211; 678; 749; 772; 328|];
+[| 9; 307; 259; 990; 737; 363; 38; 587; 677; 656; 643; 809; 963; 263; 226; 798; 708; 236; 42; 251; 307; 34; 846; 924; 760; 559; 403; 926; 899; 515; 192; 597; 788; 509; 61; 519; 299; 701; 207; 902; 262; 987; 669; 231; 367; 593; 551; 626; 996; 71; 483; 949; 62; 783; 971; 880; 65; 310; 912; 932; 983; 826; 649; 13; 480; 83; 628; 450; 470; 52; 294; 255; 422; 256; 253; 465; 384; 207; 61; 63; 190; 194; 809; 39; 400; 99; 704; 245; 223; 254; 539; 787; 447; 30; 271; 484; 691; 262; 595; 83|];
+[| 579; 750; 97; 495; 662; 984; 177; 220; 940; 166; 952; 193; 460; 816; 375; 979; 658; 22; 748; 843; 188; 604; 106; 423; 696; 628; 164; 169; 522; 543; 522; 830; 361; 737; 599; 255; 736; 222; 667; 865; 121; 639; 221; 158; 234; 629; 46; 659; 955; 311; 567; 473; 640; 705; 320; 807; 339; 83; 748; 373; 506; 589; 775; 696; 857; 121; 141; 485; 180; 14; 807; 74; 813; 855; 156; 899; 602; 412; 592; 402; 128; 382; 350; 586; 400; 639; 462; 558; 901; 230; 493; 640; 631; 808; 168; 219; 96; 99; 947; 334|];
+[| 482; 440; 368; 861; 131; 880; 527; 106; 587; 252; 366; 5; 999; 963; 787; 431; 39; 934; 626; 564; 323; 383; 609; 824; 724; 722; 971; 669; 262; 6; 283; 598; 823; 722; 87; 251; 492; 782; 231; 192; 450; 903; 195; 885; 809; 596; 887; 610; 192; 779; 52; 427; 567; 785; 19; 498; 154; 429; 8; 752; 671; 797; 691; 885; 982; 124; 696; 871; 763; 691; 22; 410; 275; 16; 435; 356; 558; 956; 88; 742; 884; 778; 626; 129; 636; 866; 543; 130; 977; 88; 808; 26; 560; 685; 710; 138; 151; 427; 440; 211|];
+[| 328; 906; 697; 29; 55; 68; 959; 288; 817; 207; 752; 492; 136; 662; 122; 545; 474; 972; 50; 910; 316; 540; 126; 642; 537; 297; 526; 358; 210; 189; 452; 689; 16; 342; 557; 43; 204; 155; 69; 500; 598; 138; 188; 860; 199; 984; 960; 665; 161; 673; 791; 517; 221; 310; 266; 805; 92; 217; 355; 70; 544; 989; 920; 798; 180; 31; 806; 957; 509; 860; 764; 116; 371; 321; 195; 985; 673; 223; 4; 502; 78; 635; 563; 357; 173; 43; 388; 428; 935; 330; 484; 352; 967; 14; 27; 938; 170; 653; 706; 29|];
+[| 847; 382; 568; 628; 735; 967; 242; 714; 803; 898; 484; 976; 233; 202; 218; 85; 138; 267; 744; 362; 922; 591; 47; 668; 906; 799; 670; 272; 956; 857; 948; 325; 345; 486; 355; 18; 927; 502; 490; 908; 484; 452; 908; 170; 360; 179; 833; 230; 203; 432; 402; 130; 935; 874; 347; 960; 193; 886; 896; 407; 504; 86; 391; 990; 873; 615; 72; 51; 634; 737; 248; 795; 303; 472; 895; 804; 225; 358; 205; 493; 57; 575; 870; 354; 888; 164; 770; 122; 810; 189; 922; 335; 332; 447; 124; 197; 643; 33; 551; 683|];
+[| 43; 761; 664; 547; 393; 684; 116; 499; 853; 989; 735; 240; 258; 926; 978; 353; 378; 639; 287; 413; 196; 165; 944; 772; 180; 935; 181; 897; 791; 103; 47; 666; 976; 878; 977; 13; 455; 590; 215; 777; 55; 770; 57; 421; 393; 808; 932; 366; 456; 303; 977; 211; 19; 944; 882; 421; 314; 592; 891; 878; 855; 160; 186; 507; 841; 673; 666; 123; 440; 447; 870; 57; 710; 480; 637; 737; 909; 324; 780; 128; 347; 243; 432; 620; 354; 955; 360; 619; 878; 757; 505; 643; 54; 298; 875; 928; 744; 472; 273; 221|];
+[| 391; 920; 604; 17; 118; 417; 316; 482; 101; 631; 941; 612; 188; 443; 263; 511; 140; 70; 319; 404; 632; 817; 150; 414; 573; 557; 210; 172; 892; 612; 952; 762; 196; 207; 323; 172; 368; 444; 321; 34; 337; 215; 887; 645; 874; 316; 321; 438; 452; 995; 37; 627; 186; 710; 706; 816; 609; 188; 346; 170; 547; 706; 895; 896; 866; 811; 373; 351; 403; 870; 196; 831; 978; 341; 26; 432; 80; 619; 456; 740; 366; 327; 179; 702; 114; 327; 218; 762; 442; 340; 123; 986; 521; 723; 298; 457; 28; 646; 606; 479|];
+[| 265; 626; 62; 191; 564; 34; 879; 324; 702; 322; 670; 962; 230; 85; 857; 313; 57; 521; 356; 968; 609; 245; 164; 696; 656; 895; 542; 315; 709; 240; 276; 871; 550; 770; 700; 377; 962; 431; 429; 841; 820; 705; 516; 383; 451; 843; 307; 481; 105; 990; 998; 2; 663; 111; 931; 527; 946; 290; 38; 70; 877; 251; 963; 269; 549; 887; 398; 419; 214; 216; 314; 603; 804; 613; 985; 820; 24; 203; 564; 668; 322; 910; 866; 283; 651; 819; 112; 620; 536; 172; 145; 803; 904; 510; 259; 501; 402; 958; 244; 861|];
+[| 463; 424; 437; 281; 945; 866; 801; 977; 701; 360; 263; 125; 250; 57; 175; 785; 454; 77; 293; 403; 401; 107; 138; 266; 710; 177; 294; 840; 592; 345; 852; 492; 478; 801; 199; 750; 238; 950; 746; 917; 192; 21; 108; 31; 31; 665; 392; 195; 642; 452; 532; 609; 709; 824; 568; 618; 144; 455; 268; 758; 682; 963; 891; 705; 877; 710; 428; 660; 532; 260; 481; 921; 194; 540; 463; 946; 393; 461; 209; 944; 255; 898; 228; 659; 136; 213; 937; 440; 765; 352; 149; 841; 356; 328; 290; 130; 20; 782; 261; 823|];
+[| 372; 534; 678; 476; 344; 493; 56; 171; 926; 202; 998; 573; 943; 961; 478; 273; 131; 73; 47; 920; 490; 348; 61; 762; 34; 361; 334; 383; 774; 249; 661; 194; 137; 761; 126; 796; 800; 208; 525; 92; 868; 104; 546; 321; 456; 501; 535; 791; 995; 38; 922; 843; 304; 570; 382; 470; 704; 165; 691; 533; 612; 595; 896; 948; 477; 341; 345; 370; 136; 677; 183; 114; 309; 578; 500; 267; 960; 800; 613; 607; 135; 399; 979; 982; 808; 910; 292; 192; 414; 432; 485; 714; 153; 849; 752; 805; 656; 896; 426; 483|];
+[| 796; 938; 942; 484; 15; 345; 212; 464; 604; 133; 320; 308; 400; 796; 229; 549; 551; 244; 458; 464; 424; 63; 816; 879; 191; 404; 401; 259; 645; 216; 692; 356; 644; 596; 85; 899; 696; 248; 687; 703; 715; 593; 803; 798; 856; 388; 20; 946; 586; 8; 488; 276; 352; 513; 270; 143; 808; 288; 885; 538; 453; 842; 739; 503; 948; 756; 427; 309; 493; 522; 722; 335; 242; 646; 301; 428; 708; 299; 392; 850; 540; 999; 977; 456; 748; 996; 481; 88; 499; 460; 484; 433; 369; 917; 60; 579; 717; 670; 161; 966|];
+[| 115; 863; 547; 152; 738; 93; 342; 420; 717; 628; 832; 395; 973; 547; 621; 757; 986; 778; 604; 270; 808; 277; 606; 128; 781; 795; 314; 101; 245; 377; 474; 992; 597; 393; 444; 612; 865; 120; 45; 787; 646; 670; 748; 654; 368; 544; 773; 156; 738; 864; 937; 881; 130; 120; 697; 565; 764; 682; 464; 297; 886; 726; 53; 968; 455; 318; 338; 988; 512; 788; 423; 105; 679; 308; 591; 586; 901; 261; 351; 548; 98; 818; 589; 543; 703; 999; 955; 310; 749; 554; 926; 732; 771; 173; 431; 165; 862; 662; 155; 139|];
+[| 607; 327; 200; 135; 948; 235; 469; 59; 49; 31; 507; 818; 558; 225; 674; 787; 460; 990; 469; 996; 415; 280; 153; 934; 125; 275; 853; 71; 616; 146; 943; 40; 470; 822; 652; 73; 969; 701; 667; 268; 950; 171; 413; 385; 398; 286; 713; 946; 975; 749; 956; 958; 752; 416; 898; 631; 885; 417; 216; 392; 412; 510; 309; 345; 83; 48; 449; 854; 512; 808; 40; 521; 484; 761; 625; 40; 939; 239; 810; 69; 414; 169; 311; 0; 34; 348; 138; 883; 23; 746; 973; 158; 806; 749; 796; 691; 279; 784; 589; 736|];
+[| 212; 167; 41; 134; 286; 403; 775; 953; 461; 528; 368; 395; 860; 341; 746; 582; 259; 512; 500; 614; 808; 624; 178; 831; 391; 275; 609; 472; 412; 63; 629; 31; 887; 615; 240; 544; 709; 845; 233; 400; 293; 920; 462; 509; 753; 43; 224; 844; 946; 559; 151; 76; 873; 224; 907; 713; 606; 277; 639; 124; 674; 641; 713; 477; 948; 259; 528; 851; 443; 202; 174; 336; 379; 219; 610; 174; 819; 538; 796; 603; 733; 226; 907; 542; 252; 562; 631; 688; 518; 242; 40; 57; 264; 67; 443; 583; 892; 542; 591; 292|];
+[| 893; 830; 368; 620; 531; 303; 337; 311; 371; 26; 827; 929; 746; 489; 290; 540; 657; 787; 89; 128; 271; 339; 484; 899; 887; 459; 129; 973; 881; 593; 666; 731; 784; 268; 841; 99; 103; 803; 34; 680; 583; 393; 94; 141; 612; 313; 149; 198; 914; 353; 526; 2; 479; 377; 562; 379; 508; 982; 61; 863; 22; 482; 237; 418; 258; 608; 222; 151; 187; 233; 952; 828; 442; 981; 119; 585; 723; 419; 282; 74; 786; 683; 517; 364; 861; 560; 496; 966; 143; 60; 293; 856; 924; 463; 956; 356; 25; 96; 7; 809|];
+[| 507; 739; 898; 679; 89; 695; 429; 665; 357; 661; 714; 991; 408; 970; 344; 374; 277; 170; 136; 237; 139; 667; 726; 258; 761; 425; 756; 207; 15; 403; 244; 879; 978; 821; 802; 215; 768; 779; 513; 451; 641; 581; 993; 10; 107; 692; 757; 385; 724; 637; 94; 871; 408; 791; 201; 59; 909; 154; 714; 230; 419; 651; 674; 467; 710; 524; 257; 349; 470; 890; 85; 962; 67; 105; 172; 968; 226; 373; 896; 516; 54; 553; 33; 873; 787; 863; 515; 778; 98; 960; 985; 125; 459; 625; 806; 195; 796; 913; 475; 718|];
+[| 450; 911; 444; 773; 583; 620; 569; 531; 860; 810; 214; 121; 336; 115; 286; 183; 240; 940; 103; 96; 273; 933; 972; 435; 704; 139; 500; 724; 14; 860; 954; 577; 451; 239; 286; 431; 439; 567; 550; 420; 144; 793; 316; 447; 2; 512; 762; 8; 374; 775; 263; 978; 675; 331; 617; 271; 925; 419; 577; 127; 179; 178; 741; 937; 942; 604; 129; 693; 19; 174; 541; 774; 569; 210; 146; 704; 639; 762; 734; 875; 145; 477; 636; 997; 652; 267; 204; 854; 856; 238; 12; 854; 206; 446; 362; 175; 834; 966; 43; 80|];
+[| 425; 226; 297; 921; 399; 369; 134; 655; 377; 609; 188; 979; 455; 612; 639; 724; 369; 785; 951; 344; 777; 879; 410; 296; 698; 487; 666; 41; 37; 420; 920; 791; 196; 461; 212; 658; 41; 309; 442; 374; 458; 986; 854; 233; 901; 206; 138; 586; 705; 182; 561; 478; 500; 896; 569; 830; 9; 249; 501; 52; 912; 964; 259; 53; 491; 573; 733; 82; 971; 937; 782; 125; 302; 55; 414; 439; 181; 869; 76; 565; 182; 451; 571; 51; 131; 743; 813; 813; 395; 756; 76; 473; 928; 538; 230; 110; 139; 666; 402; 576|];
+[| 321; 295; 868; 382; 715; 356; 106; 688; 460; 547; 859; 215; 587; 738; 250; 657; 468; 288; 882; 878; 924; 902; 354; 828; 140; 187; 752; 665; 438; 311; 399; 18; 605; 314; 30; 502; 294; 516; 443; 403; 798; 539; 578; 959; 302; 692; 44; 521; 527; 685; 505; 310; 377; 371; 877; 26; 121; 909; 754; 851; 419; 682; 248; 958; 529; 945; 649; 603; 544; 607; 631; 557; 823; 777; 576; 369; 983; 952; 65; 584; 52; 371; 988; 597; 982; 28; 249; 783; 169; 167; 893; 32; 171; 831; 618; 166; 920; 864; 399; 215|];
+[| 707; 383; 785; 550; 566; 663; 922; 906; 812; 7; 341; 795; 414; 588; 131; 350; 623; 668; 910; 473; 361; 63; 418; 665; 155; 64; 179; 224; 356; 306; 492; 794; 770; 702; 959; 393; 656; 355; 491; 869; 185; 273; 761; 778; 270; 849; 882; 857; 972; 980; 330; 850; 162; 556; 807; 113; 848; 431; 811; 448; 372; 56; 651; 854; 233; 88; 283; 914; 581; 942; 501; 848; 197; 967; 702; 743; 679; 278; 501; 219; 386; 933; 598; 709; 649; 703; 453; 841; 256; 452; 249; 437; 986; 54; 50; 867; 600; 387; 975; 77|];
+[| 68; 273; 716; 190; 641; 951; 502; 848; 672; 176; 206; 31; 577; 923; 105; 216; 355; 89; 356; 70; 22; 698; 639; 293; 348; 78; 308; 106; 678; 479; 239; 694; 557; 317; 402; 555; 235; 176; 409; 832; 511; 46; 873; 23; 278; 679; 381; 872; 423; 265; 623; 295; 698; 401; 692; 670; 256; 198; 612; 541; 307; 126; 26; 696; 610; 404; 345; 409; 481; 828; 461; 977; 108; 61; 489; 630; 748; 259; 721; 469; 430; 298; 569; 706; 203; 284; 969; 868; 772; 916; 398; 990; 509; 616; 871; 801; 535; 743; 994; 760|];
+[| 416; 913; 216; 683; 588; 820; 97; 673; 951; 49; 756; 362; 359; 62; 552; 598; 625; 938; 223; 87; 31; 76; 73; 623; 428; 894; 102; 487; 329; 669; 976; 105; 937; 659; 561; 82; 313; 707; 491; 68; 185; 698; 522; 106; 411; 135; 100; 616; 52; 904; 456; 644; 762; 227; 239; 502; 781; 905; 862; 409; 757; 315; 597; 864; 155; 883; 231; 223; 314; 972; 780; 98; 441; 670; 40; 552; 455; 769; 559; 64; 135; 31; 698; 686; 302; 945; 920; 974; 131; 762; 111; 603; 607; 693; 790; 681; 794; 909; 137; 713|];
+[| 557; 779; 321; 762; 28; 946; 726; 213; 136; 309; 855; 707; 737; 472; 53; 2; 604; 252; 865; 632; 442; 101; 922; 482; 230; 299; 601; 296; 841; 553; 884; 50; 907; 380; 816; 875; 981; 838; 636; 268; 894; 857; 26; 591; 815; 908; 854; 474; 125; 411; 625; 785; 305; 434; 715; 852; 165; 334; 177; 101; 134; 534; 906; 502; 488; 683; 629; 204; 957; 543; 357; 172; 576; 95; 887; 147; 432; 813; 260; 369; 421; 852; 177; 441; 788; 698; 621; 163; 39; 566; 351; 394; 359; 152; 170; 195; 20; 626; 643; 904|];
+[| 419; 837; 684; 761; 385; 68; 997; 373; 222; 279; 412; 591; 808; 410; 455; 9; 478; 987; 145; 506; 365; 580; 50; 612; 867; 46; 435; 583; 934; 598; 238; 737; 240; 543; 526; 471; 807; 484; 701; 687; 685; 938; 407; 185; 604; 741; 568; 148; 368; 619; 238; 949; 501; 113; 216; 726; 477; 374; 851; 309; 992; 459; 33; 365; 824; 690; 754; 716; 408; 610; 391; 66; 362; 131; 863; 962; 225; 952; 928; 564; 404; 920; 167; 19; 262; 329; 705; 389; 131; 247; 824; 962; 918; 547; 707; 9; 368; 574; 639; 145|];
+[| 693; 917; 254; 863; 543; 631; 996; 599; 757; 952; 520; 803; 487; 670; 677; 258; 54; 973; 418; 659; 509; 7; 347; 280; 300; 418; 852; 179; 954; 541; 524; 490; 101; 11; 281; 596; 184; 689; 265; 857; 653; 204; 503; 356; 190; 796; 735; 358; 80; 810; 909; 378; 846; 947; 338; 963; 67; 965; 122; 820; 506; 260; 689; 855; 488; 602; 84; 164; 930; 595; 488; 577; 639; 678; 208; 535; 673; 283; 912; 716; 407; 145; 953; 67; 217; 48; 68; 948; 982; 79; 65; 14; 527; 689; 61; 914; 299; 455; 614; 349|];
+[| 639; 49; 212; 859; 318; 60; 534; 845; 617; 611; 43; 845; 616; 132; 583; 680; 619; 763; 607; 705; 382; 677; 739; 613; 561; 651; 51; 449; 684; 976; 154; 17; 721; 303; 518; 905; 225; 100; 496; 277; 852; 201; 63; 273; 182; 990; 710; 686; 771; 862; 851; 105; 944; 527; 807; 964; 216; 251; 394; 649; 296; 775; 967; 302; 757; 191; 843; 88; 975; 419; 333; 493; 898; 425; 862; 722; 119; 555; 797; 85; 738; 942; 692; 648; 860; 525; 653; 356; 972; 537; 878; 30; 111; 141; 111; 648; 463; 793; 63; 12|];
+[| 876; 388; 206; 833; 480; 472; 796; 445; 32; 590; 449; 93; 657; 252; 644; 617; 665; 2; 400; 253; 587; 161; 99; 980; 427; 216; 760; 245; 293; 922; 487; 894; 608; 389; 610; 6; 477; 907; 928; 95; 681; 127; 951; 234; 819; 356; 171; 697; 273; 391; 761; 674; 530; 993; 130; 475; 838; 539; 507; 546; 405; 999; 771; 999; 242; 33; 165; 232; 427; 974; 827; 577; 322; 968; 681; 826; 489; 622; 140; 854; 424; 526; 579; 948; 105; 264; 53; 289; 256; 203; 373; 567; 101; 951; 877; 342; 502; 575; 396; 834|];
+[| 656; 536; 51; 232; 965; 16; 498; 579; 975; 694; 570; 238; 342; 125; 513; 199; 538; 737; 862; 860; 80; 316; 769; 11; 940; 630; 849; 243; 689; 539; 17; 658; 678; 939; 981; 762; 559; 221; 699; 553; 180; 384; 423; 588; 544; 186; 26; 54; 595; 738; 701; 197; 978; 647; 631; 943; 478; 368; 682; 206; 885; 525; 788; 908; 102; 961; 53; 98; 294; 849; 520; 357; 38; 889; 804; 361; 871; 856; 303; 198; 437; 227; 110; 229; 66; 266; 576; 256; 844; 298; 168; 186; 351; 432; 166; 876; 537; 733; 324; 154|];
+[| 219; 878; 913; 50; 734; 116; 135; 419; 454; 939; 215; 703; 666; 716; 427; 71; 447; 496; 94; 419; 72; 911; 610; 161; 118; 985; 557; 223; 631; 365; 66; 905; 171; 937; 393; 573; 366; 221; 793; 851; 986; 250; 273; 175; 766; 425; 905; 101; 580; 531; 467; 86; 127; 465; 51; 730; 357; 813; 502; 968; 638; 296; 438; 865; 724; 848; 731; 232; 156; 635; 510; 246; 523; 34; 321; 539; 553; 926; 757; 625; 707; 330; 270; 183; 802; 823; 102; 773; 426; 607; 28; 83; 838; 705; 884; 370; 628; 907; 849; 857|];
+[| 981; 135; 455; 228; 880; 843; 799; 993; 874; 568; 419; 983; 25; 530; 41; 733; 968; 443; 908; 120; 263; 382; 137; 583; 277; 182; 503; 279; 985; 208; 622; 291; 180; 92; 117; 324; 201; 360; 975; 399; 152; 737; 49; 624; 733; 310; 227; 245; 939; 160; 222; 110; 848; 15; 548; 561; 59; 772; 872; 673; 406; 664; 789; 967; 974; 809; 651; 620; 931; 39; 472; 993; 740; 330; 381; 316; 555; 750; 223; 704; 708; 340; 66; 518; 320; 281; 810; 307; 869; 962; 592; 785; 180; 295; 983; 971; 441; 190; 578; 226|];
+[| 517; 314; 778; 104; 936; 330; 684; 500; 75; 200; 423; 581; 931; 231; 326; 712; 529; 126; 233; 877; 318; 821; 664; 874; 947; 553; 223; 147; 41; 84; 609; 287; 948; 134; 103; 795; 148; 121; 751; 503; 541; 458; 690; 334; 858; 409; 574; 72; 51; 269; 535; 226; 128; 944; 174; 481; 482; 258; 320; 620; 548; 864; 575; 31; 430; 81; 895; 510; 572; 852; 120; 217; 11; 642; 64; 572; 617; 659; 52; 417; 768; 609; 857; 858; 504; 561; 312; 922; 485; 225; 276; 185; 545; 783; 86; 212; 378; 885; 288; 669|];
+[| 836; 341; 191; 759; 752; 283; 946; 946; 259; 568; 524; 257; 297; 145; 950; 408; 248; 938; 133; 837; 452; 876; 672; 147; 618; 486; 816; 733; 877; 856; 901; 351; 318; 352; 547; 968; 838; 287; 335; 291; 104; 522; 940; 394; 794; 293; 381; 260; 962; 473; 176; 281; 50; 306; 841; 170; 586; 454; 810; 669; 800; 725; 294; 99; 563; 636; 663; 33; 78; 318; 873; 893; 43; 278; 641; 843; 925; 948; 22; 132; 23; 101; 608; 87; 337; 779; 290; 247; 960; 696; 852; 49; 360; 175; 197; 559; 336; 568; 719; 509|];
+[| 430; 8; 161; 969; 96; 214; 496; 333; 252; 169; 680; 199; 463; 36; 602; 39; 815; 92; 982; 177; 766; 894; 400; 17; 126; 896; 519; 396; 397; 509; 195; 201; 776; 967; 334; 717; 452; 405; 300; 144; 373; 938; 508; 770; 828; 151; 947; 88; 746; 45; 260; 899; 981; 893; 767; 312; 806; 441; 336; 697; 720; 494; 350; 845; 24; 198; 327; 485; 256; 384; 986; 369; 921; 760; 412; 82; 481; 13; 83; 271; 855; 432; 273; 28; 713; 653; 786; 867; 155; 441; 110; 276; 431; 303; 231; 200; 281; 365; 415; 658|];
+[| 519; 228; 523; 208; 330; 431; 823; 41; 820; 593; 289; 95; 166; 534; 544; 942; 577; 106; 561; 963; 229; 31; 326; 922; 753; 727; 874; 12; 481; 352; 209; 342; 279; 873; 825; 804; 398; 860; 798; 792; 471; 347; 975; 786; 866; 750; 795; 21; 446; 55; 909; 160; 874; 46; 886; 259; 305; 780; 966; 231; 752; 11; 171; 649; 997; 796; 629; 476; 937; 745; 958; 637; 751; 917; 682; 348; 629; 847; 73; 180; 453; 309; 47; 339; 668; 441; 415; 713; 438; 58; 315; 933; 671; 983; 654; 516; 431; 310; 171; 463|];
+[| 297; 985; 225; 627; 993; 722; 826; 92; 113; 995; 498; 714; 803; 816; 462; 331; 229; 508; 294; 607; 942; 583; 480; 855; 884; 102; 821; 944; 238; 75; 653; 888; 347; 912; 968; 796; 119; 640; 455; 740; 6; 464; 319; 455; 540; 51; 85; 184; 862; 710; 830; 544; 406; 352; 766; 453; 632; 443; 214; 965; 677; 35; 216; 6; 579; 700; 623; 297; 313; 578; 365; 320; 506; 569; 317; 554; 564; 37; 645; 976; 864; 360; 874; 204; 636; 33; 951; 143; 740; 356; 961; 560; 802; 24; 14; 497; 324; 916; 123; 51|];
+[| 324; 98; 895; 105; 896; 664; 708; 779; 648; 989; 824; 971; 939; 274; 408; 102; 949; 143; 261; 214; 114; 646; 351; 864; 23; 920; 992; 501; 279; 375; 395; 237; 436; 936; 578; 669; 42; 234; 857; 811; 790; 918; 284; 87; 640; 927; 51; 413; 257; 697; 248; 368; 719; 258; 705; 72; 100; 914; 606; 982; 692; 283; 24; 675; 204; 319; 964; 613; 842; 588; 335; 676; 439; 221; 723; 909; 489; 557; 502; 827; 410; 793; 256; 811; 273; 673; 765; 522; 796; 68; 681; 700; 984; 307; 801; 798; 129; 395; 679; 424|];
+[| 986; 596; 799; 236; 629; 385; 96; 790; 283; 403; 299; 416; 379; 276; 263; 635; 133; 382; 666; 483; 478; 439; 939; 664; 307; 933; 447; 863; 564; 102; 401; 413; 171; 166; 24; 242; 622; 795; 595; 64; 563; 47; 584; 944; 970; 735; 751; 497; 270; 627; 143; 756; 983; 584; 464; 857; 22; 46; 89; 204; 302; 569; 892; 660; 630; 880; 897; 458; 32; 390; 772; 255; 491; 862; 336; 453; 597; 832; 189; 245; 304; 92; 691; 712; 454; 707; 783; 219; 891; 723; 0; 892; 374; 907; 29; 466; 839; 682; 608; 404|];
+[| 878; 417; 23; 356; 116; 345; 772; 551; 265; 230; 59; 902; 319; 748; 747; 233; 80; 7; 22; 536; 413; 695; 593; 447; 364; 262; 210; 44; 136; 286; 183; 985; 744; 765; 140; 18; 286; 649; 554; 387; 971; 78; 398; 626; 32; 234; 377; 754; 796; 109; 919; 503; 761; 578; 121; 456; 478; 384; 517; 646; 714; 98; 242; 351; 4; 943; 587; 618; 202; 676; 995; 906; 247; 851; 625; 543; 133; 665; 695; 939; 530; 664; 963; 344; 974; 819; 269; 427; 899; 663; 919; 660; 135; 809; 31; 541; 382; 200; 914; 687|];
+[| 367; 774; 980; 429; 808; 997; 404; 441; 843; 177; 792; 509; 966; 135; 905; 578; 718; 433; 865; 29; 342; 724; 202; 138; 803; 71; 267; 233; 685; 866; 551; 928; 299; 211; 606; 960; 981; 350; 264; 477; 408; 381; 756; 158; 741; 238; 278; 814; 608; 714; 599; 638; 370; 516; 975; 291; 527; 745; 395; 751; 213; 618; 934; 837; 607; 568; 565; 745; 943; 299; 741; 634; 586; 17; 478; 159; 99; 761; 523; 857; 785; 983; 843; 330; 418; 755; 376; 804; 267; 644; 328; 435; 361; 651; 744; 646; 590; 727; 881; 958|];
+[| 984; 569; 207; 203; 88; 750; 706; 3; 37; 600; 497; 448; 173; 758; 860; 295; 533; 491; 609; 510; 95; 200; 89; 31; 35; 430; 700; 667; 26; 848; 623; 802; 270; 398; 213; 217; 115; 159; 296; 226; 753; 151; 911; 24; 792; 70; 378; 752; 791; 300; 975; 768; 471; 29; 999; 417; 12; 317; 771; 888; 519; 607; 722; 305; 594; 840; 812; 351; 75; 811; 259; 598; 306; 536; 7; 953; 13; 839; 301; 196; 957; 858; 909; 858; 937; 693; 908; 774; 281; 448; 978; 989; 367; 228; 381; 724; 608; 174; 423; 453|];
+[| 324; 606; 751; 83; 280; 223; 529; 775; 22; 841; 251; 871; 223; 506; 791; 652; 601; 818; 992; 720; 928; 159; 432; 758; 184; 348; 911; 266; 792; 307; 315; 297; 433; 797; 93; 417; 887; 841; 948; 915; 159; 198; 826; 465; 50; 908; 342; 815; 233; 560; 662; 262; 64; 415; 13; 358; 79; 707; 438; 250; 707; 266; 22; 714; 648; 424; 278; 403; 404; 732; 925; 350; 659; 553; 216; 159; 485; 297; 899; 980; 785; 928; 93; 310; 690; 612; 933; 949; 544; 580; 828; 495; 608; 124; 6; 685; 698; 549; 798; 882|];
+[| 273; 637; 805; 744; 8; 352; 248; 321; 510; 782; 857; 412; 33; 869; 49; 205; 93; 814; 735; 213; 268; 597; 760; 983; 300; 595; 807; 980; 577; 667; 357; 956; 71; 359; 177; 775; 892; 248; 270; 704; 829; 575; 345; 168; 514; 829; 541; 926; 138; 428; 80; 774; 164; 700; 411; 253; 704; 194; 158; 472; 72; 218; 90; 492; 490; 35; 118; 335; 202; 604; 335; 205; 877; 487; 750; 193; 844; 438; 575; 433; 96; 408; 687; 662; 268; 949; 176; 28; 225; 731; 756; 431; 610; 504; 667; 122; 555; 769; 48; 763|];
+[| 127; 134; 16; 708; 757; 899; 491; 149; 93; 834; 174; 632; 403; 711; 300; 642; 524; 826; 9; 855; 957; 921; 353; 734; 723; 974; 908; 974; 368; 90; 132; 567; 511; 786; 340; 914; 233; 591; 766; 436; 451; 888; 453; 817; 599; 762; 989; 74; 908; 496; 377; 854; 331; 39; 216; 383; 166; 447; 224; 803; 459; 761; 722; 752; 99; 312; 579; 680; 642; 201; 286; 78; 194; 191; 786; 890; 438; 898; 14; 745; 268; 6; 589; 490; 774; 626; 320; 508; 275; 831; 52; 564; 763; 134; 915; 397; 936; 729; 365; 792|];
+[| 500; 96; 73; 766; 6; 294; 166; 847; 64; 864; 692; 289; 478; 204; 760; 455; 556; 874; 413; 808; 192; 901; 45; 327; 749; 817; 635; 308; 63; 420; 271; 649; 54; 694; 926; 638; 76; 408; 978; 353; 618; 318; 505; 708; 27; 800; 268; 691; 473; 445; 511; 482; 613; 309; 349; 446; 850; 900; 694; 203; 82; 102; 856; 202; 995; 646; 565; 558; 51; 825; 576; 797; 909; 785; 541; 470; 242; 858; 182; 419; 887; 764; 903; 224; 792; 955; 535; 14; 887; 787; 102; 719; 911; 404; 982; 906; 59; 104; 663; 14|];
+[| 272; 54; 357; 257; 980; 63; 783; 845; 111; 921; 353; 914; 385; 372; 535; 272; 101; 557; 947; 570; 405; 355; 176; 744; 356; 163; 602; 841; 427; 160; 104; 925; 242; 756; 425; 231; 145; 909; 69; 157; 891; 214; 33; 120; 174; 394; 700; 478; 8; 757; 649; 652; 346; 805; 130; 811; 596; 962; 140; 350; 225; 686; 895; 603; 657; 359; 756; 879; 404; 212; 757; 857; 88; 571; 569; 940; 454; 956; 383; 531; 127; 980; 265; 72; 327; 651; 903; 732; 721; 723; 107; 489; 93; 843; 557; 82; 955; 93; 607; 488|];
+[| 365; 251; 463; 814; 818; 350; 53; 955; 600; 754; 947; 652; 616; 348; 152; 823; 329; 31; 815; 792; 130; 470; 371; 562; 152; 670; 800; 498; 539; 684; 69; 273; 335; 535; 240; 682; 49; 979; 222; 638; 448; 71; 230; 689; 784; 647; 650; 400; 673; 344; 824; 950; 483; 497; 284; 746; 734; 305; 631; 90; 827; 853; 859; 422; 57; 938; 39; 92; 651; 818; 156; 704; 253; 671; 206; 836; 180; 278; 111; 690; 876; 636; 581; 776; 410; 183; 242; 688; 904; 15; 835; 616; 166; 173; 740; 56; 245; 750; 59; 487|];
+[| 26; 48; 944; 320; 209; 976; 293; 670; 349; 98; 991; 375; 319; 231; 466; 933; 240; 227; 68; 67; 61; 709; 928; 155; 373; 212; 239; 564; 472; 781; 479; 630; 706; 810; 603; 684; 560; 753; 98; 43; 973; 869; 810; 920; 570; 314; 50; 564; 189; 40; 853; 656; 371; 678; 459; 502; 428; 684; 490; 608; 524; 609; 499; 677; 169; 952; 891; 599; 44; 190; 990; 826; 57; 758; 283; 108; 862; 333; 694; 594; 872; 281; 50; 583; 702; 875; 443; 157; 674; 855; 597; 458; 93; 399; 911; 440; 110; 159; 206; 982|];
+[| 565; 541; 575; 259; 427; 408; 94; 900; 264; 190; 789; 740; 817; 54; 474; 656; 693; 392; 148; 899; 756; 463; 485; 752; 611; 425; 503; 739; 93; 172; 730; 601; 223; 551; 244; 18; 715; 493; 104; 572; 373; 472; 551; 324; 246; 574; 41; 760; 546; 560; 908; 193; 937; 720; 391; 700; 418; 500; 754; 413; 747; 819; 928; 801; 741; 954; 611; 688; 792; 960; 491; 444; 920; 16; 320; 100; 588; 455; 17; 606; 865; 177; 987; 568; 812; 400; 48; 159; 972; 477; 504; 310; 81; 663; 807; 517; 749; 442; 374; 758|];
+[| 952; 772; 319; 516; 456; 461; 812; 22; 62; 732; 28; 386; 681; 368; 837; 619; 130; 50; 189; 365; 301; 669; 128; 467; 735; 739; 374; 874; 535; 218; 525; 754; 951; 463; 504; 241; 738; 480; 435; 182; 498; 879; 742; 300; 916; 76; 593; 363; 637; 526; 250; 838; 231; 176; 723; 356; 30; 15; 167; 102; 289; 155; 778; 816; 868; 654; 430; 530; 789; 796; 561; 873; 267; 838; 258; 274; 155; 262; 520; 680; 558; 373; 665; 504; 522; 209; 19; 182; 489; 892; 252; 12; 854; 88; 169; 658; 728; 860; 158; 763|];
+[| 657; 767; 984; 360; 119; 134; 626; 496; 46; 300; 378; 261; 968; 188; 769; 438; 896; 256; 370; 455; 399; 16; 718; 3; 201; 921; 395; 583; 295; 100; 158; 533; 86; 950; 759; 988; 820; 178; 787; 37; 843; 434; 431; 673; 114; 486; 671; 9; 780; 794; 758; 653; 532; 142; 572; 462; 648; 453; 161; 235; 364; 780; 429; 75; 147; 154; 250; 805; 60; 834; 416; 690; 442; 7; 732; 914; 102; 190; 382; 246; 762; 437; 865; 77; 294; 603; 180; 733; 143; 566; 222; 237; 138; 134; 288; 974; 750; 60; 877; 735|];
+[| 515; 643; 907; 708; 984; 722; 461; 360; 888; 961; 689; 490; 673; 749; 19; 202; 789; 108; 165; 892; 125; 669; 840; 38; 375; 739; 691; 785; 284; 833; 338; 712; 139; 364; 843; 935; 359; 934; 91; 292; 239; 603; 501; 808; 863; 808; 894; 925; 129; 606; 354; 609; 380; 608; 894; 967; 210; 127; 796; 614; 938; 963; 591; 528; 412; 918; 847; 969; 474; 815; 820; 629; 653; 372; 528; 752; 817; 68; 211; 483; 370; 803; 488; 259; 456; 235; 409; 626; 759; 694; 589; 746; 334; 207; 727; 692; 581; 48; 24; 490|];
+[| 615; 138; 479; 250; 430; 821; 771; 908; 333; 409; 622; 903; 676; 991; 378; 191; 45; 296; 335; 992; 107; 78; 676; 779; 950; 797; 138; 529; 579; 364; 627; 509; 908; 702; 559; 657; 320; 599; 995; 335; 902; 941; 954; 935; 949; 960; 220; 669; 568; 700; 271; 122; 127; 563; 447; 730; 364; 771; 328; 206; 434; 602; 87; 875; 391; 369; 113; 797; 457; 444; 174; 623; 439; 76; 825; 381; 893; 262; 498; 365; 880; 67; 106; 552; 679; 790; 487; 745; 421; 488; 52; 606; 470; 923; 94; 814; 420; 402; 367; 471|];
+[| 777; 989; 575; 0; 530; 976; 506; 845; 22; 697; 362; 558; 206; 753; 176; 309; 905; 76; 796; 176; 812; 615; 440; 147; 294; 897; 727; 76; 961; 305; 778; 912; 738; 882; 265; 333; 417; 840; 775; 200; 700; 931; 21; 872; 123; 236; 81; 310; 537; 585; 873; 112; 419; 99; 568; 538; 364; 997; 919; 794; 972; 181; 519; 511; 463; 505; 341; 524; 866; 36; 150; 941; 489; 993; 41; 276; 927; 548; 21; 817; 750; 327; 310; 417; 134; 219; 739; 847; 105; 587; 268; 601; 108; 846; 428; 762; 975; 626; 977; 78|];
+[| 557; 127; 559; 473; 783; 542; 610; 967; 839; 515; 34; 586; 414; 725; 726; 206; 741; 62; 84; 675; 64; 579; 194; 346; 892; 840; 929; 742; 569; 380; 985; 550; 148; 835; 579; 708; 37; 843; 110; 806; 657; 98; 368; 923; 384; 957; 551; 107; 878; 316; 664; 507; 205; 358; 643; 570; 250; 796; 905; 724; 846; 80; 295; 130; 850; 877; 890; 438; 805; 627; 850; 984; 771; 464; 287; 987; 29; 822; 541; 185; 430; 556; 430; 304; 931; 36; 961; 215; 359; 726; 949; 178; 159; 840; 955; 218; 537; 383; 671; 767|];
+[| 969; 580; 785; 75; 176; 805; 666; 451; 185; 210; 700; 19; 710; 764; 419; 497; 891; 389; 442; 22; 708; 810; 435; 265; 324; 575; 887; 332; 718; 405; 570; 813; 647; 317; 136; 511; 683; 353; 806; 753; 889; 275; 716; 170; 518; 289; 893; 711; 596; 140; 437; 743; 585; 818; 869; 605; 69; 524; 515; 136; 140; 640; 732; 458; 895; 982; 122; 365; 501; 707; 319; 128; 223; 161; 904; 677; 60; 175; 60; 258; 237; 54; 808; 357; 415; 883; 203; 333; 357; 93; 734; 767; 803; 873; 409; 198; 950; 833; 727; 674|];
+[| 868; 632; 904; 179; 869; 730; 66; 530; 460; 868; 383; 844; 406; 624; 814; 948; 180; 371; 701; 576; 813; 217; 853; 625; 167; 593; 352; 761; 666; 779; 367; 519; 75; 959; 465; 845; 435; 713; 332; 824; 95; 65; 973; 294; 275; 52; 444; 624; 843; 479; 789; 382; 812; 184; 935; 198; 658; 870; 851; 776; 365; 42; 426; 815; 423; 491; 117; 787; 195; 65; 486; 90; 842; 721; 673; 356; 586; 305; 543; 335; 383; 169; 814; 65; 384; 404; 978; 234; 296; 36; 204; 165; 98; 639; 851; 376; 984; 316; 894; 319|];
+[| 424; 678; 569; 558; 109; 430; 549; 864; 866; 282; 282; 29; 487; 905; 257; 145; 310; 490; 521; 69; 673; 924; 961; 394; 448; 100; 293; 630; 583; 691; 93; 750; 926; 729; 568; 724; 169; 27; 793; 896; 521; 75; 783; 112; 311; 551; 314; 349; 860; 512; 94; 341; 752; 929; 363; 15; 569; 588; 782; 415; 889; 356; 138; 530; 801; 666; 624; 380; 827; 996; 623; 900; 899; 296; 7; 264; 17; 793; 315; 143; 249; 33; 181; 801; 382; 555; 956; 281; 556; 591; 606; 82; 950; 202; 997; 390; 380; 185; 575; 111|];
+[| 885; 502; 515; 779; 714; 172; 651; 461; 623; 261; 576; 79; 702; 791; 332; 781; 522; 869; 570; 185; 762; 565; 624; 360; 533; 518; 710; 286; 961; 531; 549; 801; 925; 485; 68; 442; 563; 124; 485; 537; 896; 196; 75; 800; 641; 19; 112; 922; 367; 831; 34; 299; 911; 941; 521; 751; 688; 373; 258; 770; 703; 189; 741; 241; 310; 977; 36; 963; 760; 417; 577; 702; 284; 6; 980; 70; 164; 750; 762; 918; 837; 949; 448; 437; 675; 485; 505; 424; 743; 291; 350; 780; 663; 266; 675; 968; 303; 911; 865; 85|];
+[| 802; 219; 197; 645; 524; 741; 149; 322; 937; 768; 999; 571; 728; 119; 708; 622; 201; 31; 312; 404; 865; 9; 410; 623; 728; 867; 896; 134; 71; 970; 363; 826; 602; 281; 279; 933; 692; 202; 376; 146; 12; 460; 560; 334; 318; 786; 952; 967; 950; 107; 200; 961; 212; 905; 721; 423; 301; 462; 522; 725; 458; 825; 146; 135; 387; 823; 727; 896; 969; 239; 882; 191; 83; 75; 920; 692; 419; 412; 684; 967; 369; 363; 739; 597; 188; 830; 721; 345; 128; 784; 247; 658; 100; 711; 190; 138; 199; 200; 482; 91|];
+[| 378; 644; 233; 989; 350; 591; 387; 967; 611; 876; 891; 421; 907; 709; 719; 246; 554; 493; 665; 860; 298; 584; 309; 711; 551; 493; 907; 60; 838; 780; 396; 28; 206; 41; 224; 237; 592; 629; 985; 323; 925; 438; 863; 57; 393; 762; 626; 916; 102; 446; 351; 842; 517; 79; 358; 991; 323; 798; 106; 992; 382; 973; 769; 10; 241; 713; 224; 644; 527; 62; 912; 936; 266; 973; 238; 625; 388; 935; 683; 421; 725; 73; 358; 958; 836; 645; 3; 902; 324; 377; 497; 849; 953; 301; 187; 377; 354; 215; 718; 354|];
+[| 718; 561; 503; 907; 558; 310; 147; 559; 180; 841; 753; 527; 228; 803; 738; 652; 875; 440; 952; 651; 198; 810; 221; 692; 275; 589; 340; 76; 304; 538; 81; 978; 414; 514; 269; 272; 274; 324; 708; 24; 35; 456; 432; 904; 814; 852; 211; 283; 593; 403; 289; 855; 317; 203; 285; 844; 925; 252; 785; 9; 195; 2; 779; 629; 526; 297; 594; 545; 555; 996; 810; 292; 378; 711; 581; 40; 155; 469; 894; 42; 144; 735; 462; 68; 439; 804; 599; 453; 532; 382; 234; 922; 46; 560; 663; 579; 151; 619; 512; 962|];
+[| 267; 158; 607; 622; 133; 230; 262; 182; 837; 528; 301; 325; 570; 523; 141; 982; 854; 650; 331; 564; 308; 932; 164; 498; 642; 981; 99; 961; 264; 955; 827; 840; 58; 223; 353; 657; 68; 686; 659; 991; 635; 111; 525; 795; 838; 600; 94; 607; 488; 279; 886; 713; 203; 107; 657; 292; 731; 519; 467; 339; 669; 425; 161; 644; 939; 55; 727; 812; 797; 839; 213; 145; 833; 584; 804; 639; 168; 211; 842; 253; 972; 73; 4; 997; 955; 445; 352; 313; 231; 722; 88; 974; 229; 109; 350; 348; 436; 469; 453; 412|];
+[| 786; 313; 406; 848; 778; 604; 373; 685; 607; 81; 297; 278; 399; 898; 552; 18; 103; 680; 334; 426; 76; 823; 981; 117; 852; 140; 402; 233; 10; 133; 457; 185; 785; 211; 79; 65; 253; 155; 392; 492; 164; 967; 344; 288; 348; 436; 505; 928; 856; 107; 229; 957; 468; 543; 783; 976; 946; 96; 6; 905; 752; 526; 123; 883; 106; 579; 512; 770; 23; 181; 915; 832; 509; 156; 96; 423; 630; 686; 3; 209; 211; 994; 67; 809; 159; 752; 820; 658; 842; 571; 796; 418; 957; 572; 994; 340; 947; 152; 288; 722|];
+[| 549; 983; 337; 805; 677; 278; 888; 137; 204; 131; 165; 377; 738; 967; 752; 852; 463; 768; 605; 28; 318; 640; 737; 751; 9; 212; 433; 479; 714; 121; 781; 718; 949; 78; 665; 284; 635; 250; 446; 263; 72; 163; 849; 933; 831; 660; 471; 151; 307; 801; 49; 711; 191; 238; 672; 789; 756; 531; 946; 701; 880; 711; 355; 729; 864; 904; 934; 64; 583; 153; 400; 375; 62; 84; 494; 71; 103; 689; 713; 857; 291; 557; 245; 712; 291; 183; 18; 743; 305; 827; 400; 758; 832; 62; 168; 375; 758; 38; 496; 870|];
+[| 792; 798; 284; 431; 306; 812; 79; 589; 745; 589; 358; 150; 280; 422; 386; 238; 984; 563; 609; 157; 326; 475; 487; 851; 260; 321; 754; 148; 114; 404; 891; 369; 755; 798; 400; 352; 213; 745; 75; 464; 95; 503; 63; 709; 997; 661; 343; 900; 69; 847; 422; 352; 315; 561; 988; 720; 59; 892; 208; 463; 23; 291; 755; 183; 924; 954; 650; 342; 926; 812; 516; 236; 799; 865; 253; 542; 216; 751; 610; 296; 773; 530; 803; 441; 15; 479; 588; 470; 118; 238; 17; 611; 890; 136; 566; 147; 714; 202; 480; 358|];
+[| 532; 644; 973; 437; 354; 856; 647; 464; 324; 737; 166; 662; 606; 543; 22; 279; 954; 103; 498; 894; 534; 579; 451; 582; 301; 11; 292; 795; 560; 592; 945; 895; 463; 96; 22; 623; 26; 406; 61; 848; 467; 161; 512; 95; 690; 942; 41; 754; 641; 653; 811; 647; 784; 87; 507; 439; 79; 815; 446; 811; 948; 296; 685; 217; 485; 752; 398; 140; 962; 730; 292; 708; 638; 4; 798; 697; 256; 419; 472; 270; 729; 470; 586; 717; 441; 848; 991; 836; 50; 695; 696; 437; 153; 659; 841; 457; 933; 690; 591; 724|];
+[| 396; 316; 856; 63; 63; 734; 58; 251; 299; 371; 17; 429; 490; 74; 864; 936; 850; 335; 173; 185; 391; 573; 183; 899; 203; 951; 641; 181; 395; 206; 414; 296; 194; 256; 634; 337; 148; 803; 373; 525; 379; 632; 410; 485; 168; 996; 427; 124; 264; 895; 620; 630; 761; 927; 855; 254; 798; 677; 610; 784; 741; 847; 793; 775; 547; 950; 354; 501; 264; 633; 721; 813; 411; 685; 556; 828; 512; 73; 684; 836; 73; 362; 810; 438; 984; 788; 438; 517; 53; 103; 487; 446; 923; 407; 685; 973; 927; 988; 662; 643|]
+|];;
+
+
+matrix_mul matrix1 matrix2;;

+ 227 - 0
talks/cant-c-a-thing/code/ocaml_lists.ml

@@ -0,0 +1,227 @@
+let rec mapn f lists =
+  assert (lists <> []);
+  if List.mem [] lists then
+    []
+  else
+    f (List.map List.hd lists) :: mapn f (List.map List.tl lists)
+ 
+let matrix_mul a b =
+  List.map
+    (fun row ->
+      mapn
+       (fun column ->
+         List.fold_left (+) 0
+          (List.map2 ( * ) row column))
+       b)
+    a
+
+
+
+let matrix1 = [ 
+[ 13; 458; 667; 794; 474; 190; 590; 164; 418; 713; 461; 72; 808; 252; 398; 668; 675; 392; 658; 285; 465; 759; 931; 318; 252; 137; 161; 961; 719; 710; 638; 475; 874; 832; 38; 775; 127; 232; 360; 53; 940; 62; 834; 369; 202; 101; 776; 475; 614; 112; 878; 94; 933; 957; 406; 604; 754; 746; 686; 798; 137; 978; 644; 884; 243; 949; 886; 430; 897; 838; 532; 691; 752; 321; 769; 303; 796; 355; 712; 761; 899; 191; 196; 890; 354; 641; 645; 756; 456; 422; 420; 681; 502; 386; 475; 342; 263; 387; 903; 585];
+[ 471; 29; 715; 919; 269; 703; 485; 770; 409; 254; 766; 399; 174; 697; 871; 663; 380; 843; 374; 861; 376; 161; 177; 496; 590; 688; 857; 47; 148; 889; 542; 711; 837; 884; 50; 40; 243; 409; 225; 214; 348; 695; 524; 82; 976; 235; 500; 85; 660; 982; 477; 200; 673; 702; 258; 697; 475; 580; 895; 399; 241; 308; 930; 974; 975; 961; 934; 114; 972; 556; 734; 407; 792; 470; 457; 682; 210; 70; 318; 884; 711; 682; 318; 154; 932; 336; 876; 988; 780; 498; 570; 997; 380; 738; 643; 576; 500; 417; 951; 917];
+[ 832; 881; 641; 275; 630; 836; 447; 177; 398; 30; 183; 520; 1; 999; 941; 108; 465; 609; 893; 320; 359; 422; 443; 319; 446; 263; 2; 670; 464; 407; 855; 133; 107; 287; 627; 742; 782; 850; 897; 311; 641; 142; 467; 219; 832; 465; 172; 979; 819; 633; 122; 970; 242; 412; 560; 644; 724; 404; 705; 89; 197; 43; 173; 888; 518; 104; 758; 204; 445; 31; 837; 708; 511; 140; 909; 371; 382; 295; 262; 144; 617; 943; 209; 216; 485; 541; 77; 737; 708; 787; 696; 5; 332; 954; 519; 374; 488; 84; 176; 365];
+[ 574; 23; 755; 550; 81; 403; 35; 616; 646; 26; 489; 502; 115; 469; 766; 975; 981; 710; 689; 386; 420; 405; 326; 65; 384; 845; 869; 556; 845; 222; 99; 770; 290; 199; 545; 105; 510; 728; 668; 66; 248; 426; 171; 283; 488; 514; 769; 673; 190; 819; 110; 94; 868; 49; 837; 36; 170; 10; 686; 475; 105; 215; 209; 579; 271; 959; 46; 48; 408; 948; 184; 534; 672; 325; 79; 301; 59; 232; 251; 53; 763; 78; 29; 570; 942; 216; 524; 357; 528; 893; 652; 545; 250; 14; 137; 158; 332; 710; 517; 625];
+[ 818; 612; 346; 27; 59; 585; 113; 883; 534; 331; 320; 641; 671; 432; 58; 181; 961; 434; 290; 417; 643; 816; 835; 135; 924; 927; 182; 838; 457; 625; 528; 642; 360; 651; 208; 28; 117; 174; 272; 243; 57; 923; 809; 156; 935; 979; 780; 844; 976; 674; 108; 117; 254; 434; 897; 630; 117; 241; 712; 234; 203; 870; 56; 828; 399; 909; 606; 402; 936; 581; 155; 691; 359; 771; 41; 589; 773; 622; 502; 182; 486; 583; 68; 291; 669; 981; 65; 976; 418; 278; 899; 573; 399; 45; 513; 261; 540; 411; 29; 511];
+[ 666; 292; 522; 214; 224; 159; 941; 805; 847; 174; 206; 116; 557; 570; 181; 749; 523; 791; 180; 802; 802; 690; 726; 798; 967; 398; 528; 528; 53; 719; 67; 201; 100; 995; 945; 252; 644; 328; 15; 60; 934; 849; 239; 242; 169; 131; 468; 54; 750; 27; 700; 704; 89; 810; 951; 599; 959; 279; 578; 364; 512; 269; 478; 570; 735; 792; 614; 198; 671; 140; 553; 311; 813; 741; 354; 272; 743; 829; 376; 464; 294; 111; 61; 791; 774; 754; 659; 180; 273; 959; 379; 113; 147; 279; 749; 728; 727; 937; 25; 257];
+[ 740; 239; 730; 571; 449; 192; 418; 178; 562; 591; 474; 832; 119; 113; 669; 259; 565; 309; 512; 529; 761; 353; 568; 734; 0; 883; 607; 455; 447; 5; 229; 350; 432; 4; 30; 879; 861; 445; 153; 120; 713; 810; 715; 584; 255; 953; 352; 938; 396; 729; 738; 459; 413; 367; 370; 917; 360; 375; 802; 497; 971; 250; 140; 731; 627; 507; 907; 694; 678; 36; 221; 42; 977; 71; 908; 51; 448; 530; 959; 427; 1; 442; 849; 703; 313; 754; 830; 311; 995; 947; 51; 791; 250; 79; 611; 302; 375; 846; 562; 141];
+[ 524; 47; 289; 556; 90; 800; 78; 576; 605; 104; 882; 770; 481; 84; 379; 340; 314; 304; 994; 948; 504; 887; 249; 246; 744; 729; 176; 573; 458; 443; 601; 963; 531; 671; 631; 16; 258; 290; 706; 23; 578; 812; 79; 85; 863; 503; 369; 26; 751; 294; 933; 231; 776; 913; 904; 603; 891; 113; 985; 470; 384; 234; 601; 919; 21; 570; 399; 464; 150; 46; 825; 250; 559; 244; 14; 767; 59; 964; 996; 644; 730; 802; 570; 963; 883; 89; 279; 826; 407; 140; 665; 611; 63; 741; 617; 842; 792; 557; 982; 154];
+[ 104; 977; 251; 143; 854; 149; 416; 480; 898; 704; 578; 132; 977; 511; 15; 172; 678; 862; 400; 293; 157; 407; 344; 540; 138; 752; 66; 308; 427; 641; 608; 369; 406; 182; 549; 858; 985; 752; 219; 235; 532; 103; 618; 522; 470; 156; 704; 129; 45; 133; 478; 26; 814; 987; 479; 117; 488; 414; 475; 53; 870; 232; 166; 22; 978; 181; 529; 480; 376; 25; 842; 153; 141; 602; 147; 199; 408; 673; 196; 576; 733; 488; 498; 521; 93; 360; 836; 534; 220; 599; 982; 461; 797; 269; 872; 46; 710; 367; 739; 41];
+[ 944; 815; 471; 676; 226; 348; 413; 145; 474; 762; 642; 335; 664; 964; 219; 614; 456; 704; 155; 810; 23; 460; 648; 876; 448; 842; 292; 833; 576; 837; 185; 343; 957; 219; 746; 482; 174; 812; 583; 501; 336; 309; 518; 483; 367; 8; 440; 690; 619; 316; 548; 833; 923; 677; 574; 132; 967; 271; 202; 111; 374; 962; 450; 749; 129; 563; 948; 778; 502; 943; 863; 935; 312; 731; 142; 726; 510; 953; 798; 766; 934; 26; 94; 776; 289; 972; 821; 503; 540; 203; 783; 333; 503; 781; 874; 309; 502; 644; 947; 219];
+[ 689; 803; 790; 729; 230; 755; 567; 824; 402; 897; 482; 519; 429; 148; 825; 631; 265; 443; 895; 638; 204; 878; 718; 953; 367; 52; 601; 503; 254; 108; 718; 360; 21; 116; 746; 527; 348; 660; 684; 36; 560; 306; 30; 498; 326; 932; 348; 674; 509; 881; 167; 443; 360; 208; 46; 924; 959; 574; 482; 620; 561; 158; 497; 33; 786; 723; 528; 931; 166; 935; 424; 624; 576; 523; 458; 733; 472; 750; 252; 257; 778; 541; 478; 673; 343; 253; 498; 778; 380; 320; 630; 315; 764; 266; 740; 940; 941; 217; 30; 825];
+[ 833; 651; 195; 221; 32; 666; 999; 967; 651; 934; 414; 224; 184; 573; 33; 975; 223; 768; 330; 38; 94; 464; 358; 119; 900; 880; 141; 835; 345; 520; 45; 302; 609; 251; 998; 38; 862; 643; 535; 811; 525; 284; 540; 529; 124; 760; 176; 81; 590; 93; 378; 947; 195; 93; 949; 470; 405; 971; 106; 16; 367; 242; 193; 217; 550; 178; 368; 806; 220; 296; 245; 604; 806; 241; 437; 866; 480; 411; 233; 321; 579; 46; 107; 706; 313; 322; 183; 959; 274; 868; 426; 156; 684; 458; 220; 750; 708; 750; 188; 343];
+[ 708; 130; 155; 703; 348; 471; 382; 659; 917; 59; 96; 572; 599; 435; 821; 14; 863; 747; 495; 360; 680; 737; 20; 49; 639; 398; 769; 625; 159; 599; 377; 875; 982; 181; 466; 7; 29; 197; 979; 707; 169; 204; 895; 104; 505; 120; 780; 233; 875; 390; 503; 12; 361; 994; 211; 3; 627; 747; 277; 496; 17; 15; 607; 929; 1; 463; 595; 422; 344; 924; 709; 939; 461; 629; 44; 678; 156; 816; 421; 853; 376; 172; 857; 777; 293; 89; 203; 729; 110; 770; 289; 795; 251; 729; 302; 102; 545; 642; 468; 373];
+[ 950; 758; 247; 434; 855; 261; 874; 334; 356; 805; 220; 109; 525; 566; 533; 947; 941; 654; 95; 511; 939; 546; 568; 815; 89; 260; 272; 792; 891; 367; 608; 91; 689; 636; 446; 488; 74; 508; 257; 13; 408; 273; 789; 984; 749; 965; 583; 920; 703; 762; 723; 124; 689; 249; 885; 39; 829; 261; 748; 415; 665; 481; 817; 525; 808; 3; 626; 481; 434; 112; 946; 135; 708; 340; 949; 288; 526; 523; 417; 959; 668; 87; 106; 586; 430; 151; 966; 824; 997; 343; 235; 676; 944; 147; 720; 909; 394; 769; 618; 263];
+[ 799; 8; 776; 287; 629; 611; 860; 539; 22; 707; 685; 40; 548; 156; 23; 104; 751; 759; 392; 941; 520; 193; 978; 686; 396; 123; 151; 513; 570; 944; 516; 339; 195; 96; 656; 821; 273; 128; 695; 937; 230; 714; 936; 220; 783; 375; 489; 512; 173; 358; 702; 989; 438; 938; 772; 149; 587; 453; 373; 6; 270; 237; 169; 344; 288; 520; 166; 872; 67; 334; 409; 508; 729; 149; 540; 916; 519; 541; 115; 58; 580; 311; 115; 648; 640; 727; 373; 837; 350; 739; 610; 325; 643; 348; 917; 815; 990; 416; 636; 837];
+[ 726; 669; 723; 348; 924; 410; 280; 192; 682; 371; 132; 487; 106; 951; 298; 983; 724; 8; 507; 451; 219; 966; 979; 704; 156; 237; 930; 29; 899; 488; 812; 209; 701; 934; 807; 159; 55; 767; 700; 485; 173; 352; 60; 947; 19; 140; 793; 856; 949; 48; 331; 458; 785; 238; 463; 691; 765; 461; 133; 114; 406; 125; 643; 108; 725; 435; 807; 25; 797; 734; 618; 75; 728; 3; 452; 109; 202; 59; 949; 437; 673; 129; 230; 150; 788; 58; 542; 171; 393; 146; 436; 496; 310; 927; 813; 107; 496; 132; 959; 400];
+[ 177; 48; 86; 428; 979; 703; 678; 260; 714; 25; 547; 712; 192; 853; 723; 465; 795; 490; 931; 912; 272; 819; 531; 754; 148; 492; 906; 112; 187; 652; 872; 468; 179; 703; 595; 54; 537; 948; 553; 36; 951; 568; 749; 651; 154; 528; 27; 256; 676; 430; 147; 838; 91; 747; 256; 246; 983; 690; 483; 525; 157; 673; 871; 771; 465; 693; 143; 365; 540; 710; 782; 174; 890; 793; 207; 174; 638; 905; 577; 225; 628; 224; 195; 560; 860; 613; 68; 588; 415; 805; 873; 782; 885; 650; 772; 297; 449; 307; 398; 777];
+[ 422; 178; 282; 640; 756; 342; 94; 657; 563; 336; 87; 763; 710; 889; 632; 757; 76; 954; 651; 728; 824; 195; 621; 648; 386; 493; 941; 970; 816; 198; 450; 809; 760; 418; 558; 695; 331; 200; 1; 596; 954; 428; 440; 41; 531; 682; 117; 33; 498; 389; 161; 298; 199; 462; 273; 780; 289; 613; 764; 646; 813; 514; 508; 311; 52; 896; 816; 434; 488; 79; 806; 349; 592; 20; 424; 916; 172; 837; 574; 937; 374; 213; 668; 358; 714; 851; 782; 617; 336; 132; 914; 202; 104; 224; 676; 674; 349; 795; 308; 794];
+[ 16; 483; 140; 120; 643; 768; 463; 61; 37; 58; 347; 586; 746; 588; 850; 964; 29; 443; 949; 960; 219; 994; 861; 870; 517; 338; 529; 353; 229; 635; 34; 763; 207; 278; 633; 43; 403; 926; 545; 544; 620; 385; 755; 88; 620; 219; 169; 438; 632; 953; 694; 327; 448; 729; 927; 644; 289; 643; 792; 738; 712; 871; 799; 513; 217; 764; 14; 676; 989; 463; 892; 918; 5; 350; 240; 156; 17; 850; 282; 127; 470; 109; 82; 624; 940; 428; 555; 116; 572; 566; 731; 617; 370; 943; 347; 885; 947; 321; 913; 102];
+[ 952; 592; 850; 993; 3; 924; 234; 709; 905; 727; 716; 595; 889; 404; 538; 72; 238; 382; 919; 35; 39; 768; 834; 270; 160; 512; 956; 175; 579; 719; 70; 626; 321; 923; 251; 873; 261; 189; 105; 139; 151; 386; 808; 899; 901; 97; 53; 415; 163; 536; 564; 176; 160; 386; 511; 749; 868; 179; 881; 601; 270; 232; 782; 799; 21; 488; 712; 127; 95; 315; 572; 320; 871; 458; 184; 621; 481; 219; 327; 442; 196; 409; 471; 959; 487; 200; 527; 272; 433; 657; 650; 409; 788; 291; 832; 689; 36; 0; 481; 400];
+[ 924; 734; 63; 557; 915; 936; 250; 720; 481; 315; 153; 122; 21; 727; 371; 8; 225; 826; 827; 344; 235; 867; 629; 747; 213; 786; 851; 780; 526; 56; 233; 204; 123; 49; 864; 403; 62; 270; 980; 732; 165; 40; 590; 522; 862; 214; 496; 166; 366; 946; 76; 703; 744; 541; 227; 293; 983; 841; 442; 455; 670; 45; 564; 905; 117; 692; 903; 540; 154; 73; 136; 932; 969; 563; 514; 969; 50; 322; 447; 729; 285; 262; 663; 83; 963; 456; 745; 699; 33; 875; 590; 469; 541; 846; 136; 700; 283; 869; 974; 224];
+[ 239; 879; 691; 695; 216; 457; 353; 264; 211; 712; 911; 679; 421; 540; 492; 10; 796; 828; 84; 992; 596; 906; 500; 340; 425; 452; 801; 930; 74; 718; 338; 297; 277; 474; 455; 739; 439; 692; 384; 776; 760; 888; 175; 977; 381; 614; 613; 229; 632; 56; 959; 997; 155; 427; 408; 52; 122; 295; 651; 164; 395; 638; 713; 142; 200; 912; 189; 71; 673; 938; 26; 516; 244; 566; 378; 896; 542; 591; 97; 320; 978; 881; 228; 615; 728; 704; 776; 962; 473; 274; 915; 977; 687; 131; 232; 19; 537; 796; 186; 317];
+[ 386; 376; 759; 333; 495; 987; 100; 685; 405; 463; 662; 37; 219; 586; 480; 110; 918; 751; 796; 530; 38; 384; 30; 25; 432; 114; 49; 174; 119; 883; 701; 789; 132; 75; 456; 147; 425; 769; 582; 89; 732; 417; 462; 723; 278; 595; 5; 897; 600; 809; 797; 359; 242; 450; 175; 194; 920; 718; 38; 242; 671; 915; 870; 724; 247; 328; 354; 30; 997; 534; 523; 659; 758; 755; 208; 281; 235; 688; 360; 55; 193; 644; 797; 925; 40; 902; 155; 573; 584; 997; 665; 896; 808; 161; 827; 47; 631; 868; 761; 574];
+[ 347; 127; 476; 519; 358; 390; 895; 21; 811; 806; 246; 53; 934; 542; 250; 694; 855; 290; 294; 657; 969; 896; 427; 598; 703; 724; 108; 242; 202; 44; 222; 99; 169; 821; 338; 631; 937; 412; 487; 581; 824; 873; 636; 555; 529; 866; 156; 502; 381; 524; 693; 242; 380; 3; 423; 820; 380; 740; 273; 907; 53; 361; 743; 889; 746; 297; 585; 374; 650; 916; 191; 807; 128; 258; 16; 299; 972; 581; 573; 419; 762; 618; 484; 24; 908; 744; 686; 114; 417; 166; 802; 745; 577; 734; 647; 689; 979; 552; 783; 914];
+[ 492; 272; 626; 991; 522; 426; 989; 162; 420; 452; 25; 886; 910; 824; 236; 597; 295; 908; 525; 243; 140; 895; 281; 728; 953; 540; 307; 510; 133; 89; 321; 547; 417; 284; 759; 913; 796; 646; 675; 454; 747; 15; 173; 467; 543; 543; 331; 83; 965; 432; 178; 876; 238; 713; 365; 204; 620; 722; 51; 443; 203; 942; 759; 337; 482; 231; 363; 570; 866; 142; 816; 104; 312; 477; 315; 478; 557; 289; 713; 644; 597; 261; 290; 227; 452; 762; 882; 500; 997; 306; 920; 258; 340; 703; 502; 940; 172; 153; 356; 800];
+[ 370; 977; 232; 299; 448; 891; 102; 428; 597; 70; 828; 395; 629; 183; 65; 539; 803; 148; 942; 824; 644; 698; 880; 949; 793; 669; 20; 435; 757; 243; 860; 802; 814; 318; 870; 0; 884; 653; 386; 520; 882; 722; 323; 259; 856; 279; 830; 577; 395; 518; 967; 332; 2; 920; 374; 42; 398; 109; 739; 511; 691; 536; 934; 748; 755; 7; 55; 126; 661; 23; 687; 199; 153; 884; 32; 254; 887; 480; 448; 232; 230; 149; 610; 711; 534; 567; 567; 190; 391; 459; 707; 292; 398; 349; 402; 733; 998; 794; 601; 712];
+[ 285; 622; 412; 377; 604; 165; 215; 583; 140; 830; 2; 949; 438; 866; 642; 542; 412; 880; 869; 306; 251; 832; 604; 413; 9; 342; 79; 147; 747; 982; 275; 240; 394; 77; 52; 596; 22; 446; 385; 528; 109; 52; 514; 384; 383; 267; 157; 113; 835; 23; 471; 948; 203; 387; 169; 81; 738; 218; 189; 24; 626; 398; 483; 392; 573; 993; 983; 720; 666; 23; 344; 698; 726; 911; 587; 332; 918; 696; 606; 930; 92; 946; 851; 305; 919; 795; 842; 47; 721; 972; 942; 538; 917; 465; 887; 66; 117; 432; 522; 680];
+[ 186; 183; 101; 655; 548; 888; 405; 282; 835; 795; 530; 847; 257; 70; 571; 926; 342; 352; 419; 228; 116; 343; 287; 397; 25; 185; 310; 615; 993; 594; 668; 889; 381; 714; 407; 874; 908; 525; 938; 878; 868; 87; 583; 926; 782; 440; 213; 964; 846; 536; 66; 655; 472; 184; 664; 540; 124; 306; 276; 336; 548; 884; 597; 410; 633; 756; 2; 366; 660; 836; 218; 5; 362; 589; 266; 75; 27; 723; 773; 691; 891; 380; 678; 571; 341; 348; 306; 901; 302; 59; 791; 23; 932; 172; 540; 712; 607; 473; 230; 640];
+[ 821; 438; 975; 442; 709; 268; 695; 469; 686; 121; 393; 979; 83; 868; 156; 75; 716; 379; 204; 235; 886; 270; 266; 245; 628; 678; 785; 459; 723; 597; 99; 301; 901; 192; 707; 692; 69; 242; 175; 901; 754; 481; 679; 333; 560; 91; 359; 741; 900; 894; 552; 432; 839; 866; 744; 246; 823; 529; 193; 828; 839; 744; 494; 778; 244; 373; 90; 111; 828; 388; 83; 914; 367; 159; 503; 176; 266; 525; 23; 725; 984; 562; 100; 720; 466; 914; 87; 611; 107; 726; 727; 87; 803; 79; 689; 173; 602; 822; 143; 994];
+[ 391; 754; 164; 791; 822; 431; 156; 633; 9; 898; 762; 50; 270; 90; 928; 691; 382; 524; 238; 795; 548; 131; 886; 893; 48; 36; 973; 668; 675; 593; 489; 121; 639; 772; 613; 756; 881; 632; 298; 703; 341; 92; 705; 190; 734; 859; 19; 302; 156; 203; 383; 732; 40; 191; 893; 66; 980; 759; 175; 69; 778; 659; 102; 510; 50; 338; 529; 100; 102; 613; 151; 453; 833; 492; 626; 506; 836; 155; 658; 466; 637; 906; 502; 239; 306; 665; 245; 580; 213; 43; 373; 769; 539; 70; 733; 53; 242; 943; 432; 129];
+[ 958; 780; 435; 245; 741; 922; 433; 828; 14; 309; 151; 786; 237; 507; 163; 306; 362; 807; 940; 83; 4; 82; 855; 950; 616; 639; 401; 15; 675; 213; 114; 143; 957; 998; 388; 422; 407; 852; 285; 179; 910; 538; 379; 947; 537; 757; 136; 659; 339; 310; 323; 371; 92; 75; 52; 496; 155; 522; 491; 985; 150; 534; 674; 440; 668; 287; 454; 367; 70; 740; 208; 923; 522; 328; 78; 630; 874; 741; 157; 121; 340; 727; 835; 525; 373; 120; 365; 612; 668; 436; 498; 435; 227; 457; 130; 783; 698; 674; 750; 358];
+[ 752; 748; 639; 811; 169; 386; 755; 923; 610; 406; 430; 579; 122; 550; 503; 27; 937; 908; 867; 19; 466; 550; 394; 175; 115; 718; 128; 456; 614; 116; 543; 733; 993; 813; 847; 644; 305; 608; 331; 436; 475; 910; 998; 420; 728; 223; 987; 691; 163; 122; 568; 961; 981; 276; 616; 405; 684; 120; 259; 420; 29; 817; 833; 549; 197; 350; 479; 755; 34; 776; 733; 529; 155; 738; 104; 714; 908; 833; 518; 641; 798; 538; 794; 183; 94; 454; 603; 188; 493; 138; 66; 759; 104; 884; 837; 241; 546; 894; 666; 354];
+[ 543; 651; 287; 308; 110; 188; 968; 950; 593; 484; 126; 983; 274; 647; 386; 510; 604; 649; 125; 994; 379; 309; 632; 503; 812; 358; 513; 145; 569; 213; 519; 638; 40; 311; 578; 761; 22; 785; 168; 543; 973; 809; 789; 388; 41; 969; 464; 624; 514; 166; 328; 912; 706; 540; 657; 735; 367; 776; 25; 106; 449; 758; 897; 869; 369; 748; 89; 680; 439; 144; 480; 488; 130; 204; 479; 581; 573; 167; 497; 962; 385; 339; 597; 807; 851; 463; 706; 481; 408; 761; 688; 14; 432; 854; 981; 890; 222; 792; 616; 395];
+[ 830; 656; 853; 928; 408; 355; 638; 646; 180; 301; 783; 257; 149; 571; 443; 522; 179; 321; 926; 223; 557; 238; 18; 165; 805; 410; 203; 567; 549; 201; 841; 511; 342; 41; 80; 561; 759; 481; 704; 506; 645; 316; 59; 984; 337; 786; 831; 474; 971; 90; 293; 531; 568; 846; 588; 160; 308; 837; 683; 105; 24; 791; 859; 228; 280; 604; 627; 223; 781; 709; 376; 777; 886; 73; 268; 177; 101; 354; 817; 75; 181; 927; 26; 633; 505; 660; 361; 165; 153; 516; 606; 925; 311; 169; 130; 213; 381; 989; 772; 427];
+[ 702; 358; 467; 189; 170; 5; 768; 288; 103; 3; 155; 754; 312; 179; 645; 54; 750; 589; 618; 950; 417; 586; 382; 539; 970; 987; 684; 516; 307; 777; 779; 734; 891; 508; 596; 278; 816; 106; 320; 526; 33; 245; 543; 900; 854; 475; 207; 482; 377; 992; 440; 939; 446; 799; 70; 491; 624; 557; 787; 991; 859; 658; 39; 802; 952; 898; 829; 848; 287; 268; 57; 279; 746; 899; 782; 487; 368; 715; 945; 93; 198; 510; 859; 941; 815; 96; 831; 822; 937; 414; 197; 592; 919; 478; 507; 701; 919; 794; 301; 548];
+[ 176; 43; 493; 300; 286; 807; 706; 313; 629; 560; 329; 558; 722; 315; 638; 12; 684; 144; 657; 107; 456; 690; 371; 526; 917; 699; 962; 792; 640; 755; 136; 734; 348; 601; 823; 73; 384; 240; 186; 187; 929; 718; 349; 981; 74; 290; 703; 846; 509; 906; 652; 762; 872; 556; 928; 461; 334; 619; 114; 719; 828; 449; 596; 616; 92; 278; 403; 770; 284; 525; 649; 102; 316; 493; 691; 726; 413; 283; 671; 906; 935; 988; 409; 476; 276; 890; 572; 500; 111; 559; 126; 666; 754; 601; 144; 445; 912; 145; 645; 367];
+[ 557; 336; 787; 429; 932; 959; 980; 109; 975; 72; 248; 841; 668; 586; 504; 672; 428; 582; 815; 290; 343; 860; 270; 872; 413; 498; 656; 138; 550; 204; 981; 782; 605; 231; 368; 519; 175; 639; 382; 307; 413; 609; 0; 240; 725; 226; 163; 503; 825; 869; 535; 362; 712; 980; 178; 995; 292; 251; 542; 175; 546; 157; 172; 743; 625; 16; 664; 911; 42; 464; 801; 313; 508; 998; 964; 284; 894; 852; 577; 992; 780; 88; 39; 504; 302; 336; 432; 888; 724; 435; 783; 595; 110; 155; 997; 618; 269; 451; 358; 750];
+[ 195; 674; 151; 665; 281; 824; 780; 877; 977; 655; 329; 780; 789; 810; 381; 972; 731; 496; 341; 301; 50; 936; 36; 334; 758; 730; 735; 330; 107; 847; 973; 256; 416; 686; 947; 377; 580; 38; 667; 366; 670; 107; 416; 687; 425; 16; 894; 269; 57; 439; 369; 998; 388; 632; 643; 718; 733; 17; 46; 362; 165; 111; 925; 25; 224; 948; 576; 425; 505; 872; 72; 782; 639; 874; 318; 848; 591; 459; 340; 943; 139; 220; 790; 325; 219; 657; 45; 781; 825; 15; 174; 773; 374; 525; 257; 437; 82; 395; 241; 580];
+[ 522; 210; 868; 79; 613; 801; 455; 715; 266; 198; 240; 957; 860; 523; 69; 908; 664; 393; 647; 61; 211; 462; 584; 262; 353; 997; 467; 983; 192; 568; 647; 315; 214; 516; 147; 576; 992; 552; 997; 116; 670; 277; 817; 310; 286; 603; 614; 192; 894; 841; 411; 971; 403; 510; 673; 905; 146; 444; 850; 802; 239; 330; 732; 791; 407; 418; 98; 314; 662; 226; 89; 187; 811; 45; 698; 937; 110; 819; 664; 298; 373; 471; 932; 26; 787; 430; 811; 424; 135; 593; 55; 863; 588; 596; 773; 81; 15; 397; 998; 73];
+[ 902; 102; 761; 737; 520; 943; 576; 22; 694; 901; 702; 791; 333; 53; 726; 686; 175; 637; 374; 953; 737; 217; 641; 204; 397; 583; 658; 900; 432; 303; 6; 810; 417; 264; 752; 828; 221; 843; 971; 29; 915; 779; 686; 974; 204; 727; 799; 439; 497; 146; 29; 737; 883; 410; 195; 783; 705; 91; 856; 761; 639; 622; 575; 528; 533; 826; 764; 983; 673; 749; 884; 145; 419; 912; 716; 183; 361; 398; 217; 66; 254; 853; 914; 867; 736; 538; 74; 29; 457; 562; 39; 442; 416; 212; 594; 13; 137; 159; 798; 200];
+[ 806; 464; 233; 550; 895; 229; 19; 6; 462; 976; 896; 643; 318; 620; 109; 758; 4; 618; 498; 751; 114; 471; 965; 354; 309; 372; 488; 991; 369; 15; 464; 110; 641; 89; 921; 852; 86; 136; 387; 281; 294; 142; 704; 432; 188; 197; 842; 169; 504; 173; 299; 927; 764; 104; 472; 468; 497; 303; 886; 486; 68; 794; 41; 448; 206; 550; 669; 863; 35; 461; 505; 282; 551; 972; 58; 939; 56; 134; 701; 288; 286; 109; 427; 996; 605; 919; 754; 443; 690; 31; 30; 395; 793; 128; 332; 393; 901; 755; 63; 806];
+[ 360; 305; 726; 670; 640; 860; 408; 962; 803; 189; 524; 424; 327; 959; 401; 975; 456; 65; 73; 20; 948; 928; 206; 629; 280; 283; 633; 66; 288; 398; 431; 396; 583; 544; 201; 417; 793; 934; 239; 166; 890; 76; 288; 543; 614; 275; 500; 664; 723; 878; 419; 121; 653; 180; 393; 283; 9; 347; 416; 381; 782; 733; 337; 506; 567; 334; 802; 560; 323; 70; 861; 74; 331; 880; 180; 546; 875; 202; 378; 659; 246; 84; 160; 174; 777; 805; 680; 387; 360; 781; 77; 673; 646; 297; 631; 780; 805; 642; 986; 168];
+[ 438; 909; 393; 147; 68; 794; 310; 353; 785; 259; 443; 618; 838; 482; 31; 53; 183; 222; 874; 134; 363; 687; 807; 870; 534; 327; 195; 442; 715; 619; 458; 790; 459; 45; 163; 579; 834; 631; 743; 759; 210; 262; 182; 323; 633; 820; 162; 502; 546; 442; 771; 519; 186; 154; 663; 80; 295; 638; 810; 435; 392; 280; 64; 700; 34; 960; 43; 280; 648; 939; 372; 818; 63; 276; 622; 992; 210; 32; 340; 543; 112; 7; 747; 148; 751; 542; 269; 973; 559; 842; 986; 358; 621; 757; 821; 573; 313; 404; 364; 235];
+[ 995; 102; 157; 156; 393; 342; 601; 521; 188; 365; 398; 572; 435; 444; 482; 147; 697; 225; 78; 684; 788; 902; 715; 434; 410; 234; 189; 838; 84; 809; 249; 612; 973; 845; 708; 532; 310; 423; 115; 918; 224; 318; 96; 677; 663; 437; 964; 598; 256; 762; 185; 388; 881; 681; 274; 661; 87; 487; 957; 488; 718; 156; 305; 929; 694; 681; 873; 193; 948; 586; 449; 837; 666; 963; 612; 16; 11; 14; 80; 351; 882; 778; 348; 588; 268; 322; 857; 808; 66; 485; 932; 872; 131; 130; 542; 861; 637; 652; 925; 678];
+[ 399; 883; 908; 844; 371; 977; 645; 68; 142; 437; 102; 642; 713; 580; 220; 644; 573; 860; 644; 961; 855; 399; 631; 548; 516; 48; 419; 387; 445; 0; 139; 15; 949; 137; 66; 682; 583; 210; 82; 238; 887; 327; 754; 392; 458; 241; 591; 77; 283; 543; 497; 71; 297; 571; 897; 553; 788; 668; 687; 742; 724; 656; 563; 824; 672; 501; 297; 230; 867; 399; 174; 863; 736; 281; 859; 564; 64; 576; 780; 706; 83; 863; 416; 818; 882; 825; 554; 522; 846; 100; 619; 409; 858; 106; 604; 358; 84; 469; 907; 945];
+[ 479; 776; 514; 556; 70; 130; 285; 164; 743; 683; 949; 848; 944; 465; 532; 668; 423; 448; 377; 321; 126; 183; 670; 420; 623; 718; 663; 314; 346; 963; 261; 806; 532; 700; 279; 854; 560; 826; 670; 627; 55; 401; 600; 489; 988; 131; 854; 810; 432; 620; 401; 602; 644; 128; 16; 285; 851; 267; 757; 599; 640; 576; 367; 286; 841; 547; 953; 240; 422; 287; 866; 268; 508; 448; 218; 691; 225; 387; 896; 661; 792; 592; 512; 663; 879; 107; 939; 597; 442; 86; 622; 180; 920; 124; 779; 768; 842; 186; 510; 940];
+[ 92; 218; 621; 633; 308; 71; 148; 12; 914; 219; 487; 357; 172; 917; 410; 357; 790; 253; 588; 338; 476; 899; 347; 804; 221; 382; 438; 618; 739; 118; 785; 672; 565; 288; 439; 701; 870; 771; 178; 968; 279; 461; 37; 443; 536; 346; 454; 284; 105; 315; 260; 619; 794; 182; 339; 611; 722; 300; 650; 90; 344; 540; 959; 296; 565; 162; 535; 920; 155; 352; 979; 410; 419; 686; 133; 537; 528; 263; 257; 155; 457; 647; 288; 629; 540; 87; 715; 498; 727; 642; 965; 513; 242; 382; 188; 279; 711; 925; 797; 271];
+[ 345; 580; 999; 63; 651; 525; 771; 954; 946; 536; 360; 649; 170; 743; 628; 171; 103; 71; 425; 209; 292; 528; 319; 501; 420; 666; 430; 50; 49; 824; 402; 975; 198; 798; 948; 975; 523; 729; 300; 115; 950; 722; 999; 325; 727; 763; 290; 977; 927; 498; 305; 558; 800; 346; 732; 828; 559; 352; 986; 94; 774; 216; 572; 258; 632; 639; 789; 847; 686; 76; 911; 227; 714; 914; 764; 263; 203; 867; 154; 815; 121; 533; 113; 833; 137; 491; 473; 434; 971; 183; 797; 546; 366; 673; 109; 914; 117; 250; 922; 973];
+[ 983; 801; 354; 120; 648; 917; 306; 406; 995; 22; 512; 442; 767; 755; 194; 412; 329; 941; 382; 489; 305; 904; 573; 189; 197; 25; 109; 693; 54; 127; 418; 872; 68; 649; 876; 595; 8; 635; 31; 14; 151; 755; 382; 986; 584; 493; 202; 854; 836; 116; 840; 869; 910; 634; 911; 659; 396; 910; 788; 172; 289; 20; 398; 897; 765; 249; 779; 710; 553; 235; 955; 626; 201; 639; 788; 149; 856; 214; 126; 296; 74; 674; 303; 130; 416; 736; 410; 725; 175; 753; 632; 376; 270; 114; 294; 282; 59; 793; 495; 757];
+[ 787; 11; 493; 807; 254; 33; 848; 99; 676; 476; 862; 990; 627; 308; 709; 188; 459; 327; 95; 835; 782; 191; 864; 618; 310; 917; 401; 925; 184; 306; 830; 439; 7; 501; 58; 874; 268; 482; 27; 56; 183; 874; 167; 101; 467; 41; 508; 64; 133; 292; 491; 517; 49; 673; 525; 862; 224; 789; 42; 810; 906; 637; 305; 636; 91; 663; 759; 437; 632; 706; 590; 371; 87; 263; 926; 346; 594; 645; 399; 981; 571; 228; 24; 438; 947; 670; 348; 496; 435; 581; 300; 637; 757; 695; 317; 883; 706; 226; 210; 445];
+[ 958; 461; 56; 421; 682; 860; 811; 823; 348; 292; 232; 942; 221; 490; 150; 805; 386; 379; 576; 527; 295; 934; 316; 679; 298; 676; 215; 142; 223; 879; 1; 524; 375; 928; 180; 402; 912; 341; 83; 228; 785; 505; 485; 376; 538; 897; 44; 468; 378; 145; 355; 589; 498; 502; 100; 239; 608; 789; 738; 571; 762; 831; 107; 905; 856; 209; 744; 53; 334; 951; 313; 924; 943; 528; 738; 56; 283; 456; 696; 478; 722; 649; 360; 147; 121; 522; 699; 662; 227; 79; 147; 267; 572; 756; 705; 891; 171; 595; 544; 130];
+[ 220; 901; 493; 966; 413; 676; 45; 552; 315; 309; 745; 997; 181; 231; 608; 761; 782; 280; 377; 707; 520; 711; 888; 966; 787; 812; 924; 909; 486; 704; 409; 222; 176; 780; 409; 943; 251; 151; 910; 552; 192; 388; 700; 690; 326; 453; 552; 421; 803; 602; 465; 698; 462; 763; 340; 400; 951; 757; 890; 270; 861; 860; 983; 749; 317; 922; 467; 305; 873; 229; 186; 365; 770; 270; 197; 42; 742; 539; 809; 425; 622; 43; 256; 457; 45; 233; 798; 152; 975; 968; 912; 732; 206; 53; 235; 669; 897; 393; 489; 269];
+[ 504; 536; 873; 35; 906; 508; 877; 548; 363; 549; 963; 945; 817; 68; 106; 302; 741; 237; 15; 403; 679; 193; 475; 571; 459; 737; 280; 255; 5; 766; 63; 306; 521; 61; 33; 334; 581; 372; 861; 187; 591; 852; 149; 137; 138; 353; 961; 192; 931; 633; 299; 558; 944; 821; 176; 25; 983; 14; 728; 287; 898; 311; 520; 963; 505; 210; 611; 856; 129; 686; 870; 361; 432; 822; 946; 518; 554; 58; 775; 988; 514; 637; 603; 81; 708; 684; 759; 115; 262; 68; 472; 78; 133; 36; 256; 576; 930; 711; 823; 424];
+[ 353; 407; 869; 653; 839; 201; 930; 469; 413; 980; 216; 990; 332; 247; 825; 504; 351; 329; 7; 656; 766; 87; 335; 780; 420; 185; 440; 848; 573; 458; 662; 289; 390; 824; 831; 604; 618; 569; 272; 947; 37; 865; 402; 442; 484; 869; 621; 448; 73; 862; 154; 180; 437; 148; 817; 139; 79; 252; 603; 962; 530; 143; 519; 292; 661; 77; 851; 193; 706; 901; 710; 595; 905; 753; 771; 924; 278; 913; 161; 504; 619; 146; 42; 903; 133; 740; 805; 594; 93; 605; 76; 751; 785; 519; 416; 855; 593; 27; 948; 715];
+[ 436; 944; 136; 790; 478; 878; 349; 862; 428; 542; 404; 958; 576; 185; 364; 719; 776; 427; 509; 415; 647; 393; 801; 473; 699; 945; 966; 261; 731; 738; 883; 180; 267; 543; 369; 336; 2; 678; 768; 999; 482; 559; 965; 743; 230; 282; 173; 413; 873; 516; 442; 599; 456; 623; 355; 87; 492; 294; 694; 910; 910; 479; 250; 679; 975; 425; 946; 57; 461; 690; 82; 847; 707; 480; 17; 658; 143; 984; 585; 786; 368; 787; 107; 715; 411; 75; 475; 846; 633; 469; 23; 635; 645; 766; 818; 723; 197; 560; 6; 869];
+[ 529; 1; 434; 535; 110; 21; 521; 976; 468; 45; 988; 859; 426; 359; 861; 430; 256; 965; 908; 323; 559; 548; 386; 41; 484; 161; 816; 619; 12; 348; 659; 845; 183; 354; 849; 11; 69; 816; 677; 305; 395; 990; 641; 936; 976; 559; 810; 218; 309; 167; 747; 747; 288; 945; 702; 411; 609; 571; 789; 308; 33; 687; 52; 228; 274; 152; 704; 114; 422; 694; 922; 856; 754; 492; 425; 94; 944; 371; 96; 509; 640; 554; 387; 481; 76; 202; 437; 279; 513; 91; 529; 46; 346; 65; 136; 314; 733; 184; 826; 29];
+[ 96; 760; 913; 599; 849; 413; 885; 848; 867; 877; 518; 17; 17; 716; 312; 333; 56; 231; 611; 761; 35; 913; 993; 847; 516; 584; 671; 889; 85; 971; 701; 855; 266; 516; 683; 485; 803; 698; 183; 494; 209; 444; 381; 714; 840; 733; 258; 757; 813; 581; 182; 700; 390; 918; 281; 194; 150; 818; 350; 33; 764; 815; 203; 51; 683; 608; 828; 563; 48; 835; 580; 770; 697; 870; 331; 373; 738; 265; 137; 895; 860; 753; 314; 575; 43; 560; 374; 696; 341; 840; 398; 821; 658; 917; 738; 846; 847; 937; 203; 500];
+[ 652; 815; 235; 964; 317; 475; 299; 195; 815; 941; 199; 15; 277; 877; 683; 958; 713; 974; 379; 866; 789; 143; 556; 291; 202; 413; 868; 768; 572; 705; 525; 674; 906; 898; 814; 729; 730; 859; 515; 132; 978; 220; 460; 351; 293; 64; 579; 857; 626; 535; 751; 368; 629; 437; 209; 550; 123; 299; 593; 881; 150; 708; 389; 179; 223; 324; 196; 893; 362; 108; 160; 354; 522; 667; 876; 404; 410; 308; 156; 586; 969; 757; 311; 639; 342; 751; 702; 588; 311; 885; 707; 364; 488; 722; 315; 127; 70; 900; 927; 465];
+[ 344; 464; 498; 130; 440; 992; 394; 695; 899; 821; 18; 732; 215; 438; 3; 135; 369; 433; 763; 541; 398; 751; 959; 766; 541; 672; 668; 47; 297; 632; 144; 722; 807; 940; 207; 582; 401; 755; 864; 425; 483; 233; 889; 780; 249; 844; 10; 304; 302; 194; 188; 740; 180; 749; 997; 591; 370; 926; 873; 49; 314; 603; 284; 403; 416; 147; 667; 608; 987; 54; 152; 928; 28; 389; 338; 30; 213; 390; 65; 631; 317; 219; 709; 298; 803; 88; 655; 933; 102; 530; 994; 988; 650; 445; 345; 152; 528; 898; 178; 596];
+[ 400; 844; 91; 574; 583; 793; 852; 997; 477; 28; 359; 268; 361; 904; 447; 194; 353; 534; 475; 428; 866; 399; 438; 296; 141; 784; 284; 329; 752; 146; 202; 144; 893; 38; 89; 490; 422; 25; 631; 480; 242; 93; 814; 753; 518; 657; 985; 555; 912; 351; 822; 620; 165; 190; 439; 22; 978; 449; 381; 612; 200; 814; 128; 7; 984; 567; 249; 567; 242; 563; 962; 55; 698; 998; 985; 437; 820; 20; 330; 910; 361; 349; 23; 41; 209; 175; 14; 442; 507; 664; 444; 523; 242; 783; 8; 695; 74; 273; 699; 73];
+[ 342; 441; 468; 889; 653; 509; 303; 147; 686; 236; 894; 696; 648; 36; 578; 980; 122; 766; 602; 251; 812; 627; 447; 434; 818; 828; 808; 48; 814; 384; 581; 389; 782; 656; 725; 986; 455; 729; 568; 878; 226; 154; 967; 165; 405; 52; 253; 6; 405; 936; 169; 269; 539; 960; 536; 658; 20; 619; 993; 79; 415; 554; 354; 630; 277; 221; 134; 420; 180; 495; 603; 582; 807; 51; 115; 624; 586; 658; 822; 472; 895; 648; 246; 538; 987; 107; 703; 455; 95; 270; 93; 999; 373; 21; 383; 174; 504; 522; 494; 446];
+[ 499; 522; 97; 291; 10; 370; 278; 82; 316; 156; 359; 931; 506; 767; 237; 345; 23; 916; 274; 782; 885; 566; 525; 603; 659; 768; 696; 344; 663; 706; 394; 490; 936; 518; 127; 914; 650; 757; 830; 578; 139; 128; 915; 675; 704; 76; 862; 542; 435; 404; 155; 239; 471; 306; 428; 186; 421; 578; 997; 900; 905; 455; 574; 168; 853; 360; 853; 857; 421; 75; 405; 631; 719; 664; 183; 91; 565; 623; 961; 101; 302; 755; 960; 625; 884; 165; 399; 389; 835; 688; 296; 356; 923; 250; 638; 854; 38; 222; 872; 372];
+[ 694; 678; 352; 71; 197; 336; 610; 161; 796; 657; 16; 223; 914; 857; 34; 876; 146; 268; 948; 545; 858; 613; 479; 795; 413; 812; 779; 463; 577; 477; 994; 617; 577; 453; 987; 804; 576; 605; 968; 810; 662; 681; 866; 31; 575; 162; 707; 505; 417; 937; 645; 739; 956; 217; 635; 794; 48; 806; 163; 572; 907; 110; 648; 619; 3; 434; 272; 938; 492; 986; 91; 892; 102; 268; 725; 540; 403; 805; 940; 87; 250; 161; 238; 781; 583; 767; 556; 951; 678; 382; 414; 257; 313; 411; 752; 128; 309; 265; 627; 820];
+[ 345; 397; 269; 50; 594; 928; 501; 732; 14; 474; 144; 16; 55; 264; 421; 676; 71; 793; 795; 370; 128; 600; 89; 758; 646; 880; 762; 0; 679; 844; 401; 606; 168; 994; 21; 71; 540; 183; 245; 900; 295; 198; 794; 720; 642; 453; 44; 673; 760; 98; 797; 339; 50; 22; 658; 561; 924; 799; 936; 997; 641; 398; 32; 714; 154; 811; 801; 193; 626; 646; 642; 394; 270; 660; 266; 466; 732; 440; 915; 324; 745; 343; 685; 418; 202; 260; 996; 536; 156; 119; 202; 170; 741; 947; 883; 424; 856; 900; 524; 753];
+[ 147; 529; 528; 714; 427; 418; 575; 657; 63; 695; 149; 724; 605; 99; 574; 948; 886; 159; 465; 836; 483; 330; 804; 447; 133; 214; 680; 517; 162; 710; 15; 29; 729; 627; 845; 307; 572; 642; 708; 111; 519; 551; 415; 634; 379; 272; 400; 871; 729; 489; 377; 796; 514; 337; 816; 455; 850; 438; 753; 705; 126; 345; 793; 607; 875; 279; 770; 406; 421; 41; 101; 713; 701; 865; 526; 461; 130; 98; 634; 354; 238; 281; 964; 524; 924; 972; 392; 797; 594; 388; 822; 436; 801; 287; 311; 401; 821; 501; 938; 654];
+[ 376; 371; 406; 978; 954; 794; 384; 994; 770; 670; 281; 274; 955; 836; 178; 132; 651; 471; 728; 429; 881; 970; 28; 259; 852; 897; 106; 375; 805; 129; 395; 745; 706; 885; 615; 517; 194; 564; 244; 357; 576; 944; 188; 682; 528; 673; 118; 39; 480; 374; 255; 66; 676; 494; 874; 610; 128; 547; 505; 784; 293; 182; 186; 938; 568; 378; 817; 28; 128; 962; 953; 583; 728; 521; 681; 939; 284; 52; 77; 130; 788; 984; 877; 611; 608; 997; 304; 473; 413; 535; 580; 606; 12; 397; 79; 36; 954; 46; 663; 421];
+[ 951; 659; 998; 554; 567; 889; 275; 285; 514; 492; 274; 923; 400; 170; 438; 824; 745; 447; 868; 807; 13; 695; 533; 130; 816; 794; 72; 879; 547; 557; 129; 962; 942; 440; 643; 803; 722; 611; 127; 431; 864; 86; 553; 81; 78; 653; 236; 14; 970; 587; 205; 42; 548; 74; 389; 275; 62; 834; 933; 173; 578; 702; 512; 423; 962; 51; 926; 80; 228; 526; 331; 554; 681; 109; 67; 90; 148; 586; 511; 576; 339; 590; 962; 905; 364; 729; 3; 362; 46; 871; 159; 434; 96; 625; 180; 999; 373; 979; 118; 246];
+[ 476; 285; 124; 996; 479; 320; 826; 304; 230; 580; 96; 50; 194; 263; 457; 4; 100; 182; 22; 235; 112; 129; 24; 991; 105; 933; 298; 89; 813; 414; 264; 99; 323; 374; 62; 955; 321; 256; 866; 489; 606; 805; 759; 521; 286; 346; 613; 44; 503; 324; 669; 720; 323; 926; 185; 203; 130; 834; 448; 698; 240; 912; 343; 285; 225; 597; 451; 831; 175; 976; 504; 931; 135; 168; 747; 927; 471; 636; 265; 765; 266; 728; 918; 798; 775; 954; 245; 28; 159; 633; 835; 580; 490; 80; 749; 838; 788; 331; 964; 998];
+[ 106; 377; 538; 81; 704; 880; 743; 661; 900; 300; 753; 827; 918; 251; 993; 803; 50; 815; 662; 520; 756; 92; 304; 673; 554; 40; 868; 732; 481; 742; 689; 597; 481; 638; 566; 984; 418; 403; 431; 599; 932; 899; 917; 509; 74; 418; 216; 939; 312; 438; 572; 440; 775; 796; 747; 142; 407; 226; 951; 862; 4; 353; 469; 530; 237; 170; 640; 514; 432; 927; 552; 15; 428; 671; 806; 541; 594; 539; 53; 729; 701; 778; 624; 386; 749; 227; 87; 642; 68; 524; 435; 91; 688; 494; 378; 164; 464; 70; 78; 73];
+[ 445; 32; 479; 771; 252; 849; 126; 433; 634; 465; 860; 355; 999; 663; 987; 451; 290; 895; 238; 764; 157; 865; 518; 701; 372; 936; 570; 176; 900; 607; 686; 710; 524; 346; 359; 433; 763; 832; 103; 973; 795; 922; 467; 140; 603; 7; 876; 686; 681; 921; 641; 680; 916; 396; 465; 992; 759; 424; 204; 587; 728; 167; 458; 408; 995; 3; 378; 589; 809; 866; 313; 331; 396; 301; 968; 265; 930; 51; 533; 340; 539; 300; 579; 902; 444; 832; 465; 250; 948; 806; 805; 185; 961; 582; 712; 721; 541; 975; 714; 940];
+[ 382; 165; 944; 167; 9; 292; 885; 983; 865; 16; 543; 385; 879; 563; 154; 161; 564; 365; 825; 25; 186; 147; 627; 563; 486; 695; 826; 402; 244; 108; 299; 487; 323; 496; 696; 260; 782; 951; 735; 351; 429; 933; 660; 647; 528; 32; 363; 662; 762; 767; 310; 891; 643; 379; 282; 149; 917; 326; 680; 541; 479; 490; 782; 630; 607; 395; 687; 285; 691; 507; 320; 154; 855; 220; 219; 771; 811; 667; 482; 508; 57; 624; 860; 286; 807; 254; 37; 110; 344; 769; 668; 65; 512; 815; 59; 909; 151; 266; 112; 765];
+[ 126; 267; 336; 350; 819; 322; 862; 547; 730; 497; 678; 237; 867; 199; 128; 900; 665; 555; 227; 746; 130; 796; 308; 669; 802; 420; 736; 496; 949; 751; 785; 37; 918; 389; 233; 465; 685; 958; 879; 821; 916; 681; 646; 333; 412; 381; 230; 851; 339; 564; 16; 396; 201; 169; 280; 661; 622; 589; 929; 112; 958; 282; 19; 588; 309; 214; 32; 110; 336; 145; 46; 953; 888; 307; 960; 830; 508; 258; 987; 17; 320; 396; 384; 965; 287; 309; 641; 452; 456; 162; 8; 493; 84; 79; 407; 239; 555; 105; 689; 8];
+[ 339; 32; 682; 209; 73; 135; 733; 71; 304; 819; 376; 913; 301; 696; 181; 47; 168; 454; 985; 207; 891; 469; 287; 639; 455; 994; 496; 508; 188; 257; 738; 442; 329; 654; 70; 408; 982; 855; 977; 354; 577; 348; 628; 933; 859; 74; 505; 964; 436; 884; 272; 370; 805; 696; 72; 950; 502; 254; 872; 199; 203; 361; 200; 869; 879; 652; 460; 928; 549; 286; 857; 768; 968; 48; 515; 632; 830; 288; 71; 874; 477; 531; 720; 738; 254; 802; 205; 824; 929; 522; 430; 12; 298; 568; 652; 457; 458; 407; 428; 933];
+[ 579; 840; 571; 445; 666; 467; 796; 198; 783; 39; 56; 920; 397; 495; 751; 855; 404; 511; 968; 788; 965; 137; 507; 630; 949; 472; 841; 195; 772; 101; 652; 242; 376; 326; 495; 801; 220; 473; 109; 746; 915; 362; 460; 269; 635; 427; 184; 821; 664; 9; 190; 125; 435; 327; 261; 835; 80; 732; 784; 63; 297; 689; 497; 728; 953; 346; 867; 443; 981; 808; 979; 868; 137; 570; 964; 456; 331; 525; 702; 630; 811; 60; 766; 489; 507; 1; 762; 75; 306; 957; 143; 576; 424; 928; 442; 567; 170; 910; 325; 3];
+[ 638; 927; 498; 668; 641; 288; 781; 73; 86; 947; 190; 175; 945; 946; 180; 450; 197; 590; 503; 292; 588; 399; 192; 620; 275; 294; 690; 714; 590; 827; 986; 871; 301; 834; 32; 240; 344; 853; 615; 522; 364; 617; 319; 578; 920; 433; 32; 182; 727; 234; 813; 188; 965; 630; 625; 136; 464; 74; 138; 750; 762; 164; 822; 233; 895; 68; 665; 592; 526; 450; 287; 6; 117; 465; 211; 166; 379; 572; 117; 235; 62; 33; 416; 816; 755; 175; 665; 110; 334; 59; 341; 947; 45; 129; 175; 871; 334; 140; 739; 342];
+[ 175; 997; 699; 604; 787; 270; 696; 608; 544; 923; 843; 973; 379; 378; 308; 919; 990; 144; 99; 148; 480; 925; 561; 432; 732; 484; 78; 232; 999; 492; 41; 943; 705; 642; 245; 472; 81; 645; 706; 398; 850; 233; 939; 809; 63; 799; 600; 793; 339; 329; 134; 701; 907; 157; 403; 724; 43; 233; 588; 150; 399; 868; 437; 960; 354; 875; 489; 325; 734; 617; 282; 611; 218; 187; 654; 684; 99; 756; 865; 668; 817; 721; 609; 366; 711; 948; 179; 287; 410; 488; 268; 317; 208; 755; 74; 515; 140; 561; 879; 557];
+[ 913; 32; 439; 17; 851; 939; 738; 930; 67; 865; 920; 377; 0; 151; 530; 815; 141; 795; 711; 478; 631; 891; 816; 276; 780; 375; 491; 689; 1; 949; 693; 372; 379; 147; 930; 377; 154; 797; 317; 760; 699; 909; 702; 257; 660; 838; 614; 853; 369; 92; 977; 431; 958; 928; 94; 126; 228; 35; 523; 13; 1; 757; 376; 317; 935; 63; 354; 972; 437; 524; 743; 327; 48; 749; 796; 457; 966; 291; 73; 793; 302; 163; 955; 685; 911; 978; 221; 261; 282; 918; 136; 540; 556; 80; 399; 568; 444; 400; 847; 759];
+[ 832; 685; 786; 622; 862; 855; 796; 721; 64; 497; 849; 247; 396; 453; 746; 146; 38; 928; 252; 422; 848; 923; 300; 366; 64; 546; 4; 53; 224; 420; 382; 414; 599; 609; 524; 323; 554; 805; 120; 536; 326; 994; 778; 42; 31; 543; 371; 975; 627; 879; 163; 709; 386; 317; 501; 649; 267; 4; 899; 306; 433; 246; 641; 201; 17; 601; 910; 379; 751; 800; 58; 486; 71; 126; 270; 773; 985; 624; 945; 997; 183; 546; 368; 638; 611; 9; 489; 889; 754; 878; 626; 623; 713; 898; 232; 962; 156; 893; 618; 960];
+[ 767; 32; 809; 532; 706; 675; 868; 645; 184; 865; 647; 415; 214; 585; 864; 236; 840; 90; 153; 704; 228; 491; 294; 562; 176; 403; 695; 42; 598; 359; 156; 368; 723; 268; 788; 579; 362; 910; 548; 595; 475; 768; 761; 100; 247; 917; 929; 34; 836; 961; 233; 245; 830; 901; 25; 283; 851; 454; 899; 713; 660; 141; 253; 937; 895; 268; 890; 440; 0; 918; 403; 39; 193; 492; 15; 578; 70; 196; 110; 684; 686; 111; 718; 753; 421; 155; 626; 110; 98; 14; 85; 264; 978; 721; 366; 999; 763; 226; 147; 123];
+[ 530; 640; 608; 55; 368; 63; 850; 645; 261; 546; 523; 812; 572; 991; 73; 244; 940; 92; 40; 994; 841; 911; 317; 488; 156; 651; 808; 908; 856; 380; 530; 869; 406; 947; 375; 879; 689; 601; 412; 664; 295; 194; 958; 291; 975; 229; 763; 756; 992; 395; 555; 227; 564; 261; 797; 512; 665; 222; 579; 487; 523; 45; 469; 530; 668; 726; 93; 634; 133; 587; 356; 241; 921; 275; 963; 280; 596; 755; 881; 565; 580; 771; 964; 139; 568; 287; 571; 495; 535; 971; 684; 103; 340; 492; 175; 548; 433; 111; 380; 782];
+[ 278; 234; 678; 37; 87; 23; 534; 544; 295; 324; 995; 24; 224; 663; 991; 985; 419; 327; 808; 864; 63; 986; 115; 363; 53; 12; 506; 780; 143; 579; 617; 301; 668; 919; 674; 42; 841; 165; 592; 981; 249; 896; 120; 914; 62; 559; 383; 8; 942; 320; 995; 475; 865; 102; 61; 928; 188; 131; 201; 509; 14; 276; 818; 262; 665; 565; 648; 520; 155; 26; 973; 611; 84; 820; 826; 969; 597; 632; 894; 508; 140; 174; 969; 128; 630; 322; 376; 826; 934; 321; 820; 9; 292; 597; 877; 528; 383; 629; 167; 981];
+[ 705; 778; 774; 752; 636; 716; 3; 310; 428; 231; 151; 115; 419; 840; 297; 957; 277; 243; 403; 390; 921; 180; 513; 79; 836; 349; 654; 150; 423; 127; 544; 211; 66; 943; 302; 495; 12; 110; 409; 464; 815; 690; 13; 501; 744; 916; 774; 657; 44; 466; 556; 349; 675; 759; 383; 11; 517; 450; 803; 261; 897; 690; 39; 494; 47; 87; 569; 979; 96; 679; 113; 35; 232; 975; 630; 58; 100; 515; 600; 197; 176; 894; 78; 634; 590; 409; 694; 944; 258; 294; 430; 885; 784; 980; 605; 365; 63; 404; 617; 80];
+[ 108; 779; 607; 49; 384; 492; 585; 647; 223; 760; 632; 221; 353; 992; 970; 440; 920; 956; 623; 764; 198; 125; 883; 55; 475; 749; 202; 957; 940; 203; 796; 65; 888; 486; 659; 890; 924; 68; 174; 649; 581; 911; 140; 863; 186; 826; 664; 764; 608; 830; 539; 531; 922; 780; 297; 208; 813; 15; 764; 28; 88; 180; 506; 499; 730; 535; 261; 923; 509; 814; 931; 433; 995; 275; 740; 655; 148; 692; 80; 339; 389; 551; 994; 179; 335; 54; 357; 737; 878; 539; 872; 907; 837; 423; 355; 852; 592; 973; 888; 400];
+[ 963; 523; 230; 25; 32; 421; 761; 289; 497; 136; 430; 47; 472; 370; 906; 984; 438; 19; 782; 354; 584; 941; 251; 400; 964; 560; 875; 875; 466; 184; 787; 378; 21; 599; 566; 155; 521; 844; 82; 405; 490; 569; 862; 871; 520; 74; 330; 548; 851; 509; 593; 289; 976; 111; 224; 757; 874; 280; 464; 869; 628; 878; 46; 795; 915; 787; 396; 772; 958; 132; 786; 943; 134; 726; 645; 22; 125; 239; 282; 362; 7; 145; 863; 182; 118; 459; 842; 638; 709; 743; 48; 391; 672; 753; 873; 638; 335; 942; 266; 981];
+[ 845; 783; 25; 670; 930; 50; 50; 245; 395; 681; 580; 441; 915; 592; 47; 710; 353; 899; 26; 367; 563; 955; 524; 665; 520; 517; 389; 881; 369; 25; 251; 879; 22; 373; 368; 269; 0; 48; 724; 399; 819; 781; 933; 369; 822; 552; 873; 215; 329; 152; 243; 943; 308; 643; 263; 960; 348; 379; 178; 194; 264; 583; 365; 63; 220; 521; 842; 415; 946; 703; 721; 199; 664; 937; 110; 703; 903; 271; 705; 643; 176; 104; 821; 496; 102; 142; 747; 538; 392; 515; 830; 191; 52; 318; 6; 471; 911; 945; 288; 868];
+[ 744; 863; 56; 970; 520; 556; 984; 397; 781; 886; 430; 73; 763; 356; 57; 979; 954; 320; 975; 983; 757; 879; 691; 12; 615; 308; 725; 905; 924; 884; 46; 863; 289; 201; 950; 31; 95; 908; 970; 431; 21; 772; 373; 292; 558; 887; 66; 886; 194; 919; 644; 224; 166; 803; 274; 115; 153; 670; 353; 343; 487; 989; 597; 182; 349; 666; 846; 610; 357; 457; 966; 913; 782; 90; 593; 663; 635; 331; 138; 603; 161; 128; 718; 855; 648; 49; 526; 946; 775; 161; 59; 109; 691; 346; 887; 70; 39; 733; 949; 483];
+[ 24; 241; 989; 625; 686; 511; 818; 726; 132; 278; 852; 916; 199; 722; 75; 366; 86; 273; 518; 698; 842; 280; 959; 532; 985; 395; 399; 324; 355; 480; 273; 782; 27; 313; 541; 671; 778; 876; 411; 719; 915; 712; 873; 662; 686; 913; 428; 117; 787; 210; 89; 508; 432; 807; 253; 858; 220; 139; 712; 927; 503; 724; 974; 895; 974; 944; 551; 386; 892; 490; 646; 530; 772; 591; 530; 937; 66; 773; 855; 520; 403; 534; 730; 310; 113; 515; 338; 771; 524; 343; 6; 555; 421; 194; 892; 744; 523; 885; 713; 421];
+[ 623; 848; 337; 335; 658; 580; 447; 454; 27; 518; 685; 610; 931; 81; 878; 349; 258; 762; 66; 574; 949; 825; 391; 981; 75; 315; 491; 551; 239; 44; 253; 492; 230; 509; 177; 930; 749; 796; 502; 420; 999; 741; 555; 686; 595; 41; 139; 453; 26; 538; 480; 373; 473; 553; 344; 180; 847; 437; 700; 171; 542; 62; 379; 883; 544; 254; 350; 900; 883; 273; 134; 385; 420; 505; 265; 347; 211; 916; 814; 888; 401; 690; 471; 883; 817; 94; 551; 989; 393; 885; 240; 470; 245; 468; 298; 195; 389; 227; 303; 195];
+[ 634; 752; 95; 625; 808; 285; 689; 17; 245; 323; 650; 302; 902; 658; 892; 415; 150; 595; 286; 904; 140; 435; 880; 76; 574; 844; 286; 148; 618; 936; 883; 819; 866; 175; 823; 655; 823; 921; 260; 435; 664; 656; 644; 523; 30; 129; 416; 829; 67; 803; 498; 209; 865; 857; 917; 314; 355; 689; 332; 305; 822; 882; 618; 10; 908; 544; 234; 843; 916; 103; 511; 759; 47; 576; 224; 531; 502; 913; 558; 376; 889; 222; 525; 321; 991; 829; 714; 502; 526; 388; 866; 847; 181; 761; 105; 279; 901; 218; 314; 214];
+[ 196; 966; 786; 349; 289; 745; 925; 375; 287; 178; 759; 471; 857; 901; 385; 162; 701; 468; 441; 890; 314; 789; 307; 588; 243; 925; 397; 854; 38; 288; 377; 55; 193; 56; 36; 251; 543; 342; 776; 777; 278; 542; 136; 329; 547; 558; 456; 806; 667; 260; 279; 456; 357; 893; 272; 890; 318; 842; 64; 738; 233; 27; 697; 898; 790; 570; 330; 588; 328; 938; 179; 89; 684; 892; 377; 80; 274; 650; 564; 711; 823; 302; 752; 620; 220; 553; 231; 471; 360; 704; 815; 948; 147; 790; 817; 286; 891; 357; 76; 836];
+[ 646; 913; 594; 812; 515; 997; 178; 539; 982; 320; 941; 704; 495; 644; 413; 700; 48; 527; 80; 12; 833; 523; 277; 930; 873; 637; 833; 605; 829; 399; 780; 879; 694; 607; 323; 926; 456; 846; 946; 510; 409; 754; 958; 87; 898; 553; 117; 605; 157; 845; 951; 647; 774; 175; 517; 528; 669; 356; 95; 990; 64; 710; 176; 117; 501; 78; 468; 460; 752; 235; 711; 216; 772; 529; 708; 732; 711; 159; 938; 399; 434; 646; 315; 946; 510; 493; 461; 430; 929; 999; 578; 666; 736; 70; 121; 230; 745; 421; 559; 554];
+[ 485; 982; 731; 576; 582; 599; 423; 558; 600; 304; 620; 903; 558; 521; 964; 835; 367; 718; 120; 229; 283; 207; 384; 512; 111; 633; 395; 290; 922; 159; 125; 507; 114; 19; 142; 377; 677; 552; 910; 354; 705; 603; 534; 125; 24; 504; 423; 123; 104; 241; 835; 201; 679; 604; 105; 903; 973; 653; 740; 798; 169; 320; 24; 485; 988; 973; 638; 573; 465; 315; 279; 378; 454; 972; 388; 493; 877; 577; 510; 112; 805; 854; 543; 29; 245; 937; 834; 663; 720; 830; 456; 493; 280; 459; 739; 653; 550; 963; 515; 542];
+[ 843; 689; 938; 870; 693; 557; 422; 505; 786; 954; 32; 604; 514; 626; 314; 378; 93; 771; 162; 751; 177; 374; 70; 733; 857; 894; 685; 850; 149; 302; 942; 276; 22; 924; 494; 687; 143; 397; 863; 980; 400; 531; 427; 890; 245; 553; 934; 376; 267; 67; 732; 986; 834; 321; 138; 698; 867; 943; 842; 497; 335; 128; 301; 887; 951; 734; 860; 388; 792; 49; 510; 882; 772; 92; 127; 240; 218; 34; 625; 233; 730; 837; 30; 250; 334; 302; 848; 292; 341; 215; 441; 90; 563; 97; 952; 359; 923; 77; 246; 746];
+[ 738; 804; 858; 41; 236; 917; 28; 741; 332; 918; 643; 516; 248; 45; 336; 791; 410; 718; 220; 788; 73; 163; 24; 804; 304; 321; 909; 296; 399; 204; 541; 510; 557; 370; 549; 826; 365; 922; 719; 932; 354; 947; 786; 610; 117; 259; 245; 28; 745; 119; 856; 640; 759; 977; 31; 780; 33; 420; 559; 329; 791; 942; 502; 976; 770; 0; 899; 29; 37; 539; 474; 758; 314; 667; 653; 4; 426; 854; 766; 499; 336; 686; 221; 348; 126; 827; 508; 674; 595; 420; 329; 149; 24; 701; 226; 483; 168; 581; 515; 473];
+[ 39; 495; 591; 930; 891; 242; 577; 124; 734; 976; 452; 304; 578; 850; 137; 418; 23; 701; 232; 116; 566; 149; 217; 556; 481; 818; 239; 740; 770; 434; 205; 318; 350; 793; 143; 203; 182; 573; 744; 278; 490; 611; 195; 862; 440; 59; 485; 675; 442; 159; 356; 549; 306; 348; 761; 710; 815; 319; 711; 438; 89; 248; 732; 127; 261; 777; 91; 510; 138; 790; 803; 410; 434; 880; 232; 939; 397; 543; 552; 451; 871; 615; 511; 381; 577; 662; 779; 591; 822; 656; 168; 684; 635; 428; 570; 789; 978; 134; 555; 999];
+[ 209; 692; 95; 73; 279; 954; 27; 966; 530; 504; 459; 896; 151; 761; 196; 899; 339; 477; 811; 348; 506; 34; 45; 773; 511; 665; 514; 468; 646; 237; 951; 984; 89; 101; 134; 231; 988; 399; 624; 423; 96; 713; 591; 504; 206; 110; 949; 199; 871; 812; 91; 704; 405; 254; 697; 696; 726; 566; 845; 367; 365; 519; 130; 518; 485; 375; 744; 556; 112; 482; 689; 851; 452; 507; 577; 80; 794; 331; 458; 981; 331; 358; 825; 351; 322; 57; 166; 189; 658; 948; 504; 843; 599; 425; 79; 7; 838; 705; 108; 302];
+[ 411; 81; 71; 3; 826; 621; 360; 513; 933; 922; 983; 980; 552; 759; 494; 143; 792; 798; 0; 144; 141; 216; 502; 115; 702; 815; 686; 247; 155; 376; 116; 774; 179; 567; 15; 528; 497; 923; 486; 291; 902; 188; 109; 112; 567; 608; 954; 506; 958; 975; 734; 903; 494; 612; 922; 2; 571; 759; 544; 519; 963; 970; 992; 401; 327; 506; 800; 665; 472; 232; 791; 499; 223; 488; 204; 547; 879; 939; 232; 189; 770; 858; 158; 224; 902; 885; 813; 611; 169; 117; 155; 396; 756; 607; 719; 119; 543; 84; 687; 109];
+[ 109; 823; 891; 623; 598; 710; 764; 130; 206; 801; 635; 893; 574; 753; 293; 299; 888; 56; 264; 505; 842; 765; 885; 153; 139; 614; 266; 141; 903; 254; 889; 838; 821; 232; 233; 666; 757; 357; 753; 83; 576; 67; 191; 597; 664; 917; 704; 642; 394; 529; 604; 372; 612; 906; 371; 353; 486; 180; 906; 926; 203; 676; 222; 715; 674; 433; 435; 307; 103; 544; 59; 845; 859; 74; 681; 68; 944; 810; 616; 955; 943; 381; 653; 462; 897; 471; 345; 497; 149; 101; 956; 78; 664; 111; 145; 663; 425; 579; 837; 653];
+[ 833; 626; 238; 515; 430; 387; 764; 687; 225; 828; 195; 167; 409; 612; 583; 839; 707; 126; 462; 77; 956; 633; 473; 794; 132; 82; 134; 157; 799; 454; 808; 436; 658; 514; 890; 901; 209; 778; 986; 362; 976; 726; 472; 952; 881; 55; 374; 697; 222; 350; 82; 43; 346; 336; 318; 221; 909; 793; 107; 742; 607; 481; 621; 106; 358; 772; 593; 952; 688; 955; 884; 149; 246; 35; 522; 656; 877; 882; 362; 505; 80; 936; 367; 389; 380; 452; 296; 520; 374; 651; 264; 516; 818; 37; 791; 847; 883; 228; 857; 785];
+[ 92; 714; 922; 792; 693; 891; 323; 115; 163; 493; 451; 42; 791; 717; 965; 702; 393; 949; 391; 620; 411; 446; 551; 361; 896; 635; 683; 323; 898; 497; 884; 633; 422; 133; 228; 772; 353; 735; 599; 975; 305; 985; 764; 710; 370; 358; 190; 24; 84; 875; 760; 278; 442; 391; 990; 267; 729; 163; 970; 120; 597; 426; 919; 480; 983; 862; 160; 325; 680; 314; 357; 89; 533; 790; 669; 307; 665; 491; 425; 852; 704; 865; 916; 26; 869; 153; 639; 522; 19; 408; 527; 96; 721; 995; 707; 601; 215; 352; 623; 224]
+];;
+
+let matrix2 = [ 
+[ 441; 279; 954; 649; 952; 840; 914; 480; 981; 841; 875; 520; 253; 404; 301; 768; 97; 276; 25; 760; 968; 548; 7; 730; 141; 549; 980; 249; 810; 203; 543; 770; 158; 200; 288; 866; 188; 789; 827; 105; 987; 989; 97; 898; 40; 460; 88; 342; 404; 551; 594; 867; 543; 856; 373; 250; 781; 267; 260; 510; 555; 319; 576; 447; 288; 451; 986; 17; 406; 423; 795; 21; 19; 563; 162; 527; 26; 355; 901; 337; 109; 79; 930; 988; 778; 330; 682; 728; 95; 982; 810; 10; 44; 660; 323; 233; 869; 858; 661; 923];
+[ 483; 792; 816; 997; 368; 441; 223; 514; 282; 453; 322; 195; 828; 326; 600; 188; 191; 697; 212; 626; 580; 349; 874; 817; 670; 659; 721; 958; 532; 820; 663; 636; 122; 542; 66; 636; 432; 207; 138; 543; 461; 625; 166; 234; 603; 32; 468; 183; 977; 483; 108; 233; 53; 248; 544; 955; 243; 20; 21; 115; 259; 892; 60; 888; 744; 262; 770; 958; 814; 173; 986; 978; 90; 729; 53; 913; 290; 19; 840; 570; 91; 707; 256; 419; 663; 513; 894; 441; 97; 283; 783; 880; 559; 671; 53; 148; 583; 810; 31; 157];
+[ 55; 886; 77; 224; 564; 107; 116; 313; 912; 282; 367; 641; 354; 150; 118; 453; 181; 176; 567; 848; 651; 697; 442; 601; 917; 467; 900; 265; 832; 572; 436; 505; 555; 688; 414; 673; 830; 688; 606; 940; 215; 651; 414; 957; 337; 630; 731; 996; 787; 995; 371; 973; 401; 296; 389; 722; 315; 675; 410; 831; 676; 454; 535; 349; 810; 151; 9; 57; 290; 681; 340; 924; 493; 147; 160; 427; 998; 156; 813; 269; 368; 558; 182; 949; 485; 823; 269; 551; 504; 174; 902; 652; 874; 343; 780; 814; 202; 504; 382; 313];
+[ 437; 847; 409; 389; 903; 707; 570; 580; 7; 374; 819; 73; 312; 886; 322; 878; 997; 381; 817; 311; 121; 785; 78; 16; 550; 90; 254; 879; 37; 634; 650; 8; 446; 317; 715; 918; 883; 195; 139; 928; 239; 900; 358; 670; 37; 91; 291; 207; 304; 36; 624; 300; 357; 285; 569; 143; 908; 924; 298; 739; 352; 141; 592; 45; 309; 633; 99; 785; 17; 712; 324; 686; 324; 484; 181; 675; 431; 796; 72; 928; 854; 13; 160; 937; 684; 34; 409; 652; 49; 103; 10; 639; 940; 590; 20; 467; 405; 172; 146; 703];
+[ 441; 951; 730; 441; 425; 934; 284; 683; 888; 603; 134; 826; 676; 915; 414; 703; 83; 127; 556; 621; 248; 965; 789; 740; 633; 575; 617; 522; 729; 855; 187; 722; 714; 841; 25; 443; 136; 53; 120; 898; 852; 589; 719; 93; 58; 826; 536; 565; 816; 483; 884; 598; 837; 102; 556; 878; 878; 194; 748; 952; 487; 516; 210; 195; 828; 25; 171; 125; 616; 143; 472; 574; 326; 549; 633; 20; 806; 766; 728; 170; 24; 412; 408; 49; 847; 75; 554; 1; 507; 740; 908; 753; 388; 935; 235; 280; 863; 631; 127; 983];
+[ 694; 755; 12; 698; 999; 873; 725; 673; 590; 272; 818; 772; 159; 716; 116; 746; 417; 202; 655; 721; 383; 67; 186; 507; 160; 107; 257; 44; 243; 145; 836; 109; 883; 713; 190; 425; 59; 641; 465; 8; 986; 877; 521; 254; 493; 241; 587; 332; 436; 150; 24; 671; 604; 339; 423; 242; 213; 785; 225; 300; 288; 567; 641; 490; 580; 472; 532; 421; 264; 538; 539; 550; 996; 173; 717; 282; 864; 18; 458; 569; 365; 483; 146; 450; 613; 123; 123; 824; 721; 595; 30; 6; 628; 717; 849; 523; 652; 486; 28; 155];
+[ 195; 174; 617; 548; 536; 29; 513; 869; 469; 223; 220; 588; 182; 491; 849; 690; 16; 453; 154; 452; 557; 528; 750; 3; 654; 759; 746; 314; 686; 724; 242; 395; 808; 693; 652; 772; 335; 507; 33; 859; 104; 920; 508; 970; 620; 193; 398; 809; 664; 372; 924; 382; 172; 379; 455; 980; 264; 824; 913; 559; 919; 369; 712; 48; 251; 278; 197; 918; 777; 826; 521; 968; 50; 840; 935; 680; 393; 499; 217; 912; 773; 210; 181; 558; 405; 837; 856; 861; 920; 226; 561; 332; 54; 658; 169; 958; 827; 739; 138; 36];
+[ 774; 538; 230; 497; 912; 402; 83; 912; 435; 698; 117; 591; 384; 131; 977; 40; 950; 30; 173; 398; 310; 374; 858; 789; 717; 844; 545; 696; 732; 574; 948; 697; 196; 273; 987; 303; 846; 631; 149; 887; 475; 514; 561; 708; 69; 96; 721; 87; 185; 165; 958; 983; 603; 85; 582; 734; 757; 566; 495; 612; 340; 161; 8; 991; 579; 309; 184; 922; 655; 5; 609; 328; 894; 668; 971; 177; 800; 397; 180; 346; 144; 116; 401; 937; 84; 72; 627; 563; 865; 694; 381; 149; 82; 894; 907; 583; 441; 81; 84; 452];
+[ 136; 956; 39; 194; 120; 362; 799; 99; 819; 108; 290; 182; 688; 294; 959; 204; 889; 444; 203; 357; 353; 149; 529; 222; 876; 75; 536; 271; 596; 623; 224; 438; 824; 565; 257; 117; 770; 775; 557; 381; 353; 417; 713; 905; 729; 581; 370; 378; 200; 382; 199; 990; 108; 576; 614; 87; 179; 590; 317; 261; 834; 44; 766; 936; 960; 751; 472; 514; 685; 693; 31; 243; 311; 263; 998; 917; 583; 193; 757; 224; 276; 662; 92; 79; 23; 120; 613; 823; 334; 974; 96; 674; 395; 174; 167; 403; 780; 374; 387; 102];
+[ 778; 196; 562; 870; 74; 279; 202; 149; 786; 738; 917; 719; 716; 493; 327; 588; 436; 356; 381; 164; 946; 161; 21; 783; 108; 377; 389; 2; 745; 597; 305; 456; 347; 454; 900; 943; 846; 815; 143; 540; 192; 997; 430; 339; 550; 146; 485; 750; 342; 4; 100; 776; 14; 68; 635; 318; 871; 6; 293; 932; 245; 227; 80; 471; 56; 181; 718; 850; 567; 796; 183; 903; 166; 984; 382; 561; 333; 670; 339; 159; 587; 126; 675; 373; 139; 453; 508; 735; 463; 354; 426; 824; 276; 479; 717; 202; 598; 810; 820; 203];
+[ 31; 876; 240; 137; 950; 855; 487; 753; 554; 31; 28; 20; 924; 404; 119; 710; 161; 271; 677; 459; 866; 686; 503; 697; 254; 355; 702; 960; 448; 635; 293; 606; 618; 311; 417; 184; 706; 249; 141; 895; 421; 662; 550; 16; 587; 981; 166; 834; 39; 174; 403; 163; 764; 179; 5; 935; 887; 670; 598; 402; 637; 309; 478; 934; 746; 194; 713; 207; 826; 602; 484; 981; 561; 978; 300; 284; 266; 939; 952; 514; 553; 387; 975; 380; 909; 20; 382; 458; 430; 608; 911; 934; 767; 475; 804; 14; 113; 241; 200; 413];
+[ 162; 469; 576; 680; 795; 354; 91; 136; 450; 764; 173; 925; 989; 494; 158; 7; 499; 483; 936; 439; 253; 465; 339; 431; 504; 966; 381; 581; 983; 794; 788; 758; 361; 405; 623; 808; 90; 6; 213; 38; 509; 418; 50; 207; 817; 735; 656; 682; 942; 833; 285; 673; 397; 163; 764; 928; 235; 668; 752; 902; 574; 918; 676; 821; 623; 340; 513; 175; 187; 50; 73; 899; 412; 555; 410; 747; 686; 281; 733; 484; 690; 791; 391; 187; 875; 480; 408; 835; 648; 526; 673; 916; 551; 466; 601; 325; 304; 248; 237; 358];
+[ 221; 754; 971; 735; 372; 83; 236; 950; 708; 563; 411; 583; 930; 361; 603; 709; 731; 521; 310; 980; 990; 943; 473; 986; 200; 792; 62; 937; 34; 503; 514; 842; 305; 933; 115; 731; 660; 132; 570; 48; 337; 610; 641; 6; 222; 746; 616; 410; 145; 655; 930; 866; 833; 158; 287; 893; 965; 397; 602; 773; 833; 109; 626; 587; 273; 870; 277; 589; 850; 287; 809; 363; 955; 319; 766; 897; 45; 121; 441; 621; 970; 902; 892; 965; 639; 788; 634; 692; 727; 573; 298; 898; 299; 973; 937; 282; 48; 645; 153; 572];
+[ 978; 396; 738; 290; 850; 165; 953; 155; 523; 907; 438; 118; 676; 144; 402; 149; 402; 588; 773; 419; 745; 317; 647; 294; 856; 781; 656; 630; 220; 249; 258; 219; 638; 794; 862; 902; 750; 469; 282; 533; 528; 214; 899; 328; 129; 875; 92; 978; 968; 569; 151; 602; 339; 165; 374; 154; 80; 537; 526; 281; 735; 462; 762; 862; 906; 331; 409; 99; 99; 935; 172; 174; 717; 703; 457; 634; 112; 371; 335; 701; 976; 991; 817; 182; 814; 336; 377; 613; 348; 361; 906; 107; 918; 328; 3; 41; 579; 378; 116; 810];
+[ 262; 1; 167; 365; 518; 949; 869; 250; 482; 407; 606; 319; 448; 594; 697; 376; 859; 224; 245; 915; 516; 96; 694; 728; 302; 526; 809; 879; 970; 660; 712; 478; 529; 194; 385; 881; 595; 287; 70; 613; 847; 978; 322; 18; 408; 488; 701; 157; 546; 950; 368; 423; 562; 457; 804; 525; 60; 813; 385; 432; 370; 879; 81; 226; 742; 419; 965; 323; 739; 486; 864; 63; 140; 670; 216; 898; 111; 24; 465; 582; 129; 383; 392; 62; 436; 465; 497; 112; 599; 853; 395; 681; 792; 278; 305; 203; 917; 947; 519; 683];
+[ 24; 828; 335; 457; 672; 58; 979; 236; 589; 660; 280; 196; 213; 200; 968; 740; 711; 99; 190; 657; 507; 242; 804; 102; 561; 999; 890; 372; 698; 619; 96; 78; 741; 349; 209; 837; 550; 61; 142; 54; 272; 316; 973; 168; 71; 892; 893; 785; 857; 161; 62; 554; 190; 696; 30; 55; 748; 395; 55; 119; 511; 979; 776; 412; 65; 3; 435; 551; 356; 350; 988; 240; 260; 209; 948; 592; 690; 826; 433; 997; 356; 211; 890; 792; 541; 476; 529; 935; 697; 777; 108; 396; 648; 131; 167; 254; 510; 969; 543; 495];
+[ 422; 112; 540; 74; 165; 34; 293; 110; 483; 634; 185; 895; 84; 150; 781; 224; 991; 115; 807; 242; 340; 512; 109; 217; 255; 913; 898; 206; 975; 756; 98; 456; 695; 686; 50; 720; 610; 201; 94; 742; 186; 110; 582; 114; 889; 159; 955; 163; 388; 685; 688; 874; 537; 390; 950; 155; 430; 664; 947; 222; 772; 244; 714; 94; 497; 718; 608; 658; 449; 766; 875; 784; 734; 263; 993; 34; 269; 978; 790; 993; 277; 143; 529; 918; 919; 558; 638; 134; 822; 105; 766; 28; 828; 461; 518; 289; 453; 680; 102; 597];
+[ 333; 538; 184; 445; 751; 50; 370; 58; 373; 357; 839; 956; 956; 720; 580; 510; 586; 968; 626; 188; 750; 518; 548; 563; 422; 316; 723; 867; 849; 755; 726; 143; 46; 457; 55; 891; 592; 18; 465; 787; 836; 333; 948; 760; 928; 903; 875; 98; 696; 1; 909; 956; 244; 547; 270; 854; 272; 136; 763; 194; 348; 130; 499; 157; 272; 873; 679; 935; 620; 687; 889; 110; 12; 184; 344; 442; 184; 542; 692; 518; 477; 434; 344; 536; 100; 816; 153; 939; 70; 803; 970; 332; 489; 732; 761; 820; 256; 846; 966; 955];
+[ 696; 801; 249; 667; 447; 127; 422; 648; 524; 215; 126; 448; 445; 693; 604; 193; 114; 811; 500; 461; 277; 928; 802; 181; 259; 647; 906; 126; 361; 549; 657; 968; 510; 735; 696; 471; 864; 264; 266; 626; 506; 410; 669; 179; 768; 743; 659; 235; 112; 893; 962; 694; 467; 197; 568; 300; 476; 572; 590; 312; 30; 158; 38; 684; 219; 98; 321; 40; 839; 64; 796; 888; 109; 577; 363; 813; 758; 428; 23; 546; 742; 704; 857; 451; 5; 221; 155; 396; 697; 713; 110; 513; 479; 109; 932; 244; 879; 139; 564; 838];
+[ 927; 542; 126; 712; 97; 84; 571; 887; 775; 648; 411; 884; 980; 873; 876; 391; 836; 867; 971; 277; 905; 309; 377; 932; 350; 779; 838; 638; 88; 397; 256; 826; 401; 134; 735; 665; 827; 720; 432; 616; 475; 767; 842; 792; 582; 515; 740; 231; 253; 664; 216; 604; 683; 306; 316; 419; 192; 978; 428; 737; 711; 484; 786; 906; 87; 703; 824; 611; 307; 153; 251; 708; 526; 815; 710; 339; 290; 732; 712; 280; 567; 219; 2; 865; 392; 598; 700; 475; 101; 941; 28; 445; 388; 104; 948; 815; 639; 942; 619; 176];
+[ 388; 828; 511; 554; 262; 928; 333; 381; 551; 618; 696; 383; 62; 972; 313; 962; 601; 261; 401; 536; 836; 565; 206; 457; 671; 30; 899; 566; 763; 840; 40; 98; 0; 926; 944; 344; 821; 418; 131; 849; 696; 841; 457; 517; 566; 371; 323; 109; 552; 527; 169; 963; 487; 279; 424; 30; 19; 512; 717; 412; 945; 751; 848; 89; 725; 87; 395; 530; 245; 678; 978; 939; 753; 297; 440; 879; 25; 915; 375; 582; 856; 729; 790; 667; 408; 628; 497; 875; 11; 442; 778; 425; 987; 71; 717; 988; 378; 88; 125; 170];
+[ 138; 973; 776; 541; 679; 589; 474; 139; 827; 963; 527; 327; 720; 908; 156; 507; 163; 695; 413; 997; 437; 675; 997; 694; 829; 582; 842; 242; 312; 109; 206; 246; 540; 896; 212; 419; 475; 671; 35; 539; 19; 682; 278; 371; 413; 916; 772; 121; 516; 774; 78; 910; 809; 80; 631; 607; 401; 229; 359; 281; 189; 891; 215; 199; 382; 702; 836; 395; 176; 348; 591; 510; 941; 610; 7; 369; 515; 534; 964; 176; 965; 548; 251; 726; 661; 318; 647; 313; 747; 649; 830; 761; 610; 206; 202; 265; 656; 679; 503; 987];
+[ 960; 534; 850; 481; 839; 206; 353; 760; 316; 16; 919; 798; 693; 977; 576; 310; 715; 704; 600; 533; 597; 163; 205; 525; 934; 309; 286; 239; 158; 149; 389; 579; 656; 896; 577; 74; 618; 330; 644; 890; 688; 885; 851; 562; 640; 762; 460; 628; 283; 54; 450; 854; 183; 49; 815; 466; 114; 718; 263; 849; 474; 31; 561; 153; 373; 902; 154; 732; 683; 594; 79; 246; 717; 872; 6; 396; 703; 322; 525; 123; 498; 677; 24; 848; 849; 449; 780; 81; 616; 83; 428; 259; 854; 560; 41; 242; 945; 569; 447; 529];
+[ 953; 751; 515; 618; 258; 198; 129; 898; 610; 211; 348; 409; 962; 177; 240; 719; 790; 18; 129; 172; 121; 503; 114; 594; 562; 781; 172; 462; 190; 827; 529; 276; 361; 165; 739; 447; 712; 3; 798; 229; 287; 7; 858; 72; 536; 504; 267; 363; 223; 723; 194; 105; 628; 221; 911; 368; 198; 578; 240; 418; 587; 892; 909; 470; 35; 217; 221; 821; 932; 76; 753; 938; 172; 696; 144; 84; 375; 36; 299; 165; 995; 282; 511; 469; 917; 36; 828; 830; 950; 460; 792; 9; 293; 482; 290; 310; 630; 268; 358; 625];
+[ 203; 54; 356; 983; 226; 436; 950; 167; 688; 150; 546; 975; 710; 327; 339; 521; 712; 318; 533; 137; 855; 716; 641; 662; 344; 878; 890; 900; 826; 124; 221; 305; 909; 397; 523; 216; 581; 250; 58; 143; 176; 812; 695; 645; 603; 764; 774; 79; 947; 280; 860; 605; 312; 957; 752; 276; 353; 859; 245; 324; 297; 236; 387; 625; 312; 120; 769; 839; 236; 204; 29; 275; 753; 256; 920; 255; 512; 176; 916; 292; 787; 773; 169; 134; 773; 586; 686; 440; 635; 481; 626; 225; 282; 45; 65; 278; 494; 105; 754; 374];
+[ 171; 86; 547; 990; 449; 64; 908; 200; 207; 306; 640; 124; 648; 872; 319; 284; 438; 596; 356; 458; 364; 419; 542; 372; 508; 308; 578; 637; 649; 800; 291; 830; 501; 103; 19; 5; 841; 4; 345; 2; 338; 584; 481; 894; 771; 719; 690; 847; 382; 929; 311; 477; 9; 882; 682; 617; 586; 812; 323; 11; 135; 877; 933; 57; 10; 746; 570; 323; 419; 873; 572; 368; 56; 876; 410; 841; 372; 403; 74; 624; 98; 863; 294; 535; 732; 107; 490; 731; 991; 903; 351; 882; 303; 870; 749; 437; 37; 9; 566; 472];
+[ 595; 343; 596; 193; 549; 195; 378; 187; 414; 599; 508; 781; 316; 422; 773; 349; 238; 945; 939; 138; 745; 513; 42; 37; 2; 775; 54; 691; 558; 977; 363; 922; 4; 581; 56; 9; 299; 956; 949; 935; 567; 583; 288; 219; 605; 813; 409; 528; 105; 115; 600; 528; 174; 364; 562; 555; 65; 166; 578; 494; 386; 396; 147; 315; 221; 548; 930; 31; 545; 176; 83; 721; 356; 306; 270; 158; 158; 485; 999; 295; 959; 845; 590; 236; 772; 559; 783; 537; 716; 182; 791; 103; 580; 646; 677; 247; 132; 839; 775; 488];
+[ 781; 240; 951; 639; 806; 23; 471; 805; 755; 373; 584; 276; 400; 163; 860; 544; 755; 647; 69; 22; 667; 167; 758; 55; 432; 576; 109; 477; 531; 932; 428; 402; 360; 385; 630; 473; 130; 383; 465; 445; 634; 266; 205; 535; 911; 723; 35; 764; 747; 338; 920; 52; 355; 335; 698; 367; 45; 432; 356; 446; 912; 226; 702; 976; 532; 239; 691; 719; 941; 898; 108; 909; 612; 184; 336; 314; 880; 413; 861; 79; 432; 349; 543; 712; 165; 475; 712; 10; 588; 29; 997; 922; 953; 502; 841; 828; 653; 279; 628; 792];
+[ 218; 938; 972; 686; 331; 346; 69; 683; 752; 956; 83; 503; 973; 916; 965; 207; 344; 664; 268; 634; 93; 316; 970; 218; 943; 838; 584; 533; 258; 859; 662; 908; 68; 654; 352; 71; 243; 913; 494; 337; 810; 515; 781; 9; 922; 56; 809; 501; 131; 685; 276; 598; 188; 927; 182; 545; 857; 20; 48; 2; 248; 448; 256; 830; 135; 360; 450; 942; 344; 386; 527; 270; 424; 63; 51; 612; 124; 582; 528; 152; 868; 772; 223; 505; 268; 441; 28; 563; 607; 555; 229; 45; 303; 172; 261; 123; 854; 623; 303; 35];
+[ 374; 739; 676; 781; 317; 377; 785; 393; 783; 801; 983; 75; 366; 464; 473; 472; 163; 798; 637; 602; 528; 505; 837; 265; 710; 936; 522; 908; 257; 934; 631; 696; 467; 619; 653; 17; 546; 379; 988; 829; 656; 52; 380; 877; 260; 418; 447; 237; 584; 490; 977; 682; 817; 670; 709; 80; 386; 19; 408; 977; 649; 983; 36; 995; 213; 587; 953; 693; 360; 744; 949; 557; 210; 746; 476; 828; 138; 886; 250; 124; 413; 175; 484; 708; 425; 799; 24; 543; 506; 570; 678; 1; 401; 974; 57; 130; 216; 216; 237; 153];
+[ 937; 536; 860; 128; 362; 493; 214; 872; 854; 358; 741; 951; 270; 177; 147; 608; 699; 391; 170; 364; 506; 157; 886; 196; 671; 231; 321; 487; 430; 888; 607; 778; 872; 379; 280; 107; 525; 946; 693; 978; 886; 395; 549; 884; 470; 331; 228; 712; 566; 914; 636; 268; 956; 542; 195; 691; 933; 337; 993; 113; 747; 597; 537; 311; 783; 385; 684; 869; 793; 626; 541; 662; 572; 947; 523; 908; 83; 233; 169; 860; 545; 904; 965; 657; 402; 628; 270; 793; 102; 132; 360; 216; 259; 237; 645; 466; 163; 489; 360; 708];
+[ 896; 428; 769; 716; 697; 610; 262; 464; 346; 501; 961; 953; 502; 932; 627; 98; 785; 822; 489; 894; 1; 831; 296; 483; 260; 591; 987; 567; 785; 266; 622; 252; 89; 719; 413; 264; 726; 973; 358; 132; 147; 691; 443; 991; 522; 417; 100; 922; 522; 966; 681; 691; 548; 273; 47; 825; 183; 29; 151; 561; 8; 715; 615; 467; 6; 953; 962; 344; 110; 258; 800; 874; 183; 71; 420; 567; 733; 444; 786; 839; 857; 646; 91; 196; 956; 759; 949; 545; 93; 13; 184; 837; 634; 203; 532; 700; 109; 505; 378; 850];
+[ 215; 708; 799; 796; 456; 187; 971; 343; 135; 411; 369; 364; 254; 297; 83; 499; 258; 565; 151; 27; 680; 845; 373; 402; 346; 134; 872; 559; 471; 802; 342; 775; 248; 964; 103; 102; 545; 322; 278; 802; 858; 662; 202; 829; 474; 590; 527; 499; 985; 934; 441; 730; 56; 409; 442; 622; 92; 713; 730; 129; 170; 445; 855; 685; 546; 196; 690; 771; 606; 143; 884; 432; 931; 181; 509; 853; 221; 243; 266; 624; 451; 845; 64; 648; 711; 245; 41; 632; 939; 947; 671; 13; 121; 603; 164; 211; 678; 749; 772; 328];
+[ 9; 307; 259; 990; 737; 363; 38; 587; 677; 656; 643; 809; 963; 263; 226; 798; 708; 236; 42; 251; 307; 34; 846; 924; 760; 559; 403; 926; 899; 515; 192; 597; 788; 509; 61; 519; 299; 701; 207; 902; 262; 987; 669; 231; 367; 593; 551; 626; 996; 71; 483; 949; 62; 783; 971; 880; 65; 310; 912; 932; 983; 826; 649; 13; 480; 83; 628; 450; 470; 52; 294; 255; 422; 256; 253; 465; 384; 207; 61; 63; 190; 194; 809; 39; 400; 99; 704; 245; 223; 254; 539; 787; 447; 30; 271; 484; 691; 262; 595; 83];
+[ 579; 750; 97; 495; 662; 984; 177; 220; 940; 166; 952; 193; 460; 816; 375; 979; 658; 22; 748; 843; 188; 604; 106; 423; 696; 628; 164; 169; 522; 543; 522; 830; 361; 737; 599; 255; 736; 222; 667; 865; 121; 639; 221; 158; 234; 629; 46; 659; 955; 311; 567; 473; 640; 705; 320; 807; 339; 83; 748; 373; 506; 589; 775; 696; 857; 121; 141; 485; 180; 14; 807; 74; 813; 855; 156; 899; 602; 412; 592; 402; 128; 382; 350; 586; 400; 639; 462; 558; 901; 230; 493; 640; 631; 808; 168; 219; 96; 99; 947; 334];
+[ 482; 440; 368; 861; 131; 880; 527; 106; 587; 252; 366; 5; 999; 963; 787; 431; 39; 934; 626; 564; 323; 383; 609; 824; 724; 722; 971; 669; 262; 6; 283; 598; 823; 722; 87; 251; 492; 782; 231; 192; 450; 903; 195; 885; 809; 596; 887; 610; 192; 779; 52; 427; 567; 785; 19; 498; 154; 429; 8; 752; 671; 797; 691; 885; 982; 124; 696; 871; 763; 691; 22; 410; 275; 16; 435; 356; 558; 956; 88; 742; 884; 778; 626; 129; 636; 866; 543; 130; 977; 88; 808; 26; 560; 685; 710; 138; 151; 427; 440; 211];
+[ 328; 906; 697; 29; 55; 68; 959; 288; 817; 207; 752; 492; 136; 662; 122; 545; 474; 972; 50; 910; 316; 540; 126; 642; 537; 297; 526; 358; 210; 189; 452; 689; 16; 342; 557; 43; 204; 155; 69; 500; 598; 138; 188; 860; 199; 984; 960; 665; 161; 673; 791; 517; 221; 310; 266; 805; 92; 217; 355; 70; 544; 989; 920; 798; 180; 31; 806; 957; 509; 860; 764; 116; 371; 321; 195; 985; 673; 223; 4; 502; 78; 635; 563; 357; 173; 43; 388; 428; 935; 330; 484; 352; 967; 14; 27; 938; 170; 653; 706; 29];
+[ 847; 382; 568; 628; 735; 967; 242; 714; 803; 898; 484; 976; 233; 202; 218; 85; 138; 267; 744; 362; 922; 591; 47; 668; 906; 799; 670; 272; 956; 857; 948; 325; 345; 486; 355; 18; 927; 502; 490; 908; 484; 452; 908; 170; 360; 179; 833; 230; 203; 432; 402; 130; 935; 874; 347; 960; 193; 886; 896; 407; 504; 86; 391; 990; 873; 615; 72; 51; 634; 737; 248; 795; 303; 472; 895; 804; 225; 358; 205; 493; 57; 575; 870; 354; 888; 164; 770; 122; 810; 189; 922; 335; 332; 447; 124; 197; 643; 33; 551; 683];
+[ 43; 761; 664; 547; 393; 684; 116; 499; 853; 989; 735; 240; 258; 926; 978; 353; 378; 639; 287; 413; 196; 165; 944; 772; 180; 935; 181; 897; 791; 103; 47; 666; 976; 878; 977; 13; 455; 590; 215; 777; 55; 770; 57; 421; 393; 808; 932; 366; 456; 303; 977; 211; 19; 944; 882; 421; 314; 592; 891; 878; 855; 160; 186; 507; 841; 673; 666; 123; 440; 447; 870; 57; 710; 480; 637; 737; 909; 324; 780; 128; 347; 243; 432; 620; 354; 955; 360; 619; 878; 757; 505; 643; 54; 298; 875; 928; 744; 472; 273; 221];
+[ 391; 920; 604; 17; 118; 417; 316; 482; 101; 631; 941; 612; 188; 443; 263; 511; 140; 70; 319; 404; 632; 817; 150; 414; 573; 557; 210; 172; 892; 612; 952; 762; 196; 207; 323; 172; 368; 444; 321; 34; 337; 215; 887; 645; 874; 316; 321; 438; 452; 995; 37; 627; 186; 710; 706; 816; 609; 188; 346; 170; 547; 706; 895; 896; 866; 811; 373; 351; 403; 870; 196; 831; 978; 341; 26; 432; 80; 619; 456; 740; 366; 327; 179; 702; 114; 327; 218; 762; 442; 340; 123; 986; 521; 723; 298; 457; 28; 646; 606; 479];
+[ 265; 626; 62; 191; 564; 34; 879; 324; 702; 322; 670; 962; 230; 85; 857; 313; 57; 521; 356; 968; 609; 245; 164; 696; 656; 895; 542; 315; 709; 240; 276; 871; 550; 770; 700; 377; 962; 431; 429; 841; 820; 705; 516; 383; 451; 843; 307; 481; 105; 990; 998; 2; 663; 111; 931; 527; 946; 290; 38; 70; 877; 251; 963; 269; 549; 887; 398; 419; 214; 216; 314; 603; 804; 613; 985; 820; 24; 203; 564; 668; 322; 910; 866; 283; 651; 819; 112; 620; 536; 172; 145; 803; 904; 510; 259; 501; 402; 958; 244; 861];
+[ 463; 424; 437; 281; 945; 866; 801; 977; 701; 360; 263; 125; 250; 57; 175; 785; 454; 77; 293; 403; 401; 107; 138; 266; 710; 177; 294; 840; 592; 345; 852; 492; 478; 801; 199; 750; 238; 950; 746; 917; 192; 21; 108; 31; 31; 665; 392; 195; 642; 452; 532; 609; 709; 824; 568; 618; 144; 455; 268; 758; 682; 963; 891; 705; 877; 710; 428; 660; 532; 260; 481; 921; 194; 540; 463; 946; 393; 461; 209; 944; 255; 898; 228; 659; 136; 213; 937; 440; 765; 352; 149; 841; 356; 328; 290; 130; 20; 782; 261; 823];
+[ 372; 534; 678; 476; 344; 493; 56; 171; 926; 202; 998; 573; 943; 961; 478; 273; 131; 73; 47; 920; 490; 348; 61; 762; 34; 361; 334; 383; 774; 249; 661; 194; 137; 761; 126; 796; 800; 208; 525; 92; 868; 104; 546; 321; 456; 501; 535; 791; 995; 38; 922; 843; 304; 570; 382; 470; 704; 165; 691; 533; 612; 595; 896; 948; 477; 341; 345; 370; 136; 677; 183; 114; 309; 578; 500; 267; 960; 800; 613; 607; 135; 399; 979; 982; 808; 910; 292; 192; 414; 432; 485; 714; 153; 849; 752; 805; 656; 896; 426; 483];
+[ 796; 938; 942; 484; 15; 345; 212; 464; 604; 133; 320; 308; 400; 796; 229; 549; 551; 244; 458; 464; 424; 63; 816; 879; 191; 404; 401; 259; 645; 216; 692; 356; 644; 596; 85; 899; 696; 248; 687; 703; 715; 593; 803; 798; 856; 388; 20; 946; 586; 8; 488; 276; 352; 513; 270; 143; 808; 288; 885; 538; 453; 842; 739; 503; 948; 756; 427; 309; 493; 522; 722; 335; 242; 646; 301; 428; 708; 299; 392; 850; 540; 999; 977; 456; 748; 996; 481; 88; 499; 460; 484; 433; 369; 917; 60; 579; 717; 670; 161; 966];
+[ 115; 863; 547; 152; 738; 93; 342; 420; 717; 628; 832; 395; 973; 547; 621; 757; 986; 778; 604; 270; 808; 277; 606; 128; 781; 795; 314; 101; 245; 377; 474; 992; 597; 393; 444; 612; 865; 120; 45; 787; 646; 670; 748; 654; 368; 544; 773; 156; 738; 864; 937; 881; 130; 120; 697; 565; 764; 682; 464; 297; 886; 726; 53; 968; 455; 318; 338; 988; 512; 788; 423; 105; 679; 308; 591; 586; 901; 261; 351; 548; 98; 818; 589; 543; 703; 999; 955; 310; 749; 554; 926; 732; 771; 173; 431; 165; 862; 662; 155; 139];
+[ 607; 327; 200; 135; 948; 235; 469; 59; 49; 31; 507; 818; 558; 225; 674; 787; 460; 990; 469; 996; 415; 280; 153; 934; 125; 275; 853; 71; 616; 146; 943; 40; 470; 822; 652; 73; 969; 701; 667; 268; 950; 171; 413; 385; 398; 286; 713; 946; 975; 749; 956; 958; 752; 416; 898; 631; 885; 417; 216; 392; 412; 510; 309; 345; 83; 48; 449; 854; 512; 808; 40; 521; 484; 761; 625; 40; 939; 239; 810; 69; 414; 169; 311; 0; 34; 348; 138; 883; 23; 746; 973; 158; 806; 749; 796; 691; 279; 784; 589; 736];
+[ 212; 167; 41; 134; 286; 403; 775; 953; 461; 528; 368; 395; 860; 341; 746; 582; 259; 512; 500; 614; 808; 624; 178; 831; 391; 275; 609; 472; 412; 63; 629; 31; 887; 615; 240; 544; 709; 845; 233; 400; 293; 920; 462; 509; 753; 43; 224; 844; 946; 559; 151; 76; 873; 224; 907; 713; 606; 277; 639; 124; 674; 641; 713; 477; 948; 259; 528; 851; 443; 202; 174; 336; 379; 219; 610; 174; 819; 538; 796; 603; 733; 226; 907; 542; 252; 562; 631; 688; 518; 242; 40; 57; 264; 67; 443; 583; 892; 542; 591; 292];
+[ 893; 830; 368; 620; 531; 303; 337; 311; 371; 26; 827; 929; 746; 489; 290; 540; 657; 787; 89; 128; 271; 339; 484; 899; 887; 459; 129; 973; 881; 593; 666; 731; 784; 268; 841; 99; 103; 803; 34; 680; 583; 393; 94; 141; 612; 313; 149; 198; 914; 353; 526; 2; 479; 377; 562; 379; 508; 982; 61; 863; 22; 482; 237; 418; 258; 608; 222; 151; 187; 233; 952; 828; 442; 981; 119; 585; 723; 419; 282; 74; 786; 683; 517; 364; 861; 560; 496; 966; 143; 60; 293; 856; 924; 463; 956; 356; 25; 96; 7; 809];
+[ 507; 739; 898; 679; 89; 695; 429; 665; 357; 661; 714; 991; 408; 970; 344; 374; 277; 170; 136; 237; 139; 667; 726; 258; 761; 425; 756; 207; 15; 403; 244; 879; 978; 821; 802; 215; 768; 779; 513; 451; 641; 581; 993; 10; 107; 692; 757; 385; 724; 637; 94; 871; 408; 791; 201; 59; 909; 154; 714; 230; 419; 651; 674; 467; 710; 524; 257; 349; 470; 890; 85; 962; 67; 105; 172; 968; 226; 373; 896; 516; 54; 553; 33; 873; 787; 863; 515; 778; 98; 960; 985; 125; 459; 625; 806; 195; 796; 913; 475; 718];
+[ 450; 911; 444; 773; 583; 620; 569; 531; 860; 810; 214; 121; 336; 115; 286; 183; 240; 940; 103; 96; 273; 933; 972; 435; 704; 139; 500; 724; 14; 860; 954; 577; 451; 239; 286; 431; 439; 567; 550; 420; 144; 793; 316; 447; 2; 512; 762; 8; 374; 775; 263; 978; 675; 331; 617; 271; 925; 419; 577; 127; 179; 178; 741; 937; 942; 604; 129; 693; 19; 174; 541; 774; 569; 210; 146; 704; 639; 762; 734; 875; 145; 477; 636; 997; 652; 267; 204; 854; 856; 238; 12; 854; 206; 446; 362; 175; 834; 966; 43; 80];
+[ 425; 226; 297; 921; 399; 369; 134; 655; 377; 609; 188; 979; 455; 612; 639; 724; 369; 785; 951; 344; 777; 879; 410; 296; 698; 487; 666; 41; 37; 420; 920; 791; 196; 461; 212; 658; 41; 309; 442; 374; 458; 986; 854; 233; 901; 206; 138; 586; 705; 182; 561; 478; 500; 896; 569; 830; 9; 249; 501; 52; 912; 964; 259; 53; 491; 573; 733; 82; 971; 937; 782; 125; 302; 55; 414; 439; 181; 869; 76; 565; 182; 451; 571; 51; 131; 743; 813; 813; 395; 756; 76; 473; 928; 538; 230; 110; 139; 666; 402; 576];
+[ 321; 295; 868; 382; 715; 356; 106; 688; 460; 547; 859; 215; 587; 738; 250; 657; 468; 288; 882; 878; 924; 902; 354; 828; 140; 187; 752; 665; 438; 311; 399; 18; 605; 314; 30; 502; 294; 516; 443; 403; 798; 539; 578; 959; 302; 692; 44; 521; 527; 685; 505; 310; 377; 371; 877; 26; 121; 909; 754; 851; 419; 682; 248; 958; 529; 945; 649; 603; 544; 607; 631; 557; 823; 777; 576; 369; 983; 952; 65; 584; 52; 371; 988; 597; 982; 28; 249; 783; 169; 167; 893; 32; 171; 831; 618; 166; 920; 864; 399; 215];
+[ 707; 383; 785; 550; 566; 663; 922; 906; 812; 7; 341; 795; 414; 588; 131; 350; 623; 668; 910; 473; 361; 63; 418; 665; 155; 64; 179; 224; 356; 306; 492; 794; 770; 702; 959; 393; 656; 355; 491; 869; 185; 273; 761; 778; 270; 849; 882; 857; 972; 980; 330; 850; 162; 556; 807; 113; 848; 431; 811; 448; 372; 56; 651; 854; 233; 88; 283; 914; 581; 942; 501; 848; 197; 967; 702; 743; 679; 278; 501; 219; 386; 933; 598; 709; 649; 703; 453; 841; 256; 452; 249; 437; 986; 54; 50; 867; 600; 387; 975; 77];
+[ 68; 273; 716; 190; 641; 951; 502; 848; 672; 176; 206; 31; 577; 923; 105; 216; 355; 89; 356; 70; 22; 698; 639; 293; 348; 78; 308; 106; 678; 479; 239; 694; 557; 317; 402; 555; 235; 176; 409; 832; 511; 46; 873; 23; 278; 679; 381; 872; 423; 265; 623; 295; 698; 401; 692; 670; 256; 198; 612; 541; 307; 126; 26; 696; 610; 404; 345; 409; 481; 828; 461; 977; 108; 61; 489; 630; 748; 259; 721; 469; 430; 298; 569; 706; 203; 284; 969; 868; 772; 916; 398; 990; 509; 616; 871; 801; 535; 743; 994; 760];
+[ 416; 913; 216; 683; 588; 820; 97; 673; 951; 49; 756; 362; 359; 62; 552; 598; 625; 938; 223; 87; 31; 76; 73; 623; 428; 894; 102; 487; 329; 669; 976; 105; 937; 659; 561; 82; 313; 707; 491; 68; 185; 698; 522; 106; 411; 135; 100; 616; 52; 904; 456; 644; 762; 227; 239; 502; 781; 905; 862; 409; 757; 315; 597; 864; 155; 883; 231; 223; 314; 972; 780; 98; 441; 670; 40; 552; 455; 769; 559; 64; 135; 31; 698; 686; 302; 945; 920; 974; 131; 762; 111; 603; 607; 693; 790; 681; 794; 909; 137; 713];
+[ 557; 779; 321; 762; 28; 946; 726; 213; 136; 309; 855; 707; 737; 472; 53; 2; 604; 252; 865; 632; 442; 101; 922; 482; 230; 299; 601; 296; 841; 553; 884; 50; 907; 380; 816; 875; 981; 838; 636; 268; 894; 857; 26; 591; 815; 908; 854; 474; 125; 411; 625; 785; 305; 434; 715; 852; 165; 334; 177; 101; 134; 534; 906; 502; 488; 683; 629; 204; 957; 543; 357; 172; 576; 95; 887; 147; 432; 813; 260; 369; 421; 852; 177; 441; 788; 698; 621; 163; 39; 566; 351; 394; 359; 152; 170; 195; 20; 626; 643; 904];
+[ 419; 837; 684; 761; 385; 68; 997; 373; 222; 279; 412; 591; 808; 410; 455; 9; 478; 987; 145; 506; 365; 580; 50; 612; 867; 46; 435; 583; 934; 598; 238; 737; 240; 543; 526; 471; 807; 484; 701; 687; 685; 938; 407; 185; 604; 741; 568; 148; 368; 619; 238; 949; 501; 113; 216; 726; 477; 374; 851; 309; 992; 459; 33; 365; 824; 690; 754; 716; 408; 610; 391; 66; 362; 131; 863; 962; 225; 952; 928; 564; 404; 920; 167; 19; 262; 329; 705; 389; 131; 247; 824; 962; 918; 547; 707; 9; 368; 574; 639; 145];
+[ 693; 917; 254; 863; 543; 631; 996; 599; 757; 952; 520; 803; 487; 670; 677; 258; 54; 973; 418; 659; 509; 7; 347; 280; 300; 418; 852; 179; 954; 541; 524; 490; 101; 11; 281; 596; 184; 689; 265; 857; 653; 204; 503; 356; 190; 796; 735; 358; 80; 810; 909; 378; 846; 947; 338; 963; 67; 965; 122; 820; 506; 260; 689; 855; 488; 602; 84; 164; 930; 595; 488; 577; 639; 678; 208; 535; 673; 283; 912; 716; 407; 145; 953; 67; 217; 48; 68; 948; 982; 79; 65; 14; 527; 689; 61; 914; 299; 455; 614; 349];
+[ 639; 49; 212; 859; 318; 60; 534; 845; 617; 611; 43; 845; 616; 132; 583; 680; 619; 763; 607; 705; 382; 677; 739; 613; 561; 651; 51; 449; 684; 976; 154; 17; 721; 303; 518; 905; 225; 100; 496; 277; 852; 201; 63; 273; 182; 990; 710; 686; 771; 862; 851; 105; 944; 527; 807; 964; 216; 251; 394; 649; 296; 775; 967; 302; 757; 191; 843; 88; 975; 419; 333; 493; 898; 425; 862; 722; 119; 555; 797; 85; 738; 942; 692; 648; 860; 525; 653; 356; 972; 537; 878; 30; 111; 141; 111; 648; 463; 793; 63; 12];
+[ 876; 388; 206; 833; 480; 472; 796; 445; 32; 590; 449; 93; 657; 252; 644; 617; 665; 2; 400; 253; 587; 161; 99; 980; 427; 216; 760; 245; 293; 922; 487; 894; 608; 389; 610; 6; 477; 907; 928; 95; 681; 127; 951; 234; 819; 356; 171; 697; 273; 391; 761; 674; 530; 993; 130; 475; 838; 539; 507; 546; 405; 999; 771; 999; 242; 33; 165; 232; 427; 974; 827; 577; 322; 968; 681; 826; 489; 622; 140; 854; 424; 526; 579; 948; 105; 264; 53; 289; 256; 203; 373; 567; 101; 951; 877; 342; 502; 575; 396; 834];
+[ 656; 536; 51; 232; 965; 16; 498; 579; 975; 694; 570; 238; 342; 125; 513; 199; 538; 737; 862; 860; 80; 316; 769; 11; 940; 630; 849; 243; 689; 539; 17; 658; 678; 939; 981; 762; 559; 221; 699; 553; 180; 384; 423; 588; 544; 186; 26; 54; 595; 738; 701; 197; 978; 647; 631; 943; 478; 368; 682; 206; 885; 525; 788; 908; 102; 961; 53; 98; 294; 849; 520; 357; 38; 889; 804; 361; 871; 856; 303; 198; 437; 227; 110; 229; 66; 266; 576; 256; 844; 298; 168; 186; 351; 432; 166; 876; 537; 733; 324; 154];
+[ 219; 878; 913; 50; 734; 116; 135; 419; 454; 939; 215; 703; 666; 716; 427; 71; 447; 496; 94; 419; 72; 911; 610; 161; 118; 985; 557; 223; 631; 365; 66; 905; 171; 937; 393; 573; 366; 221; 793; 851; 986; 250; 273; 175; 766; 425; 905; 101; 580; 531; 467; 86; 127; 465; 51; 730; 357; 813; 502; 968; 638; 296; 438; 865; 724; 848; 731; 232; 156; 635; 510; 246; 523; 34; 321; 539; 553; 926; 757; 625; 707; 330; 270; 183; 802; 823; 102; 773; 426; 607; 28; 83; 838; 705; 884; 370; 628; 907; 849; 857];
+[ 981; 135; 455; 228; 880; 843; 799; 993; 874; 568; 419; 983; 25; 530; 41; 733; 968; 443; 908; 120; 263; 382; 137; 583; 277; 182; 503; 279; 985; 208; 622; 291; 180; 92; 117; 324; 201; 360; 975; 399; 152; 737; 49; 624; 733; 310; 227; 245; 939; 160; 222; 110; 848; 15; 548; 561; 59; 772; 872; 673; 406; 664; 789; 967; 974; 809; 651; 620; 931; 39; 472; 993; 740; 330; 381; 316; 555; 750; 223; 704; 708; 340; 66; 518; 320; 281; 810; 307; 869; 962; 592; 785; 180; 295; 983; 971; 441; 190; 578; 226];
+[ 517; 314; 778; 104; 936; 330; 684; 500; 75; 200; 423; 581; 931; 231; 326; 712; 529; 126; 233; 877; 318; 821; 664; 874; 947; 553; 223; 147; 41; 84; 609; 287; 948; 134; 103; 795; 148; 121; 751; 503; 541; 458; 690; 334; 858; 409; 574; 72; 51; 269; 535; 226; 128; 944; 174; 481; 482; 258; 320; 620; 548; 864; 575; 31; 430; 81; 895; 510; 572; 852; 120; 217; 11; 642; 64; 572; 617; 659; 52; 417; 768; 609; 857; 858; 504; 561; 312; 922; 485; 225; 276; 185; 545; 783; 86; 212; 378; 885; 288; 669];
+[ 836; 341; 191; 759; 752; 283; 946; 946; 259; 568; 524; 257; 297; 145; 950; 408; 248; 938; 133; 837; 452; 876; 672; 147; 618; 486; 816; 733; 877; 856; 901; 351; 318; 352; 547; 968; 838; 287; 335; 291; 104; 522; 940; 394; 794; 293; 381; 260; 962; 473; 176; 281; 50; 306; 841; 170; 586; 454; 810; 669; 800; 725; 294; 99; 563; 636; 663; 33; 78; 318; 873; 893; 43; 278; 641; 843; 925; 948; 22; 132; 23; 101; 608; 87; 337; 779; 290; 247; 960; 696; 852; 49; 360; 175; 197; 559; 336; 568; 719; 509];
+[ 430; 8; 161; 969; 96; 214; 496; 333; 252; 169; 680; 199; 463; 36; 602; 39; 815; 92; 982; 177; 766; 894; 400; 17; 126; 896; 519; 396; 397; 509; 195; 201; 776; 967; 334; 717; 452; 405; 300; 144; 373; 938; 508; 770; 828; 151; 947; 88; 746; 45; 260; 899; 981; 893; 767; 312; 806; 441; 336; 697; 720; 494; 350; 845; 24; 198; 327; 485; 256; 384; 986; 369; 921; 760; 412; 82; 481; 13; 83; 271; 855; 432; 273; 28; 713; 653; 786; 867; 155; 441; 110; 276; 431; 303; 231; 200; 281; 365; 415; 658];
+[ 519; 228; 523; 208; 330; 431; 823; 41; 820; 593; 289; 95; 166; 534; 544; 942; 577; 106; 561; 963; 229; 31; 326; 922; 753; 727; 874; 12; 481; 352; 209; 342; 279; 873; 825; 804; 398; 860; 798; 792; 471; 347; 975; 786; 866; 750; 795; 21; 446; 55; 909; 160; 874; 46; 886; 259; 305; 780; 966; 231; 752; 11; 171; 649; 997; 796; 629; 476; 937; 745; 958; 637; 751; 917; 682; 348; 629; 847; 73; 180; 453; 309; 47; 339; 668; 441; 415; 713; 438; 58; 315; 933; 671; 983; 654; 516; 431; 310; 171; 463];
+[ 297; 985; 225; 627; 993; 722; 826; 92; 113; 995; 498; 714; 803; 816; 462; 331; 229; 508; 294; 607; 942; 583; 480; 855; 884; 102; 821; 944; 238; 75; 653; 888; 347; 912; 968; 796; 119; 640; 455; 740; 6; 464; 319; 455; 540; 51; 85; 184; 862; 710; 830; 544; 406; 352; 766; 453; 632; 443; 214; 965; 677; 35; 216; 6; 579; 700; 623; 297; 313; 578; 365; 320; 506; 569; 317; 554; 564; 37; 645; 976; 864; 360; 874; 204; 636; 33; 951; 143; 740; 356; 961; 560; 802; 24; 14; 497; 324; 916; 123; 51];
+[ 324; 98; 895; 105; 896; 664; 708; 779; 648; 989; 824; 971; 939; 274; 408; 102; 949; 143; 261; 214; 114; 646; 351; 864; 23; 920; 992; 501; 279; 375; 395; 237; 436; 936; 578; 669; 42; 234; 857; 811; 790; 918; 284; 87; 640; 927; 51; 413; 257; 697; 248; 368; 719; 258; 705; 72; 100; 914; 606; 982; 692; 283; 24; 675; 204; 319; 964; 613; 842; 588; 335; 676; 439; 221; 723; 909; 489; 557; 502; 827; 410; 793; 256; 811; 273; 673; 765; 522; 796; 68; 681; 700; 984; 307; 801; 798; 129; 395; 679; 424];
+[ 986; 596; 799; 236; 629; 385; 96; 790; 283; 403; 299; 416; 379; 276; 263; 635; 133; 382; 666; 483; 478; 439; 939; 664; 307; 933; 447; 863; 564; 102; 401; 413; 171; 166; 24; 242; 622; 795; 595; 64; 563; 47; 584; 944; 970; 735; 751; 497; 270; 627; 143; 756; 983; 584; 464; 857; 22; 46; 89; 204; 302; 569; 892; 660; 630; 880; 897; 458; 32; 390; 772; 255; 491; 862; 336; 453; 597; 832; 189; 245; 304; 92; 691; 712; 454; 707; 783; 219; 891; 723; 0; 892; 374; 907; 29; 466; 839; 682; 608; 404];
+[ 878; 417; 23; 356; 116; 345; 772; 551; 265; 230; 59; 902; 319; 748; 747; 233; 80; 7; 22; 536; 413; 695; 593; 447; 364; 262; 210; 44; 136; 286; 183; 985; 744; 765; 140; 18; 286; 649; 554; 387; 971; 78; 398; 626; 32; 234; 377; 754; 796; 109; 919; 503; 761; 578; 121; 456; 478; 384; 517; 646; 714; 98; 242; 351; 4; 943; 587; 618; 202; 676; 995; 906; 247; 851; 625; 543; 133; 665; 695; 939; 530; 664; 963; 344; 974; 819; 269; 427; 899; 663; 919; 660; 135; 809; 31; 541; 382; 200; 914; 687];
+[ 367; 774; 980; 429; 808; 997; 404; 441; 843; 177; 792; 509; 966; 135; 905; 578; 718; 433; 865; 29; 342; 724; 202; 138; 803; 71; 267; 233; 685; 866; 551; 928; 299; 211; 606; 960; 981; 350; 264; 477; 408; 381; 756; 158; 741; 238; 278; 814; 608; 714; 599; 638; 370; 516; 975; 291; 527; 745; 395; 751; 213; 618; 934; 837; 607; 568; 565; 745; 943; 299; 741; 634; 586; 17; 478; 159; 99; 761; 523; 857; 785; 983; 843; 330; 418; 755; 376; 804; 267; 644; 328; 435; 361; 651; 744; 646; 590; 727; 881; 958];
+[ 984; 569; 207; 203; 88; 750; 706; 3; 37; 600; 497; 448; 173; 758; 860; 295; 533; 491; 609; 510; 95; 200; 89; 31; 35; 430; 700; 667; 26; 848; 623; 802; 270; 398; 213; 217; 115; 159; 296; 226; 753; 151; 911; 24; 792; 70; 378; 752; 791; 300; 975; 768; 471; 29; 999; 417; 12; 317; 771; 888; 519; 607; 722; 305; 594; 840; 812; 351; 75; 811; 259; 598; 306; 536; 7; 953; 13; 839; 301; 196; 957; 858; 909; 858; 937; 693; 908; 774; 281; 448; 978; 989; 367; 228; 381; 724; 608; 174; 423; 453];
+[ 324; 606; 751; 83; 280; 223; 529; 775; 22; 841; 251; 871; 223; 506; 791; 652; 601; 818; 992; 720; 928; 159; 432; 758; 184; 348; 911; 266; 792; 307; 315; 297; 433; 797; 93; 417; 887; 841; 948; 915; 159; 198; 826; 465; 50; 908; 342; 815; 233; 560; 662; 262; 64; 415; 13; 358; 79; 707; 438; 250; 707; 266; 22; 714; 648; 424; 278; 403; 404; 732; 925; 350; 659; 553; 216; 159; 485; 297; 899; 980; 785; 928; 93; 310; 690; 612; 933; 949; 544; 580; 828; 495; 608; 124; 6; 685; 698; 549; 798; 882];
+[ 273; 637; 805; 744; 8; 352; 248; 321; 510; 782; 857; 412; 33; 869; 49; 205; 93; 814; 735; 213; 268; 597; 760; 983; 300; 595; 807; 980; 577; 667; 357; 956; 71; 359; 177; 775; 892; 248; 270; 704; 829; 575; 345; 168; 514; 829; 541; 926; 138; 428; 80; 774; 164; 700; 411; 253; 704; 194; 158; 472; 72; 218; 90; 492; 490; 35; 118; 335; 202; 604; 335; 205; 877; 487; 750; 193; 844; 438; 575; 433; 96; 408; 687; 662; 268; 949; 176; 28; 225; 731; 756; 431; 610; 504; 667; 122; 555; 769; 48; 763];
+[ 127; 134; 16; 708; 757; 899; 491; 149; 93; 834; 174; 632; 403; 711; 300; 642; 524; 826; 9; 855; 957; 921; 353; 734; 723; 974; 908; 974; 368; 90; 132; 567; 511; 786; 340; 914; 233; 591; 766; 436; 451; 888; 453; 817; 599; 762; 989; 74; 908; 496; 377; 854; 331; 39; 216; 383; 166; 447; 224; 803; 459; 761; 722; 752; 99; 312; 579; 680; 642; 201; 286; 78; 194; 191; 786; 890; 438; 898; 14; 745; 268; 6; 589; 490; 774; 626; 320; 508; 275; 831; 52; 564; 763; 134; 915; 397; 936; 729; 365; 792];
+[ 500; 96; 73; 766; 6; 294; 166; 847; 64; 864; 692; 289; 478; 204; 760; 455; 556; 874; 413; 808; 192; 901; 45; 327; 749; 817; 635; 308; 63; 420; 271; 649; 54; 694; 926; 638; 76; 408; 978; 353; 618; 318; 505; 708; 27; 800; 268; 691; 473; 445; 511; 482; 613; 309; 349; 446; 850; 900; 694; 203; 82; 102; 856; 202; 995; 646; 565; 558; 51; 825; 576; 797; 909; 785; 541; 470; 242; 858; 182; 419; 887; 764; 903; 224; 792; 955; 535; 14; 887; 787; 102; 719; 911; 404; 982; 906; 59; 104; 663; 14];
+[ 272; 54; 357; 257; 980; 63; 783; 845; 111; 921; 353; 914; 385; 372; 535; 272; 101; 557; 947; 570; 405; 355; 176; 744; 356; 163; 602; 841; 427; 160; 104; 925; 242; 756; 425; 231; 145; 909; 69; 157; 891; 214; 33; 120; 174; 394; 700; 478; 8; 757; 649; 652; 346; 805; 130; 811; 596; 962; 140; 350; 225; 686; 895; 603; 657; 359; 756; 879; 404; 212; 757; 857; 88; 571; 569; 940; 454; 956; 383; 531; 127; 980; 265; 72; 327; 651; 903; 732; 721; 723; 107; 489; 93; 843; 557; 82; 955; 93; 607; 488];
+[ 365; 251; 463; 814; 818; 350; 53; 955; 600; 754; 947; 652; 616; 348; 152; 823; 329; 31; 815; 792; 130; 470; 371; 562; 152; 670; 800; 498; 539; 684; 69; 273; 335; 535; 240; 682; 49; 979; 222; 638; 448; 71; 230; 689; 784; 647; 650; 400; 673; 344; 824; 950; 483; 497; 284; 746; 734; 305; 631; 90; 827; 853; 859; 422; 57; 938; 39; 92; 651; 818; 156; 704; 253; 671; 206; 836; 180; 278; 111; 690; 876; 636; 581; 776; 410; 183; 242; 688; 904; 15; 835; 616; 166; 173; 740; 56; 245; 750; 59; 487];
+[ 26; 48; 944; 320; 209; 976; 293; 670; 349; 98; 991; 375; 319; 231; 466; 933; 240; 227; 68; 67; 61; 709; 928; 155; 373; 212; 239; 564; 472; 781; 479; 630; 706; 810; 603; 684; 560; 753; 98; 43; 973; 869; 810; 920; 570; 314; 50; 564; 189; 40; 853; 656; 371; 678; 459; 502; 428; 684; 490; 608; 524; 609; 499; 677; 169; 952; 891; 599; 44; 190; 990; 826; 57; 758; 283; 108; 862; 333; 694; 594; 872; 281; 50; 583; 702; 875; 443; 157; 674; 855; 597; 458; 93; 399; 911; 440; 110; 159; 206; 982];
+[ 565; 541; 575; 259; 427; 408; 94; 900; 264; 190; 789; 740; 817; 54; 474; 656; 693; 392; 148; 899; 756; 463; 485; 752; 611; 425; 503; 739; 93; 172; 730; 601; 223; 551; 244; 18; 715; 493; 104; 572; 373; 472; 551; 324; 246; 574; 41; 760; 546; 560; 908; 193; 937; 720; 391; 700; 418; 500; 754; 413; 747; 819; 928; 801; 741; 954; 611; 688; 792; 960; 491; 444; 920; 16; 320; 100; 588; 455; 17; 606; 865; 177; 987; 568; 812; 400; 48; 159; 972; 477; 504; 310; 81; 663; 807; 517; 749; 442; 374; 758];
+[ 952; 772; 319; 516; 456; 461; 812; 22; 62; 732; 28; 386; 681; 368; 837; 619; 130; 50; 189; 365; 301; 669; 128; 467; 735; 739; 374; 874; 535; 218; 525; 754; 951; 463; 504; 241; 738; 480; 435; 182; 498; 879; 742; 300; 916; 76; 593; 363; 637; 526; 250; 838; 231; 176; 723; 356; 30; 15; 167; 102; 289; 155; 778; 816; 868; 654; 430; 530; 789; 796; 561; 873; 267; 838; 258; 274; 155; 262; 520; 680; 558; 373; 665; 504; 522; 209; 19; 182; 489; 892; 252; 12; 854; 88; 169; 658; 728; 860; 158; 763];
+[ 657; 767; 984; 360; 119; 134; 626; 496; 46; 300; 378; 261; 968; 188; 769; 438; 896; 256; 370; 455; 399; 16; 718; 3; 201; 921; 395; 583; 295; 100; 158; 533; 86; 950; 759; 988; 820; 178; 787; 37; 843; 434; 431; 673; 114; 486; 671; 9; 780; 794; 758; 653; 532; 142; 572; 462; 648; 453; 161; 235; 364; 780; 429; 75; 147; 154; 250; 805; 60; 834; 416; 690; 442; 7; 732; 914; 102; 190; 382; 246; 762; 437; 865; 77; 294; 603; 180; 733; 143; 566; 222; 237; 138; 134; 288; 974; 750; 60; 877; 735];
+[ 515; 643; 907; 708; 984; 722; 461; 360; 888; 961; 689; 490; 673; 749; 19; 202; 789; 108; 165; 892; 125; 669; 840; 38; 375; 739; 691; 785; 284; 833; 338; 712; 139; 364; 843; 935; 359; 934; 91; 292; 239; 603; 501; 808; 863; 808; 894; 925; 129; 606; 354; 609; 380; 608; 894; 967; 210; 127; 796; 614; 938; 963; 591; 528; 412; 918; 847; 969; 474; 815; 820; 629; 653; 372; 528; 752; 817; 68; 211; 483; 370; 803; 488; 259; 456; 235; 409; 626; 759; 694; 589; 746; 334; 207; 727; 692; 581; 48; 24; 490];
+[ 615; 138; 479; 250; 430; 821; 771; 908; 333; 409; 622; 903; 676; 991; 378; 191; 45; 296; 335; 992; 107; 78; 676; 779; 950; 797; 138; 529; 579; 364; 627; 509; 908; 702; 559; 657; 320; 599; 995; 335; 902; 941; 954; 935; 949; 960; 220; 669; 568; 700; 271; 122; 127; 563; 447; 730; 364; 771; 328; 206; 434; 602; 87; 875; 391; 369; 113; 797; 457; 444; 174; 623; 439; 76; 825; 381; 893; 262; 498; 365; 880; 67; 106; 552; 679; 790; 487; 745; 421; 488; 52; 606; 470; 923; 94; 814; 420; 402; 367; 471];
+[ 777; 989; 575; 0; 530; 976; 506; 845; 22; 697; 362; 558; 206; 753; 176; 309; 905; 76; 796; 176; 812; 615; 440; 147; 294; 897; 727; 76; 961; 305; 778; 912; 738; 882; 265; 333; 417; 840; 775; 200; 700; 931; 21; 872; 123; 236; 81; 310; 537; 585; 873; 112; 419; 99; 568; 538; 364; 997; 919; 794; 972; 181; 519; 511; 463; 505; 341; 524; 866; 36; 150; 941; 489; 993; 41; 276; 927; 548; 21; 817; 750; 327; 310; 417; 134; 219; 739; 847; 105; 587; 268; 601; 108; 846; 428; 762; 975; 626; 977; 78];
+[ 557; 127; 559; 473; 783; 542; 610; 967; 839; 515; 34; 586; 414; 725; 726; 206; 741; 62; 84; 675; 64; 579; 194; 346; 892; 840; 929; 742; 569; 380; 985; 550; 148; 835; 579; 708; 37; 843; 110; 806; 657; 98; 368; 923; 384; 957; 551; 107; 878; 316; 664; 507; 205; 358; 643; 570; 250; 796; 905; 724; 846; 80; 295; 130; 850; 877; 890; 438; 805; 627; 850; 984; 771; 464; 287; 987; 29; 822; 541; 185; 430; 556; 430; 304; 931; 36; 961; 215; 359; 726; 949; 178; 159; 840; 955; 218; 537; 383; 671; 767];
+[ 969; 580; 785; 75; 176; 805; 666; 451; 185; 210; 700; 19; 710; 764; 419; 497; 891; 389; 442; 22; 708; 810; 435; 265; 324; 575; 887; 332; 718; 405; 570; 813; 647; 317; 136; 511; 683; 353; 806; 753; 889; 275; 716; 170; 518; 289; 893; 711; 596; 140; 437; 743; 585; 818; 869; 605; 69; 524; 515; 136; 140; 640; 732; 458; 895; 982; 122; 365; 501; 707; 319; 128; 223; 161; 904; 677; 60; 175; 60; 258; 237; 54; 808; 357; 415; 883; 203; 333; 357; 93; 734; 767; 803; 873; 409; 198; 950; 833; 727; 674];
+[ 868; 632; 904; 179; 869; 730; 66; 530; 460; 868; 383; 844; 406; 624; 814; 948; 180; 371; 701; 576; 813; 217; 853; 625; 167; 593; 352; 761; 666; 779; 367; 519; 75; 959; 465; 845; 435; 713; 332; 824; 95; 65; 973; 294; 275; 52; 444; 624; 843; 479; 789; 382; 812; 184; 935; 198; 658; 870; 851; 776; 365; 42; 426; 815; 423; 491; 117; 787; 195; 65; 486; 90; 842; 721; 673; 356; 586; 305; 543; 335; 383; 169; 814; 65; 384; 404; 978; 234; 296; 36; 204; 165; 98; 639; 851; 376; 984; 316; 894; 319];
+[ 424; 678; 569; 558; 109; 430; 549; 864; 866; 282; 282; 29; 487; 905; 257; 145; 310; 490; 521; 69; 673; 924; 961; 394; 448; 100; 293; 630; 583; 691; 93; 750; 926; 729; 568; 724; 169; 27; 793; 896; 521; 75; 783; 112; 311; 551; 314; 349; 860; 512; 94; 341; 752; 929; 363; 15; 569; 588; 782; 415; 889; 356; 138; 530; 801; 666; 624; 380; 827; 996; 623; 900; 899; 296; 7; 264; 17; 793; 315; 143; 249; 33; 181; 801; 382; 555; 956; 281; 556; 591; 606; 82; 950; 202; 997; 390; 380; 185; 575; 111];
+[ 885; 502; 515; 779; 714; 172; 651; 461; 623; 261; 576; 79; 702; 791; 332; 781; 522; 869; 570; 185; 762; 565; 624; 360; 533; 518; 710; 286; 961; 531; 549; 801; 925; 485; 68; 442; 563; 124; 485; 537; 896; 196; 75; 800; 641; 19; 112; 922; 367; 831; 34; 299; 911; 941; 521; 751; 688; 373; 258; 770; 703; 189; 741; 241; 310; 977; 36; 963; 760; 417; 577; 702; 284; 6; 980; 70; 164; 750; 762; 918; 837; 949; 448; 437; 675; 485; 505; 424; 743; 291; 350; 780; 663; 266; 675; 968; 303; 911; 865; 85];
+[ 802; 219; 197; 645; 524; 741; 149; 322; 937; 768; 999; 571; 728; 119; 708; 622; 201; 31; 312; 404; 865; 9; 410; 623; 728; 867; 896; 134; 71; 970; 363; 826; 602; 281; 279; 933; 692; 202; 376; 146; 12; 460; 560; 334; 318; 786; 952; 967; 950; 107; 200; 961; 212; 905; 721; 423; 301; 462; 522; 725; 458; 825; 146; 135; 387; 823; 727; 896; 969; 239; 882; 191; 83; 75; 920; 692; 419; 412; 684; 967; 369; 363; 739; 597; 188; 830; 721; 345; 128; 784; 247; 658; 100; 711; 190; 138; 199; 200; 482; 91];
+[ 378; 644; 233; 989; 350; 591; 387; 967; 611; 876; 891; 421; 907; 709; 719; 246; 554; 493; 665; 860; 298; 584; 309; 711; 551; 493; 907; 60; 838; 780; 396; 28; 206; 41; 224; 237; 592; 629; 985; 323; 925; 438; 863; 57; 393; 762; 626; 916; 102; 446; 351; 842; 517; 79; 358; 991; 323; 798; 106; 992; 382; 973; 769; 10; 241; 713; 224; 644; 527; 62; 912; 936; 266; 973; 238; 625; 388; 935; 683; 421; 725; 73; 358; 958; 836; 645; 3; 902; 324; 377; 497; 849; 953; 301; 187; 377; 354; 215; 718; 354];
+[ 718; 561; 503; 907; 558; 310; 147; 559; 180; 841; 753; 527; 228; 803; 738; 652; 875; 440; 952; 651; 198; 810; 221; 692; 275; 589; 340; 76; 304; 538; 81; 978; 414; 514; 269; 272; 274; 324; 708; 24; 35; 456; 432; 904; 814; 852; 211; 283; 593; 403; 289; 855; 317; 203; 285; 844; 925; 252; 785; 9; 195; 2; 779; 629; 526; 297; 594; 545; 555; 996; 810; 292; 378; 711; 581; 40; 155; 469; 894; 42; 144; 735; 462; 68; 439; 804; 599; 453; 532; 382; 234; 922; 46; 560; 663; 579; 151; 619; 512; 962];
+[ 267; 158; 607; 622; 133; 230; 262; 182; 837; 528; 301; 325; 570; 523; 141; 982; 854; 650; 331; 564; 308; 932; 164; 498; 642; 981; 99; 961; 264; 955; 827; 840; 58; 223; 353; 657; 68; 686; 659; 991; 635; 111; 525; 795; 838; 600; 94; 607; 488; 279; 886; 713; 203; 107; 657; 292; 731; 519; 467; 339; 669; 425; 161; 644; 939; 55; 727; 812; 797; 839; 213; 145; 833; 584; 804; 639; 168; 211; 842; 253; 972; 73; 4; 997; 955; 445; 352; 313; 231; 722; 88; 974; 229; 109; 350; 348; 436; 469; 453; 412];
+[ 786; 313; 406; 848; 778; 604; 373; 685; 607; 81; 297; 278; 399; 898; 552; 18; 103; 680; 334; 426; 76; 823; 981; 117; 852; 140; 402; 233; 10; 133; 457; 185; 785; 211; 79; 65; 253; 155; 392; 492; 164; 967; 344; 288; 348; 436; 505; 928; 856; 107; 229; 957; 468; 543; 783; 976; 946; 96; 6; 905; 752; 526; 123; 883; 106; 579; 512; 770; 23; 181; 915; 832; 509; 156; 96; 423; 630; 686; 3; 209; 211; 994; 67; 809; 159; 752; 820; 658; 842; 571; 796; 418; 957; 572; 994; 340; 947; 152; 288; 722];
+[ 549; 983; 337; 805; 677; 278; 888; 137; 204; 131; 165; 377; 738; 967; 752; 852; 463; 768; 605; 28; 318; 640; 737; 751; 9; 212; 433; 479; 714; 121; 781; 718; 949; 78; 665; 284; 635; 250; 446; 263; 72; 163; 849; 933; 831; 660; 471; 151; 307; 801; 49; 711; 191; 238; 672; 789; 756; 531; 946; 701; 880; 711; 355; 729; 864; 904; 934; 64; 583; 153; 400; 375; 62; 84; 494; 71; 103; 689; 713; 857; 291; 557; 245; 712; 291; 183; 18; 743; 305; 827; 400; 758; 832; 62; 168; 375; 758; 38; 496; 870];
+[ 792; 798; 284; 431; 306; 812; 79; 589; 745; 589; 358; 150; 280; 422; 386; 238; 984; 563; 609; 157; 326; 475; 487; 851; 260; 321; 754; 148; 114; 404; 891; 369; 755; 798; 400; 352; 213; 745; 75; 464; 95; 503; 63; 709; 997; 661; 343; 900; 69; 847; 422; 352; 315; 561; 988; 720; 59; 892; 208; 463; 23; 291; 755; 183; 924; 954; 650; 342; 926; 812; 516; 236; 799; 865; 253; 542; 216; 751; 610; 296; 773; 530; 803; 441; 15; 479; 588; 470; 118; 238; 17; 611; 890; 136; 566; 147; 714; 202; 480; 358];
+[ 532; 644; 973; 437; 354; 856; 647; 464; 324; 737; 166; 662; 606; 543; 22; 279; 954; 103; 498; 894; 534; 579; 451; 582; 301; 11; 292; 795; 560; 592; 945; 895; 463; 96; 22; 623; 26; 406; 61; 848; 467; 161; 512; 95; 690; 942; 41; 754; 641; 653; 811; 647; 784; 87; 507; 439; 79; 815; 446; 811; 948; 296; 685; 217; 485; 752; 398; 140; 962; 730; 292; 708; 638; 4; 798; 697; 256; 419; 472; 270; 729; 470; 586; 717; 441; 848; 991; 836; 50; 695; 696; 437; 153; 659; 841; 457; 933; 690; 591; 724];
+[ 396; 316; 856; 63; 63; 734; 58; 251; 299; 371; 17; 429; 490; 74; 864; 936; 850; 335; 173; 185; 391; 573; 183; 899; 203; 951; 641; 181; 395; 206; 414; 296; 194; 256; 634; 337; 148; 803; 373; 525; 379; 632; 410; 485; 168; 996; 427; 124; 264; 895; 620; 630; 761; 927; 855; 254; 798; 677; 610; 784; 741; 847; 793; 775; 547; 950; 354; 501; 264; 633; 721; 813; 411; 685; 556; 828; 512; 73; 684; 836; 73; 362; 810; 438; 984; 788; 438; 517; 53; 103; 487; 446; 923; 407; 685; 973; 927; 988; 662; 643]
+];;
+
+
+matrix_mul matrix1 matrix2;;

+ 84 - 0
talks/cant-c-a-thing/code/ocamlmatrix.c

@@ -0,0 +1,84 @@
+#include "ruby.h"
+#include "caml/callback.h"
+     
+void initcaml(void) {
+  char *dummy_argv[] = {0};
+
+  caml_startup(dummy_argv);
+}
+
+
+VALUE OcamlMatrix = Qnil; // set up the Ruby namespace
+    
+void Init_ocamlmatrix();
+
+VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height);
+void method_assign(VALUE self, VALUE x, VALUE y, VALUE item);
+VALUE method_retrieve(VALUE self, VALUE x, VALUE y);
+VALUE method_matrix_mul(VALUE self, VALUE b);
+
+void Init_ocamlmatrix() {
+  OcamlMatrix = rb_define_module("OcamlMatrix");
+  rb_define_singleton_method(OcamlMatrix, "w_new_matrix", singleton_matrix_new, 2);
+  rb_define_method(OcamlMatrix, "w_matrix_assign", method_assign, 3);
+  rb_define_method(OcamlMatrix, "w_matrix_retrieve", method_retrieve, 2);
+  rb_define_method(OcamlMatrix, "w_matrix_mul", method_matrix_mul, 1);
+
+  initcaml();
+}
+
+
+VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+  ocaml_m = ocaml_new_matrix(Val_long(NUM2LONG(width)), Val_long(NUM2LONG(height)));
+
+  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+  rb_obj_call_init(my_m, 0, NULL);
+
+  CAMLreturnT (VALUE, my_m);
+}
+
+
+void method_assign(VALUE self, VALUE x, VALUE y, VALUE item) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+
+  Data_Get_Struct(self, value, ocaml_m);
+
+  ocaml_matrix_assign(ocaml_m, Val_long(NUM2LONG(x)), Val_long(NUM2LONG(y)), Val_long(NUM2LONG(item)));
+
+  CAMLreturn0;
+}
+
+
+VALUE method_retrieve(VALUE self, VALUE x, VALUE y) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+
+  Data_Get_Struct(self, value, ocaml_m);
+
+  VALUE retval = LONG2NUM(Long_val(ocaml_matrix_retrieve(ocaml_m,
+                                                         Val_long(NUM2LONG(x)),
+                                                         Val_long(NUM2LONG(y)))
+                                   ));
+
+  CAMLreturnT (VALUE, retval);
+}
+
+
+VALUE method_matrix_mul(VALUE self, VALUE b) {
+  CAMLparam0 ();
+  CAMLlocal3 (ocaml_m1, ocaml_m2, ocaml_m3);
+
+  Data_Get_Struct(self, value, ocaml_m1);
+  Data_Get_Struct(b, value, ocaml_m2);
+
+  ocaml_m3 = ocaml_matrix_mul(ocaml_m1, ocaml_m2);
+
+  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+  rb_obj_call_init(my_m, 0, NULL);
+
+  CAMLreturnT (VALUE, my_m);
+}
+

+ 28 - 0
talks/cant-c-a-thing/code/ocamlmatrix_old.c

@@ -0,0 +1,28 @@
+#include "ruby.h"
+
+#include "caml/callback.h"
+     
+void initcaml(void) {
+  char *dummy_argv[] = {0};
+
+  caml_startup(dummy_argv);
+}
+
+
+VALUE OcamlMatrix = Qnil; // set up the Ruby namespace
+    
+void Init_ocamlmatrix();
+
+
+int interm_matrix_mul(*a, *b) {
+  static value *f = NULL;
+
+  f = caml_named_value("ocaml_matrix_mul");
+  if (f == NULL)
+    return -1;
+
+  value r_caml;
+  r_caml = callback(*f, a, b);
+
+}
+

+ 31 - 0
talks/cant-c-a-thing/code/wrapped_ocaml.ml

@@ -0,0 +1,31 @@
+let matrix_mul x y =
+  let x0 = Array.length x
+  and y0 = Array.length y in
+  let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+  let z = Array.make_matrix x0 y1 0 in
+  for i = 0 to x0-1 do
+    for j = 0 to y1-1 do
+      for k = 0 to y0-1 do
+        z.(i).(j) <- z.(i).(j) + x.(i).(k) * y.(k).(j)
+      done
+    done
+  done;
+  z
+
+let new_matrix width height =
+  Array.make_matrix width height 0
+
+let matrix_assign m x y item =
+  m.(x).(y) <- item
+
+let matrix_retrieve m x y =
+  m.(x).(y)
+
+
+let _ = Callback.register "ocaml_new_matrix" new_matrix
+
+let _ = Callback.register "ocaml_matrix_assign" matrix_assign
+
+let _ = Callback.register "ocaml_matrix_retrieve" matrix_retrieve
+
+let _ = Callback.register "ocaml_matrix_mul" matrix_mul

BIN
talks/cant-c-a-thing/img/dhh.jpg


+ 621 - 0
talks/cant-c-a-thing/index.html

@@ -0,0 +1,621 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title>Speed Up that Library when You Can't C a Thing</title>
+
+<style>
+
+.slide {
+  border: 2px solid #668888;
+  background-color: #A4CAE1;
+  padding: 2%;
+  width: 94%;
+}
+
+pre {
+  border: 1px solid #224444;
+  background-color: #B4DAF1;
+  padding: 2px;
+}
+
+
+</style>
+
+<script src="scripts/jquery-1.2.3.js" type="text/javascript" />
+<script src="scripts/slideshow.js" type="text/javascript" />
+</head>
+
+<body>
+
+<div class='slide'>
+<h1>Speed up that library when you can't C a thing</h1>
+<p>John Melesky
+(Open Source Bridge, June 2009)</p>
+</div>
+
+
+<div class='slide'>
+<h1>Caveats</h1>
+<ul>
+<li>We're talking about Unix and Linux<ul>
+<li>Most should be applicable on OSX once you factor in the dynlib stuff</li>
+<li>I have no idea on the Windows front; sorry</li>
+</ul>
+</li>
+</ul>
+</div>
+
+
+<div class='slide'>
+<h1>Caveats</h1>
+<ul>
+<li>We're talking about Unix and Linux<ul>
+<li>Most should be applicable on OSX once you factor in the dynlib stuff</li>
+<li>I have no idea on the Windows front; sorry</li>
+</ul>
+</li>
+<li>The example is in Ruby and Ocaml<ul>
+<li>The FFIs for Perl and Python are similar</li>
+<li>Other called languages are possible</li>
+<li>I'll try to be explicit what's general and what's example-specific</li>
+</ul>
+</li>
+</ul>
+</div>
+
+
+<div class='slide'>
+<h1>Ever in this situation?</h1>
+</div>
+
+
+<div class='slide'>
+<h1>The culprit</h1>
+<pre><code>def matrix_mul(a,b)
+  rows = (0 .. a.size - 1).collect {
+    |i|
+    (0 .. b[0].size - 1).collect {
+      |j|
+      vij = 0
+      0.upto(a[i].size - 1) do
+        |k|
+        vij += a[i][k] * b[k][j]
+      end
+      vij
+    }
+  }
+  return rows
+end
+</code></pre>
+<p>(adapted from matrix.rb)</p>
+</div>
+
+
+<div class='slide'>
+<h1>The solution</h1>
+<table>
+<tr>
+<td><b><font size="+2">That's what C is for, right?</b></td>
+<td><img src="img/dhh.jpg"></td>
+</tr>
+</table>
+</div>
+
+
+<div class='slide'>
+<h1>That's not what C is for!</h1>
+<p>C is a systems language, intended for use with 30-year-old kernels and drivers, on processors with fewer than 10k transistors (we're currently in the billion-transistor range, thankyouverymuch Moore's Law).</p>
+</div>
+
+
+<div class='slide'>
+<h1>That's not what C is for!</h1>
+<p>C is a systems language, intended for use with 30-year-old kernels and drivers, on processors with fewer than 10k transistors (we're currently in the billion-transistor range, thankyouverymuch Moore's Law).</p>
+<p>In other words, it's fast only as a side effect.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Other languages are better</h1>
+<p>For raw numerics, Fortran still has C beat. APL is pretty dang fast, too.</p>
+<p>For complex datatypes, languages like Haskell or ML will all give you better type support, safer usage, and possibly faster code.</p>
+<p>Really, almost any language offers benefits over C, whether it's faster, safer, more concise, more advanced, or all of the above.</p>
+</div>
+
+
+<div class='slide'>
+<h1>What are the options?</h1>
+<p>The only constraint is that your target language needs an FFI (Foreign Function Interface) that allows C to call it. Most awesome languages allow this. I'm going to use Ocaml.</p>
+<p>The other material requirements are your language header files (<code>ruby.h</code>, <code>caml/callback.h</code>, etc.), and perserverence.</p>
+</div>
+
+
+<div class='slide'>
+<h1>What are the options?</h1>
+<p>The only constraint is that your target language needs an FFI (Foreign Function Interface) that allows C to call it. Most awesome languages allow this. I'm going to use Ocaml.</p>
+<p>The other material requirements are your language header files (<code>ruby.h</code>, <code>caml/callback.h</code>, etc.), and perserverence.</p>
+<p>(You may look warily upon the whole "allows C to call it" thing. This is understandable. Things will get worse before they get better.)</p>
+</div>
+
+
+<div class='slide'>
+<h1>What's Ocaml?</h1>
+<p>In brief, it's a statically- and strongly- typed, non-pure, strict, functional programming language. It's from the ML family (which includes SML, F#, and some others).</p>
+<p>Ocaml's pretty cool at the moment, because it's one of the fastest languages around, routinely rivalling C++ in the Shootout.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Matrix Multiplication in Ocaml, round 1</h1>
+<pre><code>let matrix_mul a b =
+  List.map
+    (fun row -&gt;
+      mapn
+       (fun column -&gt;
+         List.fold_left (+) 0
+          (List.map2 ( * ) row column))
+       b)
+    a
+</code></pre>
+<p>(cribbed from RosettaCode)</p>
+</div>
+
+
+<div class='slide'>
+<h1>Benefits</h1>
+<pre><code>let matrix_mul a b =
+  List.map
+    (fun row -&gt;
+      mapn
+       (fun column -&gt;
+         List.fold_left (+) 0
+          (List.map2 ( * ) row column))
+       b)
+    a
+</code></pre>
+<p>We've acheived a 10x speedup, and our code is not actually much more complicated than the Ruby.</p>
+<p>This code uses Lists, which are pretty natural to use in functional languages. Arrays would probably be faster, though.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Matrix Multiplication in Ocaml, round 2</h1>
+<pre><code>let matrix_mul x y =
+  let x0 = Array.length x
+  and y0 = Array.length y in
+  let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+  let z = Array.make_matrix x0 y1 0 in
+  for i = 0 to x0-1 do
+    for j = 0 to y1-1 do
+      for k = 0 to y0-1 do
+        z.(i).(j) &lt;- z.(i).(j) + x.(i).(k) * y.(k).(j)
+      done
+    done
+  done;
+  z
+</code></pre>
+<p>(also cribbed from RosettaCode)</p>
+</div>
+
+
+<div class='slide'>
+<h1>Benefits</h1>
+<pre><code>let matrix_mul x y =
+  let x0 = Array.length x
+  and y0 = Array.length y in
+  let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+  let z = Array.make_matrix x0 y1 0 in
+  for i = 0 to x0-1 do
+    for j = 0 to y1-1 do
+      for k = 0 to y0-1 do
+        z.(i).(j) &lt;- z.(i).(j) + x.(i).(k) * y.(k).(j)
+      done
+    done
+  done;
+  z
+</code></pre>
+<p>Ocaml Arrays are pretty darn fast, and have a great initialization function tailored to this sort of problem (make_matrix).</p>
+<p>We've now managed a speedup around 30-35x. And, again, the code is not that much more complex than the original Ruby.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Gluing Pieces Together</h1>
+<p>Of course, the Ocaml code is operating on Ocaml types, and Ruby wants to pass it Ruby types. What to do, what to do?</p>
+</div>
+
+
+<div class='slide'>
+<h1>It's gonna get worse before it gets better...</h1>
+<p>Ruby can call C code...</p>
+</div>
+
+
+<div class='slide'>
+<h1>It's gonna get worse before it gets better...</h1>
+<p>Ruby can call C code...</p>
+<p>Ocaml can be called by C code...</p>
+</div>
+
+
+<div class='slide'>
+<h1>It's gonna get worse before it gets better...</h1>
+<p>Ruby can call C code...</p>
+<p>Ocaml can be called by C code...</p>
+<p>They both have facilities to translate their types into C types (and Ocaml vice-versa)...</p>
+</div>
+
+
+<div class='slide'>
+<h1>FFI Aside</h1>
+<p>Many languages provide C type definitions and functions for extensions. There are two problems with this.</p>
+<ol>
+<li>Many of these FFIs aren't merely type declarations and functions, but also macros. That means they only work in C.</li>
+<li>Even if they were macro-free, few languages have all-purpose transparent C access. Thus, wrapper code is always needed.</li>
+</ol>
+</div>
+
+
+<div class='slide'>
+<h1>Dancing the Type Two-Step</h1>
+<p>Let's work our way out, from Ocaml to Ruby, via C. First, we need to tell Ocaml to export a function to C.</p>
+<pre><code>let matrix_mul x y =
+  let x0 = Array.length x
+  and y0 = Array.length y in
+  let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+  let z = Array.make_matrix x0 y1 0 in
+  for i = 0 to x0-1 do
+    for j = 0 to y1-1 do
+      for k = 0 to y0-1 do
+        z.(i).(j) &lt;- z.(i).(j) + x.(i).(k) * y.(k).(j)
+      done
+    done
+  done;
+  z
+
+let _ = Callback.register "ocaml_matrix_mul" matrix_mul;;
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Dancing the Type Two-Step</h1>
+<p>Next, we need some C code to call the Ocaml.</p>
+<pre><code>#include "caml/callback.h"
+
+void initcaml(void) {
+  char *dummy_argv[] = {0};
+
+  caml_startup(dummy_argv);
+}
+</code></pre>
+<p>First we initialize the Ocaml runtime.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Dancing the Type Two-Step</h1>
+<pre><code>int interm_matrix_mul(*a, *b) {
+  static value *f = NULL;
+
+  f = caml_named_value("ocaml_matrix_mul");
+  if (f == NULL)
+    return -1;
+
+  value r_caml;
+  r_caml = callback2(*f, a, b);
+</code></pre>
+<p>This is how we grab and execute the exported code. Note that we haven't figured the types for <code>a</code> or <code>b</code>, nor have we converted r_caml to something C-like. Who's up for C arrays of C arrays of C <code>int</code>s? (Or should they be <code>long</code>s?)</p>
+</div>
+
+
+<div class='slide'>
+<h1>Dancing the Type Two-Step</h1>
+<p>From the Ruby side, we'd do something like this.</p>
+<pre><code>#include "ruby.h"
+
+VALUE OcamlMatrix = Qnil; // set up the Ruby namespace
+
+// prototype our init and method
+void Init_ocamlmatrix();
+VALUE method_matrix_mul(VALUE self, VALUE a, VALUE b);
+
+void Init_ocamlmatrix() {
+     OcamlMatrix = rb_define_module("OcamlMatrix");
+     rb_define_method(OcamlMatrix, "matrix_mul", method_matrix_mul, 0);
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Dancing the Type Two-Step</h1>
+<pre><code>VALUE method_matrix_mul(VALUE self, VALUE a, VALUE b) {
+  // here we would convert a and b to C arrays
+
+  c = interm_matrix_mul(conv_a, conv_b);
+
+  // and then convert c from C arrays to a Ruby VALUE
+
+  return conv_c;
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>You probably saw this coming</h1>
+<p>You can convert directly from the Ruby types to Ocaml types without C intermediate types. This is nice, because it avoids any need for structs or pointer arithmetic.</p>
+</div>
+
+
+<div class='slide'>
+<h1>You probably saw this coming</h1>
+<p>You can convert directly from the Ruby types to Ocaml types without C intermediate types. This is nice, because it avoids any need for structs or pointer arithmetic.</p>
+<p>But... this only works if you can import both languages' headers without name conflicts (thankfully Ruby's VALUE and Ocaml's value are spelled differently).</p>
+</div>
+
+
+<div class='slide'>
+<h1>From Two-Step to Tango</h1>
+<pre><code>VALUE method_matrix_mul(VALUE self, VALUE a, VALUE b) {
+  CAMLparam0 ();
+  CAMLlocal2 (caml_a, caml_b);
+  CAMLlocal1 (caml_interm);
+  CAMLlocal1 (caml_item);
+
+  int x = RARRAY(a)-&gt;len;
+  for (int i = 0; i&lt;x; i++) {
+    VALUE row = rb_ary_entry(a, i); // row = a[i]
+
+    int y = RARRAY(row)-&gt;len;
+
+    caml_interm = caml_alloc(y, 0);
+
+    for (int j = 0; j&lt;y; j++) {
+      VALUE item = rb_ary_entry(row, j);
+
+      long c_item = NUM2LONG(item);
+      caml_item = Val_long(c_item);
+      Store_field(caml_interm, j, caml_item);
+    }
+  }
+
+  // do the same for b
+
+  caml_c = interm_matrix_mul(caml_a, caml_b);
+
+  // and then convert caml_c from value to a Ruby VALUE
+
+  CAMLreturnT(VALUE, ruby_c);
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Did you spot the problems?</h1>
+</div>
+
+
+<div class='slide'>
+<h1>Hmmm...</h1>
+</div>
+
+
+<div class='slide'>
+<h1>Hmmm...</h1>
+<p>Complex data is the problem.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Hmmm...</h1>
+<p>Complex data is the problem.</p>
+<p>What if the data was already in Ocaml types when matrix_mul is called?</p>
+</div>
+
+
+<div class='slide'>
+<h1>Don't wrap a function, wrap a class</h1>
+</div>
+
+
+<div class='slide'>
+<h1>Tango to Salsa</h1>
+<pre><code>let new_matrix width height =
+  Array.make_matrix width height 0
+
+let matrix_assign m x y item =
+  m.(x).(y) &lt;- item
+
+let matrix_retrieve m x y =
+  m.(x).(y)
+
+let _ = Callback.register "ocaml_new_matrix" new_matrix
+let _ = Callback.register "ocaml_matrix_assign" matrix_assign
+let _ = Callback.register "ocaml_matrix_retrieve" matrix_retrieve
+let _ = Callback.register "ocaml_matrix_mul" matrix_mul
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Tango to Salsa</h1>
+<pre><code>void Init_ocamlmatrix() {
+  OcamlMatrix = rb_define_module("OcamlMatrix");
+  rb_define_singleton_method(OcamlMatrix, "w_new_matrix", singleton_matrix_new, 2);
+  rb_define_method(OcamlMatrix, "w_matrix_assign", method_assign, 3);
+  rb_define_method(OcamlMatrix, "w_matrix_retrieve", method_retrieve, 2);
+  rb_define_method(OcamlMatrix, "w_matrix_mul", method_matrix_mul, 1);
+
+  initcaml();
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Tango to Salsa</h1>
+<pre><code>VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+  ocaml_m = ocaml_new_matrix(Val_long(NUM2LONG(width)),
+                             Val_long(NUM2LONG(height)));
+
+  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+  rb_obj_call_init(my_m, 0, NULL);
+
+  CAMLreturnT (VALUE, my_m);
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Tango to Salsa</h1>
+<pre><code>void method_assign(VALUE self, VALUE x, VALUE y, VALUE item) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+
+  Data_Get_Struct(self, value, ocaml_m);
+
+  ocaml_matrix_assign(ocaml_m, Val_long(NUM2LONG(x)),
+                      Val_long(NUM2LONG(y)),
+                      Val_long(NUM2LONG(item)));
+
+  CAMLreturn0;
+}
+
+VALUE method_retrieve(VALUE self, VALUE x, VALUE y) {
+  CAMLparam0 ();
+  CAMLlocal1 (ocaml_m);
+
+  Data_Get_Struct(self, value, ocaml_m);
+
+  VALUE retval = LONG2NUM(Long_val(ocaml_matrix_retrieve(ocaml_m,
+                                                         Val_long(NUM2LONG(x)),
+                                                         Val_long(NUM2LONG(y)))
+                                   ));
+
+  CAMLreturnT (VALUE, retval);
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Tango to Salsa</h1>
+<pre><code>VALUE method_matrix_mul(VALUE self, VALUE b) {
+  CAMLparam0 ();
+  CAMLlocal3 (ocaml_m1, ocaml_m2, ocaml_m3);
+
+  Data_Get_Struct(self, value, ocaml_m1);
+  Data_Get_Struct(b, value, ocaml_m2);
+
+  ocaml_m3 = ocaml_matrix_mul(ocaml_m1, ocaml_m2);
+
+  VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+  rb_obj_call_init(my_m, 0, NULL);
+
+  CAMLreturnT (VALUE, my_m);
+}
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Add a Ruby slipper</h1>
+</div>
+
+
+<div class='slide'>
+<h1>No Silver Bullets</h1>
+<p>But we have a couple bronze ones.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Perl</h1>
+<p>Inline:: libraries on CPAN.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Perl</h1>
+<pre><code>use Inline "Befunge";
+
+hello();
+
+__END__
+__Befunge__
+;:hello - print a msg;&lt;q_,#! #:&lt;"Hello world!"a
+
+    ;
+    :
+    a             q - ;tcartsbus:;
+    d
+    d
+    ;
+    +  ;not a valid func def;
+    q
+</code></pre>
+</div>
+
+
+<div class='slide'>
+<h1>Python</h1>
+<p>PyCaml, PyObjC.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Python Calling Python</h1>
+<p>SciPy, NumPy.</p>
+<p>Cython.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Ruby</h1>
+<p>Well after i decided on using Ruby and using Ocaml...</p>
+</div>
+
+
+<div class='slide'>
+<h1>Ruby</h1>
+<p>Well after i decided on using Ruby and using Ocaml...</p>
+<p>... i found <a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/256569">Rocaml</a>.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Ruby</h1>
+<p>Well after i decided on using Ruby and using Ocaml...</p>
+<p>... i found <a href="http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/256569">Rocaml</a>.</p>
+<p>Also available is the Ruby clone of Perl's Inline::, including InlineFortran.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Phew</h1>
+</div>
+
+
+<div class='slide'>
+<h1>Phew</h1>
+<p>Any questions?</p>
+</div>
+
+
+<div class='slide'>
+<h1>Thanks</h1>
+<p>Thanks for coming. Thanks OSBridge for putting together an awesome conference.</p>
+</div>
+
+
+<div class='slide'>
+
+</div>
+
+
+</body></html>

+ 494 - 0
talks/cant-c-a-thing/index.txt

@@ -0,0 +1,494 @@
+# Speed up that library when you can't C a thing
+
+
+
+John Melesky
+(Open Source Bridge, June 2009)
+---
+# Caveats
+
+- We're talking about Unix and Linux
+    - Most should be applicable on OSX once you factor in the dynlib stuff
+    - I have no idea on the Windows front; sorry
+
+---
+# Caveats
+
+- We're talking about Unix and Linux
+    - Most should be applicable on OSX once you factor in the dynlib stuff
+    - I have no idea on the Windows front; sorry
+- The example is in Ruby and Ocaml
+    - The FFIs for Perl and Python are similar
+    - Other called languages are possible
+    - I'll try to be explicit what's general and what's example-specific
+---
+# Ever in this situation?
+---
+# The culprit
+
+    def matrix_mul(a,b)
+      rows = (0 .. a.size - 1).collect {
+        |i|
+        (0 .. b[0].size - 1).collect {
+          |j|
+          vij = 0
+          0.upto(a[i].size - 1) do
+            |k|
+            vij += a[i][k] * b[k][j]
+          end
+          vij
+        }
+      }
+      return rows
+    end
+
+(adapted from matrix.rb)
+---
+# The solution
+
+<table>
+<tr>
+<td><b><font size="+2">That's what C is for, right?</b></td>
+<td><img src="img/dhh.jpg"></td>
+</tr>
+</table>
+---
+# That's not what C is for!
+
+C is a systems language, intended for use with 30-year-old kernels and drivers, on processors with fewer than 10k transistors (we're currently in the billion-transistor range, thankyouverymuch Moore's Law).
+
+
+---
+# That's not what C is for!
+
+C is a systems language, intended for use with 30-year-old kernels and drivers, on processors with fewer than 10k transistors (we're currently in the billion-transistor range, thankyouverymuch Moore's Law).
+
+In other words, it's fast only as a side effect.
+---
+# Other languages are better
+
+For raw numerics, Fortran still has C beat. APL is pretty dang fast, too.
+
+For complex datatypes, languages like Haskell or ML will all give you better type support, safer usage, and possibly faster code.
+
+Really, almost any language offers benefits over C, whether it's faster, safer, more concise, more advanced, or all of the above.
+---
+# What are the options?
+
+The only constraint is that your target language needs an FFI (Foreign Function Interface) that allows C to call it. Most awesome languages allow this. I'm going to use Ocaml.
+
+The other material requirements are your language header files (`ruby.h`, `caml/callback.h`, etc.), and perserverence.
+---
+# What are the options?
+
+The only constraint is that your target language needs an FFI (Foreign Function Interface) that allows C to call it. Most awesome languages allow this. I'm going to use Ocaml.
+
+The other material requirements are your language header files (`ruby.h`, `caml/callback.h`, etc.), and perserverence.
+
+(You may look warily upon the whole "allows C to call it" thing. This is understandable. Things will get worse before they get better.)
+---
+# What's Ocaml?
+
+In brief, it's a statically- and strongly- typed, non-pure, strict, functional programming language. It's from the ML family (which includes SML, F#, and some others).
+
+Ocaml's pretty cool at the moment, because it's one of the fastest languages around, routinely rivalling C++ in the Shootout.
+---
+# Matrix Multiplication in Ocaml, round 1
+
+    let matrix_mul a b =
+      List.map
+        (fun row ->
+          mapn
+           (fun column ->
+             List.fold_left (+) 0
+              (List.map2 ( * ) row column))
+           b)
+        a
+
+(cribbed from RosettaCode)
+---
+# Benefits
+
+    let matrix_mul a b =
+      List.map
+        (fun row ->
+          mapn
+           (fun column ->
+             List.fold_left (+) 0
+              (List.map2 ( * ) row column))
+           b)
+        a
+
+We've acheived a 10x speedup, and our code is not actually much more complicated than the Ruby.
+
+This code uses Lists, which are pretty natural to use in functional languages. Arrays would probably be faster, though.
+---
+# Matrix Multiplication in Ocaml, round 2
+
+    let matrix_mul x y =
+      let x0 = Array.length x
+      and y0 = Array.length y in
+      let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+      let z = Array.make_matrix x0 y1 0 in
+      for i = 0 to x0-1 do
+        for j = 0 to y1-1 do
+          for k = 0 to y0-1 do
+            z.(i).(j) <- z.(i).(j) + x.(i).(k) * y.(k).(j)
+          done
+        done
+      done;
+      z
+
+(also cribbed from RosettaCode)
+---
+# Benefits
+
+    let matrix_mul x y =
+      let x0 = Array.length x
+      and y0 = Array.length y in
+      let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+      let z = Array.make_matrix x0 y1 0 in
+      for i = 0 to x0-1 do
+        for j = 0 to y1-1 do
+          for k = 0 to y0-1 do
+            z.(i).(j) <- z.(i).(j) + x.(i).(k) * y.(k).(j)
+          done
+        done
+      done;
+      z
+
+Ocaml Arrays are pretty darn fast, and have a great initialization function tailored to this sort of problem (make_matrix).
+
+We've now managed a speedup around 30-35x. And, again, the code is not that much more complex than the original Ruby.
+---
+# Gluing Pieces Together
+
+Of course, the Ocaml code is operating on Ocaml types, and Ruby wants to pass it Ruby types. What to do, what to do?
+---
+# It's gonna get worse before it gets better...
+
+Ruby can call C code...
+---
+# It's gonna get worse before it gets better...
+
+Ruby can call C code...
+
+Ocaml can be called by C code...
+---
+# It's gonna get worse before it gets better...
+
+Ruby can call C code...
+
+Ocaml can be called by C code...
+
+They both have facilities to translate their types into C types (and Ocaml vice-versa)...
+---
+# FFI Aside
+
+Many languages provide C type definitions and functions for extensions. There are two problems with this.
+
+1. Many of these FFIs aren't merely type declarations and functions, but also macros. That means they only work in C.
+2. Even if they were macro-free, few languages have all-purpose transparent C access. Thus, wrapper code is always needed.
+---
+# Dancing the Type Two-Step
+
+Let's work our way out, from Ocaml to Ruby, via C. First, we need to tell Ocaml to export a function to C.
+
+    let matrix_mul x y =
+      let x0 = Array.length x
+      and y0 = Array.length y in
+      let y1 = if y0 = 0 then 0 else Array.length y.(0) in
+      let z = Array.make_matrix x0 y1 0 in
+      for i = 0 to x0-1 do
+        for j = 0 to y1-1 do
+          for k = 0 to y0-1 do
+            z.(i).(j) <- z.(i).(j) + x.(i).(k) * y.(k).(j)
+          done
+        done
+      done;
+      z
+
+    let _ = Callback.register "ocaml_matrix_mul" matrix_mul;;
+
+---
+# Dancing the Type Two-Step
+
+Next, we need some C code to call the Ocaml.
+
+    #include "caml/callback.h"
+     
+    void initcaml(void) {
+      char *dummy_argv[] = {0};
+     
+      caml_startup(dummy_argv);
+    }
+ 
+First we initialize the Ocaml runtime.
+---
+# Dancing the Type Two-Step
+
+    int interm_matrix_mul(*a, *b) {
+      static value *f = NULL;
+     
+      f = caml_named_value("ocaml_matrix_mul");
+      if (f == NULL)
+        return -1;
+
+      value r_caml;
+      r_caml = callback2(*f, a, b);
+
+This is how we grab and execute the exported code. Note that we haven't figured the types for `a` or `b`, nor have we converted r_caml to something C-like. Who's up for C arrays of C arrays of C `int`s? (Or should they be `long`s?)
+---
+# Dancing the Type Two-Step
+
+From the Ruby side, we'd do something like this.
+
+    #include "ruby.h"
+    
+    VALUE OcamlMatrix = Qnil; // set up the Ruby namespace
+    
+    // prototype our init and method
+    void Init_ocamlmatrix();
+    VALUE method_matrix_mul(VALUE self, VALUE a, VALUE b);
+    
+    void Init_ocamlmatrix() {
+	     OcamlMatrix = rb_define_module("OcamlMatrix");
+	     rb_define_method(OcamlMatrix, "matrix_mul", method_matrix_mul, 0);
+    }
+---
+# Dancing the Type Two-Step
+
+    VALUE method_matrix_mul(VALUE self, VALUE a, VALUE b) {
+      // here we would convert a and b to C arrays
+
+      c = interm_matrix_mul(conv_a, conv_b);
+
+      // and then convert c from C arrays to a Ruby VALUE
+
+      return conv_c;
+    }
+---
+# You probably saw this coming
+
+You can convert directly from the Ruby types to Ocaml types without C intermediate types. This is nice, because it avoids any need for structs or pointer arithmetic.
+
+---
+# You probably saw this coming
+
+You can convert directly from the Ruby types to Ocaml types without C intermediate types. This is nice, because it avoids any need for structs or pointer arithmetic.
+
+But... this only works if you can import both languages' headers without name conflicts (thankfully Ruby's VALUE and Ocaml's value are spelled differently).
+---
+# From Two-Step to Tango
+
+    VALUE method_matrix_mul(VALUE self, VALUE a, VALUE b) {
+      CAMLparam0 ();
+      CAMLlocal2 (caml_a, caml_b);
+      CAMLlocal1 (caml_interm);
+      CAMLlocal1 (caml_item);
+      
+      int x = RARRAY(a)->len;
+      for (int i = 0; i<x; i++) {
+        VALUE row = rb_ary_entry(a, i); // row = a[i]
+        
+        int y = RARRAY(row)->len;
+        
+        caml_interm = caml_alloc(y, 0);
+        
+        for (int j = 0; j<y; j++) {
+          VALUE item = rb_ary_entry(row, j);
+          
+          long c_item = NUM2LONG(item);
+          caml_item = Val_long(c_item);
+          Store_field(caml_interm, j, caml_item);
+        }
+      }
+      
+      // do the same for b
+      
+      caml_c = interm_matrix_mul(caml_a, caml_b);
+      
+      // and then convert caml_c from value to a Ruby VALUE
+      
+      CAMLreturnT(VALUE, ruby_c);
+    }
+---
+# Did you spot the problems?
+---
+# Hmmm...
+---
+# Hmmm...
+
+Complex data is the problem.
+---
+# Hmmm...
+
+Complex data is the problem.
+
+What if the data was already in Ocaml types when matrix_mul is called?
+---
+# Don't wrap a function, wrap a class
+---
+# Tango to Salsa
+    let new_matrix width height =
+      Array.make_matrix width height 0
+    
+    let matrix_assign m x y item =
+      m.(x).(y) <- item
+    
+    let matrix_retrieve m x y =
+      m.(x).(y)
+    
+    let _ = Callback.register "ocaml_new_matrix" new_matrix
+    let _ = Callback.register "ocaml_matrix_assign" matrix_assign
+    let _ = Callback.register "ocaml_matrix_retrieve" matrix_retrieve
+    let _ = Callback.register "ocaml_matrix_mul" matrix_mul
+
+---
+# Tango to Salsa
+
+    void Init_ocamlmatrix() {
+      OcamlMatrix = rb_define_module("OcamlMatrix");
+      rb_define_singleton_method(OcamlMatrix, "w_new_matrix", singleton_matrix_new, 2);
+      rb_define_method(OcamlMatrix, "w_matrix_assign", method_assign, 3);
+      rb_define_method(OcamlMatrix, "w_matrix_retrieve", method_retrieve, 2);
+      rb_define_method(OcamlMatrix, "w_matrix_mul", method_matrix_mul, 1);
+    
+      initcaml();
+    }
+---
+# Tango to Salsa
+
+    VALUE singleton_matrix_new(VALUE class, VALUE width, VALUE height) {
+      CAMLparam0 ();
+      CAMLlocal1 (ocaml_m);
+      ocaml_m = ocaml_new_matrix(Val_long(NUM2LONG(width)),
+                                 Val_long(NUM2LONG(height)));
+    
+      VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+      rb_obj_call_init(my_m, 0, NULL);
+    
+      CAMLreturnT (VALUE, my_m);
+    }
+
+---
+# Tango to Salsa
+
+    void method_assign(VALUE self, VALUE x, VALUE y, VALUE item) {
+      CAMLparam0 ();
+      CAMLlocal1 (ocaml_m);
+    
+      Data_Get_Struct(self, value, ocaml_m);
+    
+      ocaml_matrix_assign(ocaml_m, Val_long(NUM2LONG(x)),
+                          Val_long(NUM2LONG(y)),
+                          Val_long(NUM2LONG(item)));
+    
+      CAMLreturn0;
+    }
+    
+    
+    VALUE method_retrieve(VALUE self, VALUE x, VALUE y) {
+      CAMLparam0 ();
+      CAMLlocal1 (ocaml_m);
+    
+      Data_Get_Struct(self, value, ocaml_m);
+    
+      VALUE retval = LONG2NUM(Long_val(ocaml_matrix_retrieve(ocaml_m,
+                                                             Val_long(NUM2LONG(x)),
+                                                             Val_long(NUM2LONG(y)))
+                                       ));
+    
+      CAMLreturnT (VALUE, retval);
+    }
+
+---
+# Tango to Salsa
+
+    VALUE method_matrix_mul(VALUE self, VALUE b) {
+      CAMLparam0 ();
+      CAMLlocal3 (ocaml_m1, ocaml_m2, ocaml_m3);
+    
+      Data_Get_Struct(self, value, ocaml_m1);
+      Data_Get_Struct(b, value, ocaml_m2);
+    
+      ocaml_m3 = ocaml_matrix_mul(ocaml_m1, ocaml_m2);
+    
+      VALUE my_m = Data_Wrap_Struct(class, 0, 0, ocaml_m);
+      rb_obj_call_init(my_m, 0, NULL);
+    
+      CAMLreturnT (VALUE, my_m);
+    }
+
+---
+# Add a Ruby slipper
+
+---
+# No Silver Bullets
+
+But we have a couple bronze ones.
+---
+# Perl
+
+Inline:: libraries on CPAN.
+---
+# Perl
+
+    use Inline "Befunge";
+    
+    hello();
+    
+    __END__
+    __Befunge__
+    ;:hello - print a msg;<q_,#! #:<"Hello world!"a
+    
+        ;
+        :
+        a             q - ;tcartsbus:;
+        d
+        d
+        ;
+        +  ;not a valid func def;
+        q
+
+---
+# Python
+
+PyCaml, PyObjC.
+---
+# Python Calling Python
+
+SciPy, NumPy.
+
+Cython.
+---
+# Ruby
+
+Well after i decided on using Ruby and using Ocaml...
+
+---
+# Ruby
+
+Well after i decided on using Ruby and using Ocaml...
+
+... i found [Rocaml](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/256569).
+---
+# Ruby
+
+Well after i decided on using Ruby and using Ocaml...
+
+... i found [Rocaml](http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/256569).
+
+Also available is the Ruby clone of Perl's Inline::, including InlineFortran.
+---
+# Phew
+---
+# Phew
+
+Any questions?
+---
+# Thanks
+
+Thanks for coming. Thanks OSBridge for putting together an awesome conference.
+---
+

+ 136 - 0
talks/cant-c-a-thing/scripts/builder.js

@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) { 
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
+  
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+  
+    tags.each( function(tag){ 
+      scope[tag] = function() { 
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
+      } 
+    });
+  }
+}

+ 965 - 0
talks/cant-c-a-thing/scripts/controls.js

@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+//  Richard Livsey
+//  Rahul Bhargava
+//  Rob Wills
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality 
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least, 
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method 
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most 
+// useful when one of the tokens is \n (a newline), as it 
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+  throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { }
+Autocompleter.Base = Class.create({
+  baseInitialize: function(element, update, options) {
+    element          = $(element)
+    this.element     = element; 
+    this.update      = $(update);  
+    this.hasFocus    = false; 
+    this.changed     = false; 
+    this.active      = false; 
+    this.index       = 0;     
+    this.entryCount  = 0;
+    this.oldElementValue = this.element.value;
+
+    if(this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = options || { };
+
+    this.options.paramName    = this.options.paramName || this.element.name;
+    this.options.tokens       = this.options.tokens || [];
+    this.options.frequency    = this.options.frequency || 0.4;
+    this.options.minChars     = this.options.minChars || 1;
+    this.options.onShow       = this.options.onShow || 
+      function(element, update){ 
+        if(!update.style.position || update.style.position=='absolute') {
+          update.style.position = 'absolute';
+          Position.clone(element, update, {
+            setHeight: false, 
+            offsetTop: element.offsetHeight
+          });
+        }
+        Effect.Appear(update,{duration:0.15});
+      };
+    this.options.onHide = this.options.onHide || 
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+    if(typeof(this.options.tokens) == 'string') 
+      this.options.tokens = new Array(this.options.tokens);
+    // Force carriage returns as token delimiters anyway
+    if (!this.options.tokens.include('\n'))
+      this.options.tokens.push('\n');
+
+    this.observer = null;
+    
+    this.element.setAttribute('autocomplete','off');
+
+    Element.hide(this.update);
+
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+  },
+
+  show: function() {
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix && 
+      (Prototype.Browser.IE) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
+      new Insertion.After(this.update, 
+       '<iframe id="' + this.update.id + '_iefix" '+
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+      this.iefix = $(this.update.id+'_iefix');
+    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+  
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
+  },
+
+  hide: function() {
+    this.stopIndicator();
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+    if(this.iefix) Element.hide(this.iefix);
+  },
+
+  startIndicator: function() {
+    if(this.options.indicator) Element.show(this.options.indicator);
+  },
+
+  stopIndicator: function() {
+    if(this.options.indicator) Element.hide(this.options.indicator);
+  },
+
+  onKeyPress: function(event) {
+    if(this.active)
+      switch(event.keyCode) {
+       case Event.KEY_TAB:
+       case Event.KEY_RETURN:
+         this.selectEntry();
+         Event.stop(event);
+       case Event.KEY_ESC:
+         this.hide();
+         this.active = false;
+         Event.stop(event);
+         return;
+       case Event.KEY_LEFT:
+       case Event.KEY_RIGHT:
+         return;
+       case Event.KEY_UP:
+         this.markPrevious();
+         this.render();
+         Event.stop(event);
+         return;
+       case Event.KEY_DOWN:
+         this.markNext();
+         this.render();
+         Event.stop(event);
+         return;
+      }
+     else 
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+    this.changed = true;
+    this.hasFocus = true;
+
+    if(this.observer) clearTimeout(this.observer);
+      this.observer = 
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+  },
+
+  activate: function() {
+    this.changed = false;
+    this.hasFocus = true;
+    this.getUpdatedChoices();
+  },
+
+  onHover: function(event) {
+    var element = Event.findElement(event, 'LI');
+    if(this.index != element.autocompleteIndex) 
+    {
+        this.index = element.autocompleteIndex;
+        this.render();
+    }
+    Event.stop(event);
+  },
+  
+  onClick: function(event) {
+    var element = Event.findElement(event, 'LI');
+    this.index = element.autocompleteIndex;
+    this.selectEntry();
+    this.hide();
+  },
+  
+  onBlur: function(event) {
+    // needed to make click events working
+    setTimeout(this.hide.bind(this), 250);
+    this.hasFocus = false;
+    this.active = false;     
+  }, 
+  
+  render: function() {
+    if(this.entryCount > 0) {
+      for (var i = 0; i < this.entryCount; i++)
+        this.index==i ? 
+          Element.addClassName(this.getEntry(i),"selected") : 
+          Element.removeClassName(this.getEntry(i),"selected");
+      if(this.hasFocus) { 
+        this.show();
+        this.active = true;
+      }
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+  
+  markPrevious: function() {
+    if(this.index > 0) this.index--
+      else this.index = this.entryCount-1;
+    this.getEntry(this.index).scrollIntoView(true);
+  },
+  
+  markNext: function() {
+    if(this.index < this.entryCount-1) this.index++
+      else this.index = 0;
+    this.getEntry(this.index).scrollIntoView(false);
+  },
+  
+  getEntry: function(index) {
+    return this.update.firstChild.childNodes[index];
+  },
+  
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
+  },
+  
+  selectEntry: function() {
+    this.active = false;
+    this.updateElement(this.getCurrentEntry());
+  },
+
+  updateElement: function(selectedElement) {
+    if (this.options.updateElement) {
+      this.options.updateElement(selectedElement);
+      return;
+    }
+    var value = '';
+    if (this.options.select) {
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    
+    var bounds = this.getTokenBounds();
+    if (bounds[0] != -1) {
+      var newValue = this.element.value.substr(0, bounds[0]);
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+      if (whitespace)
+        newValue += whitespace[0];
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+    } else {
+      this.element.value = value;
+    }
+    this.oldElementValue = this.element.value;
+    this.element.focus();
+    
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.hasFocus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.down());
+
+      if(this.update.firstChild && this.update.down().childNodes) {
+        this.entryCount = 
+          this.update.down().childNodes.length;
+        for (var i = 0; i < this.entryCount; i++) {
+          var entry = this.getEntry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else { 
+        this.entryCount = 0;
+      }
+
+      this.stopIndicator();
+      this.index = 0;
+      
+      if(this.entryCount==1 && this.options.autoSelect) {
+        this.selectEntry();
+        this.hide();
+      } else {
+        this.render();
+      }
+    }
+  },
+
+  addObservers: function(element) {
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+  },
+
+  onObserverEvent: function() {
+    this.changed = false;   
+    this.tokenBounds = null;
+    if(this.getToken().length>=this.options.minChars) {
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+    this.oldElementValue = this.element.value;
+  },
+
+  getToken: function() {
+    var bounds = this.getTokenBounds();
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
+  },
+
+  getTokenBounds: function() {
+    if (null != this.tokenBounds) return this.tokenBounds;
+    var value = this.element.value;
+    if (value.strip().empty()) return [-1, 0];
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
+    var prevTokenPos = -1, nextTokenPos = value.length;
+    var tp;
+    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+      if (tp > prevTokenPos) prevTokenPos = tp;
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
+      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+    }
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+  }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+  var boundary = Math.min(newS.length, oldS.length);
+  for (var index = 0; index < boundary; ++index)
+    if (newS[index] != oldS[index])
+      return index;
+  return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+  initialize: function(element, update, url, options) {
+    this.baseInitialize(element, update, options);
+    this.options.asynchronous  = true;
+    this.options.onComplete    = this.onComplete.bind(this);
+    this.options.defaultParams = this.options.parameters || null;
+    this.url                   = url;
+  },
+
+  getUpdatedChoices: function() {
+    this.startIndicator();
+    
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
+      encodeURIComponent(this.getToken());
+
+    this.options.parameters = this.options.callback ?
+      this.options.callback(this.element, entry) : entry;
+
+    if(this.options.defaultParams) 
+      this.options.parameters += '&' + this.options.defaultParams;
+    
+    new Ajax.Request(this.url, this.options);
+  },
+
+  onComplete: function(request) {
+    this.updateChoices(request.responseText);
+  }
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+//                    text only at the beginning of strings in the 
+//                    autocomplete array. Defaults to true, which will
+//                    match text at the beginning of any *word* in the
+//                    strings in the autocomplete array. If you want to
+//                    search anywhere in the string, additionally set
+//                    the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+//                   a partial match (unlike minChars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+//                 Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector' 
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+  initialize: function(element, update, array, options) {
+    this.baseInitialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
+      selector: function(instance) {
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
+        var count     = 0;
+
+        for (var i = 0; i < instance.options.array.length &&  
+          ret.length < instance.options.choices ; i++) { 
+
+          var elem = instance.options.array[i];
+          var foundPos = instance.options.ignoreCase ? 
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
+            elem.indexOf(entry);
+
+          while (foundPos != -1) {
+            if (foundPos == 0 && elem.length != entry.length) { 
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
+                elem.substr(entry.length) + "</li>");
+              break;
+            } else if (entry.length >= instance.options.partialChars && 
+              instance.options.partialSearch && foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+                  foundPos + entry.length) + "</li>");
+                break;
+              }
+            }
+
+            foundPos = instance.options.ignoreCase ? 
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
+              elem.indexOf(entry, foundPos + 1);
+
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || { });
+  }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create({
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = element = $(element);
+    this.prepareOptions();
+    this._controls = { };
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+    Object.extend(this.options, options || { });
+    if (!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + '-inplaceeditor';
+      if ($(this.options.formId))
+        this.options.formId = '';
+    }
+    if (this.options.externalControl)
+      this.options.externalControl = $(this.options.externalControl);
+    if (!this.options.externalControl)
+      this.options.externalControlOnly = false;
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
+    this.element.title = this.options.clickToEditText;
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+    this._boundWrapperHandler = this.wrapUp.bind(this);
+    this.registerListeners();
+  },
+  checkForEscapeOrReturn: function(e) {
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+    if (Event.KEY_ESC == e.keyCode)
+      this.handleFormCancellation(e);
+    else if (Event.KEY_RETURN == e.keyCode)
+      this.handleFormSubmission(e);
+  },
+  createControl: function(mode, handler, extraClasses) {
+    var control = this.options[mode + 'Control'];
+    var text = this.options[mode + 'Text'];
+    if ('button' == control) {
+      var btn = document.createElement('input');
+      btn.type = 'submit';
+      btn.value = text;
+      btn.className = 'editor_' + mode + '_button';
+      if ('cancel' == mode)
+        btn.onclick = this._boundCancelHandler;
+      this._form.appendChild(btn);
+      this._controls[mode] = btn;
+    } else if ('link' == control) {
+      var link = document.createElement('a');
+      link.href = '#';
+      link.appendChild(document.createTextNode(text));
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+      link.className = 'editor_' + mode + '_link';
+      if (extraClasses)
+        link.className += ' ' + extraClasses;
+      this._form.appendChild(link);
+      this._controls[mode] = link;
+    }
+  },
+  createEditField: function() {
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+    var fld;
+    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+      fld = document.createElement('input');
+      fld.type = 'text';
+      var size = this.options.size || this.options.cols || 0;
+      if (0 < size) fld.size = size;
+    } else {
+      fld = document.createElement('textarea');
+      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+      fld.cols = this.options.cols || 40;
+    }
+    fld.name = this.options.paramName;
+    fld.value = text; // No HTML breaks conversion anymore
+    fld.className = 'editor_field';
+    if (this.options.submitOnBlur)
+      fld.onblur = this._boundSubmitHandler;
+    this._controls.editor = fld;
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+  createForm: function() {
+    var ipe = this;
+    function addText(mode, condition) {
+      var text = ipe.options['text' + mode + 'Controls'];
+      if (!text || condition === false) return;
+      ipe._form.appendChild(document.createTextNode(text));
+    };
+    this._form = $(document.createElement('form'));
+    this._form.id = this.options.formId;
+    this._form.addClassName(this.options.formClassName);
+    this._form.onsubmit = this._boundSubmitHandler;
+    this.createEditField();
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
+      this._form.appendChild(document.createElement('br'));
+    if (this.options.onFormCustomization)
+      this.options.onFormCustomization(this, this._form);
+    addText('Before', this.options.okControl || this.options.cancelControl);
+    this.createControl('ok', this._boundSubmitHandler);
+    addText('Between', this.options.okControl && this.options.cancelControl);
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+    addText('After', this.options.okControl || this.options.cancelControl);
+  },
+  destroy: function() {
+    if (this._oldInnerHTML)
+      this.element.innerHTML = this._oldInnerHTML;
+    this.leaveEditMode();
+    this.unregisterListeners();
+  },
+  enterEditMode: function(e) {
+    if (this._saving || this._editing) return;
+    this._editing = true;
+    this.triggerCallback('onEnterEditMode');
+    if (this.options.externalControl)
+      this.options.externalControl.hide();
+    this.element.hide();
+    this.createForm();
+    this.element.parentNode.insertBefore(this._form, this.element);
+    if (!this.options.loadTextURL)
+      this.postProcessEditField();
+    if (e) Event.stop(e);
+  },
+  enterHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.addClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onEnterHover');
+  },
+  getText: function() {
+    return this.element.innerHTML;
+  },
+  handleAJAXFailure: function(transport) {
+    this.triggerCallback('onFailure', transport);
+    if (this._oldInnerHTML) {
+      this.element.innerHTML = this._oldInnerHTML;
+      this._oldInnerHTML = null;
+    }
+  },
+  handleFormCancellation: function(e) {
+    this.wrapUp();
+    if (e) Event.stop(e);
+  },
+  handleFormSubmission: function(e) {
+    var form = this._form;
+    var value = $F(this._controls.editor);
+    this.prepareSubmission();
+    var params = this.options.callback(form, value) || '';
+    if (Object.isString(params))
+      params = params.toQueryParams();
+    params.editorId = this.element.id;
+    if (this.options.htmlResponse) {
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Updater({ success: this.element }, this.url, options);
+    } else {
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Request(this.url, options);
+    }
+    if (e) Event.stop(e);
+  },
+  leaveEditMode: function() {
+    this.element.removeClassName(this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+    if (this.options.externalControl)
+      this.options.externalControl.show();
+    this._saving = false;
+    this._editing = false;
+    this._oldInnerHTML = null;
+    this.triggerCallback('onLeaveEditMode');
+  },
+  leaveHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.removeClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onLeaveHover');
+  },
+  loadExternalText: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this._controls.editor.disabled = true;
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._form.removeClassName(this.options.loadingClassName);
+        var text = transport.responseText;
+        if (this.options.stripLoadedTextTags)
+          text = text.stripTags();
+        this._controls.editor.value = text;
+        this._controls.editor.disabled = false;
+        this.postProcessEditField();
+      }.bind(this),
+      onFailure: this._boundFailureHandler
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+  postProcessEditField: function() {
+    var fpc = this.options.fieldPostCreation;
+    if (fpc)
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+  },
+  prepareOptions: function() {
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+      Object.extend(this.options, defs);
+    }.bind(this));
+  },
+  prepareSubmission: function() {
+    this._saving = true;
+    this.removeForm();
+    this.leaveHover();
+    this.showSaving();
+  },
+  registerListeners: function() {
+    this._listeners = { };
+    var listener;
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+      listener = this[pair.value].bind(this);
+      this._listeners[pair.key] = listener;
+      if (!this.options.externalControlOnly)
+        this.element.observe(pair.key, listener);
+      if (this.options.externalControl)
+        this.options.externalControl.observe(pair.key, listener);
+    }.bind(this));
+  },
+  removeForm: function() {
+    if (!this._form) return;
+    this._form.remove();
+    this._form = null;
+    this._controls = { };
+  },
+  showSaving: function() {
+    this._oldInnerHTML = this.element.innerHTML;
+    this.element.innerHTML = this.options.savingText;
+    this.element.addClassName(this.options.savingClassName);
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+  },
+  triggerCallback: function(cbName, arg) {
+    if ('function' == typeof this.options[cbName]) {
+      this.options[cbName](this, arg);
+    }
+  },
+  unregisterListeners: function() {
+    $H(this._listeners).each(function(pair) {
+      if (!this.options.externalControlOnly)
+        this.element.stopObserving(pair.key, pair.value);
+      if (this.options.externalControl)
+        this.options.externalControl.stopObserving(pair.key, pair.value);
+    }.bind(this));
+  },
+  wrapUp: function(transport) {
+    this.leaveEditMode();
+    // Can't use triggerCallback due to backward compatibility: requires
+    // binding + direct element
+    this._boundComplete(transport, this.element);
+  }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+  dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+  initialize: function($super, element, url, options) {
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+    $super(element, url, options);
+  },
+
+  createEditField: function() {
+    var list = document.createElement('select');
+    list.name = this.options.paramName;
+    list.size = 1;
+    this._controls.editor = list;
+    this._collection = this.options.collection || [];
+    if (this.options.loadCollectionURL)
+      this.loadCollection();
+    else
+      this.checkForExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+
+  loadCollection: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this.showLoadingText(this.options.loadingCollectionText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        var js = transport.responseText.strip();
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+          throw 'Server returned an invalid collection representation.';
+        this._collection = eval(js);
+        this.checkForExternalText();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadCollectionURL, options);
+  },
+
+  showLoadingText: function(text) {
+    this._controls.editor.disabled = true;
+    var tempOption = this._controls.editor.firstChild;
+    if (!tempOption) {
+      tempOption = document.createElement('option');
+      tempOption.value = '';
+      this._controls.editor.appendChild(tempOption);
+      tempOption.selected = true;
+    }
+    tempOption.update((text || '').stripScripts().stripTags());
+  },
+
+  checkForExternalText: function() {
+    this._text = this.getText();
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    else
+      this.buildOptionList();
+  },
+
+  loadExternalText: function() {
+    this.showLoadingText(this.options.loadingText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._text = transport.responseText.strip();
+        this.buildOptionList();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+
+  buildOptionList: function() {
+    this._form.removeClassName(this.options.loadingClassName);
+    this._collection = this._collection.map(function(entry) {
+      return 2 === entry.length ? entry : [entry, entry].flatten();
+    });
+    var marker = ('value' in this.options) ? this.options.value : this._text;
+    var textFound = this._collection.any(function(entry) {
+      return entry[0] == marker;
+    }.bind(this));
+    this._controls.editor.update('');
+    var option;
+    this._collection.each(function(entry, index) {
+      option = document.createElement('option');
+      option.value = entry[0];
+      option.selected = textFound ? entry[0] == marker : 0 == index;
+      option.appendChild(document.createTextNode(entry[1]));
+      this._controls.editor.appendChild(option);
+    }.bind(this));
+    this._controls.editor.disabled = false;
+    Field.scrollFreeActivate(this._controls.editor);
+  }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only  exists for a while,  in order to  let ****
+//**** users adapt to  the new API.  Read up on the new ****
+//**** API and convert your code to it ASAP!            ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+  if (!options) return;
+  function fallback(name, expr) {
+    if (name in options || expr === undefined) return;
+    options[name] = expr;
+  };
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+    options.okLink == options.okButton == false ? false : undefined)));
+  fallback('highlightColor', options.highlightcolor);
+  fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+  DefaultOptions: {
+    ajaxOptions: { },
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
+    cancelControl: 'link',                      // 'link'|'button'|false
+    cancelText: 'cancel',
+    clickToEditText: 'Click to edit',
+    externalControl: null,                      // id|elt
+    externalControlOnly: false,
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
+    formClassName: 'inplaceeditor-form',
+    formId: null,                               // id|elt
+    highlightColor: '#ffff99',
+    highlightEndColor: '#ffffff',
+    hoverClassName: '',
+    htmlResponse: true,
+    loadingClassName: 'inplaceeditor-loading',
+    loadingText: 'Loading...',
+    okControl: 'button',                        // 'link'|'button'|false
+    okText: 'ok',
+    paramName: 'value',
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
+    savingClassName: 'inplaceeditor-saving',
+    savingText: 'Saving...',
+    size: 0,
+    stripLoadedTextTags: false,
+    submitOnBlur: false,
+    textAfterControls: '',
+    textBeforeControls: '',
+    textBetweenControls: ''
+  },
+  DefaultCallbacks: {
+    callback: function(form) {
+      return Form.serialize(form);
+    },
+    onComplete: function(transport, element) {
+      // For backward compatibility, this one is bound to the IPE, and passes
+      // the element directly.  It was too often customized, so we don't break it.
+      new Effect.Highlight(element, {
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
+    },
+    onEnterEditMode: null,
+    onEnterHover: function(ipe) {
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
+      if (ipe._effect)
+        ipe._effect.cancel();
+    },
+    onFailure: function(transport, ipe) {
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
+    },
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+    onLeaveEditMode: null,
+    onLeaveHover: function(ipe) {
+      ipe._effect = new Effect.Highlight(ipe.element, {
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
+      });
+    }
+  },
+  Listeners: {
+    click: 'enterEditMode',
+    keydown: 'checkForEscapeOrReturn',
+    mouseover: 'enterHover',
+    mouseout: 'leaveHover'
+  }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+  loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+});

+ 974 - 0
talks/cant-c-a-thing/scripts/dragdrop.js

@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if(Object.isUndefined(Effect))
+  throw("dragdrop.js requires including script.aculo.us' effects.js library");
+
+var Droppables = {
+  drops: [],
+
+  remove: function(element) {
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+  },
+
+  add: function(element) {
+    element = $(element);
+    var options = Object.extend({
+      greedy:     true,
+      hoverclass: null,
+      tree:       false
+    }, arguments[1] || { });
+
+    // cache containers
+    if(options.containment) {
+      options._containers = [];
+      var containment = options.containment;
+      if(Object.isArray(containment)) {
+        containment.each( function(c) { options._containers.push($(c)) });
+      } else {
+        options._containers.push($(containment));
+      }
+    }
+    
+    if(options.accept) options.accept = [options.accept].flatten();
+
+    Element.makePositioned(element); // fix IE
+    options.element = element;
+
+    this.drops.push(options);
+  },
+  
+  findDeepestChild: function(drops) {
+    deepest = drops[0];
+      
+    for (i = 1; i < drops.length; ++i)
+      if (Element.isParent(drops[i].element, deepest.element))
+        deepest = drops[i];
+    
+    return deepest;
+  },
+
+  isContained: function(element, drop) {
+    var containmentNode;
+    if(drop.tree) {
+      containmentNode = element.treeNode; 
+    } else {
+      containmentNode = element.parentNode;
+    }
+    return drop._containers.detect(function(c) { return containmentNode == c });
+  },
+  
+  isAffected: function(point, element, drop) {
+    return (
+      (drop.element!=element) &&
+      ((!drop._containers) ||
+        this.isContained(element, drop)) &&
+      ((!drop.accept) ||
+        (Element.classNames(element).detect( 
+          function(v) { return drop.accept.include(v) } ) )) &&
+      Position.within(drop.element, point[0], point[1]) );
+  },
+
+  deactivate: function(drop) {
+    if(drop.hoverclass)
+      Element.removeClassName(drop.element, drop.hoverclass);
+    this.last_active = null;
+  },
+
+  activate: function(drop) {
+    if(drop.hoverclass)
+      Element.addClassName(drop.element, drop.hoverclass);
+    this.last_active = drop;
+  },
+
+  show: function(point, element) {
+    if(!this.drops.length) return;
+    var drop, affected = [];
+    
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop))
+        affected.push(drop);
+    });
+        
+    if(affected.length>0)
+      drop = Droppables.findDeepestChild(affected);
+
+    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+    if (drop) {
+      Position.within(drop.element, point[0], point[1]);
+      if(drop.onHover)
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+      
+      if (drop != this.last_active) Droppables.activate(drop);
+    }
+  },
+
+  fire: function(event, element) {
+    if(!this.last_active) return;
+    Position.prepare();
+
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+      if (this.last_active.onDrop) {
+        this.last_active.onDrop(element, this.last_active.element, event); 
+        return true; 
+      }
+  },
+
+  reset: function() {
+    if(this.last_active)
+      this.deactivate(this.last_active);
+  }
+}
+
+var Draggables = {
+  drags: [],
+  observers: [],
+  
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+      
+      Event.observe(document, "mouseup", this.eventMouseUp);
+      Event.observe(document, "mousemove", this.eventMouseMove);
+      Event.observe(document, "keypress", this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+  
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
+      Event.stopObserving(document, "keypress", this.eventKeypress);
+    }
+  },
+  
+  activate: function(draggable) {
+    if(draggable.options.delay) { 
+      this._timeout = setTimeout(function() { 
+        Draggables._timeout = null; 
+        window.focus(); 
+        Draggables.activeDraggable = draggable; 
+      }.bind(this), draggable.options.delay); 
+    } else {
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+      this.activeDraggable = draggable;
+    }
+  },
+  
+  deactivate: function() {
+    this.activeDraggable = null;
+  },
+  
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+    
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+  
+  endDrag: function(event) {
+    if(this._timeout) { 
+      clearTimeout(this._timeout); 
+      this._timeout = null; 
+    }
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+    this.activeDraggable = null;
+  },
+  
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+  
+  addObserver: function(observer) {
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
+  },
+  
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
+    this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
+  },
+  
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] > 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
+  },
+  
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create({
+  initialize: function(element) {
+    var defaults = {
+      handle: false,
+      reverteffect: function(element, top_offset, left_offset) {
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+          queue: {scope:'_draggable', position:'end'}
+        });
+      },
+      endeffect: function(element) {
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
+          queue: {scope:'_draggable', position:'end'},
+          afterFinish: function(){ 
+            Draggable._dragging[element] = false 
+          }
+        }); 
+      },
+      zindex: 1000,
+      revert: false,
+      quiet: false,
+      scroll: false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
+      delay: 0
+    };
+    
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+      Object.extend(defaults, {
+        starteffect: function(element) {
+          element._opacity = Element.getOpacity(element);
+          Draggable._dragging[element] = true;
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
+        }
+      });
+    
+    var options = Object.extend(defaults, arguments[1] || { });
+
+    this.element = $(element);
+    
+    if(options.handle && Object.isString(options.handle))
+      this.handle = this.element.down('.'+options.handle, 0);
+    
+    if(!this.handle) this.handle = $(options.handle);
+    if(!this.handle) this.handle = this.element;
+    
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
+      options.scroll = $(options.scroll);
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
+    }
+
+    Element.makePositioned(this.element); // fix IE    
+
+    this.options  = options;
+    this.dragging = false;   
+
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
+    
+    Draggables.register(this);
+  },
+  
+  destroy: function() {
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+    Draggables.unregister(this);
+  },
+  
+  currentDelta: function() {
+    return([
+      parseInt(Element.getStyle(this.element,'left') || '0'),
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
+  },
+  
+  initDrag: function(event) {
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+      Draggable._dragging[this.element]) return;
+    if(Event.isLeftClick(event)) {    
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if((tag_name = src.tagName.toUpperCase()) && (
+        tag_name=='INPUT' ||
+        tag_name=='SELECT' ||
+        tag_name=='OPTION' ||
+        tag_name=='BUTTON' ||
+        tag_name=='TEXTAREA')) return;
+        
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      var pos     = Position.cumulativeOffset(this.element);
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+      
+      Draggables.activate(this);
+      Event.stop(event);
+    }
+  },
+  
+  startDrag: function(event) {
+    this.dragging = true;
+    if(!this.delta)
+      this.delta = this.currentDelta();
+    
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+    
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+      if (!this.element._originallyAbsolute)
+        Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+    
+    if(this.options.scroll) {
+      if (this.options.scroll == window) {
+        var where = this._getWindowScroll(this.options.scroll);
+        this.originalScrollLeft = where.left;
+        this.originalScrollTop = where.top;
+      } else {
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
+        this.originalScrollTop = this.options.scroll.scrollTop;
+      }
+    }
+    
+    Draggables.notify('onStart', this, event);
+        
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+  
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+    
+    if(!this.options.quiet){
+      Position.prepare();
+      Droppables.show(pointer, this.element);
+    }
+    
+    Draggables.notify('onDrag', this, event);
+    
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+    
+    if(this.options.scroll) {
+      this.stopScrolling();
+      
+      var p;
+      if (this.options.scroll == window) {
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+      } else {
+        p = Position.page(this.options.scroll);
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
+        p.push(p[0]+this.options.scroll.offsetWidth);
+        p.push(p[1]+this.options.scroll.offsetHeight);
+      }
+      var speed = [0,0];
+      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+      this.startScrolling(speed);
+    }
+    
+    // fix AppleWebKit rendering
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+    
+    Event.stop(event);
+  },
+  
+  finishDrag: function(event, success) {
+    this.dragging = false;
+    
+    if(this.options.quiet){
+      Position.prepare();
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      Droppables.show(pointer, this.element);
+    }
+
+    if(this.options.ghosting) {
+      if (!this.element._originallyAbsolute)
+        Position.relativize(this.element);
+      delete this.element._originallyAbsolute;
+      Element.remove(this._clone);
+      this._clone = null;
+    }
+
+    var dropped = false; 
+    if(success) { 
+      dropped = Droppables.fire(event, this.element); 
+      if (!dropped) dropped = false; 
+    }
+    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+    Draggables.notify('onEnd', this, event);
+
+    var revert = this.options.revert;
+    if(revert && Object.isFunction(revert)) revert = revert(this.element);
+    
+    var d = this.currentDelta();
+    if(revert && this.options.reverteffect) {
+      if (dropped == 0 || revert != 'failure')
+        this.options.reverteffect(this.element,
+          d[1]-this.delta[1], d[0]-this.delta[0]);
+    } else {
+      this.delta = d;
+    }
+
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
+
+    if(this.options.endeffect) 
+      this.options.endeffect(this.element);
+      
+    Draggables.deactivate(this);
+    Droppables.reset();
+  },
+  
+  keyPress: function(event) {
+    if(event.keyCode!=Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
+  },
+  
+  endDrag: function(event) {
+    if(!this.dragging) return;
+    this.stopScrolling();
+    this.finishDrag(event, true);
+    Event.stop(event);
+  },
+  
+  draw: function(point) {
+    var pos = Position.cumulativeOffset(this.element);
+    if(this.options.ghosting) {
+      var r   = Position.realOffset(this.element);
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+    }
+    
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+    
+    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+    }
+    
+    var p = [0,1].map(function(i){ 
+      return (point[i]-pos[i]-this.offset[i]) 
+    }.bind(this));
+    
+    if(this.options.snap) {
+      if(Object.isFunction(this.options.snap)) {
+        p = this.options.snap(p[0],p[1],this);
+      } else {
+      if(Object.isArray(this.options.snap)) {
+        p = p.map( function(v, i) {
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
+      } else {
+        p = p.map( function(v) {
+          return (v/this.options.snap).round()*this.options.snap }.bind(this))
+      }
+    }}
+    
+    var style = this.element.style;
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+      style.left = p[0] + "px";
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
+      style.top  = p[1] + "px";
+    
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+  },
+  
+  stopScrolling: function() {
+    if(this.scrollInterval) {
+      clearInterval(this.scrollInterval);
+      this.scrollInterval = null;
+      Draggables._lastScrollPointer = null;
+    }
+  },
+  
+  startScrolling: function(speed) {
+    if(!(speed[0] || speed[1])) return;
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+    this.lastScrolled = new Date();
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+  },
+  
+  scroll: function() {
+    var current = new Date();
+    var delta = current - this.lastScrolled;
+    this.lastScrolled = current;
+    if(this.options.scroll == window) {
+      with (this._getWindowScroll(this.options.scroll)) {
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+          var d = delta / 1000;
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
+        }
+      }
+    } else {
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
+    }
+    
+    Position.prepare();
+    Droppables.show(Draggables._lastPointer, this.element);
+    Draggables.notify('onDrag', this);
+    if (this._isScrollChild) {
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+      if (Draggables._lastScrollPointer[0] < 0)
+        Draggables._lastScrollPointer[0] = 0;
+      if (Draggables._lastScrollPointer[1] < 0)
+        Draggables._lastScrollPointer[1] = 0;
+      this.draw(Draggables._lastScrollPointer);
+    }
+    
+    if(this.options.change) this.options.change(this);
+  },
+  
+  _getWindowScroll: function(w) {
+    var T, L, W, H;
+    with (w.document) {
+      if (w.document.documentElement && documentElement.scrollTop) {
+        T = documentElement.scrollTop;
+        L = documentElement.scrollLeft;
+      } else if (w.document.body) {
+        T = body.scrollTop;
+        L = body.scrollLeft;
+      }
+      if (w.innerWidth) {
+        W = w.innerWidth;
+        H = w.innerHeight;
+      } else if (w.document.documentElement && documentElement.clientWidth) {
+        W = documentElement.clientWidth;
+        H = documentElement.clientHeight;
+      } else {
+        W = body.offsetWidth;
+        H = body.offsetHeight
+      }
+    }
+    return { top: T, left: L, width: W, height: H };
+  }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+  initialize: function(element, observer) {
+    this.element   = $(element);
+    this.observer  = observer;
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onStart: function() {
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onEnd: function() {
+    Sortable.unmark();
+    if(this.lastValue != Sortable.serialize(this.element))
+      this.observer(this.element)
+  }
+});
+
+var Sortable = {
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+  
+  sortables: { },
+  
+  _findRootElement: function(element) {
+    while (element.tagName.toUpperCase() != "BODY") {  
+      if(element.id && Sortable.sortables[element.id]) return element;
+      element = element.parentNode;
+    }
+  },
+
+  options: function(element) {
+    element = Sortable._findRootElement($(element));
+    if(!element) return;
+    return Sortable.sortables[element.id];
+  },
+  
+  destroy: function(element){
+    var s = Sortable.options(element);
+    
+    if(s) {
+      Draggables.removeObserver(s.element);
+      s.droppables.each(function(d){ Droppables.remove(d) });
+      s.draggables.invoke('destroy');
+      
+      delete Sortable.sortables[s.element.id];
+    }
+  },
+
+  create: function(element) {
+    element = $(element);
+    var options = Object.extend({ 
+      element:     element,
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
+      dropOnEmpty: false,
+      tree:        false,
+      treeTag:     'ul',
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
+      containment: element,    // also takes array of elements (or id's); or false
+      handle:      false,      // or a CSS class
+      only:        false,
+      delay:       0,
+      hoverclass:  null,
+      ghosting:    false,
+      quiet:       false, 
+      scroll:      false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      format:      this.SERIALIZE_RULE,
+      
+      // these take arrays of elements or ids and can be 
+      // used for better initialization performance
+      elements:    false,
+      handles:     false,
+      
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
+    }, arguments[1] || { });
+
+    // clear any old sortable with same element
+    this.destroy(element);
+
+    // build options for the draggables
+    var options_for_draggable = {
+      revert:      true,
+      quiet:       options.quiet,
+      scroll:      options.scroll,
+      scrollSpeed: options.scrollSpeed,
+      scrollSensitivity: options.scrollSensitivity,
+      delay:       options.delay,
+      ghosting:    options.ghosting,
+      constraint:  options.constraint,
+      handle:      options.handle };
+
+    if(options.starteffect)
+      options_for_draggable.starteffect = options.starteffect;
+
+    if(options.reverteffect)
+      options_for_draggable.reverteffect = options.reverteffect;
+    else
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+        element.style.top  = 0;
+        element.style.left = 0;
+      };
+
+    if(options.endeffect)
+      options_for_draggable.endeffect = options.endeffect;
+
+    if(options.zindex)
+      options_for_draggable.zindex = options.zindex;
+
+    // build options for the droppables  
+    var options_for_droppable = {
+      overlap:     options.overlap,
+      containment: options.containment,
+      tree:        options.tree,
+      hoverclass:  options.hoverclass,
+      onHover:     Sortable.onHover
+    }
+    
+    var options_for_tree = {
+      onHover:      Sortable.onEmptyHover,
+      overlap:      options.overlap,
+      containment:  options.containment,
+      hoverclass:   options.hoverclass
+    }
+
+    // fix for gecko engine
+    Element.cleanWhitespace(element); 
+
+    options.draggables = [];
+    options.droppables = [];
+
+    // drop on empty handling
+    if(options.dropOnEmpty || options.tree) {
+      Droppables.add(element, options_for_tree);
+      options.droppables.push(element);
+    }
+
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+      var handle = options.handles ? $(options.handles[i]) :
+        (options.handle ? $(e).select('.' + options.handle)[0] : e); 
+      options.draggables.push(
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+      Droppables.add(e, options_for_droppable);
+      if(options.tree) e.treeNode = element;
+      options.droppables.push(e);      
+    });
+    
+    if(options.tree) {
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
+        Droppables.add(e, options_for_tree);
+        e.treeNode = element;
+        options.droppables.push(e);
+      });
+    }
+
+    // keep reference
+    this.sortables[element.id] = options;
+
+    // for onupdate
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+  },
+
+  // return all suitable-for-sortable elements in a guaranteed order
+  findElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.tag);
+  },
+  
+  findTreeElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.treeTag);
+  },
+
+  onHover: function(element, dropon, overlap) {
+    if(Element.isParent(dropon, element)) return;
+
+    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+      return;
+    } else if(overlap>0.5) {
+      Sortable.mark(dropon, 'before');
+      if(dropon.previousSibling != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, dropon);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    } else {
+      Sortable.mark(dropon, 'after');
+      var nextElement = dropon.nextSibling || null;
+      if(nextElement != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, nextElement);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    }
+  },
+  
+  onEmptyHover: function(element, dropon, overlap) {
+    var oldParentNode = element.parentNode;
+    var droponOptions = Sortable.options(dropon);
+        
+    if(!Element.isParent(dropon, element)) {
+      var index;
+      
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+      var child = null;
+            
+      if(children) {
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+        
+        for (index = 0; index < children.length; index += 1) {
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
+            child = index + 1 < children.length ? children[index + 1] : null;
+            break;
+          } else {
+            child = children[index];
+            break;
+          }
+        }
+      }
+      
+      dropon.insertBefore(element, child);
+      
+      Sortable.options(oldParentNode).onChange(element);
+      droponOptions.onChange(element);
+    }
+  },
+
+  unmark: function() {
+    if(Sortable._marker) Sortable._marker.hide();
+  },
+
+  mark: function(dropon, position) {
+    // mark on ghosting only
+    var sortable = Sortable.options(dropon.parentNode);
+    if(sortable && !sortable.ghosting) return; 
+
+    if(!Sortable._marker) {
+      Sortable._marker = 
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+    }    
+    var offsets = Position.cumulativeOffset(dropon);
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+    
+    if(position=='after')
+      if(sortable.overlap == 'horizontal') 
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
+      else
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
+    
+    Sortable._marker.show();
+  },
+  
+  _tree: function(element, options, parent) {
+    var children = Sortable.findElements(element, options) || [];
+  
+    for (var i = 0; i < children.length; ++i) {
+      var match = children[i].id.match(options.format);
+
+      if (!match) continue;
+      
+      var child = {
+        id: encodeURIComponent(match ? match[1] : null),
+        element: element,
+        parent: parent,
+        children: [],
+        position: parent.children.length,
+        container: $(children[i]).down(options.treeTag)
+      }
+      
+      /* Get the element containing the children and recurse over it */
+      if (child.container)
+        this._tree(child.container, options, child)
+      
+      parent.children.push (child);
+    }
+
+    return parent; 
+  },
+
+  tree: function(element) {
+    element = $(element);
+    var sortableOptions = this.options(element);
+    var options = Object.extend({
+      tag: sortableOptions.tag,
+      treeTag: sortableOptions.treeTag,
+      only: sortableOptions.only,
+      name: element.id,
+      format: sortableOptions.format
+    }, arguments[1] || { });
+    
+    var root = {
+      id: null,
+      parent: null,
+      children: [],
+      container: element,
+      position: 0
+    }
+    
+    return Sortable._tree(element, options, root);
+  },
+
+  /* Construct a [i] index for a particular node */
+  _constructIndex: function(node) {
+    var index = '';
+    do {
+      if (node.id) index = '[' + node.position + ']' + index;
+    } while ((node = node.parent) != null);
+    return index;
+  },
+
+  sequence: function(element) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[1] || { });
+    
+    return $(this.findElements(element, options) || []).map( function(item) {
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+    });
+  },
+
+  setSequence: function(element, new_sequence) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[2] || { });
+    
+    var nodeMap = { };
+    this.findElements(element, options).each( function(n) {
+        if (n.id.match(options.format))
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+        n.parentNode.removeChild(n);
+    });
+   
+    new_sequence.each(function(ident) {
+      var n = nodeMap[ident];
+      if (n) {
+        n[1].appendChild(n[0]);
+        delete nodeMap[ident];
+      }
+    });
+  },
+  
+  serialize: function(element) {
+    element = $(element);
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
+    var name = encodeURIComponent(
+      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+    
+    if (options.tree) {
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
+        return [name + Sortable._constructIndex(item) + "[id]=" + 
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+      }).flatten().join('&');
+    } else {
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
+        return name + "[]=" + encodeURIComponent(item);
+      }).join('&');
+    }
+  }
+}
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+  if (!child.parentNode || child == element) return false;
+  if (child.parentNode == element) return true;
+  return Element.isParent(child.parentNode, element);
+}
+
+Element.findChildren = function(element, only, recursive, tagName) {   
+  if(!element.hasChildNodes()) return null;
+  tagName = tagName.toUpperCase();
+  if(only) only = [only].flatten();
+  var elements = [];
+  $A(element.childNodes).each( function(e) {
+    if(e.tagName && e.tagName.toUpperCase()==tagName &&
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+        elements.push(e);
+    if(recursive) {
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
+      if(grandchildren) elements.push(grandchildren);
+    }
+  });
+
+  return (elements.length>0 ? elements.flatten() : []);
+}
+
+Element.offsetSize = function (element, type) {
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}

+ 1122 - 0
talks/cant-c-a-thing/scripts/effects.js

@@ -0,0 +1,1122 @@
+// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
+
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if (this.slice(0,1) == '#') {  
+      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if (this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return (color.length==7 ? color : (arguments[0] || this));  
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + 0.5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+      return pos > 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+    },
+    pulse: function(pos, pulses) { 
+      pulses = pulses || 5; 
+      return (
+        ((pos % (1/pulses)) * pulses).round() == 0 ? 
+              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
+          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+        );
+    },
+    spring: function(pos) { 
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if (child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            new Element('span', {style: tagifyStyle}).update(
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if (((typeof element == 'object') || 
+        Object.isFunction(element)) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || { });
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || { });
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+  }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    var position = Object.isString(effect.options.queue) ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+
+    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
+    
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if (this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    for(var i=0, len=this.effects.length;i<len;i++) 
+      this.effects[i] && this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+    
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
+  }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+  position: null,
+  start: function(options) {
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if (this.state=="idle"){this.state="running";'+
+      codeForEvent(this.options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(this.options,'afterSetup')+
+      '};if (this.state=="running"){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(this.options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(this.options,'afterUpdate')+
+      '}}');
+    
+    this.event('beforeStart');
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if (timePos >= this.startOn) {
+      if (timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if (this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  cancel: function() {
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if (effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(), 
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) : 
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
+    var options = Object.extend({
+      from: this.element.getOpacity() || 0.0,
+      to:   1.0
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  update: function(position) {
+    this.element.setOpacity(position);
+  }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+  initialize: function(element, percent) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+    
+    this.originalStyle = { };
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if (this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if (/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if (!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if (this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    this.element.setStyle(d);
+  }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+    scrollOffsets = document.viewport.getScrollOffsets(),
+    elementOffsets = $(element).cumulativeOffset(),
+    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1] > max ? max : elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()) }
+  );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  var options = Object.extend({
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) { 
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity}); 
+    }
+  }, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+  element = $(element);
+  var options = Object.extend({
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+  to:   1.0,
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
+   );
+};
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || { })
+  );
+};
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      })
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}) }}) }}) }}) }}) }});
+};
+
+Effect.SlideDown = function(element) {
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
+  );
+};
+
+Effect.SlideUp = function(element) {
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
+  );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
+  });
+};
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01, 
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
+           }, options)
+      )
+    }
+  });
+};
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+};
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || { };
+  var oldOpacity = element.getInlineOpacity();
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+    
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        }
+      }
+    }
+    this.start(options);
+  },
+  
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]); 
+  });
+  
+  if (Prototype.Browser.IE && this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+      results[property] = css[property];
+      return results;
+    });
+    if (!styles.opacity) styles.opacity = element.getOpacity();
+    return styles;
+  };
+};
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element)
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) { 
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    }
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);

+ 3408 - 0
talks/cant-c-a-thing/scripts/jquery-1.2.3.js

@@ -0,0 +1,3408 @@
+(function(){
+/*
+ * jQuery 1.2.3 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $
+ * $Rev: 4663 $
+ */
+
+// Map over jQuery in case of overwrite
+if ( window.jQuery )
+	var _jQuery = window.jQuery;
+
+var jQuery = window.jQuery = function( selector, context ) {
+	// The jQuery object is actually just the init constructor 'enhanced'
+	return new jQuery.prototype.init( selector, context );
+};
+
+// Map over the $ in case of overwrite
+if ( window.$ )
+	var _$ = window.$;
+	
+// Map the jQuery namespace to the '$' one
+window.$ = jQuery;
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+
+// Is it a simple selector
+var isSimple = /^.[^:#\[\.]*$/;
+
+jQuery.fn = jQuery.prototype = {
+	init: function( selector, context ) {
+		// Make sure that a selection was provided
+		selector = selector || document;
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this[0] = selector;
+			this.length = 1;
+			return this;
+
+		// Handle HTML strings
+		} else if ( typeof selector == "string" ) {
+			// Are we dealing with HTML string or an ID?
+			var match = quickExpr.exec( selector );
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] )
+					selector = jQuery.clean( [ match[1] ], context );
+
+				// HANDLE: $("#id")
+				else {
+					var elem = document.getElementById( match[3] );
+
+					// Make sure an element was located
+					if ( elem )
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id != match[3] )
+							return jQuery().find( selector );
+
+						// Otherwise, we inject the element directly into the jQuery object
+						else {
+							this[0] = elem;
+							this.length = 1;
+							return this;
+						}
+
+					else
+						selector = [];
+				}
+
+			// HANDLE: $(expr, [context])
+			// (which is just equivalent to: $(content).find(expr)
+			} else
+				return new jQuery( context ).find( selector );
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) )
+			return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+		return this.setArray(
+			// HANDLE: $(array)
+			selector.constructor == Array && selector ||
+
+			// HANDLE: $(arraylike)
+			// Watch for when an array-like object, contains DOM nodes, is passed in as the selector
+			(selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) ||
+
+			// HANDLE: $(*)
+			[ selector ] );
+	},
+	
+	// The current version of jQuery being used
+	jquery: "1.2.3",
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+	
+	// The number of elements contained in the matched element set
+	length: 0,
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == undefined ?
+
+			// Return a 'clean' array
+			jQuery.makeArray( this ) :
+
+			// Return just the object
+			this[ num ];
+	},
+	
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+		// Build a new jQuery matched element set
+		var ret = jQuery( elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+	
+	// Force the current matched set of elements to become
+	// the specified array of elements (destroying the stack in the process)
+	// You should use pushStack() in order to do this, but maintain the stack
+	setArray: function( elems ) {
+		// Resetting the length to 0, then using the native Array push
+		// is a super-fast way to populate an object with array-like properties
+		this.length = 0;
+		Array.prototype.push.apply( this, elems );
+		
+		return this;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	// Determine the position of an element within 
+	// the matched set of elements
+	index: function( elem ) {
+		var ret = -1;
+
+		// Locate the position of the desired element
+		this.each(function(i){
+			if ( this == elem )
+				ret = i;
+		});
+
+		return ret;
+	},
+
+	attr: function( name, value, type ) {
+		var options = name;
+		
+		// Look for the case where we're accessing a style value
+		if ( name.constructor == String )
+			if ( value == undefined )
+				return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined;
+
+			else {
+				options = {};
+				options[ name ] = value;
+			}
+		
+		// Check to see if we're setting style values
+		return this.each(function(i){
+			// Set all the styles
+			for ( name in options )
+				jQuery.attr(
+					type ?
+						this.style :
+						this,
+					name, jQuery.prop( this, options[ name ], type, i, name )
+				);
+		});
+	},
+
+	css: function( key, value ) {
+		// ignore negative width and height values
+		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+			value = undefined;
+		return this.attr( key, value, "curCSS" );
+	},
+
+	text: function( text ) {
+		if ( typeof text != "object" && text != null )
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+		var ret = "";
+
+		jQuery.each( text || this, function(){
+			jQuery.each( this.childNodes, function(){
+				if ( this.nodeType != 8 )
+					ret += this.nodeType != 1 ?
+						this.nodeValue :
+						jQuery.fn.text( [ this ] );
+			});
+		});
+
+		return ret;
+	},
+
+	wrapAll: function( html ) {
+		if ( this[0] )
+			// The elements to wrap the target around
+			jQuery( html, this[0].ownerDocument )
+				.clone()
+				.insertBefore( this[0] )
+				.map(function(){
+					var elem = this;
+
+					while ( elem.firstChild )
+						elem = elem.firstChild;
+
+					return elem;
+				})
+				.append(this);
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		return this.each(function(){
+			jQuery( this ).contents().wrapAll( html );
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function(){
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, false, function(elem){
+			if (this.nodeType == 1)
+				this.appendChild( elem );
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, true, function(elem){
+			if (this.nodeType == 1)
+				this.insertBefore( elem, this.firstChild );
+		});
+	},
+	
+	before: function() {
+		return this.domManip(arguments, false, false, function(elem){
+			this.parentNode.insertBefore( elem, this );
+		});
+	},
+
+	after: function() {
+		return this.domManip(arguments, false, true, function(elem){
+			this.parentNode.insertBefore( elem, this.nextSibling );
+		});
+	},
+
+	end: function() {
+		return this.prevObject || jQuery( [] );
+	},
+
+	find: function( selector ) {
+		var elems = jQuery.map(this, function(elem){
+			return jQuery.find( selector, elem );
+		});
+
+		return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+			jQuery.unique( elems ) :
+			elems );
+	},
+
+	clone: function( events ) {
+		// Do the clone
+		var ret = this.map(function(){
+			if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+				// IE copies events bound via attachEvent when
+				// using cloneNode. Calling detachEvent on the
+				// clone will also remove the events from the orignal
+				// In order to get around this, we use innerHTML.
+				// Unfortunately, this means some modifications to 
+				// attributes in IE that are actually only stored 
+				// as properties will not be copied (such as the
+				// the name attribute on an input).
+				var clone = this.cloneNode(true),
+					container = document.createElement("div");
+				container.appendChild(clone);
+				return jQuery.clean([container.innerHTML])[0];
+			} else
+				return this.cloneNode(true);
+		});
+
+		// Need to set the expando to null on the cloned set if it exists
+		// removeData doesn't work here, IE removes it from the original as well
+		// this is primarily for IE but the data expando shouldn't be copied over in any browser
+		var clone = ret.find("*").andSelf().each(function(){
+			if ( this[ expando ] != undefined )
+				this[ expando ] = null;
+		});
+		
+		// Copy the events from the original to the clone
+		if ( events === true )
+			this.find("*").andSelf().each(function(i){
+				if (this.nodeType == 3)
+					return;
+				var events = jQuery.data( this, "events" );
+
+				for ( var type in events )
+					for ( var handler in events[ type ] )
+						jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+			});
+
+		// Return the cloned set
+		return ret;
+	},
+
+	filter: function( selector ) {
+		return this.pushStack(
+			jQuery.isFunction( selector ) &&
+			jQuery.grep(this, function(elem, i){
+				return selector.call( elem, i );
+			}) ||
+
+			jQuery.multiFilter( selector, this ) );
+	},
+
+	not: function( selector ) {
+		if ( selector.constructor == String )
+			// test special case where just one selector is passed in
+			if ( isSimple.test( selector ) )
+				return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+			else
+				selector = jQuery.multiFilter( selector, this );
+
+		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+		return this.filter(function() {
+			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+		});
+	},
+
+	add: function( selector ) {
+		return !selector ? this : this.pushStack( jQuery.merge( 
+			this.get(),
+			selector.constructor == String ? 
+				jQuery( selector ).get() :
+				selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ?
+					selector : [selector] ) );
+	},
+
+	is: function( selector ) {
+		return selector ?
+			jQuery.multiFilter( selector, this ).length > 0 :
+			false;
+	},
+
+	hasClass: function( selector ) {
+		return this.is( "." + selector );
+	},
+	
+	val: function( value ) {
+		if ( value == undefined ) {
+
+			if ( this.length ) {
+				var elem = this[0];
+
+				// We need to handle select boxes special
+				if ( jQuery.nodeName( elem, "select" ) ) {
+					var index = elem.selectedIndex,
+						values = [],
+						options = elem.options,
+						one = elem.type == "select-one";
+					
+					// Nothing was selected
+					if ( index < 0 )
+						return null;
+
+					// Loop through all the selected options
+					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+						var option = options[ i ];
+
+						if ( option.selected ) {
+							// Get the specifc value for the option
+							value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+							
+							// We don't need an array for one selects
+							if ( one )
+								return value;
+							
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+					
+					return values;
+					
+				// Everything else, we just grab the value
+				} else
+					return (this[0].value || "").replace(/\r/g, "");
+
+			}
+
+			return undefined;
+		}
+
+		return this.each(function(){
+			if ( this.nodeType != 1 )
+				return;
+
+			if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+					jQuery.inArray(this.name, value) >= 0);
+
+			else if ( jQuery.nodeName( this, "select" ) ) {
+				var values = value.constructor == Array ?
+					value :
+					[ value ];
+
+				jQuery( "option", this ).each(function(){
+					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+						jQuery.inArray( this.text, values ) >= 0);
+				});
+
+				if ( !values.length )
+					this.selectedIndex = -1;
+
+			} else
+				this.value = value;
+		});
+	},
+	
+	html: function( value ) {
+		return value == undefined ?
+			(this.length ?
+				this[0].innerHTML :
+				null) :
+			this.empty().append( value );
+	},
+
+	replaceWith: function( value ) {
+		return this.after( value ).remove();
+	},
+
+	eq: function( i ) {
+		return this.slice( i, i + 1 );
+	},
+
+	slice: function() {
+		return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function(elem, i){
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	},
+
+	data: function( key, value ){
+		var parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value == null ) {
+			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+			
+			if ( data == undefined && this.length )
+				data = jQuery.data( this[0], key );
+
+			return data == null && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+		} else
+			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+				jQuery.data( this, key, value );
+			});
+	},
+
+	removeData: function( key ){
+		return this.each(function(){
+			jQuery.removeData( this, key );
+		});
+	},
+	
+	domManip: function( args, table, reverse, callback ) {
+		var clone = this.length > 1, elems; 
+
+		return this.each(function(){
+			if ( !elems ) {
+				elems = jQuery.clean( args, this.ownerDocument );
+
+				if ( reverse )
+					elems.reverse();
+			}
+
+			var obj = this;
+
+			if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+				obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+			var scripts = jQuery( [] );
+
+			jQuery.each(elems, function(){
+				var elem = clone ?
+					jQuery( this ).clone( true )[0] :
+					this;
+
+				// execute all scripts after the elements have been injected
+				if ( jQuery.nodeName( elem, "script" ) ) {
+					scripts = scripts.add( elem );
+				} else {
+					// Remove any inner scripts for later evaluation
+					if ( elem.nodeType == 1 )
+						scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+					// Inject the elements into the document
+					callback.call( obj, elem );
+				}
+			});
+
+			scripts.each( evalScript );
+		});
+	}
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.prototype.init.prototype = jQuery.prototype;
+
+function evalScript( i, elem ) {
+	if ( elem.src )
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+
+	else
+		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+	if ( elem.parentNode )
+		elem.parentNode.removeChild( elem );
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+	// copy reference to target object
+	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+	// Handle a deep copy situation
+	if ( target.constructor == Boolean ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target != "object" && typeof target != "function" )
+		target = {};
+
+	// extend jQuery itself if only one argument is passed
+	if ( length == 1 ) {
+		target = this;
+		i = 0;
+	}
+
+	for ( ; i < length; i++ )
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null )
+			// Extend the base object
+			for ( var name in options ) {
+				// Prevent never-ending loop
+				if ( target === options[ name ] )
+					continue;
+
+				// Recurse if we're merging object values
+				if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType )
+					target[ name ] = jQuery.extend( target[ name ], options[ name ] );
+
+				// Don't bring in undefined values
+				else if ( options[ name ] != undefined )
+					target[ name ] = options[ name ];
+
+			}
+
+	// Return the modified object
+	return target;
+};
+
+var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
+
+// exclude the following css properties to add px
+var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i;
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		window.$ = _$;
+
+		if ( deep )
+			window.jQuery = _jQuery;
+
+		return jQuery;
+	},
+
+	// See test/unit/core.js for details concerning this function.
+	isFunction: function( fn ) {
+		return !!fn && typeof fn != "string" && !fn.nodeName && 
+			fn.constructor != Array && /function/i.test( fn + "" );
+	},
+	
+	// check if an element is in a (or is an) XML document
+	isXMLDoc: function( elem ) {
+		return elem.documentElement && !elem.body ||
+			elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+	},
+
+	// Evalulates a script in a global context
+	globalEval: function( data ) {
+		data = jQuery.trim( data );
+
+		if ( data ) {
+			// Inspired by code by Andrea Giammarchi
+			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+			var head = document.getElementsByTagName("head")[0] || document.documentElement,
+				script = document.createElement("script");
+
+			script.type = "text/javascript";
+			if ( jQuery.browser.msie )
+				script.text = data;
+			else
+				script.appendChild( document.createTextNode( data ) );
+
+			head.appendChild( script );
+			head.removeChild( script );
+		}
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+	},
+	
+	cache: {},
+	
+	data: function( elem, name, data ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// Compute a unique ID for the element
+		if ( !id ) 
+			id = elem[ expando ] = ++uuid;
+
+		// Only generate the data cache if we're
+		// trying to access or manipulate it
+		if ( name && !jQuery.cache[ id ] )
+			jQuery.cache[ id ] = {};
+		
+		// Prevent overriding the named cache with undefined values
+		if ( data != undefined )
+			jQuery.cache[ id ][ name ] = data;
+		
+		// Return the named cache data, or the ID for the element	
+		return name ?
+			jQuery.cache[ id ][ name ] :
+			id;
+	},
+	
+	removeData: function( elem, name ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// If we want to remove a specific section of the element's data
+		if ( name ) {
+			if ( jQuery.cache[ id ] ) {
+				// Remove the section of cache data
+				delete jQuery.cache[ id ][ name ];
+
+				// If we've removed all the data, remove the element's cache
+				name = "";
+
+				for ( name in jQuery.cache[ id ] )
+					break;
+
+				if ( !name )
+					jQuery.removeData( elem );
+			}
+
+		// Otherwise, we want to remove all of the element's data
+		} else {
+			// Clean up the element expando
+			try {
+				delete elem[ expando ];
+			} catch(e){
+				// IE has trouble directly removing the expando
+				// but it's ok with using removeAttribute
+				if ( elem.removeAttribute )
+					elem.removeAttribute( expando );
+			}
+
+			// Completely remove the data cache
+			delete jQuery.cache[ id ];
+		}
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		if ( args ) {
+			if ( object.length == undefined ) {
+				for ( var name in object )
+					if ( callback.apply( object[ name ], args ) === false )
+						break;
+			} else
+				for ( var i = 0, length = object.length; i < length; i++ )
+					if ( callback.apply( object[ i ], args ) === false )
+						break;
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( object.length == undefined ) {
+				for ( var name in object )
+					if ( callback.call( object[ name ], name, object[ name ] ) === false )
+						break;
+			} else
+				for ( var i = 0, length = object.length, value = object[0]; 
+					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+		}
+
+		return object;
+	},
+	
+	prop: function( elem, value, type, i, name ) {
+			// Handle executable functions
+			if ( jQuery.isFunction( value ) )
+				value = value.call( elem, i );
+				
+			// Handle passing in a number to a CSS property
+			return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+				value + "px" :
+				value;
+	},
+
+	className: {
+		// internal only, use addClass("class")
+		add: function( elem, classNames ) {
+			jQuery.each((classNames || "").split(/\s+/), function(i, className){
+				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+					elem.className += (elem.className ? " " : "") + className;
+			});
+		},
+
+		// internal only, use removeClass("class")
+		remove: function( elem, classNames ) {
+			if (elem.nodeType == 1)
+				elem.className = classNames != undefined ?
+					jQuery.grep(elem.className.split(/\s+/), function(className){
+						return !jQuery.className.has( classNames, className );	
+					}).join(" ") :
+					"";
+		},
+
+		// internal only, use is(".class")
+		has: function( elem, className ) {
+			return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( var name in options )
+			elem.style[ name ] = old[ name ];
+	},
+
+	css: function( elem, name, force ) {
+		if ( name == "width" || name == "height" ) {
+			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+		
+			function getWH() {
+				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+				var padding = 0, border = 0;
+				jQuery.each( which, function() {
+					padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+					border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+				});
+				val -= Math.round(padding + border);
+			}
+		
+			if ( jQuery(elem).is(":visible") )
+				getWH();
+			else
+				jQuery.swap( elem, props, getWH );
+			
+			return Math.max(0, val);
+		}
+		
+		return jQuery.curCSS( elem, name, force );
+	},
+
+	curCSS: function( elem, name, force ) {
+		var ret;
+
+		// A helper method for determining if an element's values are broken
+		function color( elem ) {
+			if ( !jQuery.browser.safari )
+				return false;
+
+			var ret = document.defaultView.getComputedStyle( elem, null );
+			return !ret || ret.getPropertyValue("color") == "";
+		}
+
+		// We need to handle opacity special in IE
+		if ( name == "opacity" && jQuery.browser.msie ) {
+			ret = jQuery.attr( elem.style, "opacity" );
+
+			return ret == "" ?
+				"1" :
+				ret;
+		}
+		// Opera sometimes will give the wrong display answer, this fixes it, see #2037
+		if ( jQuery.browser.opera && name == "display" ) {
+			var save = elem.style.outline;
+			elem.style.outline = "0 solid black";
+			elem.style.outline = save;
+		}
+		
+		// Make sure we're using the right name for getting the float value
+		if ( name.match( /float/i ) )
+			name = styleFloat;
+
+		if ( !force && elem.style && elem.style[ name ] )
+			ret = elem.style[ name ];
+
+		else if ( document.defaultView && document.defaultView.getComputedStyle ) {
+
+			// Only "float" is needed here
+			if ( name.match( /float/i ) )
+				name = "float";
+
+			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+			var getComputedStyle = document.defaultView.getComputedStyle( elem, null );
+
+			if ( getComputedStyle && !color( elem ) )
+				ret = getComputedStyle.getPropertyValue( name );
+
+			// If the element isn't reporting its values properly in Safari
+			// then some display: none elements are involved
+			else {
+				var swap = [], stack = [];
+
+				// Locate all of the parent display: none elements
+				for ( var a = elem; a && color(a); a = a.parentNode )
+					stack.unshift(a);
+
+				// Go through and make them visible, but in reverse
+				// (It would be better if we knew the exact display type that they had)
+				for ( var i = 0; i < stack.length; i++ )
+					if ( color( stack[ i ] ) ) {
+						swap[ i ] = stack[ i ].style.display;
+						stack[ i ].style.display = "block";
+					}
+
+				// Since we flip the display style, we have to handle that
+				// one special, otherwise get the value
+				ret = name == "display" && swap[ stack.length - 1 ] != null ?
+					"none" :
+					( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || "";
+
+				// Finally, revert the display styles back
+				for ( var i = 0; i < swap.length; i++ )
+					if ( swap[ i ] != null )
+						stack[ i ].style.display = swap[ i ];
+			}
+
+			// We should always get a number back from opacity
+			if ( name == "opacity" && ret == "" )
+				ret = "1";
+
+		} else if ( elem.currentStyle ) {
+			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+			// From the awesome hack by Dean Edwards
+			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+			// If we're not dealing with a regular pixel number
+			// but a number that has a weird ending, we need to convert it to pixels
+			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+				// Remember the original values
+				var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left;
+
+				// Put in the new values to get a computed value out
+				elem.runtimeStyle.left = elem.currentStyle.left;
+				elem.style.left = ret || 0;
+				ret = elem.style.pixelLeft + "px";
+
+				// Revert the changed values
+				elem.style.left = style;
+				elem.runtimeStyle.left = runtimeStyle;
+			}
+		}
+
+		return ret;
+	},
+	
+	clean: function( elems, context ) {
+		var ret = [];
+		context = context || document;
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if (typeof context.createElement == 'undefined') 
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+		jQuery.each(elems, function(i, elem){
+			if ( !elem )
+				return;
+
+			if ( elem.constructor == Number )
+				elem = elem.toString();
+			
+			// Convert html string into DOM nodes
+			if ( typeof elem == "string" ) {
+				// Fix "XHTML"-style tags in all browsers
+				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+						all :
+						front + "></" + tag + ">";
+				});
+
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+				var wrap =
+					// option or optgroup
+					!tags.indexOf("<opt") &&
+					[ 1, "<select multiple='multiple'>", "</select>" ] ||
+					
+					!tags.indexOf("<leg") &&
+					[ 1, "<fieldset>", "</fieldset>" ] ||
+					
+					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+					[ 1, "<table>", "</table>" ] ||
+					
+					!tags.indexOf("<tr") &&
+					[ 2, "<table><tbody>", "</tbody></table>" ] ||
+					
+				 	// <thead> matched above
+					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+					
+					!tags.indexOf("<col") &&
+					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+					// IE can't serialize <link> and <script> tags normally
+					jQuery.browser.msie &&
+					[ 1, "div<div>", "</div>" ] ||
+					
+					[ 0, "", "" ];
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + elem + wrap[2];
+				
+				// Move to the right depth
+				while ( wrap[0]-- )
+					div = div.lastChild;
+				
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( jQuery.browser.msie ) {
+					
+					// String was a <table>, *may* have spurious <tbody>
+					var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+						div.firstChild && div.firstChild.childNodes :
+						
+						// String was a bare <thead> or <tfoot>
+						wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+							div.childNodes :
+							[];
+				
+					for ( var j = tbody.length - 1; j >= 0 ; --j )
+						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+							tbody[ j ].parentNode.removeChild( tbody[ j ] );
+					
+					// IE completely kills leading whitespace when innerHTML is used	
+					if ( /^\s/.test( elem ) )	
+						div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+				
+				}
+				
+				elem = jQuery.makeArray( div.childNodes );
+			}
+
+			if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+				return;
+
+			if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+				ret.push( elem );
+
+			else
+				ret = jQuery.merge( ret, elem );
+
+		});
+
+		return ret;
+	},
+	
+	attr: function( elem, name, value ) {
+		// don't set attributes on text and comment nodes
+		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+			return undefined;
+
+		var fix = jQuery.isXMLDoc( elem ) ?
+			{} :
+			jQuery.props;
+
+		// Safari mis-reports the default selected property of a hidden option
+		// Accessing the parent's selectedIndex property fixes it
+		if ( name == "selected" && jQuery.browser.safari )
+			elem.parentNode.selectedIndex;
+		
+		// Certain attributes only work when accessed via the old DOM 0 way
+		if ( fix[ name ] ) {
+			if ( value != undefined )
+				elem[ fix[ name ] ] = value;
+
+			return elem[ fix[ name ] ];
+
+		} else if ( jQuery.browser.msie && name == "style" )
+			return jQuery.attr( elem.style, "cssText", value );
+
+		else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") )
+			return elem.getAttributeNode( name ).nodeValue;
+
+		// IE elem.getAttribute passes even for style
+		else if ( elem.tagName ) {
+
+			if ( value != undefined ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+					throw "type property can't be changed";
+
+				// convert the value to a string (all browsers do this but IE) see #1070
+				elem.setAttribute( name, "" + value );
+			}
+
+			if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) ) 
+				return elem.getAttribute( name, 2 );
+
+			return elem.getAttribute( name );
+
+		// elem is actually elem.style ... set the style
+		} else {
+			// IE actually uses filters for opacity
+			if ( name == "opacity" && jQuery.browser.msie ) {
+				if ( value != undefined ) {
+					// IE has trouble with opacity if it does not have layout
+					// Force it by setting the zoom level
+					elem.zoom = 1; 
+	
+					// Set the alpha filter to set the opacity
+					elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+						(parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+				}
+	
+				return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+					(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() :
+					"";
+			}
+
+			name = name.replace(/-([a-z])/ig, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			if ( value != undefined )
+				elem[ name ] = value;
+
+			return elem[ name ];
+		}
+	},
+	
+	trim: function( text ) {
+		return (text || "").replace( /^\s+|\s+$/g, "" );
+	},
+
+	makeArray: function( array ) {
+		var ret = [];
+
+		// Need to use typeof to fight Safari childNodes crashes
+		if ( typeof array != "array" )
+			for ( var i = 0, length = array.length; i < length; i++ )
+				ret.push( array[ i ] );
+		else
+			ret = array.slice( 0 );
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+		for ( var i = 0, length = array.length; i < length; i++ )
+			if ( array[ i ] == elem )
+				return i;
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		// We have to loop this way because IE & Opera overwrite the length
+		// expando of getElementsByTagName
+
+		// Also, we need to make sure that the correct elements are being returned
+		// (IE returns comment nodes in a '*' query)
+		if ( jQuery.browser.msie ) {
+			for ( var i = 0; second[ i ]; i++ )
+				if ( second[ i ].nodeType != 8 )
+					first.push( second[ i ] );
+
+		} else
+			for ( var i = 0; second[ i ]; i++ )
+				first.push( second[ i ] );
+
+		return first;
+	},
+
+	unique: function( array ) {
+		var ret = [], done = {};
+
+		try {
+
+			for ( var i = 0, length = array.length; i < length; i++ ) {
+				var id = jQuery.data( array[ i ] );
+
+				if ( !done[ id ] ) {
+					done[ id ] = true;
+					ret.push( array[ i ] );
+				}
+			}
+
+		} catch( e ) {
+			ret = array;
+		}
+
+		return ret;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [];
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ )
+			if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
+				ret.push( elems[ i ] );
+
+		return ret;
+	},
+
+	map: function( elems, callback ) {
+		var ret = [];
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			var value = callback( elems[ i ], i );
+
+			if ( value !== null && value != undefined ) {
+				if ( value.constructor != Array )
+					value = [ value ];
+
+				ret = ret.concat( value );
+			}
+		}
+
+		return ret;
+	}
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+	safari: /webkit/.test( userAgent ),
+	opera: /opera/.test( userAgent ),
+	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+	"styleFloat" :
+	"cssFloat";
+	
+jQuery.extend({
+	// Check to see if the W3C box model is being used
+	boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+	
+	props: {
+		"for": "htmlFor",
+		"class": "className",
+		"float": styleFloat,
+		cssFloat: styleFloat,
+		styleFloat: styleFloat,
+		innerHTML: "innerHTML",
+		className: "className",
+		value: "value",
+		disabled: "disabled",
+		checked: "checked",
+		readonly: "readOnly",
+		selected: "selected",
+		maxlength: "maxLength",
+		selectedIndex: "selectedIndex",
+		defaultValue: "defaultValue",
+		tagName: "tagName",
+		nodeName: "nodeName"
+	}
+});
+
+jQuery.each({
+	parent: function(elem){return elem.parentNode;},
+	parents: function(elem){return jQuery.dir(elem,"parentNode");},
+	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+	children: function(elem){return jQuery.sibling(elem.firstChild);},
+	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = jQuery.map( this, fn );
+
+		if ( selector && typeof selector == "string" )
+			ret = jQuery.multiFilter( selector, ret );
+
+		return this.pushStack( jQuery.unique( ret ) );
+	};
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function(name, original){
+	jQuery.fn[ name ] = function() {
+		var args = arguments;
+
+		return this.each(function(){
+			for ( var i = 0, length = args.length; i < length; i++ )
+				jQuery( args[ i ] )[ original ]( this );
+		});
+	};
+});
+
+jQuery.each({
+	removeAttr: function( name ) {
+		jQuery.attr( this, name, "" );
+		if (this.nodeType == 1) 
+			this.removeAttribute( name );
+	},
+
+	addClass: function( classNames ) {
+		jQuery.className.add( this, classNames );
+	},
+
+	removeClass: function( classNames ) {
+		jQuery.className.remove( this, classNames );
+	},
+
+	toggleClass: function( classNames ) {
+		jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+	},
+
+	remove: function( selector ) {
+		if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+			// Prevent memory leaks
+			jQuery( "*", this ).add(this).each(function(){
+				jQuery.event.remove(this);
+				jQuery.removeData(this);
+			});
+			if (this.parentNode)
+				this.parentNode.removeChild( this );
+		}
+	},
+
+	empty: function() {
+		// Remove element nodes and prevent memory leaks
+		jQuery( ">*", this ).remove();
+		
+		// Remove any remaining nodes
+		while ( this.firstChild )
+			this.removeChild( this.firstChild );
+	}
+}, function(name, fn){
+	jQuery.fn[ name ] = function(){
+		return this.each( fn, arguments );
+	};
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+	var type = name.toLowerCase();
+	
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		return this[0] == window ?
+			// Opera reports document.body.client[Width/Height] properly in both quirks and standards
+			jQuery.browser.opera && document.body[ "client" + name ] || 
+			
+			// Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+			jQuery.browser.safari && window[ "inner" + name ] ||
+			
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+		
+			// Get document width or height
+			this[0] == document ?
+				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+				Math.max( 
+					Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]), 
+					Math.max(document.body["offset" + name], document.documentElement["offset" + name]) 
+				) :
+
+				// Get or set width or height on the element
+				size == undefined ?
+					// Get width or height on the element
+					(this.length ? jQuery.css( this[0], type ) : null) :
+
+					// Set the width or height on the element (default to pixels if value is unitless)
+					this.css( type, size.constructor == String ? size : size + "px" );
+	};
+});
+
+var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+		"(?:[\\w*_-]|\\\\.)" :
+		"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+	quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+	quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+	quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+	expr: {
+		"": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},
+		"#": function(a,i,m){return a.getAttribute("id")==m[2];},
+		":": {
+			// Position Checks
+			lt: function(a,i,m){return i<m[3]-0;},
+			gt: function(a,i,m){return i>m[3]-0;},
+			nth: function(a,i,m){return m[3]-0==i;},
+			eq: function(a,i,m){return m[3]-0==i;},
+			first: function(a,i){return i==0;},
+			last: function(a,i,m,r){return i==r.length-1;},
+			even: function(a,i){return i%2==0;},
+			odd: function(a,i){return i%2;},
+
+			// Child Checks
+			"first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},
+			"last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},
+			"only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},
+
+			// Parent Checks
+			parent: function(a){return a.firstChild;},
+			empty: function(a){return !a.firstChild;},
+
+			// Text Check
+			contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},
+
+			// Visibility
+			visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},
+			hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},
+
+			// Form attributes
+			enabled: function(a){return !a.disabled;},
+			disabled: function(a){return a.disabled;},
+			checked: function(a){return a.checked;},
+			selected: function(a){return a.selected||jQuery.attr(a,"selected");},
+
+			// Form elements
+			text: function(a){return "text"==a.type;},
+			radio: function(a){return "radio"==a.type;},
+			checkbox: function(a){return "checkbox"==a.type;},
+			file: function(a){return "file"==a.type;},
+			password: function(a){return "password"==a.type;},
+			submit: function(a){return "submit"==a.type;},
+			image: function(a){return "image"==a.type;},
+			reset: function(a){return "reset"==a.type;},
+			button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");},
+			input: function(a){return /input|select|textarea|button/i.test(a.nodeName);},
+
+			// :has()
+			has: function(a,i,m){return jQuery.find(m[3],a).length;},
+
+			// :header
+			header: function(a){return /h\d/i.test(a.nodeName);},
+
+			// :animated
+			animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}
+		}
+	},
+	
+	// The regular expressions that power the parsing engine
+	parse: [
+		// Match: [@value='test'], [@foo]
+		/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+		// Match: :contains('foo')
+		/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+		// Match: :even, :last-chlid, #id, .class
+		new RegExp("^([:.#]*)(" + chars + "+)")
+	],
+
+	multiFilter: function( expr, elems, not ) {
+		var old, cur = [];
+
+		while ( expr && expr != old ) {
+			old = expr;
+			var f = jQuery.filter( expr, elems, not );
+			expr = f.t.replace(/^\s*,\s*/, "" );
+			cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+		}
+
+		return cur;
+	},
+
+	find: function( t, context ) {
+		// Quickly handle non-string expressions
+		if ( typeof t != "string" )
+			return [ t ];
+
+		// check to make sure context is a DOM element or a document
+		if ( context && context.nodeType != 1 && context.nodeType != 9)
+			return [ ];
+
+		// Set the correct context (if none is provided)
+		context = context || document;
+
+		// Initialize the search
+		var ret = [context], done = [], last, nodeName;
+
+		// Continue while a selector expression exists, and while
+		// we're no longer looping upon ourselves
+		while ( t && last != t ) {
+			var r = [];
+			last = t;
+
+			t = jQuery.trim(t);
+
+			var foundToken = false;
+
+			// An attempt at speeding up child selectors that
+			// point to a specific element tag
+			var re = quickChild;
+			var m = re.exec(t);
+
+			if ( m ) {
+				nodeName = m[1].toUpperCase();
+
+				// Perform our own iteration and filter
+				for ( var i = 0; ret[i]; i++ )
+					for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+						if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+							r.push( c );
+
+				ret = r;
+				t = t.replace( re, "" );
+				if ( t.indexOf(" ") == 0 ) continue;
+				foundToken = true;
+			} else {
+				re = /^([>+~])\s*(\w*)/i;
+
+				if ( (m = re.exec(t)) != null ) {
+					r = [];
+
+					var merge = {};
+					nodeName = m[2].toUpperCase();
+					m = m[1];
+
+					for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+						var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+						for ( ; n; n = n.nextSibling )
+							if ( n.nodeType == 1 ) {
+								var id = jQuery.data(n);
+
+								if ( m == "~" && merge[id] ) break;
+								
+								if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+									if ( m == "~" ) merge[id] = true;
+									r.push( n );
+								}
+								
+								if ( m == "+" ) break;
+							}
+					}
+
+					ret = r;
+
+					// And remove the token
+					t = jQuery.trim( t.replace( re, "" ) );
+					foundToken = true;
+				}
+			}
+
+			// See if there's still an expression, and that we haven't already
+			// matched a token
+			if ( t && !foundToken ) {
+				// Handle multiple expressions
+				if ( !t.indexOf(",") ) {
+					// Clean the result set
+					if ( context == ret[0] ) ret.shift();
+
+					// Merge the result sets
+					done = jQuery.merge( done, ret );
+
+					// Reset the context
+					r = ret = [context];
+
+					// Touch up the selector string
+					t = " " + t.substr(1,t.length);
+
+				} else {
+					// Optimize for the case nodeName#idName
+					var re2 = quickID;
+					var m = re2.exec(t);
+					
+					// Re-organize the results, so that they're consistent
+					if ( m ) {
+						m = [ 0, m[2], m[3], m[1] ];
+
+					} else {
+						// Otherwise, do a traditional filter check for
+						// ID, class, and element selectors
+						re2 = quickClass;
+						m = re2.exec(t);
+					}
+
+					m[2] = m[2].replace(/\\/g, "");
+
+					var elem = ret[ret.length-1];
+
+					// Try to do a global search by ID, where we can
+					if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+						// Optimization for HTML document case
+						var oid = elem.getElementById(m[2]);
+						
+						// Do a quick check for the existence of the actual ID attribute
+						// to avoid selecting by the name attribute in IE
+						// also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+						if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+							oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+						// Do a quick check for node name (where applicable) so
+						// that div#foo searches will be really fast
+						ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+					} else {
+						// We need to find all descendant elements
+						for ( var i = 0; ret[i]; i++ ) {
+							// Grab the tag name being searched for
+							var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+							// Handle IE7 being really dumb about <object>s
+							if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+								tag = "param";
+
+							r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+						}
+
+						// It's faster to filter by class and be done with it
+						if ( m[1] == "." )
+							r = jQuery.classFilter( r, m[2] );
+
+						// Same with ID filtering
+						if ( m[1] == "#" ) {
+							var tmp = [];
+
+							// Try to find the element with the ID
+							for ( var i = 0; r[i]; i++ )
+								if ( r[i].getAttribute("id") == m[2] ) {
+									tmp = [ r[i] ];
+									break;
+								}
+
+							r = tmp;
+						}
+
+						ret = r;
+					}
+
+					t = t.replace( re2, "" );
+				}
+
+			}
+
+			// If a selector string still exists
+			if ( t ) {
+				// Attempt to filter it
+				var val = jQuery.filter(t,r);
+				ret = r = val.r;
+				t = jQuery.trim(val.t);
+			}
+		}
+
+		// An error occurred with the selector;
+		// just return an empty set instead
+		if ( t )
+			ret = [];
+
+		// Remove the root context
+		if ( ret && context == ret[0] )
+			ret.shift();
+
+		// And combine the results
+		done = jQuery.merge( done, ret );
+
+		return done;
+	},
+
+	classFilter: function(r,m,not){
+		m = " " + m + " ";
+		var tmp = [];
+		for ( var i = 0; r[i]; i++ ) {
+			var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+			if ( !not && pass || not && !pass )
+				tmp.push( r[i] );
+		}
+		return tmp;
+	},
+
+	filter: function(t,r,not) {
+		var last;
+
+		// Look for common filter expressions
+		while ( t && t != last ) {
+			last = t;
+
+			var p = jQuery.parse, m;
+
+			for ( var i = 0; p[i]; i++ ) {
+				m = p[i].exec( t );
+
+				if ( m ) {
+					// Remove what we just matched
+					t = t.substring( m[0].length );
+
+					m[2] = m[2].replace(/\\/g, "");
+					break;
+				}
+			}
+
+			if ( !m )
+				break;
+
+			// :not() is a special case that can be optimized by
+			// keeping it out of the expression list
+			if ( m[1] == ":" && m[2] == "not" )
+				// optimize if only one selector found (most common case)
+				r = isSimple.test( m[3] ) ?
+					jQuery.filter(m[3], r, true).r :
+					jQuery( r ).not( m[3] );
+
+			// We can get a big speed boost by filtering by class here
+			else if ( m[1] == "." )
+				r = jQuery.classFilter(r, m[2], not);
+
+			else if ( m[1] == "[" ) {
+				var tmp = [], type = m[3];
+				
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+					
+					if ( z == null || /href|src|selected/.test(m[2]) )
+						z = jQuery.attr(a,m[2]) || '';
+
+					if ( (type == "" && !!z ||
+						 type == "=" && z == m[5] ||
+						 type == "!=" && z != m[5] ||
+						 type == "^=" && z && !z.indexOf(m[5]) ||
+						 type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+						 (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+							tmp.push( a );
+				}
+				
+				r = tmp;
+
+			// We can get a speed boost by handling nth-child here
+			} else if ( m[1] == ":" && m[2] == "nth-child" ) {
+				var merge = {}, tmp = [],
+					// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+					test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+						m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+						!/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+					// calculate the numbers (first)n+(last) including if they are negative
+					first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+ 
+				// loop through all the elements left in the jQuery object
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+					if ( !merge[id] ) {
+						var c = 1;
+
+						for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+							if ( n.nodeType == 1 )
+								n.nodeIndex = c++;
+
+						merge[id] = true;
+					}
+
+					var add = false;
+
+					if ( first == 0 ) {
+						if ( node.nodeIndex == last )
+							add = true;
+					} else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+						add = true;
+
+					if ( add ^ not )
+						tmp.push( node );
+				}
+
+				r = tmp;
+
+			// Otherwise, find the expression to execute
+			} else {
+				var fn = jQuery.expr[ m[1] ];
+				if ( typeof fn == "object" )
+					fn = fn[ m[2] ];
+
+				if ( typeof fn == "string" )
+					fn = eval("false||function(a,i){return " + fn + ";}");
+
+				// Execute it against the current filter
+				r = jQuery.grep( r, function(elem, i){
+					return fn(elem, i, m, r);
+				}, not );
+			}
+		}
+
+		// Return an array of filtered elements (r)
+		// and the modified expression string (t)
+		return { r: r, t: t };
+	},
+
+	dir: function( elem, dir ){
+		var matched = [];
+		var cur = elem[dir];
+		while ( cur && cur != document ) {
+			if ( cur.nodeType == 1 )
+				matched.push( cur );
+			cur = cur[dir];
+		}
+		return matched;
+	},
+	
+	nth: function(cur,result,dir,elem){
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] )
+			if ( cur.nodeType == 1 && ++num == result )
+				break;
+
+		return cur;
+	},
+	
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType == 1 && (!elem || n != elem) )
+				r.push( n );
+		}
+
+		return r;
+	}
+});
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from 
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function(elem, types, handler, data) {
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		// For whatever reason, IE has trouble passing the window object
+		// around, causing it to be cloned in the process
+		if ( jQuery.browser.msie && elem.setInterval != undefined )
+			elem = window;
+
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid )
+			handler.guid = this.guid++;
+			
+		// if data is passed, bind to handler 
+		if( data != undefined ) { 
+			// Create temporary function pointer to original handler 
+			var fn = handler; 
+
+			// Create unique handler function, wrapped around original handler 
+			handler = function() { 
+				// Pass arguments and context to original handler 
+				return fn.apply(this, arguments); 
+			};
+
+			// Store data in unique handler 
+			handler.data = data;
+
+			// Set the guid of unique handler to the same of original handler, so it can be removed 
+			handler.guid = fn.guid;
+		}
+
+		// Init the element's event structure
+		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+				// returned undefined or false
+				var val;
+
+				// Handle the second event of a trigger and when
+				// an event is called after a page has unloaded
+				if ( typeof jQuery == "undefined" || jQuery.event.triggered )
+					return val;
+		
+				val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
+		
+				return val;
+			});
+		// Add elem as a property of the handle function
+		// This is to prevent a memory leak with non-native
+		// event in IE.
+		handle.elem = elem;
+			
+			// Handle multiple events seperated by a space
+			// jQuery(...).bind("mouseover mouseout", fn);
+			jQuery.each(types.split(/\s+/), function(index, type) {
+				// Namespaced event handlers
+				var parts = type.split(".");
+				type = parts[0];
+				handler.type = parts[1];
+
+				// Get the current list of functions bound to this event
+				var handlers = events[type];
+
+				// Init the event handler queue
+				if (!handlers) {
+					handlers = events[type] = {};
+		
+					// Check for a special event handler
+					// Only use addEventListener/attachEvent if the special
+					// events handler returns false
+					if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+						// Bind the global event handler to the element
+						if (elem.addEventListener)
+							elem.addEventListener(type, handle, false);
+						else if (elem.attachEvent)
+							elem.attachEvent("on" + type, handle);
+					}
+				}
+
+				// Add the function to the element's handler list
+				handlers[handler.guid] = handler;
+
+				// Keep track of which events have been used, for global triggering
+				jQuery.event.global[type] = true;
+			});
+		
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	guid: 1,
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function(elem, types, handler) {
+		// don't do events on text and comment nodes
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		var events = jQuery.data(elem, "events"), ret, index;
+
+		if ( events ) {
+			// Unbind all events for the element
+			if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") )
+				for ( var type in events )
+					this.remove( elem, type + (types || "") );
+			else {
+				// types is actually an event object here
+				if ( types.type ) {
+					handler = types.handler;
+					types = types.type;
+				}
+				
+				// Handle multiple events seperated by a space
+				// jQuery(...).unbind("mouseover mouseout", fn);
+				jQuery.each(types.split(/\s+/), function(index, type){
+					// Namespaced event handlers
+					var parts = type.split(".");
+					type = parts[0];
+					
+					if ( events[type] ) {
+						// remove the given handler for the given type
+						if ( handler )
+							delete events[type][handler.guid];
+			
+						// remove all handlers for the given type
+						else
+							for ( handler in events[type] )
+								// Handle the removal of namespaced events
+								if ( !parts[1] || events[type][handler].type == parts[1] )
+									delete events[type][handler];
+
+						// remove generic event handler if no more handlers exist
+						for ( ret in events[type] ) break;
+						if ( !ret ) {
+							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+								if (elem.removeEventListener)
+									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+								else if (elem.detachEvent)
+									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+							}
+							ret = null;
+							delete events[type];
+						}
+					}
+				});
+			}
+
+			// Remove the expando if it's no longer used
+			for ( ret in events ) break;
+			if ( !ret ) {
+				var handle = jQuery.data( elem, "handle" );
+				if ( handle ) handle.elem = null;
+				jQuery.removeData( elem, "events" );
+				jQuery.removeData( elem, "handle" );
+			}
+		}
+	},
+
+	trigger: function(type, data, elem, donative, extra) {
+		// Clone the incoming data, if any
+		data = jQuery.makeArray(data || []);
+
+		if ( type.indexOf("!") >= 0 ) {
+			type = type.slice(0, -1);
+			var exclusive = true;
+		}
+
+		// Handle a global trigger
+		if ( !elem ) {
+			// Only trigger if we've ever bound an event for it
+			if ( this.global[type] )
+				jQuery("*").add([window, document]).trigger(type, data);
+
+		// Handle triggering a single element
+		} else {
+			// don't do events on text and comment nodes
+			if ( elem.nodeType == 3 || elem.nodeType == 8 )
+				return undefined;
+
+			var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+				// Check to see if we need to provide a fake event, or not
+				event = !data[0] || !data[0].preventDefault;
+			
+			// Pass along a fake event
+			if ( event )
+				data.unshift( this.fix({ type: type, target: elem }) );
+
+			// Enforce the right trigger type
+			data[0].type = type;
+			if ( exclusive )
+				data[0].exclusive = true;
+
+			// Trigger the event
+			if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
+				val = jQuery.data(elem, "handle").apply( elem, data );
+
+			// Handle triggering native .onfoo handlers
+			if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+				val = false;
+
+			// Extra functions don't get the custom event object
+			if ( event )
+				data.shift();
+
+			// Handle triggering of extra function
+			if ( extra && jQuery.isFunction( extra ) ) {
+				// call the extra function and tack the current return value on the end for possible inspection
+				ret = extra.apply( elem, val == null ? data : data.concat( val ) );
+				// if anything is returned, give it precedence and have it overwrite the previous value
+				if (ret !== undefined)
+					val = ret;
+			}
+
+			// Trigger the native events (except for clicks on links)
+			if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+				this.triggered = true;
+				try {
+					elem[ type ]();
+				// prevent IE from throwing an error for some hidden elements
+				} catch (e) {}
+			}
+
+			this.triggered = false;
+		}
+
+		return val;
+	},
+
+	handle: function(event) {
+		// returned undefined or false
+		var val;
+
+		// Empty object is for triggered events with no data
+		event = jQuery.event.fix( event || window.event || {} ); 
+
+		// Namespaced event handlers
+		var parts = event.type.split(".");
+		event.type = parts[0];
+
+		var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
+		args.unshift( event );
+
+		for ( var j in handlers ) {
+			var handler = handlers[j];
+			// Pass in a reference to the handler function itself
+			// So that we can later remove it
+			args[0].handler = handler;
+			args[0].data = handler.data;
+
+			// Filter the functions by class
+			if ( !parts[1] && !event.exclusive || handler.type == parts[1] ) {
+				var ret = handler.apply( this, args );
+
+				if ( val !== false )
+					val = ret;
+
+				if ( ret === false ) {
+					event.preventDefault();
+					event.stopPropagation();
+				}
+			}
+		}
+
+		// Clean up added properties in IE to prevent memory leak
+		if (jQuery.browser.msie)
+			event.target = event.preventDefault = event.stopPropagation =
+				event.handler = event.data = null;
+
+		return val;
+	},
+
+	fix: function(event) {
+		// store a copy of the original event object 
+		// and clone to set read-only properties
+		var originalEvent = event;
+		event = jQuery.extend({}, originalEvent);
+		
+		// add preventDefault and stopPropagation since 
+		// they will not work on the clone
+		event.preventDefault = function() {
+			// if preventDefault exists run it on the original event
+			if (originalEvent.preventDefault)
+				originalEvent.preventDefault();
+			// otherwise set the returnValue property of the original event to false (IE)
+			originalEvent.returnValue = false;
+		};
+		event.stopPropagation = function() {
+			// if stopPropagation exists run it on the original event
+			if (originalEvent.stopPropagation)
+				originalEvent.stopPropagation();
+			// otherwise set the cancelBubble property of the original event to true (IE)
+			originalEvent.cancelBubble = true;
+		};
+		
+		// Fix target property, if necessary
+		if ( !event.target )
+			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+				
+		// check if target is a textnode (safari)
+		if ( event.target.nodeType == 3 )
+			event.target = originalEvent.target.parentNode;
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement )
+			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var doc = document.documentElement, body = document.body;
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+		}
+			
+		// Add which for key events
+		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+			event.which = event.charCode || event.keyCode;
+		
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey )
+			event.metaKey = event.ctrlKey;
+
+		// Add which for click: 1 == left; 2 == middle; 3 == right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button )
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+			
+		return event;
+	},
+	
+	special: {
+		ready: {
+			setup: function() {
+				// Make sure the ready event is setup
+				bindReady();
+				return;
+			},
+			
+			teardown: function() { return; }
+		},
+		
+		mouseenter: {
+			setup: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+				return true;
+			},
+		
+			teardown: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+				return true;
+			},
+			
+			handler: function(event) {
+				// If we actually just moused on to a sub-element, ignore it
+				if ( withinElement(event, this) ) return true;
+				// Execute the right handlers by setting the event type to mouseenter
+				arguments[0].type = "mouseenter";
+				return jQuery.event.handle.apply(this, arguments);
+			}
+		},
+	
+		mouseleave: {
+			setup: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+				return true;
+			},
+		
+			teardown: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+				return true;
+			},
+			
+			handler: function(event) {
+				// If we actually just moused on to a sub-element, ignore it
+				if ( withinElement(event, this) ) return true;
+				// Execute the right handlers by setting the event type to mouseleave
+				arguments[0].type = "mouseleave";
+				return jQuery.event.handle.apply(this, arguments);
+			}
+		}
+	}
+};
+
+jQuery.fn.extend({
+	bind: function( type, data, fn ) {
+		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+			jQuery.event.add( this, type, fn || data, fn && data );
+		});
+	},
+	
+	one: function( type, data, fn ) {
+		return this.each(function(){
+			jQuery.event.add( this, type, function(event) {
+				jQuery(this).unbind(event);
+				return (fn || data).apply( this, arguments);
+			}, fn && data);
+		});
+	},
+
+	unbind: function( type, fn ) {
+		return this.each(function(){
+			jQuery.event.remove( this, type, fn );
+		});
+	},
+
+	trigger: function( type, data, fn ) {
+		return this.each(function(){
+			jQuery.event.trigger( type, data, this, true, fn );
+		});
+	},
+
+	triggerHandler: function( type, data, fn ) {
+		if ( this[0] )
+			return jQuery.event.trigger( type, data, this[0], false, fn );
+		return undefined;
+	},
+
+	toggle: function() {
+		// Save reference to arguments for access in closure
+		var args = arguments;
+
+		return this.click(function(event) {
+			// Figure out which function to execute
+			this.lastToggle = 0 == this.lastToggle ? 1 : 0;
+			
+			// Make sure that clicks stop
+			event.preventDefault();
+			
+			// and execute the function
+			return args[this.lastToggle].apply( this, arguments ) || false;
+		});
+	},
+
+	hover: function(fnOver, fnOut) {
+		return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+	},
+	
+	ready: function(fn) {
+		// Attach the listeners
+		bindReady();
+
+		// If the DOM is already ready
+		if ( jQuery.isReady )
+			// Execute the function immediately
+			fn.call( document, jQuery );
+			
+		// Otherwise, remember the function for later
+		else
+			// Add the function to the wait list
+			jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+	
+		return this;
+	}
+});
+
+jQuery.extend({
+	isReady: false,
+	readyList: [],
+	// Handle when the DOM is ready
+	ready: function() {
+		// Make sure that the DOM is not already loaded
+		if ( !jQuery.isReady ) {
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+			
+			// If there are functions bound, to execute
+			if ( jQuery.readyList ) {
+				// Execute all of them
+				jQuery.each( jQuery.readyList, function(){
+					this.apply( document );
+				});
+				
+				// Reset the list of functions
+				jQuery.readyList = null;
+			}
+		
+			// Trigger any bound ready events
+			jQuery(document).triggerHandler("ready");
+		}
+	}
+});
+
+var readyBound = false;
+
+function bindReady(){
+	if ( readyBound ) return;
+	readyBound = true;
+
+	// Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+	if ( document.addEventListener && !jQuery.browser.opera)
+		// Use the handy event callback
+		document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+	
+	// If IE is used and is not in a frame
+	// Continually check to see if the document is ready
+	if ( jQuery.browser.msie && window == top ) (function(){
+		if (jQuery.isReady) return;
+		try {
+			// If IE is used, use the trick by Diego Perini
+			// http://javascript.nwbox.com/IEContentLoaded/
+			document.documentElement.doScroll("left");
+		} catch( error ) {
+			setTimeout( arguments.callee, 0 );
+			return;
+		}
+		// and execute any waiting functions
+		jQuery.ready();
+	})();
+
+	if ( jQuery.browser.opera )
+		document.addEventListener( "DOMContentLoaded", function () {
+			if (jQuery.isReady) return;
+			for (var i = 0; i < document.styleSheets.length; i++)
+				if (document.styleSheets[i].disabled) {
+					setTimeout( arguments.callee, 0 );
+					return;
+				}
+			// and execute any waiting functions
+			jQuery.ready();
+		}, false);
+
+	if ( jQuery.browser.safari ) {
+		var numStyles;
+		(function(){
+			if (jQuery.isReady) return;
+			if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+			if ( numStyles === undefined )
+				numStyles = jQuery("style, link[rel=stylesheet]").length;
+			if ( document.styleSheets.length != numStyles ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+			// and execute any waiting functions
+			jQuery.ready();
+		})();
+	}
+
+	// A fallback to window.onload, that will always work
+	jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+	"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + 
+	"submit,keydown,keypress,keyup,error").split(","), function(i, name){
+	
+	// Handle event binding
+	jQuery.fn[name] = function(fn){
+		return fn ? this.bind(name, fn) : this.trigger(name);
+	};
+});
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event, elem) {
+	// Check if mouse(over|out) are still within the same parent element
+	var parent = event.relatedTarget;
+	// Traverse up the tree
+	while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
+	// Return true if we actually just moused on to a sub-element
+	return parent == elem;
+};
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery(window).bind("unload", function() {
+	jQuery("*").add(document).unbind();
+});
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( jQuery.isFunction( url ) )
+			return this.bind("load", url);
+
+		var off = url.indexOf(" ");
+		if ( off >= 0 ) {
+			var selector = url.slice(off, url.length);
+			url = url.slice(0, off);
+		}
+
+		callback = callback || function(){};
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params )
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else {
+				params = jQuery.param( params );
+				type = "POST";
+			}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			complete: function(res, status){
+				// If successful, inject the HTML into all the matched elements
+				if ( status == "success" || status == "notmodified" )
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div/>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						res.responseText );
+
+				self.each( callback, [res.responseText, status, res] );
+			}
+		});
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param(this.serializeArray());
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			return jQuery.nodeName(this, "form") ?
+				jQuery.makeArray(this.elements) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled && 
+				(this.checked || /select|textarea/i.test(this.nodeName) || 
+					/text|hidden|password/i.test(this.type));
+		})
+		.map(function(i, elem){
+			var val = jQuery(this).val();
+			return val == null ? null :
+				val.constructor == Array ?
+					jQuery.map( val, function(val, i){
+						return {name: elem.name, value: val};
+					}) :
+					{name: elem.name, value: val};
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+	jQuery.fn[o] = function(f){
+		return this.bind(o, f);
+	};
+});
+
+var jsc = (new Date).getTime();
+
+jQuery.extend({
+	get: function( url, data, callback, type ) {
+		// shift arguments if data argument was ommited
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = null;
+		}
+		
+		return jQuery.ajax({
+			type: "GET",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get(url, null, callback, "script");
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get(url, data, callback, "json");
+	},
+
+	post: function( url, data, callback, type ) {
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = {};
+		}
+
+		return jQuery.ajax({
+			type: "POST",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	ajaxSetup: function( settings ) {
+		jQuery.extend( jQuery.ajaxSettings, settings );
+	},
+
+	ajaxSettings: {
+		global: true,
+		type: "GET",
+		timeout: 0,
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		data: null,
+		username: null,
+		password: null,
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			script: "text/javascript, application/javascript",
+			json: "application/json, text/javascript",
+			text: "text/plain",
+			_default: "*/*"
+		}
+	},
+	
+	// Last-Modified header cache for next request
+	lastModified: {},
+
+	ajax: function( s ) {
+		var jsonp, jsre = /=\?(&|$)/g, status, data;
+
+		// Extend the settings, but re-extend 's' so that it can be
+		// checked again later (in the test suite, specifically)
+		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+		// convert data if not already a string
+		if ( s.data && s.processData && typeof s.data != "string" )
+			s.data = jQuery.param(s.data);
+
+		// Handle JSONP Parameter Callbacks
+		if ( s.dataType == "jsonp" ) {
+			if ( s.type.toLowerCase() == "get" ) {
+				if ( !s.url.match(jsre) )
+					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+			} else if ( !s.data || !s.data.match(jsre) )
+				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+			s.dataType = "json";
+		}
+
+		// Build temporary JSONP function
+		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+			jsonp = "jsonp" + jsc++;
+
+			// Replace the =? sequence both in the query string and the data
+			if ( s.data )
+				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+			s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+			// We need to make sure
+			// that a JSONP style response is executed properly
+			s.dataType = "script";
+
+			// Handle JSONP-style loading
+			window[ jsonp ] = function(tmp){
+				data = tmp;
+				success();
+				complete();
+				// Garbage collect
+				window[ jsonp ] = undefined;
+				try{ delete window[ jsonp ]; } catch(e){}
+				if ( head )
+					head.removeChild( script );
+			};
+		}
+
+		if ( s.dataType == "script" && s.cache == null )
+			s.cache = false;
+
+		if ( s.cache === false && s.type.toLowerCase() == "get" ) {
+			var ts = (new Date()).getTime();
+			// try replacing _= if it is there
+			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+			// if nothing was replaced, add timestamp to the end
+			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+		}
+
+		// If data is available, append data to url for get requests
+		if ( s.data && s.type.toLowerCase() == "get" ) {
+			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+			// IE likes to send both get and post data, prevent this
+			s.data = null;
+		}
+
+		// Watch for a new set of requests
+		if ( s.global && ! jQuery.active++ )
+			jQuery.event.trigger( "ajaxStart" );
+
+		// If we're requesting a remote document
+		// and trying to load JSON or Script with a GET
+		if ( (!s.url.indexOf("http") || !s.url.indexOf("//")) && s.dataType == "script" && s.type.toLowerCase() == "get" ) {
+			var head = document.getElementsByTagName("head")[0];
+			var script = document.createElement("script");
+			script.src = s.url;
+			if (s.scriptCharset)
+				script.charset = s.scriptCharset;
+
+			// Handle Script loading
+			if ( !jsonp ) {
+				var done = false;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function(){
+					if ( !done && (!this.readyState || 
+							this.readyState == "loaded" || this.readyState == "complete") ) {
+						done = true;
+						success();
+						complete();
+						head.removeChild( script );
+					}
+				};
+			}
+
+			head.appendChild(script);
+
+			// We handle everything using the script element injection
+			return undefined;
+		}
+
+		var requestDone = false;
+
+		// Create the request object; Microsoft failed to properly
+		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+		var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+		// Open the socket
+		xml.open(s.type, s.url, s.async, s.username, s.password);
+
+		// Need an extra try/catch for cross domain requests in Firefox 3
+		try {
+			// Set the correct header, if data is being sent
+			if ( s.data )
+				xml.setRequestHeader("Content-Type", s.contentType);
+
+			// Set the If-Modified-Since header, if ifModified mode.
+			if ( s.ifModified )
+				xml.setRequestHeader("If-Modified-Since",
+					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+			// Set header so the called script knows that it's an XMLHttpRequest
+			xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+			// Set the Accepts header for the server, depending on the dataType
+			xml.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+				s.accepts[ s.dataType ] + ", */*" :
+				s.accepts._default );
+		} catch(e){}
+
+		// Allow custom headers/mimetypes
+		if ( s.beforeSend )
+			s.beforeSend(xml);
+			
+		if ( s.global )
+			jQuery.event.trigger("ajaxSend", [xml, s]);
+
+		// Wait for a response to come back
+		var onreadystatechange = function(isTimeout){
+			// The transfer is complete and the data is available, or the request timed out
+			if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) {
+				requestDone = true;
+				
+				// clear poll interval
+				if (ival) {
+					clearInterval(ival);
+					ival = null;
+				}
+				
+				status = isTimeout == "timeout" && "timeout" ||
+					!jQuery.httpSuccess( xml ) && "error" ||
+					s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" ||
+					"success";
+
+				if ( status == "success" ) {
+					// Watch for, and catch, XML document parse errors
+					try {
+						// process the data (runs the xml through httpData regardless of callback)
+						data = jQuery.httpData( xml, s.dataType );
+					} catch(e) {
+						status = "parsererror";
+					}
+				}
+
+				// Make sure that the request was successful or notmodified
+				if ( status == "success" ) {
+					// Cache Last-Modified header, if ifModified mode.
+					var modRes;
+					try {
+						modRes = xml.getResponseHeader("Last-Modified");
+					} catch(e) {} // swallow exception thrown by FF if header is not available
+	
+					if ( s.ifModified && modRes )
+						jQuery.lastModified[s.url] = modRes;
+
+					// JSONP handles its own success callback
+					if ( !jsonp )
+						success();	
+				} else
+					jQuery.handleError(s, xml, status);
+
+				// Fire the complete handlers
+				complete();
+
+				// Stop memory leaks
+				if ( s.async )
+					xml = null;
+			}
+		};
+		
+		if ( s.async ) {
+			// don't attach the handler to the request, just poll it instead
+			var ival = setInterval(onreadystatechange, 13); 
+
+			// Timeout checker
+			if ( s.timeout > 0 )
+				setTimeout(function(){
+					// Check to see if the request is still happening
+					if ( xml ) {
+						// Cancel the request
+						xml.abort();
+	
+						if( !requestDone )
+							onreadystatechange( "timeout" );
+					}
+				}, s.timeout);
+		}
+			
+		// Send the data
+		try {
+			xml.send(s.data);
+		} catch(e) {
+			jQuery.handleError(s, xml, null, e);
+		}
+		
+		// firefox 1.5 doesn't fire statechange for sync requests
+		if ( !s.async )
+			onreadystatechange();
+
+		function success(){
+			// If a local callback was specified, fire it and pass it the data
+			if ( s.success )
+				s.success( data, status );
+
+			// Fire the global callback
+			if ( s.global )
+				jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+		}
+
+		function complete(){
+			// Process result
+			if ( s.complete )
+				s.complete(xml, status);
+
+			// The request was completed
+			if ( s.global )
+				jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+			// Handle the global AJAX counter
+			if ( s.global && ! --jQuery.active )
+				jQuery.event.trigger( "ajaxStop" );
+		}
+		
+		// return XMLHttpRequest to allow aborting the request etc.
+		return xml;
+	},
+
+	handleError: function( s, xml, status, e ) {
+		// If a local callback was specified, fire it
+		if ( s.error ) s.error( xml, status, e );
+
+		// Fire the global callback
+		if ( s.global )
+			jQuery.event.trigger( "ajaxError", [xml, s, e] );
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Determines if an XMLHttpRequest was successful or not
+	httpSuccess: function( r ) {
+		try {
+			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+			return !r.status && location.protocol == "file:" ||
+				( r.status >= 200 && r.status < 300 ) || r.status == 304 || r.status == 1223 ||
+				jQuery.browser.safari && r.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function( xml, url ) {
+		try {
+			var xmlRes = xml.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			return xml.status == 304 || xmlRes == jQuery.lastModified[url] ||
+				jQuery.browser.safari && xml.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	httpData: function( r, type ) {
+		var ct = r.getResponseHeader("content-type");
+		var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0;
+		var data = xml ? r.responseXML : r.responseText;
+
+		if ( xml && data.documentElement.tagName == "parsererror" )
+			throw "parsererror";
+
+		// If the type is "script", eval it in global context
+		if ( type == "script" )
+			jQuery.globalEval( data );
+
+		// Get the JavaScript object, if JSON is used.
+		if ( type == "json" )
+			data = eval("(" + data + ")");
+
+		return data;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a ) {
+		var s = [];
+
+		// If an array was passed in, assume that it is an array
+		// of form elements
+		if ( a.constructor == Array || a.jquery )
+			// Serialize the form elements
+			jQuery.each( a, function(){
+				s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+			});
+
+		// Otherwise, assume that it's an object of key/value pairs
+		else
+			// Serialize the key/values
+			for ( var j in a )
+				// If the value is an array then the key names need to be repeated
+				if ( a[j] && a[j].constructor == Array )
+					jQuery.each( a[j], function(){
+						s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+					});
+				else
+					s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );
+
+		// Return the resulting serialization
+		return s.join("&").replace(/%20/g, "+");
+	}
+
+});
+jQuery.fn.extend({
+	show: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "show", width: "show", opacity: "show"
+			}, speed, callback) :
+			
+			this.filter(":hidden").each(function(){
+				this.style.display = this.oldblock || "";
+				if ( jQuery.css(this,"display") == "none" ) {
+					var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+					this.style.display = elem.css("display");
+					// handle an edge condition where css is - div { display:none; } or similar
+					if (this.style.display == "none")
+						this.style.display = "block";
+					elem.remove();
+				}
+			}).end();
+	},
+	
+	hide: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "hide", width: "hide", opacity: "hide"
+			}, speed, callback) :
+			
+			this.filter(":visible").each(function(){
+				this.oldblock = this.oldblock || jQuery.css(this,"display");
+				this.style.display = "none";
+			}).end();
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+	
+	toggle: function( fn, fn2 ){
+		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+			this._toggle( fn, fn2 ) :
+			fn ?
+				this.animate({
+					height: "toggle", width: "toggle", opacity: "toggle"
+				}, fn, fn2) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+				});
+	},
+	
+	slideDown: function(speed,callback){
+		return this.animate({height: "show"}, speed, callback);
+	},
+	
+	slideUp: function(speed,callback){
+		return this.animate({height: "hide"}, speed, callback);
+	},
+
+	slideToggle: function(speed, callback){
+		return this.animate({height: "toggle"}, speed, callback);
+	},
+	
+	fadeIn: function(speed, callback){
+		return this.animate({opacity: "show"}, speed, callback);
+	},
+	
+	fadeOut: function(speed, callback){
+		return this.animate({opacity: "hide"}, speed, callback);
+	},
+	
+	fadeTo: function(speed,to,callback){
+		return this.animate({opacity: to}, speed, callback);
+	},
+	
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		return this[ optall.queue === false ? "each" : "queue" ](function(){
+			if ( this.nodeType != 1)
+				return false;
+
+			var opt = jQuery.extend({}, optall);
+			var hidden = jQuery(this).is(":hidden"), self = this;
+			
+			for ( var p in prop ) {
+				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+					return jQuery.isFunction(opt.complete) && opt.complete.apply(this);
+
+				if ( p == "height" || p == "width" ) {
+					// Store display property
+					opt.display = jQuery.css(this, "display");
+
+					// Make sure that nothing sneaks out
+					opt.overflow = this.style.overflow;
+				}
+			}
+
+			if ( opt.overflow != null )
+				this.style.overflow = "hidden";
+
+			opt.curAnim = jQuery.extend({}, prop);
+			
+			jQuery.each( prop, function(name, val){
+				var e = new jQuery.fx( self, opt, name );
+
+				if ( /toggle|show|hide/.test(val) )
+					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+				else {
+					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+						start = e.cur(true) || 0;
+
+					if ( parts ) {
+						var end = parseFloat(parts[2]),
+							unit = parts[3] || "px";
+
+						// We need to compute starting value
+						if ( unit != "px" ) {
+							self.style[ name ] = (end || 1) + unit;
+							start = ((end || 1) / e.cur(true)) * start;
+							self.style[ name ] = start + unit;
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] )
+							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+						e.custom( start, end, unit );
+					} else
+						e.custom( start, val, "" );
+				}
+			});
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+	
+	queue: function(type, fn){
+		if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+			fn = type;
+			type = "fx";
+		}
+
+		if ( !type || (typeof type == "string" && !fn) )
+			return queue( this[0], type );
+
+		return this.each(function(){
+			if ( fn.constructor == Array )
+				queue(this, type, fn);
+			else {
+				queue(this, type).push( fn );
+			
+				if ( queue(this, type).length == 1 )
+					fn.apply(this);
+			}
+		});
+	},
+
+	stop: function(clearQueue, gotoEnd){
+		var timers = jQuery.timers;
+
+		if (clearQueue)
+			this.queue([]);
+
+		this.each(function(){
+			// go in reverse order so anything added to the queue during the loop is ignored
+			for ( var i = timers.length - 1; i >= 0; i-- )
+				if ( timers[i].elem == this ) {
+					if (gotoEnd)
+						// force the next step to be the last
+						timers[i](true);
+					timers.splice(i, 1);
+				}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if (!gotoEnd)
+			this.dequeue();
+
+		return this;
+	}
+
+});
+
+var queue = function( elem, type, array ) {
+	if ( !elem )
+		return undefined;
+
+	type = type || "fx";
+
+	var q = jQuery.data( elem, type + "queue" );
+
+	if ( !q || array )
+		q = jQuery.data( elem, type + "queue", 
+			array ? jQuery.makeArray(array) : [] );
+
+	return q;
+};
+
+jQuery.fn.dequeue = function(type){
+	type = type || "fx";
+
+	return this.each(function(){
+		var q = queue(this, type);
+
+		q.shift();
+
+		if ( q.length )
+			q[0].apply( this );
+	});
+};
+
+jQuery.extend({
+	
+	speed: function(speed, easing, fn) {
+		var opt = speed && speed.constructor == Object ? speed : {
+			complete: fn || !fn && easing || 
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && easing.constructor != Function && easing
+		};
+
+		opt.duration = (opt.duration && opt.duration.constructor == Number ? 
+			opt.duration : 
+			{ slow: 600, fast: 200 }[opt.duration]) || 400;
+	
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function(){
+			if ( opt.queue !== false )
+				jQuery(this).dequeue();
+			if ( jQuery.isFunction( opt.old ) )
+				opt.old.apply( this );
+		};
+	
+		return opt;
+	},
+	
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+	
+	timers: [],
+	timerId: null,
+
+	fx: function( elem, options, prop ){
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		if ( !options.orig )
+			options.orig = {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+
+	// Simple function for setting a style value
+	update: function(){
+		if ( this.options.step )
+			this.options.step.apply( this.elem, [ this.now, this ] );
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+		// Set display property to block for height/width animations
+		if ( this.prop == "height" || this.prop == "width" )
+			this.elem.style.display = "block";
+	},
+
+	// Get the current size
+	cur: function(force){
+		if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+			return this.elem[ this.prop ];
+
+		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+	},
+
+	// Start an animation from one number to another
+	custom: function(from, to, unit){
+		this.startTime = (new Date()).getTime();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || "px";
+		this.now = this.start;
+		this.pos = this.state = 0;
+		this.update();
+
+		var self = this;
+		function t(gotoEnd){
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		jQuery.timers.push(t);
+
+		if ( jQuery.timerId == null ) {
+			jQuery.timerId = setInterval(function(){
+				var timers = jQuery.timers;
+				
+				for ( var i = 0; i < timers.length; i++ )
+					if ( !timers[i]() )
+						timers.splice(i--, 1);
+
+				if ( !timers.length ) {
+					clearInterval( jQuery.timerId );
+					jQuery.timerId = null;
+				}
+			}, 13);
+		}
+	},
+
+	// Simple 'show' function
+	show: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		this.custom(0, this.cur());
+
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		if ( this.prop == "width" || this.prop == "height" )
+			this.elem.style[this.prop] = "1px";
+		
+		// Start by showing the element
+		jQuery(this.elem).show();
+	},
+
+	// Simple 'hide' function
+	hide: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function(gotoEnd){
+		var t = (new Date()).getTime();
+
+		if ( gotoEnd || t > this.options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			this.options.curAnim[ this.prop ] = true;
+
+			var done = true;
+			for ( var i in this.options.curAnim )
+				if ( this.options.curAnim[i] !== true )
+					done = false;
+
+			if ( done ) {
+				if ( this.options.display != null ) {
+					// Reset the overflow
+					this.elem.style.overflow = this.options.overflow;
+				
+					// Reset the display
+					this.elem.style.display = this.options.display;
+					if ( jQuery.css(this.elem, "display") == "none" )
+						this.elem.style.display = "block";
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( this.options.hide )
+					this.elem.style.display = "none";
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( this.options.hide || this.options.show )
+					for ( var p in this.options.curAnim )
+						jQuery.attr(this.elem.style, p, this.options.orig[p]);
+			}
+
+			// If a callback was provided, execute it
+			if ( done && jQuery.isFunction( this.options.complete ) )
+				// Execute the complete function
+				this.options.complete.apply( this.elem );
+
+			return false;
+		} else {
+			var n = t - this.startTime;
+			this.state = n / this.options.duration;
+
+			// Perform the easing function, defaults to swing
+			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+			this.now = this.start + ((this.end - this.start) * this.pos);
+
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+
+};
+
+jQuery.fx.step = {
+	scrollLeft: function(fx){
+		fx.elem.scrollLeft = fx.now;
+	},
+
+	scrollTop: function(fx){
+		fx.elem.scrollTop = fx.now;
+	},
+
+	opacity: function(fx){
+		jQuery.attr(fx.elem.style, "opacity", fx.now);
+	},
+
+	_default: function(fx){
+		fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+	}
+};
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+	var left = 0, top = 0, elem = this[0], results;
+	
+	if ( elem ) with ( jQuery.browser ) {
+		var parent       = elem.parentNode, 
+		    offsetChild  = elem,
+		    offsetParent = elem.offsetParent, 
+		    doc          = elem.ownerDocument,
+		    safari2      = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
+		    fixed        = jQuery.css(elem, "position") == "fixed";
+	
+		// Use getBoundingClientRect if available
+		if ( elem.getBoundingClientRect ) {
+			var box = elem.getBoundingClientRect();
+		
+			// Add the document scroll offsets
+			add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+				box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		
+			// IE adds the HTML element's border, by default it is medium which is 2px
+			// IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+			// IE 7 standards mode, the border is always 2px
+			// This border/offset is typically represented by the clientLeft and clientTop properties
+			// However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+			// Therefore this method will be off by 2px in IE while in quirksmode
+			add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+	
+		// Otherwise loop through the offsetParents and parentNodes
+		} else {
+		
+			// Initial element offsets
+			add( elem.offsetLeft, elem.offsetTop );
+			
+			// Get parent offsets
+			while ( offsetParent ) {
+				// Add offsetParent offsets
+				add( offsetParent.offsetLeft, offsetParent.offsetTop );
+			
+				// Mozilla and Safari > 2 does not include the border on offset parents
+				// However Mozilla adds the border for table or table cells
+				if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+					border( offsetParent );
+					
+				// Add the document scroll offsets if position is fixed on any offsetParent
+				if ( !fixed && jQuery.css(offsetParent, "position") == "fixed" )
+					fixed = true;
+			
+				// Set offsetChild to previous offsetParent unless it is the body element
+				offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+				// Get next offsetParent
+				offsetParent = offsetParent.offsetParent;
+			}
+		
+			// Get parent scroll offsets
+			while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+				// Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+				if ( !/^inline|table.*$/i.test(jQuery.css(parent, "display")) )
+					// Subtract parent scroll offsets
+					add( -parent.scrollLeft, -parent.scrollTop );
+			
+				// Mozilla does not add the border for a parent that has overflow != visible
+				if ( mozilla && jQuery.css(parent, "overflow") != "visible" )
+					border( parent );
+			
+				// Get next parent
+				parent = parent.parentNode;
+			}
+		
+			// Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+			// Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+			if ( (safari2 && (fixed || jQuery.css(offsetChild, "position") == "absolute")) || 
+				(mozilla && jQuery.css(offsetChild, "position") != "absolute") )
+					add( -doc.body.offsetLeft, -doc.body.offsetTop );
+			
+			// Add the document scroll offsets if position is fixed
+			if ( fixed )
+				add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+					Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		}
+
+		// Return an object with top and left properties
+		results = { top: top, left: left };
+	}
+
+	function border(elem) {
+		add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
+	}
+
+	function add(l, t) {
+		left += parseInt(l) || 0;
+		top += parseInt(t) || 0;
+	}
+
+	return results;
+};
+})();

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 12 - 0
talks/cant-c-a-thing/scripts/jquery-1.2.3.min.js


+ 4170 - 0
talks/cant-c-a-thing/scripts/prototype.js

@@ -0,0 +1,4170 @@
+/*  Prototype JavaScript framework, version 1.6.0.1
+ *  (c) 2005-2007 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.1',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : object.toString();
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object && object.constructor === Array;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    }.bind(this));
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  function $A(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  }
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).getElementsBySelector("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position != 'static') return proceed(element);
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.clone(Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if (document.createElement('div').outerHTML) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
+    className:    'n = h.className(n, r, "#{1}", c); c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return Selector.operators[matches[2]](nodeValue, matches[3]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._counted = true;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._counted) {
+          n._counted = true;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._counted) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._counted) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._counted) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    var exprs = expressions.join(',');
+    expressions = [];
+    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  // IE returns comment nodes on getElementsByTagName("*").
+  // Filter them out.
+  Selector.handlers.concat = function(a, b) {
+    for (var i = 0, node; node = b[i]; i++)
+      if (node.tagName !== "!") a.push(node);
+    return a;
+  };
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._eventID) return element._eventID;
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._eventID = ++arguments.callee.id;
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();

+ 58 - 0
talks/cant-c-a-thing/scripts/scriptaculous.js

@@ -0,0 +1,58 @@
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+  Version: '1.8.1',
+  require: function(libraryName) {
+    // inserting via DOM fails in Safari 2.0, so brute force approach
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
+  },
+  REQUIRED_PROTOTYPE: '1.6.0',
+  load: function() {
+    function convertVersionString(versionString){
+      var r = versionString.split('.');
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
+    }
+ 
+    if((typeof Prototype=='undefined') || 
+       (typeof Element == 'undefined') || 
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) < 
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+    
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
+    }).each( function(s) {
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
+  }
+}
+
+Scriptaculous.load();

+ 275 - 0
talks/cant-c-a-thing/scripts/slider.js

@@ -0,0 +1,275 @@
+// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs 
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if (!Control) var Control = { };
+
+// options:
+//  axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+//  onChange(value)
+//  onSlide(value)
+Control.Slider = Class.create({
+  initialize: function(handle, track, options) {
+    var slider = this;
+    
+    if (Object.isArray(handle)) {
+      this.handles = handle.collect( function(e) { return $(e) });
+    } else {
+      this.handles = [$(handle)];
+    }
+    
+    this.track   = $(track);
+    this.options = options || { };
+
+    this.axis      = this.options.axis || 'horizontal';
+    this.increment = this.options.increment || 1;
+    this.step      = parseInt(this.options.step || '1');
+    this.range     = this.options.range || $R(0,1);
+    
+    this.value     = 0; // assure backwards compat
+    this.values    = this.handles.map( function() { return 0 });
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+    this.options.startSpan = $(this.options.startSpan || null);
+    this.options.endSpan   = $(this.options.endSpan || null);
+
+    this.restricted = this.options.restricted || false;
+
+    this.maximum   = this.options.maximum || this.range.end;
+    this.minimum   = this.options.minimum || this.range.start;
+
+    // Will be used to align the handle onto the track, if necessary
+    this.alignX = parseInt(this.options.alignX || '0');
+    this.alignY = parseInt(this.options.alignY || '0');
+    
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+    this.handleLength = this.isVertical() ? 
+      (this.handles[0].offsetHeight != 0 ? 
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
+        this.handles[0].style.width.replace(/px$/,""));
+
+    this.active   = false;
+    this.dragging = false;
+    this.disabled = false;
+
+    if (this.options.disabled) this.setDisabled();
+
+    // Allowed values array
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+    if (this.allowedValues) {
+      this.minimum = this.allowedValues.min();
+      this.maximum = this.allowedValues.max();
+    }
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+
+    // Initialize handles in reverse (make sure first handle is active)
+    this.handles.each( function(h,i) {
+      i = slider.handles.length-1-i;
+      slider.setValue(parseFloat(
+        (Object.isArray(slider.options.sliderValue) ? 
+          slider.options.sliderValue[i] : slider.options.sliderValue) || 
+         slider.range.start), i);
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
+    });
+    
+    this.track.observe("mousedown", this.eventMouseDown);
+    document.observe("mouseup", this.eventMouseUp);
+    document.observe("mousemove", this.eventMouseMove);
+    
+    this.initialized = true;
+  },
+  dispose: function() {
+    var slider = this;    
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    this.handles.each( function(h) {
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+    });
+  },
+  setDisabled: function(){
+    this.disabled = true;
+  },
+  setEnabled: function(){
+    this.disabled = false;
+  },  
+  getNearestValue: function(value){
+    if (this.allowedValues){
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
+      
+      var offset = Math.abs(this.allowedValues[0] - value);
+      var newValue = this.allowedValues[0];
+      this.allowedValues.each( function(v) {
+        var currentOffset = Math.abs(v - value);
+        if (currentOffset <= offset){
+          newValue = v;
+          offset = currentOffset;
+        } 
+      });
+      return newValue;
+    }
+    if (value > this.range.end) return this.range.end;
+    if (value < this.range.start) return this.range.start;
+    return value;
+  },
+  setValue: function(sliderValue, handleIdx){
+    if (!this.active) {
+      this.activeHandleIdx = handleIdx || 0;
+      this.activeHandle    = this.handles[this.activeHandleIdx];
+      this.updateStyles();
+    }
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
+    if (this.initialized && this.restricted) {
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+        sliderValue = this.values[handleIdx-1];
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+        sliderValue = this.values[handleIdx+1];
+    }
+    sliderValue = this.getNearestValue(sliderValue);
+    this.values[handleIdx] = sliderValue;
+    this.value = this.values[0]; // assure backwards compat
+    
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
+      this.translateToPx(sliderValue);
+    
+    this.drawSpans();
+    if (!this.dragging || !this.event) this.updateFinished();
+  },
+  setValueBy: function(delta, handleIdx) {
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
+      handleIdx || this.activeHandleIdx || 0);
+  },
+  translateToPx: function(value) {
+    return Math.round(
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
+      (value - this.range.start)) + "px";
+  },
+  translateToValue: function(offset) {
+    return ((offset/(this.trackLength-this.handleLength) * 
+      (this.range.end-this.range.start)) + this.range.start);
+  },
+  getRange: function(range) {
+    var v = this.values.sortBy(Prototype.K); 
+    range = range || 0;
+    return $R(v[range],v[range+1]);
+  },
+  minimumOffset: function(){
+    return(this.isVertical() ? this.alignY : this.alignX);
+  },
+  maximumOffset: function(){
+    return(this.isVertical() ? 
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
+        this.track.style.height.replace(/px$/,"")) - this.alignY : 
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
+  },  
+  isVertical:  function(){
+    return (this.axis == 'vertical');
+  },
+  drawSpans: function() {
+    var slider = this;
+    if (this.spans)
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+    if (this.options.startSpan)
+      this.setSpan(this.options.startSpan,
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+    if (this.options.endSpan)
+      this.setSpan(this.options.endSpan, 
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+  },
+  setSpan: function(span, range) {
+    if (this.isVertical()) {
+      span.style.top = this.translateToPx(range.start);
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
+    } else {
+      span.style.left = this.translateToPx(range.start);
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
+    }
+  },
+  updateStyles: function() {
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+    Element.addClassName(this.activeHandle, 'selected');
+  },
+  startDrag: function(event) {
+    if (Event.isLeftClick(event)) {
+      if (!this.disabled){
+        this.active = true;
+        
+        var handle = Event.element(event);
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
+        var track = handle;
+        if (track==this.track) {
+          var offsets  = Position.cumulativeOffset(this.track); 
+          this.event = event;
+          this.setValue(this.translateToValue( 
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+          ));
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        } else {
+          // find the handle (prevents issues with Safari)
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
+            handle = handle.parentNode;
+            
+          if (this.handles.indexOf(handle)!=-1) {
+            this.activeHandle    = handle;
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+            this.updateStyles();
+            
+            var offsets  = Position.cumulativeOffset(this.activeHandle);
+            this.offsetX = (pointer[0] - offsets[0]);
+            this.offsetY = (pointer[1] - offsets[1]);
+          }
+        }
+      }
+      Event.stop(event);
+    }
+  },
+  update: function(event) {
+   if (this.active) {
+      if (!this.dragging) this.dragging = true;
+      this.draw(event);
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+      Event.stop(event);
+   }
+  },
+  draw: function(event) {
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    var offsets = Position.cumulativeOffset(this.track);
+    pointer[0] -= this.offsetX + offsets[0];
+    pointer[1] -= this.offsetY + offsets[1];
+    this.event = event;
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+    if (this.initialized && this.options.onSlide)
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+  },
+  endDrag: function(event) {
+    if (this.active && this.dragging) {
+      this.finishDrag(event, true);
+      Event.stop(event);
+    }
+    this.active = false;
+    this.dragging = false;
+  },  
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+    this.updateFinished();
+  },
+  updateFinished: function() {
+    if (this.initialized && this.options.onChange) 
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+    this.event = null;
+  }
+});

+ 99 - 0
talks/cant-c-a-thing/scripts/slideshow.js

@@ -0,0 +1,99 @@
+var numSlides = 0;
+var currentSlide = 0;
+var slidesReady = false;
+
+$(document).ready(function() {
+    $(document).keyup(handleKeys);
+
+    numSlides = $("div.slide").each(function (i) {
+        $(this).attr("id", "slide_" + i);
+      }).size();
+
+    readySlides();
+    showSlide();
+  });
+
+
+function readySlides() {
+  slidesReady = true;
+  $("div.slide").css("display", "none").css("position", "absolute").css("height", "90%").click(nextSlide);
+}
+
+function unreadySlides() {
+  slidesReady = false;
+    $("div.slide").css("display", "inline").css("position", "static").css("height", "12em").click(function() { gotoSlide($(this)); });
+}
+
+
+function handleKeys(e) {
+  if (! slidesReady) {
+    return;
+  }
+
+  var keyID = (window.event) ? event.keyCode : e.keyCode;
+
+  switch(keyID) {
+    case 27: // escape
+      unreadySlides();
+      break;
+    case 32: // space
+      nextSlide();
+      break;
+    case 37: // left
+      prevSlide();
+      break;
+    case 38: // up
+      setSlide(0);
+      break;
+    case 39: // right
+      nextSlide();
+      break;
+    case 40: // down
+      lastSlide();
+      break;
+  }
+}
+
+
+function showSlide() {
+  $("div.slide").css("display", "none");
+
+  $("#slide_" + currentSlide).css("display", "inline");
+}
+
+function setSlide(number) {
+  currentSlide = number;
+  showSlide();
+}
+
+
+function nextSlide() {
+  currentSlide++;
+  if (currentSlide >= numSlides) {
+    currentSlide = 0;
+  }
+
+  showSlide();
+}
+
+function prevSlide() {
+  currentSlide--;
+  if (currentSlide < 0) {
+    currentSlide = 0;
+  }
+
+  showSlide();
+}
+
+function lastSlide() {
+  currentSlide = (numSlides - 1);
+  showSlide();
+}
+
+
+function gotoSlide(slide) {
+  var number = slide.attr("id").substring(6);
+
+  readySlides();
+  setSlide(number);
+}

+ 55 - 0
talks/cant-c-a-thing/scripts/sound.js

@@ -0,0 +1,55 @@
+// script.aculo.us sound.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+Sound = {
+  tracks: {},
+  _enabled: true,
+  template:
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
+  enable: function(){
+    Sound._enabled = true;
+  },
+  disable: function(){
+    Sound._enabled = false;
+  },
+  play: function(url){
+    if(!Sound._enabled) return;
+    var options = Object.extend({
+      track: 'global', url: url, replace: false
+    }, arguments[1] || {});
+    
+    if(options.replace && this.tracks[options.track]) {
+      $R(0, this.tracks[options.track].id).each(function(id){
+        var sound = $('sound_'+options.track+'_'+id);
+        sound.Stop && sound.Stop();
+        sound.remove();
+      })
+      this.tracks[options.track] = null;
+    }
+      
+    if(!this.tracks[options.track])
+      this.tracks[options.track] = { id: 0 }
+    else
+      this.tracks[options.track].id++;
+      
+    options.id = this.tracks[options.track].id;
+    $$('body')[0].insert( 
+      Prototype.Browser.IE ? new Element('bgsound',{
+        id: 'sound_'+options.track+'_'+options.id,
+        src: options.url, loop: 1, autostart: true
+      }) : Sound.template.evaluate(options));
+  }
+};
+
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
+  else
+    Sound.play = function(){}
+}

+ 568 - 0
talks/cant-c-a-thing/scripts/unittest.js

@@ -0,0 +1,568 @@
+// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+//           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+  var options = Object.extend({
+    pointerX: 0,
+    pointerY: 0,
+    buttons:  0,
+    ctrlKey:  false,
+    altKey:   false,
+    shiftKey: false,
+    metaKey:  false
+  }, arguments[2] || {});
+  var oEvent = document.createEvent("MouseEvents");
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
+  
+  if(this.mark) Element.remove(this.mark);
+  this.mark = document.createElement('div');
+  this.mark.appendChild(document.createTextNode(" "));
+  document.body.appendChild(this.mark);
+  this.mark.style.position = 'absolute';
+  this.mark.style.top = options.pointerY + "px";
+  this.mark.style.left = options.pointerX + "px";
+  this.mark.style.width = "5px";
+  this.mark.style.height = "5px;";
+  this.mark.style.borderTop = "1px solid red;"
+  this.mark.style.borderLeft = "1px solid red;"
+  
+  if(this.step)
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+  
+  $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+  var options = Object.extend({
+    ctrlKey: false,
+    altKey: false,
+    shiftKey: false,
+    metaKey: false,
+    keyCode: 0,
+    charCode: 0
+  }, arguments[2] || {});
+
+  var oEvent = document.createEvent("KeyEvents");
+  oEvent.initKeyEvent(eventName, true, true, window, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+    options.keyCode, options.charCode );
+  $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+  for(var i=0; i<command.length; i++) {
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+  }
+};
+
+var Test = {}
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = Object.inspect;
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+  initialize: function(log) {
+    this.log = $(log);
+    if (this.log) {
+      this._createLogTable();
+    }
+  },
+  start: function(testName) {
+    if (!this.log) return;
+    this.testName = testName;
+    this.lastLogLine = document.createElement('tr');
+    this.statusCell = document.createElement('td');
+    this.nameCell = document.createElement('td');
+    this.nameCell.className = "nameCell";
+    this.nameCell.appendChild(document.createTextNode(testName));
+    this.messageCell = document.createElement('td');
+    this.lastLogLine.appendChild(this.statusCell);
+    this.lastLogLine.appendChild(this.nameCell);
+    this.lastLogLine.appendChild(this.messageCell);
+    this.loglines.appendChild(this.lastLogLine);
+  },
+  finish: function(status, summary) {
+    if (!this.log) return;
+    this.lastLogLine.className = status;
+    this.statusCell.innerHTML = status;
+    this.messageCell.innerHTML = this._toHTML(summary);
+    this.addLinksToResults();
+  },
+  message: function(message) {
+    if (!this.log) return;
+    this.messageCell.innerHTML = this._toHTML(message);
+  },
+  summary: function(summary) {
+    if (!this.log) return;
+    this.logsummary.innerHTML = this._toHTML(summary);
+  },
+  _createLogTable: function() {
+    this.log.innerHTML =
+    '<div id="logsummary"></div>' +
+    '<table id="logtable">' +
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+    '<tbody id="loglines"></tbody>' +
+    '</table>';
+    this.logsummary = $('logsummary')
+    this.loglines = $('loglines');
+  },
+  _toHTML: function(txt) {
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
+  },
+  addLinksToResults: function(){ 
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run only this test"
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
+    });
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run all tests"
+      Event.observe(td, 'click', function(){ window.location.search = "";});
+    });
+  }
+}
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+  initialize: function(testcases) {
+    this.options = Object.extend({
+      testLog: 'testlog'
+    }, arguments[1] || {});
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
+    this.options.tests      = this.parseTestsQueryParameter();
+    if (this.options.testLog) {
+      this.options.testLog = $(this.options.testLog) || null;
+    }
+    if(this.options.tests) {
+      this.tests = [];
+      for(var i = 0; i < this.options.tests.length; i++) {
+        if(/^test/.test(this.options.tests[i])) {
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+        }
+      }
+    } else {
+      if (this.options.test) {
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+      } else {
+        this.tests = [];
+        for(var testcase in testcases) {
+          if(/^test/.test(testcase)) {
+            this.tests.push(
+               new Test.Unit.Testcase(
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
+               ));
+          }
+        }
+      }
+    }
+    this.currentTest = 0;
+    this.logger = new Test.Unit.Logger(this.options.testLog);
+    setTimeout(this.runTests.bind(this), 1000);
+  },
+  parseResultsURLQueryParameter: function() {
+    return window.location.search.parseQuery()["resultsURL"];
+  },
+  parseTestsQueryParameter: function(){
+    if (window.location.search.parseQuery()["tests"]){
+        return window.location.search.parseQuery()["tests"].split(',');
+    };
+  },
+  // Returns:
+  //  "ERROR" if there was an error,
+  //  "FAILURE" if there was a failure, or
+  //  "SUCCESS" if there was neither
+  getResult: function() {
+    var hasFailure = false;
+    for(var i=0;i<this.tests.length;i++) {
+      if (this.tests[i].errors > 0) {
+        return "ERROR";
+      }
+      if (this.tests[i].failures > 0) {
+        hasFailure = true;
+      }
+    }
+    if (hasFailure) {
+      return "FAILURE";
+    } else {
+      return "SUCCESS";
+    }
+  },
+  postResults: function() {
+    if (this.options.resultsURL) {
+      new Ajax.Request(this.options.resultsURL, 
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+    }
+  },
+  runTests: function() {
+    var test = this.tests[this.currentTest];
+    if (!test) {
+      // finished!
+      this.postResults();
+      this.logger.summary(this.summary());
+      return;
+    }
+    if(!test.isWaiting) {
+      this.logger.start(test.name);
+    }
+    test.run();
+    if(test.isWaiting) {
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+    } else {
+      this.logger.finish(test.status(), test.summary());
+      this.currentTest++;
+      // tail recursive, hopefully the browser will skip the stackframe
+      this.runTests();
+    }
+  },
+  summary: function() {
+    var assertions = 0;
+    var failures = 0;
+    var errors = 0;
+    var messages = [];
+    for(var i=0;i<this.tests.length;i++) {
+      assertions +=   this.tests[i].assertions;
+      failures   +=   this.tests[i].failures;
+      errors     +=   this.tests[i].errors;
+    }
+    return (
+      (this.options.context ? this.options.context + ': ': '') + 
+      this.tests.length + " tests, " + 
+      assertions + " assertions, " + 
+      failures   + " failures, " +
+      errors     + " errors");
+  }
+}
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+  initialize: function() {
+    this.assertions = 0;
+    this.failures   = 0;
+    this.errors     = 0;
+    this.messages   = [];
+  },
+  summary: function() {
+    return (
+      this.assertions + " assertions, " + 
+      this.failures   + " failures, " +
+      this.errors     + " errors" + "\n" +
+      this.messages.join("\n"));
+  },
+  pass: function() {
+    this.assertions++;
+  },
+  fail: function(message) {
+    this.failures++;
+    this.messages.push("Failure: " + message);
+  },
+  info: function(message) {
+    this.messages.push("Info: " + message);
+  },
+  error: function(error) {
+    this.errors++;
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+  },
+  status: function() {
+    if (this.failures > 0) return 'failed';
+    if (this.errors > 0) return 'error';
+    return 'passed';
+  },
+  assert: function(expression) {
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+    try { expression ? this.pass() : 
+      this.fail(message); }
+    catch(e) { this.error(e); }
+  },
+  assertEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEqual";
+    try { (expected == actual) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInspect: function(expected, actual) {
+    var message = arguments[2] || "assertInspect";
+    try { (expected == actual.inspect()) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertEnumEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEnumEqual";
+    try { $A(expected).length == $A(actual).length && 
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
+          ', actual ' + Test.Unit.inspect(actual)); }
+    catch(e) { this.error(e); }
+  },
+  assertNotEqual: function(expected, actual) {
+    var message = arguments[2] || "assertNotEqual";
+    try { (expected != actual) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertIdentical"; 
+    try { (expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNotIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertNotIdentical"; 
+    try { !(expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNull: function(obj) {
+    var message = arguments[1] || 'assertNull'
+    try { (obj==null) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertMatch: function(expected, actual) {
+    var message = arguments[2] || 'assertMatch';
+    var regex = new RegExp(expected);
+    try { (regex.exec(actual)) ? this.pass() :
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertHidden: function(element) {
+    var message = arguments[1] || 'assertHidden';
+    this.assertEqual("none", element.style.display, message);
+  },
+  assertNotNull: function(object) {
+    var message = arguments[1] || 'assertNotNull';
+    this.assert(object != null, message);
+  },
+  assertType: function(expected, actual) {
+    var message = arguments[2] || 'assertType';
+    try { 
+      (actual.constructor == expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNotOfType: function(expected, actual) {
+    var message = arguments[2] || 'assertNotOfType';
+    try { 
+      (actual.constructor != expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertInstanceOf';
+    try { 
+      (actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was not an instance of the expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertNotInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertNotInstanceOf';
+    try { 
+      !(actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was an instance of the not expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertRespondsTo: function(method, obj) {
+    var message = arguments[2] || 'assertRespondsTo';
+    try {
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsTrue: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsTrue';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      m() ? this.pass() : 
+      this.fail(message + ": method returned false"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsFalse: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsFalse';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      !m() ? this.pass() : 
+      this.fail(message + ": method returned true"); }
+    catch(e) { this.error(e); }
+  },
+  assertRaise: function(exceptionName, method) {
+    var message = arguments[2] || 'assertRaise';
+    try { 
+      method();
+      this.fail(message + ": exception expected but none was raised"); }
+    catch(e) {
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
+    }
+  },
+  assertElementsMatch: function() {
+    var expressions = $A(arguments), elements = $A(expressions.shift());
+    if (elements.length != expressions.length) {
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
+      return false;
+    }
+    elements.zip(expressions).all(function(pair, index) {
+      var element = $(pair.first()), expression = pair.last();
+      if (element.match(expression)) return true;
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
+    }.bind(this)) && this.pass();
+  },
+  assertElementMatches: function(element, expression) {
+    this.assertElementsMatch([element], expression);
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  },
+  _isVisible: function(element) {
+    element = $(element);
+    if(!element.parentNode) return true;
+    this.assertNotNull(element);
+    if(element.style && Element.getStyle(element, 'display') == 'none')
+      return false;
+    
+    return this._isVisible(element.parentNode);
+  },
+  assertNotVisible: function(element) {
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+  },
+  assertVisible: function(element) {
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  }
+}
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+  initialize: function(name, test, setup, teardown) {
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
+    this.name           = name;
+    
+    if(typeof test == 'string') {
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
+      this.test = function() {
+        eval('with(this){'+test+'}');
+      }
+    } else {
+      this.test = test || function() {};
+    }
+    
+    this.setup          = setup || function() {};
+    this.teardown       = teardown || function() {};
+    this.isWaiting      = false;
+    this.timeToWait     = 1000;
+  },
+  wait: function(time, nextPart) {
+    this.isWaiting = true;
+    this.test = nextPart;
+    this.timeToWait = time;
+  },
+  run: function() {
+    try {
+      try {
+        if (!this.isWaiting) this.setup.bind(this)();
+        this.isWaiting = false;
+        this.test.bind(this)();
+      } finally {
+        if(!this.isWaiting) {
+          this.teardown.bind(this)();
+        }
+      }
+    }
+    catch(e) { this.error(e); }
+  }
+});
+
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
+
+Test.setupBDDExtensionMethods = function(){
+  var METHODMAP = {
+    shouldEqual:     'assertEqual',
+    shouldNotEqual:  'assertNotEqual',
+    shouldEqualEnum: 'assertEnumEqual',
+    shouldBeA:       'assertType',
+    shouldNotBeA:    'assertNotOfType',
+    shouldBeAn:      'assertType',
+    shouldNotBeAn:   'assertNotOfType',
+    shouldBeNull:    'assertNull',
+    shouldNotBeNull: 'assertNotNull',
+    
+    shouldBe:        'assertReturnsTrue',
+    shouldNotBe:     'assertReturnsFalse',
+    shouldRespondTo: 'assertRespondsTo'
+  };
+  var makeAssertion = function(assertion, args, object) { 
+   	this[assertion].apply(this,(args || []).concat([object]));
+  }
+  
+  Test.BDDMethods = {};   
+  $H(METHODMAP).each(function(pair) { 
+    Test.BDDMethods[pair.key] = function() { 
+       var args = $A(arguments); 
+       var scope = args.shift(); 
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
+  });
+  
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
+    function(p){ Object.extend(p, Test.BDDMethods) }
+  );
+}
+
+Test.context = function(name, spec, log){
+  Test.setupBDDExtensionMethods();
+  
+  var compiledSpec = {};
+  var titles = {};
+  for(specName in spec) {
+    switch(specName){
+      case "setup":
+      case "teardown":
+        compiledSpec[specName] = spec[specName];
+        break;
+      default:
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
+        var body = spec[specName].toString().split('\n').slice(1);
+        if(/^\{/.test(body[0])) body = body.slice(1);
+        body.pop();
+        body = body.map(function(statement){ 
+          return statement.strip()
+        });
+        compiledSpec[testName] = body.join('\n');
+        titles[testName] = specName;
+    }
+  }
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
+};

BIN
talks/federate_fdw/federate_fdw.pdf


+ 320 - 0
talks/locking_war_stories/index.html

@@ -0,0 +1,320 @@
+<html> <head>
+<title>Two Locking War Stories</title>
+
+<style>
+
+body {
+  background-color: #dac8c0;
+}
+
+.slide {
+  font-family: Big Caslon;
+  border: 2px solid #887777;
+  background-color: #e7e3e0;
+  padding: 2%;
+  width: 94%;
+}
+
+h1 {
+  text-align: center;
+}
+
+h2 {
+  text-align: center;
+}
+
+h3 {
+  text-align: center;
+}
+
+pre {
+  border: 1px solid #664444;
+  background-color: #fff8ea;
+  padding: 2px;
+}
+
+.credit {
+  padding: 2px;
+  font-size: -2;
+  text-align: right;
+  vertical-align: bottom;
+}
+
+
+</style>
+
+<script src="scripts/jquery-1.2.3.js" type="text/javascript"></script>
+<script src="scripts/slideshow.js" type="text/javascript"></script>
+</head>
+
+<body>
+
+<div class='slide'>
+<h1>Two War Stories</h1>
+
+<h3>(in which Locking appears as a primary antagonist)</h3>
+
+<div><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/></div>
+
+<div class="credit">John Melesky<br/>
+(PDX PUG, February 2012)</div>
+
+</div>
+
+
+<div class='slide'>
+<h1>Imagine, if you will...</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The log</h1>
+
+<pre><code>SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+</code></pre>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+<ul>
+<li>Even read-only locks need to write</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+<ul>
+<li>Even read-only locks need to write</li>
+<li>That write can have contention</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The solution</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+<ul>
+<li>Concurrency is baaad, mmkay?</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+<ul>
+<li>Concurrency is baaad, mmkay?</li>
+<li>MySQL had it right all along</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>Questions?</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>Now, imagine....</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>Egads! Deadlocks?</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The culprit(s)</h1>
+
+<pre><code>SELECT * FROM sum_table WHERE sum_id = 1234
+  AND sum_date BETWEEN '2011-01-01' AND '2011-01-10' FOR UPDATE;
+SELECT * FROM sum_table WHERE sum_id = 1234
+  AND sum_date BETWEEN '2011-01-03' AND '2011-01-13' FOR UPDATE;
+</code></pre>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+<ul>
+<li>Lock acquisition is atomic</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+<ul>
+<li>Lock acquisition is only atomic per-lock</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The problem</h1>
+
+<ul>
+<li>Lock acquisition is only atomic per-lock</li>
+<li>Locks need to be acquired in a stable order</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The solution</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The real problem</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The real problem</h1>
+
+<pre><code>UPDATE sum_table SET last_update = now()
+  WHERE sum_id = 1234
+  AND sum_date BETWEEN '2011-01-01' and '2011-01-10';
+</code></pre>
+
+</div>
+
+
+<div class='slide'>
+<h1>The real solution</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The real solution</h1>
+
+<pre><code>UPDATE sum_table SET last_update = now()
+  WHERE sum_id = 1234
+  AND sum_date BETWEEN '2011-01-01' and '2011-01-10'
+  ORDER BY sum_id, sum_date;
+</code></pre>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+<ul>
+<li>Transactional atomicity is not the same as being deadlock-proof</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+<ul>
+<li>Transactional atomicity is not the same as being deadlock-proof</li>
+<li>MySQL had it right all along</li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The conclusion</h1>
+
+<ul>
+<li>Transactional atomicity is not the same as being deadlock-proof</li>
+<li><a href="http://dev.mysql.com/doc/refman/5.5/en/update.html">MySQL had it right all along</a></li>
+</ul>
+
+</div>
+
+
+<div class='slide'>
+<h1>The MySQL way</h1>
+
+<pre><code>UPDATE t SET id = id + 1;
+</code></pre>
+
+</div>
+
+
+<div class='slide'>
+<h1>The MySQL way</h1>
+
+<pre><code>UPDATE t SET id = id + 1 ORDER BY id DESC;
+</code></pre>
+
+</div>
+
+
+<div class='slide'>
+<h1>Questions?</h1>
+
+</div>
+
+
+<div class='slide'>
+<h1>Thank you!</h1>
+
+</div>
+
+
+</body></html>

+ 150 - 0
talks/locking_war_stories/index.txt

@@ -0,0 +1,150 @@
+# Two War Stories
+### (in which Locking appears as a primary antagonist)
+
+<div><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/></div>
+
+<div class="credit">John Melesky<br/>
+(PDX PUG, February 2012)</div>
+---
+# Imagine, if you will...
+
+
+---
+# The log
+
+    SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+    SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+    SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+    SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+    SELECT * FROM definition_table WHERE defname='foobarbaz' FOR SHARE;
+
+---
+# The problem
+
+---
+# The problem
+
+* Even read-only locks need to write
+
+---
+# The problem
+
+* Even read-only locks need to write
+* That write can have contention
+
+---
+# The solution
+
+---
+# The conclusion
+
+---
+# The conclusion
+
+* Concurrency is baaad, mmkay?
+
+---
+# The conclusion
+
+* Concurrency is baaad, mmkay?
+* MySQL had it right all along
+
+---
+# Questions?
+
+---
+# Now, imagine....
+
+---
+# Egads! Deadlocks?
+
+---
+# The culprit(s)
+
+    SELECT * FROM sum_table WHERE sum_id = 1234
+      AND sum_date BETWEEN '2011-01-01' AND '2011-01-10' FOR UPDATE;
+    SELECT * FROM sum_table WHERE sum_id = 1234
+      AND sum_date BETWEEN '2011-01-03' AND '2011-01-13' FOR UPDATE;
+
+
+---
+# The problem
+
+---
+# The problem
+
+* Lock acquisition is atomic
+
+---
+# The problem
+
+* Lock acquisition is only atomic per-lock
+
+---
+# The problem
+
+* Lock acquisition is only atomic per-lock
+* Locks need to be acquired in a stable order
+
+---
+# The solution
+
+
+---
+# The real problem
+
+---
+# The real problem
+
+    UPDATE sum_table SET last_update = now()
+      WHERE sum_id = 1234
+      AND sum_date BETWEEN '2011-01-01' and '2011-01-10';
+
+---
+# The real solution
+
+---
+# The real solution
+
+    UPDATE sum_table SET last_update = now()
+      WHERE sum_id = 1234
+      AND sum_date BETWEEN '2011-01-01' and '2011-01-10'
+      ORDER BY sum_id, sum_date;
+
+---
+# The conclusion
+
+---
+# The conclusion
+
+* Transactional atomicity is not the same as being deadlock-proof
+
+---
+# The conclusion
+
+* Transactional atomicity is not the same as being deadlock-proof
+* MySQL had it right all along
+
+---
+# The conclusion
+
+* Transactional atomicity is not the same as being deadlock-proof
+* [MySQL had it right all along](http://dev.mysql.com/doc/refman/5.5/en/update.html)
+
+---
+# The MySQL way
+
+    UPDATE t SET id = id + 1;
+
+---
+# The MySQL way
+
+    UPDATE t SET id = id + 1 ORDER BY id DESC;
+
+---
+# Questions?
+
+---
+# Thank you!
+
+

+ 136 - 0
talks/locking_war_stories/scripts/builder.js

@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) { 
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
+  
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+  
+    tags.each( function(tag){ 
+      scope[tag] = function() { 
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
+      } 
+    });
+  }
+}

+ 965 - 0
talks/locking_war_stories/scripts/controls.js

@@ -0,0 +1,965 @@
+// script.aculo.us controls.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+//  Richard Livsey
+//  Rahul Bhargava
+//  Rob Wills
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// Autocompleter.Base handles all the autocompletion functionality 
+// that's independent of the data source for autocompletion. This
+// includes drawing the autocompletion menu, observing keyboard
+// and mouse events, and similar.
+//
+// Specific autocompleters need to provide, at the very least, 
+// a getUpdatedChoices function that will be invoked every time
+// the text inside the monitored textbox changes. This method 
+// should get the text for which to provide autocompletion by
+// invoking this.getToken(), NOT by directly accessing
+// this.element.value. This is to allow incremental tokenized
+// autocompletion. Specific auto-completion logic (AJAX, etc)
+// belongs in getUpdatedChoices.
+//
+// Tokenized incremental autocompletion is enabled automatically
+// when an autocompleter is instantiated with the 'tokens' option
+// in the options parameter, e.g.:
+// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
+// will incrementally autocomplete with a comma as the token.
+// Additionally, ',' in the above example can be replaced with
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most 
+// useful when one of the tokens is \n (a newline), as it 
+// allows smart autocompletion after linebreaks.
+
+if(typeof Effect == 'undefined')
+  throw("controls.js requires including script.aculo.us' effects.js library");
+
+var Autocompleter = { }
+Autocompleter.Base = Class.create({
+  baseInitialize: function(element, update, options) {
+    element          = $(element)
+    this.element     = element; 
+    this.update      = $(update);  
+    this.hasFocus    = false; 
+    this.changed     = false; 
+    this.active      = false; 
+    this.index       = 0;     
+    this.entryCount  = 0;
+    this.oldElementValue = this.element.value;
+
+    if(this.setOptions)
+      this.setOptions(options);
+    else
+      this.options = options || { };
+
+    this.options.paramName    = this.options.paramName || this.element.name;
+    this.options.tokens       = this.options.tokens || [];
+    this.options.frequency    = this.options.frequency || 0.4;
+    this.options.minChars     = this.options.minChars || 1;
+    this.options.onShow       = this.options.onShow || 
+      function(element, update){ 
+        if(!update.style.position || update.style.position=='absolute') {
+          update.style.position = 'absolute';
+          Position.clone(element, update, {
+            setHeight: false, 
+            offsetTop: element.offsetHeight
+          });
+        }
+        Effect.Appear(update,{duration:0.15});
+      };
+    this.options.onHide = this.options.onHide || 
+      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
+
+    if(typeof(this.options.tokens) == 'string') 
+      this.options.tokens = new Array(this.options.tokens);
+    // Force carriage returns as token delimiters anyway
+    if (!this.options.tokens.include('\n'))
+      this.options.tokens.push('\n');
+
+    this.observer = null;
+    
+    this.element.setAttribute('autocomplete','off');
+
+    Element.hide(this.update);
+
+    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
+    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
+  },
+
+  show: function() {
+    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+    if(!this.iefix && 
+      (Prototype.Browser.IE) &&
+      (Element.getStyle(this.update, 'position')=='absolute')) {
+      new Insertion.After(this.update, 
+       '<iframe id="' + this.update.id + '_iefix" '+
+       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
+      this.iefix = $(this.update.id+'_iefix');
+    }
+    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+  },
+  
+  fixIEOverlapping: function() {
+    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
+    this.iefix.style.zIndex = 1;
+    this.update.style.zIndex = 2;
+    Element.show(this.iefix);
+  },
+
+  hide: function() {
+    this.stopIndicator();
+    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
+    if(this.iefix) Element.hide(this.iefix);
+  },
+
+  startIndicator: function() {
+    if(this.options.indicator) Element.show(this.options.indicator);
+  },
+
+  stopIndicator: function() {
+    if(this.options.indicator) Element.hide(this.options.indicator);
+  },
+
+  onKeyPress: function(event) {
+    if(this.active)
+      switch(event.keyCode) {
+       case Event.KEY_TAB:
+       case Event.KEY_RETURN:
+         this.selectEntry();
+         Event.stop(event);
+       case Event.KEY_ESC:
+         this.hide();
+         this.active = false;
+         Event.stop(event);
+         return;
+       case Event.KEY_LEFT:
+       case Event.KEY_RIGHT:
+         return;
+       case Event.KEY_UP:
+         this.markPrevious();
+         this.render();
+         Event.stop(event);
+         return;
+       case Event.KEY_DOWN:
+         this.markNext();
+         this.render();
+         Event.stop(event);
+         return;
+      }
+     else 
+       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || 
+         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
+
+    this.changed = true;
+    this.hasFocus = true;
+
+    if(this.observer) clearTimeout(this.observer);
+      this.observer = 
+        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
+  },
+
+  activate: function() {
+    this.changed = false;
+    this.hasFocus = true;
+    this.getUpdatedChoices();
+  },
+
+  onHover: function(event) {
+    var element = Event.findElement(event, 'LI');
+    if(this.index != element.autocompleteIndex) 
+    {
+        this.index = element.autocompleteIndex;
+        this.render();
+    }
+    Event.stop(event);
+  },
+  
+  onClick: function(event) {
+    var element = Event.findElement(event, 'LI');
+    this.index = element.autocompleteIndex;
+    this.selectEntry();
+    this.hide();
+  },
+  
+  onBlur: function(event) {
+    // needed to make click events working
+    setTimeout(this.hide.bind(this), 250);
+    this.hasFocus = false;
+    this.active = false;     
+  }, 
+  
+  render: function() {
+    if(this.entryCount > 0) {
+      for (var i = 0; i < this.entryCount; i++)
+        this.index==i ? 
+          Element.addClassName(this.getEntry(i),"selected") : 
+          Element.removeClassName(this.getEntry(i),"selected");
+      if(this.hasFocus) { 
+        this.show();
+        this.active = true;
+      }
+    } else {
+      this.active = false;
+      this.hide();
+    }
+  },
+  
+  markPrevious: function() {
+    if(this.index > 0) this.index--
+      else this.index = this.entryCount-1;
+    this.getEntry(this.index).scrollIntoView(true);
+  },
+  
+  markNext: function() {
+    if(this.index < this.entryCount-1) this.index++
+      else this.index = 0;
+    this.getEntry(this.index).scrollIntoView(false);
+  },
+  
+  getEntry: function(index) {
+    return this.update.firstChild.childNodes[index];
+  },
+  
+  getCurrentEntry: function() {
+    return this.getEntry(this.index);
+  },
+  
+  selectEntry: function() {
+    this.active = false;
+    this.updateElement(this.getCurrentEntry());
+  },
+
+  updateElement: function(selectedElement) {
+    if (this.options.updateElement) {
+      this.options.updateElement(selectedElement);
+      return;
+    }
+    var value = '';
+    if (this.options.select) {
+      var nodes = $(selectedElement).select('.' + this.options.select) || [];
+      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
+    } else
+      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
+    
+    var bounds = this.getTokenBounds();
+    if (bounds[0] != -1) {
+      var newValue = this.element.value.substr(0, bounds[0]);
+      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
+      if (whitespace)
+        newValue += whitespace[0];
+      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
+    } else {
+      this.element.value = value;
+    }
+    this.oldElementValue = this.element.value;
+    this.element.focus();
+    
+    if (this.options.afterUpdateElement)
+      this.options.afterUpdateElement(this.element, selectedElement);
+  },
+
+  updateChoices: function(choices) {
+    if(!this.changed && this.hasFocus) {
+      this.update.innerHTML = choices;
+      Element.cleanWhitespace(this.update);
+      Element.cleanWhitespace(this.update.down());
+
+      if(this.update.firstChild && this.update.down().childNodes) {
+        this.entryCount = 
+          this.update.down().childNodes.length;
+        for (var i = 0; i < this.entryCount; i++) {
+          var entry = this.getEntry(i);
+          entry.autocompleteIndex = i;
+          this.addObservers(entry);
+        }
+      } else { 
+        this.entryCount = 0;
+      }
+
+      this.stopIndicator();
+      this.index = 0;
+      
+      if(this.entryCount==1 && this.options.autoSelect) {
+        this.selectEntry();
+        this.hide();
+      } else {
+        this.render();
+      }
+    }
+  },
+
+  addObservers: function(element) {
+    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
+    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
+  },
+
+  onObserverEvent: function() {
+    this.changed = false;   
+    this.tokenBounds = null;
+    if(this.getToken().length>=this.options.minChars) {
+      this.getUpdatedChoices();
+    } else {
+      this.active = false;
+      this.hide();
+    }
+    this.oldElementValue = this.element.value;
+  },
+
+  getToken: function() {
+    var bounds = this.getTokenBounds();
+    return this.element.value.substring(bounds[0], bounds[1]).strip();
+  },
+
+  getTokenBounds: function() {
+    if (null != this.tokenBounds) return this.tokenBounds;
+    var value = this.element.value;
+    if (value.strip().empty()) return [-1, 0];
+    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
+    var offset = (diff == this.oldElementValue.length ? 1 : 0);
+    var prevTokenPos = -1, nextTokenPos = value.length;
+    var tp;
+    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
+      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
+      if (tp > prevTokenPos) prevTokenPos = tp;
+      tp = value.indexOf(this.options.tokens[index], diff + offset);
+      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
+    }
+    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
+  }
+});
+
+Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
+  var boundary = Math.min(newS.length, oldS.length);
+  for (var index = 0; index < boundary; ++index)
+    if (newS[index] != oldS[index])
+      return index;
+  return boundary;
+};
+
+Ajax.Autocompleter = Class.create(Autocompleter.Base, {
+  initialize: function(element, update, url, options) {
+    this.baseInitialize(element, update, options);
+    this.options.asynchronous  = true;
+    this.options.onComplete    = this.onComplete.bind(this);
+    this.options.defaultParams = this.options.parameters || null;
+    this.url                   = url;
+  },
+
+  getUpdatedChoices: function() {
+    this.startIndicator();
+    
+    var entry = encodeURIComponent(this.options.paramName) + '=' + 
+      encodeURIComponent(this.getToken());
+
+    this.options.parameters = this.options.callback ?
+      this.options.callback(this.element, entry) : entry;
+
+    if(this.options.defaultParams) 
+      this.options.parameters += '&' + this.options.defaultParams;
+    
+    new Ajax.Request(this.url, this.options);
+  },
+
+  onComplete: function(request) {
+    this.updateChoices(request.responseText);
+  }
+});
+
+// The local array autocompleter. Used when you'd prefer to
+// inject an array of autocompletion options into the page, rather
+// than sending out Ajax queries, which can be quite slow sometimes.
+//
+// The constructor takes four parameters. The first two are, as usual,
+// the id of the monitored textbox, and id of the autocompletion menu.
+// The third is the array you want to autocomplete from, and the fourth
+// is the options block.
+//
+// Extra local autocompletion options:
+// - choices - How many autocompletion choices to offer
+//
+// - partialSearch - If false, the autocompleter will match entered
+//                    text only at the beginning of strings in the 
+//                    autocomplete array. Defaults to true, which will
+//                    match text at the beginning of any *word* in the
+//                    strings in the autocomplete array. If you want to
+//                    search anywhere in the string, additionally set
+//                    the option fullSearch to true (default: off).
+//
+// - fullSsearch - Search anywhere in autocomplete array strings.
+//
+// - partialChars - How many characters to enter before triggering
+//                   a partial match (unlike minChars, which defines
+//                   how many characters are required to do any match
+//                   at all). Defaults to 2.
+//
+// - ignoreCase - Whether to ignore case when autocompleting.
+//                 Defaults to true.
+//
+// It's possible to pass in a custom function as the 'selector' 
+// option, if you prefer to write your own autocompletion logic.
+// In that case, the other options above will not apply unless
+// you support them.
+
+Autocompleter.Local = Class.create(Autocompleter.Base, {
+  initialize: function(element, update, array, options) {
+    this.baseInitialize(element, update, options);
+    this.options.array = array;
+  },
+
+  getUpdatedChoices: function() {
+    this.updateChoices(this.options.selector(this));
+  },
+
+  setOptions: function(options) {
+    this.options = Object.extend({
+      choices: 10,
+      partialSearch: true,
+      partialChars: 2,
+      ignoreCase: true,
+      fullSearch: false,
+      selector: function(instance) {
+        var ret       = []; // Beginning matches
+        var partial   = []; // Inside matches
+        var entry     = instance.getToken();
+        var count     = 0;
+
+        for (var i = 0; i < instance.options.array.length &&  
+          ret.length < instance.options.choices ; i++) { 
+
+          var elem = instance.options.array[i];
+          var foundPos = instance.options.ignoreCase ? 
+            elem.toLowerCase().indexOf(entry.toLowerCase()) : 
+            elem.indexOf(entry);
+
+          while (foundPos != -1) {
+            if (foundPos == 0 && elem.length != entry.length) { 
+              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + 
+                elem.substr(entry.length) + "</li>");
+              break;
+            } else if (entry.length >= instance.options.partialChars && 
+              instance.options.partialSearch && foundPos != -1) {
+              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+                  foundPos + entry.length) + "</li>");
+                break;
+              }
+            }
+
+            foundPos = instance.options.ignoreCase ? 
+              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : 
+              elem.indexOf(entry, foundPos + 1);
+
+          }
+        }
+        if (partial.length)
+          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
+        return "<ul>" + ret.join('') + "</ul>";
+      }
+    }, options || { });
+  }
+});
+
+// AJAX in-place editor and collection editor
+// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+  setTimeout(function() {
+    Field.activate(field);
+  }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create({
+  initialize: function(element, url, options) {
+    this.url = url;
+    this.element = element = $(element);
+    this.prepareOptions();
+    this._controls = { };
+    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
+    Object.extend(this.options, options || { });
+    if (!this.options.formId && this.element.id) {
+      this.options.formId = this.element.id + '-inplaceeditor';
+      if ($(this.options.formId))
+        this.options.formId = '';
+    }
+    if (this.options.externalControl)
+      this.options.externalControl = $(this.options.externalControl);
+    if (!this.options.externalControl)
+      this.options.externalControlOnly = false;
+    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
+    this.element.title = this.options.clickToEditText;
+    this._boundCancelHandler = this.handleFormCancellation.bind(this);
+    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
+    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
+    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
+    this._boundWrapperHandler = this.wrapUp.bind(this);
+    this.registerListeners();
+  },
+  checkForEscapeOrReturn: function(e) {
+    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
+    if (Event.KEY_ESC == e.keyCode)
+      this.handleFormCancellation(e);
+    else if (Event.KEY_RETURN == e.keyCode)
+      this.handleFormSubmission(e);
+  },
+  createControl: function(mode, handler, extraClasses) {
+    var control = this.options[mode + 'Control'];
+    var text = this.options[mode + 'Text'];
+    if ('button' == control) {
+      var btn = document.createElement('input');
+      btn.type = 'submit';
+      btn.value = text;
+      btn.className = 'editor_' + mode + '_button';
+      if ('cancel' == mode)
+        btn.onclick = this._boundCancelHandler;
+      this._form.appendChild(btn);
+      this._controls[mode] = btn;
+    } else if ('link' == control) {
+      var link = document.createElement('a');
+      link.href = '#';
+      link.appendChild(document.createTextNode(text));
+      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
+      link.className = 'editor_' + mode + '_link';
+      if (extraClasses)
+        link.className += ' ' + extraClasses;
+      this._form.appendChild(link);
+      this._controls[mode] = link;
+    }
+  },
+  createEditField: function() {
+    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
+    var fld;
+    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
+      fld = document.createElement('input');
+      fld.type = 'text';
+      var size = this.options.size || this.options.cols || 0;
+      if (0 < size) fld.size = size;
+    } else {
+      fld = document.createElement('textarea');
+      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
+      fld.cols = this.options.cols || 40;
+    }
+    fld.name = this.options.paramName;
+    fld.value = text; // No HTML breaks conversion anymore
+    fld.className = 'editor_field';
+    if (this.options.submitOnBlur)
+      fld.onblur = this._boundSubmitHandler;
+    this._controls.editor = fld;
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+  createForm: function() {
+    var ipe = this;
+    function addText(mode, condition) {
+      var text = ipe.options['text' + mode + 'Controls'];
+      if (!text || condition === false) return;
+      ipe._form.appendChild(document.createTextNode(text));
+    };
+    this._form = $(document.createElement('form'));
+    this._form.id = this.options.formId;
+    this._form.addClassName(this.options.formClassName);
+    this._form.onsubmit = this._boundSubmitHandler;
+    this.createEditField();
+    if ('textarea' == this._controls.editor.tagName.toLowerCase())
+      this._form.appendChild(document.createElement('br'));
+    if (this.options.onFormCustomization)
+      this.options.onFormCustomization(this, this._form);
+    addText('Before', this.options.okControl || this.options.cancelControl);
+    this.createControl('ok', this._boundSubmitHandler);
+    addText('Between', this.options.okControl && this.options.cancelControl);
+    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
+    addText('After', this.options.okControl || this.options.cancelControl);
+  },
+  destroy: function() {
+    if (this._oldInnerHTML)
+      this.element.innerHTML = this._oldInnerHTML;
+    this.leaveEditMode();
+    this.unregisterListeners();
+  },
+  enterEditMode: function(e) {
+    if (this._saving || this._editing) return;
+    this._editing = true;
+    this.triggerCallback('onEnterEditMode');
+    if (this.options.externalControl)
+      this.options.externalControl.hide();
+    this.element.hide();
+    this.createForm();
+    this.element.parentNode.insertBefore(this._form, this.element);
+    if (!this.options.loadTextURL)
+      this.postProcessEditField();
+    if (e) Event.stop(e);
+  },
+  enterHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.addClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onEnterHover');
+  },
+  getText: function() {
+    return this.element.innerHTML;
+  },
+  handleAJAXFailure: function(transport) {
+    this.triggerCallback('onFailure', transport);
+    if (this._oldInnerHTML) {
+      this.element.innerHTML = this._oldInnerHTML;
+      this._oldInnerHTML = null;
+    }
+  },
+  handleFormCancellation: function(e) {
+    this.wrapUp();
+    if (e) Event.stop(e);
+  },
+  handleFormSubmission: function(e) {
+    var form = this._form;
+    var value = $F(this._controls.editor);
+    this.prepareSubmission();
+    var params = this.options.callback(form, value) || '';
+    if (Object.isString(params))
+      params = params.toQueryParams();
+    params.editorId = this.element.id;
+    if (this.options.htmlResponse) {
+      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Updater({ success: this.element }, this.url, options);
+    } else {
+      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+      Object.extend(options, {
+        parameters: params,
+        onComplete: this._boundWrapperHandler,
+        onFailure: this._boundFailureHandler
+      });
+      new Ajax.Request(this.url, options);
+    }
+    if (e) Event.stop(e);
+  },
+  leaveEditMode: function() {
+    this.element.removeClassName(this.options.savingClassName);
+    this.removeForm();
+    this.leaveHover();
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+    if (this.options.externalControl)
+      this.options.externalControl.show();
+    this._saving = false;
+    this._editing = false;
+    this._oldInnerHTML = null;
+    this.triggerCallback('onLeaveEditMode');
+  },
+  leaveHover: function(e) {
+    if (this.options.hoverClassName)
+      this.element.removeClassName(this.options.hoverClassName);
+    if (this._saving) return;
+    this.triggerCallback('onLeaveHover');
+  },
+  loadExternalText: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this._controls.editor.disabled = true;
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._form.removeClassName(this.options.loadingClassName);
+        var text = transport.responseText;
+        if (this.options.stripLoadedTextTags)
+          text = text.stripTags();
+        this._controls.editor.value = text;
+        this._controls.editor.disabled = false;
+        this.postProcessEditField();
+      }.bind(this),
+      onFailure: this._boundFailureHandler
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+  postProcessEditField: function() {
+    var fpc = this.options.fieldPostCreation;
+    if (fpc)
+      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
+  },
+  prepareOptions: function() {
+    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
+    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
+    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
+      Object.extend(this.options, defs);
+    }.bind(this));
+  },
+  prepareSubmission: function() {
+    this._saving = true;
+    this.removeForm();
+    this.leaveHover();
+    this.showSaving();
+  },
+  registerListeners: function() {
+    this._listeners = { };
+    var listener;
+    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
+      listener = this[pair.value].bind(this);
+      this._listeners[pair.key] = listener;
+      if (!this.options.externalControlOnly)
+        this.element.observe(pair.key, listener);
+      if (this.options.externalControl)
+        this.options.externalControl.observe(pair.key, listener);
+    }.bind(this));
+  },
+  removeForm: function() {
+    if (!this._form) return;
+    this._form.remove();
+    this._form = null;
+    this._controls = { };
+  },
+  showSaving: function() {
+    this._oldInnerHTML = this.element.innerHTML;
+    this.element.innerHTML = this.options.savingText;
+    this.element.addClassName(this.options.savingClassName);
+    this.element.style.backgroundColor = this._originalBackground;
+    this.element.show();
+  },
+  triggerCallback: function(cbName, arg) {
+    if ('function' == typeof this.options[cbName]) {
+      this.options[cbName](this, arg);
+    }
+  },
+  unregisterListeners: function() {
+    $H(this._listeners).each(function(pair) {
+      if (!this.options.externalControlOnly)
+        this.element.stopObserving(pair.key, pair.value);
+      if (this.options.externalControl)
+        this.options.externalControl.stopObserving(pair.key, pair.value);
+    }.bind(this));
+  },
+  wrapUp: function(transport) {
+    this.leaveEditMode();
+    // Can't use triggerCallback due to backward compatibility: requires
+    // binding + direct element
+    this._boundComplete(transport, this.element);
+  }
+});
+
+Object.extend(Ajax.InPlaceEditor.prototype, {
+  dispose: Ajax.InPlaceEditor.prototype.destroy
+});
+
+Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
+  initialize: function($super, element, url, options) {
+    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
+    $super(element, url, options);
+  },
+
+  createEditField: function() {
+    var list = document.createElement('select');
+    list.name = this.options.paramName;
+    list.size = 1;
+    this._controls.editor = list;
+    this._collection = this.options.collection || [];
+    if (this.options.loadCollectionURL)
+      this.loadCollection();
+    else
+      this.checkForExternalText();
+    this._form.appendChild(this._controls.editor);
+  },
+
+  loadCollection: function() {
+    this._form.addClassName(this.options.loadingClassName);
+    this.showLoadingText(this.options.loadingCollectionText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        var js = transport.responseText.strip();
+        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
+          throw 'Server returned an invalid collection representation.';
+        this._collection = eval(js);
+        this.checkForExternalText();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadCollectionURL, options);
+  },
+
+  showLoadingText: function(text) {
+    this._controls.editor.disabled = true;
+    var tempOption = this._controls.editor.firstChild;
+    if (!tempOption) {
+      tempOption = document.createElement('option');
+      tempOption.value = '';
+      this._controls.editor.appendChild(tempOption);
+      tempOption.selected = true;
+    }
+    tempOption.update((text || '').stripScripts().stripTags());
+  },
+
+  checkForExternalText: function() {
+    this._text = this.getText();
+    if (this.options.loadTextURL)
+      this.loadExternalText();
+    else
+      this.buildOptionList();
+  },
+
+  loadExternalText: function() {
+    this.showLoadingText(this.options.loadingText);
+    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
+    Object.extend(options, {
+      parameters: 'editorId=' + encodeURIComponent(this.element.id),
+      onComplete: Prototype.emptyFunction,
+      onSuccess: function(transport) {
+        this._text = transport.responseText.strip();
+        this.buildOptionList();
+      }.bind(this),
+      onFailure: this.onFailure
+    });
+    new Ajax.Request(this.options.loadTextURL, options);
+  },
+
+  buildOptionList: function() {
+    this._form.removeClassName(this.options.loadingClassName);
+    this._collection = this._collection.map(function(entry) {
+      return 2 === entry.length ? entry : [entry, entry].flatten();
+    });
+    var marker = ('value' in this.options) ? this.options.value : this._text;
+    var textFound = this._collection.any(function(entry) {
+      return entry[0] == marker;
+    }.bind(this));
+    this._controls.editor.update('');
+    var option;
+    this._collection.each(function(entry, index) {
+      option = document.createElement('option');
+      option.value = entry[0];
+      option.selected = textFound ? entry[0] == marker : 0 == index;
+      option.appendChild(document.createTextNode(entry[1]));
+      this._controls.editor.appendChild(option);
+    }.bind(this));
+    this._controls.editor.disabled = false;
+    Field.scrollFreeActivate(this._controls.editor);
+  }
+});
+
+//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
+//**** This only  exists for a while,  in order to  let ****
+//**** users adapt to  the new API.  Read up on the new ****
+//**** API and convert your code to it ASAP!            ****
+
+Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
+  if (!options) return;
+  function fallback(name, expr) {
+    if (name in options || expr === undefined) return;
+    options[name] = expr;
+  };
+  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
+    options.cancelLink == options.cancelButton == false ? false : undefined)));
+  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
+    options.okLink == options.okButton == false ? false : undefined)));
+  fallback('highlightColor', options.highlightcolor);
+  fallback('highlightEndColor', options.highlightendcolor);
+};
+
+Object.extend(Ajax.InPlaceEditor, {
+  DefaultOptions: {
+    ajaxOptions: { },
+    autoRows: 3,                                // Use when multi-line w/ rows == 1
+    cancelControl: 'link',                      // 'link'|'button'|false
+    cancelText: 'cancel',
+    clickToEditText: 'Click to edit',
+    externalControl: null,                      // id|elt
+    externalControlOnly: false,
+    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
+    formClassName: 'inplaceeditor-form',
+    formId: null,                               // id|elt
+    highlightColor: '#ffff99',
+    highlightEndColor: '#ffffff',
+    hoverClassName: '',
+    htmlResponse: true,
+    loadingClassName: 'inplaceeditor-loading',
+    loadingText: 'Loading...',
+    okControl: 'button',                        // 'link'|'button'|false
+    okText: 'ok',
+    paramName: 'value',
+    rows: 1,                                    // If 1 and multi-line, uses autoRows
+    savingClassName: 'inplaceeditor-saving',
+    savingText: 'Saving...',
+    size: 0,
+    stripLoadedTextTags: false,
+    submitOnBlur: false,
+    textAfterControls: '',
+    textBeforeControls: '',
+    textBetweenControls: ''
+  },
+  DefaultCallbacks: {
+    callback: function(form) {
+      return Form.serialize(form);
+    },
+    onComplete: function(transport, element) {
+      // For backward compatibility, this one is bound to the IPE, and passes
+      // the element directly.  It was too often customized, so we don't break it.
+      new Effect.Highlight(element, {
+        startcolor: this.options.highlightColor, keepBackgroundImage: true });
+    },
+    onEnterEditMode: null,
+    onEnterHover: function(ipe) {
+      ipe.element.style.backgroundColor = ipe.options.highlightColor;
+      if (ipe._effect)
+        ipe._effect.cancel();
+    },
+    onFailure: function(transport, ipe) {
+      alert('Error communication with the server: ' + transport.responseText.stripTags());
+    },
+    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
+    onLeaveEditMode: null,
+    onLeaveHover: function(ipe) {
+      ipe._effect = new Effect.Highlight(ipe.element, {
+        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
+        restorecolor: ipe._originalBackground, keepBackgroundImage: true
+      });
+    }
+  },
+  Listeners: {
+    click: 'enterEditMode',
+    keydown: 'checkForEscapeOrReturn',
+    mouseover: 'enterHover',
+    mouseout: 'leaveHover'
+  }
+});
+
+Ajax.InPlaceCollectionEditor.DefaultOptions = {
+  loadingCollectionText: 'Loading options...'
+};
+
+// Delayed observer, like Form.Element.Observer, 
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create({
+  initialize: function(element, delay, callback) {
+    this.delay     = delay || 0.5;
+    this.element   = $(element);
+    this.callback  = callback;
+    this.timer     = null;
+    this.lastValue = $F(this.element); 
+    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+  },
+  delayedListener: function(event) {
+    if(this.lastValue == $F(this.element)) return;
+    if(this.timer) clearTimeout(this.timer);
+    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+    this.lastValue = $F(this.element);
+  },
+  onTimerEvent: function() {
+    this.timer = null;
+    this.callback(this.element, $F(this.element));
+  }
+});

+ 974 - 0
talks/locking_war_stories/scripts/dragdrop.js

@@ -0,0 +1,974 @@
+// script.aculo.us dragdrop.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if(Object.isUndefined(Effect))
+  throw("dragdrop.js requires including script.aculo.us' effects.js library");
+
+var Droppables = {
+  drops: [],
+
+  remove: function(element) {
+    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
+  },
+
+  add: function(element) {
+    element = $(element);
+    var options = Object.extend({
+      greedy:     true,
+      hoverclass: null,
+      tree:       false
+    }, arguments[1] || { });
+
+    // cache containers
+    if(options.containment) {
+      options._containers = [];
+      var containment = options.containment;
+      if(Object.isArray(containment)) {
+        containment.each( function(c) { options._containers.push($(c)) });
+      } else {
+        options._containers.push($(containment));
+      }
+    }
+    
+    if(options.accept) options.accept = [options.accept].flatten();
+
+    Element.makePositioned(element); // fix IE
+    options.element = element;
+
+    this.drops.push(options);
+  },
+  
+  findDeepestChild: function(drops) {
+    deepest = drops[0];
+      
+    for (i = 1; i < drops.length; ++i)
+      if (Element.isParent(drops[i].element, deepest.element))
+        deepest = drops[i];
+    
+    return deepest;
+  },
+
+  isContained: function(element, drop) {
+    var containmentNode;
+    if(drop.tree) {
+      containmentNode = element.treeNode; 
+    } else {
+      containmentNode = element.parentNode;
+    }
+    return drop._containers.detect(function(c) { return containmentNode == c });
+  },
+  
+  isAffected: function(point, element, drop) {
+    return (
+      (drop.element!=element) &&
+      ((!drop._containers) ||
+        this.isContained(element, drop)) &&
+      ((!drop.accept) ||
+        (Element.classNames(element).detect( 
+          function(v) { return drop.accept.include(v) } ) )) &&
+      Position.within(drop.element, point[0], point[1]) );
+  },
+
+  deactivate: function(drop) {
+    if(drop.hoverclass)
+      Element.removeClassName(drop.element, drop.hoverclass);
+    this.last_active = null;
+  },
+
+  activate: function(drop) {
+    if(drop.hoverclass)
+      Element.addClassName(drop.element, drop.hoverclass);
+    this.last_active = drop;
+  },
+
+  show: function(point, element) {
+    if(!this.drops.length) return;
+    var drop, affected = [];
+    
+    this.drops.each( function(drop) {
+      if(Droppables.isAffected(point, element, drop))
+        affected.push(drop);
+    });
+        
+    if(affected.length>0)
+      drop = Droppables.findDeepestChild(affected);
+
+    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
+    if (drop) {
+      Position.within(drop.element, point[0], point[1]);
+      if(drop.onHover)
+        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
+      
+      if (drop != this.last_active) Droppables.activate(drop);
+    }
+  },
+
+  fire: function(event, element) {
+    if(!this.last_active) return;
+    Position.prepare();
+
+    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
+      if (this.last_active.onDrop) {
+        this.last_active.onDrop(element, this.last_active.element, event); 
+        return true; 
+      }
+  },
+
+  reset: function() {
+    if(this.last_active)
+      this.deactivate(this.last_active);
+  }
+}
+
+var Draggables = {
+  drags: [],
+  observers: [],
+  
+  register: function(draggable) {
+    if(this.drags.length == 0) {
+      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
+      
+      Event.observe(document, "mouseup", this.eventMouseUp);
+      Event.observe(document, "mousemove", this.eventMouseMove);
+      Event.observe(document, "keypress", this.eventKeypress);
+    }
+    this.drags.push(draggable);
+  },
+  
+  unregister: function(draggable) {
+    this.drags = this.drags.reject(function(d) { return d==draggable });
+    if(this.drags.length == 0) {
+      Event.stopObserving(document, "mouseup", this.eventMouseUp);
+      Event.stopObserving(document, "mousemove", this.eventMouseMove);
+      Event.stopObserving(document, "keypress", this.eventKeypress);
+    }
+  },
+  
+  activate: function(draggable) {
+    if(draggable.options.delay) { 
+      this._timeout = setTimeout(function() { 
+        Draggables._timeout = null; 
+        window.focus(); 
+        Draggables.activeDraggable = draggable; 
+      }.bind(this), draggable.options.delay); 
+    } else {
+      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+      this.activeDraggable = draggable;
+    }
+  },
+  
+  deactivate: function() {
+    this.activeDraggable = null;
+  },
+  
+  updateDrag: function(event) {
+    if(!this.activeDraggable) return;
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    // Mozilla-based browsers fire successive mousemove events with
+    // the same coordinates, prevent needless redrawing (moz bug?)
+    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+    this._lastPointer = pointer;
+    
+    this.activeDraggable.updateDrag(event, pointer);
+  },
+  
+  endDrag: function(event) {
+    if(this._timeout) { 
+      clearTimeout(this._timeout); 
+      this._timeout = null; 
+    }
+    if(!this.activeDraggable) return;
+    this._lastPointer = null;
+    this.activeDraggable.endDrag(event);
+    this.activeDraggable = null;
+  },
+  
+  keyPress: function(event) {
+    if(this.activeDraggable)
+      this.activeDraggable.keyPress(event);
+  },
+  
+  addObserver: function(observer) {
+    this.observers.push(observer);
+    this._cacheObserverCallbacks();
+  },
+  
+  removeObserver: function(element) {  // element instead of observer fixes mem leaks
+    this.observers = this.observers.reject( function(o) { return o.element==element });
+    this._cacheObserverCallbacks();
+  },
+  
+  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
+    if(this[eventName+'Count'] > 0)
+      this.observers.each( function(o) {
+        if(o[eventName]) o[eventName](eventName, draggable, event);
+      });
+    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
+  },
+  
+  _cacheObserverCallbacks: function() {
+    ['onStart','onEnd','onDrag'].each( function(eventName) {
+      Draggables[eventName+'Count'] = Draggables.observers.select(
+        function(o) { return o[eventName]; }
+      ).length;
+    });
+  }
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Draggable = Class.create({
+  initialize: function(element) {
+    var defaults = {
+      handle: false,
+      reverteffect: function(element, top_offset, left_offset) {
+        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
+          queue: {scope:'_draggable', position:'end'}
+        });
+      },
+      endeffect: function(element) {
+        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
+        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, 
+          queue: {scope:'_draggable', position:'end'},
+          afterFinish: function(){ 
+            Draggable._dragging[element] = false 
+          }
+        }); 
+      },
+      zindex: 1000,
+      revert: false,
+      quiet: false,
+      scroll: false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
+      delay: 0
+    };
+    
+    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
+      Object.extend(defaults, {
+        starteffect: function(element) {
+          element._opacity = Element.getOpacity(element);
+          Draggable._dragging[element] = true;
+          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); 
+        }
+      });
+    
+    var options = Object.extend(defaults, arguments[1] || { });
+
+    this.element = $(element);
+    
+    if(options.handle && Object.isString(options.handle))
+      this.handle = this.element.down('.'+options.handle, 0);
+    
+    if(!this.handle) this.handle = $(options.handle);
+    if(!this.handle) this.handle = this.element;
+    
+    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
+      options.scroll = $(options.scroll);
+      this._isScrollChild = Element.childOf(this.element, options.scroll);
+    }
+
+    Element.makePositioned(this.element); // fix IE    
+
+    this.options  = options;
+    this.dragging = false;   
+
+    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
+    Event.observe(this.handle, "mousedown", this.eventMouseDown);
+    
+    Draggables.register(this);
+  },
+  
+  destroy: function() {
+    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+    Draggables.unregister(this);
+  },
+  
+  currentDelta: function() {
+    return([
+      parseInt(Element.getStyle(this.element,'left') || '0'),
+      parseInt(Element.getStyle(this.element,'top') || '0')]);
+  },
+  
+  initDrag: function(event) {
+    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
+      Draggable._dragging[this.element]) return;
+    if(Event.isLeftClick(event)) {    
+      // abort on form elements, fixes a Firefox issue
+      var src = Event.element(event);
+      if((tag_name = src.tagName.toUpperCase()) && (
+        tag_name=='INPUT' ||
+        tag_name=='SELECT' ||
+        tag_name=='OPTION' ||
+        tag_name=='BUTTON' ||
+        tag_name=='TEXTAREA')) return;
+        
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      var pos     = Position.cumulativeOffset(this.element);
+      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+      
+      Draggables.activate(this);
+      Event.stop(event);
+    }
+  },
+  
+  startDrag: function(event) {
+    this.dragging = true;
+    if(!this.delta)
+      this.delta = this.currentDelta();
+    
+    if(this.options.zindex) {
+      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+      this.element.style.zIndex = this.options.zindex;
+    }
+    
+    if(this.options.ghosting) {
+      this._clone = this.element.cloneNode(true);
+      this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
+      if (!this.element._originallyAbsolute)
+        Position.absolutize(this.element);
+      this.element.parentNode.insertBefore(this._clone, this.element);
+    }
+    
+    if(this.options.scroll) {
+      if (this.options.scroll == window) {
+        var where = this._getWindowScroll(this.options.scroll);
+        this.originalScrollLeft = where.left;
+        this.originalScrollTop = where.top;
+      } else {
+        this.originalScrollLeft = this.options.scroll.scrollLeft;
+        this.originalScrollTop = this.options.scroll.scrollTop;
+      }
+    }
+    
+    Draggables.notify('onStart', this, event);
+        
+    if(this.options.starteffect) this.options.starteffect(this.element);
+  },
+  
+  updateDrag: function(event, pointer) {
+    if(!this.dragging) this.startDrag(event);
+    
+    if(!this.options.quiet){
+      Position.prepare();
+      Droppables.show(pointer, this.element);
+    }
+    
+    Draggables.notify('onDrag', this, event);
+    
+    this.draw(pointer);
+    if(this.options.change) this.options.change(this);
+    
+    if(this.options.scroll) {
+      this.stopScrolling();
+      
+      var p;
+      if (this.options.scroll == window) {
+        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
+      } else {
+        p = Position.page(this.options.scroll);
+        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
+        p[1] += this.options.scroll.scrollTop + Position.deltaY;
+        p.push(p[0]+this.options.scroll.offsetWidth);
+        p.push(p[1]+this.options.scroll.offsetHeight);
+      }
+      var speed = [0,0];
+      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
+      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
+      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
+      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
+      this.startScrolling(speed);
+    }
+    
+    // fix AppleWebKit rendering
+    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
+    
+    Event.stop(event);
+  },
+  
+  finishDrag: function(event, success) {
+    this.dragging = false;
+    
+    if(this.options.quiet){
+      Position.prepare();
+      var pointer = [Event.pointerX(event), Event.pointerY(event)];
+      Droppables.show(pointer, this.element);
+    }
+
+    if(this.options.ghosting) {
+      if (!this.element._originallyAbsolute)
+        Position.relativize(this.element);
+      delete this.element._originallyAbsolute;
+      Element.remove(this._clone);
+      this._clone = null;
+    }
+
+    var dropped = false; 
+    if(success) { 
+      dropped = Droppables.fire(event, this.element); 
+      if (!dropped) dropped = false; 
+    }
+    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
+    Draggables.notify('onEnd', this, event);
+
+    var revert = this.options.revert;
+    if(revert && Object.isFunction(revert)) revert = revert(this.element);
+    
+    var d = this.currentDelta();
+    if(revert && this.options.reverteffect) {
+      if (dropped == 0 || revert != 'failure')
+        this.options.reverteffect(this.element,
+          d[1]-this.delta[1], d[0]-this.delta[0]);
+    } else {
+      this.delta = d;
+    }
+
+    if(this.options.zindex)
+      this.element.style.zIndex = this.originalZ;
+
+    if(this.options.endeffect) 
+      this.options.endeffect(this.element);
+      
+    Draggables.deactivate(this);
+    Droppables.reset();
+  },
+  
+  keyPress: function(event) {
+    if(event.keyCode!=Event.KEY_ESC) return;
+    this.finishDrag(event, false);
+    Event.stop(event);
+  },
+  
+  endDrag: function(event) {
+    if(!this.dragging) return;
+    this.stopScrolling();
+    this.finishDrag(event, true);
+    Event.stop(event);
+  },
+  
+  draw: function(point) {
+    var pos = Position.cumulativeOffset(this.element);
+    if(this.options.ghosting) {
+      var r   = Position.realOffset(this.element);
+      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
+    }
+    
+    var d = this.currentDelta();
+    pos[0] -= d[0]; pos[1] -= d[1];
+    
+    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
+      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
+      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
+    }
+    
+    var p = [0,1].map(function(i){ 
+      return (point[i]-pos[i]-this.offset[i]) 
+    }.bind(this));
+    
+    if(this.options.snap) {
+      if(Object.isFunction(this.options.snap)) {
+        p = this.options.snap(p[0],p[1],this);
+      } else {
+      if(Object.isArray(this.options.snap)) {
+        p = p.map( function(v, i) {
+          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this))
+      } else {
+        p = p.map( function(v) {
+          return (v/this.options.snap).round()*this.options.snap }.bind(this))
+      }
+    }}
+    
+    var style = this.element.style;
+    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
+      style.left = p[0] + "px";
+    if((!this.options.constraint) || (this.options.constraint=='vertical'))
+      style.top  = p[1] + "px";
+    
+    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
+  },
+  
+  stopScrolling: function() {
+    if(this.scrollInterval) {
+      clearInterval(this.scrollInterval);
+      this.scrollInterval = null;
+      Draggables._lastScrollPointer = null;
+    }
+  },
+  
+  startScrolling: function(speed) {
+    if(!(speed[0] || speed[1])) return;
+    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
+    this.lastScrolled = new Date();
+    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
+  },
+  
+  scroll: function() {
+    var current = new Date();
+    var delta = current - this.lastScrolled;
+    this.lastScrolled = current;
+    if(this.options.scroll == window) {
+      with (this._getWindowScroll(this.options.scroll)) {
+        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
+          var d = delta / 1000;
+          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
+        }
+      }
+    } else {
+      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
+      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
+    }
+    
+    Position.prepare();
+    Droppables.show(Draggables._lastPointer, this.element);
+    Draggables.notify('onDrag', this);
+    if (this._isScrollChild) {
+      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
+      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
+      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
+      if (Draggables._lastScrollPointer[0] < 0)
+        Draggables._lastScrollPointer[0] = 0;
+      if (Draggables._lastScrollPointer[1] < 0)
+        Draggables._lastScrollPointer[1] = 0;
+      this.draw(Draggables._lastScrollPointer);
+    }
+    
+    if(this.options.change) this.options.change(this);
+  },
+  
+  _getWindowScroll: function(w) {
+    var T, L, W, H;
+    with (w.document) {
+      if (w.document.documentElement && documentElement.scrollTop) {
+        T = documentElement.scrollTop;
+        L = documentElement.scrollLeft;
+      } else if (w.document.body) {
+        T = body.scrollTop;
+        L = body.scrollLeft;
+      }
+      if (w.innerWidth) {
+        W = w.innerWidth;
+        H = w.innerHeight;
+      } else if (w.document.documentElement && documentElement.clientWidth) {
+        W = documentElement.clientWidth;
+        H = documentElement.clientHeight;
+      } else {
+        W = body.offsetWidth;
+        H = body.offsetHeight
+      }
+    }
+    return { top: T, left: L, width: W, height: H };
+  }
+});
+
+Draggable._dragging = { };
+
+/*--------------------------------------------------------------------------*/
+
+var SortableObserver = Class.create({
+  initialize: function(element, observer) {
+    this.element   = $(element);
+    this.observer  = observer;
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onStart: function() {
+    this.lastValue = Sortable.serialize(this.element);
+  },
+  
+  onEnd: function() {
+    Sortable.unmark();
+    if(this.lastValue != Sortable.serialize(this.element))
+      this.observer(this.element)
+  }
+});
+
+var Sortable = {
+  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
+  
+  sortables: { },
+  
+  _findRootElement: function(element) {
+    while (element.tagName.toUpperCase() != "BODY") {  
+      if(element.id && Sortable.sortables[element.id]) return element;
+      element = element.parentNode;
+    }
+  },
+
+  options: function(element) {
+    element = Sortable._findRootElement($(element));
+    if(!element) return;
+    return Sortable.sortables[element.id];
+  },
+  
+  destroy: function(element){
+    var s = Sortable.options(element);
+    
+    if(s) {
+      Draggables.removeObserver(s.element);
+      s.droppables.each(function(d){ Droppables.remove(d) });
+      s.draggables.invoke('destroy');
+      
+      delete Sortable.sortables[s.element.id];
+    }
+  },
+
+  create: function(element) {
+    element = $(element);
+    var options = Object.extend({ 
+      element:     element,
+      tag:         'li',       // assumes li children, override with tag: 'tagname'
+      dropOnEmpty: false,
+      tree:        false,
+      treeTag:     'ul',
+      overlap:     'vertical', // one of 'vertical', 'horizontal'
+      constraint:  'vertical', // one of 'vertical', 'horizontal', false
+      containment: element,    // also takes array of elements (or id's); or false
+      handle:      false,      // or a CSS class
+      only:        false,
+      delay:       0,
+      hoverclass:  null,
+      ghosting:    false,
+      quiet:       false, 
+      scroll:      false,
+      scrollSensitivity: 20,
+      scrollSpeed: 15,
+      format:      this.SERIALIZE_RULE,
+      
+      // these take arrays of elements or ids and can be 
+      // used for better initialization performance
+      elements:    false,
+      handles:     false,
+      
+      onChange:    Prototype.emptyFunction,
+      onUpdate:    Prototype.emptyFunction
+    }, arguments[1] || { });
+
+    // clear any old sortable with same element
+    this.destroy(element);
+
+    // build options for the draggables
+    var options_for_draggable = {
+      revert:      true,
+      quiet:       options.quiet,
+      scroll:      options.scroll,
+      scrollSpeed: options.scrollSpeed,
+      scrollSensitivity: options.scrollSensitivity,
+      delay:       options.delay,
+      ghosting:    options.ghosting,
+      constraint:  options.constraint,
+      handle:      options.handle };
+
+    if(options.starteffect)
+      options_for_draggable.starteffect = options.starteffect;
+
+    if(options.reverteffect)
+      options_for_draggable.reverteffect = options.reverteffect;
+    else
+      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+        element.style.top  = 0;
+        element.style.left = 0;
+      };
+
+    if(options.endeffect)
+      options_for_draggable.endeffect = options.endeffect;
+
+    if(options.zindex)
+      options_for_draggable.zindex = options.zindex;
+
+    // build options for the droppables  
+    var options_for_droppable = {
+      overlap:     options.overlap,
+      containment: options.containment,
+      tree:        options.tree,
+      hoverclass:  options.hoverclass,
+      onHover:     Sortable.onHover
+    }
+    
+    var options_for_tree = {
+      onHover:      Sortable.onEmptyHover,
+      overlap:      options.overlap,
+      containment:  options.containment,
+      hoverclass:   options.hoverclass
+    }
+
+    // fix for gecko engine
+    Element.cleanWhitespace(element); 
+
+    options.draggables = [];
+    options.droppables = [];
+
+    // drop on empty handling
+    if(options.dropOnEmpty || options.tree) {
+      Droppables.add(element, options_for_tree);
+      options.droppables.push(element);
+    }
+
+    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
+      var handle = options.handles ? $(options.handles[i]) :
+        (options.handle ? $(e).select('.' + options.handle)[0] : e); 
+      options.draggables.push(
+        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+      Droppables.add(e, options_for_droppable);
+      if(options.tree) e.treeNode = element;
+      options.droppables.push(e);      
+    });
+    
+    if(options.tree) {
+      (Sortable.findTreeElements(element, options) || []).each( function(e) {
+        Droppables.add(e, options_for_tree);
+        e.treeNode = element;
+        options.droppables.push(e);
+      });
+    }
+
+    // keep reference
+    this.sortables[element.id] = options;
+
+    // for onupdate
+    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
+
+  },
+
+  // return all suitable-for-sortable elements in a guaranteed order
+  findElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.tag);
+  },
+  
+  findTreeElements: function(element, options) {
+    return Element.findChildren(
+      element, options.only, options.tree ? true : false, options.treeTag);
+  },
+
+  onHover: function(element, dropon, overlap) {
+    if(Element.isParent(dropon, element)) return;
+
+    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
+      return;
+    } else if(overlap>0.5) {
+      Sortable.mark(dropon, 'before');
+      if(dropon.previousSibling != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, dropon);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    } else {
+      Sortable.mark(dropon, 'after');
+      var nextElement = dropon.nextSibling || null;
+      if(nextElement != element) {
+        var oldParentNode = element.parentNode;
+        element.style.visibility = "hidden"; // fix gecko rendering
+        dropon.parentNode.insertBefore(element, nextElement);
+        if(dropon.parentNode!=oldParentNode) 
+          Sortable.options(oldParentNode).onChange(element);
+        Sortable.options(dropon.parentNode).onChange(element);
+      }
+    }
+  },
+  
+  onEmptyHover: function(element, dropon, overlap) {
+    var oldParentNode = element.parentNode;
+    var droponOptions = Sortable.options(dropon);
+        
+    if(!Element.isParent(dropon, element)) {
+      var index;
+      
+      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
+      var child = null;
+            
+      if(children) {
+        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
+        
+        for (index = 0; index < children.length; index += 1) {
+          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
+            offset -= Element.offsetSize (children[index], droponOptions.overlap);
+          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
+            child = index + 1 < children.length ? children[index + 1] : null;
+            break;
+          } else {
+            child = children[index];
+            break;
+          }
+        }
+      }
+      
+      dropon.insertBefore(element, child);
+      
+      Sortable.options(oldParentNode).onChange(element);
+      droponOptions.onChange(element);
+    }
+  },
+
+  unmark: function() {
+    if(Sortable._marker) Sortable._marker.hide();
+  },
+
+  mark: function(dropon, position) {
+    // mark on ghosting only
+    var sortable = Sortable.options(dropon.parentNode);
+    if(sortable && !sortable.ghosting) return; 
+
+    if(!Sortable._marker) {
+      Sortable._marker = 
+        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
+          hide().addClassName('dropmarker').setStyle({position:'absolute'});
+      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+    }    
+    var offsets = Position.cumulativeOffset(dropon);
+    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
+    
+    if(position=='after')
+      if(sortable.overlap == 'horizontal') 
+        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
+      else
+        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
+    
+    Sortable._marker.show();
+  },
+  
+  _tree: function(element, options, parent) {
+    var children = Sortable.findElements(element, options) || [];
+  
+    for (var i = 0; i < children.length; ++i) {
+      var match = children[i].id.match(options.format);
+
+      if (!match) continue;
+      
+      var child = {
+        id: encodeURIComponent(match ? match[1] : null),
+        element: element,
+        parent: parent,
+        children: [],
+        position: parent.children.length,
+        container: $(children[i]).down(options.treeTag)
+      }
+      
+      /* Get the element containing the children and recurse over it */
+      if (child.container)
+        this._tree(child.container, options, child)
+      
+      parent.children.push (child);
+    }
+
+    return parent; 
+  },
+
+  tree: function(element) {
+    element = $(element);
+    var sortableOptions = this.options(element);
+    var options = Object.extend({
+      tag: sortableOptions.tag,
+      treeTag: sortableOptions.treeTag,
+      only: sortableOptions.only,
+      name: element.id,
+      format: sortableOptions.format
+    }, arguments[1] || { });
+    
+    var root = {
+      id: null,
+      parent: null,
+      children: [],
+      container: element,
+      position: 0
+    }
+    
+    return Sortable._tree(element, options, root);
+  },
+
+  /* Construct a [i] index for a particular node */
+  _constructIndex: function(node) {
+    var index = '';
+    do {
+      if (node.id) index = '[' + node.position + ']' + index;
+    } while ((node = node.parent) != null);
+    return index;
+  },
+
+  sequence: function(element) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[1] || { });
+    
+    return $(this.findElements(element, options) || []).map( function(item) {
+      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
+    });
+  },
+
+  setSequence: function(element, new_sequence) {
+    element = $(element);
+    var options = Object.extend(this.options(element), arguments[2] || { });
+    
+    var nodeMap = { };
+    this.findElements(element, options).each( function(n) {
+        if (n.id.match(options.format))
+            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
+        n.parentNode.removeChild(n);
+    });
+   
+    new_sequence.each(function(ident) {
+      var n = nodeMap[ident];
+      if (n) {
+        n[1].appendChild(n[0]);
+        delete nodeMap[ident];
+      }
+    });
+  },
+  
+  serialize: function(element) {
+    element = $(element);
+    var options = Object.extend(Sortable.options(element), arguments[1] || { });
+    var name = encodeURIComponent(
+      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
+    
+    if (options.tree) {
+      return Sortable.tree(element, arguments[1]).children.map( function (item) {
+        return [name + Sortable._constructIndex(item) + "[id]=" + 
+                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
+      }).flatten().join('&');
+    } else {
+      return Sortable.sequence(element, arguments[1]).map( function(item) {
+        return name + "[]=" + encodeURIComponent(item);
+      }).join('&');
+    }
+  }
+}
+
+// Returns true if child is contained within element
+Element.isParent = function(child, element) {
+  if (!child.parentNode || child == element) return false;
+  if (child.parentNode == element) return true;
+  return Element.isParent(child.parentNode, element);
+}
+
+Element.findChildren = function(element, only, recursive, tagName) {   
+  if(!element.hasChildNodes()) return null;
+  tagName = tagName.toUpperCase();
+  if(only) only = [only].flatten();
+  var elements = [];
+  $A(element.childNodes).each( function(e) {
+    if(e.tagName && e.tagName.toUpperCase()==tagName &&
+      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
+        elements.push(e);
+    if(recursive) {
+      var grandchildren = Element.findChildren(e, only, recursive, tagName);
+      if(grandchildren) elements.push(grandchildren);
+    }
+  });
+
+  return (elements.length>0 ? elements.flatten() : []);
+}
+
+Element.offsetSize = function (element, type) {
+  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
+}

+ 1122 - 0
talks/locking_war_stories/scripts/effects.js

@@ -0,0 +1,1122 @@
+// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
+
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if (this.slice(0,1) == '#') {  
+      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if (this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return (color.length==7 ? color : (arguments[0] || this));  
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + 0.5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+      return pos > 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+    },
+    pulse: function(pos, pulses) { 
+      pulses = pulses || 5; 
+      return (
+        ((pos % (1/pulses)) * pulses).round() == 0 ? 
+              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
+          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+        );
+    },
+    spring: function(pos) { 
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if (child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            new Element('span', {style: tagifyStyle}).update(
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if (((typeof element == 'object') || 
+        Object.isFunction(element)) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || { });
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || { });
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+  }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    var position = Object.isString(effect.options.queue) ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+
+    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
+    
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if (this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    for(var i=0, len=this.effects.length;i<len;i++) 
+      this.effects[i] && this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+    
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
+  }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+  position: null,
+  start: function(options) {
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if (this.state=="idle"){this.state="running";'+
+      codeForEvent(this.options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(this.options,'afterSetup')+
+      '};if (this.state=="running"){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(this.options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(this.options,'afterUpdate')+
+      '}}');
+    
+    this.event('beforeStart');
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if (timePos >= this.startOn) {
+      if (timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if (this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  cancel: function() {
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if (effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(), 
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) : 
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
+    var options = Object.extend({
+      from: this.element.getOpacity() || 0.0,
+      to:   1.0
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  update: function(position) {
+    this.element.setOpacity(position);
+  }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+  initialize: function(element, percent) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+    
+    this.originalStyle = { };
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if (this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if (/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if (!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if (this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    this.element.setStyle(d);
+  }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+    scrollOffsets = document.viewport.getScrollOffsets(),
+    elementOffsets = $(element).cumulativeOffset(),
+    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1] > max ? max : elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()) }
+  );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  var options = Object.extend({
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) { 
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity}); 
+    }
+  }, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+  element = $(element);
+  var options = Object.extend({
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+  to:   1.0,
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
+   );
+};
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || { })
+  );
+};
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      })
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}) }}) }}) }}) }}) }});
+};
+
+Effect.SlideDown = function(element) {
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
+  );
+};
+
+Effect.SlideUp = function(element) {
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
+  );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
+  });
+};
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01, 
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
+           }, options)
+      )
+    }
+  });
+};
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+};
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || { };
+  var oldOpacity = element.getInlineOpacity();
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+    
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        }
+      }
+    }
+    this.start(options);
+  },
+  
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]); 
+  });
+  
+  if (Prototype.Browser.IE && this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+      results[property] = css[property];
+      return results;
+    });
+    if (!styles.opacity) styles.opacity = element.getOpacity();
+    return styles;
+  };
+};
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element)
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) { 
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    }
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);

+ 3408 - 0
talks/locking_war_stories/scripts/jquery-1.2.3.js

@@ -0,0 +1,3408 @@
+(function(){
+/*
+ * jQuery 1.2.3 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-02-06 00:21:25 -0500 (Wed, 06 Feb 2008) $
+ * $Rev: 4663 $
+ */
+
+// Map over jQuery in case of overwrite
+if ( window.jQuery )
+	var _jQuery = window.jQuery;
+
+var jQuery = window.jQuery = function( selector, context ) {
+	// The jQuery object is actually just the init constructor 'enhanced'
+	return new jQuery.prototype.init( selector, context );
+};
+
+// Map over the $ in case of overwrite
+if ( window.$ )
+	var _$ = window.$;
+	
+// Map the jQuery namespace to the '$' one
+window.$ = jQuery;
+
+// A simple way to check for HTML strings or ID strings
+// (both of which we optimize for)
+var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/;
+
+// Is it a simple selector
+var isSimple = /^.[^:#\[\.]*$/;
+
+jQuery.fn = jQuery.prototype = {
+	init: function( selector, context ) {
+		// Make sure that a selection was provided
+		selector = selector || document;
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this[0] = selector;
+			this.length = 1;
+			return this;
+
+		// Handle HTML strings
+		} else if ( typeof selector == "string" ) {
+			// Are we dealing with HTML string or an ID?
+			var match = quickExpr.exec( selector );
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] )
+					selector = jQuery.clean( [ match[1] ], context );
+
+				// HANDLE: $("#id")
+				else {
+					var elem = document.getElementById( match[3] );
+
+					// Make sure an element was located
+					if ( elem )
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id != match[3] )
+							return jQuery().find( selector );
+
+						// Otherwise, we inject the element directly into the jQuery object
+						else {
+							this[0] = elem;
+							this.length = 1;
+							return this;
+						}
+
+					else
+						selector = [];
+				}
+
+			// HANDLE: $(expr, [context])
+			// (which is just equivalent to: $(content).find(expr)
+			} else
+				return new jQuery( context ).find( selector );
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) )
+			return new jQuery( document )[ jQuery.fn.ready ? "ready" : "load" ]( selector );
+
+		return this.setArray(
+			// HANDLE: $(array)
+			selector.constructor == Array && selector ||
+
+			// HANDLE: $(arraylike)
+			// Watch for when an array-like object, contains DOM nodes, is passed in as the selector
+			(selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) ||
+
+			// HANDLE: $(*)
+			[ selector ] );
+	},
+	
+	// The current version of jQuery being used
+	jquery: "1.2.3",
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+	
+	// The number of elements contained in the matched element set
+	length: 0,
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == undefined ?
+
+			// Return a 'clean' array
+			jQuery.makeArray( this ) :
+
+			// Return just the object
+			this[ num ];
+	},
+	
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+		// Build a new jQuery matched element set
+		var ret = jQuery( elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+	
+	// Force the current matched set of elements to become
+	// the specified array of elements (destroying the stack in the process)
+	// You should use pushStack() in order to do this, but maintain the stack
+	setArray: function( elems ) {
+		// Resetting the length to 0, then using the native Array push
+		// is a super-fast way to populate an object with array-like properties
+		this.length = 0;
+		Array.prototype.push.apply( this, elems );
+		
+		return this;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	// Determine the position of an element within 
+	// the matched set of elements
+	index: function( elem ) {
+		var ret = -1;
+
+		// Locate the position of the desired element
+		this.each(function(i){
+			if ( this == elem )
+				ret = i;
+		});
+
+		return ret;
+	},
+
+	attr: function( name, value, type ) {
+		var options = name;
+		
+		// Look for the case where we're accessing a style value
+		if ( name.constructor == String )
+			if ( value == undefined )
+				return this.length && jQuery[ type || "attr" ]( this[0], name ) || undefined;
+
+			else {
+				options = {};
+				options[ name ] = value;
+			}
+		
+		// Check to see if we're setting style values
+		return this.each(function(i){
+			// Set all the styles
+			for ( name in options )
+				jQuery.attr(
+					type ?
+						this.style :
+						this,
+					name, jQuery.prop( this, options[ name ], type, i, name )
+				);
+		});
+	},
+
+	css: function( key, value ) {
+		// ignore negative width and height values
+		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+			value = undefined;
+		return this.attr( key, value, "curCSS" );
+	},
+
+	text: function( text ) {
+		if ( typeof text != "object" && text != null )
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+		var ret = "";
+
+		jQuery.each( text || this, function(){
+			jQuery.each( this.childNodes, function(){
+				if ( this.nodeType != 8 )
+					ret += this.nodeType != 1 ?
+						this.nodeValue :
+						jQuery.fn.text( [ this ] );
+			});
+		});
+
+		return ret;
+	},
+
+	wrapAll: function( html ) {
+		if ( this[0] )
+			// The elements to wrap the target around
+			jQuery( html, this[0].ownerDocument )
+				.clone()
+				.insertBefore( this[0] )
+				.map(function(){
+					var elem = this;
+
+					while ( elem.firstChild )
+						elem = elem.firstChild;
+
+					return elem;
+				})
+				.append(this);
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		return this.each(function(){
+			jQuery( this ).contents().wrapAll( html );
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function(){
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, false, function(elem){
+			if (this.nodeType == 1)
+				this.appendChild( elem );
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, true, function(elem){
+			if (this.nodeType == 1)
+				this.insertBefore( elem, this.firstChild );
+		});
+	},
+	
+	before: function() {
+		return this.domManip(arguments, false, false, function(elem){
+			this.parentNode.insertBefore( elem, this );
+		});
+	},
+
+	after: function() {
+		return this.domManip(arguments, false, true, function(elem){
+			this.parentNode.insertBefore( elem, this.nextSibling );
+		});
+	},
+
+	end: function() {
+		return this.prevObject || jQuery( [] );
+	},
+
+	find: function( selector ) {
+		var elems = jQuery.map(this, function(elem){
+			return jQuery.find( selector, elem );
+		});
+
+		return this.pushStack( /[^+>] [^+>]/.test( selector ) || selector.indexOf("..") > -1 ?
+			jQuery.unique( elems ) :
+			elems );
+	},
+
+	clone: function( events ) {
+		// Do the clone
+		var ret = this.map(function(){
+			if ( jQuery.browser.msie && !jQuery.isXMLDoc(this) ) {
+				// IE copies events bound via attachEvent when
+				// using cloneNode. Calling detachEvent on the
+				// clone will also remove the events from the orignal
+				// In order to get around this, we use innerHTML.
+				// Unfortunately, this means some modifications to 
+				// attributes in IE that are actually only stored 
+				// as properties will not be copied (such as the
+				// the name attribute on an input).
+				var clone = this.cloneNode(true),
+					container = document.createElement("div");
+				container.appendChild(clone);
+				return jQuery.clean([container.innerHTML])[0];
+			} else
+				return this.cloneNode(true);
+		});
+
+		// Need to set the expando to null on the cloned set if it exists
+		// removeData doesn't work here, IE removes it from the original as well
+		// this is primarily for IE but the data expando shouldn't be copied over in any browser
+		var clone = ret.find("*").andSelf().each(function(){
+			if ( this[ expando ] != undefined )
+				this[ expando ] = null;
+		});
+		
+		// Copy the events from the original to the clone
+		if ( events === true )
+			this.find("*").andSelf().each(function(i){
+				if (this.nodeType == 3)
+					return;
+				var events = jQuery.data( this, "events" );
+
+				for ( var type in events )
+					for ( var handler in events[ type ] )
+						jQuery.event.add( clone[ i ], type, events[ type ][ handler ], events[ type ][ handler ].data );
+			});
+
+		// Return the cloned set
+		return ret;
+	},
+
+	filter: function( selector ) {
+		return this.pushStack(
+			jQuery.isFunction( selector ) &&
+			jQuery.grep(this, function(elem, i){
+				return selector.call( elem, i );
+			}) ||
+
+			jQuery.multiFilter( selector, this ) );
+	},
+
+	not: function( selector ) {
+		if ( selector.constructor == String )
+			// test special case where just one selector is passed in
+			if ( isSimple.test( selector ) )
+				return this.pushStack( jQuery.multiFilter( selector, this, true ) );
+			else
+				selector = jQuery.multiFilter( selector, this );
+
+		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+		return this.filter(function() {
+			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+		});
+	},
+
+	add: function( selector ) {
+		return !selector ? this : this.pushStack( jQuery.merge( 
+			this.get(),
+			selector.constructor == String ? 
+				jQuery( selector ).get() :
+				selector.length != undefined && (!selector.nodeName || jQuery.nodeName(selector, "form")) ?
+					selector : [selector] ) );
+	},
+
+	is: function( selector ) {
+		return selector ?
+			jQuery.multiFilter( selector, this ).length > 0 :
+			false;
+	},
+
+	hasClass: function( selector ) {
+		return this.is( "." + selector );
+	},
+	
+	val: function( value ) {
+		if ( value == undefined ) {
+
+			if ( this.length ) {
+				var elem = this[0];
+
+				// We need to handle select boxes special
+				if ( jQuery.nodeName( elem, "select" ) ) {
+					var index = elem.selectedIndex,
+						values = [],
+						options = elem.options,
+						one = elem.type == "select-one";
+					
+					// Nothing was selected
+					if ( index < 0 )
+						return null;
+
+					// Loop through all the selected options
+					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+						var option = options[ i ];
+
+						if ( option.selected ) {
+							// Get the specifc value for the option
+							value = jQuery.browser.msie && !option.attributes.value.specified ? option.text : option.value;
+							
+							// We don't need an array for one selects
+							if ( one )
+								return value;
+							
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+					
+					return values;
+					
+				// Everything else, we just grab the value
+				} else
+					return (this[0].value || "").replace(/\r/g, "");
+
+			}
+
+			return undefined;
+		}
+
+		return this.each(function(){
+			if ( this.nodeType != 1 )
+				return;
+
+			if ( value.constructor == Array && /radio|checkbox/.test( this.type ) )
+				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+					jQuery.inArray(this.name, value) >= 0);
+
+			else if ( jQuery.nodeName( this, "select" ) ) {
+				var values = value.constructor == Array ?
+					value :
+					[ value ];
+
+				jQuery( "option", this ).each(function(){
+					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+						jQuery.inArray( this.text, values ) >= 0);
+				});
+
+				if ( !values.length )
+					this.selectedIndex = -1;
+
+			} else
+				this.value = value;
+		});
+	},
+	
+	html: function( value ) {
+		return value == undefined ?
+			(this.length ?
+				this[0].innerHTML :
+				null) :
+			this.empty().append( value );
+	},
+
+	replaceWith: function( value ) {
+		return this.after( value ).remove();
+	},
+
+	eq: function( i ) {
+		return this.slice( i, i + 1 );
+	},
+
+	slice: function() {
+		return this.pushStack( Array.prototype.slice.apply( this, arguments ) );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function(elem, i){
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	},
+
+	data: function( key, value ){
+		var parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value == null ) {
+			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+			
+			if ( data == undefined && this.length )
+				data = jQuery.data( this[0], key );
+
+			return data == null && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+		} else
+			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+				jQuery.data( this, key, value );
+			});
+	},
+
+	removeData: function( key ){
+		return this.each(function(){
+			jQuery.removeData( this, key );
+		});
+	},
+	
+	domManip: function( args, table, reverse, callback ) {
+		var clone = this.length > 1, elems; 
+
+		return this.each(function(){
+			if ( !elems ) {
+				elems = jQuery.clean( args, this.ownerDocument );
+
+				if ( reverse )
+					elems.reverse();
+			}
+
+			var obj = this;
+
+			if ( table && jQuery.nodeName( this, "table" ) && jQuery.nodeName( elems[0], "tr" ) )
+				obj = this.getElementsByTagName("tbody")[0] || this.appendChild( this.ownerDocument.createElement("tbody") );
+
+			var scripts = jQuery( [] );
+
+			jQuery.each(elems, function(){
+				var elem = clone ?
+					jQuery( this ).clone( true )[0] :
+					this;
+
+				// execute all scripts after the elements have been injected
+				if ( jQuery.nodeName( elem, "script" ) ) {
+					scripts = scripts.add( elem );
+				} else {
+					// Remove any inner scripts for later evaluation
+					if ( elem.nodeType == 1 )
+						scripts = scripts.add( jQuery( "script", elem ).remove() );
+
+					// Inject the elements into the document
+					callback.call( obj, elem );
+				}
+			});
+
+			scripts.each( evalScript );
+		});
+	}
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.prototype.init.prototype = jQuery.prototype;
+
+function evalScript( i, elem ) {
+	if ( elem.src )
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+
+	else
+		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+	if ( elem.parentNode )
+		elem.parentNode.removeChild( elem );
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+	// copy reference to target object
+	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+	// Handle a deep copy situation
+	if ( target.constructor == Boolean ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target != "object" && typeof target != "function" )
+		target = {};
+
+	// extend jQuery itself if only one argument is passed
+	if ( length == 1 ) {
+		target = this;
+		i = 0;
+	}
+
+	for ( ; i < length; i++ )
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null )
+			// Extend the base object
+			for ( var name in options ) {
+				// Prevent never-ending loop
+				if ( target === options[ name ] )
+					continue;
+
+				// Recurse if we're merging object values
+				if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType )
+					target[ name ] = jQuery.extend( target[ name ], options[ name ] );
+
+				// Don't bring in undefined values
+				else if ( options[ name ] != undefined )
+					target[ name ] = options[ name ];
+
+			}
+
+	// Return the modified object
+	return target;
+};
+
+var expando = "jQuery" + (new Date()).getTime(), uuid = 0, windowData = {};
+
+// exclude the following css properties to add px
+var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i;
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		window.$ = _$;
+
+		if ( deep )
+			window.jQuery = _jQuery;
+
+		return jQuery;
+	},
+
+	// See test/unit/core.js for details concerning this function.
+	isFunction: function( fn ) {
+		return !!fn && typeof fn != "string" && !fn.nodeName && 
+			fn.constructor != Array && /function/i.test( fn + "" );
+	},
+	
+	// check if an element is in a (or is an) XML document
+	isXMLDoc: function( elem ) {
+		return elem.documentElement && !elem.body ||
+			elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+	},
+
+	// Evalulates a script in a global context
+	globalEval: function( data ) {
+		data = jQuery.trim( data );
+
+		if ( data ) {
+			// Inspired by code by Andrea Giammarchi
+			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+			var head = document.getElementsByTagName("head")[0] || document.documentElement,
+				script = document.createElement("script");
+
+			script.type = "text/javascript";
+			if ( jQuery.browser.msie )
+				script.text = data;
+			else
+				script.appendChild( document.createTextNode( data ) );
+
+			head.appendChild( script );
+			head.removeChild( script );
+		}
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+	},
+	
+	cache: {},
+	
+	data: function( elem, name, data ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// Compute a unique ID for the element
+		if ( !id ) 
+			id = elem[ expando ] = ++uuid;
+
+		// Only generate the data cache if we're
+		// trying to access or manipulate it
+		if ( name && !jQuery.cache[ id ] )
+			jQuery.cache[ id ] = {};
+		
+		// Prevent overriding the named cache with undefined values
+		if ( data != undefined )
+			jQuery.cache[ id ][ name ] = data;
+		
+		// Return the named cache data, or the ID for the element	
+		return name ?
+			jQuery.cache[ id ][ name ] :
+			id;
+	},
+	
+	removeData: function( elem, name ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// If we want to remove a specific section of the element's data
+		if ( name ) {
+			if ( jQuery.cache[ id ] ) {
+				// Remove the section of cache data
+				delete jQuery.cache[ id ][ name ];
+
+				// If we've removed all the data, remove the element's cache
+				name = "";
+
+				for ( name in jQuery.cache[ id ] )
+					break;
+
+				if ( !name )
+					jQuery.removeData( elem );
+			}
+
+		// Otherwise, we want to remove all of the element's data
+		} else {
+			// Clean up the element expando
+			try {
+				delete elem[ expando ];
+			} catch(e){
+				// IE has trouble directly removing the expando
+				// but it's ok with using removeAttribute
+				if ( elem.removeAttribute )
+					elem.removeAttribute( expando );
+			}
+
+			// Completely remove the data cache
+			delete jQuery.cache[ id ];
+		}
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		if ( args ) {
+			if ( object.length == undefined ) {
+				for ( var name in object )
+					if ( callback.apply( object[ name ], args ) === false )
+						break;
+			} else
+				for ( var i = 0, length = object.length; i < length; i++ )
+					if ( callback.apply( object[ i ], args ) === false )
+						break;
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( object.length == undefined ) {
+				for ( var name in object )
+					if ( callback.call( object[ name ], name, object[ name ] ) === false )
+						break;
+			} else
+				for ( var i = 0, length = object.length, value = object[0]; 
+					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+		}
+
+		return object;
+	},
+	
+	prop: function( elem, value, type, i, name ) {
+			// Handle executable functions
+			if ( jQuery.isFunction( value ) )
+				value = value.call( elem, i );
+				
+			// Handle passing in a number to a CSS property
+			return value && value.constructor == Number && type == "curCSS" && !exclude.test( name ) ?
+				value + "px" :
+				value;
+	},
+
+	className: {
+		// internal only, use addClass("class")
+		add: function( elem, classNames ) {
+			jQuery.each((classNames || "").split(/\s+/), function(i, className){
+				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+					elem.className += (elem.className ? " " : "") + className;
+			});
+		},
+
+		// internal only, use removeClass("class")
+		remove: function( elem, classNames ) {
+			if (elem.nodeType == 1)
+				elem.className = classNames != undefined ?
+					jQuery.grep(elem.className.split(/\s+/), function(className){
+						return !jQuery.className.has( classNames, className );	
+					}).join(" ") :
+					"";
+		},
+
+		// internal only, use is(".class")
+		has: function( elem, className ) {
+			return jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( var name in options )
+			elem.style[ name ] = old[ name ];
+	},
+
+	css: function( elem, name, force ) {
+		if ( name == "width" || name == "height" ) {
+			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+		
+			function getWH() {
+				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+				var padding = 0, border = 0;
+				jQuery.each( which, function() {
+					padding += parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+					border += parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+				});
+				val -= Math.round(padding + border);
+			}
+		
+			if ( jQuery(elem).is(":visible") )
+				getWH();
+			else
+				jQuery.swap( elem, props, getWH );
+			
+			return Math.max(0, val);
+		}
+		
+		return jQuery.curCSS( elem, name, force );
+	},
+
+	curCSS: function( elem, name, force ) {
+		var ret;
+
+		// A helper method for determining if an element's values are broken
+		function color( elem ) {
+			if ( !jQuery.browser.safari )
+				return false;
+
+			var ret = document.defaultView.getComputedStyle( elem, null );
+			return !ret || ret.getPropertyValue("color") == "";
+		}
+
+		// We need to handle opacity special in IE
+		if ( name == "opacity" && jQuery.browser.msie ) {
+			ret = jQuery.attr( elem.style, "opacity" );
+
+			return ret == "" ?
+				"1" :
+				ret;
+		}
+		// Opera sometimes will give the wrong display answer, this fixes it, see #2037
+		if ( jQuery.browser.opera && name == "display" ) {
+			var save = elem.style.outline;
+			elem.style.outline = "0 solid black";
+			elem.style.outline = save;
+		}
+		
+		// Make sure we're using the right name for getting the float value
+		if ( name.match( /float/i ) )
+			name = styleFloat;
+
+		if ( !force && elem.style && elem.style[ name ] )
+			ret = elem.style[ name ];
+
+		else if ( document.defaultView && document.defaultView.getComputedStyle ) {
+
+			// Only "float" is needed here
+			if ( name.match( /float/i ) )
+				name = "float";
+
+			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+			var getComputedStyle = document.defaultView.getComputedStyle( elem, null );
+
+			if ( getComputedStyle && !color( elem ) )
+				ret = getComputedStyle.getPropertyValue( name );
+
+			// If the element isn't reporting its values properly in Safari
+			// then some display: none elements are involved
+			else {
+				var swap = [], stack = [];
+
+				// Locate all of the parent display: none elements
+				for ( var a = elem; a && color(a); a = a.parentNode )
+					stack.unshift(a);
+
+				// Go through and make them visible, but in reverse
+				// (It would be better if we knew the exact display type that they had)
+				for ( var i = 0; i < stack.length; i++ )
+					if ( color( stack[ i ] ) ) {
+						swap[ i ] = stack[ i ].style.display;
+						stack[ i ].style.display = "block";
+					}
+
+				// Since we flip the display style, we have to handle that
+				// one special, otherwise get the value
+				ret = name == "display" && swap[ stack.length - 1 ] != null ?
+					"none" :
+					( getComputedStyle && getComputedStyle.getPropertyValue( name ) ) || "";
+
+				// Finally, revert the display styles back
+				for ( var i = 0; i < swap.length; i++ )
+					if ( swap[ i ] != null )
+						stack[ i ].style.display = swap[ i ];
+			}
+
+			// We should always get a number back from opacity
+			if ( name == "opacity" && ret == "" )
+				ret = "1";
+
+		} else if ( elem.currentStyle ) {
+			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+			// From the awesome hack by Dean Edwards
+			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+			// If we're not dealing with a regular pixel number
+			// but a number that has a weird ending, we need to convert it to pixels
+			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+				// Remember the original values
+				var style = elem.style.left, runtimeStyle = elem.runtimeStyle.left;
+
+				// Put in the new values to get a computed value out
+				elem.runtimeStyle.left = elem.currentStyle.left;
+				elem.style.left = ret || 0;
+				ret = elem.style.pixelLeft + "px";
+
+				// Revert the changed values
+				elem.style.left = style;
+				elem.runtimeStyle.left = runtimeStyle;
+			}
+		}
+
+		return ret;
+	},
+	
+	clean: function( elems, context ) {
+		var ret = [];
+		context = context || document;
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if (typeof context.createElement == 'undefined') 
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+		jQuery.each(elems, function(i, elem){
+			if ( !elem )
+				return;
+
+			if ( elem.constructor == Number )
+				elem = elem.toString();
+			
+			// Convert html string into DOM nodes
+			if ( typeof elem == "string" ) {
+				// Fix "XHTML"-style tags in all browsers
+				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+						all :
+						front + "></" + tag + ">";
+				});
+
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var tags = jQuery.trim( elem ).toLowerCase(), div = context.createElement("div");
+
+				var wrap =
+					// option or optgroup
+					!tags.indexOf("<opt") &&
+					[ 1, "<select multiple='multiple'>", "</select>" ] ||
+					
+					!tags.indexOf("<leg") &&
+					[ 1, "<fieldset>", "</fieldset>" ] ||
+					
+					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+					[ 1, "<table>", "</table>" ] ||
+					
+					!tags.indexOf("<tr") &&
+					[ 2, "<table><tbody>", "</tbody></table>" ] ||
+					
+				 	// <thead> matched above
+					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+					
+					!tags.indexOf("<col") &&
+					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+					// IE can't serialize <link> and <script> tags normally
+					jQuery.browser.msie &&
+					[ 1, "div<div>", "</div>" ] ||
+					
+					[ 0, "", "" ];
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + elem + wrap[2];
+				
+				// Move to the right depth
+				while ( wrap[0]-- )
+					div = div.lastChild;
+				
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( jQuery.browser.msie ) {
+					
+					// String was a <table>, *may* have spurious <tbody>
+					var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+						div.firstChild && div.firstChild.childNodes :
+						
+						// String was a bare <thead> or <tfoot>
+						wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+							div.childNodes :
+							[];
+				
+					for ( var j = tbody.length - 1; j >= 0 ; --j )
+						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+							tbody[ j ].parentNode.removeChild( tbody[ j ] );
+					
+					// IE completely kills leading whitespace when innerHTML is used	
+					if ( /^\s/.test( elem ) )	
+						div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+				
+				}
+				
+				elem = jQuery.makeArray( div.childNodes );
+			}
+
+			if ( elem.length === 0 && (!jQuery.nodeName( elem, "form" ) && !jQuery.nodeName( elem, "select" )) )
+				return;
+
+			if ( elem[0] == undefined || jQuery.nodeName( elem, "form" ) || elem.options )
+				ret.push( elem );
+
+			else
+				ret = jQuery.merge( ret, elem );
+
+		});
+
+		return ret;
+	},
+	
+	attr: function( elem, name, value ) {
+		// don't set attributes on text and comment nodes
+		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+			return undefined;
+
+		var fix = jQuery.isXMLDoc( elem ) ?
+			{} :
+			jQuery.props;
+
+		// Safari mis-reports the default selected property of a hidden option
+		// Accessing the parent's selectedIndex property fixes it
+		if ( name == "selected" && jQuery.browser.safari )
+			elem.parentNode.selectedIndex;
+		
+		// Certain attributes only work when accessed via the old DOM 0 way
+		if ( fix[ name ] ) {
+			if ( value != undefined )
+				elem[ fix[ name ] ] = value;
+
+			return elem[ fix[ name ] ];
+
+		} else if ( jQuery.browser.msie && name == "style" )
+			return jQuery.attr( elem.style, "cssText", value );
+
+		else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName( elem, "form" ) && (name == "action" || name == "method") )
+			return elem.getAttributeNode( name ).nodeValue;
+
+		// IE elem.getAttribute passes even for style
+		else if ( elem.tagName ) {
+
+			if ( value != undefined ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+					throw "type property can't be changed";
+
+				// convert the value to a string (all browsers do this but IE) see #1070
+				elem.setAttribute( name, "" + value );
+			}
+
+			if ( jQuery.browser.msie && /href|src/.test( name ) && !jQuery.isXMLDoc( elem ) ) 
+				return elem.getAttribute( name, 2 );
+
+			return elem.getAttribute( name );
+
+		// elem is actually elem.style ... set the style
+		} else {
+			// IE actually uses filters for opacity
+			if ( name == "opacity" && jQuery.browser.msie ) {
+				if ( value != undefined ) {
+					// IE has trouble with opacity if it does not have layout
+					// Force it by setting the zoom level
+					elem.zoom = 1; 
+	
+					// Set the alpha filter to set the opacity
+					elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+						(parseFloat( value ).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+				}
+	
+				return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+					(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() :
+					"";
+			}
+
+			name = name.replace(/-([a-z])/ig, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			if ( value != undefined )
+				elem[ name ] = value;
+
+			return elem[ name ];
+		}
+	},
+	
+	trim: function( text ) {
+		return (text || "").replace( /^\s+|\s+$/g, "" );
+	},
+
+	makeArray: function( array ) {
+		var ret = [];
+
+		// Need to use typeof to fight Safari childNodes crashes
+		if ( typeof array != "array" )
+			for ( var i = 0, length = array.length; i < length; i++ )
+				ret.push( array[ i ] );
+		else
+			ret = array.slice( 0 );
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+		for ( var i = 0, length = array.length; i < length; i++ )
+			if ( array[ i ] == elem )
+				return i;
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		// We have to loop this way because IE & Opera overwrite the length
+		// expando of getElementsByTagName
+
+		// Also, we need to make sure that the correct elements are being returned
+		// (IE returns comment nodes in a '*' query)
+		if ( jQuery.browser.msie ) {
+			for ( var i = 0; second[ i ]; i++ )
+				if ( second[ i ].nodeType != 8 )
+					first.push( second[ i ] );
+
+		} else
+			for ( var i = 0; second[ i ]; i++ )
+				first.push( second[ i ] );
+
+		return first;
+	},
+
+	unique: function( array ) {
+		var ret = [], done = {};
+
+		try {
+
+			for ( var i = 0, length = array.length; i < length; i++ ) {
+				var id = jQuery.data( array[ i ] );
+
+				if ( !done[ id ] ) {
+					done[ id ] = true;
+					ret.push( array[ i ] );
+				}
+			}
+
+		} catch( e ) {
+			ret = array;
+		}
+
+		return ret;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [];
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ )
+			if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
+				ret.push( elems[ i ] );
+
+		return ret;
+	},
+
+	map: function( elems, callback ) {
+		var ret = [];
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			var value = callback( elems[ i ], i );
+
+			if ( value !== null && value != undefined ) {
+				if ( value.constructor != Array )
+					value = [ value ];
+
+				ret = ret.concat( value );
+			}
+		}
+
+		return ret;
+	}
+});
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1],
+	safari: /webkit/.test( userAgent ),
+	opera: /opera/.test( userAgent ),
+	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+var styleFloat = jQuery.browser.msie ?
+	"styleFloat" :
+	"cssFloat";
+	
+jQuery.extend({
+	// Check to see if the W3C box model is being used
+	boxModel: !jQuery.browser.msie || document.compatMode == "CSS1Compat",
+	
+	props: {
+		"for": "htmlFor",
+		"class": "className",
+		"float": styleFloat,
+		cssFloat: styleFloat,
+		styleFloat: styleFloat,
+		innerHTML: "innerHTML",
+		className: "className",
+		value: "value",
+		disabled: "disabled",
+		checked: "checked",
+		readonly: "readOnly",
+		selected: "selected",
+		maxlength: "maxLength",
+		selectedIndex: "selectedIndex",
+		defaultValue: "defaultValue",
+		tagName: "tagName",
+		nodeName: "nodeName"
+	}
+});
+
+jQuery.each({
+	parent: function(elem){return elem.parentNode;},
+	parents: function(elem){return jQuery.dir(elem,"parentNode");},
+	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+	children: function(elem){return jQuery.sibling(elem.firstChild);},
+	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = jQuery.map( this, fn );
+
+		if ( selector && typeof selector == "string" )
+			ret = jQuery.multiFilter( selector, ret );
+
+		return this.pushStack( jQuery.unique( ret ) );
+	};
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function(name, original){
+	jQuery.fn[ name ] = function() {
+		var args = arguments;
+
+		return this.each(function(){
+			for ( var i = 0, length = args.length; i < length; i++ )
+				jQuery( args[ i ] )[ original ]( this );
+		});
+	};
+});
+
+jQuery.each({
+	removeAttr: function( name ) {
+		jQuery.attr( this, name, "" );
+		if (this.nodeType == 1) 
+			this.removeAttribute( name );
+	},
+
+	addClass: function( classNames ) {
+		jQuery.className.add( this, classNames );
+	},
+
+	removeClass: function( classNames ) {
+		jQuery.className.remove( this, classNames );
+	},
+
+	toggleClass: function( classNames ) {
+		jQuery.className[ jQuery.className.has( this, classNames ) ? "remove" : "add" ]( this, classNames );
+	},
+
+	remove: function( selector ) {
+		if ( !selector || jQuery.filter( selector, [ this ] ).r.length ) {
+			// Prevent memory leaks
+			jQuery( "*", this ).add(this).each(function(){
+				jQuery.event.remove(this);
+				jQuery.removeData(this);
+			});
+			if (this.parentNode)
+				this.parentNode.removeChild( this );
+		}
+	},
+
+	empty: function() {
+		// Remove element nodes and prevent memory leaks
+		jQuery( ">*", this ).remove();
+		
+		// Remove any remaining nodes
+		while ( this.firstChild )
+			this.removeChild( this.firstChild );
+	}
+}, function(name, fn){
+	jQuery.fn[ name ] = function(){
+		return this.each( fn, arguments );
+	};
+});
+
+jQuery.each([ "Height", "Width" ], function(i, name){
+	var type = name.toLowerCase();
+	
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		return this[0] == window ?
+			// Opera reports document.body.client[Width/Height] properly in both quirks and standards
+			jQuery.browser.opera && document.body[ "client" + name ] || 
+			
+			// Safari reports inner[Width/Height] just fine (Mozilla and Opera include scroll bar widths)
+			jQuery.browser.safari && window[ "inner" + name ] ||
+			
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] || document.body[ "client" + name ] :
+		
+			// Get document width or height
+			this[0] == document ?
+				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+				Math.max( 
+					Math.max(document.body["scroll" + name], document.documentElement["scroll" + name]), 
+					Math.max(document.body["offset" + name], document.documentElement["offset" + name]) 
+				) :
+
+				// Get or set width or height on the element
+				size == undefined ?
+					// Get width or height on the element
+					(this.length ? jQuery.css( this[0], type ) : null) :
+
+					// Set the width or height on the element (default to pixels if value is unitless)
+					this.css( type, size.constructor == String ? size : size + "px" );
+	};
+});
+
+var chars = jQuery.browser.safari && parseInt(jQuery.browser.version) < 417 ?
+		"(?:[\\w*_-]|\\\\.)" :
+		"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",
+	quickChild = new RegExp("^>\\s*(" + chars + "+)"),
+	quickID = new RegExp("^(" + chars + "+)(#)(" + chars + "+)"),
+	quickClass = new RegExp("^([#.]?)(" + chars + "*)");
+
+jQuery.extend({
+	expr: {
+		"": function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},
+		"#": function(a,i,m){return a.getAttribute("id")==m[2];},
+		":": {
+			// Position Checks
+			lt: function(a,i,m){return i<m[3]-0;},
+			gt: function(a,i,m){return i>m[3]-0;},
+			nth: function(a,i,m){return m[3]-0==i;},
+			eq: function(a,i,m){return m[3]-0==i;},
+			first: function(a,i){return i==0;},
+			last: function(a,i,m,r){return i==r.length-1;},
+			even: function(a,i){return i%2==0;},
+			odd: function(a,i){return i%2;},
+
+			// Child Checks
+			"first-child": function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},
+			"last-child": function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},
+			"only-child": function(a){return !jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},
+
+			// Parent Checks
+			parent: function(a){return a.firstChild;},
+			empty: function(a){return !a.firstChild;},
+
+			// Text Check
+			contains: function(a,i,m){return (a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},
+
+			// Visibility
+			visible: function(a){return "hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},
+			hidden: function(a){return "hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},
+
+			// Form attributes
+			enabled: function(a){return !a.disabled;},
+			disabled: function(a){return a.disabled;},
+			checked: function(a){return a.checked;},
+			selected: function(a){return a.selected||jQuery.attr(a,"selected");},
+
+			// Form elements
+			text: function(a){return "text"==a.type;},
+			radio: function(a){return "radio"==a.type;},
+			checkbox: function(a){return "checkbox"==a.type;},
+			file: function(a){return "file"==a.type;},
+			password: function(a){return "password"==a.type;},
+			submit: function(a){return "submit"==a.type;},
+			image: function(a){return "image"==a.type;},
+			reset: function(a){return "reset"==a.type;},
+			button: function(a){return "button"==a.type||jQuery.nodeName(a,"button");},
+			input: function(a){return /input|select|textarea|button/i.test(a.nodeName);},
+
+			// :has()
+			has: function(a,i,m){return jQuery.find(m[3],a).length;},
+
+			// :header
+			header: function(a){return /h\d/i.test(a.nodeName);},
+
+			// :animated
+			animated: function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}
+		}
+	},
+	
+	// The regular expressions that power the parsing engine
+	parse: [
+		// Match: [@value='test'], [@foo]
+		/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+		// Match: :contains('foo')
+		/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+		// Match: :even, :last-chlid, #id, .class
+		new RegExp("^([:.#]*)(" + chars + "+)")
+	],
+
+	multiFilter: function( expr, elems, not ) {
+		var old, cur = [];
+
+		while ( expr && expr != old ) {
+			old = expr;
+			var f = jQuery.filter( expr, elems, not );
+			expr = f.t.replace(/^\s*,\s*/, "" );
+			cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+		}
+
+		return cur;
+	},
+
+	find: function( t, context ) {
+		// Quickly handle non-string expressions
+		if ( typeof t != "string" )
+			return [ t ];
+
+		// check to make sure context is a DOM element or a document
+		if ( context && context.nodeType != 1 && context.nodeType != 9)
+			return [ ];
+
+		// Set the correct context (if none is provided)
+		context = context || document;
+
+		// Initialize the search
+		var ret = [context], done = [], last, nodeName;
+
+		// Continue while a selector expression exists, and while
+		// we're no longer looping upon ourselves
+		while ( t && last != t ) {
+			var r = [];
+			last = t;
+
+			t = jQuery.trim(t);
+
+			var foundToken = false;
+
+			// An attempt at speeding up child selectors that
+			// point to a specific element tag
+			var re = quickChild;
+			var m = re.exec(t);
+
+			if ( m ) {
+				nodeName = m[1].toUpperCase();
+
+				// Perform our own iteration and filter
+				for ( var i = 0; ret[i]; i++ )
+					for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+						if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) )
+							r.push( c );
+
+				ret = r;
+				t = t.replace( re, "" );
+				if ( t.indexOf(" ") == 0 ) continue;
+				foundToken = true;
+			} else {
+				re = /^([>+~])\s*(\w*)/i;
+
+				if ( (m = re.exec(t)) != null ) {
+					r = [];
+
+					var merge = {};
+					nodeName = m[2].toUpperCase();
+					m = m[1];
+
+					for ( var j = 0, rl = ret.length; j < rl; j++ ) {
+						var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+						for ( ; n; n = n.nextSibling )
+							if ( n.nodeType == 1 ) {
+								var id = jQuery.data(n);
+
+								if ( m == "~" && merge[id] ) break;
+								
+								if (!nodeName || n.nodeName.toUpperCase() == nodeName ) {
+									if ( m == "~" ) merge[id] = true;
+									r.push( n );
+								}
+								
+								if ( m == "+" ) break;
+							}
+					}
+
+					ret = r;
+
+					// And remove the token
+					t = jQuery.trim( t.replace( re, "" ) );
+					foundToken = true;
+				}
+			}
+
+			// See if there's still an expression, and that we haven't already
+			// matched a token
+			if ( t && !foundToken ) {
+				// Handle multiple expressions
+				if ( !t.indexOf(",") ) {
+					// Clean the result set
+					if ( context == ret[0] ) ret.shift();
+
+					// Merge the result sets
+					done = jQuery.merge( done, ret );
+
+					// Reset the context
+					r = ret = [context];
+
+					// Touch up the selector string
+					t = " " + t.substr(1,t.length);
+
+				} else {
+					// Optimize for the case nodeName#idName
+					var re2 = quickID;
+					var m = re2.exec(t);
+					
+					// Re-organize the results, so that they're consistent
+					if ( m ) {
+						m = [ 0, m[2], m[3], m[1] ];
+
+					} else {
+						// Otherwise, do a traditional filter check for
+						// ID, class, and element selectors
+						re2 = quickClass;
+						m = re2.exec(t);
+					}
+
+					m[2] = m[2].replace(/\\/g, "");
+
+					var elem = ret[ret.length-1];
+
+					// Try to do a global search by ID, where we can
+					if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
+						// Optimization for HTML document case
+						var oid = elem.getElementById(m[2]);
+						
+						// Do a quick check for the existence of the actual ID attribute
+						// to avoid selecting by the name attribute in IE
+						// also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+						if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+							oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+						// Do a quick check for node name (where applicable) so
+						// that div#foo searches will be really fast
+						ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+					} else {
+						// We need to find all descendant elements
+						for ( var i = 0; ret[i]; i++ ) {
+							// Grab the tag name being searched for
+							var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2];
+
+							// Handle IE7 being really dumb about <object>s
+							if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+								tag = "param";
+
+							r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+						}
+
+						// It's faster to filter by class and be done with it
+						if ( m[1] == "." )
+							r = jQuery.classFilter( r, m[2] );
+
+						// Same with ID filtering
+						if ( m[1] == "#" ) {
+							var tmp = [];
+
+							// Try to find the element with the ID
+							for ( var i = 0; r[i]; i++ )
+								if ( r[i].getAttribute("id") == m[2] ) {
+									tmp = [ r[i] ];
+									break;
+								}
+
+							r = tmp;
+						}
+
+						ret = r;
+					}
+
+					t = t.replace( re2, "" );
+				}
+
+			}
+
+			// If a selector string still exists
+			if ( t ) {
+				// Attempt to filter it
+				var val = jQuery.filter(t,r);
+				ret = r = val.r;
+				t = jQuery.trim(val.t);
+			}
+		}
+
+		// An error occurred with the selector;
+		// just return an empty set instead
+		if ( t )
+			ret = [];
+
+		// Remove the root context
+		if ( ret && context == ret[0] )
+			ret.shift();
+
+		// And combine the results
+		done = jQuery.merge( done, ret );
+
+		return done;
+	},
+
+	classFilter: function(r,m,not){
+		m = " " + m + " ";
+		var tmp = [];
+		for ( var i = 0; r[i]; i++ ) {
+			var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+			if ( !not && pass || not && !pass )
+				tmp.push( r[i] );
+		}
+		return tmp;
+	},
+
+	filter: function(t,r,not) {
+		var last;
+
+		// Look for common filter expressions
+		while ( t && t != last ) {
+			last = t;
+
+			var p = jQuery.parse, m;
+
+			for ( var i = 0; p[i]; i++ ) {
+				m = p[i].exec( t );
+
+				if ( m ) {
+					// Remove what we just matched
+					t = t.substring( m[0].length );
+
+					m[2] = m[2].replace(/\\/g, "");
+					break;
+				}
+			}
+
+			if ( !m )
+				break;
+
+			// :not() is a special case that can be optimized by
+			// keeping it out of the expression list
+			if ( m[1] == ":" && m[2] == "not" )
+				// optimize if only one selector found (most common case)
+				r = isSimple.test( m[3] ) ?
+					jQuery.filter(m[3], r, true).r :
+					jQuery( r ).not( m[3] );
+
+			// We can get a big speed boost by filtering by class here
+			else if ( m[1] == "." )
+				r = jQuery.classFilter(r, m[2], not);
+
+			else if ( m[1] == "[" ) {
+				var tmp = [], type = m[3];
+				
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+					
+					if ( z == null || /href|src|selected/.test(m[2]) )
+						z = jQuery.attr(a,m[2]) || '';
+
+					if ( (type == "" && !!z ||
+						 type == "=" && z == m[5] ||
+						 type == "!=" && z != m[5] ||
+						 type == "^=" && z && !z.indexOf(m[5]) ||
+						 type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+						 (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+							tmp.push( a );
+				}
+				
+				r = tmp;
+
+			// We can get a speed boost by handling nth-child here
+			} else if ( m[1] == ":" && m[2] == "nth-child" ) {
+				var merge = {}, tmp = [],
+					// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+					test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+						m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+						!/\D/.test(m[3]) && "0n+" + m[3] || m[3]),
+					// calculate the numbers (first)n+(last) including if they are negative
+					first = (test[1] + (test[2] || 1)) - 0, last = test[3] - 0;
+ 
+				// loop through all the elements left in the jQuery object
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var node = r[i], parentNode = node.parentNode, id = jQuery.data(parentNode);
+
+					if ( !merge[id] ) {
+						var c = 1;
+
+						for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+							if ( n.nodeType == 1 )
+								n.nodeIndex = c++;
+
+						merge[id] = true;
+					}
+
+					var add = false;
+
+					if ( first == 0 ) {
+						if ( node.nodeIndex == last )
+							add = true;
+					} else if ( (node.nodeIndex - last) % first == 0 && (node.nodeIndex - last) / first >= 0 )
+						add = true;
+
+					if ( add ^ not )
+						tmp.push( node );
+				}
+
+				r = tmp;
+
+			// Otherwise, find the expression to execute
+			} else {
+				var fn = jQuery.expr[ m[1] ];
+				if ( typeof fn == "object" )
+					fn = fn[ m[2] ];
+
+				if ( typeof fn == "string" )
+					fn = eval("false||function(a,i){return " + fn + ";}");
+
+				// Execute it against the current filter
+				r = jQuery.grep( r, function(elem, i){
+					return fn(elem, i, m, r);
+				}, not );
+			}
+		}
+
+		// Return an array of filtered elements (r)
+		// and the modified expression string (t)
+		return { r: r, t: t };
+	},
+
+	dir: function( elem, dir ){
+		var matched = [];
+		var cur = elem[dir];
+		while ( cur && cur != document ) {
+			if ( cur.nodeType == 1 )
+				matched.push( cur );
+			cur = cur[dir];
+		}
+		return matched;
+	},
+	
+	nth: function(cur,result,dir,elem){
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] )
+			if ( cur.nodeType == 1 && ++num == result )
+				break;
+
+		return cur;
+	},
+	
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType == 1 && (!elem || n != elem) )
+				r.push( n );
+		}
+
+		return r;
+	}
+});
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from 
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function(elem, types, handler, data) {
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		// For whatever reason, IE has trouble passing the window object
+		// around, causing it to be cloned in the process
+		if ( jQuery.browser.msie && elem.setInterval != undefined )
+			elem = window;
+
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid )
+			handler.guid = this.guid++;
+			
+		// if data is passed, bind to handler 
+		if( data != undefined ) { 
+			// Create temporary function pointer to original handler 
+			var fn = handler; 
+
+			// Create unique handler function, wrapped around original handler 
+			handler = function() { 
+				// Pass arguments and context to original handler 
+				return fn.apply(this, arguments); 
+			};
+
+			// Store data in unique handler 
+			handler.data = data;
+
+			// Set the guid of unique handler to the same of original handler, so it can be removed 
+			handler.guid = fn.guid;
+		}
+
+		// Init the element's event structure
+		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+				// returned undefined or false
+				var val;
+
+				// Handle the second event of a trigger and when
+				// an event is called after a page has unloaded
+				if ( typeof jQuery == "undefined" || jQuery.event.triggered )
+					return val;
+		
+				val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
+		
+				return val;
+			});
+		// Add elem as a property of the handle function
+		// This is to prevent a memory leak with non-native
+		// event in IE.
+		handle.elem = elem;
+			
+			// Handle multiple events seperated by a space
+			// jQuery(...).bind("mouseover mouseout", fn);
+			jQuery.each(types.split(/\s+/), function(index, type) {
+				// Namespaced event handlers
+				var parts = type.split(".");
+				type = parts[0];
+				handler.type = parts[1];
+
+				// Get the current list of functions bound to this event
+				var handlers = events[type];
+
+				// Init the event handler queue
+				if (!handlers) {
+					handlers = events[type] = {};
+		
+					// Check for a special event handler
+					// Only use addEventListener/attachEvent if the special
+					// events handler returns false
+					if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
+						// Bind the global event handler to the element
+						if (elem.addEventListener)
+							elem.addEventListener(type, handle, false);
+						else if (elem.attachEvent)
+							elem.attachEvent("on" + type, handle);
+					}
+				}
+
+				// Add the function to the element's handler list
+				handlers[handler.guid] = handler;
+
+				// Keep track of which events have been used, for global triggering
+				jQuery.event.global[type] = true;
+			});
+		
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	guid: 1,
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function(elem, types, handler) {
+		// don't do events on text and comment nodes
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		var events = jQuery.data(elem, "events"), ret, index;
+
+		if ( events ) {
+			// Unbind all events for the element
+			if ( types == undefined || (typeof types == "string" && types.charAt(0) == ".") )
+				for ( var type in events )
+					this.remove( elem, type + (types || "") );
+			else {
+				// types is actually an event object here
+				if ( types.type ) {
+					handler = types.handler;
+					types = types.type;
+				}
+				
+				// Handle multiple events seperated by a space
+				// jQuery(...).unbind("mouseover mouseout", fn);
+				jQuery.each(types.split(/\s+/), function(index, type){
+					// Namespaced event handlers
+					var parts = type.split(".");
+					type = parts[0];
+					
+					if ( events[type] ) {
+						// remove the given handler for the given type
+						if ( handler )
+							delete events[type][handler.guid];
+			
+						// remove all handlers for the given type
+						else
+							for ( handler in events[type] )
+								// Handle the removal of namespaced events
+								if ( !parts[1] || events[type][handler].type == parts[1] )
+									delete events[type][handler];
+
+						// remove generic event handler if no more handlers exist
+						for ( ret in events[type] ) break;
+						if ( !ret ) {
+							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
+								if (elem.removeEventListener)
+									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+								else if (elem.detachEvent)
+									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+							}
+							ret = null;
+							delete events[type];
+						}
+					}
+				});
+			}
+
+			// Remove the expando if it's no longer used
+			for ( ret in events ) break;
+			if ( !ret ) {
+				var handle = jQuery.data( elem, "handle" );
+				if ( handle ) handle.elem = null;
+				jQuery.removeData( elem, "events" );
+				jQuery.removeData( elem, "handle" );
+			}
+		}
+	},
+
+	trigger: function(type, data, elem, donative, extra) {
+		// Clone the incoming data, if any
+		data = jQuery.makeArray(data || []);
+
+		if ( type.indexOf("!") >= 0 ) {
+			type = type.slice(0, -1);
+			var exclusive = true;
+		}
+
+		// Handle a global trigger
+		if ( !elem ) {
+			// Only trigger if we've ever bound an event for it
+			if ( this.global[type] )
+				jQuery("*").add([window, document]).trigger(type, data);
+
+		// Handle triggering a single element
+		} else {
+			// don't do events on text and comment nodes
+			if ( elem.nodeType == 3 || elem.nodeType == 8 )
+				return undefined;
+
+			var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),
+				// Check to see if we need to provide a fake event, or not
+				event = !data[0] || !data[0].preventDefault;
+			
+			// Pass along a fake event
+			if ( event )
+				data.unshift( this.fix({ type: type, target: elem }) );
+
+			// Enforce the right trigger type
+			data[0].type = type;
+			if ( exclusive )
+				data[0].exclusive = true;
+
+			// Trigger the event
+			if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
+				val = jQuery.data(elem, "handle").apply( elem, data );
+
+			// Handle triggering native .onfoo handlers
+			if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+				val = false;
+
+			// Extra functions don't get the custom event object
+			if ( event )
+				data.shift();
+
+			// Handle triggering of extra function
+			if ( extra && jQuery.isFunction( extra ) ) {
+				// call the extra function and tack the current return value on the end for possible inspection
+				ret = extra.apply( elem, val == null ? data : data.concat( val ) );
+				// if anything is returned, give it precedence and have it overwrite the previous value
+				if (ret !== undefined)
+					val = ret;
+			}
+
+			// Trigger the native events (except for clicks on links)
+			if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+				this.triggered = true;
+				try {
+					elem[ type ]();
+				// prevent IE from throwing an error for some hidden elements
+				} catch (e) {}
+			}
+
+			this.triggered = false;
+		}
+
+		return val;
+	},
+
+	handle: function(event) {
+		// returned undefined or false
+		var val;
+
+		// Empty object is for triggered events with no data
+		event = jQuery.event.fix( event || window.event || {} ); 
+
+		// Namespaced event handlers
+		var parts = event.type.split(".");
+		event.type = parts[0];
+
+		var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], args = Array.prototype.slice.call( arguments, 1 );
+		args.unshift( event );
+
+		for ( var j in handlers ) {
+			var handler = handlers[j];
+			// Pass in a reference to the handler function itself
+			// So that we can later remove it
+			args[0].handler = handler;
+			args[0].data = handler.data;
+
+			// Filter the functions by class
+			if ( !parts[1] && !event.exclusive || handler.type == parts[1] ) {
+				var ret = handler.apply( this, args );
+
+				if ( val !== false )
+					val = ret;
+
+				if ( ret === false ) {
+					event.preventDefault();
+					event.stopPropagation();
+				}
+			}
+		}
+
+		// Clean up added properties in IE to prevent memory leak
+		if (jQuery.browser.msie)
+			event.target = event.preventDefault = event.stopPropagation =
+				event.handler = event.data = null;
+
+		return val;
+	},
+
+	fix: function(event) {
+		// store a copy of the original event object 
+		// and clone to set read-only properties
+		var originalEvent = event;
+		event = jQuery.extend({}, originalEvent);
+		
+		// add preventDefault and stopPropagation since 
+		// they will not work on the clone
+		event.preventDefault = function() {
+			// if preventDefault exists run it on the original event
+			if (originalEvent.preventDefault)
+				originalEvent.preventDefault();
+			// otherwise set the returnValue property of the original event to false (IE)
+			originalEvent.returnValue = false;
+		};
+		event.stopPropagation = function() {
+			// if stopPropagation exists run it on the original event
+			if (originalEvent.stopPropagation)
+				originalEvent.stopPropagation();
+			// otherwise set the cancelBubble property of the original event to true (IE)
+			originalEvent.cancelBubble = true;
+		};
+		
+		// Fix target property, if necessary
+		if ( !event.target )
+			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+				
+		// check if target is a textnode (safari)
+		if ( event.target.nodeType == 3 )
+			event.target = originalEvent.target.parentNode;
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement )
+			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var doc = document.documentElement, body = document.body;
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+		}
+			
+		// Add which for key events
+		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+			event.which = event.charCode || event.keyCode;
+		
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey )
+			event.metaKey = event.ctrlKey;
+
+		// Add which for click: 1 == left; 2 == middle; 3 == right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button )
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+			
+		return event;
+	},
+	
+	special: {
+		ready: {
+			setup: function() {
+				// Make sure the ready event is setup
+				bindReady();
+				return;
+			},
+			
+			teardown: function() { return; }
+		},
+		
+		mouseenter: {
+			setup: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
+				return true;
+			},
+		
+			teardown: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
+				return true;
+			},
+			
+			handler: function(event) {
+				// If we actually just moused on to a sub-element, ignore it
+				if ( withinElement(event, this) ) return true;
+				// Execute the right handlers by setting the event type to mouseenter
+				arguments[0].type = "mouseenter";
+				return jQuery.event.handle.apply(this, arguments);
+			}
+		},
+	
+		mouseleave: {
+			setup: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
+				return true;
+			},
+		
+			teardown: function() {
+				if ( jQuery.browser.msie ) return false;
+				jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
+				return true;
+			},
+			
+			handler: function(event) {
+				// If we actually just moused on to a sub-element, ignore it
+				if ( withinElement(event, this) ) return true;
+				// Execute the right handlers by setting the event type to mouseleave
+				arguments[0].type = "mouseleave";
+				return jQuery.event.handle.apply(this, arguments);
+			}
+		}
+	}
+};
+
+jQuery.fn.extend({
+	bind: function( type, data, fn ) {
+		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+			jQuery.event.add( this, type, fn || data, fn && data );
+		});
+	},
+	
+	one: function( type, data, fn ) {
+		return this.each(function(){
+			jQuery.event.add( this, type, function(event) {
+				jQuery(this).unbind(event);
+				return (fn || data).apply( this, arguments);
+			}, fn && data);
+		});
+	},
+
+	unbind: function( type, fn ) {
+		return this.each(function(){
+			jQuery.event.remove( this, type, fn );
+		});
+	},
+
+	trigger: function( type, data, fn ) {
+		return this.each(function(){
+			jQuery.event.trigger( type, data, this, true, fn );
+		});
+	},
+
+	triggerHandler: function( type, data, fn ) {
+		if ( this[0] )
+			return jQuery.event.trigger( type, data, this[0], false, fn );
+		return undefined;
+	},
+
+	toggle: function() {
+		// Save reference to arguments for access in closure
+		var args = arguments;
+
+		return this.click(function(event) {
+			// Figure out which function to execute
+			this.lastToggle = 0 == this.lastToggle ? 1 : 0;
+			
+			// Make sure that clicks stop
+			event.preventDefault();
+			
+			// and execute the function
+			return args[this.lastToggle].apply( this, arguments ) || false;
+		});
+	},
+
+	hover: function(fnOver, fnOut) {
+		return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
+	},
+	
+	ready: function(fn) {
+		// Attach the listeners
+		bindReady();
+
+		// If the DOM is already ready
+		if ( jQuery.isReady )
+			// Execute the function immediately
+			fn.call( document, jQuery );
+			
+		// Otherwise, remember the function for later
+		else
+			// Add the function to the wait list
+			jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
+	
+		return this;
+	}
+});
+
+jQuery.extend({
+	isReady: false,
+	readyList: [],
+	// Handle when the DOM is ready
+	ready: function() {
+		// Make sure that the DOM is not already loaded
+		if ( !jQuery.isReady ) {
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+			
+			// If there are functions bound, to execute
+			if ( jQuery.readyList ) {
+				// Execute all of them
+				jQuery.each( jQuery.readyList, function(){
+					this.apply( document );
+				});
+				
+				// Reset the list of functions
+				jQuery.readyList = null;
+			}
+		
+			// Trigger any bound ready events
+			jQuery(document).triggerHandler("ready");
+		}
+	}
+});
+
+var readyBound = false;
+
+function bindReady(){
+	if ( readyBound ) return;
+	readyBound = true;
+
+	// Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
+	if ( document.addEventListener && !jQuery.browser.opera)
+		// Use the handy event callback
+		document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+	
+	// If IE is used and is not in a frame
+	// Continually check to see if the document is ready
+	if ( jQuery.browser.msie && window == top ) (function(){
+		if (jQuery.isReady) return;
+		try {
+			// If IE is used, use the trick by Diego Perini
+			// http://javascript.nwbox.com/IEContentLoaded/
+			document.documentElement.doScroll("left");
+		} catch( error ) {
+			setTimeout( arguments.callee, 0 );
+			return;
+		}
+		// and execute any waiting functions
+		jQuery.ready();
+	})();
+
+	if ( jQuery.browser.opera )
+		document.addEventListener( "DOMContentLoaded", function () {
+			if (jQuery.isReady) return;
+			for (var i = 0; i < document.styleSheets.length; i++)
+				if (document.styleSheets[i].disabled) {
+					setTimeout( arguments.callee, 0 );
+					return;
+				}
+			// and execute any waiting functions
+			jQuery.ready();
+		}, false);
+
+	if ( jQuery.browser.safari ) {
+		var numStyles;
+		(function(){
+			if (jQuery.isReady) return;
+			if ( document.readyState != "loaded" && document.readyState != "complete" ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+			if ( numStyles === undefined )
+				numStyles = jQuery("style, link[rel=stylesheet]").length;
+			if ( document.styleSheets.length != numStyles ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+			// and execute any waiting functions
+			jQuery.ready();
+		})();
+	}
+
+	// A fallback to window.onload, that will always work
+	jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+	"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + 
+	"submit,keydown,keypress,keyup,error").split(","), function(i, name){
+	
+	// Handle event binding
+	jQuery.fn[name] = function(fn){
+		return fn ? this.bind(name, fn) : this.trigger(name);
+	};
+});
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event, elem) {
+	// Check if mouse(over|out) are still within the same parent element
+	var parent = event.relatedTarget;
+	// Traverse up the tree
+	while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
+	// Return true if we actually just moused on to a sub-element
+	return parent == elem;
+};
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery(window).bind("unload", function() {
+	jQuery("*").add(document).unbind();
+});
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( jQuery.isFunction( url ) )
+			return this.bind("load", url);
+
+		var off = url.indexOf(" ");
+		if ( off >= 0 ) {
+			var selector = url.slice(off, url.length);
+			url = url.slice(0, off);
+		}
+
+		callback = callback || function(){};
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params )
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else {
+				params = jQuery.param( params );
+				type = "POST";
+			}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			complete: function(res, status){
+				// If successful, inject the HTML into all the matched elements
+				if ( status == "success" || status == "notmodified" )
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div/>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						res.responseText );
+
+				self.each( callback, [res.responseText, status, res] );
+			}
+		});
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param(this.serializeArray());
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			return jQuery.nodeName(this, "form") ?
+				jQuery.makeArray(this.elements) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled && 
+				(this.checked || /select|textarea/i.test(this.nodeName) || 
+					/text|hidden|password/i.test(this.type));
+		})
+		.map(function(i, elem){
+			var val = jQuery(this).val();
+			return val == null ? null :
+				val.constructor == Array ?
+					jQuery.map( val, function(val, i){
+						return {name: elem.name, value: val};
+					}) :
+					{name: elem.name, value: val};
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+	jQuery.fn[o] = function(f){
+		return this.bind(o, f);
+	};
+});
+
+var jsc = (new Date).getTime();
+
+jQuery.extend({
+	get: function( url, data, callback, type ) {
+		// shift arguments if data argument was ommited
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = null;
+		}
+		
+		return jQuery.ajax({
+			type: "GET",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get(url, null, callback, "script");
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get(url, data, callback, "json");
+	},
+
+	post: function( url, data, callback, type ) {
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = {};
+		}
+
+		return jQuery.ajax({
+			type: "POST",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	ajaxSetup: function( settings ) {
+		jQuery.extend( jQuery.ajaxSettings, settings );
+	},
+
+	ajaxSettings: {
+		global: true,
+		type: "GET",
+		timeout: 0,
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		data: null,
+		username: null,
+		password: null,
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			script: "text/javascript, application/javascript",
+			json: "application/json, text/javascript",
+			text: "text/plain",
+			_default: "*/*"
+		}
+	},
+	
+	// Last-Modified header cache for next request
+	lastModified: {},
+
+	ajax: function( s ) {
+		var jsonp, jsre = /=\?(&|$)/g, status, data;
+
+		// Extend the settings, but re-extend 's' so that it can be
+		// checked again later (in the test suite, specifically)
+		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+		// convert data if not already a string
+		if ( s.data && s.processData && typeof s.data != "string" )
+			s.data = jQuery.param(s.data);
+
+		// Handle JSONP Parameter Callbacks
+		if ( s.dataType == "jsonp" ) {
+			if ( s.type.toLowerCase() == "get" ) {
+				if ( !s.url.match(jsre) )
+					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+			} else if ( !s.data || !s.data.match(jsre) )
+				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+			s.dataType = "json";
+		}
+
+		// Build temporary JSONP function
+		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+			jsonp = "jsonp" + jsc++;
+
+			// Replace the =? sequence both in the query string and the data
+			if ( s.data )
+				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+			s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+			// We need to make sure
+			// that a JSONP style response is executed properly
+			s.dataType = "script";
+
+			// Handle JSONP-style loading
+			window[ jsonp ] = function(tmp){
+				data = tmp;
+				success();
+				complete();
+				// Garbage collect
+				window[ jsonp ] = undefined;
+				try{ delete window[ jsonp ]; } catch(e){}
+				if ( head )
+					head.removeChild( script );
+			};
+		}
+
+		if ( s.dataType == "script" && s.cache == null )
+			s.cache = false;
+
+		if ( s.cache === false && s.type.toLowerCase() == "get" ) {
+			var ts = (new Date()).getTime();
+			// try replacing _= if it is there
+			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+			// if nothing was replaced, add timestamp to the end
+			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+		}
+
+		// If data is available, append data to url for get requests
+		if ( s.data && s.type.toLowerCase() == "get" ) {
+			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+			// IE likes to send both get and post data, prevent this
+			s.data = null;
+		}
+
+		// Watch for a new set of requests
+		if ( s.global && ! jQuery.active++ )
+			jQuery.event.trigger( "ajaxStart" );
+
+		// If we're requesting a remote document
+		// and trying to load JSON or Script with a GET
+		if ( (!s.url.indexOf("http") || !s.url.indexOf("//")) && s.dataType == "script" && s.type.toLowerCase() == "get" ) {
+			var head = document.getElementsByTagName("head")[0];
+			var script = document.createElement("script");
+			script.src = s.url;
+			if (s.scriptCharset)
+				script.charset = s.scriptCharset;
+
+			// Handle Script loading
+			if ( !jsonp ) {
+				var done = false;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function(){
+					if ( !done && (!this.readyState || 
+							this.readyState == "loaded" || this.readyState == "complete") ) {
+						done = true;
+						success();
+						complete();
+						head.removeChild( script );
+					}
+				};
+			}
+
+			head.appendChild(script);
+
+			// We handle everything using the script element injection
+			return undefined;
+		}
+
+		var requestDone = false;
+
+		// Create the request object; Microsoft failed to properly
+		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+		var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+		// Open the socket
+		xml.open(s.type, s.url, s.async, s.username, s.password);
+
+		// Need an extra try/catch for cross domain requests in Firefox 3
+		try {
+			// Set the correct header, if data is being sent
+			if ( s.data )
+				xml.setRequestHeader("Content-Type", s.contentType);
+
+			// Set the If-Modified-Since header, if ifModified mode.
+			if ( s.ifModified )
+				xml.setRequestHeader("If-Modified-Since",
+					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+			// Set header so the called script knows that it's an XMLHttpRequest
+			xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+			// Set the Accepts header for the server, depending on the dataType
+			xml.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+				s.accepts[ s.dataType ] + ", */*" :
+				s.accepts._default );
+		} catch(e){}
+
+		// Allow custom headers/mimetypes
+		if ( s.beforeSend )
+			s.beforeSend(xml);
+			
+		if ( s.global )
+			jQuery.event.trigger("ajaxSend", [xml, s]);
+
+		// Wait for a response to come back
+		var onreadystatechange = function(isTimeout){
+			// The transfer is complete and the data is available, or the request timed out
+			if ( !requestDone && xml && (xml.readyState == 4 || isTimeout == "timeout") ) {
+				requestDone = true;
+				
+				// clear poll interval
+				if (ival) {
+					clearInterval(ival);
+					ival = null;
+				}
+				
+				status = isTimeout == "timeout" && "timeout" ||
+					!jQuery.httpSuccess( xml ) && "error" ||
+					s.ifModified && jQuery.httpNotModified( xml, s.url ) && "notmodified" ||
+					"success";
+
+				if ( status == "success" ) {
+					// Watch for, and catch, XML document parse errors
+					try {
+						// process the data (runs the xml through httpData regardless of callback)
+						data = jQuery.httpData( xml, s.dataType );
+					} catch(e) {
+						status = "parsererror";
+					}
+				}
+
+				// Make sure that the request was successful or notmodified
+				if ( status == "success" ) {
+					// Cache Last-Modified header, if ifModified mode.
+					var modRes;
+					try {
+						modRes = xml.getResponseHeader("Last-Modified");
+					} catch(e) {} // swallow exception thrown by FF if header is not available
+	
+					if ( s.ifModified && modRes )
+						jQuery.lastModified[s.url] = modRes;
+
+					// JSONP handles its own success callback
+					if ( !jsonp )
+						success();	
+				} else
+					jQuery.handleError(s, xml, status);
+
+				// Fire the complete handlers
+				complete();
+
+				// Stop memory leaks
+				if ( s.async )
+					xml = null;
+			}
+		};
+		
+		if ( s.async ) {
+			// don't attach the handler to the request, just poll it instead
+			var ival = setInterval(onreadystatechange, 13); 
+
+			// Timeout checker
+			if ( s.timeout > 0 )
+				setTimeout(function(){
+					// Check to see if the request is still happening
+					if ( xml ) {
+						// Cancel the request
+						xml.abort();
+	
+						if( !requestDone )
+							onreadystatechange( "timeout" );
+					}
+				}, s.timeout);
+		}
+			
+		// Send the data
+		try {
+			xml.send(s.data);
+		} catch(e) {
+			jQuery.handleError(s, xml, null, e);
+		}
+		
+		// firefox 1.5 doesn't fire statechange for sync requests
+		if ( !s.async )
+			onreadystatechange();
+
+		function success(){
+			// If a local callback was specified, fire it and pass it the data
+			if ( s.success )
+				s.success( data, status );
+
+			// Fire the global callback
+			if ( s.global )
+				jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+		}
+
+		function complete(){
+			// Process result
+			if ( s.complete )
+				s.complete(xml, status);
+
+			// The request was completed
+			if ( s.global )
+				jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+			// Handle the global AJAX counter
+			if ( s.global && ! --jQuery.active )
+				jQuery.event.trigger( "ajaxStop" );
+		}
+		
+		// return XMLHttpRequest to allow aborting the request etc.
+		return xml;
+	},
+
+	handleError: function( s, xml, status, e ) {
+		// If a local callback was specified, fire it
+		if ( s.error ) s.error( xml, status, e );
+
+		// Fire the global callback
+		if ( s.global )
+			jQuery.event.trigger( "ajaxError", [xml, s, e] );
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Determines if an XMLHttpRequest was successful or not
+	httpSuccess: function( r ) {
+		try {
+			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+			return !r.status && location.protocol == "file:" ||
+				( r.status >= 200 && r.status < 300 ) || r.status == 304 || r.status == 1223 ||
+				jQuery.browser.safari && r.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function( xml, url ) {
+		try {
+			var xmlRes = xml.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			return xml.status == 304 || xmlRes == jQuery.lastModified[url] ||
+				jQuery.browser.safari && xml.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	httpData: function( r, type ) {
+		var ct = r.getResponseHeader("content-type");
+		var xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0;
+		var data = xml ? r.responseXML : r.responseText;
+
+		if ( xml && data.documentElement.tagName == "parsererror" )
+			throw "parsererror";
+
+		// If the type is "script", eval it in global context
+		if ( type == "script" )
+			jQuery.globalEval( data );
+
+		// Get the JavaScript object, if JSON is used.
+		if ( type == "json" )
+			data = eval("(" + data + ")");
+
+		return data;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a ) {
+		var s = [];
+
+		// If an array was passed in, assume that it is an array
+		// of form elements
+		if ( a.constructor == Array || a.jquery )
+			// Serialize the form elements
+			jQuery.each( a, function(){
+				s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+			});
+
+		// Otherwise, assume that it's an object of key/value pairs
+		else
+			// Serialize the key/values
+			for ( var j in a )
+				// If the value is an array then the key names need to be repeated
+				if ( a[j] && a[j].constructor == Array )
+					jQuery.each( a[j], function(){
+						s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+					});
+				else
+					s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );
+
+		// Return the resulting serialization
+		return s.join("&").replace(/%20/g, "+");
+	}
+
+});
+jQuery.fn.extend({
+	show: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "show", width: "show", opacity: "show"
+			}, speed, callback) :
+			
+			this.filter(":hidden").each(function(){
+				this.style.display = this.oldblock || "";
+				if ( jQuery.css(this,"display") == "none" ) {
+					var elem = jQuery("<" + this.tagName + " />").appendTo("body");
+					this.style.display = elem.css("display");
+					// handle an edge condition where css is - div { display:none; } or similar
+					if (this.style.display == "none")
+						this.style.display = "block";
+					elem.remove();
+				}
+			}).end();
+	},
+	
+	hide: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "hide", width: "hide", opacity: "hide"
+			}, speed, callback) :
+			
+			this.filter(":visible").each(function(){
+				this.oldblock = this.oldblock || jQuery.css(this,"display");
+				this.style.display = "none";
+			}).end();
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+	
+	toggle: function( fn, fn2 ){
+		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+			this._toggle( fn, fn2 ) :
+			fn ?
+				this.animate({
+					height: "toggle", width: "toggle", opacity: "toggle"
+				}, fn, fn2) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+				});
+	},
+	
+	slideDown: function(speed,callback){
+		return this.animate({height: "show"}, speed, callback);
+	},
+	
+	slideUp: function(speed,callback){
+		return this.animate({height: "hide"}, speed, callback);
+	},
+
+	slideToggle: function(speed, callback){
+		return this.animate({height: "toggle"}, speed, callback);
+	},
+	
+	fadeIn: function(speed, callback){
+		return this.animate({opacity: "show"}, speed, callback);
+	},
+	
+	fadeOut: function(speed, callback){
+		return this.animate({opacity: "hide"}, speed, callback);
+	},
+	
+	fadeTo: function(speed,to,callback){
+		return this.animate({opacity: to}, speed, callback);
+	},
+	
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		return this[ optall.queue === false ? "each" : "queue" ](function(){
+			if ( this.nodeType != 1)
+				return false;
+
+			var opt = jQuery.extend({}, optall);
+			var hidden = jQuery(this).is(":hidden"), self = this;
+			
+			for ( var p in prop ) {
+				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+					return jQuery.isFunction(opt.complete) && opt.complete.apply(this);
+
+				if ( p == "height" || p == "width" ) {
+					// Store display property
+					opt.display = jQuery.css(this, "display");
+
+					// Make sure that nothing sneaks out
+					opt.overflow = this.style.overflow;
+				}
+			}
+
+			if ( opt.overflow != null )
+				this.style.overflow = "hidden";
+
+			opt.curAnim = jQuery.extend({}, prop);
+			
+			jQuery.each( prop, function(name, val){
+				var e = new jQuery.fx( self, opt, name );
+
+				if ( /toggle|show|hide/.test(val) )
+					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+				else {
+					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+						start = e.cur(true) || 0;
+
+					if ( parts ) {
+						var end = parseFloat(parts[2]),
+							unit = parts[3] || "px";
+
+						// We need to compute starting value
+						if ( unit != "px" ) {
+							self.style[ name ] = (end || 1) + unit;
+							start = ((end || 1) / e.cur(true)) * start;
+							self.style[ name ] = start + unit;
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] )
+							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+						e.custom( start, end, unit );
+					} else
+						e.custom( start, val, "" );
+				}
+			});
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+	
+	queue: function(type, fn){
+		if ( jQuery.isFunction(type) || ( type && type.constructor == Array )) {
+			fn = type;
+			type = "fx";
+		}
+
+		if ( !type || (typeof type == "string" && !fn) )
+			return queue( this[0], type );
+
+		return this.each(function(){
+			if ( fn.constructor == Array )
+				queue(this, type, fn);
+			else {
+				queue(this, type).push( fn );
+			
+				if ( queue(this, type).length == 1 )
+					fn.apply(this);
+			}
+		});
+	},
+
+	stop: function(clearQueue, gotoEnd){
+		var timers = jQuery.timers;
+
+		if (clearQueue)
+			this.queue([]);
+
+		this.each(function(){
+			// go in reverse order so anything added to the queue during the loop is ignored
+			for ( var i = timers.length - 1; i >= 0; i-- )
+				if ( timers[i].elem == this ) {
+					if (gotoEnd)
+						// force the next step to be the last
+						timers[i](true);
+					timers.splice(i, 1);
+				}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if (!gotoEnd)
+			this.dequeue();
+
+		return this;
+	}
+
+});
+
+var queue = function( elem, type, array ) {
+	if ( !elem )
+		return undefined;
+
+	type = type || "fx";
+
+	var q = jQuery.data( elem, type + "queue" );
+
+	if ( !q || array )
+		q = jQuery.data( elem, type + "queue", 
+			array ? jQuery.makeArray(array) : [] );
+
+	return q;
+};
+
+jQuery.fn.dequeue = function(type){
+	type = type || "fx";
+
+	return this.each(function(){
+		var q = queue(this, type);
+
+		q.shift();
+
+		if ( q.length )
+			q[0].apply( this );
+	});
+};
+
+jQuery.extend({
+	
+	speed: function(speed, easing, fn) {
+		var opt = speed && speed.constructor == Object ? speed : {
+			complete: fn || !fn && easing || 
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && easing.constructor != Function && easing
+		};
+
+		opt.duration = (opt.duration && opt.duration.constructor == Number ? 
+			opt.duration : 
+			{ slow: 600, fast: 200 }[opt.duration]) || 400;
+	
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function(){
+			if ( opt.queue !== false )
+				jQuery(this).dequeue();
+			if ( jQuery.isFunction( opt.old ) )
+				opt.old.apply( this );
+		};
+	
+		return opt;
+	},
+	
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+	
+	timers: [],
+	timerId: null,
+
+	fx: function( elem, options, prop ){
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		if ( !options.orig )
+			options.orig = {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+
+	// Simple function for setting a style value
+	update: function(){
+		if ( this.options.step )
+			this.options.step.apply( this.elem, [ this.now, this ] );
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+		// Set display property to block for height/width animations
+		if ( this.prop == "height" || this.prop == "width" )
+			this.elem.style.display = "block";
+	},
+
+	// Get the current size
+	cur: function(force){
+		if ( this.elem[this.prop] != null && this.elem.style[this.prop] == null )
+			return this.elem[ this.prop ];
+
+		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+	},
+
+	// Start an animation from one number to another
+	custom: function(from, to, unit){
+		this.startTime = (new Date()).getTime();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || "px";
+		this.now = this.start;
+		this.pos = this.state = 0;
+		this.update();
+
+		var self = this;
+		function t(gotoEnd){
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		jQuery.timers.push(t);
+
+		if ( jQuery.timerId == null ) {
+			jQuery.timerId = setInterval(function(){
+				var timers = jQuery.timers;
+				
+				for ( var i = 0; i < timers.length; i++ )
+					if ( !timers[i]() )
+						timers.splice(i--, 1);
+
+				if ( !timers.length ) {
+					clearInterval( jQuery.timerId );
+					jQuery.timerId = null;
+				}
+			}, 13);
+		}
+	},
+
+	// Simple 'show' function
+	show: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		this.custom(0, this.cur());
+
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		if ( this.prop == "width" || this.prop == "height" )
+			this.elem.style[this.prop] = "1px";
+		
+		// Start by showing the element
+		jQuery(this.elem).show();
+	},
+
+	// Simple 'hide' function
+	hide: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function(gotoEnd){
+		var t = (new Date()).getTime();
+
+		if ( gotoEnd || t > this.options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			this.options.curAnim[ this.prop ] = true;
+
+			var done = true;
+			for ( var i in this.options.curAnim )
+				if ( this.options.curAnim[i] !== true )
+					done = false;
+
+			if ( done ) {
+				if ( this.options.display != null ) {
+					// Reset the overflow
+					this.elem.style.overflow = this.options.overflow;
+				
+					// Reset the display
+					this.elem.style.display = this.options.display;
+					if ( jQuery.css(this.elem, "display") == "none" )
+						this.elem.style.display = "block";
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( this.options.hide )
+					this.elem.style.display = "none";
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( this.options.hide || this.options.show )
+					for ( var p in this.options.curAnim )
+						jQuery.attr(this.elem.style, p, this.options.orig[p]);
+			}
+
+			// If a callback was provided, execute it
+			if ( done && jQuery.isFunction( this.options.complete ) )
+				// Execute the complete function
+				this.options.complete.apply( this.elem );
+
+			return false;
+		} else {
+			var n = t - this.startTime;
+			this.state = n / this.options.duration;
+
+			// Perform the easing function, defaults to swing
+			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+			this.now = this.start + ((this.end - this.start) * this.pos);
+
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+
+};
+
+jQuery.fx.step = {
+	scrollLeft: function(fx){
+		fx.elem.scrollLeft = fx.now;
+	},
+
+	scrollTop: function(fx){
+		fx.elem.scrollTop = fx.now;
+	},
+
+	opacity: function(fx){
+		jQuery.attr(fx.elem.style, "opacity", fx.now);
+	},
+
+	_default: function(fx){
+		fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+	}
+};
+// The Offset Method
+// Originally By Brandon Aaron, part of the Dimension Plugin
+// http://jquery.com/plugins/project/dimensions
+jQuery.fn.offset = function() {
+	var left = 0, top = 0, elem = this[0], results;
+	
+	if ( elem ) with ( jQuery.browser ) {
+		var parent       = elem.parentNode, 
+		    offsetChild  = elem,
+		    offsetParent = elem.offsetParent, 
+		    doc          = elem.ownerDocument,
+		    safari2      = safari && parseInt(version) < 522 && !/adobeair/i.test(userAgent),
+		    fixed        = jQuery.css(elem, "position") == "fixed";
+	
+		// Use getBoundingClientRect if available
+		if ( elem.getBoundingClientRect ) {
+			var box = elem.getBoundingClientRect();
+		
+			// Add the document scroll offsets
+			add(box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+				box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		
+			// IE adds the HTML element's border, by default it is medium which is 2px
+			// IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
+			// IE 7 standards mode, the border is always 2px
+			// This border/offset is typically represented by the clientLeft and clientTop properties
+			// However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
+			// Therefore this method will be off by 2px in IE while in quirksmode
+			add( -doc.documentElement.clientLeft, -doc.documentElement.clientTop );
+	
+		// Otherwise loop through the offsetParents and parentNodes
+		} else {
+		
+			// Initial element offsets
+			add( elem.offsetLeft, elem.offsetTop );
+			
+			// Get parent offsets
+			while ( offsetParent ) {
+				// Add offsetParent offsets
+				add( offsetParent.offsetLeft, offsetParent.offsetTop );
+			
+				// Mozilla and Safari > 2 does not include the border on offset parents
+				// However Mozilla adds the border for table or table cells
+				if ( mozilla && !/^t(able|d|h)$/i.test(offsetParent.tagName) || safari && !safari2 )
+					border( offsetParent );
+					
+				// Add the document scroll offsets if position is fixed on any offsetParent
+				if ( !fixed && jQuery.css(offsetParent, "position") == "fixed" )
+					fixed = true;
+			
+				// Set offsetChild to previous offsetParent unless it is the body element
+				offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
+				// Get next offsetParent
+				offsetParent = offsetParent.offsetParent;
+			}
+		
+			// Get parent scroll offsets
+			while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
+				// Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
+				if ( !/^inline|table.*$/i.test(jQuery.css(parent, "display")) )
+					// Subtract parent scroll offsets
+					add( -parent.scrollLeft, -parent.scrollTop );
+			
+				// Mozilla does not add the border for a parent that has overflow != visible
+				if ( mozilla && jQuery.css(parent, "overflow") != "visible" )
+					border( parent );
+			
+				// Get next parent
+				parent = parent.parentNode;
+			}
+		
+			// Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
+			// Mozilla doubles body offsets with a non-absolutely positioned offsetChild
+			if ( (safari2 && (fixed || jQuery.css(offsetChild, "position") == "absolute")) || 
+				(mozilla && jQuery.css(offsetChild, "position") != "absolute") )
+					add( -doc.body.offsetLeft, -doc.body.offsetTop );
+			
+			// Add the document scroll offsets if position is fixed
+			if ( fixed )
+				add(Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft),
+					Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop));
+		}
+
+		// Return an object with top and left properties
+		results = { top: top, left: left };
+	}
+
+	function border(elem) {
+		add( jQuery.curCSS(elem, "borderLeftWidth", true), jQuery.curCSS(elem, "borderTopWidth", true) );
+	}
+
+	function add(l, t) {
+		left += parseInt(l) || 0;
+		top += parseInt(t) || 0;
+	}
+
+	return results;
+};
+})();

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 12 - 0
talks/locking_war_stories/scripts/jquery-1.2.3.min.js


+ 4170 - 0
talks/locking_war_stories/scripts/prototype.js

@@ -0,0 +1,4170 @@
+/*  Prototype JavaScript framework, version 1.6.0.1
+ *  (c) 2005-2007 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.1',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : object.toString();
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object && object.constructor === Array;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    }.bind(this));
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  function $A(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  }
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).getElementsBySelector("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position != 'static') return proceed(element);
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.clone(Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if (document.createElement('div').outerHTML) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);   c = false;',
+    className:    'n = h.className(n, r, "#{1}", c); c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);        c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return Selector.operators[matches[2]](nodeValue, matches[3]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._counted = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._counted = true;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._counted) {
+          n._counted = true;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._counted) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._counted) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._counted) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    var exprs = expressions.join(',');
+    expressions = [];
+    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  // IE returns comment nodes on getElementsByTagName("*").
+  // Filter them out.
+  Selector.handlers.concat = function(a, b) {
+    for (var i = 0, node; node = b[i]; i++)
+      if (node.tagName !== "!") a.push(node);
+    return a;
+  };
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._eventID) return element._eventID;
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._eventID = ++arguments.callee.id;
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();

+ 58 - 0
talks/locking_war_stories/scripts/scriptaculous.js

@@ -0,0 +1,58 @@
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+  Version: '1.8.1',
+  require: function(libraryName) {
+    // inserting via DOM fails in Safari 2.0, so brute force approach
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
+  },
+  REQUIRED_PROTOTYPE: '1.6.0',
+  load: function() {
+    function convertVersionString(versionString){
+      var r = versionString.split('.');
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
+    }
+ 
+    if((typeof Prototype=='undefined') || 
+       (typeof Element == 'undefined') || 
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) < 
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+    
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
+    }).each( function(s) {
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
+  }
+}
+
+Scriptaculous.load();

+ 275 - 0
talks/locking_war_stories/scripts/slider.js

@@ -0,0 +1,275 @@
+// script.aculo.us slider.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Marty Haught, Thomas Fuchs 
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+if (!Control) var Control = { };
+
+// options:
+//  axis: 'vertical', or 'horizontal' (default)
+//
+// callbacks:
+//  onChange(value)
+//  onSlide(value)
+Control.Slider = Class.create({
+  initialize: function(handle, track, options) {
+    var slider = this;
+    
+    if (Object.isArray(handle)) {
+      this.handles = handle.collect( function(e) { return $(e) });
+    } else {
+      this.handles = [$(handle)];
+    }
+    
+    this.track   = $(track);
+    this.options = options || { };
+
+    this.axis      = this.options.axis || 'horizontal';
+    this.increment = this.options.increment || 1;
+    this.step      = parseInt(this.options.step || '1');
+    this.range     = this.options.range || $R(0,1);
+    
+    this.value     = 0; // assure backwards compat
+    this.values    = this.handles.map( function() { return 0 });
+    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
+    this.options.startSpan = $(this.options.startSpan || null);
+    this.options.endSpan   = $(this.options.endSpan || null);
+
+    this.restricted = this.options.restricted || false;
+
+    this.maximum   = this.options.maximum || this.range.end;
+    this.minimum   = this.options.minimum || this.range.start;
+
+    // Will be used to align the handle onto the track, if necessary
+    this.alignX = parseInt(this.options.alignX || '0');
+    this.alignY = parseInt(this.options.alignY || '0');
+    
+    this.trackLength = this.maximumOffset() - this.minimumOffset();
+
+    this.handleLength = this.isVertical() ? 
+      (this.handles[0].offsetHeight != 0 ? 
+        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) : 
+      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth : 
+        this.handles[0].style.width.replace(/px$/,""));
+
+    this.active   = false;
+    this.dragging = false;
+    this.disabled = false;
+
+    if (this.options.disabled) this.setDisabled();
+
+    // Allowed values array
+    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
+    if (this.allowedValues) {
+      this.minimum = this.allowedValues.min();
+      this.maximum = this.allowedValues.max();
+    }
+
+    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
+    this.eventMouseMove = this.update.bindAsEventListener(this);
+
+    // Initialize handles in reverse (make sure first handle is active)
+    this.handles.each( function(h,i) {
+      i = slider.handles.length-1-i;
+      slider.setValue(parseFloat(
+        (Object.isArray(slider.options.sliderValue) ? 
+          slider.options.sliderValue[i] : slider.options.sliderValue) || 
+         slider.range.start), i);
+      h.makePositioned().observe("mousedown", slider.eventMouseDown);
+    });
+    
+    this.track.observe("mousedown", this.eventMouseDown);
+    document.observe("mouseup", this.eventMouseUp);
+    document.observe("mousemove", this.eventMouseMove);
+    
+    this.initialized = true;
+  },
+  dispose: function() {
+    var slider = this;    
+    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
+    Event.stopObserving(document, "mouseup", this.eventMouseUp);
+    Event.stopObserving(document, "mousemove", this.eventMouseMove);
+    this.handles.each( function(h) {
+      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
+    });
+  },
+  setDisabled: function(){
+    this.disabled = true;
+  },
+  setEnabled: function(){
+    this.disabled = false;
+  },  
+  getNearestValue: function(value){
+    if (this.allowedValues){
+      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
+      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
+      
+      var offset = Math.abs(this.allowedValues[0] - value);
+      var newValue = this.allowedValues[0];
+      this.allowedValues.each( function(v) {
+        var currentOffset = Math.abs(v - value);
+        if (currentOffset <= offset){
+          newValue = v;
+          offset = currentOffset;
+        } 
+      });
+      return newValue;
+    }
+    if (value > this.range.end) return this.range.end;
+    if (value < this.range.start) return this.range.start;
+    return value;
+  },
+  setValue: function(sliderValue, handleIdx){
+    if (!this.active) {
+      this.activeHandleIdx = handleIdx || 0;
+      this.activeHandle    = this.handles[this.activeHandleIdx];
+      this.updateStyles();
+    }
+    handleIdx = handleIdx || this.activeHandleIdx || 0;
+    if (this.initialized && this.restricted) {
+      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
+        sliderValue = this.values[handleIdx-1];
+      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
+        sliderValue = this.values[handleIdx+1];
+    }
+    sliderValue = this.getNearestValue(sliderValue);
+    this.values[handleIdx] = sliderValue;
+    this.value = this.values[0]; // assure backwards compat
+    
+    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
+      this.translateToPx(sliderValue);
+    
+    this.drawSpans();
+    if (!this.dragging || !this.event) this.updateFinished();
+  },
+  setValueBy: function(delta, handleIdx) {
+    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
+      handleIdx || this.activeHandleIdx || 0);
+  },
+  translateToPx: function(value) {
+    return Math.round(
+      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
+      (value - this.range.start)) + "px";
+  },
+  translateToValue: function(offset) {
+    return ((offset/(this.trackLength-this.handleLength) * 
+      (this.range.end-this.range.start)) + this.range.start);
+  },
+  getRange: function(range) {
+    var v = this.values.sortBy(Prototype.K); 
+    range = range || 0;
+    return $R(v[range],v[range+1]);
+  },
+  minimumOffset: function(){
+    return(this.isVertical() ? this.alignY : this.alignX);
+  },
+  maximumOffset: function(){
+    return(this.isVertical() ? 
+      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
+        this.track.style.height.replace(/px$/,"")) - this.alignY : 
+      (this.track.offsetWidth != 0 ? this.track.offsetWidth : 
+        this.track.style.width.replace(/px$/,"")) - this.alignX);
+  },  
+  isVertical:  function(){
+    return (this.axis == 'vertical');
+  },
+  drawSpans: function() {
+    var slider = this;
+    if (this.spans)
+      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
+    if (this.options.startSpan)
+      this.setSpan(this.options.startSpan,
+        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
+    if (this.options.endSpan)
+      this.setSpan(this.options.endSpan, 
+        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
+  },
+  setSpan: function(span, range) {
+    if (this.isVertical()) {
+      span.style.top = this.translateToPx(range.start);
+      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
+    } else {
+      span.style.left = this.translateToPx(range.start);
+      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
+    }
+  },
+  updateStyles: function() {
+    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
+    Element.addClassName(this.activeHandle, 'selected');
+  },
+  startDrag: function(event) {
+    if (Event.isLeftClick(event)) {
+      if (!this.disabled){
+        this.active = true;
+        
+        var handle = Event.element(event);
+        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
+        var track = handle;
+        if (track==this.track) {
+          var offsets  = Position.cumulativeOffset(this.track); 
+          this.event = event;
+          this.setValue(this.translateToValue( 
+           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
+          ));
+          var offsets  = Position.cumulativeOffset(this.activeHandle);
+          this.offsetX = (pointer[0] - offsets[0]);
+          this.offsetY = (pointer[1] - offsets[1]);
+        } else {
+          // find the handle (prevents issues with Safari)
+          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
+            handle = handle.parentNode;
+            
+          if (this.handles.indexOf(handle)!=-1) {
+            this.activeHandle    = handle;
+            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
+            this.updateStyles();
+            
+            var offsets  = Position.cumulativeOffset(this.activeHandle);
+            this.offsetX = (pointer[0] - offsets[0]);
+            this.offsetY = (pointer[1] - offsets[1]);
+          }
+        }
+      }
+      Event.stop(event);
+    }
+  },
+  update: function(event) {
+   if (this.active) {
+      if (!this.dragging) this.dragging = true;
+      this.draw(event);
+      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+      Event.stop(event);
+   }
+  },
+  draw: function(event) {
+    var pointer = [Event.pointerX(event), Event.pointerY(event)];
+    var offsets = Position.cumulativeOffset(this.track);
+    pointer[0] -= this.offsetX + offsets[0];
+    pointer[1] -= this.offsetY + offsets[1];
+    this.event = event;
+    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
+    if (this.initialized && this.options.onSlide)
+      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
+  },
+  endDrag: function(event) {
+    if (this.active && this.dragging) {
+      this.finishDrag(event, true);
+      Event.stop(event);
+    }
+    this.active = false;
+    this.dragging = false;
+  },  
+  finishDrag: function(event, success) {
+    this.active = false;
+    this.dragging = false;
+    this.updateFinished();
+  },
+  updateFinished: function() {
+    if (this.initialized && this.options.onChange) 
+      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
+    this.event = null;
+  }
+});

+ 99 - 0
talks/locking_war_stories/scripts/slideshow.js

@@ -0,0 +1,99 @@
+var numSlides = 0;
+var currentSlide = 0;
+var slidesReady = false;
+
+$(document).ready(function() {
+    $(document).keyup(handleKeys);
+
+    numSlides = $("div.slide").each(function (i) {
+        $(this).attr("id", "slide_" + i);
+      }).size();
+
+    readySlides();
+    showSlide();
+  });
+
+
+function readySlides() {
+  slidesReady = true;
+  $("div.slide").css("display", "none").css("position", "absolute").css("height", "90%").click(nextSlide);
+}
+
+function unreadySlides() {
+  slidesReady = false;
+    $("div.slide").css("display", "inline").css("position", "static").css("height", "12em").click(function() { gotoSlide($(this)); });
+}
+
+
+function handleKeys(e) {
+  if (! slidesReady) {
+    return;
+  }
+
+  var keyID = (window.event) ? event.keyCode : e.keyCode;
+
+  switch(keyID) {
+    case 27: // escape
+      unreadySlides();
+      break;
+    case 32: // space
+      nextSlide();
+      break;
+    case 37: // left
+      prevSlide();
+      break;
+    case 38: // up
+      setSlide(0);
+      break;
+    case 39: // right
+      nextSlide();
+      break;
+    case 40: // down
+      lastSlide();
+      break;
+  }
+}
+
+
+function showSlide() {
+  $("div.slide").css("display", "none");
+
+  $("#slide_" + currentSlide).css("display", "inline");
+}
+
+function setSlide(number) {
+  currentSlide = number;
+  showSlide();
+}
+
+
+function nextSlide() {
+  currentSlide++;
+  if (currentSlide >= numSlides) {
+    currentSlide = 0;
+  }
+
+  showSlide();
+}
+
+function prevSlide() {
+  currentSlide--;
+  if (currentSlide < 0) {
+    currentSlide = 0;
+  }
+
+  showSlide();
+}
+
+function lastSlide() {
+  currentSlide = (numSlides - 1);
+  showSlide();
+}
+
+
+function gotoSlide(slide) {
+  var number = slide.attr("id").substring(6);
+
+  readySlides();
+  setSlide(number);
+}

+ 55 - 0
talks/locking_war_stories/scripts/sound.js

@@ -0,0 +1,55 @@
+// script.aculo.us sound.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// Based on code created by Jules Gravinese (http://www.webveteran.com/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+Sound = {
+  tracks: {},
+  _enabled: true,
+  template:
+    new Template('<embed style="height:0" id="sound_#{track}_#{id}" src="#{url}" loop="false" autostart="true" hidden="true"/>'),
+  enable: function(){
+    Sound._enabled = true;
+  },
+  disable: function(){
+    Sound._enabled = false;
+  },
+  play: function(url){
+    if(!Sound._enabled) return;
+    var options = Object.extend({
+      track: 'global', url: url, replace: false
+    }, arguments[1] || {});
+    
+    if(options.replace && this.tracks[options.track]) {
+      $R(0, this.tracks[options.track].id).each(function(id){
+        var sound = $('sound_'+options.track+'_'+id);
+        sound.Stop && sound.Stop();
+        sound.remove();
+      })
+      this.tracks[options.track] = null;
+    }
+      
+    if(!this.tracks[options.track])
+      this.tracks[options.track] = { id: 0 }
+    else
+      this.tracks[options.track].id++;
+      
+    options.id = this.tracks[options.track].id;
+    $$('body')[0].insert( 
+      Prototype.Browser.IE ? new Element('bgsound',{
+        id: 'sound_'+options.track+'_'+options.id,
+        src: options.url, loop: 1, autostart: true
+      }) : Sound.template.evaluate(options));
+  }
+};
+
+if(Prototype.Browser.Gecko && navigator.userAgent.indexOf("Win") > 0){
+  if(navigator.plugins && $A(navigator.plugins).detect(function(p){ return p.name.indexOf('QuickTime') != -1 }))
+    Sound.template = new Template('<object id="sound_#{track}_#{id}" width="0" height="0" type="audio/mpeg" data="#{url}"/>')
+  else
+    Sound.play = function(){}
+}

+ 568 - 0
talks/locking_war_stories/scripts/unittest.js

@@ -0,0 +1,568 @@
+// script.aculo.us unittest.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//           (c) 2005-2007 Jon Tirsen (http://www.tirsen.com)
+//           (c) 2005-2007 Michael Schuerig (http://www.schuerig.de/michael/)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+// experimental, Firefox-only
+Event.simulateMouse = function(element, eventName) {
+  var options = Object.extend({
+    pointerX: 0,
+    pointerY: 0,
+    buttons:  0,
+    ctrlKey:  false,
+    altKey:   false,
+    shiftKey: false,
+    metaKey:  false
+  }, arguments[2] || {});
+  var oEvent = document.createEvent("MouseEvents");
+  oEvent.initMouseEvent(eventName, true, true, document.defaultView, 
+    options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element));
+  
+  if(this.mark) Element.remove(this.mark);
+  this.mark = document.createElement('div');
+  this.mark.appendChild(document.createTextNode(" "));
+  document.body.appendChild(this.mark);
+  this.mark.style.position = 'absolute';
+  this.mark.style.top = options.pointerY + "px";
+  this.mark.style.left = options.pointerX + "px";
+  this.mark.style.width = "5px";
+  this.mark.style.height = "5px;";
+  this.mark.style.borderTop = "1px solid red;"
+  this.mark.style.borderLeft = "1px solid red;"
+  
+  if(this.step)
+    alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
+  
+  $(element).dispatchEvent(oEvent);
+};
+
+// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
+// You need to downgrade to 1.0.4 for now to get this working
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
+Event.simulateKey = function(element, eventName) {
+  var options = Object.extend({
+    ctrlKey: false,
+    altKey: false,
+    shiftKey: false,
+    metaKey: false,
+    keyCode: 0,
+    charCode: 0
+  }, arguments[2] || {});
+
+  var oEvent = document.createEvent("KeyEvents");
+  oEvent.initKeyEvent(eventName, true, true, window, 
+    options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
+    options.keyCode, options.charCode );
+  $(element).dispatchEvent(oEvent);
+};
+
+Event.simulateKeys = function(element, command) {
+  for(var i=0; i<command.length; i++) {
+    Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
+  }
+};
+
+var Test = {}
+Test.Unit = {};
+
+// security exception workaround
+Test.Unit.inspect = Object.inspect;
+
+Test.Unit.Logger = Class.create();
+Test.Unit.Logger.prototype = {
+  initialize: function(log) {
+    this.log = $(log);
+    if (this.log) {
+      this._createLogTable();
+    }
+  },
+  start: function(testName) {
+    if (!this.log) return;
+    this.testName = testName;
+    this.lastLogLine = document.createElement('tr');
+    this.statusCell = document.createElement('td');
+    this.nameCell = document.createElement('td');
+    this.nameCell.className = "nameCell";
+    this.nameCell.appendChild(document.createTextNode(testName));
+    this.messageCell = document.createElement('td');
+    this.lastLogLine.appendChild(this.statusCell);
+    this.lastLogLine.appendChild(this.nameCell);
+    this.lastLogLine.appendChild(this.messageCell);
+    this.loglines.appendChild(this.lastLogLine);
+  },
+  finish: function(status, summary) {
+    if (!this.log) return;
+    this.lastLogLine.className = status;
+    this.statusCell.innerHTML = status;
+    this.messageCell.innerHTML = this._toHTML(summary);
+    this.addLinksToResults();
+  },
+  message: function(message) {
+    if (!this.log) return;
+    this.messageCell.innerHTML = this._toHTML(message);
+  },
+  summary: function(summary) {
+    if (!this.log) return;
+    this.logsummary.innerHTML = this._toHTML(summary);
+  },
+  _createLogTable: function() {
+    this.log.innerHTML =
+    '<div id="logsummary"></div>' +
+    '<table id="logtable">' +
+    '<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
+    '<tbody id="loglines"></tbody>' +
+    '</table>';
+    this.logsummary = $('logsummary')
+    this.loglines = $('loglines');
+  },
+  _toHTML: function(txt) {
+    return txt.escapeHTML().replace(/\n/g,"<br/>");
+  },
+  addLinksToResults: function(){ 
+    $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run only this test"
+      Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;});
+    });
+    $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log
+      td.title = "Run all tests"
+      Event.observe(td, 'click', function(){ window.location.search = "";});
+    });
+  }
+}
+
+Test.Unit.Runner = Class.create();
+Test.Unit.Runner.prototype = {
+  initialize: function(testcases) {
+    this.options = Object.extend({
+      testLog: 'testlog'
+    }, arguments[1] || {});
+    this.options.resultsURL = this.parseResultsURLQueryParameter();
+    this.options.tests      = this.parseTestsQueryParameter();
+    if (this.options.testLog) {
+      this.options.testLog = $(this.options.testLog) || null;
+    }
+    if(this.options.tests) {
+      this.tests = [];
+      for(var i = 0; i < this.options.tests.length; i++) {
+        if(/^test/.test(this.options.tests[i])) {
+          this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
+        }
+      }
+    } else {
+      if (this.options.test) {
+        this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
+      } else {
+        this.tests = [];
+        for(var testcase in testcases) {
+          if(/^test/.test(testcase)) {
+            this.tests.push(
+               new Test.Unit.Testcase(
+                 this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, 
+                 testcases[testcase], testcases["setup"], testcases["teardown"]
+               ));
+          }
+        }
+      }
+    }
+    this.currentTest = 0;
+    this.logger = new Test.Unit.Logger(this.options.testLog);
+    setTimeout(this.runTests.bind(this), 1000);
+  },
+  parseResultsURLQueryParameter: function() {
+    return window.location.search.parseQuery()["resultsURL"];
+  },
+  parseTestsQueryParameter: function(){
+    if (window.location.search.parseQuery()["tests"]){
+        return window.location.search.parseQuery()["tests"].split(',');
+    };
+  },
+  // Returns:
+  //  "ERROR" if there was an error,
+  //  "FAILURE" if there was a failure, or
+  //  "SUCCESS" if there was neither
+  getResult: function() {
+    var hasFailure = false;
+    for(var i=0;i<this.tests.length;i++) {
+      if (this.tests[i].errors > 0) {
+        return "ERROR";
+      }
+      if (this.tests[i].failures > 0) {
+        hasFailure = true;
+      }
+    }
+    if (hasFailure) {
+      return "FAILURE";
+    } else {
+      return "SUCCESS";
+    }
+  },
+  postResults: function() {
+    if (this.options.resultsURL) {
+      new Ajax.Request(this.options.resultsURL, 
+        { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
+    }
+  },
+  runTests: function() {
+    var test = this.tests[this.currentTest];
+    if (!test) {
+      // finished!
+      this.postResults();
+      this.logger.summary(this.summary());
+      return;
+    }
+    if(!test.isWaiting) {
+      this.logger.start(test.name);
+    }
+    test.run();
+    if(test.isWaiting) {
+      this.logger.message("Waiting for " + test.timeToWait + "ms");
+      setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
+    } else {
+      this.logger.finish(test.status(), test.summary());
+      this.currentTest++;
+      // tail recursive, hopefully the browser will skip the stackframe
+      this.runTests();
+    }
+  },
+  summary: function() {
+    var assertions = 0;
+    var failures = 0;
+    var errors = 0;
+    var messages = [];
+    for(var i=0;i<this.tests.length;i++) {
+      assertions +=   this.tests[i].assertions;
+      failures   +=   this.tests[i].failures;
+      errors     +=   this.tests[i].errors;
+    }
+    return (
+      (this.options.context ? this.options.context + ': ': '') + 
+      this.tests.length + " tests, " + 
+      assertions + " assertions, " + 
+      failures   + " failures, " +
+      errors     + " errors");
+  }
+}
+
+Test.Unit.Assertions = Class.create();
+Test.Unit.Assertions.prototype = {
+  initialize: function() {
+    this.assertions = 0;
+    this.failures   = 0;
+    this.errors     = 0;
+    this.messages   = [];
+  },
+  summary: function() {
+    return (
+      this.assertions + " assertions, " + 
+      this.failures   + " failures, " +
+      this.errors     + " errors" + "\n" +
+      this.messages.join("\n"));
+  },
+  pass: function() {
+    this.assertions++;
+  },
+  fail: function(message) {
+    this.failures++;
+    this.messages.push("Failure: " + message);
+  },
+  info: function(message) {
+    this.messages.push("Info: " + message);
+  },
+  error: function(error) {
+    this.errors++;
+    this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
+  },
+  status: function() {
+    if (this.failures > 0) return 'failed';
+    if (this.errors > 0) return 'error';
+    return 'passed';
+  },
+  assert: function(expression) {
+    var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
+    try { expression ? this.pass() : 
+      this.fail(message); }
+    catch(e) { this.error(e); }
+  },
+  assertEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEqual";
+    try { (expected == actual) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInspect: function(expected, actual) {
+    var message = arguments[2] || "assertInspect";
+    try { (expected == actual.inspect()) ? this.pass() :
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) + 
+        '", actual "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertEnumEqual: function(expected, actual) {
+    var message = arguments[2] || "assertEnumEqual";
+    try { $A(expected).length == $A(actual).length && 
+      expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
+        this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + 
+          ', actual ' + Test.Unit.inspect(actual)); }
+    catch(e) { this.error(e); }
+  },
+  assertNotEqual: function(expected, actual) {
+    var message = arguments[2] || "assertNotEqual";
+    try { (expected != actual) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertIdentical"; 
+    try { (expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNotIdentical: function(expected, actual) { 
+    var message = arguments[2] || "assertNotIdentical"; 
+    try { !(expected === actual) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + Test.Unit.inspect(actual) + '"'); } 
+    catch(e) { this.error(e); } 
+  },
+  assertNull: function(obj) {
+    var message = arguments[1] || 'assertNull'
+    try { (obj==null) ? this.pass() : 
+      this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertMatch: function(expected, actual) {
+    var message = arguments[2] || 'assertMatch';
+    var regex = new RegExp(expected);
+    try { (regex.exec(actual)) ? this.pass() :
+      this.fail(message + ' : regex: "' +  Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertHidden: function(element) {
+    var message = arguments[1] || 'assertHidden';
+    this.assertEqual("none", element.style.display, message);
+  },
+  assertNotNull: function(object) {
+    var message = arguments[1] || 'assertNotNull';
+    this.assert(object != null, message);
+  },
+  assertType: function(expected, actual) {
+    var message = arguments[2] || 'assertType';
+    try { 
+      (actual.constructor == expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertNotOfType: function(expected, actual) {
+    var message = arguments[2] || 'assertNotOfType';
+    try { 
+      (actual.constructor != expected) ? this.pass() : 
+      this.fail(message + ': expected "' + Test.Unit.inspect(expected) +  
+        '", actual "' + (actual.constructor) + '"'); }
+    catch(e) { this.error(e); }
+  },
+  assertInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertInstanceOf';
+    try { 
+      (actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was not an instance of the expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertNotInstanceOf: function(expected, actual) {
+    var message = arguments[2] || 'assertNotInstanceOf';
+    try { 
+      !(actual instanceof expected) ? this.pass() : 
+      this.fail(message + ": object was an instance of the not expected type"); }
+    catch(e) { this.error(e); } 
+  },
+  assertRespondsTo: function(method, obj) {
+    var message = arguments[2] || 'assertRespondsTo';
+    try {
+      (obj[method] && typeof obj[method] == 'function') ? this.pass() : 
+      this.fail(message + ": object doesn't respond to [" + method + "]"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsTrue: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsTrue';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      m() ? this.pass() : 
+      this.fail(message + ": method returned false"); }
+    catch(e) { this.error(e); }
+  },
+  assertReturnsFalse: function(method, obj) {
+    var message = arguments[2] || 'assertReturnsFalse';
+    try {
+      var m = obj[method];
+      if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)];
+      !m() ? this.pass() : 
+      this.fail(message + ": method returned true"); }
+    catch(e) { this.error(e); }
+  },
+  assertRaise: function(exceptionName, method) {
+    var message = arguments[2] || 'assertRaise';
+    try { 
+      method();
+      this.fail(message + ": exception expected but none was raised"); }
+    catch(e) {
+      ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); 
+    }
+  },
+  assertElementsMatch: function() {
+    var expressions = $A(arguments), elements = $A(expressions.shift());
+    if (elements.length != expressions.length) {
+      this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions');
+      return false;
+    }
+    elements.zip(expressions).all(function(pair, index) {
+      var element = $(pair.first()), expression = pair.last();
+      if (element.match(expression)) return true;
+      this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect());
+    }.bind(this)) && this.pass();
+  },
+  assertElementMatches: function(element, expression) {
+    this.assertElementsMatch([element], expression);
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  },
+  _isVisible: function(element) {
+    element = $(element);
+    if(!element.parentNode) return true;
+    this.assertNotNull(element);
+    if(element.style && Element.getStyle(element, 'display') == 'none')
+      return false;
+    
+    return this._isVisible(element.parentNode);
+  },
+  assertNotVisible: function(element) {
+    this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
+  },
+  assertVisible: function(element) {
+    this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
+  },
+  benchmark: function(operation, iterations) {
+    var startAt = new Date();
+    (iterations || 1).times(operation);
+    var timeTaken = ((new Date())-startAt);
+    this.info((arguments[2] || 'Operation') + ' finished ' + 
+       iterations + ' iterations in ' + (timeTaken/1000)+'s' );
+    return timeTaken;
+  }
+}
+
+Test.Unit.Testcase = Class.create();
+Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
+  initialize: function(name, test, setup, teardown) {
+    Test.Unit.Assertions.prototype.initialize.bind(this)();
+    this.name           = name;
+    
+    if(typeof test == 'string') {
+      test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,');
+      test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)');
+      this.test = function() {
+        eval('with(this){'+test+'}');
+      }
+    } else {
+      this.test = test || function() {};
+    }
+    
+    this.setup          = setup || function() {};
+    this.teardown       = teardown || function() {};
+    this.isWaiting      = false;
+    this.timeToWait     = 1000;
+  },
+  wait: function(time, nextPart) {
+    this.isWaiting = true;
+    this.test = nextPart;
+    this.timeToWait = time;
+  },
+  run: function() {
+    try {
+      try {
+        if (!this.isWaiting) this.setup.bind(this)();
+        this.isWaiting = false;
+        this.test.bind(this)();
+      } finally {
+        if(!this.isWaiting) {
+          this.teardown.bind(this)();
+        }
+      }
+    }
+    catch(e) { this.error(e); }
+  }
+});
+
+// *EXPERIMENTAL* BDD-style testing to please non-technical folk
+// This draws many ideas from RSpec http://rspec.rubyforge.org/
+
+Test.setupBDDExtensionMethods = function(){
+  var METHODMAP = {
+    shouldEqual:     'assertEqual',
+    shouldNotEqual:  'assertNotEqual',
+    shouldEqualEnum: 'assertEnumEqual',
+    shouldBeA:       'assertType',
+    shouldNotBeA:    'assertNotOfType',
+    shouldBeAn:      'assertType',
+    shouldNotBeAn:   'assertNotOfType',
+    shouldBeNull:    'assertNull',
+    shouldNotBeNull: 'assertNotNull',
+    
+    shouldBe:        'assertReturnsTrue',
+    shouldNotBe:     'assertReturnsFalse',
+    shouldRespondTo: 'assertRespondsTo'
+  };
+  var makeAssertion = function(assertion, args, object) { 
+   	this[assertion].apply(this,(args || []).concat([object]));
+  }
+  
+  Test.BDDMethods = {};   
+  $H(METHODMAP).each(function(pair) { 
+    Test.BDDMethods[pair.key] = function() { 
+       var args = $A(arguments); 
+       var scope = args.shift(); 
+       makeAssertion.apply(scope, [pair.value, args, this]); }; 
+  });
+  
+  [Array.prototype, String.prototype, Number.prototype, Boolean.prototype].each(
+    function(p){ Object.extend(p, Test.BDDMethods) }
+  );
+}
+
+Test.context = function(name, spec, log){
+  Test.setupBDDExtensionMethods();
+  
+  var compiledSpec = {};
+  var titles = {};
+  for(specName in spec) {
+    switch(specName){
+      case "setup":
+      case "teardown":
+        compiledSpec[specName] = spec[specName];
+        break;
+      default:
+        var testName = 'test'+specName.gsub(/\s+/,'-').camelize();
+        var body = spec[specName].toString().split('\n').slice(1);
+        if(/^\{/.test(body[0])) body = body.slice(1);
+        body.pop();
+        body = body.map(function(statement){ 
+          return statement.strip()
+        });
+        compiledSpec[testName] = body.join('\n');
+        titles[testName] = specName;
+    }
+  }
+  new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name });
+};

BIN
talks/ml-primer/.DS_Store


BIN
talks/ml-primer/._.DS_Store


+ 287 - 0
talks/ml-primer/index.html

@@ -0,0 +1,287 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html> <head>
+<title>Machine Learning for Fun and Profit</title>
+
+<style>
+
+.slide {
+  border: 2px solid #000066;
+  background-color: #CCCCFF;
+  position: absolute;
+  padding: 5%;
+  width: 85%;
+  height: 80%;
+}
+
+.red {
+  background-color: #FF8888;
+}
+
+
+</style>
+
+<script src="scripts/jquery-1.2.3.js" type="text/javascript" />
+<script src="scripts/slideshow.js" type="text/javascript" />
+</head>
+
+<body>
+
+
+<div class='slide'>
+<h1>Machine Learning (a primer)</h1>
+<p>(Portland Python, March 2008) (john melesky)</p>
+</div>
+
+
+
+<div class='slide'>
+<h1>In a nutshell</h1>
+<p>Take facts, turn it into knowledge.</p>
+</div>
+
+<div class='slide'>
+<h1>In a nutshell</h1>
+<p>Take facts, turn it into knowledge, algorithmically.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Discovering things</h1>
+<p>Also known as "unsupervised learning", it's what you do when you have a whole lot of unstructured data you know little about.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Spellcheck, Google-style</h1>
+<p>The problem: check the spelling of things that aren't in the dictionary</p>
+</div>
+
+
+<div class='slide'>
+<h1>Spellcheck, Google-style</h1>
+<p>The problem: check the spelling of things that aren't in the dictionary</p>
+Indigo Montoya<br/>
+Inigo Montana<br/>
+Inigo Montoya<br/>
+Neego Montoya<br/>
+Inigo Mantoya<br/>
+</div>
+
+
+<div class='slide'>
+<h1>Spellcheck</h1>
+<p>Numbers we have include: number of times a query is made, distance between queries (e.g., Levenshtein distance)</p>
+</div>
+
+
+<div class='slide'>
+<h1>Spellcheck</h1>
+<p>Numbers we have include: number of times a query is made, distance between queries (e.g., Levenshtein distance)</p>
+<p>When a new query comes in, find the most common query within a short distance and suggest it.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Spellcheck</h1>
+<p>Numbers we have include: number of times a query is made, distance between queries (e.g., Levenshtein distance)</p>
+<p>When a new query comes in, find the most common query within a short distance and suggest it.</p>
+<p>And that's it.</p>
+</div>
+
+
+
+<div class='slide'>
+<h1>Clustering</h1>
+<p>Problem: given a big pile of documents, figure out what different categories there are.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Clustering</h1>
+<p>Solution: simple geometry</p>
+</div>
+
+<div class='slide'>
+<h1>Clustering</h1>
+<p>Solution: simple (high-dimensional) geometry</p>
+</div>
+
+
+<div class='slide'>
+<h1>Clustering</h1>
+<p>Solution: (a whole lot of) simple (high-dimensional) geometry</p>
+</div>
+
+
+<div class='slide'>
+<h1>Technique: k-Means Clustering</h1>
+<ol>
+  <li>Pick some (k) random points in your vector space.</li>
+  <li>For each document, figure out the nearest point.</li>
+  <li>Lather, rinse, repeat.</li>
+  <li>Voila! Slow-cooked category discovery</li>
+</ol>
+</div>
+
+
+<div class='slide'>
+<h1>Supervised Learning</h1>
+<p>When you already know something about your data, and you want to apply that knowledge to more, less-known data</p>
+</div>
+
+
+<div class='slide'>
+<h1>Classification</h1>
+<p>You have 100 documents in two different categories. Predict the category for the next 5000 documents.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Technique: Nearest Neighbor</h1>
+<ol>
+  <li>Plot your knowns</li>
+  <li>Figure out the closest known to your unknown (geometrically)</li>
+</ol>
+</div>
+
+
+<div class='slide'>
+<h1>Technique: Linear Separation</h1>
+<ol>
+  <li>Plot your knowns</li>
+  <li>Figure out a line separating the categories</li>
+  <li>Use that line to classify the unknowns</li>
+</ol>
+</div>
+
+<div class='slide'>
+<h1>Linear Separation: Step 1</h1>
+<img src="media/basedata.png" />
+</div>
+
+<div class='slide'>
+<h1>Linear Separation: Step 2</h1>
+<img src="media/cleansep.png" />
+</div>
+
+
+<div class='slide'>
+<h1>Non-linearly separable data</h1>
+<img src="media/badset1.png" />
+</div>
+
+<div class='slide'>
+<h1>Non-linearly separable data</h1>
+<img src="media/badset2.png" />
+</div>
+
+<div class='slide'>
+<h1>Non-linearly separable data</h1>
+<img src="media/badset3.png" />
+</div>
+
+
+<div class='slide'>
+<h1>Light-bulb jokes</h1>
+</div>
+
+
+<div class='slide'>
+<h1>Technique: Support Vector Machines</h1>
+</div>
+
+<div class='slide'>
+<h1>Technique: Support Vector Machines</h1>
+<img height='450' src="media/dsc01228-02-h.jpg" />
+</div>
+
+<div class='slide'>
+<h1>Technique: Naive Bayesian Classifiers</h1>
+<p>Not geometric, but statistical.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Bayes' Theorem</h1>
+<p>Future probabilities derived from prior probabilities</p>
+</div>
+
+
+<div class='slide'>
+<h1>Bayes' Theorem</h1>
+<p>If a drug test has 95% accuracy, and Bob tests positive, what is the probability that he uses drugs?</p>
+</div>
+
+<div class='slide'>
+<h1>Bayes' Theorem</h1>
+<p>If a drug test has 95% accuracy, and Bob tests positive, what is the probability that he uses drugs?</p>
+<p>(hint: it's not 95%)</p>
+</div>
+
+
+<div class='slide'>
+<h1>Bayes' Theorem</h1>
+<p>Answer: Depends on how many people use drugs.</p>
+</div>
+
+
+<div class='slide'>
+<h1>Bayes' Theorem</h1>
+<p>Answer: Depends on how many people use drugs.</p>
+<p>If the rate of drug use is 1%, then we have:</p>
+<center>
+<table border=1>
+<tr><th></th><th>test positive</th><th>test negative</th></tr>
+<tr><th>users</th><td>95% of 1%</td><td>5% of 1%</td></tr>
+<tr><th>non-users</th><td>5% of 99%</td><td>95% of 99%</td></tr>
+</table>
+</center>
+</div>
+
+
+<div class='slide'>
+<h1>Bayes' Theorem</h1>
+<p>Answer: Depends on how many people use drugs.</p>
+<p>Number of positive results: 0.95% + 4.95% == 5.9%</p>
+<p>Number of <i>correct</i> positive results: 0.95% / 5.9% == 16.1%</p>
+</div>
+
+
+
+
+<div class='slide'>
+<h1></h1>
+</div>
+
+
+
+
+<div class='slide'>
+<h1>The basic process</h1>
+<ol>
+  <li>Look at your data, figure out a good numeric representation</li>
+  <li>Turn your data into numbers (usually vectors of numbers)</li>
+  <li>Run your algorithms</li>
+  <li>Profit! (or Fun!)</li>
+</ol>
+</div>
+
+
+<div class='slide'>
+<h1>Figuring out a good representation</h1>
+</div>
+
+
+
+
+<div class='slide'>
+<h1></h1>
+</div>
+
+
+
+
+
+
+
+</body></html>

BIN
talks/ml-primer/media/badline1.png


BIN
talks/ml-primer/media/badline2.png


BIN
talks/ml-primer/media/badset1.png


BIN
talks/ml-primer/media/badset2.png


BIN
talks/ml-primer/media/badset3.png


BIN
talks/ml-primer/media/basedata.png


BIN
talks/ml-primer/media/bordervectors.png


BIN
talks/ml-primer/media/cleansep.png


BIN
talks/ml-primer/media/dsc01228-02-h.jpg


BIN
talks/ml-primer/media/supportvectors.png


BIN
talks/ml-primer/scripts/.DS_Store


BIN
talks/ml-primer/scripts/._.DS_Store


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است