<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Acko.net]]></title>
  <link href="http://acko.net/atom.xml" rel="self"/>
  <link href="http://acko.net/"/>
  <updated>2013-05-10T11:50:48-07:00</updated>
  <id>http://acko.net/</id>
  <author>
    <name><![CDATA[Steven Wittens]]></name>
    
  </author>

  
  <entry>
    <title type="html"><![CDATA[Making MathBox]]></title>
    <link href="http://acko.net/blog/making-mathbox/"/>
    <updated>2012-11-14T00:00:00-08:00</updated>
    <id>http://acko.net/blog/making-mathbox</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>

  <h1>Making MathBox</h1>
  <h2>Presentation-Quality Math with Three.js and WebGL</h2>

</div></div><div class='c' /><aside class='g4'><div class='pad tc'>
  <iframe class='mathbox' height='600' src='/files/mathbox/MathBox.js/examples/ProjectiveLine.html?da226cb7' />
  A fun little graph involving rational functions on the <a href='http://en.wikipedia.org/wiki/Real_projective_line'>real projective line</a>.
</div></aside><div class='g8'><div class='pad'>

  <p>For most of my life, I've found math to be a visual experience. My math scores went from crap to great once I started playing with graphics code, found some demoscene tutorials, and realized I could reason about formulas by picturing the graphs they create. I could apply operators by learning how they morph, shift, turn and fold those graphs and create symmetries. I could remember equations and formulas more easily when I could layer on top the visual relationships they embody. I was less likely to make mistakes when I could augment the boring symbolic manipulation with a mental set of visual cross-checks.</p>

  <p>So, when tasked with holding a conference talk on <a href='http://www.youtube.com/watch?v=ONN3jBly364'>how to make things out of math</a> at <a href='http://www.full-frontal.org'>Full Frontal</a>, I knew the resulting presentation would have to consist of intricate visualizations as the main draw, with whatever I had to say as mere glue to hold it together.</p>

  <p>The problem was, I didn't know of a good tool to do so, and creating animations by hand would probably be too time consuming. With the writings of <a href='http://www.maa.org/devlin/LockhartsLament.pdf'>Paul Lockhart</a> and <a href='http://worrydream.com/KillMath/'>Bret Victor</a> firmly in mind, I also knew I wanted to start blogging more about mathematical concepts in a non-traditional way, showing the principles of calculus, analysis and algebra the way I learnt to see them in my head, rather than through the obscure symbols served up in engineering school.</p>

  <p>So I set out to create that tool, keeping in mind the most important lesson I've picked up as a web developer: one cannot overstate the value in being able to send someone a link and have it just work, right there. It was obvious it would have to be browser-based.</p>

</div></div><aside class='g4 i2 c'><div class='pad'>
  <p class='tc'>
    <a href='http://www.youtube.com/watch?v=ONN3jBly364&amp;list=UUyBAm31tEpZ17hka6ZvVqcg&amp;index=2&amp;feature=plcp'>
    <img alt='Video' src='/files/fullfrontal/video.jpg' style='top: 0' />
    Conference Video
    </a>
  </p>
</div></aside><aside class='g4'><div class='pad'>
  <p class='tc'>
    <a href='http://acko.net/files/fullfrontal/fullfrontal/slides-net/'>
    <img alt='Slides' src='/files/fullfrontal/slides.png' style='top: 0; -webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); -moz-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); box-shadow: 0 2px 10px rgba(0, 0, 0, .3);' />
    Slide Deck
    </a>
  </p>
</div></aside><div class='g8 i2 c'><div class='pad'>

  <h2>Choose your Poison</h2>

  <p>Now, when people think of graphs in a browser, the natural thought is vector graphics and SVG, which quickly leads to <a href='http://d3js.org'>visualization powerhouse d3.js</a>. It really is an amazing piece of tech with a vast library of useful code to accompany it. When I wrapped my head around how d3's enter/exit selections are implemented and how little it actually does to achieve so much, I was blown away. It's just so elegant and simple.</p>

  <p>Unfortunately, d3's core is intricately tied to the DOM through SVG and CSS. And that means ironically that d3 is not really capable of 3D. Additionally, d3 is a power tool that makes no assumptions: it is up to you to choose which visual elements and techniques to use to make your diagrams, and as such it is more like assembly language for graphs than a drop-in tool. These two were show stoppers.</p>

  <p>For one, manually designing layouts, grids, axes, etc. every time is tedious. You should be able to drop in a mathematical expression with as little fanfare as possible and have it come out looking right. This includes sane defaults for transitions and animations.</p>

</div></div><div class='g12'>
  <iframe class='mathbox' height='500' src='/files/mathbox/MathBox.js/examples/Intersections.html?da226cb7' width='960' />
</div><div class='g7'><div class='pad'>

  <p>For another, I've found that, when in doubt, adding an extra dimension always helps. The moment I finally realized that every implicit graph in N dimensions is really just a slice of an explicit one in N+1 dimensions, a ridiculous amount of things clicked together. And it took until years after studying signal processing to at long last discover the 4D picture of complex exponentiation that tied the entire thing together (projected into 3D below): it revealed the famous "magic formula" involving e, i and π to be a meaningless symbological distraction, a pinhole view of a much larger, much more beautiful structure, underpinning every Fourier and Z transform I'd ever encountered.</p>

</div></div><aside class='g5 m1'><div class='pad'>
  <p class='tc'><big>
    e<sup>iπ</sup> = -1
  </big></p>
  <p class='tc'>This particular formula is not that important.</p>

  <p class='tc'><big>
    e<sup>x+iy</sup> = e<sup>x</sup> &middot; e<sup>iy</sup> = e<sup>x</sup> ∠ y
    
  </big></p>
  <p class='tc'>This one is (∠ = rotate by).<br />Unfortunately it has a four dimensional graph.</p>
  
</div></aside><div class='g12'>
  <iframe class='mathbox' height='500' src='/files/mathbox/MathBox.js/examples/ComplexExponentiation.html?da226cb7' width='960' />
</div><div class='g8 i2'><div class='pad'>

  <p>So, WebGL it was, because I needed 3D. Unfortunately that meant the promise of having it just work everywhere was tempered by a lack of browser support, but I would certainly hope that's something we can overcome sooner than later. Dear Apple and Microsoft: get your shit together already. Dear Firefox and Opera: your WebGL performance could be a lot better.</p>

  <h2>Shady Dealings</h2>

  <p>These days I don't really touch WebGL without going through <a href='http://mrdoob.github.com/three.js/'>Three.js</a> first. Three.js is a wonderful, mature engine that contains tons of useful high-level components. At the same time, it also does a great job in just handling the boilerplate of WebGL while not getting in the way of doing some heavy lifting yourself.</p>

  <p>Rendering vector-style graphics with WebGL is not hard, certainly easier than photorealistic 3D. Primitives like lines and points are sized in absolute pixels by default, and with hardware multisampling for anti-aliasing, you get somewhat decent image quality out of it. Though, as is typical for a Web API, we're treated like children and can only cross our fingers and <em>request</em> anti-aliasing politely, hoping it will be available. Meanwhile native developers <a href='http://www.nvidia.com/object/coverage-sampled-aa.html'>have full control</a> over speed and quality and can adjust their strategy to the specific hardware's capabilities. The more things change... And then <a href='http://code.google.com/p/chromium/issues/detail?id=159275'>Chrome decided to disable anti-aliasing altogether</a> due to esoteric security issues with buggy drivers. Bah.</p>

  <p>Now, when rendering with WebGL, you really have two options. One is to just treat it as a dumb output layer, loading or generating all your geometry in JavaScript and rendering it directly in 3D. With the speed of JS engines today, this can get you pretty far.</p>

</div></div><div class='g7'><div class='pad'>
  <p>The second option is to leverage the GPU's own capabilities as much as possible, doing computations in GLSL through so-called vertex and fragment shader programs. These are run for every vertex in a mesh, every pixel being drawn, and have been the main force driving innovation in real-time graphics for the past decade. With the goal of butter-smooth 60fps graphical goodness, this seemed like the better choice.</p>

  <p>Unfortunately, GLSL shaders are rather monolithic things. While you do have the ability to create subroutines, every shader still has to be a stand-alone program with its own main() function. This means you either need to include a shader for every possible combination of operations, or generate shader code dynamically by concatenating pre-made snippets or using #ifdef switches to knock them out. This is the approach taken by Three.js, which results in some <a href='https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLShaders.js'>very hairy code</a> that is neither easy to read nor easy to maintain.</p>

  <p>Having made a prototype, I knew I wanted to show continuous transitions between various coordinate systems (e.g. polar and spherical), knew I needed to render shaded and unshaded geometry, and knew I would need to slot in specific snippets for things like point sprites, bezier curves/surfaces, dynamic tick marks, and more. Sorting this all out Three.js-style would be a nightmare.</p>

</div></div><aside class='g5'>
<p class='codeblock'>
<code>uniform sampler2D texture;
varying vec2 vUV;

void main() {
  gl_FragColor = texture2D(texture, vUV);
}
</code></p>
<p>
A pixel or fragment shader that looks up a pixel's color in a texture.
</p>
<p class='codeblock'>
<code>uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec4 position;
attribute vec2 uv;
varying vec2 vUV;

void main() {
  vUV = uv;

  gl_Position = projectionMatrix
              * modelViewMatrix
              * position;
}
</code>
</p>
<p>
A vertex shader that projects a 3D position into 2D by applying two matrices. It also provides UV coordinates for the texture look up.
</p>
</aside><aside class='g4 c'>
<p style='top: 0;'><img src='/files/mathbox/shadergraph.png' style='width: 93%' /></p>
<p class='codeblock'>
<code>var graph =
  factory
    .snippet('split')
    .group()
      .snippet('top')
    .next()
      .snippet('middle')
    .next()
      .snippet('bottom')
    .combine()
    .snippet('join')
    .end();
</code></p>
<p>
ShaderGraph's factory API lets you build shader chains with very little hassle. In this case, the names refer to IDs of &lt;<strong>script</strong>&gt; tags in the source.
</p>
</aside><div class='g8'><div class='pad'>

  <p>So I wrote a library to solve that problem, called <a href='https://github.com/unconed/ShaderGraph.js'>ShaderGraph.js</a>. It is best described as a smart code-concatenator, a few steps short of writing a full blown compiler. You feed it snippets of GLSL code, each with one or more inputs and outputs, and these get parsed and turned into lego-like building blocks. Each input/output becomes an outlet, and outlets are wired up in a typical dataflow style. Given a graph of connected snippets, it can be compiled back into a program by assembling the subroutines, assigning intermediate variables and constructing an appropriate main() function to invoke them. It also exports a list of all external variables, i.e. GLSL uniforms and attributes, so you can control the program's behavior easily.</p>

  <p>If I'd stopped there however, I'd have just replaced the act of manual code writing with that of manually wiring graphs. So I applied the principle of convention-over-configuration instead: you tell ShaderGraph to connect two snippets, and it will automatically match up outlets by name and type. This is augmented by a chainable factory API, which allows you to pass a partially built graph around. It allows different classes to work together to build shaders, each inserting their own snippets into the processing chain.</p>
  
<p>For example, to render a Bezier surface, the vertex shader is composed of: cubic interpolation, viewport transform (position + tangents), normal calculation and lighting. When transforming to e.g. a polar viewport, the surface normals are seamlessly recalculated. It really works like magic and I can't wait to use this in my next WebGL projects.</p>

</div></div><div class='g8 i2'><div class='pad'>
  
  <h2>Viewports, Primitives and Renderables</h2>

  <p>At its core, Three.js matches pretty directly with WebGL. You can insert objects such as a Mesh, Line or ParticleSystem into your scene, which invokes a specific GL drawing command with high efficiency. As such, I certainly didn't want to reinvent the wheel.</p>

  <p>Hence, MathBox is set up as a sort of scene-manager-within-a-scene-manager. It's a little sandbox that speaks the language of math, allowing you to insert various <em>primitives</em> like curves, vectors, axes and grids. Each of these primitives then instantiates one or more <em>renderables</em>, which simply wrap a native Three.js object and its associated ShaderGraph material. Thus, once instantiated, MathBox gets out of the way and Three.js does the heavy lifting as normal. You can even insert multiple mathboxen into a Three.js scene if you like, mixed in with other objects.</p>

<p><img alt='MathBox Architecture' class='squeeze' src='/files/mathbox/MathBox.js/resources/architecture.png' style='margin-left: -10px;' /></p>

  <p>For example, a vector primitive is rendered as an arrow: it consists of a shaft and an arrowhead, realized as a line segment and a cone. An axis primitive is an arrow as well, but it also has tick marks (specially transformed line segments), and is positioned implicitly just by specifying the axis' direction rather than a start and end point.</p>

  <p>To render curves and surfaces, you can either specify an array of data points or a live expression to be evaluated at every point. This turned out to be essential for the kinds of intricate visualizations I wanted to show, my slides being driven by timed clocks, shared arrays of data points, and live formulas and interpolations. I even fed in data from a physics engine, and it worked perfectly.</p>

  <p>This is all tied together through Viewport objects, which define a specific mapping from a mathematical coordinate space into the 3D world space of Three.js. For example, the default cartesian viewport has the range [–1, 1] in the X, Y and Z directions. Altering the viewport's extents will shift and scale anything rendered within, as well as reflow grids and tick marks on each axis.</p>

  <p>There are two more sophisticated viewport types, polar and spherical, which each apply the relevant coordinate transform, and can transition smoothly to and from cartesian. More viewport types can be added, all that is required is to define an appropriate transformation in JavaScript and GLSL. That said, defining a seamless transition to and from cartesian space is not always easy, particularly if you want to preserve the aspect-ratio through the entire process.</p>

  <h2>Interpolate all the things!</h2>

  <p>Finally, I had to tackle the problem of animation, keeping in mind a tip I learnt from the <a href='http://www.youtube.com/watch?v=4gZ5rsAHMl4'>ever so mindbending Vihart</a>: "If I can draw the point of a sentence, I don't actually need to say the sentence." This applies doubly so for animation: every time you replace a "before" and "after" with a smooth transition, your audience implicitly understands the change rather than having to go look for it.</p>

  <p>Hence, each primitive can be fully animated. Each has a set of options (controlling behavior) and styles (controlling GLSL shaders), and there is a universal animator that can interpolate between arbitrary data types in a smart fashion.</p>

  <p>For example, given a viewport with the XYZ range [[–1, 1], [–1, 1], [–1, 1]], you can tell it to animate to [[0, 2], [0, 1], [–3, 3]], and it just works. The animator will recursively animate each subarray's elements, and any dependent objects like grids and axes will reflow to match the intermediate values. This works for colors, vectors and matrices too. In case of live curves with custom expressions, the animator will invoke both the old and the new, and interpolate between the results.</p>

</div></div><div class='g8 i2'>
  <iframe class='mathbox paged' height='400' src='/files/mathbox/MathBox.js/examples/BezierSurface.html?da226cb7' width='640' />
</div><div class='g8 i2'><div class='pad'>
  <p>However, executing animations manually in code is tedious, particularly in a presentation, where you want to be able to step forward and backward. So I added a Director class whose job it is to coordinate things. All you do is feed it a script of steps (add this object, animate that object). Then, as it applies them, it remembers the previous state of each object and generates an automatic rollback script. It also contains logic to detect rapid navigation, and will hurry up animations appropriately. This avoids that agonizing situation of watching someone skip through their slide deck, playing the same cheesy PowerPoint transitions over and over again.</p>

  <h2>Presenting Naturally</h2>
  
  <p>With MathBox's core working, it was time to build my slides for the conference. After a quick survey, I quickly settled on <a href='http://imakewebthings.com/deck.js/'>deck.js</a> as an HTML5 slidedeck solution that was clean and flexible enough for my purposes. However, while MathBox can be spawned inside any DOM element, it wouldn't work to insert a dozen live WebGL canvases into the presentation. The entire thing would grind to a halt or at least become very choppy.</p>
  
  <p>So instead, I integrated each MathBox graphic as an IFRAME, and added some logic that only loads each IFRAME one slide before it's needed, and unloads it one slide after it's gone off screen. To sync up with the main presentation, all deck.js navigation events were forwarded into each active IFRAME using <em>window.postMessage</em>. With the MathBox Director running inside, this was very easy to do, and meant that I could skip around freely during the talk, without any worries of desynchronization between MathBox and the associated HTML5 overlays.</p>
  
  <p>In fact, I applied a similar principle to this post. To avoid rendering all diagrams simultaneously and spinning up laptop fans more than necessary, each MathBox IFRAME is started as it scrolls into view and stopped once it's gone.</p>
  
  <p>I've also found that having a handheld clicker makes a huge difference while speaking—as it allows you to gesture freely and move around. So, I grabbed the infrared remote code from VLC and built a <a href='https://github.com/unconed/iremotepipe/'>simple bridge</a> from to Cocoa to Node.js to WebSocket to allow the remote to work in a browser. It's a shame Apple's decided to discontinue IR ports on their laptops. I guess I'll have to come up with a BlueTooth-based solution when I upgrade my hardware.</p>

  <h2>Towards MathBox 1.0</h2>

  <p>In its current state, MathBox is still a bit rough. The selection of primitives and viewports is limited, and only includes the ones I needed for my presentation. That said, it is obvious you can already do quite a lot with it, and I couldn't have been happier to hear that all this effort had the desired response at the conference. I wasn't 100% sure whether other people would have the same a-ha moments that I've had, but I'm convinced more than ever that seeing math in motion is essential for honing our intuition about it. MathBox not only makes animated diagrams much easier to make and share, but it also opens the door to making them interactive in the future.</p>

  <p>I plan to continue to evolve MathBox as needed by using it on this site and addressing gaps that come up, though I've already identified a couple of sore points:</p>

  <ul>
  <li><span class='strike'><span>I used tQuery as a boilerplate and because I liked the idea of having a chainable API for this. However, this also means it's currently running off an outdated version of Three.js. I need to look into updating and/or dropping tQuery.</span></span><br />MathBox has been updated to Three.js r53.</li>
  <li><span class='strike'><span>Numeric or text labels are completely unsupported. It should be possible to use my CSS3D renderer for Three.js to layer on beautifully typeset <a href='http://www.mathjax.org'>MathJax</a> formulas, positioning them correctly in 3D on top of the WebGL render.</span></span><br />I've added labeling for axes. I've integrated MathJax, but it's tricky because the typesetting is painfully slow in the middle of a 60fps render. But it's automatically used if MathJax is present.</li>
  <li>All styles have to be specified on a per-object basis. Some form of stylesheet, default styles or class mechanism to allow re-use seems like an obvious next step.</li>
  <li>There are undoubtedly memory leaks, as I was focused first and foremost on getting it to work.</li>
  <li>Expressions that don't change frame-to-frame are still continuously re-evaluated, which is wasteful. There is a <code>live: false</code> flag you can set on objects, but it triggers a few bugs here and there.</li>
  <li><span class='strike'><span>There needs to be a predictable, built-in way of running a clock per slide to sync custom expressions off of. In my presentation I used a hack of clocks that start once first invoked, but this lacks repeatability.</span></span><br />I added a <code>director.clock()</code> method that gives you a clock per slide.</li> 
  </ul>

  <p>Finally, it doesn't take much imagination to imagine a MathBox Editor that would allow you to build diagrams visually rather than having to use code like I did. However, that's a can of worms I'm not going to open by myself, especially because the API is already quite straightforward to use, and the library itself is still a bit in flux. Perhaps this could be done as an extension of the <a href='http://mrdoob.github.com/three.js/editor/'>Three.js editor</a>.</p>

  <p>You can see what MathBox is really capable of in the <a href='http://www.youtube.com/watch?v=ONN3jBly364&amp;list=UUyBAm31tEpZ17hka6ZvVqcg&amp;index=2&amp;feature=plcp'>conference video</a>. I invite you to <a href='https://github.com/unconed/MathBox.js'>play around with MathBox</a> and see what you can make it do. Contributions are welcome, and the architecture is modular enough to allow its functionality to grow for quite some time.</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Going Full Frontal]]></title>
    <link href="http://acko.net/blog/going-full-frontal/"/>
    <updated>2012-11-11T00:00:00-08:00</updated>
    <id>http://acko.net/blog/going-full-frontal</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>

  <h1>Going Full Frontal</h1>
  <h2>Making things with Maths</h2>

</div></div><div class='c' /><aside class='g5'>
  <p class='tc'>
    <img alt='Duke of York&apos;s Picturehouse, a one-screen marvel' src='/files/fullfrontal/venue.jpg' style='top: 0' />
    Duke of York's Picturehouse, a gorgeous venue, and "<em>the oldest continuously operating purpose built cinema in Britain that has retained both its original name and remains largely unaltered.</em>"
  </p>
</aside><div class='g7 first'><div class='pad'>

<p>Last week, I had the privilege of speaking about "Making things with Maths" at <a href='http://www.full-frontal.org'>Full Frontal</a>, a tech conference hosted in a gorgeous picturehouse in the seaside town of Brighton, UK. I was nervous as hell: I hadn't attended a tech conference in ages, let alone taken the stage, and I'd never done a talk on this subject before. I'd been planning and working for months to assemble the code just to be able to show what I saw in my head—and of course scrambled to finish the week before regardless. This talk has been the number one thing on my mind for a while.</p>

<p>Yet two days later, I barely remember my own part in it, and find myself mulling over everything else that was said instead. It was simply too good, too provoking, not to think about.</p>

<p>The lovely duo of organizers, <a href='http://remysharp.com'>Remy</a> and <a href='https://twitter.com/Julieanne'>Julie Sharp</a>, have crafted something very special. The line up was stellar: each and every speaker challenged my preconceptions with the kind of casualness that only in-depth experience can bring.</p>

</div></div><div class='g8 i2'><div class='pad'>

<p>Arguments for abandoning the purity of vanilla HTML (<a href='http://2012.full-frontal.org/speaker/james#james'>James Pearce</a>) were followed by a philosophical lesson on not throwing away the baby with the bathwater (<a href='http://2012.full-frontal.org/speaker/john#john'>John Allsopp</a>) and I found myself agreeing wholeheartedly with both, cognitive dissonance notwithstanding. As someone who isn't a fan of the mobile app world, I had to admit I was ignorant on the difficulties of implementing offline web apps (<a href='http://2012.full-frontal.org/speaker/andrew#andrew'>Andrew Betts</a>), and blissfully unaware of the absolute zoo of devices people really do try to access the web with (<a href='http://2012.full-frontal.org/speaker/anna#anna'>Anna Debenham</a>), webological purity be damned.</p>

<p>We've barely scratched the surface of what browsers can do (<a href='http://2012.full-frontal.org/speaker/paul#paul'>Paul Kinlan</a>), we need to chase the high of writing code and actually have it Just Work (<a href='http://2012.full-frontal.org/speaker/rebecca#rebecca'>Rebecca Murphey</a>), and above all, we need to remember where it all came from (<a href='http://2012.full-frontal.org/speaker/chris#chris'>Chris Wilson</a>), lest we repeat the mistakes of the past. If you weren't one of the lucky people who managed to snag tickets before it sold out, take some time out of your busy day to enjoy these sessions in video once they are posted online rather than just skipping through slides.</p>

<p>If there's one thing that stood out though, it's how little of what I heard on and off-stage is part of the daily discourse online in the tech world, in the news or on sites like HackerNews, Twitter and Reddit. We only see caricatures of these conversations. More than ever, I'm convinced I need to filter out these echochambers from my thoughts and seek out more substance. Particularly, the Silicon Valley-centric TechCrunch-driven worship of runaway success adds nothing, and only holds us back. It makes people think they need to chase something that only ever happens by accident, and diverts attention away from rolling up your sleeves and doing what actually needs to be done. This was emphasized all the more by the fact that the venue had no wi-fi, which meant everyone had their eyes away from their screens for a change.</p>

</div></div><div class='g8'><div class='pad'>

<p>To seal the deal, the conference was flanked by in-depth workshops, the obvious drinks and social gatherings and even a <a href='http://nodecopter.com'>NodeCopter hackathon</a>, where we made quadcopters do crazy things with nothing more than JavaScript. I only wish I'd been more rested and less jetlagged so I could've spoken to more folks the past few days.</p>

<p>Thank you to the organizers and volunteers, to the event crew, to my fellow speakers, to the people who travelled from near and far to listen, and to whomever decided to stick those funky legs on top of the cinema. They heralded quite literally that things were about to be turned upside down, and the event certainly delivered on that.</p>

<h2>Video and Slides</h2>

</div></div><aside class='g4 r'>
  <p class='tc'>
    <img src='/files/fullfrontal/legs.jpg' />The Legs
  </p>
</aside><aside class='g4 c'><div class='pad'>
  <p class='tc'>
    <a href='http://www.youtube.com/watch?v=ONN3jBly364&amp;list=UUyBAm31tEpZ17hka6ZvVqcg&amp;index=2&amp;feature=plcp'>
    <img alt='Video' src='/files/fullfrontal/video.jpg' style='top: 0' />
    Conference Video
    </a>
  </p>
</div></aside><aside class='g4'><div class='pad'>
  <p class='tc'>
    <a href='http://acko.net/files/fullfrontal/fullfrontal/slides-net/'>
    <img alt='Slides' src='/files/fullfrontal/slides.png' style='top: 0; -webkit-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); -moz-box-shadow: 0 2px 10px rgba(0, 0, 0, .3); box-shadow: 0 2px 10px rgba(0, 0, 0, .3);' />
    Slide Deck
    </a>
  </p>
</div></aside><div class='g8 i2'><div class='pad'>

<p><em>You can read more about MathBox in <a href='/blog/making-mathbox'>the follow-up blog post</a>.</em></p>
<p><em>The adventurous can go see how the sausage was made and check out <a href='https://github.com/unconed/MathBox.js'>the code for MathBox</a>, the library I wrote to make it happen, as well as the <a href='https://github.com/unconed/fullfrontal'>HTML5 slide deck</a>.</em></p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Introducing Facing.me]]></title>
    <link href="http://acko.net/blog/introducing-facing-me/"/>
    <updated>2012-04-25T00:00:00-07:00</updated>
    <id>http://acko.net/blog/introducing-facing-me</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>

  <h1>Introducing Facing.me</h1>
  <h2>A unique way to meet people</h2>

</div></div><div class='c' /><aside class='g5'>
  <p class='tc'>
    <img alt='Facing.me' src='/files/fme/facing.me.face.jpg' style='top: 0' />
  </p>
</aside><div class='g7'><div class='pad'>

<p>
We've been sending out whispers for a while now, but it's finally out: a new web site called <a href='http://facing.me'>Facing.me</a>. Coded and designed by <a href='http://mikejholly.com'>Michael Holly</a>, <a href='http://rosshj.com/'>Ross Howard-Jones</a> and myself, it promises a <em>unique way to meet people online</em>. This would be the point where the obvious question is dropped: wait, what… you built a <em>dating site</em>?</p>

<p>Sort of. Let me explain.</p>

<p>Having spent many years in the web world, we'd all gotten a bit complacent. The web has settled into its comfortable rhythms. Sites and applications can be modelled quickly and coded on your framework of choice. And nowadays, Web 2.0 cred comes baked in: clean URLs, semantic HTML, AJAX, data feeds, APIs, etc. Isn't this what we all wanted?</p>

<p>But the web continues to evolve, and giants are roaming the playground. Sites like Facebook and Twitter hold people's attention with surgical precision, while engines like Google answer your queries with lightning speed. Given that we've all slotted such services into our workflows and indeed lives, it seems only natural that 'indie' developers should keep up. We can't pretend that a 2000-era style web-page-with-ajax-sprinkles is the pinnacle of modern interactive design.</p>

<p>So we set out to try something different.</p>

</div></div><div class='img12'>
  <a href='http://facing.me'><img alt='Facing.me website' src='/files/fme/facing.me.site.jpg' /></a>
</div><!--
<div class="g8 i2 first"><div class="pad">  

</div></div>
--><div class='g6'><div class='pad'>

<h2>A Guy Walks into a Bar...</h2>

<p>If you've managed to score an invite, the first thing you'll see is the wall of faces that loads and fills the screen. The second thing you'll notice—we hope at least—is the lack of everything else.</p>

<p>The metaphor we kept in mind was the idea of walking into a bar, and looking around. If you see someone you like, you can go up to them and strike up a conversation. So that's exactly what the app lets you do, through video chat. You can pan around to see more people, and just keep going. If you're looking for something specific, you can filter your view with a simple "I'm looking for…" dialog.</p>

<p>As you mouse around, you can see who's online, and flip open their profile. If you want to strike up a video chat, it happens right there too. If the person is online, they'll see your request immediately in a popup and can choose to accept or decline after reviewing your profile. If they're offline, they'll see your request next time they visit.</p>

<p>To avoid missed connections, you can 'like' people you're interested in. You'll see (and hear) a notification pop up the moment they're online. You can keep the app open in a background tab and never miss a thing.</p>

<p>Aside from some minor social glue and a few fun little extras for you to discover, that's it. It's our twist on a <em>minimally viable product</em> if you will. Studies have shown that online matching algorithms are a poor predictor for how well people mesh in person. Until you meet face-to-face, you just don't know. We think direct, spontaneous video chat is a better first step rather than endless profile matching and messaging.</p>

</div></div><aside class='g6 m1'>
  <p class='p0'><img alt='Facing.me welcome screen' src='/files/fme/facing.me.start.jpg' /></p>

  <p class='p0'><img alt='Facing.me welcome screen' src='/files/fme/facing.me.profile.jpg' /></p>

  <p class='p0'><img alt='Facing.me notification' src='/files/fme/facing.me.growl.jpg' /></p>

  <p class='p0'><img alt='Facing.me liking' src='/files/fme/facing.me.like.jpg' /></p>
</aside><div class='g8 i2'><div class='pad'>

<h2>Polishing Bacon</h2>

<p>But despite its minimalism, a big aspect of Facing.me is the effort and care we put into it. Our goal was to achieve a level of polish typically reserved for premium iPhone apps and bring it into the browser. We wrapped the whole thing in a crisp design, enhanced with tasteful web fonts. But most importantly, we sought to expose the app's functionality with as little interruption as possible. To do that, we layered on plenty of transitions driven by CSS3 and JavaScript, and stream in data and content as needed.</p>

<p>Based on previous work in custom animations—and <a href='/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon'>bacon</a>—we refined the approach of using jQuery as an animation helper for completely custom transitions. We tell jQuery to animate placeholder properties on orphaned proxy divs, and key off those animations with per-frame code to drive the fancy stuff.</p>

</div></div><div class='img12'>
  <img alt='facing.me animation example' src='/files/fme/transition.jpg' />
</div><div class='g8 i2'><div class='pad'>
<p>As a result, we can have a photo grow a picture frame as you pick it up, and then flip it around to show a person's full profile. This careful choreography involves animating about a dozen CSS properties, including borders, shadows, margins and 3D transforms, all with custom expressions and hand-tuned animation curves. Similar transitions are used for lightbox dialogs.</p>

<p>Throughout all of this, the animations remain eminently manageable. We can interrupt and reverse them at any point, and run multiple copies at the same time, thanks to pervasive use of view controllers. Far from being a useless tech demo, it actually enables us to craft the user experience exactly the way we like it: being able to acknowledge user intentions with intuitive feedback no matter what's going on, and firing off new events and requests without worrying about the internal state. Gone are the fragile jQuery behavior soups of old.</p>

<p>The one downside is that only the newer browsers—i.e. Chrome, Safari and Firefox—get to see everything the way it was intended. And actually the performance in Firefox is still a bit disappointing. IE9 users will have to be satisfied with a crude 2D approximation until IE10 comes out.</p>

</div></div><div class='g8 first'><div class='pad'>

<h2>Rapid Rails and Real-Time Node</h2>

<p>To make all this work effectively on the server-side, we used a dual-mode stack of Rails and Node.js.</p>

<p>The Rails side houses the app's models and controllers, and provides an API for all the client-side JavaScript to do its job. Video chats are handled through Flash and routed through its built-in peer-to-peer functionality.</p>

<p>The node.js component acts as a real-time presence daemon which users connect to over socket.io. It's used to drive the status notifications and to coordinate the video chats. We can exchange any sort of notifications between users with a publish-subscribe model, opening up many interesting avenues for future development.</p>

<p>Overall, this approach has worked out great. Rails' ActiveRecord and the stack around it allowed us to build out functionality quickly and with just the right amount of necessary baggage. We made generous use of Ruby Gems to save time while still maintaining full control.</p>

<p>Node.js's event-driven model adds real-time signalling with no hassle. For the few cases where node.js needs to interface with the Rails database directly, we slot in some manual SQL to take care of that. For everything else, Rails and node.js exchange signed data through the browser.</p>

</div></div><aside class='g4 m1'><div class='pad'>
  <p><img alt='Node.js' src='/files/fme/nodejs.png' /></p>

  <p><img alt='Rails' src='/files/fme/rails.jpg' /></p>
</div></aside><div class='g8 i2 first'><div class='pad'>

<h2>Come Take it for a Spin</h2>

<p>Finally, we also put our heads together and made a promo video, voiced by the lovely <a href='https://twitter.com/t1nah'>Tina Hoang</a>:</p>

</div></div><pre class='markdown-html-error' style='border: solid 3px red; background-color: pink'>REXML could not parse this XML/HTML: 
&lt;div style=&quot;max-width: 854px; width: 100%; margin: 0 auto&quot;&gt;

&lt;!--
&lt;iframe width=&quot;854&quot; height=&quot;480&quot; src=&quot;http://www.youtube.com/embed/Ua67Hf1T7yI?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
--&gt;
&lt;iframe src=&quot;http://player.vimeo.com/video/41056588?title=0&amp;amp;byline=0&amp;amp;portrait=0&quot; width=&quot;854&quot; height=&quot;480&quot; frameborder=&quot;0&quot; webkitAllowFullScreen mozallowfullscreen allowFullScreen&gt;&lt;/iframe&gt;

&lt;/div&gt;</pre><div class='g8 i2'><div class='pad'>

<p>Built in our spare time by just 3 guys in a virtual garage, we're pretty proud of the end result. We'd love for you to take it for a spin, so <a href='http://facing.me'>head over to facing.me</a> and grab yourself an invite. There's a feedback form built-in, and any suggestions are welcome.</p>

<p>Discuss on <a href='https://plus.google.com/112457107445031703644/posts/efHMJE1Wxx2'>Google Plus</a>.</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[This is Your Brain on CSS]]></title>
    <link href="http://acko.net/blog/this-is-your-brain-on-css/"/>
    <updated>2012-02-19T00:00:00-08:00</updated>
    <id>http://acko.net/blog/this-is-your-brain-on-css</id>
    <content type="html"><![CDATA[<div style='display: none'><img alt='' src='/files/mri/cover.jpg' /></div><div class='g8 i2 first'><div class='pad'>

<h1>This is Your Brain on CSS</h1>

<p>First things first: the CSS 3D renderer used to power this site is now <a href='https://github.com/unconed/CSS3D.js'>available on GitHub.com</a>. However, it's still limited to only solid lines and planes. It's also limited to WebKit browsers, as Firefox's CSS 3D support just isn't quite there yet.</p>

<p>
  But CSS 3D is not a one trick pony, and as with many things, what you get out of it depends entirely on what you put in. So here's a disembodied head made out of CSS 3D. It consists of nothing more than a bunch of images stacked up against each other, and integrates perfectly with the existing 3D parallax on this site. Click and drag to rotate, or use the slider to look inside.
</p>

<link href='/files/mri/head.css' media='screen' rel='stylesheet' type='text/css' />

<div id='head-3d'>
  <div class='head-viewport'>
    <div class='CSS3DCamera' data-var='transform'>
      <div class='pedestal'>
        
      </div>
      <div class='VolumetricView' data-var='phi slice' />
    </div>
  </div>
  <div class='Slider' data-var='slice' />
</div>

<p>
  Making the basic effect was actually quite easy. I took an MRI from the <a href='http://graphics.stanford.edu/data/voldata/'>Stanford Volume Data Archive</a> and wrote a small script to turn it into a sheet of CSS sprites. There's <a href='/files/mri/MRbrain-color.jpg'>one file for color</a>, <a href='/files/mri/MRbrain-alpha8.png'>one for opacity</a>, totalling about 2.1 MB. Both files are composited into Canvases and placed in slices into the DOM, offset forward or backwards in 3D. Then there's just some minor logic to rotate the slices in 90 degree increments to follow the camera.
</p>

<p>
  But the slices are rendered as is, and the MRI consists of <a href='/files/mri/MRbrain-alpha8.png'>boring grayscale data</a>. Luckily, I can precompute any amount of shaders and effects I want and just bake them into the slices. I geeked out by applying fake specular lighting, for that 'fresh meat' look, and volumetric obscurance to enhance the sense of depth on the inside. I changed the palette to gory colors based on local density, giving the impression of flesh and bone knitting itself together. Creepy, but cool.
</p>

<p>
  I wrapped it in a custom widget, using straight up CSS rather than Three.js this time. I've wanted to play with <a href='http://worrydream.com/Tangle/'>Tangle.js</a>, so I used that to hook up the camera controls and slider. That's pretty much it. In an ideal world, the jarring transition when rotating would be covered up by a nice transition, but the browsers don't like it.
</p>

</div></div><pre class='markdown-html-error' style='border: solid 3px red; background-color: pink'>REXML could not parse this XML/HTML: 
&lt;script&gt;
setTimeout(function () {
  if ($(&apos;div.css3d-support:visible&apos;).length == 0) return;

    var model = {
      initialize: function () {
        // State
        this.theta = 0.0;
        this.phi = 0.5;
        this.slice = 0;
      },

      update: function () {
        this.transform = &apos;rotateX(&apos;+ -this.theta +&apos;rad) rotateY(&apos;+ this.phi +&apos;rad)&apos;;
      },
    };

    Tangle.classes.CSS3DCamera = {
      initialize: function (element, options, tangle, variables) {
        this.element = element;
        this.$element = $(element);

        var that = this;
        $(element).mousedown(function (event) {
          that.drag = true;
          that.dragLast = that.dragOrigin = { x: event.pageX, y: event.pageY };
          event.preventDefault();
        });
        $(document).mouseup(function (event) {
          that.drag = false;
        });
        $(document).mousemove(function (event) {
          if (!that.drag) return;
          var total = { x: event.pageX - that.dragOrigin.x, y: event.pageY - that.dragOrigin.y },
              delta = { x: event.pageX - that.dragLast.x, y: event.pageY - that.dragLast.y };
          that.dragLast = { x: event.pageX, y: event.pageY };
          mousemove(that.dragOrigin, total, delta);
        });

        function mousemove(origin, total, delta) {
          var phi = tangle.getValue(&apos;phi&apos;) + delta.x * .01,
              theta = Math.min(1, Math.max(-.2, tangle.getValue(&apos;theta&apos;) + delta.y * .01));

          tangle.setValue(&apos;phi&apos;, phi);
          tangle.setValue(&apos;theta&apos;, theta);
        }
      },

      update: function (element, value) {
        this.$element.css({
          WebkitTransform: value,
          MozTransform: value,
          transform: value,
        })
      },
    },

    Tangle.classes.Slider = {
      initialize: function (element, options, tangle, variables) {
        var that = this;

        this.tangle = tangle;
        this.element = element;

        this.$element = $(this.element);
        this.$bar = $(&apos;&lt;div class=&quot;bar&quot;&gt;&apos;).appendTo(this.element);
        this.$handle = $(&apos;&lt;div class=&quot;handle&quot;&gt;&apos;).appendTo(this.element);

        var that = this;
        $(this.element).mousedown(function (event) {
          that.origin = that.$element.offset().left;
          that.width = that.$bar.width();
          that.drag = true;
          return false;
        });
        $(document).mousemove(function (event) {
          if (!that.drag) return;
          tangle.setValue(&apos;slice&apos;, Math.max(0, Math.min(1, (event.pageX - that.origin) / that.width)));
        });
        $(document).mouseup(function () {
          that.drag = false;
        });
      },

      update: function (element, value) {
        this.$handle.css(&apos;left&apos;, (100*value) + &apos;%&apos;);
      },
    },

    Tangle.classes.VolumetricView = {
      initialize: function (element, options, tangle, variables) {
        var that = this;

        this.tangle = tangle;
        this.element = element;
        this.$element = $(element);

        this.width = 364;
        this.height = 384;
        this.depth = 256;

        this.resX = 182;
        this.resY = 192;
        this.slices = 108;
        this.stride = 8;
        this.rows = Math.ceil(this.slices / this.stride);

        this.createSlices();

        var load = 0;
        this.image = new Image();
        this.image.onload = function () {
          if (++load == 2) that.drawSlices(); 
        };

        this.mask = new Image();
        this.mask.onload = function () {
          if (++load == 2) that.drawSlices(); 
        };

//        this.image.src = &apos;data/MRbrain.png&apos;;
        this.image.src = &apos;/files/mri/MRbrain-color.jpg&apos;;
        this.mask.src = &apos;/files/mri/MRbrain-alpha8.png&apos;;
      },

      update: function (element, value) {
        var l = Math.abs(Math.cos(value)) &gt; Math.abs(Math.sin(value));

        if (this.l != l || this.slice != slice) {
          var slice = this.tangle.getValue(&apos;slice&apos;), index, 
              n = (l ? Math.cos(value) : Math.sin(value)) &gt; 0,
              sn = n ? slice : 1 - slice;

          index = +(this.$slicesX.length * sn);

          this.$slicesX.css(&apos;display&apos;, l ? &apos;block&apos; : &apos;none&apos;);
          this.$slicesX
            .slice().css(&apos;opacity&apos;, n ? .95 : .001).end()
            .slice(0, index).css(&apos;opacity&apos;, !n ? .95 : .001).end();

          index = +(this.$slicesZ.length * sn);
  //        this.$slicesZ[!l ? &apos;show&apos; : &apos;hide&apos;]();
          this.$slicesZ.css(&apos;display&apos;, !l ? &apos;block&apos; : &apos;none&apos;);
          this.$slicesZ
            .slice(index).css(&apos;opacity&apos;, n ? .95 : .001).end()
            .slice(0, index).css(&apos;opacity&apos;, !n ? .95 : .001).end();

          this.slice = slice;
          this.l = l;
        }
      },

      createSlices: function () {
        this.$element.empty();
        this.ctxX = [];
        this.ctxZ = [];

        // X slices
        for (var i = 0; i &lt; this.slices; ++i) {
          var z = -((i / this.slices) - .5) * this.depth,
              t = &apos;translateZ(&apos; + z + &apos;px) translateX(70px)&apos;;

          var $canvas = $(&apos;&lt;canvas&gt;&apos;)
              .addClass(&apos;x&apos;)
              .attr(&apos;width&apos;, this.resX)
              .attr(&apos;height&apos;, this.resY)
              .css({
                width: this.width,
                height: this.height,
                WebkitTransform: t,
                MozTransform: t,
                transform: t,
                opacity: 1,
              });

          this.$element.append($canvas);
          this.ctxX.push($canvas[0].getContext(&apos;2d&apos;));
        }

        // Z slices
        for (var i = 0; i &lt; this.resX; ++i) {
          var z = -(this.depth - this.width) / 2,
              x = ((i / this.resX) - .5) * this.width,
              t = &apos;translateX(&apos; + x + &apos;px) translateX(70px) rotateY(90deg) translateZ(&apos; + z + &apos;px)&apos;;

          var $canvas = $(&apos;&lt;canvas&gt;&apos;)
              .addClass(&apos;z&apos;)
              .attr(&apos;width&apos;, this.slices)
              .attr(&apos;height&apos;, this.resY)
              .css({
                width: this.depth,
                height: this.height,
                WebkitTransform: t,
                MozTransform: t,
                transform: t,
                opacity: 0,
              });

          this.$element.append($canvas);
          this.ctxZ.push($canvas[0].getContext(&apos;2d&apos;));
        }

        this.$slicesX = this.$element.find(&apos;canvas.x&apos;);
        this.$slicesZ = this.$element.find(&apos;canvas.z&apos;);
      },

      drawSlices: function () {

        var s = this.stride,
            sl = this.slices,
            r = this.rows,
            w = this.resX,
            h = this.resY,
            img = this.image,
            mask = this.mask,
            ctxX = this.ctxX,
            ctxZ = this.ctxZ;

        var alpha, color;

        // X slices
        this.$slicesX.each(function (i) {
          var c = ctxX[i],
              ox = (i % s) * w, oy = Math.floor(i / s) * h;

          // Draw alpha channel and get pixels
          c.drawImage(mask, ox, oy, w, h, 0, 0, w, h);
          alpha = c.getImageData(0, 0, w, h);

          // Draw color channel and get pixels
          c.drawImage(img, ox, oy, w, h, 0, 0, w, h);
          color = c.getImageData(0, 0, w, h);

          // Copy red to alpha.
          var src = alpha.data, dst = color.data;
          for (var y = 0; y &lt; h; ++y) {
            for (var x = 0; x &lt; w; ++x) {
              var o = (x + y * w) * 4;
              dst[o + 3] = src[o];
            }
          }

          // Draw RGBA.
          c.putImageData(color, 0, 0);
        });

        // Z slices
        this.$slicesZ.each(function (i) {
          var c = ctxZ[i];

          // Render transposed slices as vertical strips.
          for (var j = 0; j &lt; sl; ++j) {
            var ox = (j % s) * w, oy = Math.floor(j / s) * h;

            // Draw alpha channel
            c.drawImage(mask, ox + i, oy, 1, h, j, 0, 1, h);
          }

          // Get pixels
          alpha = c.getImageData(0, 0, w, h);

          // Render transposed slices as vertical strips.
          for (var j = 0; j &lt; sl; ++j) {
            var ox = (j % s) * w, oy = Math.floor(j / s) * h;

            // Draw color channel
            c.drawImage(img, ox + i, oy, 1, h, j, 0, 1, h);
          }
          // Get pixels
          color = c.getImageData(0, 0, w, h);

          // Copy red to alpha.
          var src = alpha.data, dst = color.data;
          for (var y = 0; y &lt; h; ++y) {
            for (var x = 0; x &lt; w; ++x) {
              var o = (x + y * w) * 4;
              dst[o + 3] = src[o];
            }
          }

          // Draw RGBA.
          c.putImageData(color, 0, 0);
        });
      },

    };

    var tangle = new Tangle($(&apos;#head-3d&apos;)[0], model);

}, 200);
&lt;/script&gt;</pre><pre class='markdown-html-error' style='border: solid 3px red; background-color: pink'>REXML could not parse this XML/HTML: 
&lt;script&gt;
//
//  Tangle.js
//  Tangle 0.1.0
//
//  Created by Bret Victor on 5/2/10.
//  (c) 2011 Bret Victor.  MIT open-source license.
//
//  ------ model ------
//
//  var tangle = new Tangle(rootElement, model);
//  tangle.setModel(model);
//
//  ------ variables ------
//
//  var value = tangle.getValue(variableName);
//  tangle.setValue(variableName, value);
//  tangle.setValues({ variableName:value, variableName:value });
//
//  ------ UI components ------
//
//  Tangle.classes.myClass = {
//     initialize: function (element, options, tangle, variable) { ... },
//     update: function (element, value) { ... }
//  };
//  Tangle.formats.myFormat = function (value) { return &quot;...&quot;; };
//

var Tangle = this.Tangle = function (rootElement, modelClass) {

    var tangle = this;
    tangle.element = rootElement;
    tangle.setModel = setModel;
    tangle.getValue = getValue;
    tangle.setValue = setValue;
    tangle.setValues = setValues;

    var _model;
    var _nextSetterID = 0;
    var _setterInfosByVariableName = {};   //  { varName: { setterID:7, setter:function (v) { } }, ... }
    var _varargConstructorsByArgCount = [];


    //----------------------------------------------------------
    //
    // construct

    initializeElements();
    setModel(modelClass);
    return tangle;


    //----------------------------------------------------------
    //
    // elements

    function initializeElements() {
        var elements = rootElement.getElementsByTagName(&quot;*&quot;);
        var interestingElements = [];
        
        // build a list of elements with class or data-var attributes
        
        for (var i = 0, length = elements.length; i &lt; length; i++) {
            var element = elements[i];
            if (element.getAttribute(&quot;class&quot;) || element.getAttribute(&quot;data-var&quot;)) {
                interestingElements.push(element);
            }
        }

        // initialize interesting elements in this list.  (Can&apos;t traverse &quot;elements&quot;
        // directly, because elements is &quot;live&quot;, and views that change the node tree
        // will change elements mid-traversal.)
        
        for (var i = 0, length = interestingElements.length; i &lt; length; i++) {
            var element = interestingElements[i];
            
            var varNames = null;
            var varAttribute = element.getAttribute(&quot;data-var&quot;);
            if (varAttribute) { varNames = varAttribute.split(&quot; &quot;); }

            var views = null;
            var classAttribute = element.getAttribute(&quot;class&quot;);
            if (classAttribute) {
                var classNames = classAttribute.split(&quot; &quot;);
                views = getViewsForElement(element, classNames, varNames);
            }
            
            if (!varNames) { continue; }
            
            var didAddSetter = false;
            if (views) {
                for (var j = 0; j &lt; views.length; j++) {
                    if (!views[j].update) { continue; }
                    addViewSettersForElement(element, varNames, views[j]);
                    didAddSetter = true;
                }
            }
            
            if (!didAddSetter) {
                var formatAttribute = element.getAttribute(&quot;data-format&quot;);
                var formatter = getFormatterForFormat(formatAttribute, varNames);
                addFormatSettersForElement(element, varNames, formatter);
            }
        }
    }
            
    function getViewsForElement(element, classNames, varNames) {   // initialize classes
        var views = null;
        
        for (var i = 0, length = classNames.length; i &lt; length; i++) {
            var clas = Tangle.classes[classNames[i]];
            if (!clas) { continue; }
            
            var options = getOptionsForElement(element);
            var args = [ element, options, tangle ];
            if (varNames) { args = args.concat(varNames); }
            
            var view = constructClass(clas, args);
            
            if (!views) { views = []; }
            views.push(view);
        }
        
        return views;
    }
    
    function getOptionsForElement(element) {   // might use dataset someday
        var options = {};

        var attributes = element.attributes;
        var regexp = /^data-[\w\-]+$/;

        for (var i = 0, length = attributes.length; i &lt; length; i++) {
            var attr = attributes[i];
            var attrName = attr.name;
            if (!attrName || !regexp.test(attrName)) { continue; }
            
            options[attrName.substr(5)] = attr.value;
        }
         
        return options;   
    }
    
    function constructClass(clas, args) {
        if (typeof clas !== &quot;function&quot;) {  // class is prototype object
            var View = function () { };
            View.prototype = clas;
            var view = new View();
            if (view.initialize) { view.initialize.apply(view,args); }
            return view;
        }
        else {  // class is constructor function, which we need to &quot;new&quot; with varargs (but no built-in way to do so)
            var ctor = _varargConstructorsByArgCount[args.length];
            if (!ctor) {
                var ctorArgs = [];
                for (var i = 0; i &lt; args.length; i++) { ctorArgs.push(&quot;args[&quot; + i + &quot;]&quot;); }
                var ctorString = &quot;(function (clas,args) { return new clas(&quot; + ctorArgs.join(&quot;,&quot;) + &quot;); })&quot;;
                ctor = eval(ctorString);   // nasty
                _varargConstructorsByArgCount[args.length] = ctor;   // but cached
            }
            return ctor(clas,args);
        }
    }
    

    //----------------------------------------------------------
    //
    // formatters

    function getFormatterForFormat(formatAttribute, varNames) {
        if (!formatAttribute) { formatAttribute = &quot;default&quot;; }

        var formatter = getFormatterForCustomFormat(formatAttribute, varNames);
        if (!formatter) { formatter = getFormatterForSprintfFormat(formatAttribute, varNames); }
        if (!formatter) { log(&quot;Tangle: unknown format: &quot; + formatAttribute); formatter = getFormatterForFormat(null,varNames); }

        return formatter;
    }
        
    function getFormatterForCustomFormat(formatAttribute, varNames) {
        var components = formatAttribute.split(&quot; &quot;);
        var formatName = components[0];
        if (!formatName) { return null; }
        
        var format = Tangle.formats[formatName];
        if (!format) { return null; }
        
        var formatter;
        var params = components.slice(1);
        
        if (varNames.length &lt;= 1 &amp;&amp; params.length === 0) {  // one variable, no params
            formatter = format;
        }
        else if (varNames.length &lt;= 1) {  // one variable with params
            formatter = function (value) {
                var args = [ value ].concat(params);
                return format.apply(null, args);
            };
        }
        else {  // multiple variables
            formatter = function () {
                var values = getValuesForVariables(varNames);
                var args = values.concat(params);
                return format.apply(null, args);
            };
        }
        return formatter;
    }
    
    function getFormatterForSprintfFormat(formatAttribute, varNames) {
        if (!sprintf || !formatAttribute.test(/\%/)) { return null; }

        var formatter;
        if (varNames.length &lt;= 1) {  // one variable
            formatter = function (value) {
                return sprintf(formatAttribute, value);
            };
        }
        else {
            formatter = function (value) {  // multiple variables
                var values = getValuesForVariables(varNames);
                var args = [ formatAttribute ].concat(values);
                return sprintf.apply(null, args);
            };
        }
        return formatter;
    }

    
    //----------------------------------------------------------
    //
    // setters
    
    function addViewSettersForElement(element, varNames, view) {   // element has a class with an update method
        var setter;
        if (varNames.length &lt;= 1) {
            setter = function (value) { view.update(element, value); };
        }
        else {
            setter = function () {
                var values = getValuesForVariables(varNames);
                var args = [ element ].concat(values);
                view.update.apply(view,args);
            };
        }

        addSetterForVariables(setter, varNames);
    }

    function addFormatSettersForElement(element, varNames, formatter) {  // tangle is injecting a formatted value itself
        var span = null;
        var setter = function (value) {
            if (!span) { 
                span = document.createElement(&quot;span&quot;);
                element.insertBefore(span, element.firstChild);
            }
            span.innerHTML = formatter(value);
        };

        addSetterForVariables(setter, varNames);
    }
    
    function addSetterForVariables(setter, varNames) {
        var setterInfo = { setterID:_nextSetterID, setter:setter };
        _nextSetterID++;

        for (var i = 0; i &lt; varNames.length; i++) {
            var varName = varNames[i];
            if (!_setterInfosByVariableName[varName]) { _setterInfosByVariableName[varName] = []; }
            _setterInfosByVariableName[varName].push(setterInfo);
        }
    }

    function applySettersForVariables(varNames) {
        var appliedSetterIDs = {};  // remember setterIDs that we&apos;ve applied, so we don&apos;t call setters twice
    
        for (var i = 0, ilength = varNames.length; i &lt; ilength; i++) {
            var varName = varNames[i];
            var setterInfos = _setterInfosByVariableName[varName];
            if (!setterInfos) { continue; }
            
            var value = _model[varName];
            
            for (var j = 0, jlength = setterInfos.length; j &lt; jlength; j++) {
                var setterInfo = setterInfos[j];
                if (setterInfo.setterID in appliedSetterIDs) { continue; }  // if we&apos;ve already applied this setter, move on
                appliedSetterIDs[setterInfo.setterID] = true;
                
                setterInfo.setter(value);
            }
        }
    }
    

    //----------------------------------------------------------
    //
    // variables

    function getValue(varName) {
        var value = _model[varName];
        if (value === undefined) { log(&quot;Tangle: unknown variable: &quot; + varName);  return 0; }
        return value;
    }

    function setValue(varName, value) {
        var obj = {};
        obj[varName] = value;
        setValues(obj);
    }

    function setValues(obj) {
        var changedVarNames = [];

        for (var varName in obj) {
            var value = obj[varName];
            var oldValue = _model[varName];
            if (oldValue === undefined) { log(&quot;Tangle: setting unknown variable: &quot; + varName);  continue; }
            if (oldValue === value) { continue; }  // don&apos;t update if new value is the same

            _model[varName] = value;
            changedVarNames.push(varName);
        }
        
        if (changedVarNames.length) {
            applySettersForVariables(changedVarNames);
            updateModel();
        }
    }
    
    function getValuesForVariables(varNames) {
        var values = [];
        for (var i = 0, length = varNames.length; i &lt; length; i++) {
            values.push(getValue(varNames[i]));
        }
        return values;
    }

                    
    //----------------------------------------------------------
    //
    // model

    function setModel(modelClass) {
        var ModelClass = function () { };
        ModelClass.prototype = modelClass;
        _model = new ModelClass;

        updateModel(true);  // initialize and update
    }
    
    function updateModel(shouldInitialize) {
        var ShadowModel = function () {};  // make a shadow object, so we can see exactly which properties changed
        ShadowModel.prototype = _model;
        var shadowModel = new ShadowModel;
        
        if (shouldInitialize) { shadowModel.initialize(); }
        shadowModel.update();
        
        var changedVarNames = [];
        for (var varName in shadowModel) {
            if (!shadowModel.hasOwnProperty(varName)) { continue; }
            if (_model[varName] === shadowModel[varName]) { continue; }
            
            _model[varName] = shadowModel[varName];
            changedVarNames.push(varName);
        }
        
        applySettersForVariables(changedVarNames);
    }


    //----------------------------------------------------------
    //
    // debug

    function log (msg) {
        if (window.console) { window.console.log(msg); }
    }

};  // end of Tangle


//----------------------------------------------------------
//
// components

Tangle.classes = {};
Tangle.formats = {};

Tangle.formats[&quot;default&quot;] = function (value) { return &quot;&quot; + value; };

&lt;/script&gt;</pre>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Making Love to WebKit]]></title>
    <link href="http://acko.net/blog/making-love-to-webkit/"/>
    <updated>2012-01-09T00:00:00-08:00</updated>
    <id>http://acko.net/blog/making-love-to-webkit</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>
  <h1>Making Love To WebKit</h1>
  <h2>Parallax, GPUs and Technofetishism</h2>

  <p>
    If the world is going to end in 2012, Acko.net will at least go out in style: I've redesigned. Those of you reading through RSS readers will want to <a href='http://acko.net/'>enter through the front door</a> in a WebKit-browser like Chrome, Safari or even an iPad.
  </p>

  <p class='bubble'>
    The last design was meant to feel spacious, the new design <em>is</em> spacious, thanks to generous use of CSS 3D transforms.
  </p>

  <h3>CSS 3D vs. WebGL</h3>
  
  <p>
    This idea started with an accidental discovery: if you put a CSS perspective on a scrollable &lt;DIV&gt;, then 3D elements inside that &lt;DIV&gt; will retain their perspective while you scroll. This results in smooth, native parallax effects, and makes objects jump out of the page, particularly when using an analog input device with inertial scrolling.
  </p>

  <p>
    This raises the obvious question: how far can you take it? Of course, this only works on WebKit browsers, who currently have the only CSS 3D implementation out of beta, so it's not a viable strategy by itself yet. IE10 and Firefox will be the next browsers to offer it. There's WebGL in Chrome and Firefox that can be used to do similar things, but WebGL is its own sandbox: you can't put DOM elements in there, or use native interaction. And any amount of WebGL rendering in response to e.g. scrolling is going to involve some amount of lag. Still, I wasn't going put a lot of effort into making a CSS 3D-only design without some backup.
  </p>

  <p>
    That's why I actually built the whole thing on top of <a href='https://github.com/mrdoob/three.js/'>Three.js</a>, mrdoob's excellent JavaScript 3D engine. Aside from providing a comprehensive standard library for 3D manipulation, it also lets you swap out the rendering component. Out of the box, it can render to a 2D canvas, a WebGL canvas, or SVG.
  </p>

</div></div><div class='g7'><div class='pad'>

  <h3>The DOM Scenegraph</h3>
  
  <p>
    So I augmented it with a CSS 3D renderer (<a href='https://github.com/unconed/CSS3D.js'>GitHub</a>). It reads out the scene and renders each object using DOM elements, shaped and transformed into the right 3D position, orientation and appearance. They sit ‘in’ the page, and the browser projects and composits them for you. Of course, this only works for simple geometric shapes like lines or rectangles, but luckily that's all I need.
  </p>
  
  <p>
    It would be too slow to have to render out new elements for every frame, so the CSS 3D renderer's elements persist. Moving or rotating an object involves just changing a CSS property. Same for the camera: the entire scene is wrapped in a &lt;DIV&gt; that has its own 3D transform.
  </p>

  <p>
    So it's VRML all over again, but this time, it actually sort of performs. With our browsers being actual 3D engines, it's not a huge leap from here to having a &lt;MESH&gt; tag in HTML6, can-of-worms-factor not withstanding.
  </p>

  <p>
    Having built a quick prototype, I was satisfied with how well it worked, particularly in Safari on OS X, where the cross-pollination from the iPhone's mature tile-based GPU renderer has clearly paid off and there is no lag at all.
  </p>

</div></div><aside class='g5'>
  <p class='m3'><img alt='CSS 3D DOM' src='/files/making-love-to-webkit/dom.png' />The DOM tree of this page. Yup, nasty.</p>
</aside><div class='c' /><aside class='g5'>
  <p class='m3'><img alt='Acko.net old design' src='/files/making-love-to-webkit/old-acko.png' />Previous design (<a href='/tag/acko.net'>Archive</a>)</p>
  <p><img alt='Initial sketch' src='/files/making-love-to-webkit/sketch.jpg' />Initial sketch</p>
  <p><img alt='Initial sketch' src='/files/making-love-to-webkit/editor.png' />Scene editor</p>
</aside><div class='g7'><div class='pad'>

  <h3>
    Design Process
  </h3>

  <p>
    Now all that was needed was a design. Last time I drew out a manual perspective drawing in Illustrator, which was tedious, but still basically came down to designing a flat image. This time, it would have to work in 3D. I started with a quick sketch to get a feel for the perspective, now that it no longer needed to double as a flat frame for the site's content.
  </p>
  
  <p>
    Simple geometric shapes, parallel lines, consistent angles. Simple enough. But if real perspective was involved, I would have to place items so they would look good from multiple angles, and each would need convincing depth and shading. To do this all by hand, typing out coordinates and perpetually refreshing the page, would take forever.
  </p>
  
  <p>
    So instead I built a simple editor to speed up the process. It's super ghetto, and basically just exists to manipulate the colors, positions and orientations of objects in a Three.js scene. It spits out a JSON object describing them, which can then be unserialized again into a scene.
  </p>

  <p>
    This also helped maintain a consistent palette. The colors are built from a few base tints, brightened or darked in linear RGB—i.e. before gamma correction. This ensures even tones and allowed for easy color adjustments.
  </p>

  <p>
    The editor is almost entirely keyboard operated, but with its minimum amount of features I was at least able to place items in 3D, copy/paste objects and see it from any angle or position I wanted. To 'save', I just copied the output into a .JS file, where I could make manual tweaks too if necessary.
  </p>

  <p>
    As for the actual site and content, I wanted to keep it much more sober. Like many others these days, I want to treat blogging more like publishing. That way I can focus on crafting each post more like an article with illustrations and asides rather than just a text blog.
  </p>
    
  <p>
    Hence, while there's a big party upstairs, it's all <a href='http://www.amazon.com/Elements-Typographic-Style-Robert-Bringhurst/dp/0881791326'>typography</a> down below. The font of choice is <a href='http://processtypefoundry.com/fonts/klavika/'>Klavika</a>, a humanist/geometric sans-serif with just the right kind of “Dutch Art Museum Signage” meets “Cyberpunk” I was looking for. The layout is a responsive multi-column grid that collapses down for smaller screens and devices. Finally, a strict vertical rhythm is enforced in the lines to keep everything nice and tidy.
  </p>
  
</div></div><div class='g9'><div class='pad'>
  <h4>Editor</h4>
  <iframe frameborder='0' height='580' src='/load.html' width='680' />
  <p class='m0 l0'>
    <a class='editor-open' href='/editor.html' target='_blank'>Open editor in new window</a>
  </p>
</div></div><div class='g3'><div class='pad'>
  <h4>Controls</h4>
  <ul class='flat'>
    <li><kbd>Click</kbd>+<kbd>Drag</kbd> — Orbit camera</li>

    <li><kbd>Enter</kbd> — New object</li>
    <li><kbd>Space</kbd> — Clone object</li>
    <li><kbd>Backspace</kbd> — Delete object</li>
    <li><kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd><br />Cycle through objects</li>

    <li><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd>&nbsp; <kbd>Q</kbd><kbd>E</kbd><br />Move object</li>
    <li><kbd>Shift</kbd>+<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> &nbsp; <kbd>Q</kbd><kbd>E</kbd><br />Resize object</li>
    <li><kbd>Ctrl</kbd>+<kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> &nbsp; <kbd>Q</kbd><kbd>E</kbd><br />Move camera</li>

    <li><kbd>[</kbd><kbd>]</kbd> — Lower/raise units</li>
    <li><kbd>Z</kbd><kbd>X</kbd><br />Orbit distance</li>
    <li><kbd>T</kbd>/<kbd>T</kbd>/<kbd>U</kbd><br />Tag/untag/untag all</li>
  </ul>
</div></div><div class='g8 i2'><div class='pad'>
  <h3>She cannae take the power cap'n!</h3>
  <p>
    307 objects later it was finished, and not a single image was used. Unfortunately, as you can see there are tons of glitches in the editor—though some objects only have one side by design, and it works a lot better in a separate window. CSS 3D was never meant to do this, and you often see incorrect depth layering and flickering. Luckily most of these are caused by the floating grid markers and aren't a problem in the final view. The rest was resolved by splitting up objects or dual layering problematic surfaces, but some minor problems remain. Also for some reason, the background &lt;DIV&gt;'s click areas extend beyond their visible area, causing some click layering issues that I had to work around. Text resizing in the browser also leads to breakage, though multi-touch zoom works in Safari.
  </p>
  
  <p>
    Performance in Safari is wonderfully smooth too, but Chrome OS X starts to lag a bit. Luckily the effects are turned off as soon as they go off screen, so any lag should be confined to the top of the page. Finally, there's also a random bug where sometimes the page will refuse to scroll if the mouse is over a 3D object, which is unfortunate, but also near-impossible to reproduce reliably.
  </p>
  
  <p>
    In theory the iPad would perform second, but it has its own issues. The use of page-in-page scrolling disables inertia, but this is entirely beyond my control. The other issue is that sometimes, the iPad will decide to render the page content at lower resolution, making it hard to read. I guess the CSS wizardry confuses its GPU texture management. A refresh usually fixes this.
  </p>
  
  <p>
    I also discovered some funny ways of abusing CSS 3D for weird effects. If you have a WebKit browser, scroll to the top and enter the Konami code for an impressionistic version of the same thing.
  </p>
    
  <p>
    I guess I'm now the proud owner of the first unofficial CSS 3D ‘ACID’ test. I'm eager to see how the next browser handles it. If it ends up being a silly idea in the long run, I can always just switch the output to WebGL, but for now I'm willing to run with it. I put in a universal CSS 3D detector and prefixes for all the major browsers.
  </p>
  
  <p>
    For non-CSS 3D browsers, I simply rendered the header into a static image. It's not as fun without the shifting perspective, but it adds its own kind of optical illusion as you scroll down.
  </p>
  
  <h3>
    Putting it all together
  </h3>
  
  <p>
    To power the site, I got rid of Drupal and replaced it with the nimble <a href='http://jekyllrb.com/'>Jekyll</a>. Hat tip to <a href='http://walkah.net/'>James Walker</a>, who did the same thing just a few days earlier and put all the code on GitHub to learn from.
  </p>
    
  <p>
    I've been really impressed with Jekyll's simple workflow, and though it's all static HTML, it's a refreshing change of pace. And thanks to client-side JS, it doesn't preclude adding interactive elements at all. I can treat my site as just a database of documents retrievable over HTTP, and wrap the logic around that.
  </p>
  
  <p>
    So I created a nice client-side navigator that transitions between pages, using 2D transforms, which also work on Firefox. It uses the HTML5 pushState API and replaces regular links with AJAX requests. Aside from being a faster way to navigate around, it also lets me link up multiple articles in a series elegantly. When you go back to a previous screen, it literally presses the browser's back button, thus avoiding creating a long, useless history trail. You go back exactly the way you came, scrolling back to where you were, just like the real back/forward buttons do. For example, click over to my <a href='/blog/making-worlds-introduction/'>Making Worlds</a> series of posts. You can come back right away.
  </p>
  
  <p>
    I didn't use any libraries or router frameworks for this, simply because I wanted to have done it all myself at least once. As it now says on my <a href='/about'>About page</a>, quoting Feynman: <em>"What I cannot create, I do not understand"</em>. The only way to grok the intricacies of something like browser history state, which we all use every day, is to dive in and replicate it. Otherwise, you'll just take carefully choreographed behavior for granted and your mental model will be incomplete.
  </p>

  <p>
    To keep code size down, I compiled a custom build of Three.js with only the parts I need. I also used YUI compressor to minify the CSS and JS. However, I don't mean to obfuscate the code: the important bits will make their way onto Github soon enough.
  </p>
  
  <p><em>Update: The CSS 3D renderer and editor are now <a href='https://github.com/unconed/CSS3D.js'>available on GitHub</a>.</em></p>
  
  <h3>
    And Done?
  </h3>

  <p>
    I migrated over most of the content and did some house cleaning while I was at it. Most things should be back, but further fixes will be made. I also haven't implemented any commenting solution so far, but I'll be adding it back somehow as soon as I figure something out. In the mean time, there's <a href='https://plus.google.com/112457107445031703644/posts/HDJMgpDRAey'>a Google Plus thread</a>.
  </p>

  <p>
    The final result looks like something that would perhaps once unironically be labeled <strong>The Information Superhighway</strong> in a magazine from the 90s, though with less neon green. I like it.
  </p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[On TermKit]]></title>
    <link href="http://acko.net/blog/on-termkit/"/>
    <updated>2011-05-17T00:00:00-07:00</updated>
    <id>http://acko.net/blog/on-termkit</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>
  
<h1>On TermKit</h1>

<p>I've been administering Unix machines for many years now, and frankly, it kinda sucks. It makes me wonder, when sitting in front of a crisp, 2.3 million pixel display (i.e. a laptop) why I'm telling those pixels to draw me a computer terminal from the 80s.
</p>

<p>
<img alt='Regular bash terminal' src='/files/termkit/termkit-1.png' />
</p>

<p>
And yet, that's what us tech nerds do every day. The default Unix toolchain, marked in time by the 1970 epoch, operates in a world where data is either binary or text, and text is displayed in monospace chunks. The interaction is strictly limited to a linear flow of keystrokes, always directed at only one process. And that process is capable of communicating only in short little grunts of text, perhaps coalescing into a cutesy little ASCII art imitation of things that grown-ups call "dialogs", "progress bars", "tables" and "graphs".
</p>

<p>
The <a href='http://www.faqs.org/docs/artu/ch01s06.html'>Unix philosophy</a> talks about software as a toolset, about tiny programs that can be composed seamlessly. The principles are sound, and have indeed stood the test of time. But they were implemented in a time when computing resources were orders of magnitude smaller, and computer interaction was undiscovered country.
</p>

<p>
In the meantime, we've gotten a lot better at displaying information. We've also learned a lot of lessons through the web about data interchange, network transparency, API design, and more. We know better how small tweaks in an implementation can make a world of difference in usability.
</p>

<p>
And yet the world of Unix is rife with jargon, invisible processes, traps and legacy bits. Every new adept has to pass a constant trial by fire, of not destroying their system at every opportunity it gives them.
</p>

<p>
So while I agree that having a flexible toolbox is great, in my opinion, those pieces could be built a lot better. I don't want the computer equivalent of a screwdriver and a hammer, I want a tricorder and a laser saw. TermKit is my attempt at making these better tools and addresses a couple of major pain points.
</p>

<p>
I see TermKit as an extension of what Apple did with OS X, in particular the system tools like Disk Utility and Activity Monitor. Tech stuff doesn't have to look like it comes from the Matrix.
</p>

<h2>Rich Display</h2>

<p>
It's 2011, and monospace text just doesn't cut it anymore. In the default ANSI color palette, barely any of the possible color combinations are even readable. We can't display graphs, mathematical formulas, tables, etc. We can't use the principles of modern typography to lay out information in a readable, balanced way.
</p>

<p>
<img alt='TermKit example' src='/files/termkit/termkit-2.png' />
</p>

<p>
So instead, I opted for a front-end built in WebKit. Programs can display anything that a browser can, including HTML5 media. The output is built out of generic widgets (lists, tables, images, files, progress bars, etc.). The goal is to offer a rich enough set for the common data types of Unix, extensible with plug-ins. The back-end streams display output to the front-end, as a series of objects and commands.
</p>

<p>
I should stress that despite WebKit it is not my intent to make HTML the lingua franca of Unix. The front-end is merely implemented in it, as it makes it instantly accessible to anyone with HTML/CSS knowledge.
</p>

<h2>Pipes</h2>

<p>
  Unix pipes are anonymous binary streams, and each process comes with at least three: Standard In, Standard Out and Standard Error. This corresponds to the typical <em>Input</em> > <em>Processing</em> > <em>Output</em> model, with an additional error channel. However, in actual usage, there are two very different scenarios.
</p>

<p>
<img alt='' src='/files/termkit/termkit-3.png' />
</p>

<p>
One is the case of interactive usage: a human watches the program output (from Std Out) on a display, and types keystrokes to interact with it (into Std In). Another case is the data processing job: a program accepts a data stream in a particular format on Std In, and immediately outputs a related data stream on Std Out. These two can be mixed, in that a chain of piped commands can have a human at either end, though usually this implies non-interactive operation.
</p>

<p>
These two cases are shoehorned into the same pipes, but happen quite differently. Human input is spontaneous, sporadic and error prone. Data input is strictly formatted and continuous. Human output is ambiguous, spaced out and wordy. Data output is conservative and monolithic.
</p>

<p>
As a result, many Unix programs have to be careful about data. For example, many tools dynamically detect whether they are running in interactive mode, and adjust their output to be more human-friendly or computer-friendly. Other tools come with flags to request the input/output in specific formats.
</p>

<p>
This has lead to "somewhat parseable text" being the default interchange format of choice. This seems like an okay choice, until you start to factor in the biggest lesson learned on the web: there is no such thing as plain text. Text is messy. Text-based formats lie at the basis of every SQL injection, XSS exploit and encoding error. And it's in text-parsing code where you'll likely find buffer overflows.
</p>

<p>
What this means in practice is that in every context, there are some forbidden characters, either by convention or by spec. For example, no Unicode or spaces in filenames. In theory, it's perfectly fine, but in practice, there's at least one shell script on your system that would blow up if you tried. Despite the promise of text as the universal interchange format, we've been forced to impose tons of vague limits.
</p>

<p>
So how do we fix this? By separating the "data" part from the "human" part. Then we can use messy text for humans, and pure data for the machines. Enter "Data In/Out", "View In/Out".
</p>

<p>
<img alt='TermKit data flow diagram' src='/files/termkit/termkit-4.png' />
</p>

<p>
The data pipes correspond to the classical Std pipes, with one difference: the stream is prefixed with MIME-like headers (Content-Type, Content-Length, etc). Of these, only the 'Content-Type' is required. It allows programs to know what kind of input they're receiving, and handle it graciously without sniffing. Aside from that, the data on the pipe is a raw binary stream.
</p>

<p>
The view pipes carry the display output and interaction to the front-end. Widgets and UI commands are streamed back and forth as JSON messages over the view pipes.
</p>

<p>
The real magic happens when these two are combined. The last dangling Std Out pipe of any command chain needs to go into the Terminal, to be displayed as output. But the data coming out of Data Out is not necessarily human-friendly.
</p>

<p>
Thanks to the MIME-types, we can solve this universally. TermKit contains a library of output formatters which each handle a certain type of content (text, code, images, ...). It selects the right formatter based on the Content-Type, which then generates a stream of view updates. These go over the View Out pipe and are added to the command output.
</p>

<p>
<img alt='Cat&apos;ing an image' src='/files/termkit/termkit-7.png' />
</p>

<p>
As a result, you can <code>cat</code> a PNG and have it just work. TermKit <code>cat</code> doesn't know how to process PNGs or HTML—it only guesses the MIME type based on the filename and pipes the raw data to the next process. Then the formatter sends the image to the front-end. If you <code>cat</code> a source code file, it gets printed with line numbers and syntax highlighting.
</p>

<p>
So where does "somewhat parseable text" fit in? It turns out to be mostly unnecessary. Commands like <code>ls</code> output structured data by nature, i.e. a listing of files from one or more locations. It makes sense to pipe around this data in machine-form. Output flags like <code>ls&nbsp;-l</code> become mere hints for the final display, which can toggle on-the-fly between compact and full listing.
</p>

<p>
In TermKit's case, JSON is the interchange format of choice. The <code>Content-Type</code> for file listings is <code>application/json;&nbsp;schema=termkit.files</code>. The <code>schema</code> acts as a marker to select the right output plug-in. In this case, we want the file formatter rather than the generic raw JSON formatter.
</p>

<p>
<img alt='Formatting data in TermKit' src='/files/termkit/termkit-8.png' />
</p>

<p>
Isn't JSON data harder to work with than lines of text? Only in some ways, but parsing JSON is trivial these days in any language. Because of this, I built TermKit <code>grep</code> so it supports grepping JSON data recursively. This happens transparently when the input is <code>application/json</code> instead of <code>text/plain</code>. As a result <code>ls&nbsp;|&nbsp;grep</code> works as you'd expect it to.
</p>

<p>
To slot in traditional Unix utilities in this model, we can pipe their data as <code>application/octet-stream</code> to start with, and enhance specific applications with type hints and wrapper scripts.
</p>

<p>
<img alt='' src='/files/termkit/termkit-6.png' />
</p>

<p>
Finally, having type annotations on pipes opens up another opportunity: it allows us to pipe in HTTP GET / POST requests almost transparently. Getting a URL becomes no different from catting a file, and both can have fancy progress bars, even when inside a pipe chain like <code>get&nbsp;|&nbsp;grep</code>.
</p>

<h2>Synchronous interaction</h2>

<p>
All interaction in a traditional terminal is synchronous. Only one process is interactive at a time, and each keystroke must be processed by the remote shell before it is displayed. This leads to an obvious daily frustration: SSH keystroke lag.
</p>

<p>
To fix this, TermKit is built out of a separate front-end and back-end. The front-end can run locally, controlling a back-end on a remote machine. The connection can be tunneled over SSH for security.
</p>

<p class='tc'>
<a href='https://github.com/unconed/TermKit/raw/master/Mockups/Architecture.pdf'><img alt='TermKit architecture' src='/files/termkit/termkit-9.png' />Architecture diagram</a> (TK stands for TermKit)
</p>

<p>
Additionally, all display updates and queries are asynchronous. The WebKit-based HTML display is split up into component views, and the view pipes of each subprocess are routed to their own view. Vice-versa, any interactive widgets inside a view can send callback messages back to their origin process, as long as it's still running.
</p>

<p>
This also allows background processes to work without overflowing the command prompt.
</p>

<h2>String-based command line</h2>

<p>
A lot of my frustration comes from bash's arcane syntax. It has a particularly nasty variant of C-style escaping. Just go ahead and <em>try</em> to match a regular expression involving both types of quotes.
</p>

<p>
But at its core, a bash command is a series of tokens. Some tokens are single words, some are flags, some are quoted strings, some are modifiers (like | and >). It makes sense for the input to reflect this.
</p>

<p>
<img alt='TermKit command-line' src='/files/termkit/termkit-5.png' />
</p>

<p>
TermKit's input revolves around tokenfield.js, a new snappy widget with plenty of tricks. It can do auto-quoting, inline autocomplete, icon badges, and more. It avoids the escaping issue altogether, by always processing the command as tokens rather than text. Keys that trigger special behaviors (like a quote) can be pressed again to undo the behavior and just type one character.
</p>

<p>
The behaviors are encoded in a series of objects and regexp-based triggers, which transform and split tokens as they are typed. That means it's extensible too.
</p>

<h2>Usability</h2>

<p>
At the end of the day, Unix just has bad usability. It tricks us with unnecessary abbreviations, inconsistent arguments (-r vs -R) and nitpicky syntax. Additionally, Unix has a habit of giving you raw data, but not telling you useful facts, e.g. 'r-xr-xr-x' instead of "You can't touch this" (<em>ba-dum tsshh</em>).
</p>

<p>
One of the Unix principles is nobly called "Least Surprise", but in practice, from having observed new Unix users, I think it often becomes "Maximum Confusion". We should be more pro-active in nudging our users in the right direction, and our tools should be designed for maximum discoverability.
</p>

<p>
For example, I want to see the relevant part of a man page in a tooltip when I'm typing argument switches. I'd love for dangerous flags to be highlighted in red. I'd love to see regexp hints of possible patterns inline.
</p>

<p>
There's tons to be done here, but we can't do anything without modern UI abilities.
</p>

<h2>Focus and Status</h2>

<p>
With a project like TermKit, it's easy to look at the shiny exterior and think "meh", or that I'm just doing things differently for difference's sake. But to me, the real action is under the hood. With a couple of tweaks and some uncompromising spring cleaning, we can get Unix to do a lot more for us.
</p>

<p>
The current version of TermKit is just a rough alpha, and what it does is in many ways just parlour tricks compared to what it could be doing in a few months. The architecture definitely supports it.
</p>

<p>
I've worked on TermKit off and on for about a year now, so I'd love to hear feedback and ideas. Please <a href='http://github.com/unconed/TermKit'>go check out the code</a>.
</p>

<p>
TermKit owes its existence to Node.js, Socket.IO, jQuery and WebKit. Thanks to everyone who has contributed to those projects.
</p>

<p>
<em>Edit, a couple of quick points:</em>
</p>

<ul>
<li>A Linux port will definitely happen, since it's built out of WebKit and Node.js. Whoever does it first gets a cookie.</li>
<li>TermKit is not tied to JSON except in its own internal communication channels. TermKit Pipes can be in any format, and old-school plain-text still works. JSON just happens to be very handy and very lightweight.</li>
<li>The current output is just a proof of concept and lacks many planned usability enhancements. There are mockups on github.</li>
<li>If you're going to tell me I'm stupid, please read all the other 100 comments doing so first, so we can keep this short for everyone else.</li>
</ul>

<p>
<em>Edit, random fun:</em>
</p>

<p>Someone asked for AVS instead of TermKit in the comments... best I could do was JS1K with a PDF surprise:</p>

<iframe allowfullscreen='allowfullscreen' frameborder='0' height='349' src='http://www.youtube.com/embed/dAeZTgRuWsU' style='margin: 0 auto;' width='425' />

<iframe allowfullscreen='allowfullscreen' frameborder='0' height='349' src='http://www.youtube.com/embed/_6Z5dnlfcls' width='560' />

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[My JS1K Demo - The Making Of]]></title>
    <link href="http://acko.net/blog/js1k-demo-the-making-of/"/>
    <updated>2010-08-06T00:00:00-07:00</updated>
    <id>http://acko.net/blog/js1k-demo-the-making-of</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>
  
<h1>My JS1K Demo - The Making Of</h1>

<p>If you haven't seen it yet, check out the <a href='http://js1k.com'>JS1K demo contest</a>. The goal is to do something neat in 1 kilobyte of JavaScript code.</p>
<p>I couldn't resist making one myself, so I pulled out my bag of tricks from my <a href='/design/avs'>Winamp music visualization</a> days and started coding. I'm really happy with how it turned out. And no, it won't work in Internet Explorer 8 or less.</p>

<p><em>Edit: OH SNAP! I just rewrote the demo to include volumetric light beams and still fit in 1K:</em></p>

</div></div><div class='g6'><div class='pad'>

<h3>Original Version</h3>
<p><iframe frameborder='0' height='345' id='1kjs' src='about:blank' style='background:#000;border:0' width='510' /></p>
<p><button id='1kjs-stop' onclick='$(&apos;#1kjs&apos;).attr(&apos;src&apos;,&apos;about:blank&apos;);$(this).hide();$(&apos;#1kjs-start&apos;).show();' style='display: none'>Stop Demo</button><button id='1kjs-start' onclick='$(&apos;#1kjs&apos;).attr(&apos;src&apos;,&apos;/files/making-of-js1k/1022b.html&apos;);$(this).hide();$(&apos;#1kjs-stop&apos;).show();'>Start Demo</button><button onclick='$(&apos;#1kjs&apos;).attr(&apos;src&apos;,&apos;/files/making-of-js1k/1022s.html&apos;);$(&apos;#1kjs-start&apos;).show();$(&apos;#1kjs-stop&apos;).hide();'>View Source</button></p>

</div></div><div class='g6'><div class='pad'>

<h3>Improved Version</h3>

<p><iframe frameborder='0' height='345' id='1kjs2' src='about:blank' style='background:#000;border:0' width='510' /></p>
<p><button id='1kjs2-stop' onclick='$(&apos;#1kjs2&apos;).attr(&apos;src&apos;,&apos;about:blank&apos;);$(this).hide();$(&apos;#1kjs2-start&apos;).show();' style='display: none'>Stop Demo</button><button id='1kjs2-start' onclick='$(&apos;#1kjs2&apos;).attr(&apos;src&apos;,&apos;/files/making-of-js1k/1024b.html&apos;);$(this).hide();$(&apos;#1kjs2-stop&apos;).show();'>Start Demo</button><button onclick='$(&apos;#1kjs2&apos;).attr(&apos;src&apos;,&apos;/files/making-of-js1k/1024s.html&apos;);$(&apos;#1kjs2-start&apos;).show();$(&apos;#1kjs2-stop&apos;).hide();'>View Source</button></p>

</div></div><div class='g8 i2'><div class='pad'>

<p>Now, whenever size is an issue, the best way to make a small program is to generate all data on the fly, i.e. procedurally. This saves valuable storage space. While this might seem like a black art, often it just comes down to clever use of (high school) math. And as is often the case, the best tricks are also the simplest, as they use the least amount of code.</p>
<p>To illustrate this, I'm going to break down my demo and show you all the major pieces and shortcuts used. Unlike the actual 1K demo, the code snippets here will feature legible spacing and descriptive variable names.</p>

<h3>Initialization</h3>

<p>JS1K's rules give you a Canvas tag to work with, so the first piece of code initializes it and makes it fill the window.</p>
<p>From then on, it just renders frames of the demo. There are four major parts to this:</p>
<ul>
<li>Animating the wires</li>
<li>Rotating and projecting the wires into the camera view</li>
<li>Coloring the wires</li>
<li>Animating the camera</li>
</ul>
<p>All of this is done 30 times per second, using a normal <code>setInterval</code> timer:</p>
<p class='codeblock'><code>setInterval(function () { ... }, 33);</code></p>

<h3>Drawing Wires</h3>

<p>The most obvious trick is that everything in the demo is drawn using only a single primitive: a line segment of varying color and stroke width. This allows the whole drawing process to be streamlined into two tight, nested loops. Each inner iteration draws a new line segment from where the previous one ended, while the outer iteration loops over the different wires.</p>

<p><img alt='drawing process for demo' src='/files/making-of-js1k/drawing-process.png' title='Drawing each wire in sequence.' /></p>

<p>The lines are blended additively, using the built-in 'lighten' mode, which means they can be drawn in any order. This avoids having to manually sort them back-to-front.</p>
<p>To simplify the perspective transformations, I use a coordinate system that places the point (0, 0) in the center of the canvas and ranges from -1 to 1 in both coordinates. This is a compact and convenient way of dealing with varying window sizes, without using up a lot of code:</p>
<p class='codeblock'><code>with (graphics) {<br />&nbsp; ratio = width / height;<br />&nbsp; globalCompositeOperation = &#039;lighter&#039;;<br />&nbsp; scale(width / 2 / ratio, height / 2);<br />&nbsp; translate(ratio, 1);<br />&nbsp; lineWidthFactor = 45 / height;<br />&nbsp; ...</code></p>
<p>I add a correction <code>ratio</code> for non-square windows and calculate a reference line width <code>lineWidthFactor</code> for later. Here, I'm using the <code>with</code> construct to save valuable code space, though its use is generally discouraged. </p>
<p>Then there's the two nested <code>for</code> loops: one iterating over the wires, and one iterating over the individual points along each wire. In pseudo-code they look like:</p>
<p class='codeblock'><code>For (12 wires =&gt; wireIndex) {<br />&nbsp; Begin new wire<br />&nbsp; For (45 points along each wire =&gt; pointIndex) {<br />&nbsp;&nbsp;&nbsp; Calculate path of point on a sphere: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Extrude outwards in swooshes: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Translate and Rotate into camera view: (x,y,z)<br />&nbsp;&nbsp;&nbsp; Project to 2D: (x,y)<br />&nbsp;&nbsp;&nbsp; Calculate color, width and luminance of this line: (r,g,b) (w,l)<br />&nbsp;&nbsp;&nbsp; If (this point is in front of the camera) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; If (the last point was visible) {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Draw line segment from last point to (x,y)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; else {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mark this point as invisible<br />&nbsp;&nbsp;&nbsp; }<br />&nbsp;&nbsp;&nbsp; Mark beginning of new line segment at (x,y)<br />&nbsp; }<br />}</code></p>

<h3>Mathbending</h3>

<p>To generate the wires, I start with a formula which generates a sinuous path on a sphere, using latitude/longitude. This controls the tip of each wire and looks like:</p>
<p class='codeblock'><code>offset = time - pointIndex * 0.03 - wireIndex * 3;<br />longitude = cos(offset + sin(offset * 0.31)) * 2<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + sin(offset * 0.83) * 3 + offset * 0.02;<br />latitude = sin(offset * 0.7) - cos(3 + offset * 0.23) * 3;</code></p>
<p>This is classic procedural coding at its best: take a time-based <code>offset</code> and plug it into a random mish-mash of easily available functions like cosine and sine. Tweak it until it 'does the right thing'. It's a very cheap way of creating interesting, organic looking patterns.</p>
<p>This is more art than science, and mostly just takes practice. Any time spent with a graphical calculator will definitely pay off, as you will know better which mathematical ingredients result in which shapes or patterns. Also, there are a couple of things you can do to maximize the appeal of these formulas.</p>
<p>First, always include some non-linear combinations of operators, e.g. nesting the sin() inside the cos() call. Combined, they are more interesting than when one is merely overlaid on the other. In this case, it turns regular oscillations into time-varying frequencies.</p>
<p>Second, always scale different wave periods using prime numbers. Because primes have no factors in common, this ensures that it takes a very long time before there is a perfect repetition of all the individual periods. Mathematically, the <a href='http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+31+83+70+23%29%29+%2F+100'>least common multiple of the chosen periods</a> is huge (414253 units ~ 4.8 hours). Plotting the longitude/latitude for <code>offset = 0..600</code> you get:</p>
<p><img alt='a pseudo-random set of oscillations' src='/files/making-of-js1k/js1k-2.jpg' title='Looks pretty random' /></p>
<p>The graph looks like a random tangled curve, with no apparent structure, which makes for motions that never seem to repeat. If however, you reduce each constant to only a single significant digit (e.g. 0.31 -> 0.3, 0.83 -> 0.8), then suddenly repetition becomes apparent:</p>
<p><img alt='a not so pseudo-random set of oscillations' src='/files/making-of-js1k/js1k-3.jpg' title='Not so random' /></p>
<p>This is because the least common multiple has <a href='http://www.wolframalpha.com/input/?i=%28least+common+multiple+of+%28100+30+80+70+20%29%29+%2F+100'>dropped to 84 units ~ 3.5 seconds</a>. Note that both formulas have the same code complexity, but radically different results. This is why all procedural coding involves some degree of creative fine tuning.</p>
<h3>Extrusion</h3>

<p>Given the formula for the tip of each wire, I can generate the rest of the wire by sweeping its tail behind it, delayed in time. This is why <code>pointIndex</code> appears as a negative in the formula for <code>offset</code> above. At the same time, I move the points outwards to create long tails.</p>
<p>I also need to convert from lat/long to regular 3D XYZ, which is done using the <a href='http://en.wikipedia.org/wiki/Spherical_coordinate_system'>spherical coordinate transform</a>:</p>
<p><img alt='spherical coordinates' src='/files/making-of-js1k/js1k-4.jpg' /><center>Source: <a href='http://en.wikipedia.org/wiki/File:Coord_system_SE_0.svg'>Wikipedia</a></center></p>
<p class='codeblock'><code>distance = f.sqrt(pointIndex+.2);<br />x = cos(longitude) * cos(latitude) * distance;<br />y = sin(longitude) * cos(latitude) * distance;<br />z = sin(latitude) * distance;</code></p>
<p>You might notice that rather than making <code>distance</code> a straight up function of the length <code>pointIndex</code> along the wire, I applied a square root. This is another one of those procedural tricks that seems arbitrary, but actually serves an important visual purpose. This is what the square root looks like (solid curve):</p>
<p><img alt='square root' src='/files/making-of-js1k/js1k-5.jpg' /></p>
<p>The dotted curve is the square root's derivative, i.e. it indicates the slope of the solid curve. Because the slope goes down with increasing distance, this trick has the effect of slowing down the outward motion of the wires the further they get. In practice, this means the wires are more tense in the middle, and more slack on the outside. It adds just enough faux-physics to make the effect visually appealing.</p>
<h3>Rotation and Projection</h3>

<p>Once I have absolute 3D coordinates for a point on a wire, I have to render it from the camera's point of view. This is done by moving the origin to the camera's position (X,Y,Z), and applying two rotations: one around the vertical (yaw) and one around the horizontal (pitch). It's like spinning on a wheely chair, while tilting your head up/down.</p>
<p class='codeblock'><code>x -= X; y -= Y; z -= Z;<br /><br />x2 = x * cos(yaw) + z * sin(yaw);<br />y2 = y;<br />z2 = z * cos(yaw) - x * sin(yaw);<br /><br />x3 = x2;<br />y3 = y2 * cos(pitch) + z2 * sin(pitch);<br />z3 = z2 * cos(pitch) - y2 * sin(pitch);</code></p>
<p>The camera-relative coordinates are projected in perspective by dividing by Z — the further away an object, the smaller it is. Lines with negative Z are behind the camera and shouldn't be drawn. The width of the line is also scaled proportional to distance, and the first line segment of each wire is drawn thicker, so it looks like a plug of some kind:</p>
<p class='codeblock'><code>plug = !pointIndex;<br />lineWidth = lineWidthFactor * (2 + plug) / z3;<br />x = x3 / z3;<br />y = y3 / z3;<br /><br />lineTo(x, y);<br />if (z3 &gt; 0.1) {<br />&nbsp; if (lastPointVisible) {<br />&nbsp;&nbsp;&nbsp; stroke();<br />&nbsp; }<br />&nbsp; else {<br />&nbsp;&nbsp;&nbsp; lastPointVisible = true;<br />&nbsp; }<br />}<br />else {<br />&nbsp; lastPointVisible = false;<br />}<br />beginPath();<br />moveTo(x, y);</code></p>
<p>There's a subtle optimization hiding in plain sight here. Because I'm drawing lines, I need both the previous and the current point's coordinates at each step. To avoid using variables for these, I place the <code>moveTo</code> command at the end of the loop rather than at the beginning. As a result, the previous coordinates are remembered transparently into the next iteration, and all I need to do is make sure the first call to <code>stroke()</code> doesn't happen.</p>

<h3>Coloring</h3>
<p>Each line segment also needs an appropriate coloring. Again, I used some trial and error to find a simple formula that works well. It uses a sine wave to rotate overall luminance in and out of the (Red, Green, Blue) channels in a deliberately skewed fashion, and shifts the R component slowly over time. This results in a nice varied palette that isn't overly saturated.</p>
<p class='codeblock'><code>pulse = max(0, sin(time * 6 - pointIndex / 8) - 0.95) * 70;<br />luminance = round(45 - pointIndex) * (1 + plug + pulse);<br />strokeStyle=&#039;rgb(&#039; + <br />&nbsp; round(luminance * (sin(plug + wireIndex + time * 0.15) + 1)) + &#039;,&#039; + <br />&nbsp; round(luminance * (plug + sin(wireIndex - 1) + 1)) + &#039;,&#039; +<br />&nbsp; round(luminance * (plug + sin(wireIndex - 1.3) + 1)) +<br />&nbsp; &#039;)&#039;;</code></p>
<p>Here, <code>pulse</code> causes bright pulses to run across the wires. I start with a regular sine wave over the length of the wire, but truncate off everything but the last 5% of each crest to turn it into a sparse pulse train:</p>
<p><img alt='sine pulse train' src='/files/making-of-js1k/js1k-6.jpg' /></p>
<h3>Camera Motion</h3>

<p>With the main visual in place, almost all my code budget is gone, leaving very little room for the camera. I need a simple way to create consistent motion of the camera's X, Y and Z coordinates. So, I use a neat low-tech trick: repeated interpolation. It looks like this:</p>
<p class='codeblock'><code>sample += (target - sample) * fraction</code></p>
<p><code>target</code> is set to a random value. Then, every frame, <code>sample</code> is moved a certain fraction towards it (e.g. <code>0.1</code>). This turns <code>sample</code> into a smoothed version of <code>target</code>. Technically, this is a <em>one-pole low-pass filter</em>.</p>
<p>This works even better when you apply it twice in a row, with an intermediate value being interpolated as well:</p>
<p class='codeblock'><code>intermediate += (target - intermediate) * fraction<br />sample += (intermediate - sample) * fraction</code></p>
<p>A sample run with <code>target</code> being changed at random might look like this:</p>
<p><img alt='sine pulse train' src='/files/making-of-js1k/js1k-7.jpg' /></p>
<p>You can see that with each interpolation pass, more discontinuities get filtered out. First, jumps are turned into kinks. Then, those are smoothed out into nice bumps.</p>
<p>In my demo, this principle is applied separately to the camera's X, Y and Z positions. Every ~2.5 seconds a new target position is chosen:</p>
<p class='codeblock'><code>if (frames++ &gt; 70) {<br />&nbsp; Xt = random() * 18 - 9;<br />&nbsp; Yt = random() * 18 - 9;<br />&nbsp; Zt = random() * 18 - 9;<br />&nbsp; frames = 0;<br />}<br /><br />function interpolate(a,b) {<br />&nbsp; return a + (b-a) * 0.04;<br />}<br /><br />Xi = interpolate(Xi, Xt);<br />Yi = interpolate(Yi, Yt);<br />Zi = interpolate(Zi, Zt);<br /><br />X&nbsp; = interpolate(X,&nbsp; Xi);<br />Y&nbsp; = interpolate(Y,&nbsp; Yi);<br />Z&nbsp; = interpolate(Z,&nbsp; Zi);</code></p>
<p>The resulting path is completely smooth and feels quite dynamic.</p>
<h3>Camera Rotation</h3>

<p>The final piece is orienting the camera properly. The simplest solution would be to point the camera straight at the center of the object, by calculating the appropriate <code>pitch</code> and <code>yaw</code> directly off the camera's position (X,Y,Z):</p>
<p class='codeblock'><code>yaw&nbsp;&nbsp; = atan2(Z, -X);<br />pitch = atan2(Y, sqrt(X * X + Z * Z));</code></p>
<p>However, this gives the demo a very static, artificial appearance. What's better is making the camera point in the right direction, but with just enough freedom to pan around a bit.</p>
<p>Unfortunately, the 1K limit is unforgiving, and I don't have any space to waste on more 'magic' formulas or interpolations. So instead, I cheat by replacing the formulas above with:</p>
<p class='codeblock'><code>yaw&nbsp;&nbsp; = atan2(Z, -X * 2);<br />pitch = atan2(Y * 2, sqrt(X * X + Z * Z));</code></p>
<p>By multiplying X and Y by 2 strategically, the formula is 'wrong', but the error is limited to about 45 degrees and varies smoothly. Essentially, I gave the camera a lazy eye, and got the perfect dynamic motion with only 4 bytes extra!</p>
<h3>Addendum</h3>

<p>After seeing the other demos in the contest, I wasn't so sure about my entry, so I started working on a version 2. The main difference is the addition of glowy light beams around the object.</p>
<p>As you might suspect, I'm cheating massively here: rather than do physically correct light scattering calculations, I'm just using a 2D effect. Thankfully it comes out looking great.</p>
<p>Essentially, I take the rendered image, and process it in a second Canvas that is hidden. This new image is then layered on the original.</p>
<p>I take the image and repeatedly blend it with a zoomed out copy of itself. With every pass the number of copies doubles, and the zoom factor is squared every time. After 3 passes, the image has been smeared out into an 8 = 2<sup>3</sup> 'tap' radial blur. I lock the zooming to the center of the 3D object. This makes the beams look like they're part of the 3D world rather than drawn on later.</p>
<p>For additional speed, the beam image is processed at half the resolution. As a side effect, the scaling down acts like a slight blur filter for the beams.</p>
<p>Unfortunately, this effect was not very compact, as it required a lot of drawing mode changes and context switches. I had no room for it  in the source code.</p>
<p>So, I had to squeeze out some more room in the original. First, I simplified the various formulas to the bare minimum required for interesting visuals. I replaced the camera code with a much simpler one, and started aggressively shaving off every single byte I could find. Then I got creative, and ended up recreating the secondary canvas every frame just to avoid switching back its state to the default.</p>
<p>Eventually, after a lot of bit twiddling, a version came out that was 1024 bytes long. I had to do a lot of unholy things to get it to fit, but I think the end result is worth it ;). </p>
<h3>Closing Thoughts</h3>

<p>I've long been a fan of the demo scene, and fondly remember <a href='http://www.youtube.com/watch?v=dQveVMQDJlg'>Second Reality</a> in 1993 as my introduction to the genre. Since then, I've always looked at math as a tool to be mastered and wielded rather than subject matter to be absorbed.</p>
<p>With this blog post, I hope to inspire you to take the plunge and see where some simple formulas can take you.</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[JavaScript audio synthesis with HTML 5]]></title>
    <link href="http://acko.net/blog/javascript-audio-synthesis-with-html-5/"/>
    <updated>2009-08-12T00:00:00-07:00</updated>
    <id>http://acko.net/blog/javascript-audio-synthesis-with-html-5</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>JavaScript audio synthesis with HTML 5</h1>
  
<aside class='r'><a href='http://acko.net/files/audiosynth/index.html'><img alt='Audio wave' src='/files/audiosynth/audio.png' /></a></aside>

<p>
  HTML5 gives us a couple new toys to play with, such as &lt;AUDIO&gt; and &lt;VIDEO&gt; tags. On the visual side, we've already seen <a href='https://developer.mozilla.org/samples/video/chroma-key/index.xhtml'>live green-screening</a> with Canvas and JS, and in terms of audio there's been several JS <a href='http://www.randomthink.net/labs/html5drums/'>drum machines</a> already. But the question I was interested in was: can you use JavaScript to stream live data into these media tags?
</p>

<p>
Enter the <a href='http://acko.net/files/audiosynth/index.html'>JavaScript audio synth</a>. It generates a handful of samples using very basic time-domain synthesis, wraps them up in a WAVE file header and embeds them in &lt;AUDIO&gt; tags using base64-encoded data URIs. Each sample is then triggered using timers to play the drum pattern. It's quite simple to do and runs fast enough in HTML5 capable browsers to be unnoticeable. Yes, it sounds tinny, but that's just because I'm too lazy to design proper filters for toys like this.
<!--break-->
Unfortunately, while the synthesis is fast enough to run real-time, you can't actually use it for a full live audio stream, as there is no way to queue up chunks of synthesized audio for seamless playback. I tried triggering multiple &lt;AUDIO&gt; tags in parallel to address this, but that didn't work either.
</p>

<p>
My final attempt was to generate tons of periodic audio loops only a couple of ms long, and to play them back with looping turned on while altering each tag's volume in real time, hence doing a sort of additive wavetable synthesis. Unfortunately, looping is not a fully supported feature, and the only browser I found that does it (Safari) doesn't loop seamlessly at all.
</p>

<p>
All in all, my first brush with the &lt;AUDIO&gt; tag was a major disappointment. The &lt;VIDEO&gt; tag's high-level approach leads to similar limitations, but they are offset by the flexibility and power of the &lt;CANVAS&gt; tag. Unfortunately, there is no 'audio canvas' to solve similar problems with audio.
</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Projective Texturing with Canvas]]></title>
    <link href="http://acko.net/blog/projective-texturing-with-canvas/"/>
    <updated>2008-11-11T00:00:00-08:00</updated>
    <id>http://acko.net/blog/projective-texturing-with-canvas</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>Projective Texturing with Canvas</h1>

<p><em>Update: People keep asking me to use this code, apparently unaware this has been made obsolete by CSS 3D. So it's gone now.</em></p>
  
<p>The <a href='http://en.wikipedia.org/wiki/Canvas_(HTML_element)'>Canvas</a> tag's popularity is slowly increasing around the web. I've seen big sites use it for <a href='http://code.google.com/p/jquery-rotate/'>image rotation</a>, <a href='http://code.google.com/p/flot/'>graph plotting</a>, <a href='http://ajaxian.com/archives/canvas-reflectionjs'>reflection effects</a> and much more.
</p>

<p>
However, Canvas is still limited to 2D: its drawing operations can only do typical vector graphics with so-called affine transformations, i.e. scaling, rotating, skewing and translation. Though there have been <a href='https://wiki.mozilla.org/Canvas:3D'>some efforts</a> to try and add a 3D context to Canvas, these efforts are still experimental and only available for a minority of browsers through plug-ins.
</p>

<p>
So when my colleague <a href='http://rosshj.com/'>Ross</a> asked me if we could build a <a href='http://en.wikipedia.org/wiki/Cover_Flow'>Cover Flow</a>-like widget with JavaScript, my initial reaction was no... but really, that's just a cop out. All you need are textured rectangles drawn in a convincing perspective: a much simpler scenario than full blown 3D.
</p>

<p>
<a href='/files/projective-canvas/index.html'><img alt='' src='/files/projective-canvas/projective-transform.png' /></a>
</p>

<p>
<!--break-->
Perspective views are described by so-called <em>projective transforms</em>, which Canvas2D does not support. However, it does support arbitrary clipping masks as well as affine transforms of both entire and partial images. These can be used to do a fake projective transform: you cut up your textured surface into a bunch of smaller patches (which are almost-affine) and render each with a normal affine transform. Of course you need to place the patches just right, so as to cover any possible gaps. As long as the divisions are small enough, this looks convincingly 3D.
</p>

<p>
So some hacking later, I have a <a href='/files/projective-canvas/index.html'>working projective transform renderer</a> in JavaScript. The algorithm uses adaptive subdivision to maintain quality and can be tuned for detail or performance. At its core it's really just a lot of linear algebra, though I did have to add a bunch of tweaks to make it look seamless due to annoying aliasing effects.
</p>

<p>
Unfortunately Safari seems to be the only browser that can render it at an acceptable speed, so this technique is just a curiosity for now. The current code was mostly written for readability rather than performance though, so it's possible it could be optimized to a more useful state. <strike>Feel free to <a href='/files/projective-canvas/projective.js'>browse the code</a></strike>.
</p>

<p>
A real 3D Canvas in the browser would obviously rock, but you can still do some nifty things if you know the right tricks...</p></div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Abusing jQuery.animate for fun and profit (and bacon)]]></title>
    <link href="http://acko.net/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon/"/>
    <updated>2008-09-22T00:00:00-07:00</updated>
    <id>http://acko.net/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>Abusing jQuery.animate for fun and profit (and bacon)</h1><p>The days of static UIs that only have jarring transitions between pages are pretty much over. With frameworks like <a href='http://developer.apple.com/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html'>CoreAnimation</a> or <a href='http://jquery.com'>jQuery</a>, it's easy to add useful animations to applications and webpages. In the case of jQuery, you can easily animate any CSS property, and you get free work-arounds for browser bugs to boot. You can run multiple animations (of arbitrary duration) at the same time, queue animations and even animate complex properties like colors or clipping rectangles.
</p>


<aside class='r m1'><img alt='Strip of bacon' src='/files/bacon/bacon1.png' style='width: 100px' /></aside>

<p>But what if you want to go beyond mere CSS? You might have a custom widget that is drawn using <code>&lt;canvas&gt;</code>, whose contents are controlled by internal variables; maybe you're using 3D transformations to scale and position images on a page, and simple 2D tweening just doesn't cut it.
</p>

<p>
In that case, it would seem you are out of luck: jQuery's .animate() method can only be applied to a collection of DOM elements, and relies heavily on the browser's own semantics for processing CSS values and their units. However thanks to JavaScript's flexibility and jQuery's architecture, we can work around this, and re-use jQuery's excellent animation core for our own nefarious purposes.
</p>

<h2>Hackity hack hack</h2>

<p>
First, we need an object to store all the variables we wish to animate. We use an anonymous <code>&lt;div&gt;</code> outside of the main document, so that jQuery's DOM calls still work on it. We simply add our own properties to it:
</p>

<p class='codeblock'>
<code>var&nbsp;vars&nbsp;=&nbsp;$.extend($('&lt;div&gt;')[0],&nbsp;{<br />
&nbsp;&nbsp;foo:&nbsp;1,<br />
&nbsp;&nbsp;bar:&nbsp;2,<br />
<br />
&nbsp;&nbsp;customAnimate:&nbsp;true,<br />
&nbsp;&nbsp;updated:&nbsp;true<br />
});<br />
</code>
</p>

<p>
In this case, our properties are <code>foo</code> and <code>bar</code>. We also set <code>customAnimate</code> and <code>updated</code> to identify this object (see below).
</p>

<p>
Next we need to override jQuery's default step function, which gets called for every step of an animation, and applies new values to an element's CSS properties.
</p>

<p class='codeblock'>
<code>&nbsp;&nbsp;&nbsp;//&nbsp;jQuery.fx.step._default<br />
&nbsp;&nbsp;&nbsp;&nbsp;_default:&nbsp;function(fx)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fx.elem.style[fx.prop]&nbsp;=&nbsp;fx.now&nbsp;+&nbsp;fx.unit;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
</code>
</p>

<p>
We can replace it using the following snippet:
</p>

<p class='codeblock'>
<code>var&nbsp;$_fx_step_default&nbsp;=&nbsp;$.fx.step._default;<br />
$.fx.step._default&nbsp;=&nbsp;function&nbsp;(fx)&nbsp;{<br />
&nbsp;&nbsp;if&nbsp;(!fx.elem.customAnimate)&nbsp;return&nbsp;$_fx_step_default(fx);<br />
&nbsp;&nbsp;fx.elem[fx.prop]&nbsp;=&nbsp;fx.now;<br />
&nbsp;&nbsp;fx.elem.updated&nbsp;=&nbsp;true;<br />
};<br />
</code>
</p>

<p>
With the new step function, jQuery will check for the presence of a <code>customAnimate</code> property on any element it is animating. If present, it will assign the (unit-less) value to <code>element.property</code> rather than <code>element.style.property</code> and mark the element by setting <code>element.updated</code> to true.
</p>

<p>
Now we're ready to animate, using the normal <code>$.animate</code> syntax:
</p>

<p class='codeblock'>
<code>$(vars).animate({&nbsp;foo:&nbsp;5,&nbsp;bar:&nbsp;10&nbsp;},&nbsp;{&nbsp;duration:&nbsp;1000&nbsp;});<br />
</code>
</p>

<p>
The values <code>vars.foo</code> and <code>vars.bar</code> will now smoothly change over time. You can use any of <a href='http://docs.jquery.com/Effects/animate'>jQuery's animation abilities</a> as usual.
</p>

<p>
Now what about that <code>updated</code> variable? Well to actually use these animated values, you will need some kind of timer or step callback to read them back and draw them on the page. If you're using <code>&lt;canvas&gt;</code>, you need to redraw your entire widget for every change, but you don't want to be wasting CPU time by constantly refreshing it. Furthermore, if you're running multiple animations at the same time, you'll want to aggregate all your property changes into a single redraw per frame. This is easy with the <code>updated</code> property and a simple timer:
</p>

<p class='codeblock'>
<code>setInterval(function&nbsp;()&nbsp;{<br />
&nbsp;&nbsp;if&nbsp;(!vars.updated)&nbsp;return;<br />
&nbsp;&nbsp;vars.updated&nbsp;=&nbsp;false;<br />
&nbsp;&nbsp;<br />
&nbsp;&nbsp;drawWidget();<br />
},&nbsp;30);<br />
</code>
</p>

<p>
Now your widget will only refresh itself when its values are changed by the animation step function we defined earlier, and very few CPU cycles are wasted. As a plus, you can render updates as fast or as slow as you want, without affecting the duration of your animations.
</p>

<h2>Demo</h2>

<p>
I whipped up a quick demo which renders a <a href='/files/bacon/animation-demo.html'>cloud of bacon</a> using <code>&lt;canvas&gt;</code>. All the motion in the demo is created through <code>$.animate()</code>, with a bunch of animations running at once.
</p>

<p>
<small><em>This demo will not work in Internet Explorer, and has only been tested in Firefox 3 and Safari 3.</em></small>
</p>

<p>
This is a rather esoteric example, but there are plenty of useful ways to apply this technique. I've used it to implement <a href='/files/bacon/omgpizza.mov'>smooth, beautiful, usable widgets</a>. You can combine multiple motion and opacity animations triggered by clicks and hovers without issues.
</p>
 
<h2>Final notes</h2>

<p>
While this technique works great, there is one big caveat. You should avoid animating any property that exists in CSS ('float', 'display', 'opacity', ...) because these have unexpected side effects depending on the browser.
</p>

<p>
There are also a couple of weaknesses:
</p>

<ul>
  <li>jQuery does not support continued easing. That is, when you override an animation that is already in progress, the variable being animated will instantly stop and restart from its current position. The rate of change is not continuous between the two animations.</li>
  <li>Animating angles is tricky. E.g. when animating from 350˚ to 0˚, you want it to animate across 10˚ and not the long way around. This requires manual correction.</li>
</ul>

<p>
And obviously, it would be cleaner if jQuery's animation core was refactored to separate out the CSS-specific code instead...
</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Welcome to the World of Tomorrow!]]></title>
    <link href="http://acko.net/blog/welcome-to-the-world-of-tomorrow/"/>
    <updated>2008-07-20T00:00:00-07:00</updated>
    <id>http://acko.net/blog/welcome-to-the-world-of-tomorrow</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>Welcome to the World of Tomorrow!</h1><p><small>(with apologies to <a href='http://en.wikipedia.org/wiki/Futurama'>Matt Groening</a>)</small>
</p>

<p>
After about <a href='/blog/new-design-for-acko-net'>two years</a>, it's time for another make-over of my site.
</p>

<p>
My last design had a relatively quirky look, with a bold red/yellow theme built from various irregular vector shapes. The idea was to step away from the typical mold of rectangular aligned frames on a page. I tried to incorporate some elements of perspective into the page composition, but it ended up being a relatively flat, geometrical theme.
</p>

<p>
This time I wanted to work on the depth aspect and try to create something that feels spacious. To do this, I based the entire redesign on a two-point perspective. While the content itself is normal 2D markup, it sits in a 3D frame.
</p>

<p>
<img alt='' src='/files/redesign-2008/wirepron.png' title='Some of the guide lines used in the construction process.' /></p>

<p>
<img alt='' src='/files/making-love-to-webkit/old-acko.png' /></p>

<p>
The header image is a regular illustration file (which is 100% manual vector work) and the content is typical HTML/CSS. However there is a twist: the perspective from the header is continued into the content with some simple 3D decorations, created on-demand with Canvas tags and JavaScript (<a href='javascript:void(0);' onclick='highlightCanvases();return false;'>highlight canvases</a>, check out the footer).
</p>

<p>
While this perspective works perfectly near the top, the further down you go, the more vertically stretched the shapes get and it ends up looking weird. To compromise, the projection actually gets more and more isometric the further down you go. This creates an interesting effect when scrolling down.
</p>

<p>
The design also uses various CSS3 methods (@font-face, text-shadow, box-shadow) throughout, and uses sIFR 3 as a fallback for the headline font. Unfortunately CSS3 is still mostly unsupported in the browserscape, so only Safari 3.1 users get the luxury combo of <em>pretty, fast and no Flash</em>. Everyone else will have to suffer through hacks.
</p>

<p>
As a total surprise, the canvas-rocket-science trickery even works in IE6 thanks to Google's <a href='http://excanvas.sourceforge.net/'>ExplorerCanvas</a> library.
</p>

<p>
I'll probably be tweaking it a bit more in the days to come, but feedback is appreciated.
</p>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ComicJuice gets even better]]></title>
    <link href="http://acko.net/blog/comicjuice-gets-even-better/"/>
    <updated>2007-03-09T00:00:00-08:00</updated>
    <id>http://acko.net/blog/comicjuice-gets-even-better</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>ComicJuice gets even better</h1><p>I finished some more tweaks to ComicJuice:
</p>

<ul>
<li>
<p>IE6 and 7 are now supported, thanks to the amazing <a href='http://excanvas.sourceforge.net/'>ExplorerCanvas</a> by Google. It emulates the <a href='http://developer.mozilla.org/en/docs/Drawing_Graphics_with_Canvas'>&lt;canvas&gt; tag</a> in IE, meaning that client-side scriptable vector graphics are now available on all the major browsers (IE, Firefox, Safari, Opera). I doubt Konqueror will be far behind.
</p>

<p>
This opens up some cool abilities, like dynamic in-page graphs, mini-widgets (sliders, dials, maps, ...) and even pure JS games. There's a bunch of examples linked on <a href='http://en.wikipedia.org/wiki/Canvas_(HTML_element)'>Wikipedia</a> (though most don't use ExplorerCanvas yet).</p>
</li>
<li>
<p>I added support for uploading your own images rather than using pictures on the web. It uses a customized and themed version of core's JS uploader.
</p>
<p class='tc'><img alt='comic juice' src='/files/comicjuice/comicjuice.png' /></p>
</li>
<li>I improved the clipping of speech bubbles so there should be less useless whitespace around comics, especially when embedding them.</li>
</ul>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Announcing.... ComicJuice!]]></title>
    <link href="http://acko.net/blog/announcing-comicjuice/"/>
    <updated>2007-03-06T00:00:00-08:00</updated>
    <id>http://acko.net/blog/announcing-comicjuice</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>
  
<h1>Announcing.... ComicJuice!</h1>
  
<p>I'm proud to announce the start of ComicJuice, a web 2.0 social mashup tool that lets you create comics in your browser and share them with others.
</p>

<p>
<em>Update: Now with Internet Explorer support! Thanks to <a href='http://code.google.com/p/explorercanvas/'>Google's ExplorerCanvas</a>. Viewing comics works in IE6 and 7, while editing still requires IE7.</em>
</p>

<p>
<object height='350' style='margin-top: 10px' width='425'><param name='movie' value='http://www.youtube.com/v/qhVMU48GfvE' /><param name='wmode' value='transparent' /><embed height='350' src='http://www.youtube.com/v/qhVMU48GfvE' type='application/x-shockwave-flash' width='425' wmode='transparent' /></object>
</p>

<p>
The crazy part is that I started working on this only friday evening (that's 4 days ago). Once I had the initial idea and a rough plan, I simply couldn't not code it.
</p>

<p>
A lot of jQuery and JavaScript later, with some &lt;canvas&gt; wizardry (boy is that thing inconsistent across browsers), we have a fully-fledged comic creator. The best part is that all of it is rendered client-side, so no actual images need to be generated. To display a comic, we use the same code as the editing interface. The down-side is that it doesn't work in IE, but I've been thinking about maybe doing a rough canvas emulation. We'll see. For now, the latest versions of Safari, Firefox and Opera have been tested and work well.
</p>

<p>
You can also embed comics with iframes, and copy/pastable code is provided. Like this lame example:
</p>

<p><em>(No longer available)</em></p>

<p>
I figured a Web 2.0 mash-up would not be complete without a fitting design to go in, so I designed icons, sliders and toolbars for the editor, as well as a theme for the website. The theme is a Garland knock-off: I guess I'm proving myself wrong that it's a bad base theme. It's actually quite good as it has fluid/fixed 1-3 column layouts in it.
</p>

<p>
I'm curious to see if ComicJuice takes off and what people do with it. It was a blast to code in any case. Check it out.</p></div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Mouse Handling and Absolute Positions in JavaScript]]></title>
    <link href="http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript/"/>
    <updated>2006-10-27T00:00:00-07:00</updated>
    <id>http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>Mouse Handling and Absolute Positions in JavaScript</h1><p>As I was working on the new <a href='http://acko.net/garland/?q=admin/build/themes/settings/garland'>recolorable Garland Drupal theme</a>, I noticed that suddenly my Farbtastic color picker wasn't working right anymore in IE. A lot of headscratching later, I found the cause and discovered a useful trick for dealing with mouse coordinates in JavaScript.
<!--break-->
Essentially, when you click on Farbtastic, the mouse position is compared to the position of the color picker, so we can determine which color you clicked. Sounds simple? Well, no. There is no direct DOM API to get an elements's absolute position on the page. The most common technique to find it is to iterate through an element's offsetParents until you reach the root, and add together all the offsets:
<code><br />
&nbsp;&nbsp;function&nbsp;getAbsolutePosition(element)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;r&nbsp;=&nbsp;{&nbsp;x:&nbsp;element.offsetLeft,&nbsp;y:&nbsp;element.offsetTop&nbsp;};<br />
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(element.offsetParent)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;tmp&nbsp;=&nbsp;getAbsolutePosition(element.offsetParent);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r.x&nbsp;+=&nbsp;tmp.x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;r.y&nbsp;+=&nbsp;tmp.y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;r;<br />
&nbsp;&nbsp;};<br />
</code>
</p>

<p>
Unfortunately, even this does not work well. Various browsers have various quirks, but (no surprise) IE wins the contest hands down. When you try to resolve absolute positions in any sort of advanced CSS-based layout, the return coordinates are often completely wrong. This is exactly what was happening in the new (completely tableless) Garland theme.
</p>

<p>
After trying various ways to correct the absolute values, I decided I didn't want to waste hours of my life cleaning up after somebody elses mess. And of course, hardcoding in the correction is mostly useless in a dynamic CMS like Drupal.
</p>

<p>
I did come up with an alternative which works well enough, and is perfectly suited for making self-contained HTML widgets: that's the most common use case after all.
</p>

<p>
You see, aside from the absolute mouse position (event.pageX/Y) we often also get the mouse position relative to the clicked element (event.offsetX/Y). Now, if we try to resolve these coordinates back to the root of the page, we end up with the same problem. The trick is to realize that we often don't need completely absolute coordinates: all we need is coordinates relative to a common reference frame. So, we need to find the closest, common offsetParent for the clicked element and the reference element, and then compare the coordinates in that frame.
</p>

<p>
The snippet below achieves this. As most of the bad offsetParent numbers are located very high up in the page hierarchy, they are practically never used with this approach. Typically you only go up one or two offsetParents and there is no error.
</p>

<p>
Some browsers don't provide the offsetX/Y information (e.g. Firefox) or tend to screw it up (e.g. Opera), but luckily they are the ones that provide (mostly) accurate pageX/Y coordinates, even in exotic layouts. So using that as a fallback, we end up with the following function, which works in every browser I've tried:
</p>

<p>
<code><br />
&nbsp;&nbsp;/**<br />
&nbsp;&nbsp;&nbsp;*&nbsp;Retrieve&nbsp;the&nbsp;coordinates&nbsp;of&nbsp;the&nbsp;given&nbsp;event&nbsp;relative&nbsp;to&nbsp;the&nbsp;center<br />
&nbsp;&nbsp;&nbsp;*&nbsp;of&nbsp;the&nbsp;widget.<br />
&nbsp;&nbsp;&nbsp;*<br />
&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;event<br />
&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;A&nbsp;mouse-related&nbsp;DOM&nbsp;event.<br />
&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;reference<br />
&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;A&nbsp;DOM&nbsp;element&nbsp;whose&nbsp;position&nbsp;we&nbsp;want&nbsp;to&nbsp;transform&nbsp;the&nbsp;mouse&nbsp;coordinates&nbsp;to.<br />
&nbsp;&nbsp;&nbsp;*&nbsp;@return<br />
&nbsp;&nbsp;&nbsp;*&nbsp;&nbsp;&nbsp;&nbsp;A&nbsp;hash&nbsp;containing&nbsp;keys&nbsp;'x'&nbsp;and&nbsp;'y'.<br />
&nbsp;&nbsp;&nbsp;*/<br />
&nbsp;&nbsp;function&nbsp;=&nbsp;getRelativeCoordinates(event,&nbsp;reference)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;x,&nbsp;y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;event&nbsp;=&nbsp;event&nbsp;||&nbsp;window.event;<br />
&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;el&nbsp;=&nbsp;event.target&nbsp;||&nbsp;event.srcElement;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(!window.opera&nbsp;&amp;&amp;&nbsp;typeof&nbsp;event.offsetX&nbsp;!=&nbsp;'undefined')&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Use&nbsp;offset&nbsp;coordinates&nbsp;and&nbsp;find&nbsp;common&nbsp;offsetParent<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;pos&nbsp;=&nbsp;{&nbsp;x:&nbsp;event.offsetX,&nbsp;y:&nbsp;event.offsetY&nbsp;};<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Send&nbsp;the&nbsp;coordinates&nbsp;upwards&nbsp;through&nbsp;the&nbsp;offsetParent&nbsp;chain.<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;e&nbsp;=&nbsp;el;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(e)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.mouseX&nbsp;=&nbsp;pos.x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.mouseY&nbsp;=&nbsp;pos.y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos.x&nbsp;+=&nbsp;e.offsetLeft;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pos.y&nbsp;+=&nbsp;e.offsetTop;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e&nbsp;=&nbsp;e.offsetParent;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Look&nbsp;for&nbsp;the&nbsp;coordinates&nbsp;starting&nbsp;from&nbsp;the&nbsp;reference&nbsp;element.<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;e&nbsp;=&nbsp;reference;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;offset&nbsp;=&nbsp;{&nbsp;x:&nbsp;0,&nbsp;y:&nbsp;0&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(e)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(typeof&nbsp;e.mouseX&nbsp;!=&nbsp;'undefined')&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;e.mouseX&nbsp;-&nbsp;offset.x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y&nbsp;=&nbsp;e.mouseY&nbsp;-&nbsp;offset.y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;offset.x&nbsp;+=&nbsp;e.offsetLeft;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;offset.y&nbsp;+=&nbsp;e.offsetTop;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e&nbsp;=&nbsp;e.offsetParent;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Reset&nbsp;stored&nbsp;coordinates<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e&nbsp;=&nbsp;el;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while&nbsp;(e)&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.mouseX&nbsp;=&nbsp;undefined;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e.mouseY&nbsp;=&nbsp;undefined;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;e&nbsp;=&nbsp;e.offsetParent;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;else&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Use&nbsp;absolute&nbsp;coordinates<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;var&nbsp;pos&nbsp;=&nbsp;getAbsolutePosition(reference);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x&nbsp;=&nbsp;event.pageX&nbsp;&nbsp;-&nbsp;pos.x;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y&nbsp;=&nbsp;event.pageY&nbsp;-&nbsp;pos.y;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;Subtract&nbsp;distance&nbsp;to&nbsp;middle<br />
&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;{&nbsp;x:&nbsp;x,&nbsp;y:&nbsp;y&nbsp;};<br />
&nbsp;&nbsp;}<br />
</code>
</p>

<p>
Obviously if the elements you apply it to have an exotic positioning, it'll still go sour, but the above code at least improves the situation massively in Internet Explorer. Here's a little <a href='http://www.acko.net/files/mouse-positioning.html'>example</a>.</p></div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[jQuery is in Drupal core!]]></title>
    <link href="http://acko.net/blog/jquery-is-in-drupal-core/"/>
    <updated>2006-09-01T00:00:00-07:00</updated>
    <id>http://acko.net/blog/jquery-is-in-drupal-core</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>jQuery is in Drupal core!</h1><p>After a long wait, the awesome <a href='http://jquery.com'>jQuery library</a> has finally been committed to <a href='http://drupal.org'>Drupal</a> core. jQuery 1.0 will be part of the next major Drupal release, for which the code freeze is about to begin.
</p>

<p>
While we did take advantage of jQuery to add some minor glitter to our JavaScript features, the main advantage is that it makes it easier to develop JavaScript features. Using a simple CSS-based syntax, you can manipulate any element in the page easily. Loading in chunks of HTML through Ajax is a snap too.
</p>

<p>
Hopefully jQuery will help more Drupal developers look at using JavaScript to build kick-ass interfaces in the browser.
</p>

<p>
Thanks to <a href='http://ejohn.org'>John Resig</a> and the jQuery community for making such an awesome library. On the Drupal site, thanks to: <a href='http://drupal.org/user/14572'>Konstantin</a>, <a href='http://drupal.org/user/45890'>Steve</a>, <a href='http://drupal.org/user/24967'>Angie</a>, <a href='http://drupal.org/user/12932'>Ted</a>, <a href='http://drupal.org/user/16496'>Jeff</a> and everyone else who tested the patch.
</p>

<p>
<small>See also: <a href='http://acko.net/blog/new-wave-javascript-in-drupal-jquery'>'New Wave' JavaScript in Drupal: jQuery</a>.</small>
</p>

<p>
<small>PS: There will still be some bugs in there that I haven't found, but that's what the code freeze is for ;).</small></p></div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Farbtastic Color Picker]]></title>
    <link href="http://acko.net/blog/farbtastic-jquery-color-picker-plug-in/"/>
    <updated>2006-07-14T00:00:00-07:00</updated>
    <id>http://acko.net/blog/farbtastic-jquery-color-picker-plug-in</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'>
  
<h1>Farbtastic Color Picker</h1>
<h2>jQuery Plug-in</h2>

<p>
Farbtastic is a <a href='http://www.jquery.com/'>jQuery</a> plug-in that can add one or more color picker widgets into a page through JavaScript. Each widget is then linked to an existing element (e.g. a text field) and will update the element's value when a color is selected.
</p>

<p>
<a href='/files/farbtastic/farbtastic12.zip'>Download Farbtastic 1.2</a> - January 8, 2007 (License: <a href='http://www.gnu.org/copyleft/gpl.html'>GPL</a>).
</p>

</div></div><div class='g5 i2'><div class='pad'>

<h2>Demo</h2>

<p>
Farbtastic uses layered transparent PNGs to render a saturation/luminance gradient inside of a hue circle. No Flash, no pixel sized divs.
</p>

<p>
Click and drag over the selector to try it out.
</p>

<p>
<form action=''>
  <div class='form-item'><label for='color'>Color:</label><input id='color' name='color' style='width: 195px' type='text' value='#123456' /></div>
</form>
</p>

</div></div><div class='g3'><div class='pad m3'>

<link href='/files/farbtastic/demo/farbtastic.css' media='screen' rel='stylesheet' type='text/css' />

<script charset='utf-8' src='/files/farbtastic/demo/farbtastic.js' type='text/javascript' />

<script charset='utf-8' type='text/javascript'>
  setTimeout(function() {
    jQuery.farbtastic('#picker').linkTo('#color');
  }, 100);
</script>

<div id='picker' />

</div></div><div class='g8 i2'><div class='pad'>

<h2>Basic Usage</h2>
<ol>
<li>
  <p>Include farbtastic.js and farbtastic.css in your HTML:</p>
<p class='codeblock'>
<code>&lt;script&nbsp;type=&quot;text/javascript&quot;&nbsp;src=&quot;farbtastic.js&quot;&gt;&lt;/script&gt;<br />
&lt;link&nbsp;rel=&quot;stylesheet&quot;&nbsp;href=&quot;farbtastic.css&quot;&nbsp;type=&quot;text/css&quot;&nbsp;/&gt;
</code></p>
</li><li>
  <p>Add a placeholder div and a text field to your HTML, and give each an ID:</p>
<p class='codeblock'><code>&lt;form&gt;&lt;input&nbsp;type=&quot;text&quot;&nbsp;id=&quot;color&quot;&nbsp;name=&quot;color&quot;&nbsp;value=&quot;#123456&quot;&nbsp;/&gt;&lt;/form&gt;<br />
&lt;div&nbsp;id=&quot;colorpicker&quot;&gt;&lt;/div&gt;<br />
</code></p>
</li>
<li>
  <p>Add a ready() handler to the document which initializes the color picker and link it to the text field with the following syntax:</p>
<p class='codeblock'><code>&lt;script&nbsp;type=&quot;text/javascript&quot;&gt;<br />
&nbsp;&nbsp;$(document).ready(function()&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;$('#colorpicker').farbtastic('#color');<br />
&nbsp;&nbsp;});<br />
&lt;/script&gt;<br />
</code></p>
</li></ol>

<p>
See demo1.html and demo2.html for an example (jquery.js is not included!).
</p>

<h2>Styling</h2>

<p>
The color picker is a block-level element and is 195x195 pixels large. You can control the position by styling your placeholder (e.g. floating it).
</p>

<p>
Note that the black/white gradients inside wheel.png and mask.png were generated programmatically and cannot be recreated easily in an image editing program.
</p>

<h2>Advanced Usage</h2>

<h3>jQuery Method</h3>
<dl>
<dt>$(...).farbtastic()
$(...).farbtastic(callback)</dt>
<dd>This creates color pickers in the selected objects. <code>callback</code> is optional and can be a:
<ul>
<li><em>DOM Node</em>, <em>jQuery object</em> or <em>jQuery selector</em>: the color picker will be linked to the selected element(s) by syncing the value (for form elements) and color (all elements).</li>
<li><p><em>Function</em>: this function will be called whenever the user chooses a different color. It should have the following signature:
</p>

<p class='codeblock'><code>function&nbsp;callback(color)&nbsp;{&nbsp;...&nbsp;}</code>
</p>

<p>
With <code>color</code> the chosen color in hex representation (e.g. '#123456').
</p>
</li>
</ul>
</dd>
</dl>

<h3>Object</h3>
<dl>
<dt>$.farbtastic(placeholder)
$.farbtastic(placeholder, callback)
</dt>
<dd><p>Invoking <code>$.farbtastic(placeholder)</code> is the same as using <code>$(placeholder).farbtastic()</code> except that the Farbtastic object is returned instead of the jQuery object. This allows you to use the Farbtastic methods and properties below.
</p>

<p>
Note that there is only one Farbtastic object per placeholder. If you call <code>$.farbtastic(placeholder)</code> twice <em>with the same placeholder</em>, you will get the same object back each time.
</p>

<p>
The optional <code>callback</code> argument behaves exactly as for the jQuery method.
</p>
</dd>
</dl>

<h3>Methods</h3>
<dl>
<dt>.linkTo(callback)</dt>
<dd>Allows you to set a new callback. Any existing callbacks are removed. See above for the meaning of <code>callback</code>.</dd>
<dt>.setColor(string)</dt>
<dd>Sets the picker color to the given color in hex representation (e.g. '#123456').</dd>
<dt>.setColor([h, s, l])</dt>
<dd>Sets the picker color to the given color in normalized HSL (0..1 scale).</dd>
</dl>

<h3>Properties</h3>
<dl>
<dt>.linked</dt>
<dd>The elements (jQuery object) or callback function this picker is linked to.</dd>
<dt>.color</dt>
<dd>Current color in hex representation (e.g. '#123456').</dd>
<dt>.hsl</dt>
<dd>Current color in normalized HSL (e.g. [0.3, 0.4, 0.5]).</dd>
</dl>

</div></div>]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA['New Wave' JavaScript in Drupal: jQuery]]></title>
    <link href="http://acko.net/blog/new-wave-javascript-in-drupal-jquery/"/>
    <updated>2006-06-23T00:00:00-07:00</updated>
    <id>http://acko.net/blog/new-wave-javascript-in-drupal-jquery</id>
    <content type="html"><![CDATA[<div class='g8 i2 first'><div class='pad'><h1>'New Wave' JavaScript in Drupal: jQuery</h1><p>After an Ajax related chat today, a battle plan has been created for integrating the <a href='http://jquery.com/'>jQuery Javascript library</a> into Drupal. As this is the result of a long discussion in private mails and on <a href='http://groups.drupal.org/node/665'>Drupal Groups</a>, I wanted to jot down the reasons in one place for future reference.
</p>

<p>
Though several people have suggested integrating third party JavaScript libraries in Drupal, I rarely heard good arguments for doing so. The last thing we want is just to add some 'bells and whistles'. I wanted a good, small platform that would serve as a suitable base for both the (relatively small) core JavaScript work as well as the extended needs of the Drupal community at large.
</p>

<p>
The most important thing is to keep in mind the context of the Drupal system. We have a very advanced code base on the server-side already: text filtering, access control, localization, form API, theming, etc. A well-written, Drupal-like module has to use these systems to integrate with Drupal. Heavy client-side JavaScript applications are not likely to happen soon, as that will lead to self-contained functionality that does not 'play nice' with others.
</p>

<p>
This means that browser platforms such as Dojo are not good candidates. Though well written, they are simply overkill for what we need. When we look for small, functional libraries, jQuery stands out as the most suitable:
<ul>
<li><strong>It focuses on making common JS tasks easy to do, without much fuss or overkill</strong>. There are no grand claims, just a set of well written, self-contained utilities. This means you can focus on your functionality.</li>
<li><strong>It is centered around CSS-like queries</strong>. Finding and interacting with DOM elements is done with a CSS selector. This means it is easy to pick-up even for those with no JavaScript knowledge.</li>
<li><strong>It provides much bang for your buck</strong>. In only 10KB, you get the CSS-query system, basic Ajax, basic animations and smart events.</li>
<li><strong>It is built by smart, dedicated people</strong>. In fact, John Resig (the author) has personally gotten involved in discussions and answered our many questions.</li>
<li><strong>It has a modular plug-in system</strong>. This means that contributed module authors can easily include advanced functionality, while we can keep core simple. Including an existing or custom plug-in is as simple as adding in another .js file.</li>
</ul>
</p>

<p>
To summarize: we chose jQuery because it is compact enough for core, easy enough for non-JS experts and because it provides a broad platform onto which rich contributed modules can be built.
</p>

<p>
The concrete steps for getting this stuff into Drupal:
<ol>
<li>Remove those parts of drupal.js that have equivalents in jQuery, and rewrite the left-overs to use jQuery's APIs.</li>
<li>Rewrite the other .js files in core to use jQuery functionality. This is mostly the replacing of complicated code with very simple calls.</li>
<li>Add in new functionality that jQuery makes easy, thus proving how this was a smart move.</li>
</ol>
</p>

<p>
I'm confident we can get this done for Drupal 4.8.</p></div></div>]]></content>
  </entry>
  
</feed>
