<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88
/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}

a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}

h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}

.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}

.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}

.tabSelected{color:[[ColorPalette::PrimaryDark]];
	background:[[ColorPalette::TertiaryPale]];
	border-left:1px solid [[ColorPalette::TertiaryLight]];
	border-top:1px solid [[ColorPalette::TertiaryLight]];
	border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}

#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}

.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
	border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background:[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
	border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
	border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
	
.wizard .notChanged {background:transparent;}
.wizard .changedLocally {background:#80ff80;}
.wizard .changedServer {background:#8080ff;}
.wizard .changedBoth {background:#ff8080;}
.wizard .notFound {background:#ffff80;}
.wizard .putToServer {background:#ff80ff;}
.wizard .gotFromServer {background:#80ffff;}

#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}

.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}

.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}

.tiddler .defaultCommand {font-weight:bold;}

.shadow .title {color:[[ColorPalette::TertiaryDark]];}

.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}

.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}

.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}

.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}

.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}

.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}

.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}

.imageLink, #displayArea .imageLink {background:transparent;}

.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}

.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}

.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}

.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}

.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}

.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}

#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/
/*{{{*/
* html .tiddler {height:1%;}

body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}

h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}

hr {height:1px;}

a {text-decoration:none;}

dt {font-weight:bold;}

ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}

.txtOptionInput {width:11em;}

#contentWrapper .chkOptionInput {border:0;}

.externalLink {text-decoration:underline;}

.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}

.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}

/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}

#mainMenu .tiddlyLinkExisting,
	#mainMenu .tiddlyLinkNonExisting,
	#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}

.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}

.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}

#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}

#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}

.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}

#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}

.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}

.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}

.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}

#contentWrapper {display:block;}
#splashScreen {display:none;}

#displayArea {margin:1em 17em 0em 14em;}

.toolbar {text-align:right; font-size:.9em;}

.tiddler {padding:1em 1em 0em 1em;}

.missing .viewer,.missing .title {font-style:italic;}

.title {font-size:1.6em; font-weight:bold;}

.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}

.tiddler .button {padding:0.2em 0.4em;}

.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}

.footer {font-size:.9em;}
.footer li {display:inline;}

.annotation {padding:0.5em; margin:0.5em;}

* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}

.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}

.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}

.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}

.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}

.sparkline {line-height:1em;}
.sparktick {outline:0;}

.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}

* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; width:90%; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}

.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/
/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
/*}}}*/
/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton, #backstageArea {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/
<!--{{{-->
<div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'>
<div class='headerShadow'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div class='headerForeground'>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>&nbsp;
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
</div>
<div id='mainMenu' refresh='content' tiddler='MainMenu'></div>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
<div class='tagged' macro='tags'></div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagClear'></div>
<!--}}}-->
<!--{{{-->
<div class='toolbar' macro='toolbar [[ToolbarCommands::EditToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='editor' macro='edit title'></div>
<div macro='annotations'></div>
<div class='editor' macro='edit text'></div>
<div class='editor' macro='edit tags'></div><div class='editorFooter'><span macro='message views.editor.tagPrompt'></span><span macro='tagChooser'></span></div>
<!--}}}-->
To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle & SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: <<option txtUserName>>
These InterfaceOptions for customising TiddlyWiki are saved in your browser

Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)

<<option txtUserName>>
<<option chkSaveBackups>> SaveBackups
<<option chkAutoSave>> AutoSave
<<option chkRegExpSearch>> RegExpSearch
<<option chkCaseSensitiveSearch>> CaseSensitiveSearch
<<option chkAnimate>> EnableAnimations

----
Also see AdvancedOptions
<<importTiddlers>>
DefaultTiddlers 2241 500 225 99
HelloThere 518 -1007 1100 568
JeremyRuston 1667 -742 381 163
MainMenu 2506 499 225 69
MyMap 2755 498 225 325
OverlayMenu 2997 506 225 110
PageTemplate 3235 511 225 139
Principles 2593 672 225 164
ProjectCecily 1636 -115 459 532
Seagully -266 1149 842 609
Serializer 100 150 50 100
SideBarTabs -1272 -619 225 321
SiteSubtitle 3643 850 225 69
SiteTitle 3649 963 225 69
Swampglade -1085 563 448 395
TODO 300 150 50 1000
Tenerife -1040 1039 716 627
Tiddlers 2000 250 450 1000
TiddlyWiki 1668 -896 388 132
ToDo -879 -1493 929 932
UrlMap 400 150 50 1000
ViewTemplate 3247 666 225 159
WebKit 1657 746 389 602
Weyflection -595 577 498 438
ZoomingUserInterface%20from%20Wikipedia 530 -396 1042 1443
ZoomingUserInterfaces -684 -502 1098 907

/***
|''Name:''|CecilyPlugin|
|''Description:''|A zooming user interface for TiddlyWiki|
|''Author:''|Jeremy Ruston (jeremy (at) osmosoft (dot) com)|
|''Source:''|http://svn.tiddlywiki.org/Trunk/contributors/JeremyRuston/plugins/CecilyPlugin.js|
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/JeremyRuston/plugins/CecilyPlugin.js|
|''Version:''|0.0.9|
|''Status:''|Under Development|
|''Date:''|July 20, 2008|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev|
|''License:''|BSD|
|''~CoreVersion:''|2.4.0|
***/

//{{{
if(!version.extensions.CecilyPlugin) {
version.extensions.CecilyPlugin = {installed:true};

//-----------------------------------------------------------------------------------
// Point and Rectangle classes
//-----------------------------------------------------------------------------------

// Point class {x:,y:}
function Point(x,y) {
	if(x instanceof Point) {
		this.x = x.x;
		this.y = x.y;
	} else {
		this.x = x;
		this.y = y;
	}
}

// Rectangle class {x:,y:,w:,h:} (w and h are both set to zero for empty rectangles)
function Rect(x,y,w,h) {
	if(x instanceof Rect) {
		this.x = x.x;
		this.y = x.y;
		this.w = x.w;
		this.h = x.h;
	} else {
		this.x = x ? x : 0;
		this.y = y ? y : 0;
		this.w = w ? w : 0;
		this.h = h ? h : 0;
	}
}

// Determines if this rectangle is empty
Rect.prototype.isEmpty = function() {
	return !this.w || !this.h;
}

// Returns the smallest rectangle that contains both this and the source rectangles
Rect.prototype.union = function(src) {
	if(this.isEmpty())
		return new Rect(src);
	if(src.isEmpty())
		return new Rect(this);
	var r = new Rect(Math.min(this.x,src.x),Math.min(this.y,src.y));
	r.w = Math.max(this.x+this.w-r.x,src.x+src.w-r.x);
	r.h = Math.max(this.y+this.h-r.y,src.y+src.h-r.y);
	return r;
}

// Determines if the source rectangle is completely contained within this rectangle
Rect.prototype.contains = function(src) {
	return (src.x > this.x) && ((this.x+this.w) > (src.x+src.w))
		&& (src.y > this.y) && ((this.y+this.h) > (src.y+src.h));
}

// Interpolates between this (t=0) and the source retangle (t=1)
Rect.prototype.interpolate = function(src,t) {
	var interpolate = function(a,b,t) {return a + (b - a) * t;};
	return new Rect(interpolate(this.x,src.x,t), interpolate(this.y,src.y,t),
		interpolate(this.w,src.w,t), interpolate(this.h,src.h,t));
}

// Scales a rectangle around it's centre
Rect.prototype.scale = function(scale) {
	var w = this.w * scale;
	var h = this.h * scale;
	return new Rect(this.x - (w-this.w)/2,this.y - (h-this.h)/2,w,h);
}

// Returns the midpoint of a rectangle
Rect.prototype.midPoint = function() {
	return new Point(this.x + this.w/2, this.y + this.h/2);
}

//-----------------------------------------------------------------------------------
// Generic helper functions
//-----------------------------------------------------------------------------------

// Given a point in the coordinates of a target element, compute the coordinates relative to a specified base element
function normalisePoint(base,target,pt) {
	var e = target;
	var parent = target.offsetParent;
	var r = new Point(pt.x,pt.y);
	while(e !== base && parent) {
		r.x += parent.offsetLeft;
		r.y += parent.offsetTop;
		e = parent;
		parent = e.offsetParent;
	}
	if(e == base)
		return r;
	else
		return null;
}

// Checks which of an array of classes are applied to a given element. Returns an array of the classes that are found
function hasClasses(e,classNames)
{
	var classes = e.className ? e.className.split(" ") : [];
	var results = [];
	for(var t=0; t<classNames.length; t++) {
		if(classes.indexOf(classNames[t]) != -1) {
			results.push(classNames[t]);
		}
	}
	return results;
}

//-----------------------------------------------------------------------------------
// Slider control
//-----------------------------------------------------------------------------------

function SliderControl(sliderInfo) {
	merge(this,sliderInfo);
	if(!this.getterTransform)
		this.getterTransform = function(x) {return x;};
	if(!this.setterTransform)
		this.setterTransform = function(x) {return x;};
	this.slider = createTiddlyElement(this.place,"input");
	this.slider.type = "range";
	this.slider.min = this.min;
	this.slider.max = this.max;
	this.slider.style["-webkit-appearance"] = "slider-horizontal";
	var me = this;
	var handler = function (ev) {
		me.onChange(me.getterTransform(parseInt(me.slider.value,10)));
	};
	this.slider.oninput = handler;
	this.slider.onchange = handler;
}

SliderControl.prototype.set = function(value) {
	var n = this.setterTransform(value).toString();
	if(this.slider.value != n)
		this.slider.value = n;
};

//-----------------------------------------------------------------------------------
// Zoom macro
//-----------------------------------------------------------------------------------

config.macros.cecilyZoom = {};

config.macros.cecilyZoom.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	var zoomElem = createTiddlyElement(place,"span",null,"cecilyLabel cecilyZoom","zoom ");
	var me = this;
	zoomElem.sliderControl = new SliderControl({
		place: place,
		min: 0,
		max: 100,
		getterTransform: function(slider) {
			return Math.pow(Math.E,(slider/100)*12-6);
		},
		setterTransform: function(value) {
			var n = ((Math.log(value)+6)/12)*100;
			n = Math.min(100,Math.max(0,Math.floor(n + 0.5)));
			return n;
		},
		onChange: function(value) {
			if(cecily) {
				var w = cecily.frame.offsetWidth;
				var h = cecily.frame.offsetHeight;
				var cx = cecily.view.x + cecily.view.w/2;
				var cy = cecily.view.y + cecily.view.h/2;
				var newView = new Rect(0,0,w / value,h / value);
				newView.x = cx - newView.w/2;
				newView.y = cy - newView.h/2;
				cecily.setView(newView);
			}
		}
	});
}

config.macros.cecilyZoom.propagate = function(scale) {
	var zoomers = document.getElementsByClassName("cecilyZoom");
	for(var t = 0; t < zoomers.length; t++) {
		zoomers[t].sliderControl.set(scale);
	}
}

//-----------------------------------------------------------------------------------
// Zoom All macro
//-----------------------------------------------------------------------------------

config.macros.cecilyZoomAll = {};

config.macros.cecilyZoomAll.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	createTiddlyButton(place,"zoom everything","Zoom out to see everything",function(ev) {
		if(cecily)
			cecily.scrollToAllTiddlers();
	});
}

//-----------------------------------------------------------------------------------
// Switch background macro
//-----------------------------------------------------------------------------------

config.macros.cecilyBackground = {
};

config.macros.cecilyBackground.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if(cecily) {
		createTiddlyElement(place,"span",null,"cecilyLabel","background ");
		var onchange = function(ev) {
			var sel = this.options[this.selectedIndex].value;
			if(sel != cecily.background) {
				cecily.setBackground(sel);
			}
		};
		var options = [];
		for(var t in Cecily.backgrounds) {
			options.push({name: t, caption: Cecily.backgrounds[t].title});
		}
		var d = createTiddlyDropDown(place,onchange,options,cecily.background);
		addClass(d,"cecilyBackground");
	}
};

config.macros.cecilyBackground.propagate = function(background) {
	var backgrounders = document.getElementsByClassName("cecilyBackground");
	for(var k=0; k<backgrounders.length; k++) {
		var b = backgrounders[k];
		for(var s=0; s<b.options.length; s++) {
			if(b.options[s].value === background && b.selectedIndex !== s)
				b.selectedIndex = s;
		}
	}
};

//-----------------------------------------------------------------------------------
// Switch map macro
//-----------------------------------------------------------------------------------

config.macros.cecilyMap = {
};

config.macros.cecilyMap.handler = function(place,macroName,params,wikifier,paramString,tiddler) {
	if(cecily) {
		createTiddlyElement(place,"span",null,"cecilyLabel","map ");
		var onchange = function(ev) {
			var sel = this.options[this.selectedIndex].value;
			if(sel != cecily.mapTitle) {
				cecily.setMap(sel);
			}
			cecily.scrollToAllTiddlers();
		};
		var options = [];
		var mapTiddlers = store.getTaggedTiddlers("cecilyMap")
		for(var t=0; t<mapTiddlers.length; t++) {
			options.push({name: mapTiddlers[t].title, caption: mapTiddlers[t].title});
		}
		var d = createTiddlyDropDown(place,onchange,options,cecily.mapTitle);
		addClass(d,"cecilyMap");
	}
};

config.macros.cecilyMap.propagate = function(map) {
	var mappers = document.getElementsByClassName("cecilyMap");
	for(var k=0; k<mappers.length; k++) {
		var m = mappers[k];
		for(var s=0; s<m.options.length; s++) {
			if(m.options[s].value === map && m.selectedIndex !== s)
				m.selectedIndex = s;
		}
	}
};

//-----------------------------------------------------------------------------------
// Cecily main class
//-----------------------------------------------------------------------------------

function Cecily()
{
	this.background = config.options.txtCecilyBackground ? config.options.txtCecilyBackground : "plain";
	this.mapTitle = config.options.txtCecilyMap ? config.options.txtCecilyMap : "MyMap";
	this.drag = null;
	this.map = null;
}

Cecily.prototype.createDisplay = function() {
	this.overlayMenu = document.getElementById("overlayMenu");
	this.addEventHandler(this.overlayMenu,"mouseout",this.onMouseOutOverlay,false);
	this.loadMap(this.mapTitle);
	this.container = document.getElementById(story.containerId());
	this.frame = this.container.parentNode;
	addClass(this.frame,"cecily");
	this.canvas = createTiddlyElement(null,"canvas",null,"cecilyCanvas");
	this.frame.insertBefore(this.canvas,this.frame.firstChild);
	this.setViewSize();
	this.setView(new Rect(0,0,25000,12000));
	this.initScroller();
	var me = this;
	this.addEventHandler(window,"resize",this.onWindowResize,false);
	this.addEventHandler(window,"mousewheel",this.onMouseWheel,true);
	this.addEventHandler(document,"click",this.onMouseClickBubble,false);
	this.addEventHandler(document,"dblclick",this.onMouseDoubleClickBubble,false);
	this.addEventHandler(document,"mousedown",this.onMouseDownCapture,true);
	this.addEventHandler(document,"mousemove",this.onMouseMoveCapture,true);
	this.addEventHandler(document,"mouseup",this.onMouseUpCapture,true);
	this.defaultTiddler = null;
	window.setTimeout(function() {me.scrollToTiddler(me.defaultTiddler);},10);
}

Cecily.prototype.setViewSize = function() {
	var h = findWindowHeight();
	this.frame.style.height = h + "px";
	this.canvas.width = this.frame.offsetWidth;
	this.canvas.height = this.frame.offsetHeight;
}

Cecily.prototype.addEventHandler = function(element,type,handler,capture) {
	var me = this;
	element.addEventListener(type,function (ev) {
		if(ev.offsetX === undefined)
			ev.offsetX = ev.clientX;
		if(ev.offsetY === undefined)
			ev.offsetY = ev.clientY;
		if(ev.toElement === undefined)
			ev.toElement = ev.relatedTarget;
		return handler.call(me,ev);
		},capture);
}

Cecily.prototype.onWindowResize = function(ev) {
	this.setViewSize();
	this.drawBackground();
	return false;
}

Cecily.prototype.onMouseWheel = function(ev) {
	var newView = new Rect(this.view);
	newView.x -= (ev.wheelDeltaX/120) * (this.view.w/16);
	newView.y -= (ev.wheelDeltaY/120) * (this.view.w/16);
	this.setView(newView);
	return false;
};

Cecily.prototype.onMouseClickBubble = function(ev) {
	var tiddler = story.findContainingTiddler(ev.target);
	if(tiddler && this.drag === null && hasClasses(ev.target,["tiddlyLink","toolbar","title","tagged"]).length == 0) { 
		tiddler.parentNode.insertBefore(tiddler,null); 
		this.scrollToTiddler(tiddler); 
	} 
};

Cecily.prototype.onMouseDoubleClickBubble = function(ev) {
	this.showOverlayMenu(new Point(ev.offsetX,ev.offsetY));
};

Cecily.prototype.onMouseDownCapture = function(ev) {
	for(var d=0; d<Cecily.draggerList.length; d++) {
		var dragger = Cecily.draggers[Cecily.draggerList[d]];
		if(dragger.isDrag(this,ev.target,ev)) {
			this.drag = {dragger: dragger};
			dragger.dragDown(this,ev.target,ev);
			break;
		}
	}
	if(this.drag !== null) {
		ev.stopPropagation();
		ev.preventDefault();
		return false;
	}
};

Cecily.prototype.onMouseMoveCapture = function(ev) {
	if(this.drag) {
		this.drag.dragger.dragMove(this,ev.target,ev);
		ev.stopPropagation();
		ev.preventDefault();
		return false;
	}
};

Cecily.prototype.onMouseUpCapture = function(ev) {
	if(this.drag) {
		this.drag.dragger.dragUp(this,ev.target,ev);
		this.drag = null;
		ev.stopPropagation();
		ev.preventDefault();
		return false;
	}
};

Cecily.draggers = {};
Cecily.draggerList = ["tiddlerDragger","tiddlerResizer","backgroundDragger"];

Cecily.draggers.tiddlerDragger = {
	isDrag: function(cecily,target,ev) {
		return hasClass(target,"toolbar") || hasClass(target,"title");
	},
	dragDown: function(cecily,target,ev) {
		var tiddler = story.findContainingTiddler(target);
		tiddler.parentNode.insertBefore(tiddler,null);
		cecily.drag.tiddler = tiddler;
		cecily.drag.tiddlerTitle = tiddler.getAttribute("tiddler");
		cecily.drag.lastPoint = normalisePoint(cecily.frame,target,new Point(ev.offsetX,ev.offsetY));
		addClass(tiddler,"drag");
	},
	dragMove: function(cecily,target,ev) {
		var dragThis = normalisePoint(cecily.frame,target,new Point(ev.offsetX,ev.offsetY));
		if(dragThis) {
			var s = cecily.frame.offsetWidth/cecily.view.w;
			cecily.drag.tiddler.realPos = new Point(cecily.drag.tiddler.realPos.x + (dragThis.x - cecily.drag.lastPoint.x) / s,
													cecily.drag.tiddler.realPos.y + (dragThis.y - cecily.drag.lastPoint.y) / s);
			cecily.transformTiddler(cecily.drag.tiddler);
			cecily.drag.lastPoint = dragThis;
		}
	},
	dragUp: function(cecily,target,ev) {
		removeClass(cecily.drag.tiddler,"drag");
		cecily.updateTiddlerPosition(cecily.drag.tiddlerTitle,cecily.drag.tiddler);
	}
};

Cecily.draggers.tiddlerResizer = {
	isDrag: function(cecily,target,ev) {
		return findRelated(target,"tagged","className","parentNode") !== null;
	},
	dragDown: function(cecily,target,ev) {
		var tiddler = story.findContainingTiddler(target);
		tiddler.parentNode.insertBefore(tiddler,null);
		cecily.drag.tiddler = tiddler;
		cecily.drag.tiddlerTitle = tiddler.getAttribute("tiddler");
		cecily.drag.startPoint = normalisePoint(cecily.frame,target,new Point(ev.offsetX,ev.offsetY));
		cecily.drag.startWidth = tiddler.scaledWidth;
		addClass(tiddler,"drag");
	},
	dragMove: function(cecily,target,ev) {
		var s = cecily.frame.offsetWidth/cecily.view.w;
		var dragThis = normalisePoint(cecily.frame,target,new Point(ev.offsetX,ev.offsetY));
		if(dragThis) {
			var newWidth = cecily.drag.startWidth + (dragThis.x - cecily.drag.startPoint.x) / s;
			if(newWidth < 0.01)
				newWidth = 0.01;
			cecily.drag.tiddler.scaledWidth = newWidth;
			cecily.transformTiddler(cecily.drag.tiddler);
		}
	},
	dragUp: function(cecily,target,ev) {
		removeClass(cecily.drag.tiddler,"drag");
		cecily.updateTiddlerPosition(cecily.drag.tiddlerTitle,cecily.drag.tiddler);
	}
};

Cecily.draggers.backgroundDragger = {
	isDrag: function(cecily,target,ev) {
		return target === cecily.canvas;
	},
	dragDown: function(cecily,target,ev) {
		cecily.drag.lastPoint = {x: ev.offsetX, y: ev.offsetY};
	},
	dragMove: function(cecily,target,ev) {
		var s = cecily.frame.offsetWidth/cecily.view.w;
		var newView = new Rect(cecily.view);
		newView.x -= (ev.offsetX - cecily.drag.lastPoint.x)/s;
		newView.y -= (ev.offsetY - cecily.drag.lastPoint.y)/s;
		cecily.drag.lastPoint = {x: ev.offsetX, y: ev.offsetY};
		cecily.setView(newView);
	},
	dragUp: function(cecily,target,ev) {
	}
};

Cecily.prototype.showOverlayMenu = function(pos)
{
	this.overlayMenu.style.display = "block";
	var overlayPos = new Rect(pos.x - this.overlayMenu.offsetWidth/2,pos.y - this.overlayMenu.offsetHeight/2,
							this.overlayMenu.offsetWidth,this.overlayMenu.offsetHeight);
	var w = this.frame.offsetWidth;
	var h = this.frame.offsetHeight;
	if(overlayPos.w > w || overlayPos.h > h) {
		overlayPos = overlayPos.scale(Math.min(w/overlayPos.w,h/overlayPos.h));
	}
	if(overlayPos.x < 0)
		overlayPos.x = 0;
	if(overlayPos.y < 0)
		overlayPos.y = 0;
	if(overlayPos.x + overlayPos.w > w)
		overlayPos.x = w - overlayPos.w;
	if(overlayPos.y + overlayPos.h > h)
		overlayPos.y = h - overlayPos.h;
	var scale = overlayPos.h / this.overlayMenu.offsetHeight;
	this.overlayMenu.style[Cecily.cssTransform] = "scale(" + scale + "," + scale + ")";
	this.overlayMenu.style.left = overlayPos.x + "px";
	this.overlayMenu.style.top = overlayPos.y + "px";
	this.overlayMenu.style.opacity = "0.9";
};

Cecily.prototype.onMouseOutOverlay = function(ev)
{
	if(findRelated(ev.toElement,"overlayMenu","id","parentNode") == null) {
		this.overlayMenu.style.opacity = "0.0";
		this.overlayMenu.style.display = "none";
	}
};

Cecily.prototype.displayTiddler = function(superFunction,args) {
	var tiddler = args[1];
	var srcElement = args[0];
	args[0] = "bottom"; // srcElement to disable animation and scrolling
	var title = (tiddler instanceof Tiddler) ? tiddler.title : tiddler;
	var tiddlerElemBefore = story.getTiddler(title);
	superFunction.apply(story,args);
	var tiddlerElem = story.getTiddler(title);
	if(!tiddlerElem)
	 	return;
	var pos = this.getTiddlerPosition(title,srcElement);
	tiddlerElem.realPos = new Point(pos.x,pos.y);
	tiddlerElem.scaledWidth = pos.w;
	tiddlerElem.rotate = 0;
	tiddlerElem.enlarge = 1.0;
	this.transformTiddler(tiddlerElem);
	this.updateTiddlerPosition(title,tiddlerElem);
	if(!startingUp) {
		if(tiddlerElem.nextSibling) { // Move tiddler to the bottom of the Z-order if it's not already there
			tiddlerElem.parentNode.insertBefore(tiddlerElem,null);
		}
		this.scrollToTiddler(title);
	}
	this.defaultTiddler = tiddlerElem;
};

// Load the current map from a named tiddler
Cecily.prototype.loadMap = function(title) {
	this.map = {};
	var mapText = store.getTiddlerText(title,"");
    var positionRE = /^(\S+)\s(-?[0-9\.E]+)\s(-?[0-9\.E]+)\s(-?[0-9\.E]+)\s(-?[0-9\.E]+)$/mg;
    do {
        var match = positionRE.exec(mapText);
		if(match) {
			var title = decodeURIComponent(match[1]);
			this.map[title] = {
				x: parseFloat(match[2]),
				y: parseFloat(match[3]),
				w: parseFloat(match[4]),
				h: parseFloat(match[5])
			};
		}
	} while(match);
}

// Save the current map into a named tiddler
Cecily.prototype.saveMap = function(title) {
	var mapTiddler = store.getTiddler(title);
	if((mapTiddler == null) || (mapTiddler.isTagged("cecilyMap"))) {
		var text = [];
		for(var t in this.map) {
			var m = this.map[t];
			text.push(encodeURIComponent(t) + " " + Math.floor(m.x) + " " + Math.floor(m.y) + " " + Math.floor(m.w) + " " + Math.floor(m.h));
		}
		text.sort();
		store.saveTiddler(title,title,text.join("\n"),"Cecily");
		autoSaveChanges(null,[mapTiddler]);
	}
}

// Gets the Rect() position of a named tiddler
Cecily.prototype.getTiddlerPosition = function(title,srcElement) {
	var p = this.map[title];
	if(p)
		return new Rect(p.x,p.y,p.w,p.h);
	else {
		this.nextPos = this.nextPos ? this.nextPos + 250 : 250;
		return new Rect(this.nextPos,500,225,250);
	}
}

// Updates the position of a named tiddler into the current map
Cecily.prototype.updateTiddlerPosition = function(title,tiddlerElem) {
	var pos = new Rect(tiddlerElem.realPos.x,
						tiddlerElem.realPos.y,
						tiddlerElem.scaledWidth,
						tiddlerElem.offsetHeight * (tiddlerElem.scaledWidth/tiddlerElem.offsetWidth));
	this.map[title] = pos;
	this.saveMap(this.mapTitle);
}

// Switch to a new map
Cecily.prototype.setMap = function(title)
{
	this.mapTitle = title;
	config.options.txtCecilyMap = title;
	saveOptionCookie("txtCecilyMap");
	this.loadMap(title);
	var me = this;
	story.forEachTiddler(function(tiddler,elem) {
		var pos = me.getTiddlerPosition(tiddler);
		elem.realPos = new Point(pos.x,pos.y);
		elem.scaledWidth = pos.w;
		elem.rotate = 0;
		elem.enlarge = 1.0;
		me.transformTiddler(elem);
	});
	this.drawBackground();
	config.macros.cecilyMap.propagate(title);
}

// Applies the tiddler transformation properties (rotate & enlarge) to the display
Cecily.prototype.transformTiddler = function(tiddlerElem) {
	var pos = tiddlerElem.realPos;
	var s = tiddlerElem.scaledWidth/360;
	var r = tiddlerElem.rotate;
	var e = tiddlerElem.enlarge;
	tiddlerElem.style[Cecily.cssTransform] = "translate(-50%,-50%) scale(" + s + "," + s + ") translate(50%,50%) translate(" + pos.x / s + "px," + pos.y / s + "px) rotate(" + r + "rad) scale(" + e + ")";
	// Experimental beginnings of support for semantic zooming
	var w = tiddlerElem.scaledWidth * (this.frame.offsetWidth)/(this.view.w);
	var f = w < 60 ? addClass : removeClass;
	f(tiddlerElem,"tooSmallToRead");
};

// Moves the viewport to accommodate the specified rectangle
Cecily.prototype.setView = function(newView) {
	var w = this.frame.offsetWidth;
	var h = this.frame.offsetHeight;
	var centre = newView.midPoint();
	this.view = new Rect(newView);
	if((w/h) > (newView.w/newView.h)) {
		this.view.w = newView.h * (w/h);
	} else {
		this.view.h = newView.w * (h/w);
	}
	this.view.x = centre.x - this.view.w/2;
	this.view.y = centre.y - this.view.h/2;
	var s = w/this.view.w;
	var transform = "scale(" + s + ") translate(" + -this.view.x + "px," + -this.view.y + "px)";
	this.container.style[Cecily.cssTransform] = transform;
	config.macros.cecilyZoom.propagate(s);
	this.drawBackground();
};

Cecily.prototype.startHightlight = function(elem) {
	var me = this;
	var animationStart = new Date();
	var animationDuration = 3 * 1000;
	var highlight = {};
	var highlightElem = findRelated(elem.firstChild,"viewer","className","nextSibling");
	highlight.tick = function() {
		if(!highlightElem.parentNode)
			return false;
		var now = new Date();
		var t = (now - animationStart) / animationDuration;
		if(t < 1) {
			var p = (Math.sin(t*Math.PI*4 + Math.PI/2)+1)/2;
			highlightElem.style.backgroundColor = (new RGB("#ffff88")).mix(new RGB("#ffffff"),(p+1)/2).toString();
			return true;
		} else {
			highlightElem.style.backgroundColor = "";
			return false;
		}
	}
	if(highlightElem)
		anim.startAnimating(highlight);
};

Cecily.prototype.scrollToAllTiddlers = function() {
	var currRect = null;
	story.forEachTiddler(function (title,tiddlerElem) {
		var tiddlerRect = new Rect(tiddlerElem.realPos.x,
								tiddlerElem.realPos.y,
								tiddlerElem.scaledWidth,
								tiddlerElem.offsetHeight * (tiddlerElem.scaledWidth/tiddlerElem.offsetWidth));
		if(!currRect)
			currRect = tiddlerRect;
		else
			currRect = tiddlerRect.union(currRect);
	});
	if(currRect)
		this.startScroller([currRect.scale(1.2)]);
}

// Highlight a particular tiddler and scroll it into view
//  tiddler - title of tiddler or reference to tiddlers DOM element
Cecily.prototype.scrollToTiddler = function(tiddler) {
	var tiddlerElem = typeof tiddler == "string" ? story.getTiddler(tiddler) : tiddler;
	if(tiddlerElem) {
		this.startHightlight(tiddlerElem);
		var targetRect = new Rect(tiddlerElem.realPos.x,
									tiddlerElem.realPos.y,
									tiddlerElem.scaledWidth,
									tiddlerElem.offsetHeight * (tiddlerElem.scaledWidth/tiddlerElem.offsetWidth));
		if(this.view.contains(targetRect)) {
			this.startScroller([targetRect.scale(1.2)]);
		} else {
			var passingRect = this.view.union(targetRect);
			this.startScroller([passingRect.scale(1.1),targetRect.scale(1.2)]);
		}
	}
}

Cecily.prototype.initScroller = function() {
	var me = this;
	this.scroller = {
		scrolling: false
	};
	me.scroller.tick = function() {
		var now = new Date();
		var t = (now - me.scroller.animationStart) / me.scroller.animationDuration;
		if(t > 1)
			t = 1;
		me.setView(me.scroller.rectList[me.scroller.currRect].interpolate(me.scroller.rectList[me.scroller.currRect + 1],t));
		if(t == 1) {
			me.scroller.currRect++;
			if(me.scroller.currRect + 1 >= me.scroller.rectList.length) {
				me.scroller.scrolling = false;
				return false;
			}
			me.scroller.animationStart = now;
		}
		return true;
	};
}

Cecily.prototype.startScroller = function(rectList,duration) { // One or more rectangles to scroll to in turn
	var s = this.scroller;
	s.rectList = [this.view];
	for(var r = 0; r < rectList.length; r++)
		s.rectList.push(rectList[r]);
	s.animationStart = new Date();
	s.animationDuration = duration ? duration : 0.75 * 1000;
	s.currRect = 0;
	if(!s.scrolling) {
		s.scrolling = true;
		anim.startAnimating(s);
	}
};

Cecily.prototype.setBackground = function(background) {
	cecily.background = background;
	config.options.txtCecilyBackground = background;
	saveOptionCookie("txtCecilyBackground");
	cecily.drawBackground();
	config.macros.cecilyBackground.propagate(background);
};

Cecily.prototype.drawBackground = function() {
	var b = Cecily.backgrounds[this.background];
	if(b) {
		b.drawBackground(this.canvas,this.view);
	} else {
		var ctx = this.canvas.getContext('2d');
		ctx.fillStyle = "#cccccc";
		ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
	}
};

//-----------------------------------------------------------------------------------
// Background plumbing and generators
//-----------------------------------------------------------------------------------

Cecily.backgrounds = {};

Cecily.backgrounds.plain = {
		title: "Plain",
		description: "Plain",
		drawBackground: function(canvas,view) {
			var w = canvas.width;
			var h = canvas.height;
			var ctx = canvas.getContext('2d');
			ctx.fillStyle = "#aaaacc";
			ctx.fillRect(0, 0, w, h);
		}
};

Cecily.backgrounds.fractal = {
		title: "Fractal",
		description: "Fractal cracks",
		drawBackground: function(canvas,view) {
			var w = canvas.width;
			var h = canvas.height;
			var scale = w/view.w;
			var ctx = canvas.getContext('2d');
			ctx.fillStyle = "#cc8888";
			ctx.fillRect(0, 0, w, h);
			var Turtle = function Turtle(x,y,direction) {
				this.x = x ? x : 0;
				this.y = y ? y : 0;
				this.direction = direction ? direction : 0;
			};
			Turtle.prototype.line = function(d) {
					this.x += Math.sin(this.direction) * d;
					this.y -= Math.cos(this.direction) * d;
				};
			Turtle.prototype.turn = function(a) {
					this.direction += a;
				};
			// Gosper curve as a series of angles to turn (in degrees anti clockwise, for humans)
			var fractalPath =  [0,300,240,60,120,0,60]; // [0,-60,60,-240,240];
			// Work out the overall angle and length of the curve
			var turtle = new Turtle(0,0,0);
			for(var t=0; t<fractalPath.length; t++) {
				turtle.turn(fractalPath[t] / 180 * Math.PI);
				turtle.line(1);
			}
			var fractalAngle = Math.atan2(turtle.y,turtle.x);
			var fractalLength = Math.sqrt(Math.pow(turtle.x,2)+Math.pow(turtle.y,2));
			// Recursive function to draw a generation of the curve
			var drawLeg = function drawLeg(p1,p2,depth) {
				// Work out the angle and length required
				var legLength = Math.sqrt(Math.pow(p2.x-p1.x,2)+Math.pow(p2.y-p1.y,2));
				var legAngle = Math.atan2(p2.y-p1.y,p2.x-p1.x);
				// Initialise the turtle
				var legScale = legLength / fractalLength;
				var turtle = new Turtle(p1.x,p1.y,legAngle);
				turtle.turn(-fractalAngle);
				// Step through the curve
				for(var t=0; t<fractalPath.length; t++) {
					var prevX = turtle.x;
					var prevY = turtle.y;
					turtle.turn(fractalPath[t] / 180 * Math.PI);
					turtle.line(legScale);
					if(depth > 0)
						drawLeg(new Point(prevX,prevY),new Point(turtle.x,turtle.y),depth - 1);
					ctx.lineTo(turtle.x,turtle.y);
				}
			}
			var drawCircle = function(x,y,r) {
				var radgrad = ctx.createRadialGradient(x,y,r,x-r/3,y-r/3,1);
				radgrad.addColorStop(0, '#8888cc');
				radgrad.addColorStop(0.9, '#f0f0ff');
				radgrad.addColorStop(1, '#ffffff');
				ctx.fillStyle = radgrad;
				ctx.beginPath();
				ctx.arc(x,y,r,0,2*Math.PI,0);
				ctx.fill();
			}
			var scale = w/view.w;
			// Get the position of the canvas on the plane
			var px = view.x + view.w/2 - (w/2) / scale;
			var py = view.y + view.h/2 - (h/2) / scale;
			var pw = w / scale;
			var ph = h / scale;
			// Map coordinates
			var p1 = new Point(-430,11);
			var p2 = new Point(1530,674);
			var x = 100;
			var y = 100;
			var r = 500;
			// To 0..1,0..1 for viewport
			p1.x = (p1.x - px)/pw;
			p1.y = (p1.y - py)/ph;
			p2.x = (p2.x - px)/pw;
			p2.y = (p2.y - py)/ph;
			
			x = (x - px)/pw;
			y = (y - py)/ph;
			r = r / pw;
			// To x,y for canvas
			x = x * w;
			y = y * h;
			r = r * w;
			
			p1.x = p1.x * w;
			p1.y = p1.y * h;
			p2.x = p2.x * w;
			p2.y = p2.y * h;
			
			// Draw the circle
			drawCircle(x,y,r);
			// Draw the curve
			ctx.strokeStyle = "#0ff";
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.moveTo(p1.x,p1.y);
			drawLeg(p1,p2,3);
			ctx.stroke();
			// Draw the curve
			ctx.strokeStyle = "#F00";
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.moveTo(p1.x,p1.y);
			drawLeg(p1,p2,2);
			ctx.stroke();
			// Draw the curve
			ctx.strokeStyle = "#Ff0";
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.moveTo(p1.x,p1.y);
			drawLeg(p1,p2,1);
			ctx.stroke();
			// Draw the curve
			ctx.strokeStyle = "#F0f";
			ctx.lineWidth = 1;
			ctx.beginPath();
			ctx.moveTo(p1.x,p1.y);
			drawLeg(p1,p2,0);
			ctx.stroke();
		}
};

Cecily.backgrounds.experimental = {
		title: "Experimental",
		description: "Experimental scratchpad",
		drawBackground: function(canvas,view) {
			var w = canvas.width;
			var h = canvas.height;
			var ctx = canvas.getContext('2d');
			ctx.fillStyle = "#cccccc";
			ctx.fillRect(0, 0, w, h);
			var drawCircle = function(x,y,r) {
				var radgrad = ctx.createRadialGradient(x,y,r,x-r/3,y-r/3,1);
				radgrad.addColorStop(0, '#8888cc');
				radgrad.addColorStop(0.9, '#f0f0ff');
				radgrad.addColorStop(1, '#ffffff');
				ctx.fillStyle = radgrad;
				ctx.beginPath();
				ctx.arc(x,y,r,0,2*Math.PI,0);
				ctx.fill();
			}
			var scale = w/view.w;
			var px = view.x + view.w/2 - (w/2) / scale;
			var py = view.y + view.h/2 - (h/2) / scale;
			var pw = w / scale;
			var ph = h / scale;
			// Map coordinates
			var x = 100;
			var y = 100;
			var r = 500;
			// To 0..1,0..1 for viewport
			x = (x - px)/pw;
			y = (y - py)/ph;
			r = r / pw;
			// To x,y for canvas
			x = x * w;
			y = y * h;
			r = r * w;
			drawCircle(x,y,r);
				ctx.drawWindow(window, 0, 0, 100, 200, "rgb(0,0,0)");
		}
};

Cecily.backgrounds.honeycomb = {
		title: "Honeycomb",
		description: "Honeycomb balls",
		drawBackground: function(canvas,view) {
			var w = canvas.width;
			var h = canvas.height;
			var scale = w/view.w;
			var t = ((Math.log(scale)+6)/12);
			t = Math.max(t,0);
			t = Math.min(t,1);
			var ctx = canvas.getContext('2d');
			ctx.fillStyle = "#cccc88";
			ctx.fillRect(0, 0, w, h);
			var drawCircle = function(x,y,r,c) {
				ctx.fillStyle = c ? c : '#bbbb66';
				ctx.beginPath();
				ctx.arc(x,y,r,0,2*Math.PI,0);
				ctx.fill();
			}
			var modulo = function(num,denom) {
				return num-Math.floor(num/denom)*denom;
			}
			var gapX = 2000 * scale;
			var yscale = Math.sin(Math.PI/3)*2;
			var gapY = gapX * yscale;
			var radius = 600 * scale;
			if(gapX < 15) {
				gapX = 15;
				gapY = 15;
			}
			if(radius < 7) {
				radius = 7;
			}
			
			for(var y = -modulo(view.y * scale,gapY) - gapY; y < h + gapY; y += gapY) {
				for(var x = -modulo(view.x * scale,gapX) - gapX; x < w + gapX; x += gapX) {
					drawCircle(x,y,radius);
					drawCircle(x + gapX/2,y + gapY/2,radius);
					/*
					drawCircle(x,y,radius/2,"#555577");
					drawCircle(x + gapX/4,y + gapY/4,radius/2,"#555577");
					drawCircle(x + gapX/2,y,radius/2,"#555577");
					drawCircle(x + gapX/4,y - gapY/4,radius/2,"#555577");
					drawCircle(x - gapX/4,y + gapY/4,radius/2,"#555577");
					drawCircle(x - gapX/2,y,radius/2,"#555577");
					drawCircle(x - gapX/4,y - gapY/4,radius/2,"#555577");
					drawCircle(x + gapX/2,y + gapY/2,radius/2,"#555577");
					drawCircle(x + gapX,y + gapY/2,radius/2,"#555577");
					*/
				}
			}
		}
};

//-----------------------------------------------------------------------------------
// Utilities for class substitution
//-----------------------------------------------------------------------------------

function overrideMethod(instance,method,override)
{
	var oldFunction = instance[method];
	instance[method] = function () {return override(oldFunction,arguments);};
}

//-----------------------------------------------------------------------------------
// Initialisation code (executed during loading of plugin)
//-----------------------------------------------------------------------------------

function runCecily()
{
	setStylesheet(store.getRecursiveTiddlerText(tiddler.title + "##StyleSheet"),"cecily");
	window.cecily = new Cecily();
	overrideMethod(story,"displayTiddler",function(superFunction,arguments) {cecily.displayTiddler(superFunction,arguments);});
	store.addNotification("PageTemplate",function () {cecily.createDisplay();});
}

Cecily.cssTransform = null;
if(document.body.style['-webkit-transform'] !== undefined)
	Cecily.cssTransform = '-webkit-transform';
if(document.body.style['MozTransform'] !== undefined)
	Cecily.cssTransform = 'MozTransform';

if(Cecily.cssTransform) {
	runCecily();
} else {
	alert("ProjectCecily currently only works on Safari 3.1, Firefox 3.1 and Google Chrome. Use the WebKit nightly build from http://webkit.org/ for the best experience");
}

} // if(!version.extensions.CecilyPlugin)

/***
!StyleSheet

body {
	font-family: helvetica,arial;
}

#displayArea.cecily {
	float: none;
	margin: 0em 0em 0em 0em;
	position: relative;
	background-color: #ffff88;
	overflow: hidden;
}

div#messageArea {
	-webkit-transition: opacity 0.3s ease-in-out;
	-webkit-border-radius: 4px;
	-moz-border-radius: 4px;
	border: 1px solid #222;
	background-color: [[ColorPalette::SecondaryLight]];
	background-image: -webkit-gradient(linear, left top, left bottom, from([[ColorPalette::SecondaryPale]]), to([[ColorPalette::SecondaryDark]]), color-stop(0.1,[[ColorPalette::SecondaryLight]]), color-stop(0.6,[[ColorPalette::SecondaryMid]]));
	opacity: 0.8;
}

div#messageArea:hover {
	opacity: 1.0;
}

div#messageArea .button {
	padding: 0 0.25em 0 0.25em;
	text-decoration: none;
	-webkit-transition: opacity 0.3s ease-in-out;
	opacity: 0;
	-webkit-border-radius: 3px;
	-moz-border-radius: 3px;
	background-color: #aaa;
	background: -webkit-gradient(linear, left top, left bottom, from([[ColorPalette::PrimaryLight]]), to([[ColorPalette::PrimaryDark]]), color-stop(0.5,[[ColorPalette::PrimaryMid]]));
	color: [[ColorPalette::TertiaryPale]];
}

div#messageArea:hover .button {
	opacity: 1;
}

div#messageArea:hover .button:active {
	background-color: [[ColorPalette::Foreground]];
	color: [[ColorPalette::Background]];
}

#overlayMenu {
	-webkit-box-shadow: 2px 2px 13px #000;
	-moz-box-shadow: 2px 2px 13px #000;
	-webkit-transition: opacity 0.2s ease-in-out;
	z-index: 100;
	position: absolute;
	padding: 0.1em 0.1em 0.1em 0.1em;
	font-size: 0.8em;
	-webkit-border-radius: 4px;
	-moz-border-radius: 4px;
	border: 1px solid #666;
	background-color: #bbb;
	background-image: -webkit-gradient(linear, left top, left bottom, from(#999), to(#ddd), color-stop(0.3,#bbb));
	opacity: 0;
	display: none;
}

#overlayMenu table.twtable {
	border: none;
}

#overlayMenu .twtable th{
	border: none;
}

#overlayMenu .twtable td {
	border: none;
}

#overlayMenu .twtable tr {
	border: none;
	border-bottom: 1px solid #ccc;
}

#overlayMenu a {
	-webkit-transition: color 0.3s ease-in-out;
	text-decoration: none;
	font-weight: bold;
	font-style: normal;
	color: #000;
	background-color: #999;
	border: none;
	margin: 0 0.25em 0 0.25em;
	padding: 3px 3px 3px 3px;
	-webkit-border-radius: 3px;
	-moz-border-radius: 3px;
}

#overlayMenu a:hover {
	text-decoration: none;
	font-weight: bold;
	font-style: normal;
	color: #000;
	background-color: #ff0;
	border: none;
}

#overlayMenu .overlayCommand {
	font-size: 2em;
	color: #fff;
	text-shadow: #000 2px 2px 3px;
}

div#backstageArea {
	position: absolute;
}

.cecilyCanvas {
	position: absolute;
	left: 0px;
	top: 0px;
	background-color: #eee;
}

#tiddlerDisplay {
	position: relative;
	-webkit-transform-origin: 0% 0%;
	-moz-transform-origin: 0% 0%;
}

.cecily .tiddler {
	position: absolute;
	top: 0px;
	left: 0px;
	width: 360px;
	padding: 0;
	background-color: #fff;
	overflow: hidden;
	border: 1px solid black;
}

.cecily .tiddler.drag {
	-webkit-box-shadow: 2px 2px 13px #000;
	-moz-box-shadow: 2px 2px 13px #000;
}

.cecily .tiddler .heading {
	background-color: #bbb;
	background-image: -webkit-gradient(linear, left top, left bottom,
		from(#fff), color-stop(0.5,#bbb), color-stop(0.51,#aaa), to(#999));
}

.cecily .tiddler .toolbar {
	cursor: all-scroll;
	padding: 4pt 2pt 4pt 4pt;
	color: #aaa;
}

.cecily .tiddler.selected .toolbar {
	color: #fff;
}

.cecily .tiddler .toolbar a {
	-webkit-transition: opacity 0.3s ease-in-out;
	opacity: 0;
	margin: 0 0.25em 0 0.25em;
	border: none;
	-webkit-border-radius: 3px;
	-moz-border-radius: 3px;
}

.cecily .tiddler.selected .toolbar a {
	opacity: 1;
	background-color: #aaa;
	background: -webkit-gradient(linear, left top, left bottom, from(#888), to(#ccc), color-stop(0.5,#aaa), color-stop(0.7,#bbb));
	color: #fff;
}

.cecily .tiddler.selected .toolbar a:hover {
	background-color: #c80;
	background-image: -webkit-gradient(linear, left top, left bottom, from(#c80), to(#fc1), color-stop(0.5,#c80));
	color: #000;
}

.cecily .tiddler.selected .toolbar a:active {
	background-color: [[ColorPalette::Foreground]];
	background-image: none;
	color: [[ColorPalette::Background]];
}

.cecily .tiddler .title {
	cursor: all-scroll;
	padding: 2pt 8pt 2pt 8pt;
	color: #000;
	background-color: transparent;
}

.cecily .tiddler .subtitle {
	padding: 2pt 8pt 4pt 8pt;
	color: #444;
	font-size: 0.6em;
}

.cecily .tiddler .viewer {
	padding: 4pt 8pt 4pt 8pt;
	background-color: #fff;
}

.cecily .tiddler .tagging, .cecily .tiddler .tagged {
	float: none;
	border: none;
	padding: 2pt 8pt 2pt 8pt;
	background-image: -webkit-gradient(linear, left bottom, left top, from(#888), to(#ccc), color-stop(0.5,#ccc), color-stop(0.95,#fff));
	margin: auto;
}

.cecily .tiddler .tagged {
	cursor: nwse-resize;
}

.cecily .tiddler.selected .tagging, .cecily .tiddler.selected .tagged {
	background-color: auto;
	border: auto;
}

.cecilyButton {
	-webkit-appearance: push-button;
}

!(end of StyleSheet)

***/
HelloThere [[ZoomingUserInterface from Wikipedia]] WebKit ZoomingUserInterfaces ProjectCecily JefRaskin JeremyRuston AzaRaskin [[Tiddlers]] TiddlyWiki Serializer [[Feature Requests]] [[TODO]] UrlMap
Like Webkit, Firefox is an awesome piece of work, as evidenced by the way that this embedded video plays smoothly even with lots of zooming and scaling going on:
{{{
<html>
<video src="Ecl1-11-08-1999.ogg" controls="true" width="320" height="240" autoplay playcount="999">
</video>
</html>
}}}
<html><video src="Ecl1-11-08-1999.ogg" controls="true" width="320" height="240" autoplay playcount="999"></video></html>
Welcome to ProjectCecily, an experimental [[ZoomingUserInterface|ZoomingUserInterfaces]] for TiddlyWiki created by JeremyRuston. It runs on WebKit 3.1 (and above) and [[Firefox]] 3.1 (and above).

Have a look at these pictures: [[Swampglade]], [[Tenerife]], [[Weyflection]] and [[Seagully]]
JeremyRuston is the Head of Open Source Innovation at BT. He runs [[Osmosoft|http://www.osmosoft/]], a small team of developers dedicated to exploring the innovation potential of open source. Jeremy created TiddlyWiki and ProjectCecily.
HelloThere
<!--{{{-->
<meta name="viewport" content="width=device-width" />
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml' />
<!--}}}-->
DefaultTiddlers 2241 500 225 99
HelloThere -387 -463 945 587
JeremyRuston 1319 -417 269 129
MainMenu 2506 499 225 69
MyMap 2755 498 225 325
OverlayMenu 2997 506 225 110
PageTemplate 3235 511 225 139
Principles 2593 672 225 164
ProjectCecily 825 159 996 1191
Seagully 127 1159 225 165
Serializer 100 150 50 100
SideBarTabs 604 -500 415 593
SiteSubtitle 3643 850 225 69
SiteTitle 3649 963 225 69
Swampglade -179 855 225 201
TODO 300 150 50 1000
Tenerife 122 883 225 200
Tiddlers 2000 250 450 1000
TiddlyWiki 1103 -260 460 179
ToDo 1624 -592 408 410
UrlMap 400 150 50 1000
ViewTemplate 3247 666 225 159
WebKit 1915 -46 424 727
Weyflection -425 1105 498 445
ZoomingUserInterface%20from%20Wikipedia 379 778 399 590
ZoomingUserInterfaces 168 161 618 554
|{{overlayCommand{go}}} |[[home|ProjectCecily]][[a-z|SideBarTabs]]<<search>> |
|{{overlayCommand{zoom}}} |<<cecilyZoomAll>><<cecilyZoom>> |
|{{overlayCommand{map}}} |<<cecilyMap>><<closeAll>> |
|{{overlayCommand{share}}} |<<permaview>> |
|{{overlayCommand{create}}} |<<saveChanges>><<newTiddler>><<newJournal "DD MMM YYYY" "journal">> |
|{{overlayCommand{tweak}}} |<<cecilyBackground>>[[options|OptionsPanel]] |
<!--{{{-->
<div id='displayArea'>
<div id='overlayMenu' refresh='content' tiddler='OverlayMenu'></div>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<!--}}}-->
ProjectCecily is a user interface concept that combines the functionality of a wiki with a ZoomingUserInterface.

I've been working on ProjectCecily for many years (it actually pre-dates TiddlyWiki). This latest implementation was built in pure HTML, JavaScript and CSS. It only works on recent versions of WebKit (Safari version 3.1 and above, and [[iPhone]] 2.0+). It's still very experimental, and lots of things don't work as expected. Some of that is due to WebKitBugs, but it's mostly down to incompleteness.

ProjectCecily is based on some simple [[Principles]], mostly stolen from [[Wikis]] and ZoomingUserInterfaces, many of which have already been seen in TiddlyWiki.  There are also some AntiPrinciples, approaches that have been consciously avoided.

More information:
* ImplementationDetails
http://www.flickr.com/photos/jermy/216409952/
See also [[Swampglade]], [[Tenerife]] and [[Weyflection]]
<html>
<img src="Seagully.jpg" width="256" height="135" />
</html>
a zooming user interface for TiddlyWiki
Project Cecily
http://www.flickr.com/photos/jermy/65637412/
See also [[Tenerife]], [[Weyflection]] and [[Seagully]]
<html>
<img src="Swampglade.jpg" width="256" height="192" />
</html>
http://www.flickr.com/photos/jermy/94035340/
See also [[Swampglade]], [[Weyflection]] and [[Seagully]]
<html>
<img src="Tenerife.jpg" width="256" height="190" />
</html>
TiddlyWiki is a wiki that runs entirely in the browser, without needing a server. It was originally created by JeremyRuston.
Things to do:
* Make the overlay menu popup at the mouse position
* Add keyboard shortcuts, and awesome/omni bar
* Do semantic zooming in CSS by progressively adding classNames like "lessThan20px", "lessThan10px"
* Improve zooming to use smoother bezier splines
* Animate map transitions
* A background based on gradient fills

Problems:
* Zooming could be smoother
* Dragging tiddlers sometimes judders
* Some tiddlers don't like being opened in edit mode (I think that this is a WebKit problem)
<!--{{{-->
<div class='heading'>
<div class='toolbar' macro='toolbar [[ToolbarCommands::ViewToolbar]]'></div>
<div class='title' macro='view title'></div>
<div class='subtitle'><span macro='view modifier link'></span>, <span macro='view modified date'></span> (<span macro='message views.wikified.createdPrompt'></span> <span macro='view created date'></span>)</div>
<div class='tagging' macro='tagging'></div>
</div>
<div class='viewer' macro='view text wikified'></div>
<div class='tagged' macro='tags'></div>
<div class='tagClear'></div>
<!--}}}-->
WebKit is an open source web browser begun by Apple, forked off the KHTML codebase. See http://www.webkit.org/

This implementation of ProjectCecily exploits several features of recent versions of WebKit:
* [[Canvas]]
* [[CSS Transforms|http://webkit.org/blog/130/css-transforms/]]
* [[CSS Gradients|http://webkit.org/blog/175/introducing-css-gradients/]]
* [[CSS Animation|http://webkit.org/blog/138/css-animation/]]

WebKit is an awesome piece of work, as evidenced by the way that this embedded video plays smoothly even with lots of zooming and scaling going on:
{{{
<html>
<video src="jumps.mov" autoplay playcount="999">
</video>
</html>
}}}
<html><video src="jumps.mov" autoplay playcount="999"></video></html>
http://www.flickr.com/photos/jermy/65625750/
See also [[Swampglade]], [[Tenerife]] and [[Seagully]]
<html>
<img src="Weyflection.jpg" width="256" height="192" />
</html>
<html><object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/_M9qMkcL-sY&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/_M9qMkcL-sY&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="425" height="344"></embed></object></html>
<html>
<img alt="Example of a ZUI" src="ZUI_example.png" width="194" height="82" border="0" style="float:right;" />
<p>In <a href="http://en.wikipedia.org/wiki/Computing" title="Computing" target="_blank">computing</a>, a <b>Zooming User Interface</b> or <b>Zoomable User Interface</b> (<b>ZUI</b>, pronounced Zoo-ee) is a graphical environment where users can change the scale of the viewed area in to see more detail or less. A ZUI is a type of <a href="http://en.wikipedia.org/wiki/Graphical_user_interface" title="Graphical user interface" target="_blank">graphical user interface</a> (GUI). Information elements appear directly on an infinite <a href="http://en.wikipedia.org/wiki/Virtual_desktop" title="Virtual desktop" target="_blank">virtual desktop</a> (usually created using <a href="http://en.wikipedia.org/wiki/Vector_graphics" title="Vector graphics" target="_blank">vector graphics</a>), instead of in windows. Users can pan across the virtual surface in two dimensions and zoom into objects of interest. For example, as you zoom into a text object it may be represented as a small dot, then a thumbnail of a page of text, then a full-sized page and finally a magnified view of the page.</p> 
<p>Some experts consider the ZUI interface paradigm as a flexible and realistic successor to the traditional windowing GUI. But little effort is currently spent developing ZUIs, while there are ongoing efforts for developing GUIs.</p> 

</html>

See the rest of the article [["Zooming User Interface" on Wikipedia|http://en.wikipedia.org/wiki/Zooming_user_interface]]
ZoomingUserInterfaces (ZUIs) feature entities of different sizes laid out on an (almost) infinite wall. Navigation is accomplished by panning and zooming a virtual camera around the wall. See [[the Wikipedia article|ZoomingUserInterface from Wikipedia]].

ZUIs have been studied in industry and academia for several years (see BenBederson, JefRaskin and AzaRaskin). Although a few concepts first developed in the field have subsequently been adopted, there aren't yet any mainstream products that try to exploit the capabilities of ZUIs.

There's a [[good paper at Advogato.com|http://www.advogato.org/article/788.html]] that covers some practical design issues with ZUIs.