PDFObject Updated, Moved to GitHub

PDFObject is a JavaScript utility I created in 2008 to embed PDFs in HTML documents. It was modeled on SWFObject.

Three years have passed since PDFObject 1.0 was released, and the browser landscape has changed dramatically. I figured it’s time to dust off PDFObject and see if it can be improved and/or updated for today’s browsers.

I’ve placed a modified edition of PDFObject (version 1.1) on GitHub as an open-source project, allowing anyone to create a fork and make modifications. There are also a handful of example/test HTML files there, too. I could use some help testing the examples in different OS/browser combinations, and with different PDF plugins.

If you have suggestions for improvements, or have a few minutes to test PDFObject examples in your browser/OS of choice, I’d be grateful to hear from you. Just leave a comment below, or send me a message on twitter. So far I’ve only performed some cursory tests in Firefox 3.6 in OSX 10.6.6, but everything seems to work as expected.

https://github.com/pipwerks/PDFObject

I plan to update PDFObject.com soon, too.

For Your Reading Pleasure: EasyCaptions

Introducing EasyCaptions: A simple system for adding captions and an interactive transcript to online videos. EasyCaptions uses progressive enhancement to provide the best possible experience for all visitors, regardless of their browser’s JavaScript, HTML5 or Flash support.
Demonstration

Background

I don’t produce much video these days, but as a web surfer I often encounter other people’s videos, and was recently impressed by two video implementations: a TED Talks video page, and an HTML5 video demo produced by Bruce Lawson.

The TED Talks page had a great feature I’d never really seen anywhere else: an interactive transcript of the video that you can read and click. For example, you can click the third sentence of the transcript and the video will jump to that point.

Bruce Lawson’s demo illustrated a dead-simple way to add captions to an HTML5 video using just a wee bit of HTML markup and JavaScript.

Both of these pages shared a unique attribute: they stored complete transcripts of the videos in the markup of the HTML page itself, NOT in an external XML file, as is most commonly seen. What’s the big deal about inline transcripts, you ask? Well, today’s most common captioning options require placing your caption text in an external XML file that loads via ActionScript or JavaScript/AJAX. Both the TED page and Bruce’s demo eschew that approach and place the full transcript in the HTML. This means the transcript is always available to the visitor, regardless of that browser’s support for HTML5, JavaScript or Flash Player.

In both cases, the transcript had been marked up as paragraphs, with extra markup denoting phrases that align with the video’s timecode. The TED site used a tags to mark each phrase, with inline JavaScript triggering the playback of the clicked link. This is functional when JavaScript is enabled, but fails when JavaScript is disabled, leaving you with a ton of bloated markup that doesn’t work:


<p>
<a href="#" class="transcriptLink" onclick="seekVideo(0); return false;">One way to change our genes is to make new ones,</a> 
<a href="#" class="transcriptLink" onclick="seekVideo(2000); return false;">as Craig Venter has so elegantly shown.</a>
</p>

Bruce’s span tags, on the other hand, are semantically sound — a span is a neutral, unobtrusive inline element meant to be a child of a block element such as p. Perfect. But wait, there’s more! Since this was an HTML5 demo, Bruce took advantage of the new data attribute that can be used on just about any HTML element. (The short version is: you can create any custom attribute you want, so long as the name begins with data-.) Bruce decided to create two attributes for each span: data-begin, which indicates (in seconds) when the phrase starts in the video, and data-end, which indicates when that phrase has ended in the video. Simple as can be, and extremely efficient:


<p>
<span data-begin=1 data-end=6>Hi, my name's Dr Archimedes Einstein 
and I'm a Dctor of Science at the University of Science</span>
</p>

Enter: EasyCaptions

I was incredibly inspired by these two pages and decided to combine their features, creating a new system I call EasyCaptions. The goal of EasyCaptions is to make it as simple as possible to add useful captions to your online videos. I want to eliminate the headaches of captioning as well as the excuses (too hard, confusing, etc.).

What EasyCaptions does:

  • Dynamically generates a div under your video that will display caption text (the div‘s style and positioning is completely configurable via CSS).
  • Dynamically makes each span in your transcript clickable, jumping to that point in the video. This is done via progressive enhancement and event delegation, leaving your markup clean and avoiding heavy-handed use of onclick.
  • Works out-of-the-box with HTML5.
  • Includes HTML5 video support detection, enabling you to use a Flash-based fallback if you desire.
  • Provides a hook for Flash-based fallback systems, enabling the captions and transcript to behave identically to the HTML5 version.

What you’ll need to do:

  • Type up the transcript of your video, using <span> tags to wrap each phrase, just like Bruce’s example above.
  • Place the transcript in a container element with a unique ID (such as div id="transcript").
  • Add a teeny bit of JavaScript (about 4 lines).

The final product. Successfully tested in Firefox, Safari, Opera, and Internet Explorer (IE and older versions of the other browsers all use a Flash Fallback if provided).

Peruse the test suite to get an idea of how flexible the system is.

The documentation and downloads are located on the EasyCaptions page.

TextAreaExpander Class for MooTools

I’ve had to create a slew of forms over the last few months, and have become fond of textareas that auto-expand when they become filled. There are a number of MooTools scripts on the interwebs that can handle this task, but none of them suited me, and I generally like to roll my own code as a learning experience.

So, without further ado, I present a very simple MooTools Class: TextAreaExpander. It does exactly what is says: expand textareas. No more, no less.

Details

Options

Defaults are shown.

  • textarea_selector: none
    • Uses CSS selector syntax, such as “#myId” or “textarea.expand-o-rama”
  • padding_bottom: 0
    • Only accepts pixels, no percentages or ems
  • fx_transition: Fx.Transitions.Expo.easeOut
  • fx_duration: 350

Returns

Array of textarea elements

Requires

MooTools 1.2.4 core

Demos

Sample implementation using defaults (grabs ALL textareas on the page):


window.addEvent("domready", function (){
    new TextAreaExpander();								  
});

View ‘simple’ demo

 

Sample implementation specifying all available parameters:


window.addEvent("domready", function (){
    new TextAreaExpander({
        textarea_selector: "#demo",
        padding_bottom: 12,
        fx_transition: Fx.Transitions.Quad.easeInOut,
        fx_duration: 500
    });
});

View ‘options’ demo

Download

License

MIT-style license. Completely free. Enjoy.

Rounded corners on images using CSS3

Most browsers do not allow images to be cropped using CSS3’s border-radius. Tim Van Damme recently posted a workaround for this issue. It’s a nice trick, and doesn’t require JavaScript.

It does, however, require an extra span to be added to the page’s markup, which is quite a pain to manually apply to each image. Bram Van Damme (no relation to Tim) posted a simple jQuery script that automate’s Tim’s workaround with minimal effort. It’s a perfect example of using JavaScript for progressive enhancement purposes, as the page is still usable when JavaScript and/or CSS is disabled.

Being a MooTools kind of guy, I decided to whip up a MooTools-flavored version. Enjoy!


window.addEvent("domready", function (){
    $$("img").each(function(img){
        new Element("span", {
            "class": "rounded",
            styles: {
                "background-image": "url(" + img.getProperty('src') + ")",
                height: img.getProperty('height') + "px",
                width: img.getProperty('width') + "px"
            }
        }).wraps(img);
    });
});

Bear in mind this JavaScript relies on your page having some pre-defined CSS:


.rounded {
    -webkit-border-radius: 25px;
    -moz-border-radius: 25px;
    border-radius: 25px;
    display: block;
}
.rounded img { opacity: 0; }

View demo

Notes:

  • The demo page has the code wrapped in a function, which makes the code reusable; it accepts any CSS selector, such as “img”, “#myid img”, “p img”, “img.rounded”, etc.
  • This will only work in browsers that support CSS3 border-radius, which means no IE6/7/8 or older versions of Safari, Firefox, or Opera.

HTML5 Video, minus Ogg

As you’ve probably read somewhere on the interwebs, HTML5 is bringing native video support to browsers. This will enable us to embed a video file directly in our HTML, much like a SWF or image.

Background

You may have also heard that there’s currently a big controversy over what kind of video files will be supported. The defacto standard is MP4/H.264, which is used by Adobe in their Flash video format, and by huge media sites like YouTube. Mozilla, the makers of Firefox, refuse to support the MP4/H.264 standard because it isn’t open-source and free from licensing constraints.

Turns out H.264 is not public domain. Although the company that owns the H.264 patent has temporarily agreed to waive royalty fees for the next decade or so, they reserve the right to charge fees later on. Mozilla says no way, we will only support a video format that is free from licensing issues and has no patent holders (because patent holders can decide to sue some day). Mozilla supports the completely free/open-source Ogg format.

Apple and Adobe, already knee-deep in MP4/H.264 with their Quicktime and Flash video products, vow to press on with H.264.  Google also supports H.264 because YouTube relies on it, and because Google’s new Chrome browser is based on the WebKit project, which has Apple as a main code contributor. In case you haven’t noticed, Apple, Adobe and Google have pretty much cornered the internet video market the past few years, so if they’re throwing their support behind H.264, you can count on it being around for a while. Not to mention that many mobile devices, including the iPhone and most Android phones, have hardware that is designed specifically to support H.264 video, enabling smoother playback and less battery drain than non-dedicated hardware.

(For what it’s worth, Opera is in agreement with Mozilla and supports Ogg. However, not many people seem to pay attention to Opera these days, so they don’t appear to have much influence in this race. Microsoft has endorsed H.264 with it’s upcoming IE9 browser, but it won’t be available for some time.)

The problem

Firefox and Opera are essentially forcing websites to offer two versions of each video: an Ogg version and an MP4 version. In my opinion — and the opinion of many others — this simply will not do. Providing two different video files is not realistic, Ogg’s quality is inferior to H.264, and many computers and mobile devices have direct hardware support for H.264 but not Ogg. In reality, without MP4 support, HTML5 video is rendered useless for most site developers in Firefox and Opera.

The most logical workaround is to code <video> elements to work for MP4 and have a Flash Player-based fallback for older browsers and browsers that only support Ogg. Since the <video> element is designed to allow for fallback content, just like the <object> element, we can do this:


<video id="myvideo" width="480" height="360" controls>
    <source src="/video/file.m4v" type="video/mp4"></source>
    <object data="flash-video-player.swf" type="application/x-shockwave-flash" width="480" height="360">  
        <param value="flash-video-player.swf" name="movie"/>  
        <param value="file=/video/file.m4v" name="flashvars"/>
    </object>
</video>

This works fine in Safari, Chrome, Internet Explorer and older versions of Firefox and Opera that don’t support the <video> element. However, Firefox 3.6 and Opera 10.5 do something very irritating: even though they KNOW their <video> doesn’t support “video/MP4”, they load the <video> element anyway. Which is … like … OMG so duh … because the video can’t possibly play!

If the <video> element is loaded, Firefox and Opera will never load the fallback <object> containing the Flash-based fallback.

Because this behavior cannot be fixed with markup, we’re forced find a scripted workaround (notice that we haven’t used a single bit of JavaScript yet). Thankfully, there’s a pretty straightforward solution: Delete the <video> element in Firefox and Opera.

A Solution

Here’s a simple script that will detect whether HTML 5 video is supported, and if it is, will check to see if MP4 is supported. If HTML5 video is supported but MP4 is NOT supported, the script deletes the specified <video> element but leaves the Flash fallback in its place.


var detectVideoSupport = function (){
    var detect = document.createElement('video') || false;
    this.html5 = detect && typeof detect.canPlayType !== "undefined";
    this.mp4 = this.html5 && (detect.canPlayType("video/mp4") === "maybe" || detect.canPlayType("video/mp4") === "probably");
    this.ogg = this.html5 && (detect.canPlayType("video/ogg") === "maybe" || detect.canPlayType("video/ogg") === "probably");
    return this;
};

var replaceVideoWithObject = function (video_id){    
    if(!video_id){ return false; }
    var video = document.getElementById(video_id);
    if(video){
        var obj = video.getElementsByTagName("object")[0];
        if(obj){
            var obj_copy = obj.cloneNode(true);
            video.parentNode.insertBefore(obj_copy, video);
            video.parentNode.removeChild(video);
        }
    }
};

window.onload = function (){
    var video = detectVideoSupport();
    //Both Opera and Firefox support OGG but lack MP4 support
    if(video.ogg && !video.mp4){
        replaceVideoWithObject("myvideo");
    }
};
</script>

Functioning demo.

A few notes

Tested successfully in:

  • Windows XP: Firefox 3.0, Firefox 3.5.8, Internet Explorer 6, Internet Explorer 8, Google Chrome 4.1.2
  • Windows Vista: Internet Explorer 7
  • Mac OS X (10.6): Firefox 3.6, Safari 4.01, Chrome 5 (beta), Opera 10.1, Opera 10.5b

(Note: IE6, IE7 & IE8 give an unexplained “object required” error in the demo page, but everything works fine. I will investigate as time permits.)

This demo uses the JW Media Player as the Flash fallback video player, but you can use any Flash-based video player on your page.

This demo doesn’t do any Flash Player version detection to ensure the visitor has a supported version of Flash Player. If you need to add Flash Player version detection, you can use SWFObject to embed your Flash file.

Update April 3, 2010: This post was updated to add Opera 10.5 to the list of non-behaving browsers and remove Firefox user agent sniffing.

A new removeClasses utility for MooTools

Note: If you want to skip to the final code (in both MooTools and framework-neutral flavors), it’s at the bottom of this post.

The problem

Readers of this blog know that I enjoy MooTools. Like other JavaScript frameworks, it has many excellent features, including addClass and removeClass functions, which I use all the time. However, when I was working on my CustomInput class the other day, I discovered a major shortcoming of MooTools’ removeClass function — it doesn’t work very well when trying to remove multiple classes (as of MooTools version 1.2.4). In particular, if you specify multiple classes to remove, removeClass will only work if the classes are listed in that element’s className property in the order specified.


//Assuming element has className "hello cruel world"

//Single terms work fine
element.removeClass("world"); //becomes "hello  world"
element.removeClass("hello"); //becomes " cruel world"

//Multiple terms listed in same order as className works fine
element.removeClass("hello cruel"); //becomes " world"

//Multiple terms NOT listed in same order as className fail
element.removeClass("hello world"); //remains "hello cruel world"
element.removeClass("cruel hello"); //remains "hello cruel world"

The cause

A peek at MooTools’ removeClass code reveals the shortcoming:


removeClass: function(className){
   this.className = this.className.replace(new RegExp('(^|\s)' + className + '(?:\s|$)'), '$1');
   return this;
}

The string containing the class names you want to remove is passed as-is; it remains a whole string, and is not broken down into substrings. So "hello world" remains the single string "hello world" instead of two separate strings "hello" and "world".

I tested jQuery’s removeClass function and noticed it doesn’t have the same problem; it will remove each word, no matter what order you specify. Taking a look under the hood reveals a completely different approach to removeClass:


removeClass: function( value ) {
   if ( jQuery.isFunction(value) ) {
      return this.each(function(i) {
         var self = jQuery(this);
         self.removeClass( value.call(this, i, self.attr("class")) );
      });
   }

   if ( (value && typeof value === "string") || value === undefined ) {
      var classNames = (value || "").split(rspace);

      for ( var i = 0, l = this.length; i < l; i++ ) {
         var elem = this[i];

         if ( elem.nodeType === 1 && elem.className ) {
            if ( value ) {
               var className = (" " + elem.className + " ").replace(rclass, " ");
               for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
                  className = className.replace(" " + classNames[c] + " ", " ");
               }
               elem.className = jQuery.trim( className );

            } else {
               elem.className = "";
            }
         }
      }
   }

   return this;
}

The big difference between jQuery and MooTools in this case is that jQuery converts the arguments to an array (split using spaces as a delimiter) then loops through the className property to search for each word in the array, whereas MooTools performs a simple full-string replace using regular expressions.

The search for a solution

My first reaction was to build a MooTools-flavored variation of jQuery’s removeClass code.


Element.implement({
   removeClasses: function (classNames) {
      if(this.className){
         var classNameString = this.className;
         classNames.split(/s+/).each(function (term){
            classNameString = classNameString.replace(term, " ");
         });
         this.className = classNameString.clean();
      }
      return this;
   }
});

It follows the same basic principles of the jQuery version, but uses MooTools’ Array.each and String.clean utility functions. It works well, but I wasn’t thrilled about using a loop. I thought maybe a regular expression would be better suited for the job.

I eventually came up with this:
(purposely verbose to explain what’s happening)


Element.implement({
   removeClasses: function (classNames) {
      if(classNames && this.className){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = this.className.replace(reg, " ");
         //Use MooTools' 'clean' method to remove extraneous spaces
         newClass = newClass.clean();
         //Set element's classname to new cleaned list of classes
         this.className = newClass;
      }
      return this;
   }
});
Notes:

  • I’m no expert on JavaScript speed tests, so for all I know a loop might be quicker. However, a regular expression feels more elegant.
  • I named my utility removesClasses so it doesn’t overwrite MooTools’ built-in utility.

Final MooTools version

Here’s a more concise version:


Element.implement({
   removeClasses: function (classNames) {
      this.className = this.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").clean();
      return this;
   }
});

You can see it in action via the MooShell (apparently the MooTools Shell is no longer online)

Standalone (framework-neutral) version

For those of you who don’t use MooTools, a few small edits will allow you to use this code without relying on any outside JavaScript libraries. First, a verbose version explaining what’s happening:


function removeClass(el, classNames) {
   //Only run if the element is available and supports the className property
   if(el && el.className && classNames){
         //Replace all spaces in classNames with vertical beams
         var terms = classNames.replace(/s+/g, "|");
         //Create a regular expression using terms variable
         var reg = new RegExp('\b(' + terms + ')\b', 'g');
         //Use the new regular expression to replace all specified terms with a space
         var newClass = el.className.replace(reg, " ");
         //Use regular expression to remove extraneous whitespace between class names
         newClass = newClass.replace(/s+/g, " ");
         //Use regular expression to remove all whitespace at front and end of string
         newClass = newClass.replace(/^s+|s+$/g, "");
         //Set element's classname to new cleaned list of classes
         el.className = newClass;
   }
}

The concise version:


function removeClass(el, classNames) {
   if(el && el.className && classNames){
      el.className = el.className.replace(new RegExp("\b(" + classNames.replace(/s+/g, "|") + ")\b", "g"), " ").replace(/s+/g, " ").replace(/^s+|s+$/g, "");
   }
}

Used as follows:


var myelement = document.getElementById("myelement");
removeClass(myelement, "one three");
//<div id="myelement" class="one two three"></div>
//becomes
//<div id="myelement" class="two"></div>

Enjoy!

Successfully tested in Internet Explorer 6 (WinXP), Firefox 3.5 (WinXP), Firefox 3.6 (OS X 10.6.2), Safari 4 (OS X 10.6.2), Opera 10.1 (OS X 10.6.2), Chrome 5 (OS X 10.6.2)

CustomInput Class: Accessible, Custom-Styled Checkboxes and Radio Buttons

I’m a big fan of the Filament Group’s UI work.  They put a lot of thought into their work, and ensure everything they make is not only beautiful, but as accessible and as semantic as possible.

One of my favorite pieces of work by the Filament Group is their approach to stylized checkbox and radio <input> elements, as described in their post Accessible, Custom Designed Checkbox and Radio Button Inputs Styled with CSS (and a dash of jQuery)

The system is remarkably well-constructed, degrades gracefully, and is completely accessible if JavaScript, image loading, or CSS is disabled can’t be loaded. No small feat. Plus it remains semantically sound, with no bloated markup.

Having said all that, I’ve never actually used any of Filment Group’s UI code in my projects because of one little obstacle: they rely on jQuery.  Mind you, I’m not a jQuery hater, but I prefer MooTools and build all my major projects using MooTools. Switching to jQuery for such a small UI customization is not going to happen. I’ve often thought it would be great to build a MooTools version of Filament’s UI examples, but never had the time… until today.

I’m currently working on a new quiz system at work, and decided I’d incorporate Filament’s wonderful stylized checkboxes and radio buttons into my project, which meant it was time to roll up my sleeves and code me some Moo. 

View Demo

I made a couple of changes to the underlying JavaScript to suit a MooTools approach (and add some flexibility with the CSS selectors) but the basic premise is the same as Filament’s demo.

The following JavaScript will style ALL checkbox and radio inputs on your page. Note that CustomInput returns the collection of elements that have been styled.


window.addEvent("domready", function(){ 
    var all_styled_inputs = new CustomInput();
});

If you want to target specific parts of the page or only certain input types, pass a CSS selector as an argument:


window.addEvent("domready", function(){
    //Only style checkboxes
    var styled_checkboxes = new CustomInput("input[type='checkbox']");

    //Only style radios in a div named "walkman"
    var styled_radios = new CustomInput("#walkman input[type='radio']");
});

The CustomInput class has been fully JSLinted, and remains very close to the original jQuery version’s size. When compressed (YUI), it squeezes down to about 1.6kb. It uses ‘dollar-safe’ mode for compatibility with other JS libraries.

It has been successfully tested in the following systems:

Windows XP

  • Internet Explorer 6
  • Firefox 3.5
  • Safari 4
  • Chrome 4
  • Opera 10

Mac OS X (10.6.2)

  • Firefox 3.6
  • Safari 4
  • Opera 10.1
  • Chrome 5 (beta)

View Demo | Download Project Files

Major kudos to Filament Group for sharing their ideas with the world.

Update 3/12/2010: Fixed a typo in the JS file preventing the checkedHover class from being assigned.

Lazy loading excanvas.js

excanvas.js is a script that enables developers to use the canvas element in Internet Explorer; since IE doesn’t support canvas or the canvas API, the excanvas script converts (most) canvas commands to Vector Markup Language (VML), the only vector rendering language supported natively in Internet Explorer.

excanvas is designed specifically for Internet Explorer, so most people simply use a conditional comment to load it in IE and avoid loading it in other browsers:


<!--[if IE]>
<script type="text/javascript" src="/scripts/excanvas.js" src="/scripts/excanvas.js">
<![endif]-->

This works fine if you have access to the HTML file that needs to load excanvas, but what if you can’t edit the HTML? This was my predicament with a recent project; I decided to use a lazy loader approach (on-demand loading) and load excanvas.js dynamically.

I started by developing an HTML example page that used the canvas element and had the excanvas.js file hard-coded. Everything worked as planned. I then took out the hard-coded excanvas.js file and replaced it with a JavaScript-based lazy loader. Guess what? It didn’t work.

I scratched my head for a while and did some more testing. The strange thing was that excanvas.js was loading when and where it was supposed to, but the script itself wasn’t firing.

After digging around the excanvas.js source code a bit, I found the problem: the script contains an init function that only gets invoked when the document’s readystate changes. If the document is already loaded, the readystate won’t change and the init function will never fire!

A simple modification to the excanvas.js file fixed the problem:

Original code (starting at line 87 of excanvas.js)


init: function(opt_doc) {
    if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        var doc = opt_doc || document;
        // Create a dummy element so that IE will allow canvas elements to be
        // recognized.
        doc.createElement('canvas');
        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
    }
},

Modified version


init: function(opt_doc) {
    if (/MSIE/.test(navigator.userAgent) && !window.opera) {
        var doc = opt_doc || document;
        // Create a dummy element so that IE will allow canvas elements to be
        // recognized.
        doc.createElement('canvas');

        if(doc.readyState !== "complete"){

            doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));

        } else {

           this.init_(doc);

        }

    }
},

Basically all we’re doing is checking to see if the readystate is already “completed” before attempting to do attachEvent. If the state is completed, we don’t need attachEvent and can just invoke this.init_ directly.

Update: Alternate solution

January 2014. Unfortunately, the authors of ExCanvas haven’t addressed this issue yet. Since it has been over 3 years since I submitted an issue on their project site, I assume it will not be addressed. (At this point I can’t blame them, who wants to support old versions of IE anyway?)

However, there is some good news if you still need to use ExCanvas: Willis at badsyntax.co came up with a different solution that doesn’t require modifying the source code. I recommend using his technique, as it’s cleaner and allows you to use a CDN copy of ExCanvas instead of maintaining your own copy.

Image-Free Progress Bar using MooTools and Canvas

As part of my ongoing experiments with <canvas>, I decided to convert an image-based progress bar to an image-free canvas-based system. I just finished whipping up a proof-of-concept; it uses MooTools to generate the canvas and CSS code. No images were harmed in the making of this progress bar.

More info later (time permitting)

Modal.js updated

Shortest post ever: Just wanted to mention my Modal.js class is still a work-in-progress. Today I made a few updates, most notably to some CSS handling and to the styling of the ‘close’ button (looks much more sophisticated now). Check it out.