Pimp My CAT

The CAT Pimper

当前为 2015-10-15 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          Pimp My CAT
// @namespace     http://www.pimpmycat.nothing
// @description   The CAT Pimper
// @include       http://training.cat-europe.com/TrainingModeFrame.aspx
// @include       https://training.cat-europe.com/TrainingModeFrame.aspx
// @version       10.7untested
// @screenshot    http://k.min.us/ilyyO4.png
// @id            pimpmycat@ptntools
// @author        Axel Bock <[email protected]>
// @require       http://code.jquery.com/jquery-1.6.min.js
// @require       https://greasyfork.org/scripts/13081-pimp-my-cat-hotkeys-script/code/Pimp%20My%20CAT%20hotkeys%20script.user.js
// ==/UserScript==

//       ********************************************
//       ******* ALSO CHANGE MAIN VERSION HERE ******
//       ********************************************
pmc_version = "10.6";

//  setInterval() -> setTimeout() in 1 function


function getNotAnswered() {
    return $('tr:not(.hide-row) > td:nth-child(2) > img[src="images/spacer.gif"]', 
        window.frames[1].document).size();    
}

function getCorrectAnswered() {
    return $('tr:not(.hide-row) > td:nth-child(2) > img[src="images/frage_richtig.gif"]', 
        window.frames[1].document).size();    
}

function getWronglyAnswered() {
    return $('tr:not(.hide-row) > td:nth-child(2) > img[src="images/frage_falsch.gif"]', 
        window.frames[1].document).size();    
}

function getPinned() {
    return $('tr:not(.hide-row) > td:nth-child(3) > img[src="images/pin.gif"]', 
        window.frames[1].document).size();    
}

function updateStatBox() {
    var ans_ok = getCorrectAnswered();
    var ans_no = getWronglyAnswered();
    var ans_xx = getNotAnswered();
    var pinned = getPinned();
    var all_quest = (ans_ok + ans_no + ans_xx);
    var ans_quest = (ans_ok + ans_no);
    var perc = (ans_ok / ans_quest * 100).toFixed(2);
    if (ans_quest == 0) {
        perc = 0;
    }
    // the percentage color and the comment string :)
    var color = "#cccccc";
    var comment = 'Let\'s roll ...';
    if (perc == 100) {
        color = "#FFD700";
        if (ans_quest > 10) {
            comment = '<a target="new" style="color:#FFD700; font-weight: bold;" href="http://www.youtube.com/watch?v=BBRQ8XUJOeI">Shag-a-tastic, baby!!</a>';
        } else if (ans_quest > 7) {
            color = "#ccac00";
            comment = 'Yooou <span style="color:#ccac00;">get it </span>baby!!';
        }     else if (ans_quest > 4) {
            color = "#b29600";
            comment = 'You\'re <a target="new" style="color: #b29600;" href="http://www.youtube.com/watch?v=06kXXJUddHQ">on the roll</a> ...';
        }    else if (ans_quest > 1) {
            color = "#806c00";
            comment = "Keep it coming ...";
        } else if (ans_quest == 1) {
            color = "#665600";
            comment = '<a target="new" style="color: #665600;" href="http://www.youtube.com/watch?v=-8MjBU2sNK4">Great start! :)</a>';
        }
    } else if (perc >= 90) {
        comment = '<a target="new" href="http://www.youtube.com/watch?v=Ai813WFOtwQ">First class.</a>';
        color = "#32CD32";
    } else if (perc >= 80) {
        comment = '<b>you\'re <a target="new" style="color:#32CD32;" href="http://www.youtube.com/watch?v=lbbLT94L-T0">the one</a></span>!</b>';
        color = "#32CD32";
    } else if (perc >= 75) {
        color = "#FFB1A3"; 
        comment = 'Close, but failure.';
    } else if (perc > 50) {
        color = "#FF6347";
        comment = '<b><a href="http://www.youtube.com/watch?v=1G0aemJnlyU" target="new" style="color:#FF6347;">you suck</a>!!</b>';
    } else if ( ans_quest > 3){
        color = "red";
        comment = '<span style="color:red">Don\'t fly. Please.</span>';
    } else if ( ans_quest == 3){
        color = "red";
        comment = 'Really? Come on!';
    } else if ( ans_quest == 2){
        color = "red";
        comment = 'Bad day?';
    } else if ( ans_quest == 1){
        color = "red";
        comment = 'Ouch.';
    }
    // how many more good answers to be cool
    if (perc < 80 && ans_quest > 0) {
        var more = 4*ans_no - ans_ok;
        more = 
            '<br><span style="font-size:8pt;color:#cccccc;">'
            + '+<span style="color:#32CD32;">'
            + more.toFixed(0)
            + '</span> to be good.</span>';
    } else {
        var more = "";
    }
    // find out about display or question filters
    // display filters
    var wf1d = window.frames[1].document;
    var disp_filter = $('head', wf1d).attr('display_filter');
    if (! disp_filter || disp_filter == "0,0,0,0") {
        disp_filter = "";
    } else {
        disp_filter = "F";
    }
    // question filters ... easier ;)
    var qstn_filter = dfil_get_filter('set_filter') ? "Q" : "";
    // construct the box
    var innerHTML = '\
    <table style="color:#cccccc; font-size:8pt;" width="100%" border="0"><tr>\
        <td style="font-size:16pt;color:%COLOR" valign="center"><b>%PERCENT %</b>%MORE%</td>  \
        <td><b>%DISP_FILTER<br>%QSTN_FILTER</b></td>\
        <td align="right"><span style="color:#32CD32">%CORRECT</span> \
        + <span style="color:#FF6347">%WRONG</span>, %LEFT left\
        <br>\
        <span style="color:#fdcb68">%PINNED</span> marked\
        <br>\
        %COMMENT</td> \
      </tr>\
    </table>';
    innerHTML = innerHTML.
        replace("%COMMENT", comment).
        replace("%PERCENT", perc).
        replace("%COLOR", color).
        replace("%CORRECT", ans_ok).
        replace("%WRONG", ans_no).
        replace("%DISP_FILTER", disp_filter).
        replace("%QSTN_FILTER", qstn_filter).
        replace("%LEFT", ans_xx).
        replace("%PINNED", pinned).
        replace("%MORE%", more);
    var box = $('#pimpmyCAT', window.frames[0].document);
    if (! box.size()) {
        box = $('<div id="pimpmyCAT"></div>');
        $('body > :last-child', window.frames[0].document).after(box);
    }
    var hashString = 
        perc
        + '_' + ans_quest.toString() 
        + '_' + ans_ok.toString()
        + '_' + ans_xx.toString()
        + '_' + pinned.toString()
        + '_' + disp_filter
        + '_' + qstn_filter;
    if (! box.attr('hash') || box.attr('hash') != hashString) {
        box[0].innerHTML = '';
        box.attr('hash', hashString);
        box.html(innerHTML);
    }
}

function getQBox() {
    // this is soooo fucked up. the company behind this should be shot 
    // and burned and banned to hell for all eternity. 
    var idbox = $('form > div > div', window.frames[2].document);
    var retVal = false;
    idbox.each( function() {
       var bg_image = $(this).css('background-image');
       if (bg_image != null && bg_image.indexOf('kreis') > -1) {
           retVal = $(this);
           return false;
       }
    });
    return retVal;
}

// returns FALSE or the number in the question id box
function getQuestionNumber() {
    var retVal = getQBox();
    if (retVal) {
        retVal = parseInt(retVal.find('td').text());
    }
    return retVal;
}

function clickOn(target, doc) {
    var evt = doc.createEvent("MouseEvents");
    evt.initEvent('click', true, true);
    var rv = target.dispatchEvent(evt);
    if (!rv) {
        alert('clickOn() failed for some reason.');
    }
    return rv;
}

function goTo(target, framewin) {
    if (target) {
        $('html,body', framewin.document).animate(
            { scrollTop: $(target).offset().top-$(framewin).height()/2 }, 
            { duration: 'medium', easing: 'swing'});
    }
}

function clickNB(no) {
    var wf0d = window.frames[0].document;
    target = $('#Table1 td:nth-child(' + no + ')', wf0d)[0];
    clickOn(target, wf0d);
    return false;
}

function rgb2hex(rgbString) {
    var parts = rgbString.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
    if (!parts) { return rgbString; }
    // parts now should be ["rgb(0, 70, 255", "0", "70", "255"]
    delete (parts[0]);
    for (var i = 1; i <= 3; ++i) {
        parts[i] = parseInt(parts[i]).toString(16);
        if (parts[i].length == 1) parts[i] = '0' + parts[i];
    }
    return parts.join(''); // "rrggbb"
}

// you MUST MUST call filterNeighbors after, cause filterNeighbors
// checks for visibility ... 
function initNeighbors() {
    var wf1d = window.frames[1].document;
    var retval = [[], null, []];
    var indx = 0;
    var questions = $('table tr .bg', wf1d);
    questions.each( function(i) {
        if (indx == 0 && rgb2hex($(this).css('background-color')) == 'e2e2e2') {
            indx++;
            retval[indx] = [$(this)];
            indx++;
        } else {
            retval[indx].push($(this));
        }
    }); 
    return retval;
}

function get_matrix_for(element) {
    var v0 = 1;
    var v1 = element.find('img[src="images/pin.gif"]').size();
    var v2 = element.find('img[src="images/frage_richtig.gif"]').size();
    var v3 = element.find('img[src="images/frage_falsch.gif"]').size();
    if (v2+v3 > 0) {
        v0 = 0;
    }
    return [v0, v1, v2, v3];    
}

function keep_element(matrix, filter) {
    var applid = 0;
    var failed = 0;
    for (var k=0; k<4; k++) {
        if (filter[k]){
            applid ++;
            if (filter[k]-1 != matrix[k]) {
                failed++;
            }
        }
    } 
    // no applied filter means EVERYTHING passes :)
    return (applid == 0 || applid > failed);
}

// PARAM FILTER: 
// the parameter filter is an array of values of 0..2
// [new / marked / positive_ans / negative_ans]
// 0 == do not compare this value.
// 1 == DO NOT include those questions
// 2 == INCLUDE those questions
// the filter values are ORed. 
// example: get all NEGATIVE and MARKED questions: 
// [0, 2, 0, 2] 
// --> do NOT filter for new and positive (array 0 & 2)
// --> FILTER for MARKED==TRUE and NEGATIVE==TRUE (array 1 & 3)
// PARAM APPLY: 
// do not only filter, but APPLY the filter to visibility
// RETURNS: 
// returns a 3-array:
// all previous visible neigbors (according to filter)
// the current item
// all following neighbors (according to filter)
function filterNeighbors(set, filter, apply) {
    if (! filter) {
        filter = dfil_get_filter('display_filter');
        if (! filter) { filter = [0,0,0,0]; }
    }
    var qset = dfil_get_filter('set_filter');
    var invt = parseInt(dfil_get_var('invert_qset'));
    if (qset) { 
        qset = qset.map( function(n) { return n.toString(); });
    }
    var dbg=0;
    var retval = [[], [], []];
    var matrix, cur, positive;
    for (var i=0; i<set.length; i++) {
        for (var j=0; j<set[i].length; j++) {
            cur = set[i][j];
            matrix = get_matrix_for(cur);
            positive = keep_element(matrix, filter);
            if (qset) { 
                if (! invt) {
                    positive = positive && (qset.indexOf(cur.text().trim()) != -1);
                } else {
                    positive = positive && (qset.indexOf(cur.text().trim()) == -1);
                }
            } 
            if (apply) {
                if (positive) {
                    retval[i].push(cur);
                    cur.removeClass('hide-row');
                } else {
                    cur.addClass('hide-row');
                }
            } else if (positive && ! cur.hasClass('hide-row')) {
                retval[i].push(cur);
            }
        }
    }
    return retval; 
}

// if filter is omitted it is set to [0,0,0,0] automatically 
// fwd = true if forward.
function next_with_filter(fwd, filter) {
    var wf1d = window.frames[1].document;
    var set = initNeighbors();
    if (!filter) filter = [0, 0, 0, 0];
    set = filterNeighbors(set, filter);
    if (!fwd) {
        var tmp = set[0].reverse();
        set[0] = set[2].reverse();
        set[2] = tmp;
    }
    var target = null;
    if (set[2].length) {
        target = set[2].shift();
    } 
    else {
        if (set[0].length) target = set[0].shift(); 
    }
    if (target) {
        clickOn(target[0], wf1d);
        goTo(target, window.frames[1]);
    }
    return (target) ? true : false;
}

function current() {
    var wf1d = window.frames[1].document;
    var set = initNeighbors();
    target = set[1].shift();
    clickOn(target[0], wf1d);
    goTo(target, window.frames[1]);
    return false;
}

function key_nf(fwd, filter) {
    logDate('key_nf');
    if (!next_with_filter(fwd, filter)) { alert('no matching questions.'); }
    logDate('key_nf done');
    return false;
}

function key_enter(forward) {
    if (! next_with_filter(forward, [2,0,0,0]))
        next_with_filter(forward);
    return false;
}



// ==========================================================================
// Filter based on question properties (answered/new, marked, etc.)
// ==========================================================================

function dfil_get_var(varname) {
    var wf1d = window.frames[1].document;
    var variable = $('head', wf1d).attr(varname);
    return variable ? variable : false;
}

function dfil_save_var(varname, value) {
    $('head', window.frames[1].document).attr(varname, value);    
}

function dfil_get_filter(attr_name) {
    var wf1d = window.frames[1].document;
    var current_filter = $('head', wf1d).attr(attr_name);
    if (! current_filter) {
        return false;
    } else {
        current_filter = current_filter.split(",");
        current_filter = current_filter.map( function(n) { return parseInt(n); }); 
    }
    return current_filter;
}

function dfil_save_filter(filter_array, filter_name) {
    var wf1d = window.frames[1].document;
    if (! filter_name) { filter_name = 'display_filter'; }
    if (filter_array) {
        $('head', wf1d).attr(filter_name, filter_array.join(','));    
    } else {
        $('head', wf1d).removeAttr(filter_name);    
    }
}

function dfil_display_filter(f) {
    var wf2d = window.frames[2].document;
    var text = [
        "all <b>answered</b> questions", "all <b>new</b> questions",
        "all <b><u>un</u>marked</b>", "all <b>marked</b>",
        "everything <b><u>not</u> marked correct</b>", "everything <b>marked correct</b>", 
        "everything <b><u>not</u> marked incorrect</b>", "everything <b>marked incorrect</b>"];
    var display = '<p>Displaying all of the following:</p><ul>';
    var added = false;
    for (var i=0; i<4; i++) {
        if (f[i]) {
            display += "<li>"+text[2*i+f[i]-1]+"</li>";
            added = true;
        }
    }
    if (!added) 
        display += "display everything."; 
    display += "</ul>"
    // now display that :)
    var box = $('body > div#infobox', wf2d);
    if (box.size()) box.html(display);
    else {
        $('body', wf2d).
            prepend("<div id='infobox' class='infobox'>"+display+"</div>");
    }
    return false;
}

filter_wait = false;
function dfil_qjump_later(q, click) {
    if (filter_wait) {
        clearTimeout(filter_wait);
    }
    filter_wait = setTimeout(function() {
        filter_wait = false;
        goTo(q, window.frames[1]);
        if (click) {
            // remember: clickOn needs a DOM object!!
            clickOn(q[0], window.frames[1].document);
        }
    }, 350);
}

function dfil_apply_filter(f) {
    var wf1d = window.frames[1].document;
    dfil_save_filter(f);
    var n = initNeighbors();
    n = filterNeighbors(n, f, true);
    dfil_display_filter(f);
    // if the current question was filtered out - go to the next visible one.
    if (n[1].length) { 
        dfil_qjump_later(n[1][0]);
    } else {
        if (n[2].length) { 
            dfil_qjump_later(n[2][0], true);
        } else {
            dfil_qjump_later(n[0][0], true);
        }
    }
}

function dfil(item) {
    var f = dfil_get_filter('display_filter');
    if (!f) { f = [0,0,0,0]; }
    f[item] = ++f[item] % 3;
    dfil_apply_filter(f);
    return false;
}

function dfil_reapply() {
    var f = dfil_get_filter('display_filter');
    if (!f) { f = [0,0,0,0]; }
    dfil_apply_filter(f);
    return false;
}


// ==========================================================================
// common screen methods
// ==========================================================================

function screen_abort() {
    var wf2d = window.frames[2].document;
    if ($('.pmc_screen', wf2d).size()) {
        $('.pmc_screen', wf2d).remove();
        $('body', wf2d).children().each( function() {
            $(this).removeClass('hide-row'); 
        });
    }
    return false;
}

function screen_prepare() {
    var wf2d = window.frames[2].document;
    $('body', wf2d).children().each( function() {
        $(this).addClass('hide-row'); 
    });
}

function screen_check_already_there(idstr) {
    // if THIS screen is already there we will simply return "yes"
    if ($('#'+idstr, window.frames[2].document).size()) { return true; }
    // otherwise if another screen is there we will kill it
    if ($('.pmc_screen', window.frames[2].document).size()) {
        screen_abort();
    }
    return false;
}


// ==========================================================================
// Info screens
// ==========================================================================

function info_screen() {
    var wf2d = window.frames[2].document;
    if (screen_check_already_there('pmc_info_screen')) {
        return screen_abort();
    }
    screen_prepare();
    var ifr;
    ifr = $('<div style="text-align:center; margin-top:20px;">\
                <div id="dismiss">\
                <b style="color: #777">Press ESC or click here to dismiss.</b></div>\
                <object width="50%" height="220"></object>\
            </div>'); 
    $('body > :first-child', wf2d).before(ifr);
    ifr.attr('id', 'pmc_info_screen');
    ifr.addClass('pmc_screen');
    ifr.find('object').attr('data', 
        'http://training.cat-europe.com/ShowQuestionInfo.aspx');
    ifr.find('#dismiss').click(screen_abort);
    return false;
}

function visi_screen() {
    var wf2d = window.frames[2].document;
    var wf1d = window.frames[1].document;
    if (screen_check_already_there('pmc_einfo_screen')) {
        return false;
    }
    screen_prepare();
    var newhtml = '\
    <table class="pmc_screen pmc_input_table">\
        <tr><td colspan="2">\
            Currently <b>visible</b> questions are, depending on display and \
            question filters). Press ESC or click &quot;Close&quot; to dismiss.\
        </td></tr>\
        <tr><td colspan="2">\
            <textarea rows=10 cols=50 name="textarea" id="textarea" />\
        </td></tr>\
        <tr><td colspan="2">\
            Note that this is <b>not</b> necessarily the same as the \
            question set. The shift-1 ... shift-4 filters are applied here, \
            too.\
        </td></tr>\
        <tr><td colspan="2">\
        <input type="submit" id="dismiss" value="Close" />\
        </td></tr>\
    </table>';
    var ifr = $(newhtml);
    ifr.attr('id', 'pmc_einfo_screen');
    // now create the question list
    var qlist = $('table tr.bg:not(.hide-row)', wf1d);
    var text = "";
    qlist.each( function(i) {
        text += $(this).text().trim() + " ";
    });
    $('body > :first-child', wf2d).before(ifr);
    $('#textarea', ifr).val(text);
    $('#textarea', ifr).bind('keydown', 'esc', screen_abort);
    $('#dismiss', ifr).click(screen_abort);
    return false;
}


// ==========================================================================
// Set filter screen
// ==========================================================================

function set_form() {
    var wf2d = window.frames[2].document;
    if (screen_check_already_there('pmc_qset_table')) { return false; }
    screen_prepare();
    var newhtml = '\
    <table id="pmc_qset_table" class="pmc_screen pmc_input_table">\
    <form>\
        <tr><td colspan="2">\
            Enter the list of <b>question numbers you want to keep visible</b>. \
            Separate by SPACE or ENTER or any combination of those. You can disable \
            the filter and make everything visible again later by pressing \
            <span style="font-family: courier;">shift-6</span>.\
        </td></tr>\
        <tr><td colspan="2">\
            <textarea rows=10 cols=50 name="textarea" id="question_set" />\
        </td></tr>\
        <tr><td colspan="2">\
            <input type="checkbox" name="invert" id="invert_box"/>Invert\
            (hide questions entered here and leave others visible) \
        </td></tr>\
        <tr>\
            <td width="50%">\
                <input type="button" id="pmc_submit_set" value="Submit" />\
            </td>\
            <td width="50%">\
                <input type="button" id="pmc_abort_set" value="Abort" />\
            </td>\
        </tr>\
    </form>\
    </table>';
    $('body > :first-child', wf2d).before(newhtml);
    $('#question_set').focus();
    $('#pmc_submit_set', wf2d).click(set_form_submit);
    $('#pmc_abort_set', wf2d).click(screen_abort);
    $('#question_set', wf2d).bind('keydown', 'esc', screen_abort);
    $('#invert_box', wf2d).prop('checked', dfil_get_var('invert_qset')=='1');
    var set = dfil_get_filter('set_filter');
    if (set != false) { 
        $('#question_set', wf2d).val(set.join('\n'));
    }
    return false;
}

function set_form_submit() {
    var wf1d = window.frames[1].document;
    var wf2d = window.frames[2].document;
    var list = $('#question_set', wf2d).val();
    var invt = $('#invert_box', wf2d).prop('checked');
    var num = /\d+/;
    var splitter = /\s+/;
    list = list.split(splitter);
    list = list.filter( function(elem,indx,array) {
        return num.test(elem.trim());
    });
    // filter out double entries from the list so you can simply 
    // merge lists in the form ... 
    var list2 = [];
    for (var j=0; j<list.length; j++) {
        if (list2.indexOf(list[j]) == -1) {
            list2.push(list[j]);
        }
    }
    list = list2; 
    list2 = null; // garbage collection? ... hm. 
    // let's restore visibility for everything ;)
    screen_abort();
    // now we have the array of questions we want to see. 
    // now let's apply that stuff :)
    dfil_save_filter(list, 'set_filter');
    dfil_save_var('invert_qset', invt ? 1 : 0);
    dfil_reapply();
}

function set_reset() {
    dfil_save_filter(false, 'set_filter');
    dfil_save_var('invert_qset', 0);
    dfil_reapply();
    return false;
}


// ==========================================================================
// Goto screen
// ==========================================================================

function goto_screen() {
    var wf2d = window.frames[2].document;
    if (screen_check_already_there('pmc_goto')) { return false; }
    screen_prepare();
    var newhtml = '\
    <table width="50%" style="padding: 30px;" \
        id="pmc_goto" class="pmc_screen pmc_input_table">\
    <form>\
        <tr><td colspan="2" style="padding: 15px;">\
            Input the question number to jump to and press ENTER: \
        </td></tr>\
        <tr><td colspan="2" style="text-align: center; padding: 15px;">\
            <input type="text" size=5 name="qnumber" id="qnumber" />\
        </td></tr>\
        <tr>\
            <td width="50%" style="padding: 15px;">\
                <input type="submit" id="pmc_submit_set" value="Go to" />\
            </td>\
            <td width="50%" style="padding: 15px;">\
                <input type="button" id="pmc_abort_set" value="Abort" />\
            </td>\
        </tr>\
    </form>\
    </table>';
    $('body > :first-child', wf2d).before(newhtml);
    $('#pmc_submit_set', wf2d).click(goto_screen_submit);
    $('#pmc_abort_set', wf2d).click(screen_abort);
    var q = $('#qnumber', wf2d);
    q.bind('keydown', 'return', goto_screen_submit);
    q.bind('keydown', 'esc', screen_abort);
    return false;
}

function goto_screen_submit() {
    var wf1d = window.frames[1].document;
    var wf2d = window.frames[2].document;
    var qnumber = $('#qnumber', wf2d).val().trim();
    var qlist = [];
    $('table tr .bg', wf1d).each( function(i) {
        qlist.push($(this));
    }); 
    screen_abort();
    for (var i=0; i<qlist.length; i++) {
        var q = qlist[i];
        var qtext = q.text().trim();
        if (qtext == qnumber) {
            q.removeClass('hide-row');
            var filter_list = dfil_get_filter('set_filter');
            if (filter_list) {
                var found = false;
                for (var j=0; j<filter_list.length; j++) {
                    if (filter_list[j] == qtext)  {
                        found = true; 
                        break;
                    }
                }
                if (!found) {
                    filter_list.push(qtext);
                    dfil_save_filter(filter_list, 'set_filter');
                }
            }
            clickOn(q[0], wf1d);
            goTo(q, window.frames[1]);
            break;
        }
    }
    // SUBMIT methods MAY NOT RETURN FALSE!!!!
}


// ==========================================================================
// Help screen
// ==========================================================================

function help_screen() {
    // <tr><th>key</th><th>function</th></tr>\
    var wf2d = window.frames[2].document;
    if (screen_check_already_there('help_table')) { 
        return screen_abort();
    }
    screen_prepare();
    var newhtml = '\
    <table width="75%" id="help_table" class="pmc_screen" \
        style="margin-left: auto; margin-right: auto; padding:20px; ">\
        <tr>\
            <td colspan="2" style="text-align:center" id="dismiss">\
                <em><b style="color: #777">press ESC or click this text to dismiss help</b></em> \
            </td>\
        </tr>\
        <tr>\
            <td>Version</td>\
            <td>'+pmc_version+', \
            <a href="http://userscripts.org/scripts/show/97267" target="new" \
            >check for update</a></td>\
        </tr>\
        <tr>\
            <td>Questions</td>\
            <td>Axel &lt;[email protected]&gt;</td>\
        </tr>\
        <tr><th colspan="2"><div style="align:center;">\
            Selecting answers\
        </div></th></tr> \
        <tr>\
            <td>1-4</td>\
            <td>directly select / unselect a question</td>\
        </tr>\
        <tr>\
            <td>0</td>\
            <td>unselect all questions</td>\
        </tr>\
        <tr>\
            <td>shift-left / shift-right</td>\
            <td>if there\'s an attachment, switch between attachment and the question itself</td>\
        </tr>\
        <tr>\
            <td>up / down</td>\
            <td>mark questions with keyboard</td>\
        </tr>\
        <tr>\
            <td>double-click on answer</td>\
            <td>same as marking it and pressing "n"</td>\
        </tr>\
        <tr><th colspan="2"><div style="align:center;">\
            Navigation & answering questions\
        </div></th></tr> \
        <tr>\
            <td><b style="color: red">input boxes</b></td>\
            <td>if your cursor is blinking within in input box, you can \
                <span style="color:red">use most of the single-key shortcuts,\
                but with the CTRL- modifier added.</span> \
                Example: <span style="color:red">&quot;s&quot;</span> \
                for <u>s</u>how solution \
                <span style="color:red">will become CTRL-s</span>. \
                ENTER will stay the same. Once the input box \
                is no longer active, the normal shortcuts will work again. \
            </td>\
        </tr>\
        <tr>\
            <td>ENTER</td>\
            <td>either go directly to next unanswered question, OR to next question if all are already answered</td>\
        </tr>\
        <tr>\
            <td>shift-ENTER</td>\
            <td>The same as ENTER, just backwards</td>\
        </tr>\
        <tr>\
            <td>g</td>\
            <td>go to question directly by number</td>\
        </tr>\
        <tr>\
            <td>s</td>\
            <td>show "solution"</td>\
        </tr>\
        <tr>\
            <td>m</td>\
            <td>"mark" question</td>\
        </tr>\
        <tr>\
            <td>f</td>\
            <td>"finish" session</td>\
        </tr>\
        <tr>\
            <td>n / p</td>\
            <td>go to next/previous question directly (without showing solution of answers)</td>\
        </tr>\
        <tr>\
            <td>+ / -</td>\
            <td>go to next/previous UNANSWERED question directly</td>\
        </tr>\
        <tr>\
            <td>b / B</td>\
            <td>go to previous / next <u>B</u>ADLY ;) \
                ANSWERED question.\
        </tr>\
        <tr>\
            <td>left / right</td>\
            <td>same as "left"/"right" buttons in navigation bar</td>\
        </tr>\
        <tr><th colspan="2"><div style="align:center;">\
            Filters & functions \
        </div></th></tr> \
        <tr>\
            <td>i</td>\
            <td>display question info</td>\
        </tr>\
        <tr>\
            <td>q</td>\
            <td>manually enter questions to be shown. \
            <span style="color:red;">cool stuff.</span></td>\
        </tr>\
        <tr>\
            <td>shift-1 - shift-4</td>\
            <td>set display filters (play with it!)</td>\
        </tr>\
        <tr>\
            <td>v</td>\
            <td>display visible questions. useful to copy/paste for later\
                use with &quot;q&quot;. this takes both the &quot;q&quot;\
                and the shift-1..shift-4 filters into account.</td>\
        </tr>\
        <tr>\
            <td>w / W</td>\
            <td>go to the question wiki to the page regarding the current \
                catalog subject. Just a registration\
                is required. The capital \'W\' opens the page directly in \
                edit mode. \
                <span style="color:red;">potentially very cool stuff.</span>\
            </td>\
        </tr>\
        <tr>\
            <td>shift-5</td>\
            <td>remove all display filters (the ones from shift-1 to shift-4)</td>\
        </tr>\
        <tr>\
            <td>shift-0</td>\
            <td>reapply current display & set filters</td>\
        </tr>\
        <tr>\
            <td>shift-6</td>\
            <td>remove the question set filter (the one you activate with \'q\')</td>\
        </tr>\
        <tr><th colspan="2"><div style="align:center;">\
            Well ... \
        </div></th></tr> \
        <tr>\
            <td style="vertical-align: middle;">\
                <form action="https://www.paypal.com/cgi-bin/webscr" style="margin: 0px;" method="post">\
                <input type="hidden" name="cmd" value="_s-xclick">\
                <input type="image" id="donatebutton" src="https://www.paypalobjects.com/WEBSCR-640-20110401-1/en_GB/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">\
                <input type="hidden" name="hosted_button_id" value="57BWCFYLTLLAC">\
                <img alt="" border="0" src="https://www.paypalobjects.com/WEBSCR-640-20110401-1/de_DE/i/scr/pixel.gif" width="1" height="1">\
                </form>\
            </td>\
            <td>\
                <small>\
                The &quot;price&quot; for this little tool used to be a medium \
                sized latte macchiato. So if this tool helps you, please consider \
                donating one (2,50 EUR) if you really cannot get me in the flesh. \
                But I do prefer coffee in person, because this is more of a way to \
                let me know I have helped you, which makes me kind of happy. \
                </small>\
            </td>\
        </tr>\
    </table>';
    $('body > :first-child', wf2d).before(newhtml);
    $('#dismiss', wf2d).click(screen_abort);
    return false;
}


// ==========================================================================
// the ajax stuff for the question link :)
// ==========================================================================

function changeQboxLayout(qbox, color, qhtml) {
    qbox
        .attr('id', 'qbox')
        .addClass('qbox-class')
        .css('background-image', '')
        .css('cursor', 'pointer')
        .css('-moz-border-radius', '9px')
        .css('-webkit-border-radius', '9px')
        .css('border-radius', '9px')
        .css('border', '3px solid ' + color)
        .click(info_screen)
    .find('table')
        .css('height', '100%')
    .find('td')
        .html(qhtml);
}

function pimpAjax(qbox) {
    // now get the damn question id from that %&§/&! question id page
    // FIRST, find out whether we're working with HTTPS or HTTP
    // and use the same ... 
    var my_url = location.href;
    var my_url = my_url.substr(0, my_url.indexOf('://'))
        + '://training.cat-europe.com/ShowQuestionInfo.aspx';
    // SECOND, do the ajax call
    $.ajax(
        {
            url: my_url,
            cache: false,
            dataType: 'html', 
            success: function(data) {
                // kill all newline & CR in here, otherwise regexp search & 
                // replace will fail. bloody shit.
                data = data
                    .replace(/\n/g, "")
                    .replace(/\s/g, " ");
                // extract EVERYTHING INSIDE BODY as regexp
                // for jquery to be able to parse it.
                // bloody hellfire fucked up apeshit
                var body = $(data.match(/<body[^>]*>(.*)<\/body>/)[1]);
                var editor_id = body.find(
                    'table > tbody > '
                    + 'tr:nth-child(2) > td:nth-child(2)')
                    .text()
                    .trim();
                var url_base = 
                    body.find(
                    'table > tbody > '
                    + 'tr:nth-child(5) > td:nth-child(2)')
                    .text()
                    .trim();
                var url = url_base
                    .replace(/[^A-Za-z0-9]/g, "_")
                    .replace(/__/g, "_")
                    .replace(/_*$/, "");
                var color = '#76c276';
                var qnum = qbox.text().trim();
                var qhtml = qnum;
                if (editor_id != qnum) {
                    color = "orange";
                    qhtml = '<span style="color: #AAA">'
                        + qnum 
                        + '</span><br><small>('
                        + editor_id
                        + ')</small>';
                }
                $('head > title', document).html('CAT Q'+editor_id);
                changeQboxLayout(qbox, color, qhtml, url);
                // now add the "wiki-w"
                var after = $('<div id="wikiClickEnabled"\
                    alt="' + url_base + '"> \
                    <table width="100%" height="100%"> \
                        <tr><td>W</td></tr> \
                    </table></div>')
                    .attr('url', url)
                    .click(function() { goToWiki(); });
                qbox.after(after);
            }, 
            error: function() {
                changeQboxLayout(qbox, '#cc6363', qbox.text());
            }   
        }
    );
}

function goToWiki(edit) {
    var wf2d = window.frames[2].document;
    var target = $('#wikiClickEnabled', wf2d);
    var url_mid = ""; 
    if (edit) {
        url_mid = "page/edit/";
    }
    if (target.size()) {
        var url_end = target.attr('url');
        if (url_end != "") {
            open('http://lbastudentwiki.wikispaces.com/' + url_mid + url_end);
        }
    }
    return false;
}


// ==========================================================================
// Click on attachment tabs
// ==========================================================================

// test question: MET Q872
function attTab(offset) {
    var att_cur = $('.a_col_active', window.frames[2].document);
    if (att_cur.size()) {        
        var n_id = '#a_col' 
            + (parseInt(att_cur.attr('id').substr(5,6))+offset).toFixed(0);
        att_cur.removeClass('a_col_active');
    } else {
        var n_id = '#a_col1';
    }
    clickOn(
        $(n_id, window.frames[2].document)[0], 
        window.frames[2].document);
    $(n_id, window.frames[2].document).addClass('a_col_active');
    return false;
}


// ==========================================================================
// Keyboard navigation functions
// ==========================================================================

lastkeypress = 0;
function k()
{
    var c = new Date();
    if (c - lastkeypress < 25) { 
        logDate('Aborting keypress. Delta='+(c-lastkeypress).toString());
        return false; 
    }
    lastkeypress = c;
    return true;
}

function sel(num) {
    $('.q-answer[qpos="'+num.toString()+'"]', window.frames[2].document)
        .addClass('q-sel')
        .find(':input').prop('checked', true);
    return false;
}

function uns() {
    $('.q-answer', window.frames[2].document)
        .removeClass('q-sel')
        .find(':input')
        .attr('checked', false)
        .removeProp('checked');
    return false;
}

function advanceCursor(direction) {
    var q = $('.q-sel', window.frames[2].document);
    var a = $('.q-answer', window.frames[2].document);
    var t;
    if (q.size() == 0) {
        t = direction>0 ? 0 : a.size()-1;
    } else {
        t = parseInt(q.eq(0).attr('qpos')) + direction;
    }
    // now simply do it :)
    uns();
    return sel(t);
}

function scroll_current() {
    var n = initNeighbors();
    n = filterNeighbors(n);
    if (n[1].length) {
        goTo(n[1][0], window.frames[1]);
    }
    return false;
}


// ==========================================================================
// Keyboard shortcut initializations
// ==========================================================================

function logDate(logmessage) {
   console.log(new Date().toLocaleFormat('%Y-%m-%d %H:%M.%S') + ": " + logmessage);
}

in_pimpKeyboardShortcuts = 0;
function pimpKeyboardShortcuts() {
    var wf2d = window.frames[2].document;
    // var it = [];
    // for (var i=0; i<window.frames.length; i++) it.push(window.frames[i].document);
    // // TODO is it really necessary to push the document itself? 
    // //it.push(document);
    // for (var i=0; i<it.length; i++) {
        // curr = it[i];
    if (in_pimpKeyboardShortcuts || 
        $('html', wf2d).hasClass('pimped-keyboard')) { return; }
    in_pimpKeyboardShortcuts = 1;
    logDate('pimpKeyboardShortcuts()');
    // navigation keys - n,p,+,-,r,return
    var ret = 'return';
    var srt = 'shift+return';
    var shb = 'shift+b';
    $(wf2d).bind('keydown', 'n', function() { return k() && key_nf(1); });
    $(wf2d).bind('keydown', 'p', function() { return k() && key_nf(0); });
    $(wf2d).bind('keydown', 'r', function() { return k() && current(); });
    $(wf2d).bind('keydown', ret, function() { return k() && key_enter(true); });
    $(wf2d).bind('keydown', srt, function() { return k() && key_enter(false); });
    $(wf2d).bind('keydown', '-', function() { return k() && key_nf(0, [2,0,0,0]); });
    $(wf2d).bind('keydown', '+', function() { return k() && key_nf(1, [2,0,0,0]); });
    $(wf2d).bind('keydown', 'b', function() { return k() && key_nf(0, [0,0,0,2]); });
    $(wf2d).bind('keydown', shb, function() { return k() && key_nf(1, [0,0,0,2]); });
    // up / down keyboard navigation
    $(wf2d).bind('keydown', 'down', function() { return k() && advanceCursor(1); });
    $(wf2d).bind('keydown', 'up', function() { return k() && advanceCursor(-1); });
    // ESC
    $(wf2d).bind('keydown', 'esc', function() { 
        return k() && !screen_abort() && uns(); 
    });
    // the keys 1-4 for the direct selection
    for (var j=0; j<4; j++) {
        // TODO DO NOT LIKE THIS
        let j_num = j;
        let j_key = (j+1).toString();
        $(wf2d).bind('keydown', j_key, function() { 
            return k() && !uns() && sel(j_num); 
        });
    }
    $(wf2d).bind('keydown', '0', function() { return k() && uns(); });
    // Go to wiki :)
    var shw = "shift+w";
    $(wf2d).bind('keydown', 'w', function() { return k() && goToWiki(); }); 
    $(wf2d).bind('keydown', shw, function() { return k() && goToWiki(1); }); 
    // filtering of answers - keys shift-1 .. shift-4, shift-0
    $(wf2d).bind('keydown', 'shift+1', function() { return k() && dfil(0); });
    $(wf2d).bind('keydown', 'shift+2', function() { return k() && dfil(1); });
    $(wf2d).bind('keydown', 'shift+3', function() { return k() && dfil(2); });
    $(wf2d).bind('keydown', 'shift+4', function() { return k() && dfil(3); });
    $(wf2d).bind('keydown', 'shift+5', function() { 
        dfil_apply_filter([0,0,0,0]); 
        return false;
    });
    $(wf2d).bind('keydown', 'shift+6', function() { return k() && set_reset(); });
    $(wf2d).bind('keydown', 'shift+0', function() { return k() && dfil_reapply(); });
    // the different screens - help, set filter, go to, info
    $(wf2d).bind('keydown', 'h', function() { return k() && help_screen(); });
    $(wf2d).bind('keydown', 'q', function() { return k() && set_form(); });
    $(wf2d).bind('keydown', 'g', function() { return k() && goto_screen(); });
    $(wf2d).bind('keydown', 'c', function() { return k() && scroll_current(); });
    $(wf2d).bind('keydown', 'i', function() { return k() && info_screen(); });
    $(wf2d).bind('keydown', 'v', function() { return k() && visi_screen(); });
    // nav bar mappers - left, right, m, s, f
    $(wf2d).bind('keydown', 'left',  function() { return k() && clickNB(1); });
    $(wf2d).bind('keydown', 'right', function() { return k() && clickNB(2); });
    $(wf2d).bind('keydown', 's',     function() { return k() && clickNB(3); });
    $(wf2d).bind('keydown', 'm',     function() { return k() && clickNB(4); });
    $(wf2d).bind('keydown', 'f',     function() { return k() && clickNB(9); });
    // attachment mappers
    $(wf2d).bind('keydown', "shift+left", function() { return k() && attTab(-1); });  
    $(wf2d).bind('keydown', "shift+right", function() { return k() && attTab(1); });  
    $('html', wf2d).addClass('pimped-keyboard');    
    in_pimpKeyboardShortcuts = 0;
}

function pimpInputBoxes(answers) {
    var ret = "return";
    var srt = "shift+return";
    var ctrlandl = 'ctrl+left';
    var ctrlandr = 'ctrl+right';
    answers.each( function(idx) {
        // make TAB work ...
        $(this).bind('keydown', 'tab', function() { 
            let let_idx = idx;
            if (let_idx == answers.length - 1) {
                answers[0].focus();
            } else {
                answers[let_idx + 1].focus();
            }
        });
        // bind nav stuff
        $(this).bind('keydown', ret, function() { return k() && key_enter(true); });
        $(this).bind('keydown', srt, function() { return k() && key_enter(false); });
        $(this).bind('keydown', 'ctrl+n', function() { return k() && key_nf(1); });
        $(this).bind('keydown', 'ctrl+p', function() { return k() && key_nf(0); });
        $(this).bind('keydown', 'ctrl+s', function() { return k() && clickNB(3); });
        $(this).bind('keydown', 'ctrl+m', function() { return k() && clickNB(4); });
        $(this).bind('keydown', 'ctrl-c', function() { return k() && scroll_current(); });
        $(this).bind('keydown', 'ctrl+f', function() { return k() && clickNB(9); });
        $(this).bind('keydown', ctrlandl, function() { return k() && clickNB(1); });
        $(this).bind('keydown', ctrlandr, function() { return k() && clickNB(2); });
        // screens & wiki
        $(this).bind('keydown', 'ctrl+h', function() { return k() && help_screen(); });
        $(this).bind('keydown', 'ctrl+g', function() { return k() && goto_screen(); });
        $(this).bind('keydown', 'ctrl+i', function() { return k() && info_screen(); });
        $(this).bind('keydown', 'ctrl+v', function() { return k() && visi_screen(); });
        $(this).bind('keydown', 'ctrl+q', function() { return k() && set_form(); });
        $(this).bind('keydown', 'ctrl+w', function() { return k() && goToWiki(); });
        // attachment convenience :)
        $(this).bind('keydown', "shift+right", function() { return k() && attTab(1); });  
        $(this).bind('keydown', "shift+left", function() { return k() && attTab(-1); });  
    });
}

function pimpCheckboxes(answers) {
    var wf2d = window.frames[2].document;
    // bubblesort
    var swapped = true;
    var order = []; 
    for (var i=0; i<answers.size(); i++) { order.push(i); }
    while (swapped) {
        swapped = false;
        for (i=0; i<answers.size()-1; i++) {
            t0 = parseInt(answers.eq(order[i  ]).css('top'));
            t1 = parseInt(answers.eq(order[i+1]).css('top'));
            if (t0>t1) {
                swapped = true;
                var tmp = order[i];
                order[i] = order[i+1];
                order[i+1] = tmp;
            }
        }
    }
    // now answers is sorted, the biggest element should be at the end
    // ONLY preparation of answers and mouse pimping here ... 
    for (var i=0; i<order.length; i++) {
        // FORMERLY see here: http://mzl.la/dIqEqA
        var current = answers.eq(order[i]);
        // add the question position here :)
        current
            .attr('qpos', i.toString())
            .addClass('q-answer');
        // now add the mouse highlights and clicks
        current.find('table table').click( function () {
            var newval = ! $(this).find(':input').attr('checked');
            var mypar = $(this).closest('.q-answer');
            // uncheck everything else
            $('.q-sel', window.frames[2].document).removeClass('q-sel');
            $('.q-answer :input', window.frames[2].document)
                .attr('checked', false)
                .removeProp('checked');
            // now check or uncheck :)
            if (newval) {
                mypar.addClass('q-sel');
            }
            mypar.find(':input').prop('checked', newval);
            return false;
        });
        current.find('table table').dblclick( function () {
            $('.q-sel', window.frames[2].document).removeClass('q-sel');
            $(this).addClass('q-sel');
            $(this).find(':input').attr('checked', true);
            key_enter(true);
            return false;
        });
        current.hover( function() {
            $(this).addClass('q-high');
        }, function () {
            $(this).removeClass('q-high');
        });
        // move "cursor" over pre-selected answer if present
        if (current.find(':input').prop('checked')) {
            current.addClass('q-sel');
        }
    }
}

function pimpAnswers() {
    var wf2d = window.frames[2].document;
    // get the real question id from the info terminal :)
    if (! $('html', wf2d).hasClass('id-present')) {
        qbox = getQBox();
        if (qbox) {
            logDate('pimpAnswers() - pimping AJAX');
            pimpAjax(qbox); 
            logDate('pimpAnswers() - pimping AJAX DONE');
            $('html', wf2d).addClass('id-present');
        }
    }
    // if we did that already - just exit.
    if ($('html', wf2d).hasClass('fully-question-pimped')) {
        return;
    }
    // we can't kill that calculator shit, cause then the annexes
    // won't work for *some* reason. This is the most incompetent
    // and fucked up pice of shit work I have ever seen. 
    // MAYBE this was the memory leak source
    $('#calculator', wf2d).find('input').remove();
    // now start.
    var answers = $('form input:checkbox', wf2d).closest('div');
    if (answers.size() == 4) {
        logDate('pimpAnswers() - pimping checkboxes');
        // just assume the answer number is 4 and perform a little bubblesort.
        pimpCheckboxes(answers);
        $('html', wf2d).addClass('fully-question-pimped');
    } else {
        answers = $('form div input:text', wf2d);
        if (answers.size()) {
            logDate('pimpAnswers() - pimping input boxes');
            pimpInputBoxes(answers);
            $('html', wf2d).addClass('fully-question-pimped');
        }
    }
}


// ==========================================================================
// Core functionality & style injection
// ==========================================================================

function checkInjectedStyles() {
    // style for the question's mouseover highlight
    var wf1d = window.frames[1].document;
    var wf2d = window.frames[2].document;
    var h;
    // first frame - hiding of questions
    h = $('head', wf1d);
    if (! h.hasClass('pimp_styles')) {
        logDate('inject styles wf1d');
        s = '\
            <style type="text/css"> \
                .hide-row { \
                    display: none; \
                } \
            </style>';
        $(s).appendTo(h);
        h.addClass('pimp_styles');
    }
    // second frame - styles for the questions (mouseover, keyboard select)
    h = $('head', wf2d);
    if (! h.hasClass('pimp_styles')) {
        logDate('inject styles wf2d');
        s1 = '\
            <style type="text/css"> \
                .infobox { \
                    position: absolute; \
                    display: block; \
                    bottom: 30px; \
                    margin-left: auto; \
                    margin-right: auto; \
                    z-index: 10000; \
                    font-family: verdana, sans \
                    } \
            </style>';
        s2 = '\
            <style type="text/css"> \
                .q-high { \
                    background-color: #cccccc; \
                    -moz-border-radius: 0.5em; \
                    -webkit-border-radius: 0.5em; \
                    border-radius: 0.5em; \
                    } \
                .q-sel { \
                    background-color: #8ee88e !important; \
                    -moz-border-radius: 0.5em; \
                    -webkit-border-radius: 0.5em; \
                    border-radius: 0.5em; \
                    } \
                </style>';
        s3 = '\
            <style type="text/css"> \
                .hide-row { \
                    display: none; \
                } \
            </style>';
        s4 = '\
            <style type="text/css"> \
                #help_table { \
                    font-family: verdana; \
                    font-size: 0.9em; \
                    border-spacing: 0px; \
                } \
            </style>';
        s5 = '\
            <style type="text/css"> \
                #help_table td, #help_table th { \
                    padding-top: 5px; \
                    padding-bottom: 5px; \
                    padding-right: 5px; \
                    border-bottom: 2px solid #aaaaaa; \
                    border-collapse: collapse; \
                } \
            </style>';
        s6 = '\
            <style type="text/css"> \
                #message_table { \
                    margin-left: auto; \
                    margin-right: auto; \
                    font-family: verdana; \
                    padding-top: 50px; \
                    padding-bottom: 5px; \
                } \
            </style>';
        s7 = '\
            <style type="text/css"> \
                .pmc_input_table { \
                    position: absolute; \
                    top: 75px; \
                    margin-left: 12.5%; \
                    font-family: verdana; \
                    padding: 15px; \
                    background-color: #ccc; \
                    border: 2px solid #666; \
                    width: 75%; \
                } \
            </style>';
        s75 = '\
            <style type="text/css"> \
                .pmc_input_table td { \
                    padding-top: 7px; \
                    padding-bottom: 7px; \
                } \
            </style>';
        s8 = '\
            <style type="text/css"> \
                #pmc_set_table td, #pmc_set_table th  { \
                    border-style: none; \
                    padding: 15px; \
                } \
            </style>';
        s9 = '\
            <style type="text/css"> \
                #wikiClickEnabled { \
                    -moz-border-radius: 15px; \
                    border-radius: 15px; \
                    background-color: 76c276; \
                    position: absolute; \
                    top: 90px; \
                    left: 21px; \
                    width: 30px; \
                    height: 30px; \
                    cursor: pointer; \
                } \
            </style>';
        s10 = '\
            <style type="text/css"> \
                #wikiClickEnabled td { \
                    font-family: Verdana; \
                    font-weight: bold; \
                    text-align: center; \
                    color: white; \
                    vertical-align: middle; \
                    align: center; \
                } \
            </style>';
        $(s1).appendTo(h);
        $(s2).appendTo(h);
        $(s3).appendTo(h);
        $(s4).appendTo(h);
        $(s5).appendTo(h);
        $(s6).appendTo(h);
        $(s7).appendTo(h);
        $(s75).appendTo(h);
        $(s8).appendTo(h);
        $(s9).appendTo(h);
        $(s10).appendTo(h);
        h.addClass('pimp_styles');
    }
    h = $('head', window.frames[0].document);
    if (! h.hasClass('pimp_styles')) {
        logDate('inject styles wf0d');
        s01 = '\
            <style type="text/css"> \
                #pimpmyCAT { \
                    font-family: Arial; \
                    color: #cccccc; \
                    font-size: 8pt; \
                    position: fixed; \
                    top: 2px; \
                    right: 5px; \
                    width: 250px; \
                    padding: 2px; \
                } \
            </style>';
        s02 = '\
            <style type="text/css"> \
                #pimpmyCAT table { \
                    color: #cccccc; \
                    font-size: 8pt; \
                    width: 100%; \
                    border: 0px; \
                    padding: 0px; \
                    margin: 0px; \
                } \
            </style>';
        $(s01).appendTo(h);
        $(s02).appendTo(h);
        h.addClass('pimp_styles');
    }  
}


// ==========================================================================
// THE MAGIC. THE PUMPING HEART.
// ==========================================================================

function update() {
    if (! perfUpdate) { return; }
    perfUpdate = false;
    withinLoop = true;
    try {    
        checkInjectedStyles();
        updateStatBox();        
        pimpAnswers();
        pimpKeyboardShortcuts();
    } catch (e) {
        alert('ERROR: '+e.message);
        logDate('ERROR: '+e.message);
    }
    withinLoop = false;
}

withinloop = false;
perfUpdate = true;
$(document).ready( function() {
    setInterval(update, 200);
    
    $(window.frames[1]).bind('DOMSubtreeModified', function() { 
        if (! withinLoop) { perfUpdate = true; }
        return false;
    });
});