/*
 * poto - A smooth and minimalistic WordPress theme
 *
 * Author: ais-design
 * License: GNU General Public License Version 3
 * License URI: http://www.gnu.org/licenses/gpl.html
 */

$(function() {

// Config

var WP_ROOT = '/wp/';

// Constants

var IMAGE_MAX_W = 960,
    IMAGE_MARGIN = 10,
    STAGE_CLEARANCE = 100,
    BAR_HEIGHT = 50,
    BODY_HEIGHT = 90,
    STD_RATIO = 960 / 720,
    WIDE_RATIO = 1940 / 720;

var wrapper = $('.slide-wrapper').css({position: 'absolute', left: '0'}),
    logo = $('h1').css('position', 'absolute');

// Extensions for jQuery

jQuery.fn.fallback = function(elem) {
    return 0 < this.length ? this : $(elem);
};


// Utility

var Util = function() {};

Util.POST_ID_PAT = /p\=(\d+)/;

Util.getPostId = function(url) {
    var m = Util.POST_ID_PAT.exec(url);
    if (m === null || m.length < 2) {
        return null;
    }
    try {
        return parseInt(m[1], 10);
    } catch (e) {}
    return null;
};

Util._norm = function(len, index) {
    if (index < 0) {
        index = len + index;
    } else if (len - 1 < index) {
        index = index - len;
    }
    return index;
};

Util.slice = function(list, start, end) {
    var ret = [], len = list.length;

    start = Util._norm(len, start);
    end = Util._norm(len, end);

    if (start === end) {
        return [];
    } else if (end < start) {
        ret = list.slice(start);
        start = 0;
    }
    return ret.concat(list.slice(start, end));
};

Util.rid = function(list) {
    var start, end, len = list.length;
    if (arguments.length === 2) {
        start = arguments[1];
        end = start + 1;
    } else if (2 < arguments.length) {
        start = arguments[1];
        end = arguments[2];
    } else {
        return list;
    }

    start = Util._norm(len, start);
    end = Util._norm(len, end);

    if (start === end) {
        return list;
    } else if (start < end) {
        return list.slice(0, start).concat(list.slice(end));
    }
    return list.slice(end, start);
};


// Join: waits while loading resources and signals to them

var Join = function() {
    this.groups = {};
};

Join.TIMEOUT = 8000;

Join.WAIT = 'WAIT';
Join.ERROR = 'ERROR';
Join.DONE = 'DONE';

Join.prototype.wait = function(name, items) {
    var info = this.groups[name] = {
        name: name,
        items: {},
        total: 0,
        done: 0,
        triggered: false
    }, i, len = items.length;

    for (i = 0; i < len; i++) {
        info.items[items[i]] = Join.WAIT;
        info.total++;
    }
};

Join.prototype.notify = function(name, item, status) {
    if (typeof this.groups[name] === 'undefined') {
        return;
    }

    status = status || Join.DONE; // default value: Join.DONE

    var info = this.groups[name];
    if (typeof info.items[item] === 'undefined') {
        return;
    }

    info.items[item] = status;
    if (status === Join.DONE) {
        info.done++;
    }

    // console.log('Join.notify: ', JSON.stringify(info));

    if (info.total === info.done) {
        $(this).trigger('waitingdone', [info]);
        info.triggered = true;
    }
};

var join = new Join();


// State: keeps app states

var State = function() {
    this.post = null;
    this.list = $('.slide');
    this.open = false;

    this.init();
};

State.FIRST = 'FIRST';
State.LAST = 'LAST';
State.PREV = 'PREV';
State.NEXT = 'NEXT';
State.NTH = 'NTH';
State.TOP = 'TOP';

State.OPENING = 'OPENING'; // opening animation
State.NORMAL = 'NORMAL'; // general animation
State.NONE = 'NONE'; // no animation

State.prototype.init = function() {
    var self = this;

    // determine post id to show on startup
    this.post = Util.getPostId(location.href.toString());
};

State.prototype.focus = function(options) {
    options = options || {};
    if (typeof options.anim === 'undefined') {
        options.anim = State.NORMAL;
    }
    if (typeof options.target === 'undefined' || options.target === null) {
        options.target = this.post || State.FIRST;
    }
    if (typeof options.history === 'undefined') {
        options.history = true;
    }

    // console.log('State.focus:', options);

    // determine item to focus
    var old = this.post, pos;
    switch (options.target) {
        case State.TOP:
        case State.FIRST:
            this.post = loader.get(State.FIRST);
            break;
        case State.LAST:
            this.post = loader.get(State.LAST);
            break;
        case State.PREV:
            pos = loader.find(this.post);
            if (0 < pos) {
                this.post = loader.get(pos - 1);
            }
            break;
        case State.NEXT:
            pos = loader.find(this.post);
            if (pos < loader.len() - 1) {
                this.post = loader.get(pos + 1);
            } else if (pos === loader.len() - 1 && !renderer.isSliding()) {
                this.post = loader.get(State.FIRST); // rewind to the first image
            }
            break;
        case State.NTH:
            pos = typeof options.param === 'undefined' ? 0 : options.param;
            if (loader.len() - 1 < pos) {
                this.post = loader.get(State.LAST);
            } else {
                this.post = loader.get(pos);
            }
            break;
        default:
            this.post = options.target;
    }

    if (typeof loader.posts[this.post] === 'undefined') {
        // show the first image if not found
        this.post = loader.get(State.FIRST);
    }

    // console.log('State.focus.params', pos, this.post, old);

    if (old !== this.post) {
        // render images
        var opt = {};
        switch (options.anim) {
            case State.NONE:
                opt.anim = false;
                opt.prefetch = false;
                break;
            case State.OPENING:
                opt.duration = 1000;
                opt.easing = 'easeInOutCubic';
                break;
        }

        renderer.render(opt);

        // push to history
        if (options.history && typeof history.pushState === 'function') {
            var url = location.pathname;
            if (options.target !== State.TOP || this.post !== loader.get(State.FIRST)) {
                url += '?p=' + this.post;
            }
            history.pushState(null, null, url);
        }
    }
};

State.prototype.has = function(target) {
    var pos = loader.find(this.post);
    if (pos === -1) {
        return false;
    }
    switch (target) {
        case State.PREV:
            return loader.get(pos - 1) !== false;
        case State.NEXT:
            return loader.get(pos + 1) !== false;
    }
    return false;
};

var state = new State();


// Post Loader: loads and manages WordPress posts

var PostLoader = function() {
    this.posts = {};
    this.order = [];

    this.count = 1;
    this.total = null;
    this._loading = false;
    this._first = false;

    this.init();
};

PostLoader.NUM_LOAD = 200;

PostLoader.SINGLE = 'SINGLE';
PostLoader.RANGE = 'RANGE';
PostLoader.OLDER = 'OLDER';
PostLoader.NEWER = 'NEWER';

PostLoader.prototype.init = function() {
    this.load();
};

PostLoader.prototype.load = function() {
    // console.log('PostLoader.load.start', this.count, this.total, this.order, state.post);

    var self = this;

    if (!this._first && $.inArray(state.post, this.order) !== -1) {
        // console.log('PostLoader.load.found');
        this._first = true;
        $(this).trigger('datafound');
    }

    if (this._loading) {
        return; // already started
    }

    if (this.total !== null && this.total < this.count) {
        // console.log('PostLoader.load.complete', this.order);
        $(this).trigger('dataloaded');
        return; // already loaded
    }

    this._loading = true;

    $.ajax({
        type: 'GET',
        url: WP_ROOT,
        data: {
            json: 'get_recent_posts',
            count: PostLoader.NUM_LOAD,
            page: this.count,
            date_format: 'Y.m.d'
        },
        dataType: 'json',
        success: function(data) {
            // store posts with filtering
            var valid = $(data.posts).filter(function() {
                return this.status === 'publish';
            }).each(function() {
                self.posts[this.id] = this;
                self.order.push(this.id);
            }).length;

            // store stats
            self.count++;
            self.total = data.pages;
            self._loading = false;

            // console.log('PostLoader.load.done', self.count, self.total, self.order, state.post);

            // load more
            setTimeout(function() {
                self.load();
            }, 10);
        },
        error: function(xhr) {
            // console.log(xhr);
        }
    });
};

PostLoader.prototype.isLoading = function() {
    return this._loading;
};

// get post ID or list of post IDs by post index
PostLoader.prototype.get = function(options) {
    // console.log('PostLoader.get: orig:', options);

    if (typeof options === 'number' || typeof options === 'string') {
        options = {target: options};
    } else {
        options = options || {};
    }

    if (typeof options.target === 'undefined') {
        return false;
    }
    if (typeof options.method === 'undefined') {
        options.method = PostLoader.SINGLE;
    }
    if (typeof options.num === 'undefined') {
        options.num = PostRenderer.BATCH_RENDER;
    }

    switch (options.target) {
        case State.FIRST:
            return this.order[0];
        case State.LAST:
            return this.order[this.order.length - 1];
    }

    if (options.method === PostLoader.SINGLE) {
        var ret = this.order[options.target];
        return typeof ret === 'number' ? ret : false;
    }

    console.log('PostLoader.get:', options);

    var self = this, pos = this.find(options.target), start, end, edge,
        find = function(ref, dir) {
            do {
                ref += dir === PostLoader.OLDER ? 1 : -1;
            } while ($('#wp-post-' + self.get(ref)).length);
            return ref;
        };
    switch (options.method) {
        case PostLoader.RANGE:
            start = pos - Math.floor(options.num / 2);
            end = start + options.num + 1;
            break;
        case PostLoader.OLDER:
            edge = find(pos, PostLoader.OLDER);
            start = edge;
            end = edge + options.num + 1;
            break;
        case PostLoader.NEWER:
            edge = find(pos, PostLoader.NEWER);
            start = edge - options.num + 1;
            end = edge + 1;
            break;
    }

    if (typeof start !== 'undefined' && typeof end !== 'undefined') {
        console.log('PostLoader.get: ok:', start, end);
        return Util.slice(this.order, start, end);
    }

    return false;
};

// get post index by post ID
PostLoader.prototype.find = function(post) {
    return $.inArray(post, this.order);
};

PostLoader.prototype.len = function() {
    return this.order.length;
};

var loader = new PostLoader();


// Input Mediator: receives user inputs and triggers UI responses

var InputMediator = function() {
    this.init();
};

InputMediator.prototype.init = function() {
    var self = this;

    // keyboard
    $(document).keydown(function(ev) {
        switch (ev.keyCode) {
            case 37: // left key
                $(self).trigger('prevtriggered');
                break;
            case 39: // right key
                $(self).trigger('nexttriggered');
                break;
        }
    });

    // prev/next buttons
    $('ul.ui > li > a', '#main').click(function(ev) {
        ev.preventDefault();
        switch ($(this).attr('href')) {
            case '#prev':
                $(self).trigger('prevtriggered');
                break;
            case '#next':
                $(self).trigger('nexttriggered');
                break;
        }
    });

    // logo button
    $('a', logo).click(function(ev) {
        ev.preventDefault();
        $(self).trigger('toptriggered');
    });
};

InputMediator.prototype.later = function() {
    var self = this;

    // browser's back/forward buttons
    $(window).bind('popstate', function() {
        var id = Util.getPostId(location.href.toString());
        $(self).trigger('postrequested', [id]);
    });
};

var input = new InputMediator();


// PostRenderer: renders WordPress posts

var PostRenderer = function() {
    this.list = $('.slide');
    this.date = $('div.title-wrapper > span.date');
    this.title = $('div.title-wrapper > span.title');
    this.btitle = $('head > title');
    this.bar = $('div.body-wrapper');
    this.body = $('div.body-wrapper > p.body');

    this.init();
};

PostRenderer.POS_FLOW = 8;
PostRenderer.MIN_RENDER = 12;
PostRenderer.BATCH_RENDER = 8;
PostRenderer.PROX_RANGE = 4;
PostRenderer.DUMMY_RANGE = 3;

PostRenderer.FORWARD = 'FORWARD';
PostRenderer.BACKWARD = 'BACKWARD';

PostRenderer.COLORS = {
    '*': '#808183',
    'kuroda': '#FF0077',
    'sasaki': '#39EB4A',
    'ikeya': '#33CCFF'
};

PostRenderer.prototype.init = function() {
    var self = this;

    $(join).bind('waitingdone', function(ev, info) {
        // console.log('PostRenderer.init: waitingdone: ', info);

        // setup stage
        var items = self.list.children('li:hidden').show();
        view.adjust();

        // prepare dummy posts
        var li = self.list.children('li[id]'), item, cl,
            cur = $.inArray(state.post, li.get());
            clone = function() {
                var candidates = Util.rid(li.get(), cur - PostRenderer.DUMMY_RANGE, cur + PostRenderer.DUMMY_RANGE),
                    pos, item, target = null;
                while (candidates.length) {
                    pos = Math.round(Math.random() * candidates.length);
                    item = $(candidates[pos]).children('img');
                    if (1 < item.width() / item.height()) {
                        target = item.parent();
                        break;
                    }
                    candidates = Util.rid(candidates, pos);
                }
                if (target === null) {
                    target = li.last();
                }
                return target.clone().removeAttr('id');
            };
        if (typeof self.list.children('li:first').attr('id') !== 'undefined') {
            self.list.children('li.first').not('li[id]').remove();
            clone().addClass('first').prependTo(self.list);
        }
        if (typeof self.list.children('li:last').attr('id') !== 'undefined') {
            self.list.children('li.last').not('li[id]').remove();
            clone().addClass('last').appendTo(self.list);
        }

        // start fading in
        items.hide();
        self.list.children('li').fadeIn(800);

        if (!state.open) {
            // rewind to N-th image (without animation)
            var post = state.post || State.FIRST;
            state.focus({
                anim: State.NONE,
                target: State.NTH,
                param: PostRenderer.POS_FLOW,
                history: false
            });

            // start gallery animation
            state.focus({
                anim: State.OPENING,
                target: post,
                history: false
            });

            console.log('PostRenderer.init: prog: ', self.list.children('li[id]').length, '/', PostRenderer.MIN_RENDER);

            if (PostRenderer.MIN_RENDER <= self.list.children('li[id]').length) {
                view.endLoading();
                input.later();
            }
        }
    });
};

PostRenderer.prototype.prepare = function(options) {
    options = options || {};
    if (typeof options.target === 'undefined' && typeof options.dir === 'undefined') {
        return;
    }

    // console.log('PostRenderer.prepare', options);

    // check whether the target is existing or not
    if ($('#wp-post-' + options.target).length) {
        // console.log('PostRenderer.prepare: existing: ', options.target);
        return;
    }

    var self = this,
        i, post, url, len, start, posts,
        loaded = function() {
            var item = $(this).data('wp-post');
            // console.log('PostRenderer.prepare: loaded: ', item);
            join.notify(handle, item);
        },
        search = function(content) {
            var img = content.find('a > img');
            if (img.length) {
                var href = img.parent().attr('href') || '';
                if (href.length) {
                    return href;
                }
            }
            img = content.find('img');
            if (img.length) {
                var src = img.attr('src') || '';
                if (src.length) {
                    return src;
                }
            }
        },
        handle = 'img_' + (new Date()).getTime();

    // determine posts which should be loaded
    if (typeof options.target !== 'undefined') {
        posts = loader.get({
            method: PostLoader.RANGE,
            target: state.post,
            num: PostRenderer.MIN_RENDER
        });
    } else if (options.dir === PostRenderer.FORWARD) {
        posts = loader.get({
            method: PostLoader.OLDER,
            target: state.post
        });
    } else if (options.dir === PostRenderer.BACKWARD) {
        posts = loader.get({
            method: PostLoader.NEWER,
            target: state.post
        });
    }

    console.log('PostRenderer.prepare: posts', posts);

    // setup for waiting until requested images are loaded
    join.wait(handle, posts);

    // insert HTML structure of posts
    var dict = {};
    for (i = 0, len = posts.length; i < len; i++) {
        post = loader.posts[posts[i]];
        if (!post || $('#wp-post-' + post.id).length) {
            continue;
        }
        post.dom = $(post.content);
        url = search(post.dom);
        if (!url || !url.length) {
            continue;
        }
        dict[post.id] = $('<li/>').attr('id', 'wp-post-' + post.id)
                                  .css('display', 'none')
                                  .append($('<img/>').data('wp-post', post.id)
                                                     .load(loaded)
                                                     .attr('src', url));
    }

    // sort posts base on the order of JSON data
    posts = $.merge(this.list.find('li[id] > img').map(function() {
        return $(this).data('wp-post');
    }).get(), posts);
    console.log('PostRenderer.prepare: before: ', posts);
    posts.sort(function(a, b) {
        return loader.find(a) - loader.find(b);
    });
    console.log('PostRenderer.prepare: after: ', posts);
    for (i = 0, len = posts.length; i < len; i++) {
        var id = posts[i];
        $('#wp-post-' + id).fallback(dict[id]).appendTo(this.list);
    }

    // reorder dummy posts
    this.list.children('.first').prependTo(this.list);
    this.list.children('.last').appendTo(this.list);
};

PostRenderer.prototype.render = function(options) {
    options = options || {};
    if (typeof options.anim === 'undefined') {
        options.anim = true;
    }
    if (typeof options.prefetch === 'undefined') {
        options.prefetch = true;
    }
    if (typeof options.duration === 'undefined') {
        options.duration = 400;
    }
    if (typeof options.easing === 'undefined') {
        options.easing = 'easeOutExpo';
    }

    console.log('PostRenderer.render', options);

    // shrink images
    var width, height, el, target;
    this.list.find('img').each(function() {
        el = $(this).removeAttr('width').removeAttr('height');
        width = el.width();
        height = el.height();
        $(this).attr({
            width: (view.baseHeight / height) * width,
            height: view.baseHeight
        });
    });

    target = $('#wp-post-' + state.post + ' > img');
    if (target.length) {
        // center the image
        var first = this.list.find('li:first img'),
            left = (view.stageWidth - target.width()) / 2,
            relLeft = target.position().left - first.position().left;
        if (options.anim === true) {
            this.list.clearQueue().animate({marginLeft: (left - relLeft) + 'px'},
                    options.duration, options.easing);
        } else {
            this.list.css({marginLeft: (left - relLeft) + 'px'});
        }

        // show date/title/body of the post
        var post = loader.posts[target.data('wp-post')],
            author = post.author.name;
        this.date.text(post.date);
        this.title.html(post.title);
        this.btitle.html(post.title + WP_BLOGNAME);
        this.body.html(post.dom.filter(function() {
            return $(this).is('p') && $(this).find('img').length === 0;
        }).map(function() {
            return $(this).html();
        }).get().join('<br>'));

        // change color of bar along with post author
        this.bar.css('border-left-color',
                     typeof PostRenderer.COLORS[author] === 'undefined' ? PostRenderer.COLORS['*']
                                                                        : PostRenderer.COLORS[author]);
    }

    if (options.prefetch) {
        var pos = loader.find(state.post),
            lower = pos - PostRenderer.PROX_RANGE,
            higher = pos + PostRenderer.PROX_RANGE, item;

        if (lower < 0) {
            lower = 0;
        }

        if (loader.len() - 1 < higher) {
            higher = loader.len() - 1;
        }

        // console.log('PostRenderer.render: prefetch: ', pos + ' (' + state.post + '), ', lower + ' (' + loader.get(lower) + '), ', higher + ' (' + loader.get(higher) + ')');

        item = loader.get(lower);
        if (item !== -1 && $('#wp-post-' + item).length === 0) {
            // console.log('PostRenderer.render: load (new)');
            this.prepare({
                dir: PostRenderer.BACKWARD
            });
        }

        item = loader.get(higher);
        if (item !== -1 && $('#wp-post-' + item).length === 0) {
            // console.log('PostRenderer.render: load (old)');
            this.prepare({
                dir: PostRenderer.FORWARD
            });
        }
    }
};

PostRenderer.prototype.isSliding = function() {
    return this.list.is(':animated');
};

var renderer = new PostRenderer();


// Env: notifier of environment information

var Env = function() {
    this.init();
};

Env.prototype.init = function() {
    var self = this,
        ua = navigator.userAgent.toLowerCase();

    if (ua.search('iphone') !== -1 || ua.search('ipad') !== -1 ||
            ua.search('ipod') !== -1 || ua.search('android') !== -1) {
        this.isMobile = true;
    } else {
        this.isMobile = false;
    }
};

var env = new Env();


// View: setups the stage and manages the client application

var View = function() {
    this.list = $('.slide');
    this.prev = $('.ui > li.prev > a > span');
    this.next = $('.ui > li.next > a > span');
    this.body = $('.body-wrapper');
    this.menu = $('.menu-wrapper');
    this.social = $('#menu-social');

    this.nav = false;

    this.init();
};

View.DURATION = 400;

View.prototype.init = function() {
    var self = this,
        visible = function() {
            if (env.isMobile || !env.isMobile && self.nav) {
                if (state.has(State.PREV)) {
                    self.prev.fadeIn(View.DURATION);
                } else {
                    self.prev.fadeOut(View.DURATION);
                }
                if (state.has(State.NEXT)) {
                    self.next.fadeIn(View.DURATION);
                } else {
                    self.next.fadeOut(View.DURATION);
                }
            }
        };

    $(loader).bind('datafound dataloaded', function() {
        renderer.prepare({
            target: state.post || State.FIRST
        });
    });

    $(input).bind('toptriggered', function() {
        state.focus({
            target: State.TOP
        });
        visible();
    });

    $(input).bind('prevtriggered', function() {
        state.focus({
            target: State.PREV
        });
        visible();
    });

    $(input).bind('nexttriggered', function() {
        state.focus({
            target: State.NEXT
        });
        visible();
    });

    $(input).bind('postrequested', function(ev, postId) {
        postId = postId || State.FIRST;
        state.focus({
            target: postId,
            history: false
        });
    });

    $(window).resize(function() {
        self.adjust();
    });

    if (!env.isMobile) {
        // hide ui buttons
        this.prev.add(this.next).fadeOut(View.DURATION);

        // handle events of ui buttons
        $('.slide-wrapper').hover(
            function() {
                self.nav = true;
                self.prev.filter(':animated').stop(true, true);
                self.next.filter(':animated').stop(true, true);
                if (!state.has(State.PREV)) {
                    self.next.fadeIn(View.DURATION);
                } else if (!state.has(State.NEXT)) {
                    self.prev.fadeIn(View.DURATION);
                } else {
                    self.prev.fadeIn(View.DURATION);
                    self.next.fadeIn(View.DURATION);
                }
            },
            function() {
                self.nav = false;
                if (!state.has(State.PREV)) {
                    self.next.fadeOut(View.DURATION);
                } else if (!state.has(State.NEXT)) {
                    self.prev.fadeOut(View.DURATION);
                } else {
                    self.prev.fadeOut(View.DURATION);
                    self.next.fadeOut(View.DURATION);
                }
            }
        );
    }

    this.startLoading();
};

View.prototype.startLoading = function() {
    // cover viewport with black shade
    var w = $(window).width(),
        h = $(window).height();
    $('#shade-wrapper, #shade').css({
        width: w + 'px',
        height: h + 'px'
    }).show();

    // center loading indication
    var loading = $('#shade > img');
    loading.css({
        left: (w - loading.width()) / 2 + 'px',
        top: (h - loading.height()) / 2 + 'px'
    }).show();

    // back to white background
    $('html').css('background-color', 'white');
};

View.prototype.endLoading = function() {
    var self = this;

    state.open = true;

    $('#shade > img').fadeOut(300, function() {
        $('#shade').animate({
            top: $(window).height() + 'px'
        }, 500, 'easeOutCubic', function() {
            // hide shade
            $('#shade-wrapper').hide();

            // show elements
            logo.add(self.body).add(self.menu).add(self.social).fadeIn().delay(400, function() {
                if (env.isMobile) {
                    if (!state.has(State.PREV)) {
                        self.next.fadeIn(View.DURATION);
                    } else if (!state.has(State.NEXT)) {
                        self.prev.fadeIn(View.DURATION);
                    } else {
                        self.prev.fadeIn(View.DURATION);
                        self.next.fadeIn(View.DURATION);
                    }
                }
            });
        });
    });
};

View.prototype.adjust = function() {
    // console.log('View.adjust');

    var h = $(window).height() - BAR_HEIGHT,
        w = $(window).width(),
        logoHeight = (w - 1070) / 34 + 50,
        gapHeight = 0.6 * logoHeight - 10;
    this.stageWidth = w - 2 * STAGE_CLEARANCE;
    this.baseHeight = this.stageWidth / WIDE_RATIO;

    // check required height
    var reqHeight = this.baseHeight + logoHeight + BODY_HEIGHT + 3 * gapHeight + BAR_HEIGHT;
    if (h < reqHeight) {
        // recalc bases
        this.baseHeight = h - 3 * gapHeight - BODY_HEIGHT - logoHeight - BAR_HEIGHT;
        this.stageWidth = this.baseHeight * WIDE_RATIO;
    }

    // centering/resizing stage
    var stageTop = (h - this.baseHeight) / 2,
        stageLeft = (w - this.stageWidth) / 2;
    wrapper.css({
        top: stageTop + 'px',
        left: stageLeft + 'px',
        width: this.stageWidth + 'px'
    });

    // positioning/shrinking logo
    logo.find('img').attr({
        height: logoHeight
    });
    logo.css({
        top: (stageTop - gapHeight - logoHeight) + 'px',
        left: stageLeft + 'px'
    });

    // positioning menus
    var stdWidth = this.baseHeight * STD_RATIO,
        menuLeft = (w - stdWidth) / 2;
    this.menu.css('padding-left', menuLeft + 'px');
    this.social.css('padding-right', stageLeft + 'px');

    // positioning body
    this.body.css({
        top: stageTop + this.baseHeight + gapHeight + 'px',
        left: menuLeft + 'px',
        width: (this.stageWidth - (menuLeft - stageLeft) - 2 * IMAGE_MARGIN) + 'px'
    });

    // positioning prev/next buttons
    this.prev.add(this.next).css('top', (this.baseHeight - 65) / 2 + 'px')
        .parents().css('height', this.baseHeight + 'px');

    // render images
    setTimeout(function() {
        renderer.render({
            anim: false,
            prefetch: false
        });
    }, 200);
};

var view = new View();

});

