/*******************************
* File: cbbg.js
* Desc: Script for blog gadgets.
* Auth: Kevin Machin.
*******************************/

/*jsl:import ../Scripts/cb.js       */
/*jsl:import ../Scripts/exlinks.js  */

// Loading
cbOnLoad (cbBgLoad);
var gsCbBgClass = "cbbg";

function cbBgLoad ()
{
    var iGadgets = 0, aoGadget = new Array;
    var sErrMsg = "ErrMsg";

    try
    {
        // Set up array of gadgets
        gadgetAdd ("CbBgWiP",     cbBgWiP);
        gadgetAdd ("CbBgGMatrix", cbBgGMatrix);
        gadgetAdd ("CbBgContact", cbBgContact);
        gadgetAdd ("CbBgReading", cbBgReading);
        gadgetAdd ("CbBgExLinks", cbBgExLinks);
        
        // Load the gadgets
        gadgetsLoad ();

        // Load any sub-documents
        cbSubDocsLoad ();

        // Set up resizing for in-line frames
        cbOnResize (cbIFrameResize);
        cbIFrameResize (null);
    }
    catch (e)
    {
        cbErrMsg ("Failed to display gadget!", e, null, sErrMsg);
    }

    ///////////////////////////////////////////////////////
    // Priv: gadgetAdd
    // Desc: Add a gadget to the array.
    // Args: psId      -- Identifier for container element.
    //       pfnLoader -- Loader function.
    ///////////////////////////////////////////////////////

    function gadgetAdd (psId, pfnLoader)
    {
        var oGadget;

        // Create gadget and add it to the array
        oGadget = new gadget (psId, pfnLoader);
        aoGadget[iGadgets] = oGadget; iGadgets++;
    
        // Constructor
        function gadget (psId, pfnLoader)
        {
            this.sContainer = psId;
            this.oContainer = null;
            this.sMessage = sErrMsg = psId + "ErrMsg";
            this.oMessage = null;
            this.pfnLoader = pfnLoader;
        }
        // End of gadget (constructor)
    }
    // End of gadgetAdd

    //////////////////////////////////////////////////
    // Priv: gadgetsLoad
    // Desc: Attempt to load the gadgets in the array.
    //////////////////////////////////////////////////
    
    function gadgetsLoad ()
    {
        var iG, oG, oContainer, oMessage, pfnLoader, oGadget;
        
        // Iterate through gadgets
        for (iG = 0; iG < iGadgets; iG++)
        {
            // Consider the next gadget
            oG = aoGadget[iG];
            sErrMsg = oG.sMessage;
            oContainer = document.getElementById (oG.sContainer);
            if (oContainer !== null)
            {
                // Save container element
                oG.oContainer = oContainer;
                
                // Add "loading" message to container element
                oG.oMessage = oMessage = cbTag ("p", "Loading...");
                oMessage.className = gsCbBgClass + "error";
                oMessage.id = oG.sMessage;
                oContainer.appendChild (oMessage);
            }
        }
        
        // Second pass to load the gadgets
        for (iG = 0; iG < iGadgets; iG++)
        {
            oG = aoGadget[iG];
            sErrMsg = oG.sMessage;
            oContainer = oG.oContainer;
            if (oContainer !== null)
            {
                // Is there a loader
                pfnLoader = oG.pfnLoader;
                if (pfnLoader !== null)
                {
                    // Call the loader
                    try
                    {
                        oGadget = pfnLoader ();
                    }
                    catch (e)
                    {
                        oGadget = null;
                        cbErrMsg ("Failed to display gadget!", e, null, sErrMsg);
                    }
                    if (oGadget !== null)
                    {
                        // Attach the gadget to the container
                        oContainer.appendChild (oGadget);

                        // Remove "loading" message
                        oMessage = oG.oMessage;
                        if (oMessage !== null)
                        {
                            oContainer.removeChild (oMessage);
                        }
                    }
                }
            }
        }
    }
    // End of gadgetsLoad
}
// End of cbBgLoad

/////////////////////////////////
// Func: cbBgContact
// Desc: Display contact details.
// Retn: Gadget object reference.
/////////////////////////////////

function cbBgContact ()
{
    var oTable, oLink;

    // Create table
    oTable = cbTable ();
    oTable.className = "cbbg";

    // E-mail
    oLink = cbLink ("E-Mail");
    oLink.href = "#";
    oLink.title = "Send me a message...";
    oTable.row ("E", oLink);

    // Blogger
    oLink = cbLink ("Blogger", "http://www.blogger.com/profile/16290852640283747439");
    oLink.title = "See my blogger profile...";
    oTable.row ("B", oLink);

    // Web site
    oLink = cbLink ("Web site", "http://www.kevinmachin.pwp.blueyonder.co.uk");
    oLink.title = "Visit my web site...";
    oTable.row ("W", oLink);

    // Facebook
    oLink = cbLink ("Faceache", "http://www.facebook.com/dark.skipper");
    oLink.title = "My facebook profile...";
    oTable.row ("F", oLink);

    // Twitter
    oLink = cbLink ("@DarkSkipper", "http://www.twitter.com/DarkSkipper");
    oLink.title = "See my twitter profile...";
    oTable.row ("T", oLink);

    return (oTable);
}
// End of cbBgContact

///////////////////////////////////
// Func: cbBgWiP
// Desc: Display works in progress.
// Retn: Gadget object reference.
///////////////////////////////////

function cbBgWiP ()
{
    var oTable, oCell, i, iC, iCount, iRows = 0, oDB, oProj;

    // Open projects database
    oDB = new cbProjects ("");
    iCount = oDB.iCountProj;
    iC = oDB.iCountActive;

    // Create a table to hold the meters
    oTable = cbTable ();
    oTable.className = gsCbBgClass + "wip";
    oTable.row ();
    if (iC === 0)
    {
        oTable.cell ("No active projects.");
    }
    else if (iC > 2)
    {
        iC = 2;
    }
    for (i = 0; i < iC; i++)
    {
        oCell = oTable.cell ("th", "Title");
        oCell.style.textAlign = "Left";
        if (i === 1)
        {
            oCell.style.borderLeft = "2px Solid #D3D3D3"; // LightGray
        }
        oTable.cell ("th", "Count");
        oTable.cell ("th", "Expected");
        oCell = oTable.cell ("th", "Progress");
        oCell.colSpan = 2;
        oCell = oTable.cell ("th", "Phase");
        if (i === 0)
        {
            oCell.style.borderRight = "2px Solid Gray";
        }
    }

    // Display data
    for (i = 0; i < iCount; i++)
    {
        oProj = oDB.aoProj[i];
        if (oProj.bActive)
        {
            meterRow (oProj);
        }
    }
    
    // Finish off
    if ((iC > 1) && ((iRows % 2) !== 0))
    {
        oCell = oTable.cell ();
        oCell.colSpan = 6;
        oCell.style.borderLeft = "2px Solid #D3D3D3"; // LightGray
    }

    ////////////////////////////////
    // Priv: meterRow
    // Desc: Add a row to the table.
    // Args: oPj -- Project.
    ////////////////////////////////

    function meterRow (oPj)
    {
        var bNewRow, iW, iM;
        
        // Start new row if necessary
        iRows++;
        bNewRow = ((iRows % 2) === 0) ? false : true;
        if (bNewRow)
        {
            oTable.row ();
        }
        
        // Title
        oCell = oTable.cell (oPj.sTitle);
        if (!bNewRow)
        {
            oCell.style.borderLeft = "2px Solid #D3D3D3"; // LightGray
        }

        // Count and maximum
        iW = oPj.iWords;
        iM = oPj.iMax;
        oCell = oTable.number (iW);
        oCell.className = gsCbBgClass + "wii";
        oCell = oTable.number (iM);
        oCell.className = gsCbBgClass + "wii";
        
        // Percentage
        oCell = oTable.cell (cbPercent (iW, iM) + "%");
        oCell.className = gsCbBgClass + "wii";
        oCell.style.borderRight = "0 None";
        
        // Bar
        oCell = oTable.bar (iW, iM);
        oCell.style.borderLeft = "0 None";

        // Phase
        oCell = oTable.cell (oPj.sPhase + " (" + oPj.iIter + ")");
        if (bNewRow)
        {
            oCell.style.borderRight = "2px Solid Gray";
        }
    }
    // End of meterRow

    return (oTable);
}
// End of cbBgWiP

/////////////////////////////////////////////////////
// Func: cbBgReading
// Desc: Display the "currently reading" book covers.
// Retn: Gadget object reference.
/////////////////////////////////////////////////////

function cbBgReading ()
{
    var aoBook = new Array, iBooks = 0;
    var oTable, oBook, i, iC, iW, iRS, iCS;
    var sClass = gsCbBgClass + "cr";

    // Set up list of book covers (most recent first)
    /*
    bookAdd ("Week_in_December",             "1.bp.blogspot.com/_78Do_yAqJDI/S2xL2-KjZwI/AAAAAAAAAbA/eJaTj5lFCtk");
    bookAdd ("Meltdown",                     "1.bp.blogspot.com/_78Do_yAqJDI/S2xL2rtMEKI/AAAAAAAAAa4/4ZtnwhG2bVk");
    bookAdd ("Relentless",                   "2.bp.blogspot.com/_78Do_yAqJDI/S2ApLN8f6iI/AAAAAAAAAaw/wxS08EjQTaM");
    bookAdd ("Bad_Science",                  "4.bp.blogspot.com/_78Do_yAqJDI/S2AovBt1tnI/AAAAAAAAAag/A_Kz9CN62S8");
    */
    bookAdd ("Beggars_Banquet",              "3.bp.blogspot.com/_78Do_yAqJDI/S2Ao88GFCjI/AAAAAAAAAao/rzXyeslofWc");
    bookAdd ("Perfect_Alibis",               "1.bp.blogspot.com/_78Do_yAqJDI/SzyP3gScrXI/AAAAAAAAAaA/zDoMuBxhfsw");
    bookAdd ("Matter",                       "2.bp.blogspot.com/_78Do_yAqJDI/SwP_-Rif6OI/AAAAAAAAAZo/u7MFuY7vjdQ");
    bookAdd ("Exit_Music",                   "3.bp.blogspot.com/_78Do_yAqJDI/Sxjo-f-rmxI/AAAAAAAAAZw/2yLtZgNQp-c");
    bookAdd ("Heaven_can_Wait",              "4.bp.blogspot.com/_78Do_yAqJDI/SugjorxrzXI/AAAAAAAAAZg/SPzlecpdhFg");
    bookAdd ("Secret_Shoppers_Revenge",      "2.bp.blogspot.com/_78Do_yAqJDI/SreZDQ-DGPI/AAAAAAAAAYk/2F2pto7O-Vs");
    bookAdd ("Your_Heart_Belongs_to_Me",     "3.bp.blogspot.com/_78Do_yAqJDI/SqdtTbKAYMI/AAAAAAAAAYc/cjPEp1Qf4qY");
    bookAdd ("Double_Cross",                 "1.bp.blogspot.com/_78Do_yAqJDI/SoxHZzBP_OI/AAAAAAAAAYU/JMuKSjwbz5Q");
    bookAdd ("First_Drop",                   "2.bp.blogspot.com/_78Do_yAqJDI/Snf17_BZIuI/AAAAAAAAAYM/kkunCFIbAl8");
    bookAdd ("HowNotToWriteANovel",          "2.bp.blogspot.com/_78Do_yAqJDI/Sm26EcUVGVI/AAAAAAAAAYE/7bUTUG5NTiU");
    bookAdd ("Mollys_Millions",              "4.bp.blogspot.com/_78Do_yAqJDI/SlzK5DzpkLI/AAAAAAAAAVE/UPrF0-BzmTg");
    bookAdd ("House_of_Thunder",             "1.bp.blogspot.com/_78Do_yAqJDI/Sk8BIIkjwSI/AAAAAAAAAU8/qye0klYLzm4");
    bookAdd ("Sleeping_Doll",                "1.bp.blogspot.com/_78Do_yAqJDI/SizaKt78LoI/AAAAAAAAAUk/34hMNT2pJQ0");
    bookAdd ("Nano_Flower",                  "1.bp.blogspot.com/_78Do_yAqJDI/Sf6jbpumAtI/AAAAAAAAATc/CwyP1rHHBJU");
    bookAdd ("Black_Boxes",                  "1.bp.blogspot.com/_78Do_yAqJDI/SeTo8oH4DyI/AAAAAAAAATU/5Mt7FNBZvq0");
    bookAdd ("Not_the_End_of_the_World",     "2.bp.blogspot.com/_78Do_yAqJDI/ScQDzCjX2jI/AAAAAAAAATM/wHRlsyApa0Y");
    bookAdd ("Dead_Mans_Footsteps",          "3.bp.blogspot.com/_78Do_yAqJDI/SbFW3MVPNLI/AAAAAAAAAS0/kuCJ121E76o");
    bookAdd ("Walking_on_Glass",             "1.bp.blogspot.com/_78Do_yAqJDI/SYdHO0Ew8kI/AAAAAAAAASs/q1p8LP_A5oU");
    bookAdd ("Temporal_Void",                "4.bp.blogspot.com/_78Do_yAqJDI/SVC_-M75IWI/AAAAAAAAAP4/B-Yw6MOeABM");
    bookAdd ("When_Will_There_Be_Good_News", "2.bp.blogspot.com/_78Do_yAqJDI/SWptpKpheaI/AAAAAAAAAQg/lE1glLnn2LI");
    bookAdd ("Over_You",                     "lh6.ggpht.com/_78Do_yAqJDI/SUqatJbrj-I/AAAAAAAAAPo/ovGLMVdhuWo");

    // Start table
    oTable = cbTable ();
    oTable.className = sClass;
    oTable.row ();

    // Iterate over the books
    for (i = iC = 0; i < iBooks; i++)
    {
        // Start new row if necessary
        if (iC >= 8)
        {
            if (i < 5)
            {
                iC = 4;
            }
            else
            {
                iC = 0;
            }
            oTable.row ();
        }

        // Decide on size of book image
        oBook = aoBook[i];
        iRS = iCS = 1;
        if (i === 0)
        {
            // First book biggest
            iW = 128;
            iC += 4;
            iRS = 2;
            iCS = 4;
        }
        else if (i < 5)
        {
            // Next four books medium sized
            iW = 64;
            iC += 2;
            iCS = 2;
        }
        else
        {
            // All other books small
            iW = 32;
            iC++;
        }

        // Render the book image
        imgWrite (oBook, iW, iRS, iCS);
    }

    // Complete the row
    for (iW = 32 ; iC < 8; iC++)
    {
        imgWrite (null, iW);
    }

    /////////////////////////////////////
    // Priv: bookAdd
    // Desc: Constructor for book object.
    // Args: psFile -- Image file name.
    //       psLocn -- File location.
    /////////////////////////////////////
    
    function bookAdd (psFile, psLocn)
    {
        var oBook;
        
        // Create new book and add it to the array
        oBook = new book (psFile, psLocn);
        aoBook[iBooks] = oBook; iBooks++;
        
        // Constructor
        function book (psFile, psLocn)
        {
            // Location
            if ((psLocn !== "") && (psLocn.indexOf (":") < 0))
            {
                psLocn = "http://" + psLocn;
            }
            this.sURL = psLocn;

            // File name
            if ((psFile.indexOf (".") < 0) && (psFile.length > 1))
            {
                psFile += ".jpg";
            }
            this.sImg = psFile;
        }
        // End of book (constructor)
    }
    // End of bookAdd

    //////////////////////////////////////////////////
    // Priv: imgWrite
    // Desc: Render an image.
    // Args: oBook -- Book object.
    //       iSize -- Size in pixels.
    //       iRows -- Number of table rows to span.
    //       iCols -- Number of table columns to span.
    //////////////////////////////////////////////////
    
    function imgWrite (oBook, iSize)
    {
        var sLarge, sSmall, sURL, sImg, oCell, oLink, oImg;
        
        // Get cell spanning parameters
        var iRows = (arguments.length > 2) ? arguments[2] : 1;
        var iCols = (arguments.length > 3) ? arguments[3] : 1;

        // Get book details
        if (oBook !== null)
        {
            sURL = oBook.sURL;
            sImg = oBook.sImg;
        }
        else
        {
            sURL = sImg = "";
        }

        // Directories for different sizes
        if (sURL.indexOf ("http://") >= 0)
        {
            sLarge = "/s1600-h/";
            sSmall = "/s400/";
        }
        else
        {
            sLarge = sSmall = "/";
        }

        // Render the table cell with the image
        oCell = oTable.cell ();
        oCell.rowSpan = iRows;
        oCell.colSpan = iCols;
        if (sURL === "")
        {
            if (sImg === "")
            {
                sImg = (cbDebugMask ()) ? "x" : "&nbsp;";
            }
            oCell.innerHTML = sImg;
        }
        else
        {
            oImg = cbTag ("img");
            oImg.className = sClass;
            oImg.style.maxWidth = oImg.style.width = iSize + "px";
            oImg.style.maxHeight = Math.floor ((iSize * 3) / 2) + "px";
            oImg.src = sURL + sSmall + sImg;
            oImg.alt = "Book cover";
            oLink = cbLink (oImg, sURL + sLarge + sImg, "title=Click for bigger picture...");
            oLink.className = sClass;
            oLink.target = "_blank";
            oCell.appendChild (oLink);
        }
    }
    // End of imgWrite
    
    // Return gadget object
    return (oTable);
}
// End of cbBgReading

//////////////////////////////////
// Func: cbBgGMatrix
// Desc: Display the genre matrix.
// Retn: Gadget object reference.
//////////////////////////////////

function cbBgGMatrix ()
{
    var asGenre = new Array, aoAuth = new Array;
    var i, iA, iG, sL, sT, oT, oC, oE;

    var sClass = gsCbBgClass + "gm"; // Class name

    // Set up genre data
    i = 0;
    /*jsl:ignore*/
    asGenre[i++] = "Children";
    asGenre[i++] = "Crime";
    asGenre[i++] = "Horror";
    asGenre[i++] = "Humour";
    asGenre[i++] = "Poetry";
    asGenre[i++] = "Romance";
    asGenre[i++] = "Saga";
    asGenre[i++] = "SF/Fantasy";

    // Set up author data
    i = 0;
    aoAuth[i++] = new author ("Annie");
    aoAuth[i++] = new author ("Captain Black");
    aoAuth[i++] = new author ("Caroline");
    aoAuth[i++] = new author ("Denise");
    aoAuth[i++] = new author ("Fiona");
    aoAuth[i++] = new author ("Lane");
    aoAuth[i++] = new author ("Leatherdykeuk");
    aoAuth[i++] = new author ("Shaun");
    /*jsl:end*/

    // Fill in as we go...
    aoAuth[1].asGenre[0] = "2008/12/genre-matrix-children-captain";
    aoAuth[1].asGenre[1] = "2008/10/crime-captain";
    aoAuth[1].asGenre[2] = "2008/08/genre-matrix-horror-captain";
    aoAuth[1].asGenre[7] = "2008/12/story-about-toy-duck";
    aoAuth[4].asGenre[2] = "2008/08/spanish-house-edited-if-bloggers";
    aoAuth[5].asGenre[0] = "2008/08/genre-matrix-children-lane";
    aoAuth[5].asGenre[7] = "2008/11/dreamail-lane";
    aoAuth[6].asGenre[0] = "2008/08/childrens-story-kias-lunch-by-rachel";
    aoAuth[6].asGenre[1] = "2008/10/genre-matrix-crime-rachel";

    // Create for table
    oT = cbTable ();
    oT.className = sClass;
    oT.row ();
    oC = oT.cell ("th", "Author");
    oC.rowSpan = 2;
    oC.style.verticalAlign = "Bottom";
    oC = oT.cell ("th", "Genre");
    oC.colSpan = 8;
    oT.row ();
    for (iG = 0; iG < asGenre.length; iG++)
    {
        oC = oT.cell (asGenre[iG]);
        oC.style.fontStyle = "Italic";
    }

    // Iterate over authors
    for (iA = 0; iA < aoAuth.length; iA++)
    {
        oT.row ();
        oC = oT.cell (aoAuth[iA].sName);
        oC.style.textAlign = "Left";
        for (iG = 0; iG < asGenre.length; iG++)
        {
            sL = aoAuth[iA].asGenre[iG];
            if (sL === "")
            {
                oE = "&nbsp;";
            }
            else
            {
                sT =  "title=Click to read " + asGenre[iG].toLowerCase ();
                sT += " story by " + aoAuth[iA].sName + "...";
                oE = cbTag ("a", "X", sT);
                oE.href = "http://cloud-line.blogspot.com/" + sL + ".html";
                oE.className = sClass;
            }
            oC = oT.cell (oE);
        }
    }

    ////////////////////////////////////////
    // Priv: author
    // Desc: Constructor for author objects.
    // Args: psN -- Author's name.
    // Retn: Object.
    ////////////////////////////////////////

    function author (psN)
    {
        var iG;

        this.sName = psN;
        this.asGenre = new Array ();
        for (iG = 0; iG < asGenre.length; iG++)
        {
            if ((iG + 1) >= arguments.length)
            {
                this.asGenre[iG] = "";
            }
            else
            {
                this.asGenre[iG] = arguments[iG + 1];
            }
        }
    }
    // End of author
    
    return (oT);
}
// End of cbBgGMatrix

/////////////////////////////////////////
// Func: cbBgExLinks
// Desc: Display lists of external links.
// Retn: Gadget object reference.
/////////////////////////////////////////

function cbBgExLinks ()
{
    var oGadget, oDB, aoExLink;
    var S_AUTH = 1, S_TYPE = 2, S_CAT = 4;

    // FIXME: Temporary testing code.
    //gbCbLegacyCode = false;

    // Create gadget container
    oGadget = cbTag ("div");
    oGadget.className = gsCbBgClass;
    oGadget.style.margin = "0";
    oGadget.style.border = "0 None";
    oGadget.style.padding = "0";
    
    // Initialise database
    oDB = new cbExLinks ();
    aoExLink = oDB.aoLink;

    // Create the lists
    exList ("Fiction",  "Blog",        "Fiction", S_AUTH);
    exList ("Blogs",    "Blog",        "Group",   S_CAT);
    exList ("",         "Blog",        "Personal",S_AUTH);
    exList ("Links",    "Web,Facebook","*",       S_TYPE);
    exList ("Resources","Resource",    "*",       S_CAT);

    /////////////////////////////////////////////////
    // Func: exList
    // Desc: Produce a list of links.
    // Args: psHeading -- List heading.
    //       psTypes   -- Comma separated types list.
    //       psCats    -- Categories list.
    //       iShow     -- Flags for display fields.
    /////////////////////////////////////////////////

    function exList (psHeading, psTypes, psCats, iShow)
    {
        var oTag, oList, oItem, iL, oL, sType, s, sTitle, oA;

        // Heading
        if (psHeading !== "")
        {
            oTag = cbTag ("h5", psHeading);
            oTag.className = gsCbBgClass;
            oGadget.appendChild (oTag);
        }

        // Start list
        oList = cbTag ("ul");
        oList.className = gsCbBgClass;
        oGadget.appendChild (oList);

        // Iterate through items
        for (iL = 0; iL < oDB.iCountLinks; iL++)
        {
            // Type we want to display?
            oL = aoExLink[iL];
            sType = oL.sType;
            if (psTypes.indexOf (sType) >= 0)
            {
                // Category we want?
                if ((psCats == "*") || (psCats.indexOf (oL.sCat) >= 0))
                {
                    // Create list item
                    oItem = cbTag ("li");
                    oItem.className = gsCbBgClass;
                    oList.appendChild (oItem);

                    // Author
                    if (iShow & S_AUTH)
                    {
                        s = oL.sAuthor + ": ";
                        oTag = document.createTextNode (s);
                        oItem.appendChild (oTag);
                    }

                    // Description (hover-over)
                    sTitle = oL.sDesc;
                    if (sTitle === "")
                    {
                        sTitle = "Click to visit...";
                    }

                    // Link
                    oA = cbTag ("a", oL.sName, "target=_blank");
                    oA.href = oL.sURL;
                    oA.title = sTitle;
                    oA.className = gsCbBgClass;
                    oItem.appendChild (oA);

                    // Type
                    s = "";
                    if (iShow & S_TYPE)
                    {
                        s += " (" + oL.sType + ")";
                    }

                    // Category
                    if (iShow & S_CAT)
                    {
                        s += " (" + oL.sCat + ")";
                    }

                    if (s !== "")
                    {
                        oTag = document.createTextNode (s);
                        oItem.appendChild (oTag);
                    }
                }
            }
        }
    }
    // End of exList
    
    return (oGadget);
}
// End of cbBgExLinks

////////////////////////////////////////////////
// Func: cbProjects
// Desc: Database of projects and their details.
// Args: psDire -- Current directory.
// Retn: Database object.
////////////////////////////////////////////////

function cbProjects (psDire)
{
    var iCountWord = 0, iCountMax = 0, iCountProj = 0, aoProj = new Array;
    var iCountFilt = 0, iCountParent = 0, iCountActive = 0, aoFilt, i, iP, oP, oProj;
    var sDesc, sJason;

    // Handle parameters
    if (psDire !== "")
    {
        psDire += "/";
    }

    // Set up the data
    // Novels
    projAdd ("BotR","","Blood on the Rooftops","Novel","Crime","Creation",1, 53386, 85000,"Blood/botr");
    oProj =
    projAdd ("TBAF","","The Bridge Across Forever","Novel","SF","Creation",1, 35655, 56000,"Bridge/baf");
    partAdd (oProj, "The Camping Party",  12443, 24886, "");
    partAdd (oProj, "Natural Science",     8943, 11498, "");
    partAdd (oProj, "Building Bridges", 14269, 21404, "");
    oProj =
    projAdd ("Dril","","Driller","Novel","SF","Creation"  ,1,  4301, 60000,"Driller/driller");
    partAdd (oProj, "One", 4301, 20000, "");
    partAdd (oProj, "Two",    0, 20000, "");
    partAdd (oProj, "Three",  0, 20000, "");
    oProj =
    projAdd ("Ins" ,"","Insight","Novel","SF","Refinement",2,125026,125026,"Insight/insight"); // 40/45 chap synopses
    partAdd (oProj, "Main Stream",         74491, 74491, "");
    partAdd (oProj, "Adventure Stream",    57951, 57951, "");
    partAdd (oProj, "Extras &amp; Unplaced", 204,   204, "");

    // Serials
    projAdd ("Ast","","Asteroid"      ,"Serial" ,"SF"   ,"Creation",1,29191,59000,"Asteroid/ast_nav");
    projAdd ("KaM","","Kim &amp; Mark","Serial" ,"Crime","Creation",1,10712,14000,"Kim_Mark/km_nav");

    // Novellas
    projAdd ("FiDM","","Fugue in D Minus"      ,"Novella","Thriller","Creation",2,24447,24447,"Fugue/fugue_nav");
    projAdd ("SeeS","","Seeing Sharp"          ,"Novella","Thriller",""        ,0,    0,    0,"");
    projAdd ("TWOH","","The Wild One's Hideout","Novella","SF"      ,"Creation",2,16649,16649,"Twoh_Sample");

    // Short stories
    projAdd ("Harr","BotR","Harry"              ,"Short","Crime"      ,"Creation"  ,1,2109,2109,"Blood/botr_harry");
    projAdd ("GBak","BotR","Going Back"         ,"Short","Crime"      ,"Creation"  ,1, 376, 376,"Blood/botr_goingback");
    projAdd ("TPtN","TBAF","The Path to Nowhere","Short","SF"         ,"Creation"  ,1,1617,1617,"Bridge/baf_path");
    projAdd ("Cel1","TBAF","The Celebration (1)","Short","SF"         ,"Creation"  ,1, 464, 464,"Bridge/baf_celebration");
    projAdd ("Cili","TBAF","Cilicia"            ,"Short","SF"         ,"Creation"  ,1,3242,3242,"Bridge/baf_cilicia");
    projAdd ("Trop","Dril","Tropic"             ,"Short","SF"         ,"Creation"  ,1, 751, 751,"Driller/dr_tropic");
    projAdd ("Refl","Ins" ,"Reflections"        ,"Short","SF"         ,"Refinement",2,1076,1076,"Insight/is_reflections");
    projAdd ("IMap","Ins" ,"Map"                ,"Map"  ,"SF"         ,"Refinement",2,   0,   0,"../Images/insight_map.gif");
    projAdd ("Amb" ,""    ,"Ambush"             ,"Short","Children"   ,"Creation"  ,1, 334, 334,"Shorts/ambush");
    projAdd ("Cel2",""    ,"The Celebration (2)","Short",""           ,"Creation"  ,1, 846, 846,"Shorts/celebration");
    projAdd ("Hood",""    ,"Hoody"              ,"Short","Thriller"   ,"Submission",2,1869,1869,"");
    projAdd ("OtC" ,""    ,"On the Clock"       ,"Short",""           ,"Creation"  ,1, 807, 807,"Shorts/on_the_clock");
    projAdd ("OoR" ,""    ,"Out of Reach"       ,"Short","SF"         ,"Creation"  ,1, 391, 391,"Shorts/out_of_reach");
    projAdd ("PJ"  ,""    ,"Practical Joke"     ,"Short","SF"         ,"Creation"  ,1,3058,3058,"Shorts/practical_joke");
    projAdd ("Stil",""    ,"Still"              ,"Short","Non-Fiction","Creation"  ,1, 271, 271,"Shorts/still");
    projAdd ("SP"  ,""    ,"Street Prejudice"   ,"Short","SF"         ,"Creation"  ,1,3463,3463,"Shorts/street_prejudice");
    projAdd ("UfS" ,""    ,"Unlucky for Some"   ,"Short","Crime"      ,"Creation"  ,1, 824, 824,"Shorts/unlucky");

    // Poetry
    projAdd ("AoD" ,"","Articles of Desire"  ,"Poem","","Creation",1,134,134,"Poems/aod");
    projAdd ("Crus","","Cruising"            ,"Poem","","Creation",1,490,490,"Poems/cruising_poem");
    projAdd ("Help","","I'm Here To Help You","Poem","","Creation",1,240,240,"Poems/help_poem");
    projAdd ("NMfW","","Not Made From Wheat" ,"Poem","","Creation",1,  1,  1,"Poems/notwheat");
    projAdd ("Torm","","Tormato"             ,"Poem","","Creation",1,100,100,"Poems/tormato_poem");

    // Co-author's link
    sJason =  "<a title=\"See Jason's facebook profile...\" ";
    sJason += "href=\"http://www.facebook.com/profile.php?id=691450533\">Jason Harris</a>";

    // Add descriptions for novels
    projDesc ("BotR", "A complex crime thriller set in a small town in the UK.");
    projDesc ("TBAF", "Epic saga, spanning five centuries.");
    projDesc ("Dril", "Working title. Rather dormant project.");
    sDesc =  "A rebel and his clan struggle against the tyranny of the oppressive Atlas company, ";
    sDesc += "who dominate the economy and governance of a post-crisis UK, often with military force. ";
    sDesc += "But Atlas have a secret weakness... Originally co-authored with, now being edited by ";
    sDesc += sJason + ".";
    projDesc ("Ins" , sDesc, false);

    // Add descriptions for serials
    projDesc ("Ast" ,"Episodic blog story.");
    projDesc ("KaM" ,"A weekly prompt-driven story.");

    // Add descriptions for novellas
    sDesc =  "Kerry Mason is having problems with her memory. ";
    sDesc += "She keeps waking up in strange places, not knowing how she got there...";
    projDesc ("FiDM", sDesc);
    projDesc ("SeeS", "Planned for the future.");
    projDesc ("TWOH", "What is Lark Island's ancient secret? Only the Wild One knows.");

    // Add descriptions for shorts
    projDesc ("Amb" , "A brief war story.");
    projDesc ("Cel1", "Extract from part two.");
    projDesc ("Cel2", "Revenge is a dish best served drunk. Or is it?");
    projDesc ("Cili", "Extract from part three, featuring <i>The Geography Class</i>.");
    projDesc ("GBak", "Extract from a character's blog diary.");
    projDesc ("Harr", "Terrorist bomber's first strike.");
    projDesc ("Hood", "A micro-thriller set on a London commuter train.");
    projDesc ("IMap", "The geography for the main events of Insight.");
    projDesc ("OtC" , "One of Jo's Weekly Workout exercises (week eight).");
    projDesc ("OoR",  "Story inspired by Hawkwind's <i>Fable of a Failed Race</i>.");
    projDesc ("TPtN", "Extract from part one.");
    projDesc ("PJ"  , "A story about a toy duck.");
    projDesc ("Refl", "Is it magic or is it technology?");
    projDesc ("Stil", "A mountain walk in the winter.");
    sDesc =  "A high street adventure. Co-authored with ";
    sDesc += sJason + ".";
    projDesc ("SP"  , sDesc);
    projDesc ("Trop", "Two men investigate a mysterious occurance in the Australian desert.");
    projDesc ("UfS" , "A Valentine's day special.");

    // Add descriptions for poems
    projDesc ("AoD", "A somewhat cryptic poem.");
    projDesc ("Crus","A short poem about a bird. Or is it?");
    projDesc ("Help","Can you escape the 'system'?");
    projDesc ("NMfW","...but made from something else entirely...");
    projDesc ("Torm","Tempting but tormenting.");

    // Reset filter
    filterProj (null);

    // Set parent pointers and counters
    for (iP = 0; iP < iCountProj; iP++)
    {
        oProj = aoProj[iP];
        if (oProj.bActive)
        {
            iCountActive++;
        }
        if (oProj.sParent === "")
        {
            iCountParent++;
        }
        else
        {
            for (i = 0; i < iCountProj; i++)
            {
                oP = aoProj[i];
                if (oProj.sParent === oP.sCode)
                {
                    oProj.oParent = oP;
                    break;
                }
            }
        }
    }

    //////////////////////////////////////////////////////
    // Priv: projAdd
    // Desc: Add a new project to the array.
    // Args: psCode   -- Abbreviation code for project.
    //       psParent -- Code for parent project.
    //       psTitle  -- Title of project.
    //       psClass  -- Class of project.
    //       psGenre  -- Genre of project.
    //       psPhase  -- Project's development phase.
    //       iIter    -- Project's iteration number.
    //       iWords   -- Current word count.
    //       iMax     -- Expected maximum word count.
    //       psFile   -- File name (for viewing/download).
    // Retn: Project object reference.
    //////////////////////////////////////////////////////

    function projAdd (psCode, psParent, psTitle, psClass, psGenre, psPhase, iIter, iWords, iMax, psFile)
    {
        var oProj;

        // Create new object and add it to the array
        oProj = new project (psCode, psParent, psTitle, psClass, psGenre, psPhase, iIter, iWords, iMax, psFile);
        aoProj[iCountProj] = oProj; iCountProj++;

        // Update statistics
        iCountWord += oProj.iWords;
        iCountMax  += oProj.iMax;

        // Constructor
        function project (psA, psO, psT, psC, psG, psP, iI, iW, iM, psF)
        {
            // Adjust file name for directory
            if (psF !== "")
            {
                psF = psDire + psF;
            }

            // Fill in results
            this.bActive = false;
            this.sCode   = psA;
            this.sParent = psO;
            this.oParent = null;
            this.sTitle  = psT;
            this.sFile   = psF;
            this.sClass  = psC;
            this.sGenre  = psG;
            this.sDesc   = "<i style=\"color:maroon\">No description available</i>";
            this.sPhase  = psP;
            this.iIter   = iI;
            this.iWords  = iW;
            this.iMax    = iM;
            this.aoPart  = null;
        }
        // End of project (constructor)

        return (oProj);
    }
    // End of projAdd

    //////////////////////////////////////////
    // Priv: projDesc
    // Desc: Add a description to a project.
    // Args: psCode  -- Project code.
    //       psDesc  -- Description.
    //       bActive -- Project active or not?
    //////////////////////////////////////////

    function projDesc (psCode, psDesc)
    {
        var iP, oProj;

        // Handle parameters
        var bActive = (arguments.length > 2) ? arguments[2] : false;

        // Locate the project
        for (iP = 0; iP < iCountProj; iP++)
        {
            oProj = aoProj[iP];
            if (oProj.sCode === psCode)
            {
                // Set description and activity flag
                oProj.bActive = bActive;
                oProj.sDesc = psDesc;
                break;
            }
        }
    }
    // End of projDesc

    ///////////////////////////////////////////
    // Priv: partAdd
    // Desc: Add a part to a project.
    // Args: oProj   -- Project to add part to.
    //       psTitle -- Title of part.
    //       iWords  -- Word count.
    //       iMax    -- Maximum word count.
    //       psDesc  -- Description.
    ///////////////////////////////////////////

    function partAdd (oProj, psTitle, iWords, iMax, psDesc)
    {
        var oPart;

        // Create new part and add it to the project's array
        oPart = new part (psTitle, psDesc, iWords, iMax);
        if (oProj.aoPart === null)
        {
            oProj.aoPart = new Array;
        }
        oProj.aoPart.push (oPart);

        // Constructor
        function part (psT, psD, iW, iM)
        {
            this.sTitle = psT;
            this.sDesc  = psD;
            this.iWords = iW;
            this.iMax   = iM;
        }
        // End of part (constructor)
    }
    // End of partAdd

    ///////////////////////////////////////////
    // Func: filterProj
    // Desc: Filter the projects.
    // Args: psKey  -- Key to filter on.
    //       psSift -- List of matches to sift.
    ///////////////////////////////////////////

    function filterProj (psKey, psSift)
    {
        var i, oProj, sValue;

        // Key given?
        if ((psKey === null) || (psKey === ""))
        {
            // Reset filter
            this.aoFilt = aoFilt = aoProj;
            iCountFilt = iCountProj;
        }
        else
        {
            // New filter results
            this.aoFilt = aoFilt = new Array;
            iCountFilt = 0;

            // Iterate through projects
            for (i = 0; i < iCountProj; i++)
            {
                // Sift
                oProj = aoProj[i];
                switch (psKey)
                {
                case "Code":
                    sValue = oProj.sCode;
                    break;
                case "Parent":
                    sValue = oProj.sParent;
                    break;
                case "Class":
                    sValue = oProj.sClass;
                    break;
                default:
                    sValue = null;
                    break;
                }

                // Add sifted items to results
                if ((sValue !== null) && (sValue !== "") && (psSift.indexOf (sValue) >= 0))
                {
                    aoFilt[iCountFilt] = oProj;
                    iCountFilt++;
                }
            }
        }

        // Update count
        this.iCountFilt = iCountFilt;
    }
    // End of filterProj

    /////////////////////////////////////////////
    // Func: listProj
    // Desc: Display writing projects.
    // Args: psId -- Element to put display into.
    //       ...  -- Names of required columns.
    /////////////////////////////////////////////

    function listProj (psId)
    {
        var oPage, oTable, oCell, iP, oProj;
        var i, iCols, asCol = new Array, sCol, tValue, sClass;

        // Does page element exist?
        oPage = document.getElementById (psId);
        if (oPage !== null)
        {
            // Create table
            oTable = cbTable ();
            oTable.row ();

            // Build column list
            for (i = 1, iCols = 0; i < arguments.length; i++, iCols++)
            {
                sCol = arguments[i];
                asCol[iCols] = sCol;
                oCell = oTable.cell ("th", sCol);
                if (sCol === "Progress")
                {
                    oCell.colSpan = 2;
                }
            }

            // Iterate through projects
            for (iP = 0; iP < iCountFilt; iP++)
            {
                // Handle next project
                oTable.row ();

                // Iterate through columns
                for (i = 0; i < iCols; i++)
                {
                    // Select value to display in cell
                    oProj = aoFilt[iP];
                    sClass = oTable.className;
                    sCol = asCol[i];
                    switch (sCol)
                    {
                    case "Active":
                        tValue = oProj.bActive ? "Yes" : "No";
                        break;
                    case "Code":
                        tValue = oProj.sCode;
                        break;
                    case "Parent":
                    case "Project":
                        oProj = oProj.oParent;
                        if (oProj === null)
                        {
                            tValue = "";
                            break;
                        }
                        /* Drop into next case... */
                    case "Title":
                        tValue = oProj.sTitle;
                        if (oProj.sFile === "")
                        {
                            tValue = oProj.sTitle;
                        }
                        else
                        {
                            tValue = cbLink (oProj.sTitle, oProj.sFile);
                            if (oProj.sFile.indexOf ("_nav") > 0)
                            {
                                tValue.target = "CbNav";
                            }
                            oTable.cell (tValue);
                            tValue = null;
                        }
                        break;
                    case "Class":
                        tValue = oProj.sClass;
                        break;
                    case "Genre":
                        tValue = oProj.sGenre;
                        break;
                    case "Phase":
                        tValue = oProj.sPhase;
                        break;
                    case "Iteration":
                        tValue = cbStrNum (oProj.iIter, cbnum_CSEP);
                        sClass = "number";
                        break;
                    case "Words":
                        tValue = cbStrNum (oProj.iWords, cbnum_CSEP);
                        sClass = "number";
                        break;
                    case "Expected":
                        tValue = cbStrNum (oProj.iMax, cbnum_CSEP);
                        sClass = "number";
                        break;
                    case "Progress":
                        oTable.meter (oProj.iWords, oProj.iMax);
                        tValue = null;
                        break;
                    case "Description":
                        tValue = oProj.sDesc;
                        break;
                    default:
                        tValue = sCol + "?";
                        break;
                    }

                    // Fill cell with value
                    if (tValue !== null)
                    {
                        oCell = oTable.cell ();
                        oCell.innerHTML = tValue;
                        oCell.className = sClass;
                    }
                }
            }

            // Place table on the page
            oPage.appendChild (oTable);

            // Remove message
            cbErrMsg (null);
        }
    }
    // End of listProj

    //////////////////////////////////////////////////
    // Priv: listPart
    // Desc: List the parts (and total) for a project.
    // Args: psId  -- Container element identifier.
    //       oProj -- Project object.
    //////////////////////////////////////////////////

    function listPart (psId, oProj)
    {
        var oTable, oSect, aoPart, i, oPart;

        // Any parts?
        aoPart = oProj.aoPart;
        if (aoPart === null)
        {
            // Just display overall progress
            listProj (psId, "Words", "Expected", "Progress");
        }
        else
        {
            // Anywhere to put things?
            oSect = document.getElementById (psId);
            if (oSect !== null)
            {
                // Create table
                oTable = cbTable ();
                oTable.row ("th", "Part", "Words", "Expected");
                oTable.cell ("th", "Progress", "colspan=2");

                // Iterate over parts
                for (i = 0; i < aoPart.length; i++)
                {
                    // Display next part in a row
                    oPart = aoPart[i];
                    oTable.row (oPart.sTitle);
                    oTable.progress (oPart.iWords, oPart.iMax);
                }

                // Add total row
                oTable.row ("<b>Total</b>");
                oTable.progress (oProj.iWords, oProj.iMax);

                // Put table into container
                oSect.appendChild (oTable);
            }
        }
    }
    // End of listPart

    //////////////////////////////////////////////////////
    // Priv: listInfo
    // Desc: Display a list of project samples.
    // Args: psCode  -- Project code.
    //       psCopy  -- Copyright.
    //       psIndex -- Index file for series.
    //       psFootA -- Footnote.
    //       psFootB -- More footnote text.
    // Retn: Project object.
    //////////////////////////////////////////////////////

    function listInfo (psCode)
    {
        var oProj, sTitle, sErrId;

        // Handle arguments
        var psCopy  = (arguments.length > 1) ? arguments[1] : "";
        var psIndex = (arguments.length > 2) ? arguments[2] : "";
        var psFootA = (arguments.length > 3) ? arguments[3] : "";
        var psFootB = (arguments.length > 4) ? arguments[4] : "";

        // Get title
        filterProj (null);
        filterProj ("Code", psCode);
        if (iCountFilt !== 1)
        {
            throw ("Failed to find unique project '" + psCode + "' !");
        }
        oProj = aoFilt[0];
        sTitle = oProj.sTitle;

        // Display info
        cbBgDocInfo (sTitle, psCopy, psIndex, psFootA, psFootB);

        // Display progress
        sErrId = "ErrProgress";
        try
        {
            listPart ("Progress", oProj);
            cbErrMsg (null, null, null, sErrId);
        }
        catch (e)
        {
            cbErrMsg ("Failed to display!", e, null, sErrId);
        }

        // Sift and display projects who's parent is the given code
        sErrId = "ErrSamples";
        try
        {
            filterProj (null);
            filterProj ("Parent", psCode);
            listProj ("Samples", "Title", "Description", "Words");
            cbErrMsg (null, null, null, sErrId);
        }
        catch (e)
        {
            cbErrMsg ("Failed to display!", e, null, sErrId);
        }

        return (oProj);
    }
    // End of listInfo

    // Return results
    this.aoProj       = aoProj;
    this.iCountParent = iCountParent;
    this.iCountProj   = iCountProj;
    this.iCountActive = iCountActive;
    this.iCountFilt   = iCountFilt;
    this.iCountWord   = iCountWord;
    this.iCountMax    = iCountMax;
    this.filter       = filterProj;
    this.list         = listProj;
    this.info         = listInfo;
    return (this);
}
// End of cbProjects

////////////////////////////////////////////////////////////
// Func: cbSubDocsLoad
// Desc: Look for sub-documents and load them into the page.
////////////////////////////////////////////////////////////

function cbSubDocsLoad ()
{
    var sSubId = "";

    try
    {
        subDocsLoad ();
    }
    catch (e)
    {
        cbErrMsg ("Failed to display sub-document!", e, null, sSubId);
    }

    /////////////////////////////////////////
    // Priv: subDocsLoad
    // Desc: Scan for and load sub-documents.
    /////////////////////////////////////////

    function subDocsLoad ()
    {
        var i, iSub, aoLink, oLink, oSect, oPage, sTitle, oText, oForm, oButton;

        // Find all sub-document hyperlinks
        aoLink = document.getElementsByTagName ("a");
        for (i = iSub = 0; i < aoLink.length; i++)
        {
            // Filter out all but the class we're interested in
            oLink = aoLink[i];
            if (oLink.className.indexOf ("subdoc") >= 0)
            {
                // Found a sub-document
                iSub++;

                // Is there a section for the subdocument?
                oSect = oLink.parentNode;
                if ((oSect.nodeName !== "DIV") || (oSect.className.indexOf ("subdoc") < 0))
                {
                    // Create a new section
                    oPage = oSect;
                    oSect = cbTag ("div");
                    oSect.className = gsCbBgClass + "subdoc";

                    // Put the section on the page
                    oPage.insertBefore (oSect, oLink);
                }

                // Set up identifier for messages
                sSubId = oSect.id;
                if (sSubId === "")
                {
                    sSubId = oSect.id = "CbBgSubDoc_Section_" + iSub;
                }

                // Title
                sTitle = oLink.title;
                if (sTitle === "")
                {
                    sTitle = "the document";
                }
                oText = cbTag ("h5", sTitle);
                oSect.innerHTML = "";
                oSect.appendChild (oText);

                // Form for controls
                oForm = cbTag ("form");
                oForm.action = "#";
                oForm.id = "CbBgSubDoc_Form_" + iSub;
                oSect.appendChild (oForm);

                // Re-work and move the hyperlink into the section
                oLink.title = "Read \"" + sTitle + "\" on the Captain's web site...";
                oLink.innerHTML = "web site";

                // Prepare form
                oForm.innerHTML = "You can ";

                // Create button for reading locally
                oButton = cbTag ("input", "type=button");
                oButton.value = "read it here";
                oButton.title = "Click this button to load \"" + sTitle + "\" and display it on this page...";
                oButton.id = "CbBgSubDoc_Button_" + iSub;
                oButton.onclick = subDocShow;

                // Add button and hyperlink to form
                oForm.appendChild (oButton);
                oText = document.createTextNode (" on this blog.");
                oForm.appendChild (oText);
                oText = cbTag ("br");
                oForm.appendChild (oText);
                oText = document.createTextNode ("Alternatively you can read it on Captain Black's ");
                oForm.appendChild (oText);
                oForm.appendChild (oLink);
                oText = document.createTextNode (".");
                oForm.appendChild (oText);

                // Reset message identifier
                sSubId = "";
            }
        }
    }
    // End of subDocsLoad

    /////////////////////////////////
    // Priv: subDocShow
    // Desc: Display a sub-document.
    // Args: oEvent -- Event details.
    /////////////////////////////////

    function subDocShow (oEvent)
    {
        try
        {
            oEvent = cbEvent (oEvent);
            subDocHandle (oEvent);
        }
        catch (eE)
        {
            cbErrMsg ("Failed to display sub-document!", eE, null, oEvent.target.parentNode.id);
        }

        ///////////////////////
        // Priv: subDocHandle
        // Desc: Event handler.
        ///////////////////////

        function subDocHandle (oEvent)
        {
            var oSect, oFrame, oLink, oText, sText;

            oSect = oEvent.target;
            while (oSect.nodeName !== "DIV")
            {
                oSect = oSect.parentNode;
            }

            // Find hyperlink
            oLink = oSect.getElementsByTagName ("a");
            oLink = oLink[0];

            // Create in-line frame
            oFrame = cbTag ("iframe", "frameborder=0", "scrolling=auto");
            oFrame.className = gsCbBgClass + "subdoc";
            oFrame.src = oLink.href;

            // Put instructions inside, in case i-frames's are not supported
            sText =  "Your browser does not support in-line frames, or has this feature disabled.";
            sText += "<br />Please read the document on Captain Black's ";
            oText = cbTag ("p", sText);
            oText.appendChild (oLink);
            oText.innerHTML += ".";
            frameInner (oFrame, oText);

            // Insert frame in section and trigger a resize
            oSect.innerHTML = "";
            oSect.appendChild (oFrame);
            cbIFrameResize (null);
        }

        /////////////////////////////////////////////////
        // Priv: frameInner
        // Desc: Append to the inner contents of a frame.
        // Args: oIFrame -- I-frame to change.
        //       oInner  -- Inner content to append.
        /////////////////////////////////////////////////

        function frameInner (oIFrame, oInner)
        {
            try
            {
                oIFrame.appendChild (oInner);
            }
            catch (eF)
            {
                oInner.innerHTML = "";
            }
        }
        // End of frameInner
    }
    // End of suDocShow
}
// End of cbSubDocsLoad

///////////////////////////////////////////
// Func: cbIFrameResize
// Desc: Adjust the size of in-line frames.
// Args: oEvent -- Event details.
///////////////////////////////////////////

function cbIFrameResize (oEvent)
{
    try
    {
        resize ();
    }
    catch (e)
    {
        // Benign error - Comment out next line unless debugging
        //cbErrMsg ("Failed to resize sub-document!", e);
    }

    //////////////////////////////////////////
    // Priv: resize
    // Desc: Resize all sub-document i-frames.
    //////////////////////////////////////////

    function resize ()
    {
        var i, oIF, aoIF, iHeight;

        // Get required height
        iHeight = document.documentElement.clientHeight;
        if ((iHeight == undefined) || (iHeight <= 0))
        {
            iHeight = document.body.clientHeight;
        }
        if ((iHeight == undefined) || (iHeight <= 0))
        {
            iHeight = window.innerHeight;
        }
        if ((iHeight == undefined) || (iHeight <= 0))
        {
            throw ("Window height could not be determined!");
        }

        // Make height 90 percent of window height
        iHeight = Math.floor (iHeight * 0.9);

        // Iterate through list of all i-frames
        aoIF = document.getElementsByTagName ("iframe");
        for (i = 0; i < aoIF.length; i++)
        {
            // Sift out all but the one's we're interested in
            oIF = aoIF[i];
            if (oIF.className.indexOf ("subdoc") >= 0)
            {
                // Adjust the size
                oIF.style.height = iHeight + "px";
            }
        }
    }
    // End of resize
}
// End of cbIFrameSize

////////////////////////////////////////////
// Func: cbBgDocInfo
// Desc: Render document information.
// Args: psTitle -- Document (series) title.
//       psCopyR -- Copyright message.
//       psIndex -- Index file for series.
//       psFootA -- First part of footnote.
//       psFootB -- Second part of footnote.
////////////////////////////////////////////

function cbBgDocInfo (psTitle)
{
    var oSect, oE, oTable, sFoot, sHover, oLink;
    var i, iCountExtra, aoExtra, oExtra;

    // Handle arguments
    var psCopyR = (arguments.length > 1) ? arguments[1] : "";
    var psIndex = (arguments.length > 2) ? arguments[2] : "";
    var psFootA = (arguments.length > 3) ? arguments[3] : "";
    var psFootB = (arguments.length > 4) ? arguments[4] : "";
    if (psIndex === "")
    {
        psIndex = "http://www.kevinmachin.pwp.blueyonder.co.uk";
    }

    // Are we on the main web site?
    var bMain = (window.name === "CbBody") ? true : false;

    // Handle footnote
    sFoot = sHover = "";
    if (!bMain && (psFootA !== ""))
    {
        sFoot  += psFootA;
        sHover += psFootA;
        if (psFootB !== "")
        {
            sFoot  += " <i>" + psTitle + "</i> " + psFootB;
            sHover += " \""   + psTitle + "\" "    + psFootB;
        }
        sFoot  += " on ";
        sHover += " on Captain Black's web site...";
        footNote (sFoot);
    }

    // Is there a section for the title?
    oSect = document.getElementById ("Title");
    if (oSect !== null)
    {
        // Create the title table
        oTable = cbTable ("class=hidden");
        oTable.row ();

        // Title
        if (bMain || (psIndex === null))
        {
            oLink = psTitle;
        }
        else
        {
            oLink = cbLink (psTitle, psIndex, "target=_top");
            if (sHover === "")
            {
                sHover = "Read more of \"" + psTitle + "\" on Captain Black's web site...";
            }
            oLink.title = sHover;
        }
        oE = cbTag ("h1", oLink);
        oTable.cell (oE);

        // Copyright
        if (psCopyR !== null)
        {
            if (psCopyR === "")
            {
                psCopyR = "&copy; Kevin Machin";
            }
            oE = oTable.cell (psCopyR);
            oE.className = "ridden";
        }

        // Render the title table
        oSect.appendChild (oTable);
    }

    // Handle extra sections
    aoExtra = document.getElementsByTagName ("div");
    for (i = iCountExtra = 0; i < aoExtra.length; i++)
    {
        oExtra = aoExtra[i];
        if (oExtra.className === "extra")
        {
            iCountExtra++;

            // Hide or show section, according to where we are
            if (bMain)
            {
                oExtra.style.display = "Block";
            }
            else
            {
                oExtra.style.display = "None";
            }
        }
    }

    // Were there any extra sections?
    if (!bMain && (iCountExtra > 0))
    {
        // Add extra footnote
        footNote ("You can read a longer version on ");
    }

    ///////////////////////////////////////////////////
    // Priv: footNote
    // Desc: Add a footnote to the end of the document.
    // Args: psText -- Text to display.
    // Note: Hyperlink will automatically be added.
    ///////////////////////////////////////////////////

    function footNote (psText)
    {
        var sWeb = "Captain Black's web site";

        // Create link
        if (psIndex === null)
        {
            oLink = cbTag ("span", sWeb);
        }
        else
        {
            oLink = cbLink (sWeb, psIndex, "target=_top");
            if (psIndex.slice (0, 4) !== "http")
            {
                oLink.title = "Click to go to the \"" + psTitle + "\" page on " + sWeb + "...";
            }
        }

        // Create footnote
        oE = cbTag ("p", psText, "class=safety");
        oE.style.fontSize = "80%";
        oE.appendChild (oLink);
        oE.innerHTML += ".";

        // Attach footnote
        oSect = document.body;
        oSect.appendChild (oE);
    }
    // End of footNote
}
// End of cbBgDocInfo
/* End of file cbbg.js */
