Source: js/starlet.js

/** @namespace Starlet
    @desc      Handles loading of Starlet-specific content, ensuring all necessary information is present.
               Provide starlet identifier (directory) to html counterpart via hash, e.g. "starlet.html#/starletname".
               Ghost-chasing note: if seeing errors emanating from deep within jQuery (particularly its append method),
               it's likely a syntax error within the starlet/widget index.html/js. Starlet-specific content is loaded
               via ajax and errors in it will surface when the html/js is parsed. Logs won't indicate the actual script
               and line number because the content is "anonymous".
*/
var Starlet = Starlet ||
{
    /* properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
        
        // internal
            'lastEventTime': true,
            /** @member     {Array} Starlet~loadArguments
                @private
                @desc       Temporary array of args passed to synchronizer from Framework (name only).
                @deprecated since version 2.0.2
                @see        {@link Starlet~synchronizer}
            */
            'loadArguments': [],
            
            /** @member  {Object} Starlet~loadSpinner
                @private
                @desc    Initial load spinner instance. A curtain to hide the page layout until onRun.
                @see     {@link Starlet~synchronizer}
            */
            'loadSpinner': null,
            
            /** @member  {Object} Starlet~loadTimer
                @private
                @desc    Instance of TimerClass, measures time from Starlet creation to load event.
                @see     {@link Starlet~synchronizer}
            */
            'loadTimer': new TimerClass(),
            
            /** @member  {Boolean} Starlet~flagDefined
                @private
                @desc    Starlet synchronizer flag, indicates readiness of starlet behaviors to run.
                @see     {@link Starlet~synchronizer}
            */
            'flagDefined': false,
            
            /** @member  {Boolean} Starlet~flagDom
                @private
                @desc    Starlet synchronizer flag, indicates (safe) dom alterability.
                @see     {@link Starlet~synchronizer}
            */
            'flagDom': false,
            
            /** @member  {Boolean} Starlet~flagFramework
                @private
                @desc    Starlet synchronizer flag, indicates receipt of framework args.
                @see     {@link Starlet~synchronizer}
            */
            'flagFramework': false,
            
            /** @member  {Boolean} Starlet~flagLoaded
                @private
                @desc    Starlet synchronizer flag, indicates presence of essential assets
                         (structure/style/behavior, !media).
                @see     {@link Starlet~synchronizer}
            */
            'flagLoaded': false,
            
            /** @member  {Boolean} Starlet~flagCovered
                @private
                @desc    Starlet synchronizer flag, indicates whether content has been hidden from view for setup.
                @see     {@link Starlet~synchronizer}
            */
            'flagCovered': false,
            
            /** @member  {Number} Starlet~near1
                @private
                @desc    Threshold of first overlay handle mouse proximity effect (px).
                @see     {@link Starlet~onProximity}
            */
            'near1': 30,
            
            /** @member   {Number} Starlet~near2
                @private
                @desc     Threshold of second overlay handle mouse proximity effect (px).
                @see      {@link Starlet~onProximity}
            */
            'near2': 8,
            
            /** @member  {Boolean} Starlet~nearSb1
                @private
                @desc    Whether mouse is within first mouse proximity threshold for shoebox.
                @see     {@link Starlet~onProximity}
            */
            'nearSb1': false,
            
            /** @member  {Boolean} Starlet~nearSb2
                @private
                @desc    Whether mouse is within second mouse proximity threshold for shoebox.
                @see     {@link Starlet~onProximity}
            */
            'nearSb2': false,
            
            /** @member  {Boolean} Starlet~nearNav1
                @private
                @desc    Whether mouse is within first mouse proximity threshold for navigation.
                @see     {@link Starlet~onProximity}
            */
            'nearNav1': false,
            
            /** @member  {Boolean} Starlet~nearNav2
                @private
                @desc    Whether mouse is within second mouse proximity threshold for navigation.
                @see     {@link Starlet~onProximity}
            */
            'nearNav2': false,
            
            /** @member  {Number} Starlet~pageViewTimeout
                @private
                @desc    Google Analytics page tracking timeout identifier.
                @see     {@link Starlet~onPageView}
            */
            'pageViewTimeout': null,
            
        // read only
            
            /** @member   {Object} Starlet.connectivity
                @readonly
                @desc     Hashlike object of connectivity states.
            */   
            'connectivity': {'xmpp': true, 'internet': true, 'exiting': false},
            
            /** @member   {Number} Starlet.loaded
                @readonly
                @desc     Time that starlet controller was loaded (ms timestamp).
            */
            'loaded': new Date().getTime(),
            
            /** @member   {String} Starlet.type
                @readonly
                @desc     Represents Framework-given permissions and behavior of WebView.
            */
            'type': 'trueted_starlet',
            
            /** @member   {String} Starlet.url
                @readonly
                @desc     Location of WebView.
            */
            'url': location.href,
            
            /** @member   {String} Starlet.root
                @readonly
                @desc     Server path to current starlet directory.
            */
            'root': PATH.substring(0, PATH.lastIndexOf('/') + 1) + HASH.substring(1),
            
            /** @member   {String} Starlet.name
                @readonly
                @desc     Framework's WebView handle.
            */
            'name': null,
            
            /** @member   {String} Starlet.identifier
                @readonly
                @desc     Current starlet directory.
            */
            'identifier': HASH.substring(1),
            
            /** @member   {String} Starlet.title
                @readonly
                @desc     Display name of starlet shown in header. Override in custom starlet definition.
                @see      {@link Starlet.define}
            */
            'title': '(Untitled Starlet)',
            
            /** @member   {Number} Starlet.width
                @readonly
                @desc     Width of starlet's WebView (px).
            */
            'width': 0,
            
            /** @member   {Number} Starlet.height
                @readonly
                @desc     Height of starlet's WebView (px).
            */
            'height': 0,
            
            /** @member   {Number} Starlet.order
                @readonly
                @desc     Order of last use. Used internally for restoring Framework state.
            */
            'order': -1,
            
            /** @member   {Number} Starlet.state
                @readonly
                @desc     Represents the successful loading, registration,
                          and readiness of starlet to receive instructions and data.
            */
            'state': S_OPENED,
            
            /** @member   {Boolean} Starlet.header
                @readonly
                @desc     Whether the starlet layout includes a header, override in custom starlet definition.
                @see      {@link Starlet.define}
            */
            'header': true,
            
            /** @member   {Object} Starlet.footerData
                @readonly
                @desc     Whether the starlet layout includes a footer,
                          and what variables were passed to the footer template.
                          Override in custom starlet definition.
                @see      {@link Starlet.define}
            */
            'footerData': {},
            
            /** @member   {Array} Starlet.widgets
                @readonly
                @desc     References to all Widget controller instances within Starlet.
            */
            'widgets': [],
            
            /** @member     {Boolean} Starlet.production
                @readonly
                @deprecated since version 2.0.2, use constant {@link PRODUCTION} instead.
                @desc       Indicates whether resources are to be loaded from Test server.
            */
            'production': !!~document.location.hostname.indexOf('.com'),
            
            /** @member   {String} Starlet.mode
                @readonly
                @desc     Will be 'prod', 'qa', or 'test', depending on the environment.
                @todo     Create QA constant.
            */
            'mode': !!~OrionFramework.apiURL.indexOf('.com')
                        ? 'prod'
                        :
                        (
                            !!~OrionFramework.apiURL.indexOf('web1-test')
                                ? 'qa'
                                : 'test'
                        ),
            
            /** @member   {String} Starlet.apiServerName
                @readonly
                @desc     API server name.
                @todo     Create API_HOST constant.
                @see      {@link OrionFramework.apiURL}
            */
            'apiServerName': OrionFramework.apiURL.replace(/http[s]*:\/\//i, '').replace(/:[0-9]{1,4}/i, ''),
        
        // event based states
            
            /** @member   {Object} Starlet.states
                @readonly
                @desc     Hashlike grouping of flags relating to user interactions.
                
                @property {Boolean} main        - Whether current starlet is anchored in the main application window.
                @property {Boolean} overlay     - Whether current starlet is an overlay with specific behavior and
                                                  extended permissions (shoebox, navigation, notification).
                @property {Boolean} lightbox    - Whether current starlet is anchored in a lightbox.
                                                  See {@link Starlet.lightbox}.
                @property {Boolean} ordered     - Whether current starlet is part of the normal navigation cycle
                                                  (anchored in main window, not an overlay or lightbox).
                @property {Boolean} tearoff     - Whether current starlet has been switched from being anchored in the
                                                  main window to a dedicated one. See {@link Starlet.tearoff}.
                @property {Boolean} minitearoff - Whether current starlet was initially loaded in a dedicated window.
                @property {Boolean} visible     - Whether current starlet is considered visible to the user.
                                                  See {@link Starlet.onVisible}, {@link Starlet~onVisibleWrapper}.
                @property {Boolean} active      - Whether current starlet is in a frontmost window
                                                  and is the primary focus.
                                                  See {@link Starlet.onActive}, {@link Starlet~onActiveWrapper}.
                @property {Boolean} focused     - Whether current starlet's anchoring window has focus.
                                                  See {@link Starlet.onFocused}, {@link Starlet~onFocusedWrapper}.
                @property {Boolean} minimized   - Whether current starlet's anchoring window is minimized.
                                                  See {@link Starlet.onMinimized}, {@link Starlet~onMinimizedWrapper}.
                @property {Boolean} hidden      - Whether current starlet's anchoring window is hidden.
                                                  See {@link Starlet.onHidden}, {@link Starlet~onHiddenWrapper}.
                @property {Boolean} mouse       - Whether current starlet's WebView contains the mouse pointer.
                                                  See {@link Starlet.onMouse}, {@link Starlet~onMouseWrapper}.
            */
            'states':
            {
                'main'       : false,
                'overlay'    : false,
                'lightbox'   : false,
                'ordered'    : false,
                'tearoff'    : false,
                'minitearoff': false,
                'visible'    : false,
                'active'     : false,
                'focused'    : false,
                'minimized'  : false,
                'hidden'     : false,
                'mouse'      : false
            },
            
        // element references
            
            /** @member   {jQuery} Starlet.$html
                @readonly
                @desc     Reference to html element (root node).
            */
            '$html': $(),
            
            /** @member   {jQuery} Starlet.$title
                @readonly
                @desc     Reference to title element.
            */
            '$title': $(),
            
            /** @member   {jQuery} Starlet.$body
                @readonly
                @desc     Reference to body element.
            */
            '$body': $(),
            
            /** @member   {jQuery} Starlet.$content
                @readonly
                @desc     Reference to main content area (section#content element).
            */
            '$content': $(),
            
            /** @member   {jQuery} Starlet.$header
                @readonly
                @desc     Reference to header element.
            */
            '$header': $(),
            
            /** @member   {jQuery} Starlet.$footer
                @readonly
                @desc     Reference to footer element.
            */
            '$footer': $(),
            
            /** @member   {jQuery} Starlet.$spinner
                @readonly
                @desc     Reference to initial loading spinner (div.spinner element).
            */
            '$spinner': $(),
            
            /** @member   {jQuery} Starlet.$tint
                @readonly
                @desc     Reference to overlay tint (div#tint element).
                @see      {@link Starlet.tint}
            */
            '$tint': $(),
            
            /** @member   {jQuery} Starlet.$modal
                @readonly
                @desc     Reference to modal dialog container (div#modal) element.
                @see      {@link Starlet.modal}
            */
            '$modal': $(),
            
            /** @member   {jQuery} Starlet.$minishoebox
                @readonly
                @desc     Reference to minishoebox container (div#minishoebox element).
                @see      {@link Starlet.minishoebox}
            */
            '$minishoebox': $(),
            
            /** @member   {jQuery} Starlet.$menus
                @readonly
                @desc     Reference to dropdown menu container (div#menus element).
                @see      {@link Starlet.menu}
            */
            '$menus': $(),
    
    /* developer callbacks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
    
    /** @member   {callbackGeneric} Starlet.onRun
        @abstract
        @desc     Developer callback.
                  Behavioral starting point, fired on successful initialization of Starlet controller.
                  This includes complete loading of templates and scripts, but not multimedia or styles.
                  Will not be retriggered by network reconnection, see {@link Starlet.onReconnect} for this functionality.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onRun}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onDisconnect}
        @see      {@link Starlet.onReconnect}
        @see      {@link Starlet.onUnload}
        @see      {@link Starlet.onExit}
    */
    'onRun': function () {},
    
    /** @member   {callbackGeneric} Starlet.onDisconnect
        @abstract
        @desc     Developer callback.
                  Fired on termination of User Server XMPP connection.
                  Use this event to disable user interfaces that are not usable when offline.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onDisconnect}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onRun}
        @see      {@link Starlet.onReconnect}
        @see      {@link Starlet.onUnload}
        @see      {@link Starlet.onExit}
    */
    'onDisconnect': function () {},
    
    /** @member   {callbackGeneric} Starlet.onReconnect
        @abstract
        @desc     Developer callback.
                  Fired on reestablishment of User Server XMPP connection.
                  Use this event to refresh lists of data, and to reenable user interfaces that were disabled while offline.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onReconnect}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onRun}
        @see      {@link Starlet.onDisconnect}
        @see      {@link Starlet.onUnload}
        @see      {@link Starlet.onExit}
    */
    'onReconnect': function () {},
    
    /** @member   {callbackGeneric} Starlet.onUnload
        @abstract
        @desc     Developer callback.
                  Fired before unloading Starlet, expect no more than ~200ms before WebView stops.
                  Use this event to save state or preferences, and to perform cleanup.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onUnload}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onRun}
        @see      {@link Starlet.onDisconnect}
        @see      {@link Starlet.onReconnect}
        @see      {@link Starlet.onExit}
    */
    'onUnload': function () {},
    
    /** @member   {callbackGeneric} Starlet.onExit
        @abstract
        @desc     Developer callback.
                  Fired before unloading Starlet, but only when Framework is about to exit.
                  Expect no more than ~200ms before WebView stops.
                  Use this event to save state or preferences, and to perform cleanup.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onExit}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onRun}
        @see      {@link Starlet.onDisconnect}
        @see      {@link Starlet.onReconnect}
        @see      {@link Starlet.onUnload}
    */
    'onExit': function () {},
    
    /** @member   {callbackFrameworkEvent} Starlet.onVisible
        @abstract
        @desc     Developer callback. Fired on change of visibility of Starlet to the user.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onStarletVisible}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onActive}
        @see      {@link Starlet.onFocused}
        @see      {@link Starlet.onMinimized}
        @see      {@link Starlet.onHidden}
        @see      {@link Starlet.onMouse}
    */
    'onVisible': function (state) {},
    
    /** @member   {callbackFrameworkEvent} Starlet.onActive
        @abstract
        @desc     Developer callback. Fired on change of whether Starlet has primary focus.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onStarletActive}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onVisible}
        @see      {@link Starlet.onFocused}
        @see      {@link Starlet.onMinimized}
        @see      {@link Starlet.onHidden}
        @see      {@link Starlet.onMouse}
    */
    'onActive': function (state) {},
    
    /** @member   {callbackFrameworkEvent} Starlet.onFocused
        @abstract
        @desc     Developer callback. Fired on change of whether Starlet's application window has focus.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onStarletFocused}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onVisible}
        @see      {@link Starlet.onActive}
        @see      {@link Starlet.onMinimized}
        @see      {@link Starlet.onHidden}
        @see      {@link Starlet.onMouse}
    */
    'onFocused': function (state) {},
    
    /** @member   {callbackFrameworkEvent} Starlet.onMinimized
        @abstract
        @desc     Developer callback. Fired on change of whether Starlet's application window is minimized.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onStarletMinimized}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onVisible}
        @see      {@link Starlet.onActive}
        @see      {@link Starlet.onFocused}
        @see      {@link Starlet.onHidden}
        @see      {@link Starlet.onMouse}
    */
    'onMinimized': function (state) {},
    
    /** @member   {callbackFrameworkEvent} Starlet.onHidden
        @abstract
        @desc     Developer callback. Fired on change of whether Starlet's application window is hidden.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onStarletHidden}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onVisible}
        @see      {@link Starlet.onActive}
        @see      {@link Starlet.onFocused}
        @see      {@link Starlet.onMinimized}
        @see      {@link Starlet.onMouse}
    */
    'onHidden': function (state) {},
    
    /** @member   {callbackFrameworkEvent} Starlet.onMouse
        @abstract
        @desc     Developer callback. Fired on change of whether Starlet's WebView contains the mouse pointer.
                  Override in custom Starlet definition.
        @see      {@link Widget.ControllerClass#onStarletMouse}
        @see      {@link Starlet.define}
        @see      {@link Starlet.onVisible}
        @see      {@link Starlet.onActive}
        @see      {@link Starlet.onFocused}
        @see      {@link Starlet.onMinimized}
        @see      {@link Starlet.onHidden}
    */
    'onMouse': function (state) {},
    
    /* callbacks - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
    
    /** @member  {callbackData} Starlet~onConnectivity
        @private
        @desc    Fired on change of connectivity status, fires appropriate developer callbacks.
    */
    'onConnectivity': function (packet)
    {
        var now = packet.data[0];
        
        if (now.exiting === true)
        {
            Starlet.connectivity.exiting = true;
            Starlet.$html.addClass('exiting');
            Starlet.onExit && Starlet.onExit();
            Widget && Widget.onExit();
        }
        else if (Starlet.connectivity.xmpp !== now.xmpp)
        {
            Starlet.connectivity.xmpp = now.xmpp;
            
            if (!Starlet.connectivity.exiting)
            {
                if (now.xmpp)
                {
                    Starlet.$html.removeClass('disconnected').addClass('connected');
                    Starlet.onReconnect && Starlet.onReconnect();
                    Widget && Widget.onReconnect();
                }
                else
                {
                    Starlet.$html.removeClass('connected').addClass('disconnected');
                    Starlet.onDisconnect && Starlet.onDisconnect();
                    Widget && Widget.onDisconnect();
                }
            }
        }
    },
    
    /** @member  {callbackData} Starlet~onStarletData
        @private
        @desc    Fired on change of any Starlet properties.
    */
    'onStarletData': function (packet)
    {
        var data = dataArrayToObject(packet.data, 'name')[Starlet.name];
        
        //console.log('onStarletData', packet);
        //data && console.log('onStarletData', data);
        
        if (data) { switch (packet.action)
        {
            case 'read'  :
            case 'update':
                
                //console.log('onStarletData', packet);
                //return false;
                
                $.extend(Starlet,
                {
                    'type'      : data.type,
                    //'url'       : data.url,
                    //'name'      : data.name,
                    //'identifier': data.identifier,
                    'title'     : data.title,
                    'width'     : data.width,
                    'height'    : data.height,
                    'order'     : data.order,
                    'state'     : data.state
                });
                
                $.extend(Starlet.states,
                {
                    'main'       : data.main,
                    'overlay'    : data.overlay,
                    'lightbox'   : data.lightbox,
                    'ordered'    : data.ordered,
                    'tearoff'    : data.tearoff,
                    'minitearoff': data.minitearoff
                });
                
                Starlet.onActiveWrapper   (data.active   , 1);
                Starlet.onFocusedWrapper  (data.focused  , 1);
                Starlet.onMinimizedWrapper(data.minimized, 1);
                Starlet.onHiddenWrapper   (data.hidden   , 1);
                Starlet.onMouseWrapper    (data.mouse    , 1);
                
                break;
                
            default: break;
        }}
    },
    
    /** @member  {callbackData} Starlet~onOffset
        @private
        @desc    Fired on change of frontmost WebView's horizontal position during transition animation.
                 Currently unused due to synchronization issue.
    */
    'onOffset': function (packet)
    {
        log(L_FUNC, 'Starlet.onOffset', arguments);
        
        var position = packet.data[0],
            x        = position.x * 100,
            y        = position.y * 100;
        
        //console.log('onOffset', x, y);
        
        Starlet.$header.css({'left': x + '%', 'top': y + '%'});
    },
    
    /** @member  {callbackData} Starlet~onOverlayState
        @private
        @desc    Fired on change of overlay visibility states (shoebox|navigation|lightbox), controls tint opacity.
    */
    'onOverlayState': function (packet)
    {
        log(L_FUNC, 'Starlet.onOverlayState', arguments);
        
        var states = packet.data[0];
        
        Starlet.states.ordered
            && Starlet.tint(states.navigation || states.shoebox || states.lightbox);
    },
    
    /**
        @member  {callbackGeneric} Starlet~onVisibleWrapper
        @private
        @desc    Fired on need of recalculation of Starlet visibility state (whether Starlet is onscreen).
        @see     {@link Starlet.onVisible}
        @see     {@link Widget.onStarletVisible}
        @see     {@link Starlet~onActiveWrapper}
        @see     {@link Starlet~onFocusedWrapper}
        @see     {@link Starlet~onMinimizedWrapper}
        @see     {@link Starlet~onHiddenWrapper}
        @see     {@link Starlet~onMouseWrapper}
    */
    'onVisibleWrapper': function ()
    {
        var visible = Starlet.states.active && !Starlet.states.minimized && !Starlet.states.hidden;
        
        if (Starlet.states.visible !== visible)
        {
            //console.log('visible', visible);
            
            Starlet.states.visible = visible;
            Starlet.onVisible && Starlet.onVisible(visible);
            Starlet.speakWidget('starletVisible', visible);
            //Bus.tell('starlets', {'action': 'update', 'states': {'visible': visible}});
        }
    },
    
    /** @member  {callbackFrameworkEvent} Starlet~onActiveWrapper
        @private
        @desc    Fired on change of Starlet active state (input focus or switch).
                 The Starlet is active when its WebView has primary focus in its application window,
                 and that window is frontmost.
        @see     {@link OrionStarletWindow.setStarletVisibleCallback}
        @see     {@link Starlet.onActive}
        @see     {@link Widget.onStarletActive}
        @see     {@link Starlet~onVisibleWrapper}
        @see     {@link Starlet~onFocusedWrapper}
        @see     {@link Starlet~onMinimizedWrapper}
        @see     {@link Starlet~onHiddenWrapper}
        @see     {@link Starlet~onMouseWrapper}
    */
    'onActiveWrapper': function (active, instruction) { if (Starlet.states.active !== active)
    {
        //console.log('active', active);
        
        Starlet.states.active = active;
        Starlet.onActive && Starlet.onActive(active);
        Starlet.speakWidget('starletActive', active);
        //Bus.tell('starlets', {'action': 'update', 'states': {'active': active}});
        Starlet.onVisibleWrapper();
        Starlet.onPageView(active);
        
        //active && OrionStarletWindow.show();
    }},
    
    /**
        @member  {callbackFrameworkEvent} Starlet~onFocusedWrapper
        @private
        @desc    Fired on change of Starlet focused state.
                 The Starlet is focused when its WebView is in a foreground application window.
        @see     {@link OrionStarletWindow.setFocusCallback}
        @see     {@link Starlet.onFocused}
        @see     {@link Widget.onStarletFocused}
        @see     {@link Starlet~onVisibleWrapper}
        @see     {@link Starlet~onActiveWrapper}
        @see     {@link Starlet~onMinimizedWrapper}
        @see     {@link Starlet~onHiddenWrapper}
        @see     {@link Starlet~onMouseWrapper}
    */
    'onFocusedWrapper': function (focused, instruction) { if (Starlet.states.focused !== focused)
    {
        //console.log('focused', focused);
        
        Starlet.states.focused = focused;
        Starlet.onFocused && Starlet.onFocused(focused);
        Starlet.speakWidget('starletFocused', focused);
        !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'focused': focused}});
        Starlet.onVisibleWrapper();
    }},
    
    /** @member  {callbackFrameworkEvent} Starlet~onMinimizedWrapper
        @private
        @desc    Fired on change of Starlet minimized state.
                 The Starlet is minimized when its WebView is in a minimized application window.
        @see     {@link OrionStarletWindow.setMinimizeCallback}
        @see     {@link Starlet.onMinimized}
        @see     {@link Widget.onStarletMinimized}
        @see     {@link Starlet~onVisibleWrapper}
        @see     {@link Starlet~onActiveWrapper}
        @see     {@link Starlet~onFocusedWrapper}
        @see     {@link Starlet~onHiddenWrapper}
        @see     {@link Starlet~onMouseWrapper}
    */
    'onMinimizedWrapper': function (minimized, instruction) { if (Starlet.states.minimized !== minimized)
    {
        //console.log('minimized', minimized);
        
        Starlet.states.minimized = minimized;
        Starlet.onMinimized && Starlet.onMinimized(minimized);
        Starlet.speakWidget('starletMinimized', minimized);
        !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'minimized': minimized}});
        Starlet.onVisibleWrapper();
    }},
    
    /** @member  {callbackFrameworkEvent} Starlet~onHiddenWrapper
        @private
        @desc    Fired on change of Starlet hidden state.
                 The Starlet is hidden when its WebView is in a hidden application window.
        @see     {@link OrionStarletWindow.setHiddenCallback}
        @see     {@link Starlet.onHidden}
        @see     {@link Widget.onStarletHidden}
        @see     {@link Starlet~onVisibleWrapper}
        @see     {@link Starlet~onActiveWrapper}
        @see     {@link Starlet~onFocusedWrapper}
        @see     {@link Starlet~onMinimizedWrapper}
        @see     {@link Starlet~onMouseWrapper}
    */
    'onHiddenWrapper': function (hidden, instruction) { if (Starlet.states.hidden !== hidden && new Date().getTime() - Starlet.loaded > T_SECOND)
    {
        //console.log('hidden', hidden);
        
        Starlet.states.hidden = hidden;
        Starlet.onHidden && Starlet.onHidden(hidden);
        Starlet.speakWidget('starletHidden', hidden);
        !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'hidden': hidden}});
        Starlet.onVisibleWrapper();
    }},
    
    /** @member  {callbackFrameworkEvent} Starlet~onMouseWrapper
        @private
        @desc    Fired on change of Starlet mouse state.
                 The Starlet "has" mouse when the mouse pointer is within its WebView.
        @see     {@link OrionStarletWindow.setMouseCallback}
        @see     {@link Starlet.onMouse}
        @see     {@link Widget.onStarletMouse}
        @see     {@link Starlet~onVisibleWrapper}
        @see     {@link Starlet~onActiveWrapper}
        @see     {@link Starlet~onFocusedWrapper}
        @see     {@link Starlet~onMinimizedWrapper}
        @see     {@link Starlet~onHiddenWrapper}
    */
    'onMouseWrapper': function (mouse, instruction) { if (Starlet.states.mouse !== mouse)
    {
        //console.log('mouse', mouse);
        
        Starlet.states.mouse = mouse;
        Starlet.onMouse && Starlet.onMouse(mouse);
        Starlet.speakWidget('starletMouse', mouse);
        !instruction && Bus.tell('starlets', {'action': 'update', 'states': {'mouse': mouse}});
        Starlet.onVisibleWrapper();
    }},
    
    /** @member  {callbackEvent} Starlet~onMouseMove
        @private
        @desc    Fired on mouse movement within the Starlet's WebView.
        @see     {@link http://api.jquery.com/mousemove/}
        @see     {@link Starlet.onMouse}
        @see     {@link Widget.onStarletMouse}
    */
    'onMouseMove': function (event)
    {
        !Starlet.states.mouse && Starlet.states.visible && Starlet.onMouseWrapper(true);
        
        if (!Starlet.states.overlay && !Starlet.states.minitearoff)
        {
            var width       = $(document).width(),
                distanceSb  = event.pageX,
                distanceNav = width - event.pageX,
                nearSb1     = distanceSb  <= Starlet.near1,
                nearSb2     = distanceSb  <= Starlet.near2,
                nearNav1    = distanceNav <= Starlet.near1,
                nearNav2    = distanceNav <= Starlet.near2;
            
            if (nearSb1 !== Starlet.nearSb1 || nearSb2 !== Starlet.nearSb2)
            {
                Starlet.nearSb1 = nearSb1;
                Starlet.nearSb2 = nearSb2;
                Bus.tell('proximity', {'action': 'set', 'sb1': nearSb1, 'sb2': nearSb2});
            }
            
            if (nearNav1 !== Starlet.nearNav1 || nearNav2 !== Starlet.nearNav2)
            {
                Starlet.nearNav1 = nearNav1;
                Starlet.nearNav2 = nearNav2;
                Bus.tell('proximity', {'action': 'set', 'nav1': nearNav1, 'nav2': nearNav2});
            }
        }
    },
    
    /** @member  {callbackData} Starlet~onProximity
        @private
        @desc    Fired on movement of mouse through overlay handle proximity thresholds.
    */
    'onProximity': function (packet)
    {
        var data = packet.data[0];
        
        // Starlet.$body
        //     [data.sb1  ? 'addClass' : 'removeClass']('proximitySb')
        //     [data.nav1 ? 'addClass' : 'removeClass']('proximityNav');
    },
    
    /** @member  {callbackEvent} Starlet~onTintClick
        @private
        @desc    Fired on click of tint layer.
        @see     {@link http://api.jquery.com/click/}
    */
    'onTintClick': function (event)
    {
        event.preventDefault();
        
        Bus.tell('overlays', {'action': 'set', 'states':
        {
            'navigation': false,
            'shoebox'   : false,
            'lightbox'  : false
        }});
    },
    
    /** @member  {callbackEvent} Starlet~onLinkBubble
        @private
        @desc    Fired on click of anchor tags, prevents location change and determines whether and how to open externally.
        @see     {@link http://api.jquery.com/click/}
    */
    'onLinkBubble': function (event)
    {
        event.preventDefault();
        
        var href = null;
        
        if (href = this.href)
        {
            var isFiller = /^(javascript|#)/i.test(href),
                isMail   = /^mailto:/i.test(href);
            
            !isFiller && isExternal(href) &&
            (
                ISORION
                    ? (isMail ? OrionSystem.executeMailTo(href) : OrionSystem.openExternalURL(href))
                    : window.open(href)
            );
        }
    },
    
    /** @member  {callbackEvent} Starlet~onDragBubble
        @private
        @desc    Fired on start of drag events, cancels if not marked with class ui-draggable.
        @see     {@link https://developer.mozilla.org/en-US/docs/Web/Events/dragstart}
    */
    'onDragBubble': function (event)
    {
        !$(event.target).closest('.ui-draggable').length // not inside draggable?
            && event.preventDefault();
    },
    
    /** @member  {callbackEvent} Starlet~onTrack
        @private
        @desc    Fired on click of trackable elements, records with Google Analytics.
        @see     {@link http://api.jquery.com/click/}
        @see     {@link http://www.google.com/analytics/}
    */
    'onTrack': function (event, alternateText)
    {
        var gaLoaded = typeof ga !== 'undefined',
            //type     = this.nodeName === 'a' ? 'link' : 'button',
            //$this    = $(this),
            $this    = $(event.target).closest(TRACKABLES.join(',')),
            type,
            eAction  = 'click',
			$dialog  = $this.closest('.modal'),
            text     = alternateText                             ||
                       $.trim($this.attr('data-ga-title'))       ||
                       $.trim($this.text())                      ||
                       $.trim($this.attr('title'))               ||
                       $.trim($this.attr('data-original-title')) ||
                       $.trim($this.attr('value'))               ||
                       $this.outerHTML;
		
		// if there's a dialog, prepend the title
		
		$dialog.length && (text = '{0} - {1}'.format($.trim($dialog.find('.modal-header').text()), text));
        $this.length   && (type = $this[0].nodeName);
        
        // for event types that are not 'tracked, catch then here and set the type and eAction accordingly.
        
        event.type === 'keyup'     &&                                   (type = 'search'   ) && (eAction = type);
        event.type === 'keyup'     && $(event.target).is('textarea') && (type = 'Edit Note') && (eAction = type);
        event.type === 'mousedown' &&                                   (type = 'ext-click') && (eAction = type);
        event.type === 'drop'      &&                                   (type = 'drop'     ) && (eAction = type);
        
        //var x = $(event.target).closest(TRACKABLES);
        
        if (Starlet.lastEventTime &&  Starlet.lastEventTime < event.timeStamp - 50)
        {
            if (type)
            {
                Starlet.lastEventTime = event.timeStamp;
                //console.log('onTrack: ', type, Starlet.title, text, event);
                gaLoaded && ga('send', 'event', Starlet.title, eAction, text);
            }
            else
            {
                //console.log('NOPE onTrack: ', type, Starlet.title, text, $this, event);
            }
        } 
    },
    
    /** @member  {callbackFrameworkEvent} Starlet~onPageView
        @private
        @desc    Fired on activation (input focus or switch) of Starlet.
        @see     {@link Starlet.onActive}
        @see     {@link Starlet.onActiveWrapper}
    */
    'onPageView': function (active)
    {
        log(L_FUNC, 'Starlet.onPageView', arguments);
        
        if (active)
        {
            // do nothing if timeout already exists
            if (Starlet.pageViewTimeout) { return; }
            
            Starlet.pageViewTimeout = setTimeout(function ()
            {
                // clear timeout
                Starlet.pageViewTimeout = null;
                
                // send pageView
                //console.log('PageView: ', PATH, Starlet.title);
                ga && ga('send', 'pageview', {'page': PATH + (HASH ? HASH : ''), 'title': Starlet.title});
                
                log(L_INFO, 'Starlet.onPageView', 'GA PageView Fired: ' + Starlet.title);
            },
            1000);
        }
        else if (Starlet.pageViewTimeout)
        {
            // clear timeout if exists on unfocus
            clearTimeout(Starlet.pageViewTimeout);
            Starlet.pageViewTimeout = null;
        }
    },
    
    /** @member  {callbackData} Starlet~onModal
        @private
        @desc    Fired on receipt of instruction to create modal dialog.
        @see     {@link Starlet.modal}
    */
    'onModal': function (packet)
    {
        log(L_FUNC, 'Starlet.onModal', arguments);
        
        // if there is already a modal dialog, don't show this
        
        if (Starlet.$modal.children().length)
        {
            console.log('Dialing Modal aborted due to modal div already in use');
            return false;
        }
        
        var modal = packet.data[0];
        
        if (!modal.starlet || modal.starlet === Starlet.name) { switch (modal.type)
        {
            case 'dial':
                
                modal.timeOut = 10;
                
                // For case where a parked call is dropped on a user's extension that is NOT the default extension,
                // need to reset the extension value in the extension object to the extension value from the
                // originatingExtension object
                modal.extension.extension = modal.originatingExtension.extension;
                
                Starlet.modal(templates.call_alert(modal), {'className': 'call_alert'});
                
                // after 10 seconds, close the modal...
                
                var x        = modal.timeOut,
                    $counter = Starlet.$modal.find('.timeOut');
                
                var countdown = setInterval(function ()
                {
                    if (x > 0) { $counter.text(--x); } else
                    {
                        clearInterval(countdown);
                        
                        Starlet.$modal
                            .find('.btn-primary')
                                .trigger('click')
                                .end()
                            .find('div.alertDialog').remove();
                        
                        Starlet.modal('hide');
                    }
                },
                1000);
                
                // bindings
                
                var event  = 'click.starlet.onModal',
                    remove = function ()
                    {
                        clearInterval(countdown);
                        
                        Starlet.$modal
                            .off('.starlet.onModal')
                            .find('div.alertDialog')
                                .remove();
                        
                        Starlet.modal('hide');
                    };
                
                Starlet.$modal
                    
                    .on(event, '.btn.btn-primary', function () { remove(); })
                    
                    .on(event, '.btn.reject', function ()
                    {
                        Bus.relay('starscope', 'rejectCall', {'extData': modal.extension});
                        
                        remove();
                    })
                    
                    .on(event, 'label', function ()
                    {
                        Bus.tell('options',
                        {
                            'action'  : 'set',
                            'category': '_global',
                            'key'     : 'hideCallAlerts',
                            'value'   : 'true'
                        });
                        
                        remove();
                    })
                    
                    .on(event, 'i', function () { $(this).removeClass('flash').addClass('bounce'); });

                break;
                
            default: break;
        }}
    },
    
    'onApplicationState': function (packet) // in development
    {
        //console.log('STATES', packet.data);
    },
    
    /* wrapper methods - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
    
    /** @method    Starlet.lightbox
        @readonly
        @chainable
        @desc      Opens a lightbox overlay with specific location and dimensions.
        
        @arg       {String} name   - Framework name of new WebView.
        @arg       {String} url    - Location to open in lightbox.
        @arg       {Number} width  - Width at which to open lightbox.
        @arg       {Number} height - Height at which to open lightbox.
        @returns   {Object}          {@link Starlet}
        
        @see       {@link Starlet.lightbox(2)}
        @see       {@link OrionLightbox.open}
    */
    /** @method    Starlet.lightbox(2)
        @readonly
        @chainable
        @desc      Closes a lightbox overlay by name.
        
        @arg       {String} name - Framework name of WebView to close.
        @returns   {Object}        {@link Starlet}
        
        @see       {@link Starlet.lightbox}
        @see       {@link OrionLightbox.close}
        @see       {@link OrionLightbox.setClosedCallback}
    */
    'lightbox': function ()
    {
        log(L_FUNC, 'Starlet.lightbox', arguments);
        
        var args = arguments;
        
        switch (args.length)
        {
            case 1: OrionLightbox.close(args[0]); break;
            
            case 4: var closeCallback = function () { Bus.tell('overlays', {'action': 'set', 'states': {'lightbox': false}}); },
                        name          = args[0],
                        url           = args[1],
                        width         = Math.round(args[2]),
                        height        = Math.round(args[3]);
                    
                    OrionLightbox.setClosedCallback('window', linkCallback(closeCallback));
                    Bus.tell('overlays', {'action': 'set', 'states': {'lightbox': true}});
                    setTimeout(function () { OrionLightbox.open(name, url, width, height); }, 0);
                    break;
            
            default: log(L_ERROR, 'Starlet.lightbox', 'invalid arguments'); return Starlet;
        }
        
        return Starlet;
    },
    
    /** @method   Starlet.modal
        @readonly
        @desc     Creates a Bootstrap modal dialog in a manner that forces a consistent z-index.
        
        @arg      {String}         html                        - Raw html to pass through to Bootstrap modal method.
        @arg      {Object}         [options={}]                - Additional options.
        @arg      {String}         [options.className]         - Custom CSS class name to add to modal element.
        @arg      {Boolean|String} [options.backdrop='static'] - Whether to include backdrop,
                                                                 and to allow click to dismiss.
        @arg      {Boolean}        [options.keyboard=true]     - Whether to allow esc to dismiss.
        @arg      {Boolean}        [options.show=true]         - Whether to show immediately.
        @arg      {Boolean|String} [options.remote=false]      - Remote content URL.
        @returns  {jQuery}                                       {@link Starlet.$modal}
        
        @see      {@link Starlet.modal(2)}
        @see      {@link http://getbootstrap.com/2.3.2/javascript.html#modals}
    */
    /** @method   Starlet.modal(2)
        @readonly
        @desc     Controls Bootstrap modal dialog.
        
        @arg      {String} action         - Action (show|hide|toggle) to pass through to Bootstrap modal method.
        @returns  {jQuery}                  {@link Starlet.$modal}
        
        @see      {@link Starlet.modal}
        @see      {@link http://getbootstrap.com/2.3.2/javascript.html#modals}
    */
    'modal': function (action, options)
    {
        log(L_FUNC, 'Starlet.modal', arguments);
        
        switch (action)
        {
            case 'show'  : Starlet.minishoebox().menu(); // close
            case 'hide'  :
            case 'toggle': Starlet.$modal.modal(action); break;
            
            default      : Starlet.$modal.attr('class', 'modal hide fade');
                           
                           !options                   && (options = {});
                           !exists(options.backdrop)  && (options.backdrop = 'static');
                            options.className         && Starlet.$modal.addClass(options.className);
                           
                           Starlet
                               .minishoebox().menu()     // close
                               .$modal
                                   .empty()
                                   .append(action)       // html
                                   .modal(options);      // show
                           
                           break;
        }
        
        return Starlet.$modal;
    },
    
    /** @method    Starlet.minishoebox
        @readonly
        @chainable
        @desc      Manages a centralized, preloaded minishoebox Widget within a Bootstrap popover
                   in a manner that forces a consistent z-index.
        
        @arg       {Element|jQuery} anchor               - Element with which to align popover.
        @arg       {String}         [placement='bottom'] - Direction in which to align popover.
        @arg       {String}         [session='']         - Session id for minishoebox to pass to target after selection.
        @arg       {Array.<Number>} [participants=[]]    - Array of user ids to preselect.
        @returns   {Object}                                {@link Starlet}
        
        @see       {@link Starlet.minishoebox(2)}
        @see       {@link http://getbootstrap.com/2.3.2/javascript.html#popovers}
    */
    /** @method    Starlet.minishoebox(2)
        @readonly
        @chainable
        @desc      Closes minishoebox if open.
        
        @returns   {Object} {@link Starlet}
        
        @see       {@link Starlet.minishoebox}
        @see       {@link http://getbootstrap.com/2.3.2/javascript.html#popovers}
    */
    'minishoebox': function (anchor, placement, session, participants)
    {
        // close?
        
        if (!arguments.length)
        {
            if (Starlet.$minishoebox.data('showing'))
            {
                Starlet.$minishoebox
                    .css({'display': 'none', 'left': 0, 'top': 0})
                    .append($('body > .popover div.widget')) // prevent event/data loss
                    .data('showing', false)
                    .popover('hide');
                
                Starlet.$html.removeClass('minishoebox');
            }
            
            return Starlet;
        }
        
        // validate
        
        anchor       = jUnwrap(anchor);
        placement    = placement    || 'bottom';
        session      = session      || '';
        participants = participants || [];
        
        if (!anchor)                                    { log(L_ERROR, 'Starlet.minishoebox', 'no anchor provided' ); return false; }
        if (!~$.inArray(placement, POPOVER_PLACEMENTS)) { log(L_ERROR, 'Starlet.minishoebox', 'invalid placement'  ); return false; }
        
        // init
        
        var mode       = Starlet.$minishoebox.data('mode'),
            pop        = Starlet.$minishoebox.data('popover'),
            widget     = Starlet.$minishoebox.data('widget'),
            controller = Starlet.$minishoebox.data('controller'),
            rect       = anchor.getBoundingClientRect(),
            midWidth   = rect.width  / 2 + rect.left,
            midHeight  = rect.height / 2 + rect.top,
            modal      = $(anchor).parents('.modal').length;
        
        // set context
        
        Starlet.speakWidget('minishoeboxContext',
        {
            'calling_widget'      : mode,
            'session_id'          : session,
            'current_participants': participants
        });
        
        // show
        
        Starlet.menu();
        
        Starlet.$minishoebox.css
            (
                $.extend(
                {
                    'left'  : {'left': rect.left , 'top': midHeight  },
                    'top'   : {'left': midWidth  , 'top': rect.top   },
                    'bottom': {'left': midWidth  , 'top': rect.bottom},
                    'right' : {'left': rect.right, 'top': midHeight  }
                }
                [placement], {'display': 'block'})
            )
            .data('placement', placement)
            .data('showing'  , true)
            .popover('show');
        
        $('body > .popover')[modal ? 'addClass' : 'removeClass']('higher');
        
        Starlet.$html.addClass('minishoebox');
        
        return Starlet;
    },
    
    /** @method    Starlet.menu
        @readonly
        @chainable
        @desc      Replaces a select element with a styled Bootstrap dropdown.
        
        @arg       {Element|jQuery} select  - Element to replace.
        @returns   {Object}                   {@link Starlet}
        
        @see       {@link Starlet.menu(2)}
        @see       {@link Starlet.menu(3)}
        @see       {@link Starlet.menu(4)}
        @see       {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns}
    */
    /** @method    Starlet.menu(2)
        @readonly
        @chainable
        @desc      Initializes a Bootstrap dropdown defined with custom structure and associates it with an element.
        @todo      Document structure.
        
        @arg       {Element|jQuery} anchor    - Element to toggle menu.
        @arg       {Object}         structure - Definition of menu structure.
        @returns   {Object}                     {@link Starlet}
        
        @see       {@link Starlet.menu}
        @see       {@link Starlet.menu(3)}
        @see       {@link Starlet.menu(4)}
        @see       {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns}
    */
    /** @method    Starlet.menu(3)
        @readonly
        @chainable
        @desc      Closes menu if open.
        
        @returns   {Object} {@link Starlet}
        
        @see       {@link Starlet.menu}
        @see       {@link Starlet.menu(2)}
        @see       {@link Starlet.menu(4)}
        @see       {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns}
    */
    /** @method    Starlet.menu(4)
        @readonly
        @private
        @chainable
        @desc      Internal variation for toggling menus that have already been initialized.
                   Filler arguments are only to make arguments.length 3, differentiating the behavior of this method.
        
        @arg       {Element|jQuery} anchor  - Element that has been initialized as a menu toggling button.
        @arg       {Mixed}          filler1 - Filler.
        @arg       {Mixed}          filler2 - Filler.
        @returns   {Object}                   {@link Starlet}
        
        @see       {@link Starlet.menu}
        @see       {@link Starlet.menu(2)}
        @see       {@link Starlet.menu(3)}
        @see       {@link http://getbootstrap.com/2.3.2/javascript.html#dropdowns}
    */
    'menu': function ()
    {
        // init args
        
        var self        = arguments.callee,
            anchor      = jUnwrap(arguments[0]),
            select      = null,
            structure   = null,
            initialized = false;
        
        // init static methods
        
        self.getStructure = self.getStructure || function (group, count)
        {
            // init
            
            var self      = arguments.callee,
                $group    = jWrap(group),
                $selected = $group.closest('select').find('option:selected'),
                count     = count || 0,
                structure =
                {
                    'selected': !!$group.attr('selected'),
                    'disabled': !!$group.attr('disabled'),
                    'right'   : !!$group.attr('data-right'),
                    'up'      : !!$group.attr('data-up'),
                    'title'   :   $group.attr('title')                  ||
                                  $group.attr('label')                  ||
                                  $group.find('option:selected').html() ||
                                  '',
                    'items'   :   []
                };
            
            count && (structure.count = count);
            
            // loop select's top level
            
            $group.find('> option, > optgroup').each(function (index, item)
            {
                var $item        = $(item),
                    submenu      = item.tagName.toLowerCase() !== 'option',
                    substructure = null;
                
                $item.attr('data-count', ++count);
                
                // add appropriate object
                
                var value    = $item.attr('value'),
                    value    = typeof value === 'string' ? value : $item.text(),
                    selected = $selected[0] === $item[0];
                
                structure.items.push(submenu ? (substructure = self(item, count)) : // submenu (recurse) | item
                {
                    'count'    :   count,
                    'divider'  :   false,
                    'html'     :   true,
                    'value'    :   value,
                    'selected' :   selected,
                    'disabled' : !!$item.attr('disabled'),
                    'title'    :   $item.html(),
                    'url'      :   $item.attr('data-href') || '',
                    'items'    :   [],
                    'callbacks':   {'click': function (e)
                    {
                        // init
                        
                        var $li       = $(this),
                            value     = $li.attr('data-value'),
                            which     = $li.attr('data-count'),
                            $anchor   = $(Starlet.menu.anchor).closest('div'),
                            $select   = $($anchor.data('starlet_menu_select')),
                            $options  = $select.find('option'),
                            $option   = $select.find('option[data-count="{0}"]'.format(which));
                        
                        // validate
                        
                        if ($option.attr('disabled')) { return false; }
                        
                        // mark selected
                        
                        $options.removeAttr('selected');
                        $option.attr('selected', 'selected');
                        !$select.attr('title') && $anchor.find('span.title').html($option.html());
                        
                        // close, trigger
                        
                        Starlet.menu();
                        $select.trigger('change');
                    }}
                });
                
                substructure && (count += substructure.items.length);
            });
            
            return structure;
        };
        
        self.getCallbacks = self.getCallbacks || function (structure)
        {
            // init
            
            var self      = arguments.callee,
                callbacks = [],
                structure = structure || Starlet.menu.structure;
            
            // validate
            
            if (!structure) { log(L_ERROR, 'Starlet.menu.getCallbacks', 'structure not found'); return callbacks; }
            
            // collect
            
            structure.items && $.each(structure.items, function (index, item)
            {
                !item.divider &&  callbacks.push(item.callbacks || {});      // item
                 item.items   && (callbacks = callbacks.concat(self(item))); // submenu
            });
            
            return callbacks;
        };
        
        self.setPosition = self.setPosition || function (e)
        {
            // init
            
            var structure, rect;
            
            // do anything?
            
            self.open
                &&                self.anchor
                && (rect      =   self.anchor .getBoundingClientRect())
                && (structure = $(self.anchor).closest('div').data('starlet_menu_structure'))
                &&                Starlet.$menus.css(
                {
                    'left': rect[structure.right ? 'right' : 'left'  ],
                    'top' : rect[structure.up    ? 'top'   : 'bottom']
                });
        };
        
        // switch mode
        
        switch (arguments.length)
        {
            case 3 : select      = anchor;                                                // internal toggle
                     anchor      = $(select).data('starlet_menu_button');
                     initialized = true;                                  break;
            case 1 : select      = anchor;                                                // replace element
                     initialized = $(select).hasClass('starlet_menu');    break;
            case 2 : structure   = arguments[1];                          break;          // custom structure
            case 0 : anchor      = null;                                  break;          // close anything
            default: log(L_ERROR, 'Starlet.menu', 'invalid arguments');   return Starlet; // invalid
        }
        
        // close?
        
        if (!anchor || anchor == self.anchor)
        {
            if (self.open)
            {
                // unbind
                
                $('*')   .off('.starlet.menu');
                $(window).off('.starlet.menu');
                
                // dom reset
                
				Starlet.$menus.removeClass('dropup open');
				
				// wait for animation to finish
				
				setTimeout(function ()
				{
					Starlet.$menus
						.hide()                      // hide
						.empty()                     // clear
						.css({'left': 0, 'top': 0}); // position
				}, 200);
				
                // update states
                
                Starlet.$html.removeClass('menu');
                self.open   = false;
                self.anchor = null;
            }
            
            return Starlet;
        }
        
        // replace element?
        
        if (select)
        {
            // create structure
            
            structure = self.getStructure(select);
            
            // validate
            
            if (!structure) { log(L_ERROR, 'Starlet.menu', 'select tree malformed'); return Starlet; }
            
            // replace element?
            
            if (!initialized)
            {
                var $select  = $(select),
                    $button  = $(templates.menu_button(structure)),
                    update   = function ()
                    {
                        // update title?
                        
                        if (!$select.attr('title'))
                        {
                            var title = $select.find('option:selected, option[selected]').html() || '(None Selected)';
                            
                            $button.find('span.title').html(title);
                        }
                        
                        // rebuild?
                        
                        self.open && self() && self($select, 1, 1);
                    },
                    observer = new MutationObserver(update);
                
                // hide, insert
                
                $select
                    .data('starlet_menu_button'   , $button.find('> a')[0]) // cross reference
                    .data('starlet_menu_structure', structure)
                    .data('starlet_menu_observer' , observer)
                    .on('update', update)
                    .hide();
                
                $button
                    .data('starlet_menu_select'   , select)                 // cross reference
                    .data('starlet_menu_structure', structure)
                    .data('starlet_menu_observer' , observer)
                    .on('click.starlet', function (e)                       // bind internal toggle
                    {
                        !$select.attr('disabled')
                            && Starlet.menu(select, 1, 1);
                    })
                    .addClass('starlet_menu')                               // mark as processed
                    .insertAfter(select);
                
                // listen for changes
                
                observer.observe($select[0],
                {
                    'childList'    : true,
                    'characterData': true,
                    'subtree'      : true
                });
                
                // stop
                
                return Starlet;
            }
        }
        
        // validate
        
        if (!structure) { log(L_ERROR, 'Starlet.menu', 'no structure provided'); return Starlet; }
        
        // init for build
        
        var rect      = anchor.getBoundingClientRect(),
            callbacks = self.getCallbacks(structure),
			$select   = $(select),
            $menu     = $(templates.menu(structure)),
			gaTitle	  = ($select && $select.length) ? $select.attr('data-ga-title') : '';
        
		gaTitle && $menu.attr('data-ga-title', gaTitle);
		
        // update states
        
        self.open   = true;
        self.anchor = anchor;
        Starlet.$html.addClass('menu');
        
        // dom setup
        
        Starlet.$menus
            .empty()                                // clear
            .addClass(structure.up ? 'dropup' : '') // position
            .css(
            {
                'left': rect[structure.right ? 'right' : 'left'  ],
                'top' : rect[structure.up    ? 'top'   : 'bottom']
            })
            .append($menu)                          // insert
            .show()                                 // show
            .find('li:not(.divider)')               // bind
                .each(function (index, item)
                {
                    var callbacksItem = callbacks[index],
                        count         = 0;
                    
                    // add namespaces
                    
                    for (var name in callbacksItem) { if (callbacksItem.hasOwnProperty(name))
                    {
                        var callback   = callbacksItem[name],
                            eventspace = '{0}.starlet.menu'.format(name);
                        
                        // wrap
                        
                        delete callbacksItem[name];
                        callbacksItem[eventspace] = function (e) { callback.apply(this, arguments); killEvent(e); };
                        count++;
                    }}
                    
                    // any? bind
                    
                    count && $(this).on(callbacksItem);
                });
        
        // "fork" for rendering
        
        fork(function ()
        {
            // init bootstrap
            
            $menu.dropdown('toggle');
            
            // listen for click-out
            
            $('*:not(body > #menus *)')
                .not(anchor)
                .not($(anchor).find('*'))
                .on('click.starlet.menu', function (e) { self.open && Starlet.menu(); });
            
            // listen for scroll|resize
            
            var throttled = $.throttle(10, Starlet.menu.setPosition);
            
            $(window)  .on(    'resize.starlet.menu', throttled);
            $(document).on('mousewheel.starlet.menu', throttled);
        });
        
        return Starlet;
    },
    
    /* methods - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
    
    /** @method    Starlet.speakWidget
        @readonly
        @chainable
        @desc      Emit data under a specific packet name to any Widget listening for it.
                   Listening Widgets must prepend 'Starlet_' to the packet name to differentiate between broadcasts
                   originating from Starlet versus another Widget.
                   Alternatively, trigger listeners as a Widget would using {@link Starlet.broadcastWidget}.
        
        @arg       {String}       packetName - Topic to target specific listeners.
        @arg       {Object|Array} data       - The item to broadcast.
        @returns   {Object}                    {@link Starlet}
        
        @see       {@link Starlet.broadcastWidget}
        @see       {@link Starlet.listenWidget}
        @see       {@link Starlet.listenBroadcast}
        @see       {@link Starlet.ignoreWidget}
        @see       {@link Starlet.ignoreBroadcast}
    */
    'speakWidget': function (packetName, data)
    {
        log(L_DEBUG, 'Starlet.speakWidget', arguments);
        
        var widgets = Widget.instances(),
            packet  = {'widget': 'Starlet', 'name': packetName, 'data': typeof data !== 'undefined' ? data : {}};
        
        $.each(widgets, function ()
        {
            this.$element.triggerHandler('Starlet_' + packetName, [packet]);
        });
        
        return Starlet;
    },
    
    /** @method    Starlet.broadcastWidget
        @readonly
        @chainable
        @desc      Emit data under a specific packet name to any Widget listening for it.
                   Listening Widgets will be triggered as if by another Widget so they needn't prepend 'Starlet_'
                   to the packet name. Alternatively, trigger listeners in a way that signifies the origin is Starlet
                   by using {@link Starlet.speakWidget}.
        
        @arg       {String}       packetName - Topic to target specific listeners.
        @arg       {Object|Array} data       - The item to broadcast.
        @returns   {Object}                    {@link Starlet}
        
        @see       {@link Starlet.speakWidget}
        @see       {@link Starlet.listenWidget}
        @see       {@link Starlet.listenBroadcast}
        @see       {@link Starlet.ignoreWidget}
        @see       {@link Starlet.ignoreBroadcast}
    */
    'broadcastWidget': function (packetName, data)
    {
        log(L_DEBUG, 'Starlet.broadcastWidget', arguments);
        
        var widgets = Widget.instances(),
            packet  = {'name': packetName, 'data': typeof data !== 'undefined' ? data : {}};
        
        $.each(widgets, function ()
        {
            this.$element.triggerHandler(packetName, [packet]);
        });
        
        return Starlet;
    },
    
    /** @method    Starlet.listenWidget
        @readonly
        @chainable
        @desc      Listen for data from a specific Widget type and of a specific packet name.
        
        @arg       {String}   widgetType - Type of Widget to listen for data from.
        @arg       {String}   packetName - Topic to listen for.
        @arg       {Function} callback   - Function to respond to incoming data.
        @returns   {Object}                {@link Starlet}
        
        @see       {@link Starlet.speakWidget}
        @see       {@link Starlet.broadcastWidget}
        @see       {@link Starlet.listenBroadcast}
        @see       {@link Starlet.ignoreWidget}
        @see       {@link Starlet.ignoreBroadcast}
    */
    'listenWidget': function (widgetType, packetName, callback)
    {
        log(L_DEBUG, 'Starlet.listenWidget', arguments);
        
        Starlet.$content.on(widgetType + '_' + packetName, function (e, packet) { callback(packet); });
        
        return Starlet;
    },
    
    /** @method    Starlet.ignoreWidget
        @readonly
        @chainable
        @desc      Stop listening for data from a specific Widget type and of a specific packet name.
        
        @arg       {String}   widgetType - Type of Widget to stop listening for data from.
        @arg       {String}   packetName - Topic to stop listening for.
        @returns   {Object}                {@link Starlet}
        
        @see       {@link Starlet.speakWidget}
        @see       {@link Starlet.broadcastWidget}
        @see       {@link Starlet.listenWidget}
        @see       {@link Starlet.listenBroadcast}
        @see       {@link Starlet.ignoreBroadcast}
    */
    'ignoreWidget': function (widgetType, packetName)
    {
        log(L_DEBUG, 'Starlet.ignoreWidget', arguments);
        
        Starlet.$content.off(widgetType + '_' + packetName);
        
        return Starlet;
    },
    
    /** @method    Starlet.listenBroadcast
        @readonly
        @chainable
        @desc      Listen for data of a specific packet name.
        
        @arg       {String}   packetName - Topic to listen for.
        @arg       {Function} callback   - Function to respond to incoming data.
        @returns   {Object}                {@link Starlet}
        
        @see       {@link Starlet.speakWidget}
        @see       {@link Starlet.broadcastWidget}
        @see       {@link Starlet.listenWidget}
        @see       {@link Starlet.ignoreWidget}
        @see       {@link Starlet.ignoreBroadcast}
    */
    'listenBroadcast': function (packetName, callback)
    {
        log(L_DEBUG, 'Starlet.listenBroadcast', arguments);
        
        Starlet.$content.on('_{0}'.format(packetName), function (e, packet) { callback(packet); });
        
        return Starlet;
    },
    
    /** @method    Starlet.ignoreBroadcast
        @readonly
        @chainable
        @desc      Stop listening for data of a specific packet name.
        @todo      Eliminate widgetType as an argument.
                   How broadcast differs from speak is that no type filter is needed.
        
        @arg       {String} widgetType - Type of Widget to stop listening for data from (see todo).
        @arg       {String} packetName - Topic to stop listening for.
        @returns   {Object}              {@link Starlet}
        
        @see       {@link Starlet.speakWidget}
        @see       {@link Starlet.broadcastWidget}
        @see       {@link Starlet.listenWidget}
        @see       {@link Starlet.listenBroadcast}
        @see       {@link Starlet.ignoreWidget}
    */
    'ignoreBroadcast': function (widgetType, packetName)
    {
        log(L_DEBUG, 'Starlet.ignoreBroadcast', arguments);
        
        Starlet.$content.off('_{0}'.format(packetName));
        
        return Starlet;
    },
    
    /** @method    Starlet.define
        @readonly
        @chainable
        @desc      Extends Starlet with custom properties, methods, and callbacks.
                   Call this in your Starlet directory's index.js, it signifies to the synchronizer that fundamental
                   custom Starlet resources have been loaded and parsed.
        @todo      Document definition.
        
        @arg       {Object} definition - Structure with which to extend Starlet,
                                         must include override for onRun to initiate any behavior.
        @returns   {Object}              {@link Starlet}
        
        @see       {@link Starlet.title}
        @see       {@link Starlet.header}
        @see       {@link Starlet.footerData}
        @see       {@link Starlet.onRun}
        @see       {@link Starlet.onDisconnect}
        @see       {@link Starlet.onReconnect}
        @see       {@link Starlet.onUnload}
        @see       {@link Starlet.onVisible}
        @see       {@link Starlet.onActive}
        @see       {@link Starlet.onFocused}
        @see       {@link Starlet.onMinimized}
        @see       {@link Starlet.onHidden}
        @see       {@link Starlet.onMouse}
        @see       {@link Starlet.load}
        @see       {@link Starlet~synchronizer}
    */
    'define': function (definition)
    {
        log(L_FUNC, 'Starlet.define');
        
        if (!definition) { log(L_ERROR, 'Starlet.define', 'nothing provided'); return false; }
        
        $.extend(Starlet, definition);
        
        // process counterparts
        
        detectTemplates();
        detectStyles();
        
        // css flags
        
        DEVELOPER               && Starlet.$html.addClass('dev');
        !Starlet.header         && Starlet.$html.addClass('no-header');
        Starlet.states.overlay  && Starlet.$html.addClass('overlay');
        Starlet.states.lightbox && Starlet.$html.addClass('lightbox');
        
        // set up content
        
        Starlet.$title.text(Starlet.title);
        Starlet.$body.append(templates.starlet(Starlet));
        
        Starlet.$header      = Starlet.$body.find('> header');
        Starlet.$content     = Starlet.$body.find('> section#content');
        Starlet.$footer      = Starlet.$body.find('> footer');
        Starlet.$minishoebox = Starlet.$body.find('> #minishoebox');
        Starlet.$menus       = Starlet.$body.find('> #menus');
        
        'footer' in templates
            ? Starlet.$footer.append(templates.footer(Starlet.footerData))
            : Starlet.$html.addClass('no-footer');
        
        // signal
        
        Starlet.synchronizer.notify('defined');
        
        return Starlet;
    },
    
    /** @method    Starlet.load
        @readonly
        @chainable
        @desc      Fetches custom Starlet index.html and notifies synchronizer.
        @todo      Verify that occasional bad root was due to location hash being changed, remove relevant code here.
        @returns   {Object} {@link Starlet}
        @see       {@link Starlet.define}
        @see       {@link Starlet~synchronizer}
    */
    'load': function ()
    {
        log(L_FUNC, 'Starlet.load');
       
        // process counterparts
        
        detectTemplates();
        
        $('link[type="text/less"]').addClass('processed'); // less handles these initially but doesn't mark
        
        // jes hack becuase this is unset some times
        
        var d = $.Deferred(); 
        
        if
        (
            !(Starlet && Starlet.identifier && Starlet.identifier.length) ||
            !~Starlet.root.indexOf(Starlet.identifier)
        ){
            log(L_ERROR, 'Starlet.load', 'bad Starlet.root', Starlet.root, Starlet.identifier);
            
            Bus.ask('starlets', {'action': 'active'}).then(function (packet)
            {
                Starlet.root += packet.data[0].identifier;
                d.resolve(Starlet);
            });
        }
        else { d.resolve(Starlet); }
        
        d.promise().then(function (data)
        {
            var requestUrl = [Starlet.root, 'index.html'].join('/'),
            
            requestTimer = null,
            parseTimer   = null;
            
            log(L_INFO, 'Starlet.load', 'requesting...', requestUrl)
                && (requestTimer = new TimerClass());
            
            $.ajax(requestUrl, {'type': 'GET'}).success(function (html, status, xhr)
            {
                log(L_INFO, 'Starlet.load', 'received in ' + requestTimer.elapsed(1), 'parsing...')
                    && (parseTimer = new TimerClass());
                
                // insert content
                
                $('head').append($($.trim(html)))
                    && log(L_INFO, 'Starlet.load', 'parsed in ' + parseTimer.elapsed(1));
                
                // signal
                
                Starlet.synchronizer.notify('loaded');
            })
            .error(function (xhr, status, error) { log(L_ERROR, 'Starlet.load', status, error, requestUrl); });
        });
        
        return Starlet;
    },
    
    'setting': function ()
    {
        log(L_DEBUG, 'Starlet.setting', arguments);
        
        var args = Array.prototype.slice.call(arguments);
        
        args[0] = 'starlet.' + Starlet.identifier + '.' + args[0];
        
        return Bus.setting.apply(Bus, args);
    },
    
    /** @member   {Object} Starlet~synchronizer
        @readonly
        @private
        @desc     Deferred object handling foundational bindings and property population when loading as aspects of the
                  WebView become available, finally calling {@link Starlet.onRun}.
        @see      {@link Starlet.load}
        @see      {@link Starlet.define}
        @see      {@link Starlet~loadArguments}
        @see      {@link Starlet~loadSpinner}
        @see      {@link Starlet~loadTimer}
        @see      {@link Starlet~flagDefined}
        @see      {@link Starlet~flagDom}
        @see      {@link Starlet~flagFramework}
        @see      {@link Starlet~flagLoaded}
        @see      {@link Starlet~flagCovered}
    */
    'synchronizer': (new $.Deferred())
        
        .progress(function (flag, args)
        {
            log(L_FUNC, 'Starlet.synchronizer', arguments);
            
            // status, actions
        
            switch (flag)
            {
                case 'dom':
                    
                    Starlet.flagDom  = true;
                    Starlet.$html    = $('html');
                    Starlet.$title   = $('title');
                    Starlet.$body    = $('body');
                    Starlet.$modal   = $('#modal');
                    Starlet.$spinner = $('body > div.spinner');
                    Starlet.$tint    = $('#tint');
                    
                    // spinner
                    
                    Starlet.$spinner.fadeIn(
                    {
                        'duration': 250,
                        'easing'  : 'easeInOutCubic',
                        'done'    : function ()
                        {
                            Starlet.$html.removeClass('loading');
                            Starlet.synchronizer.notify('covered');
                        }
                    });
                    
                    // lock location
                    
                    $('html')
                        .on('click'    , 'a'                                         , Starlet.onLinkBubble)
                        .on('dragstart', '*:not(ui-draggable), *:not(ui-draggable) *', Starlet.onDragBubble);
                    
                    window.addEventListener('dragover', function (e) { e.preventDefault(); }, false);
                    window.addEventListener('drop'    , function (e) { e.preventDefault(); }, false);
                    
                    // analytics
                    
                    //Starlet.$body.off('click.ga').on('click.ga', TRACKABLES.join(', '), Starlet.onTrack);
                    Starlet.$body[0].addEventListener('click', Starlet.onTrack, true);
                    // other events
                    
                    Starlet.$html.on('mousemove', $.throttle(T_HUNDREDTH, Starlet.onMouseMove));
                    Starlet.$tint.on('click'    , Starlet.onTintClick);
                    Starlet.$body
                        
                        .on('dblclick', '> header', function (e)
                        {
                            killEvent(e);
                            
                            var starlet;
                            
                            DEVELOPER
                                && (starlet = prompt('Open developer console for starlet (sql name):'))
                                && Bus.console(starlet);
                        })
                        
                        .on('click', '> header ul.nav li#goto-shoebox', function (e)
                        {
                            killEvent(e);
                            
                            Starlet.onTrack(e, 'shoebox');
                            Bus.tell('overlays', {'action': 'set', 'states': {'shoebox': true}});
                        })
                        
                        .on('click', '> header ul.nav li#goto-help', function (e)
                        {
                            killEvent(e);
                            Starlet.onTrack(e, 'help');
                           // typeof ga !== 'undefined' && ga('send', 'event', 'button', 'click', 'Help');
							
							// fileName - custom help file name
                            Starlet.lightbox('help', ORIGIN + Starlet.root + '/help/{0}'.format(this.attributes['fileName'] ? this.attributes['fileName'].value : 'index.html'),
								window.innerWidth * .8, window.innerHeight * .8);
                        })
                        .on('click', '> header ul.nav li#goto-settings', function (e)
                        {
                            killEvent(e);
                            Starlet.onTrack(e, 'Settings');
                            //typeof ga !== 'undefined' && ga('send', 'event', 'button', 'click', 'Settings');
                            Bus.navRelay('settings', 'openPage', { 'page': Starlet.name });
                        })
                        
                        .on('click', '> header ul.nav li#goto-navigation', function (e)
                        {
                            killEvent(e);
                            Starlet.onTrack(e, 'Navigation');
                            //typeof ga !== 'undefined' && ga('send', 'event', 'button', 'click', 'Navigation');
                            Bus.tell('overlays', {'action': 'set', 'states': {'navigation': true}});
                        })
                        
                        .on('click', '> header ul.nav li.dev', function (e)
                        {
                            killEvent(e);
                            
                            var $this = $(this);
                            
                            if ($this.hasClass('console'))
                            {
                                var starlet = $this.attr('data-starlet');
                                
                                starlet && Bus.console(starlet);
                            }
                            else if ($this.hasClass('tearoff'))
                            {
                                Starlet.tearoff();
                            }
                        })
                        
                        .on('keyup', '', function (e)
                        {
                            e.keyCode === 27
                                && Starlet.$tint
                                && Starlet.$tint.click();
                        });
                    
                    // fetch
                    
                    Starlet.load();
                    
                    break;
                    
                case 'framework':
                    
                    // init
                    
                    var url = location.href.parseUrl();
                    
                    Starlet.loadArguments      =  Array.prototype.slice.call(args);
                    Starlet.flagFramework      =  true;
                    Starlet.name               =  Starlet.loadArguments[0];
                    Starlet.states.overlay     =  !!~$.inArray(Starlet.name, S_NAMES_OVERLAYS);
                    Starlet.states.lightbox    =  !!~$.inArray(Starlet.name, S_NAMES_LIGHTBOXES);
                    Starlet.states.tearoff     =  String(url.query.tearoff    ).toBoolean();
                    Starlet.states.minitearoff =  String(url.query.minitearoff).toBoolean();
                    Starlet.states.main        =  Starlet.name !== 'notification' &&
                                                 !Starlet.states.tearoff          &&
                                                 !Starlet.states.minitearoff;
                    Starlet.states.ordered     =  Starlet.states.main && !Starlet.states.lightbox && !Starlet.states.overlay;
                    
                    // bind events
                    
                    OrionStarletWindow.setStarletVisibleCallback('', linkCallback(function (e) { Starlet.onActiveWrapper   (e.visible); }                       , Starlet, true));
                    OrionStarletWindow.setFocusCallback         ('', linkCallback(function (e) { Starlet.onFocusedWrapper  (e.eventName === 'windowFocus'); }   , Starlet, true));
                    OrionStarletWindow.setMinimizeCallback      ('', linkCallback(function (e) { Starlet.onMinimizedWrapper(e.eventName === 'windowMinimize'); }, Starlet, true));
                    OrionStarletWindow.setHiddenCallback        ('', linkCallback(function (e) { Starlet.onHiddenWrapper   (e.eventName === 'windowHidden'); }  , Starlet, true));
                    OrionStarletWindow.setMouseCallback         ('', linkCallback(function (e) { Starlet.onMouseWrapper    (e.eventName === 'mouseEnter'); }    , Starlet, true));
                    
                    Bus.ask('connectivity' , {                  'subscribe': true, 'callback': Starlet.onConnectivity});
                    Bus.ask('starlets'     , {'action': 'list', 'subscribe': true, 'callback': Starlet.onStarletData});
                    Bus.ask('modals'       , {                  'subscribe': true, 'callback': Starlet.onModal});
                    //Bus.ask('window'       , {                  'subscribe': true, 'callback': Starlet.onWindowState});
                    //Bus.ask('application'  , {                  'subscribe': true, 'callback': Starlet.onApplicationState});
                    
                    if (!Starlet.states.overlay && !Starlet.states.lightbox)
                    {
                        Bus.ask('overlays' , {'subscribe': true, 'callback': Starlet.onOverlayState});
                        Bus.ask('userinfo', {'action': 'get'}).then(function (packet)
                        {
                                    
                            Raygun && packet.data && packet.data[0] && Raygun.setUser(packet.data[0].email);
                            !(Raygun && packet.data && packet.data[0])  &&  Console.log(' user email not set yet for raygun ... starlet.js ');
                            !packet.data[0].isGuest
                                && Starlet.$html.removeClass('guest').addClass('user');
                        });
                        
                        //Bus.ask('proximity', {'subscribe': true, 'callback': Starlet.onProximity});
                        //Bus.ask('offset'   , {'subscribe': true, 'callback': Starlet.onOffset});
                    }
                    
                    break;
                    
                case 'loaded' : Starlet.flagLoaded  = true; break;
                case 'defined': Starlet.flagDefined = true; break;
                case 'covered': Starlet.flagCovered = true; break;
                default       :                             break;
            }
            
            // all ready?
            
            if (Starlet.flagDom && Starlet.flagFramework && Starlet.flagLoaded)
            {
                !Widget.detected && Widget.detect($('body')[0]);
                
                if (Starlet.flagDefined)
                {
                    if (!Bus.registered) // temporary to prevent double binds, redo sync promise system
                    {
                        Bus.register();
                        
                        var mode = MINISHOEBOX_MODES.filter(function (term, index) { return ~Starlet.name.indexOf(term); })[0];

                        if (mode)
                        {
                            Starlet.$minishoebox
                                .data('showing', false)
                                .data('mode'   , mode)
                                .popover(
                                {
                                    'html'     : true,
                                    'placement': function () { return Starlet.$minishoebox.data('placement') || 'bottom'; },
                                    'content'  : Widget.create('MiniShoebox',
                                    {
                                        'calling_widget'      : mode,
                                        'session_id'          : '',
                                        'current_participants': []
                                    })
                                });
                            
                            $(document).on('click',
                            [
                                'html.minishoebox section#content',
                                'html.minishoebox header',
                                'html.minishoebox footer',
                                'html.minishoebox .modal',
                                'html.minishoebox .modal-backdrop',
                                'html.minishoebox #tint'
                            ]
                            .join(', '), function () { Starlet.minishoebox(); });
                        }
                        
                        $(window).on('beforeunload', function (e)
                        {
                            //e.stopPropagation();
                            //e.preventDefault();
                            log('unload');
                            //return false;
                            Starlet.onUnload && Starlet.onUnload();
                            Widget.onUnload();
                            Bus.unregister();
                        });
                        
                        window.onhashchange = function (e) { if (location.hash.length < 2)
                        {
                            location.href = e.oldURL;
                            log(L_WARN, 'window.onhashchange', e.newURL, e.oldURL);
                        }};
                    }
                    
                    if (Starlet.flagCovered && Starlet.synchronizer.state() !== 'resolved')
                    {
                        log(L_MAIN, 'Starlet.synchronizer', 'ready in ' + Starlet.loadTimer.elapsed(1));
                        Starlet.synchronizer.resolve();
                    }
                };
            }
        })
        
        .done(function ()
        {
            var loadArguments = Starlet.loadArguments;
            
            log(L_MAIN, 'Starlet.onRun', loadArguments)
                && Starlet.onRun.apply(Starlet, loadArguments);
            
            Starlet.$spinner.fadeOut(
            {
                'duration': 250,
                'easing'  : 'easeInOutCubic',
                'done'    : function ()
                {
                    Starlet.$spinner.remove();
                    setTimeout(function () { Bus.tell('starlets', {'action': 'update', 'states': {'state': S_READY}}); }, 50);
                }
            });
        }),
    
    /** @method   Starlet.tint
        @readonly
        @desc     Shows or hides the mouse-blocking tint layer.
        
        @arg      {Boolean}  state    - Whether to show or hide tint.
        @arg      {Function} callback - Function to call when transition is complete.
        @returns  {jQuery}              {@link Starlet.$tint}
    */
    'tint': function (state, callback)
    {
        log(L_DEBUG, 'Starlet.tint', arguments);
        
        var self     = arguments.callee,
            $tint    = Starlet.$tint,
            fade     = $tint[state ? 'fadeIn' : 'fadeOut'];
        
        clearTimeout(self.timeout || 0);
        $tint.stop();
        
        self.timeout = setTimeout
        (
            function ()
            {
                fade.apply($tint,
                [{
                    'duration': 0,
                    'easing'  : 'easeOutQuint',
                    'done'    : callback || function () {}
                }]);
            },
            10
        );
        
        if (state) { Starlet.minishoebox(); Starlet.menu(); }
        
        return Starlet.$tint;
    },
    
    /** @method   Starlet.dialog
        @readonly
        @desc     Displays a modal dialog with custom content and configuration.
        @todo     Return promise instead of deferred, since it needs to be turned into one to attach callbacks anyway.
                  Only the creator of the deferred should have control of it, promise holders only "take a number".
                  Also fix replacement behavior when called successively.
        
        @arg      {String}  title             - Display title of modal dialog
        @arg      {String}  description       - Body text of dialog.
        @arg      {Boolean} isHTMLDescription - Whether provided description contains HTML.
        @arg      {Boolean} isError           - Whether dialog should be styled as an error.
        @arg      {Boolean} showCancel        - Whether to include a cancel button.
        @arg      {String}  okText            - Text to fill confirmation button with.
        @returns  {Object}                      Deferred.
        
        @see      {@link Starlet.alertDialog}
        @see      {@link Starlet.confirmDialog}
        @see      {@link Starlet.freeformDialog}
        @see      {@link Starlet.showError}
        @see      {@link Starlet.modal}
        @see      {@link http://api.jquery.com/category/deferred-object/}
    */
    'dialog': function (title, description, isHTMLDescription, isError, showCancel, okText)
    {
        var existingDeferred    = $.Deferred(),
            deferred            = $.Deferred();
        
        // check for existing dialog
        if (Starlet.$modal.filter(':visible').length)
        {
            // hide existing
            Starlet.modal('hide');
            
            // resolve after timeout
            setTimeout(existingDeferred.resolve, 500);
        }
        else { existingDeferred.resolve(); }
        
        existingDeferred.then(function ()
        {
            Starlet.modal
            (
                $(Templates.Render('_dialog',
                {
                    'Title'            : title,
                    'Description'      : description,
                    'IsHTMLDescription': isHTMLDescription,
                    'IsError'          : isError,
                    'ShowCancel'       : showCancel,
                    'OkText'           : okText
                }))
                .find('.modal-footer .ok'    ).click(deferred.resolve).end()
                .find('.modal-footer .cancel').click(deferred.reject ).end()
            );
			
			// set focus to ok button so enter / esc work on this dialog
			setTimeout(function () { Starlet.$modal.find('.ok').focus(); }, 250);
        });
        
		Starlet.$modal.on('webkitTransitionEnd', function (event)
        {
            !$(this).filter(':visible').length
                && Starlet.$modal.removeAttr('type');
        });
		
        return deferred;
    },
    
    /** @method   Starlet.alertDialog
        @readonly
        @desc     Displays a modal alert dialog with custom content and configuration, but no HTML or cancel button.
        @todo     Return promise instead of deferred, since it needs to be turned into one to attach callbacks anyway.
                  Only the creator of the deferred should have control of it, promise holders only "take a number".
        
        @arg      {String}  title       - Display title of modal dialog
        @arg      {String}  description - Body text of dialog.
        @arg      {Boolean} isError     - Whether dialog should be styled as an error.
        @arg      {String}  okText      - Text to fill confirmation button with.
        @returns  {Object}                Deferred.
        
        @see      {@link Starlet.dialog}
        @see      {@link Starlet.confirmDialog}
        @see      {@link Starlet.freeformDialog}
        @see      {@link Starlet.showError}
        @see      {@link Starlet.modal}
        @see      {@link http://api.jquery.com/category/deferred-object/}
    */
    'alertDialog': function (title, description, isError, okText)
    {
        //console.log('existing dialog', this.$modal.filter(':visible').length);
        
        return this.dialog(title, description, false, isError, false, okText);
    },
    
    /** @method   Starlet.confirmDialog
        @readonly
        @desc     Displays a modal confirmation dialog with custom content and configuration,
                  but no HTML or error styling.
        @todo     Return promise instead of deferred, since it needs to be turned into one to attach callbacks anyway.
                  Only the creator of the deferred should have control of it, promise holders only "take a number".
        
        @arg      {String}  title       - Display title of modal dialog
        @arg      {String}  description - Body text of dialog.
        @arg      {String}  okText      - Text to fill confirmation button with.
        @returns  {Object}                Deferred.
        
        @see      {@link Starlet.dialog}
        @see      {@link Starlet.alertDialog}
        @see      {@link Starlet.freeformDialog}
        @see      {@link Starlet.showError}
        @see      {@link Starlet.modal}
        @see      {@link http://api.jquery.com/category/deferred-object/}
    */
    'confirmDialog': function (title, description, okText)
    {
        return this.dialog(title, description, false, false, true, okText);
    },
	
    /** @method   Starlet.freeformDialog
        @readonly
        @desc     Displays a modal freeform dialog with completely custom content.
        
        @arg      {String}  content     - HTML content of dialog.
        @arg      {String}  customClass - Unique classname allowing for custom styling.
        @returns  {jQuery}                {@link Starlet.$modal}
        
        @see      {@link Starlet.dialog}
        @see      {@link Starlet.alertDialog}
        @see      {@link Starlet.confirmDialog}
        @see      {@link Starlet.showError}
        @see      {@link Starlet.modal}
    */
	'freeformDialog': function (content, customClass)
	{
		return Starlet.modal($(Templates.Render('_dialogFreeform', { Content: content, Class: customClass })));
	},
    
    /** @method   Starlet.showError
        @readonly
        @desc     Displays a modal error dialog and logs a separate message and object for debugging.
        
        @arg      {String}  userErrorMessage - Message to be displayed to the user.
        @arg      {String}  logErrorMessage  - Message to log.
        @arg      {String}  logErrorObject   - Object to log.
        @returns  {Boolean}                    true
        
        @see      {@link Starlet.dialog}
        @see      {@link Starlet.alertDialog}
        @see      {@link Starlet.confirmDialog}
        @see      {@link Starlet.freeformDialog}
        @see      {@link Starlet.modal}
    */
    'showError': function (userErrorMessage, logErrorMessage, logErrorObject)
    {
        // show alert if we have a user message
        
        userErrorMessage
            && Starlet.alertDialog('Error', userErrorMessage, true);
        
        // if we have a user message, but no log message, log the user message
        
        userErrorMessage
            && !logErrorMessage
            && (logErrorMessage = userErrorMessage);
        
        // log error if we have something to log
        
        (userErrorMessage || logErrorMessage || logErrorObject)
            && log(L_ERROR, logErrorMessage, logErrorObject)
            && console.log(logErrorMessage, logErrorObject);
        
        return true;
    },
    
    /** @method    Starlet.tearoff
        @readonly
        @chainable
        @desc      Toggles whether the Starlet's WebView is in the main application window, does not cause a reload.
        
        @arg       {Boolean} [mode] - Whether to take WebView out of the main application window or restore it.
                                      Toggles if omitted.
        @returns   {Object}           {@link Starlet}
        
        @see       {@link Starlet.states.tearoff}
        @see       {@link OrionTearoff.create}
        @see       {@link OrionTearoff.remove}
    */
    'tearoff': function (mode)
    {
        mode = arguments.length ? !!mode : !Starlet.states.tearoff;
        
        if (Starlet.states.tearoff !== mode)
        {
            Starlet.states.tearoff =  mode;
            Starlet.states.main    = !mode;
            OrionTearoff[mode ? 'create' : 'remove']();
            Bus.tell('starlets', {'action': 'update', 'states': {'tearoff': mode, 'main': !mode}});
        }
        
        return Starlet;
    },
    
    /** @method    Starlet.repaint
        @readonly
        @chainable
        @desc      Momentarily alters the DOM in a way that causes a repaint of the entire WebView.
        @returns   {Object} {@link Starlet}
    */
    'repaint': function ()
    {
                           Starlet.$html.addClass   ('repaint');
        fork(function () { Starlet.$html.removeClass('repaint'); });
        
        //$('<style></style>').appendTo(document.body).remove(); // didn't work
        
        return Starlet;
    }
};

/* READIES */

function on_load() { Starlet.synchronizer.notify('framework', arguments); };

$(function () { Starlet.synchronizer.notify('dom'); });