OPNFV Infra Dashboard 11/15011/2
authorjose.lausuch <jose.lausuch@ericsson.com>
Wed, 1 Jun 2016 21:00:08 +0000 (23:00 +0200)
committerjose.lausuch <jose.lausuch@ericsson.com>
Thu, 2 Jun 2016 18:12:49 +0000 (20:12 +0200)
JIRA: RELENG-12

Change-Id: I7451a3d234e4e5d946cdb905d5720be6159b6544
Signed-off-by: jose.lausuch <jose.lausuch@ericsson.com>
44 files changed:
tools/infra-dashboard/README.md [new file with mode: 0644]
tools/infra-dashboard/css/bootstrap.min.css [new file with mode: 0644]
tools/infra-dashboard/css/dataTables.bootstrap.min.css [new file with mode: 0644]
tools/infra-dashboard/css/font-awesome.css [new file with mode: 0644]
tools/infra-dashboard/css/fullcalendar.css [new file with mode: 0644]
tools/infra-dashboard/css/fullcalendar.print.css [new file with mode: 0644]
tools/infra-dashboard/css/highslide.min.css [new file with mode: 0644]
tools/infra-dashboard/css/opnfv.css [new file with mode: 0644]
tools/infra-dashboard/css/source-sans-pro.css [new file with mode: 0644]
tools/infra-dashboard/css/template.css [new file with mode: 0644]
tools/infra-dashboard/css/theme.css [new file with mode: 0644]
tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf [new file with mode: 0644]
tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf [new file with mode: 0644]
tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf [new file with mode: 0644]
tools/infra-dashboard/fonts/fontawesome-webfont.ttf [new file with mode: 0644]
tools/infra-dashboard/fonts/fontawesome-webfont.woff [new file with mode: 0644]
tools/infra-dashboard/fonts/fontawesome-webfont.woff2 [new file with mode: 0644]
tools/infra-dashboard/fonts/source-sans-pro.black.ttf [new file with mode: 0644]
tools/infra-dashboard/index.php [new file with mode: 0644]
tools/infra-dashboard/js/bootstrap.js [new file with mode: 0644]
tools/infra-dashboard/js/fullcalendar.js [new file with mode: 0644]
tools/infra-dashboard/js/fullcalendar.min.js [new file with mode: 0644]
tools/infra-dashboard/js/highslide-full.min.js [new file with mode: 0644]
tools/infra-dashboard/js/highslide.config.min.js [new file with mode: 0644]
tools/infra-dashboard/js/jquery-1.7.2.js [new file with mode: 0644]
tools/infra-dashboard/js/jquery.min.js [new file with mode: 0644]
tools/infra-dashboard/js/modernizr.js [new file with mode: 0644]
tools/infra-dashboard/js/moment.min.js [new file with mode: 0644]
tools/infra-dashboard/js/script.js [new file with mode: 0644]
tools/infra-dashboard/js/test_graph.js [new file with mode: 0644]
tools/infra-dashboard/media/ajax-loader.gif [new file with mode: 0644]
tools/infra-dashboard/media/collaborative-projects-logo.png [new file with mode: 0644]
tools/infra-dashboard/media/diagonal-white.png [new file with mode: 0644]
tools/infra-dashboard/media/favicon_400x400.jpg [new file with mode: 0644]
tools/infra-dashboard/media/loader.white.gif [new file with mode: 0644]
tools/infra-dashboard/media/opnfvlogo.JPG [new file with mode: 0644]
tools/infra-dashboard/pages/ci_pods.php [new file with mode: 0644]
tools/infra-dashboard/pages/dev_pods.php [new file with mode: 0644]
tools/infra-dashboard/pages/slaves.php [new file with mode: 0644]
tools/infra-dashboard/populateDB.txt [new file with mode: 0644]
tools/infra-dashboard/utils/book.php [new file with mode: 0644]
tools/infra-dashboard/utils/database.php [new file with mode: 0644]
tools/infra-dashboard/utils/jenkinsAdapter.php [new file with mode: 0644]
tools/infra-dashboard/utils/login.php [new file with mode: 0644]

diff --git a/tools/infra-dashboard/README.md b/tools/infra-dashboard/README.md
new file mode 100644 (file)
index 0000000..b43afd6
--- /dev/null
@@ -0,0 +1,3 @@
+# OPNFV Infrastructure Dashboard
+This is the dashboard proposal for OPNFV Pharos and CI resources reservation.
+Creator and maintainer: jose.lausuch@ericsson.com
diff --git a/tools/infra-dashboard/css/bootstrap.min.css b/tools/infra-dashboard/css/bootstrap.min.css
new file mode 100644 (file)
index 0000000..1d23653
--- /dev/null
@@ -0,0 +1,5488 @@
+/*!
+ * Bootstrap v3.2.0 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+/*!
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=e82eeb1f3b04b506268e)
+ * Config saved to config.json and https://gist.github.com/e82eeb1f3b04b506268e
+ */
+/*! normalize.css v3.0.1 | MIT License | git.io/normalize */
+
+.sr-only,
+svg:not(:root) {
+    overflow: hidden
+}
+hr,
+img {
+    border: 0
+}
+body,
+figure {
+    margin: 0
+}
+.img-thumbnail,
+.thumbnail {
+    -webkit-transition: all .2s ease-in-out
+}
+.btn-group>.btn-group,
+.btn-toolbar .btn-group,
+.btn-toolbar .input-group,
+.col-xs-1,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.dropdown-menu {
+    float: left
+}
+.navbar-fixed-bottom .navbar-collapse,
+.navbar-fixed-top .navbar-collapse,
+.pre-scrollable {
+    max-height: 340px
+}
+html {
+    font-family: sans-serif;
+    -ms-text-size-adjust: 100%;
+    -webkit-text-size-adjust: 100%;
+    font-size: 10px;
+    -webkit-tap-highlight-color: transparent
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+    display: block
+}
+audio,
+canvas,
+progress,
+video {
+    display: inline-block;
+    vertical-align: baseline
+}
+audio:not([controls]) {
+    display: none;
+    height: 0
+}
+[hidden],
+template {
+    display: none
+}
+a {
+    background: 0 0
+}
+a:active,
+a:hover {
+    outline: 0
+}
+b,
+optgroup,
+strong {
+    font-weight: 700
+}
+dfn {
+    font-style: italic
+}
+h1 {
+    margin: .67em 0
+}
+mark {
+    background: #ff0;
+    color: #000
+}
+sub,
+sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline
+}
+sup {
+    top: -.5em
+}
+sub {
+    bottom: -.25em
+}
+img {
+    vertical-align: middle
+}
+hr {
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+    height: 0
+}
+pre,
+textarea {
+    overflow: auto
+}
+code,
+kbd,
+pre,
+samp {
+    font-size: 1em
+}
+button,
+input,
+optgroup,
+select,
+textarea {
+    color: inherit;
+    font: inherit;
+    margin: 0
+}
+button {
+    overflow: visible
+}
+button,
+select {
+    text-transform: none
+}
+button,
+html input[type=button],
+input[type=reset],
+input[type=submit] {
+    -webkit-appearance: button;
+    cursor: pointer
+}
+button[disabled],
+html input[disabled] {
+    cursor: default
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+    border: 0;
+    padding: 0
+}
+input[type=checkbox],
+input[type=radio] {
+    box-sizing: border-box;
+    padding: 0
+}
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+    height: auto
+}
+input[type=search]::-webkit-search-cancel-button,
+input[type=search]::-webkit-search-decoration {
+    -webkit-appearance: none
+}
+table {
+    border-collapse: collapse;
+    border-spacing: 0
+}
+td,
+th {
+    padding: 0
+}
+@media print {
+    blockquote,
+    img,
+    pre,
+    tr {
+        page-break-inside: avoid
+    }
+    * {
+        text-shadow: none!important;
+        color: #000!important;
+        background: 0 0!important;
+        box-shadow: none!important
+    }
+    a,
+    a:visited {
+        text-decoration: underline
+    }
+    a[href]:after {
+        content: " (" attr(href) ")"
+    }
+    abbr[title]:after {
+        content: " (" attr(title) ")"
+    }
+    a[href^="#"]:after,
+    a[href^="javascript:"]:after {
+        content: ""
+    }
+    blockquote,
+    pre {
+        border: 1px solid #999
+    }
+    thead {
+        display: table-header-group
+    }
+    img {
+        max-width: 100%!important
+    }
+    h2,
+    h3,
+    p {
+        orphans: 3;
+        widows: 3
+    }
+    h2,
+    h3 {
+        page-break-after: avoid
+    }
+    select {
+        background: #fff!important
+    }
+    .navbar {
+        display: none
+    }
+    .table td,
+    .table th {
+        background-color: #fff!important
+    }
+    .btn>.caret,
+    .dropup>.btn>.caret {
+        border-top-color: #000!important
+    }
+    .label {
+        border: 1px solid #000
+    }
+    .table {
+        border-collapse: collapse!important
+    }
+    .table-bordered td,
+    .table-bordered th {
+        border: 1px solid #ddd!important
+    }
+}
+.btn,
+.btn-danger.active,
+.btn-danger:active,
+.btn-default.active,
+.btn-default:active,
+.btn-info.active,
+.btn-info:active,
+.btn-primary.active,
+.btn-primary:active,
+.btn-warning.active,
+.btn-warning:active,
+.btn.active,
+.btn:active,
+.dropdown-menu>.disabled>a:focus,
+.dropdown-menu>.disabled>a:hover,
+.form-control,
+.navbar-toggle,
+.open>.dropdown-toggle.btn-danger,
+.open>.dropdown-toggle.btn-default,
+.open>.dropdown-toggle.btn-info,
+.open>.dropdown-toggle.btn-primary,
+.open>.dropdown-toggle.btn-warning {
+    background-image: none
+}
+.img-thumbnail,
+body {
+    background-color: #fff
+}
+*,
+:after,
+:before {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box
+}
+body {
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+    font-size: 14px;
+    line-height: 1.42857143;
+    color: #333
+}
+button,
+input,
+select,
+textarea {
+    font-family: inherit;
+    font-size: inherit;
+    line-height: inherit
+}
+a {
+    color: #428bca;
+    text-decoration: none
+}
+a:focus,
+a:hover {
+    color: #2a6496;
+    text-decoration: underline
+}
+a:focus {
+    outline: dotted thin;
+    outline: -webkit-focus-ring-color auto 5px;
+    outline-offset: -2px
+}
+.carousel-inner>.item>a>img,
+.carousel-inner>.item>img,
+.img-responsive,
+.thumbnail a>img,
+.thumbnail>img {
+    display: block;
+    width: 100%\9;
+    max-width: 100%;
+    height: auto
+}
+.img-rounded {
+    border-radius: 6px
+}
+.img-thumbnail {
+    padding: 4px;
+    line-height: 1.42857143;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    -o-transition: all .2s ease-in-out;
+    transition: all .2s ease-in-out;
+    display: inline-block;
+    width: 100%\9;
+    max-width: 100%;
+    height: auto
+}
+.img-circle {
+    border-radius: 50%
+}
+hr {
+    margin-top: 20px;
+    margin-bottom: 20px;
+    border-top: 1px solid #eee
+}
+.sr-only {
+    position: absolute;
+    width: 1px;
+    height: 1px;
+    margin: -1px;
+    padding: 0;
+    clip: rect(0, 0, 0, 0);
+    border: 0
+}
+.sr-only-focusable:active,
+.sr-only-focusable:focus {
+    position: static;
+    width: auto;
+    height: auto;
+    margin: 0;
+    overflow: visible;
+    clip: auto
+}
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+    font-family: inherit;
+    font-weight: 500;
+    line-height: 1.1;
+    color: inherit
+}
+.h1 .small,
+.h1 small,
+.h2 .small,
+.h2 small,
+.h3 .small,
+.h3 small,
+.h4 .small,
+.h4 small,
+.h5 .small,
+.h5 small,
+.h6 .small,
+.h6 small,
+h1 .small,
+h1 small,
+h2 .small,
+h2 small,
+h3 .small,
+h3 small,
+h4 .small,
+h4 small,
+h5 .small,
+h5 small,
+h6 .small,
+h6 small {
+    font-weight: 400;
+    line-height: 1;
+    color: #777
+}
+.h1,
+.h2,
+.h3,
+h1,
+h2,
+h3 {
+    margin-top: 20px;
+    margin-bottom: 10px
+}
+.h1 .small,
+.h1 small,
+.h2 .small,
+.h2 small,
+.h3 .small,
+.h3 small,
+h1 .small,
+h1 small,
+h2 .small,
+h2 small,
+h3 .small,
+h3 small {
+    font-size: 65%
+}
+.h4,
+.h5,
+.h6,
+h4,
+h5,
+h6 {
+    margin-top: 10px;
+    margin-bottom: 10px
+}
+.h4 .small,
+.h4 small,
+.h5 .small,
+.h5 small,
+.h6 .small,
+.h6 small,
+h4 .small,
+h4 small,
+h5 .small,
+h5 small,
+h6 .small,
+h6 small {
+    font-size: 75%
+}
+.h1,
+h1 {
+    font-size: 36px
+}
+.h2,
+h2 {
+    font-size: 30px
+}
+.h3,
+h3 {
+    font-size: 24px
+}
+.h4,
+h4 {
+    font-size: 18px
+}
+.h5,
+h5 {
+    font-size: 14px
+}
+.h6,
+h6 {
+    font-size: 9pt
+}
+p {
+    margin: 0 0 10px
+}
+.lead {
+    margin-bottom: 20px;
+    font-size: 1pc;
+    font-weight: 300;
+    line-height: 1.4
+}
+dt,
+label {
+    font-weight: 700
+}
+address,
+blockquote .small,
+blockquote footer,
+blockquote small,
+dd,
+dt,
+pre {
+    line-height: 1.42857143
+}
+@media (min-width: 768px) {
+    .lead {
+        font-size: 21px
+    }
+}
+.small,
+small {
+    font-size: 85%
+}
+cite {
+    font-style: normal
+}
+.mark,
+mark {
+    background-color: #fcf8e3;
+    padding: .2em
+}
+.list-inline,
+.list-unstyled {
+    padding-left: 0;
+    list-style: none
+}
+.text-left {
+    text-align: left
+}
+.text-right {
+    text-align: right
+}
+.text-center {
+    text-align: center
+}
+.text-justify {
+    text-align: justify
+}
+.text-nowrap {
+    white-space: nowrap
+}
+.text-lowercase {
+    text-transform: lowercase
+}
+.text-uppercase {
+    text-transform: uppercase
+}
+.text-capitalize {
+    text-transform: capitalize
+}
+.text-muted {
+    color: #777
+}
+.text-primary {
+    color: #428bca
+}
+a.text-primary:hover {
+    color: #3071a9
+}
+.text-success {
+    color: #3c763d
+}
+a.text-success:hover {
+    color: #2b542c
+}
+.text-info {
+    color: #31708f
+}
+a.text-info:hover {
+    color: #245269
+}
+.text-warning {
+    color: #8a6d3b
+}
+a.text-warning:hover {
+    color: #66512c
+}
+.text-danger {
+    color: #a94442
+}
+a.text-danger:hover {
+    color: #843534
+}
+.bg-primary {
+    color: #fff;
+    background-color: #428bca
+}
+a.bg-primary:hover {
+    background-color: #3071a9
+}
+.bg-success {
+    background-color: #dff0d8
+}
+a.bg-success:hover {
+    background-color: #c1e2b3
+}
+.bg-info {
+    background-color: #d9edf7
+}
+a.bg-info:hover {
+    background-color: #afd9ee
+}
+.bg-warning {
+    background-color: #fcf8e3
+}
+a.bg-warning:hover {
+    background-color: #f7ecb5
+}
+.bg-danger {
+    background-color: #f2dede
+}
+a.bg-danger:hover {
+    background-color: #e4b9b9
+}
+pre code,
+table {
+    background-color: transparent
+}
+.page-header {
+    padding-bottom: 9px;
+    margin: 40px 0 20px;
+    border-bottom: 1px solid #eee
+}
+dl,
+ol,
+ul {
+    margin-top: 0
+}
+blockquote ol:last-child,
+blockquote p:last-child,
+blockquote ul:last-child,
+ol ol,
+ol ul,
+ul ol,
+ul ul {
+    margin-bottom: 0
+}
+address,
+dl {
+    margin-bottom: 20px
+}
+ol,
+ul {
+    margin-bottom: 10px
+}
+.list-inline {
+    margin-left: -5px
+}
+.list-inline>li {
+    display: inline-block;
+    padding-left: 5px;
+    padding-right: 5px
+}
+dd {
+    margin-left: 0
+}
+@media (min-width: 768px) {
+    .dl-horizontal dt {
+        float: left;
+        width: 10pc;
+        clear: left;
+        text-align: right;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap
+    }
+    .dl-horizontal dd {
+        margin-left: 180px
+    }
+    .container {
+        width: 750px
+    }
+}
+abbr[data-original-title],
+abbr[title] {
+    cursor: help;
+    border-bottom: 1px dotted #777
+}
+.initialism {
+    font-size: 90%;
+    text-transform: uppercase
+}
+blockquote {
+    padding: 10px 20px;
+    margin: 0 0 20px;
+    font-size: 17.5px;
+    border-left: 5px solid #eee
+}
+blockquote .small,
+blockquote footer,
+blockquote small {
+    display: block;
+    font-size: 80%;
+    color: #777
+}
+legend,
+pre {
+    display: block;
+    color: #333
+}
+blockquote .small:before,
+blockquote footer:before,
+blockquote small:before {
+    content: '\2014 \00A0'
+}
+.blockquote-reverse,
+blockquote.pull-right {
+    padding-right: 15px;
+    padding-left: 0;
+    border-right: 5px solid #eee;
+    border-left: 0;
+    text-align: right
+}
+code,
+kbd {
+    padding: 2px 4px;
+    font-size: 90%
+}
+.blockquote-reverse .small:before,
+.blockquote-reverse footer:before,
+.blockquote-reverse small:before,
+blockquote.pull-right .small:before,
+blockquote.pull-right footer:before,
+blockquote.pull-right small:before {
+    content: ''
+}
+.blockquote-reverse .small:after,
+.blockquote-reverse footer:after,
+.blockquote-reverse small:after,
+blockquote.pull-right .small:after,
+blockquote.pull-right footer:after,
+blockquote.pull-right small:after {
+    content: '\00A0 \2014'
+}
+.popover>.arrow:after,
+blockquote:after,
+blockquote:before {
+    content: ""
+}
+address {
+    font-style: normal
+}
+code,
+kbd,
+pre,
+samp {
+    font-family: Menlo, Monaco, Consolas, "Courier New", monospace
+}
+code {
+    color: #c7254e;
+    background-color: #f9f2f4;
+    border-radius: 4px
+}
+kbd {
+    color: #fff;
+    background-color: #333;
+    border-radius: 3px;
+    box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25)
+}
+kbd kbd {
+    padding: 0;
+    font-size: 100%;
+    box-shadow: none
+}
+pre {
+    padding: 9.5px;
+    margin: 0 0 10px;
+    font-size: 13px;
+    word-break: break-all;
+    word-wrap: break-word;
+    background-color: #f5f5f5;
+    border: 1px solid #ccc;
+    border-radius: 4px
+}
+.container,
+.container-fluid {
+    margin-right: auto;
+    margin-left: auto
+}
+pre code {
+    padding: 0;
+    font-size: inherit;
+    color: inherit;
+    white-space: pre-wrap;
+    border-radius: 0
+}
+.container,
+.container-fluid {
+    padding-left: 15px;
+    padding-right: 15px
+}
+.pre-scrollable {
+    overflow-y: scroll
+}
+@media (min-width: 992px) {
+    .container {
+        width: 970px
+    }
+}
+@media (min-width: 1200px) {
+    .container {
+        width: 1170px
+    }
+}
+.row {
+    margin-left: -15px;
+    margin-right: -15px
+}
+.col-lg-1,
+.col-lg-10,
+.col-lg-11,
+.col-lg-12,
+.col-lg-2,
+.col-lg-3,
+.col-lg-4,
+.col-lg-5,
+.col-lg-6,
+.col-lg-7,
+.col-lg-8,
+.col-lg-9,
+.col-md-1,
+.col-md-10,
+.col-md-11,
+.col-md-12,
+.col-md-2,
+.col-md-3,
+.col-md-4,
+.col-md-5,
+.col-md-6,
+.col-md-7,
+.col-md-8,
+.col-md-9,
+.col-sm-1,
+.col-sm-10,
+.col-sm-11,
+.col-sm-12,
+.col-sm-2,
+.col-sm-3,
+.col-sm-4,
+.col-sm-5,
+.col-sm-6,
+.col-sm-7,
+.col-sm-8,
+.col-sm-9,
+.col-xs-1,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9 {
+    position: relative;
+    min-height: 1px;
+    padding-left: 15px;
+    padding-right: 15px
+}
+.col-xs-12 {
+    width: 100%
+}
+.col-xs-11 {
+    width: 91.66666667%
+}
+.col-xs-10 {
+    width: 83.33333333%
+}
+.col-xs-9 {
+    width: 75%
+}
+.col-xs-8 {
+    width: 66.66666667%
+}
+.col-xs-7 {
+    width: 58.33333333%
+}
+.col-xs-6 {
+    width: 50%
+}
+.col-xs-5 {
+    width: 41.66666667%
+}
+.col-xs-4 {
+    width: 33.33333333%
+}
+.col-xs-3 {
+    width: 25%
+}
+.col-xs-2 {
+    width: 16.66666667%
+}
+.col-xs-1 {
+    width: 8.33333333%
+}
+.col-xs-pull-12 {
+    right: 100%
+}
+.col-xs-pull-11 {
+    right: 91.66666667%
+}
+.col-xs-pull-10 {
+    right: 83.33333333%
+}
+.col-xs-pull-9 {
+    right: 75%
+}
+.col-xs-pull-8 {
+    right: 66.66666667%
+}
+.col-xs-pull-7 {
+    right: 58.33333333%
+}
+.col-xs-pull-6 {
+    right: 50%
+}
+.col-xs-pull-5 {
+    right: 41.66666667%
+}
+.col-xs-pull-4 {
+    right: 33.33333333%
+}
+.col-xs-pull-3 {
+    right: 25%
+}
+.col-xs-pull-2 {
+    right: 16.66666667%
+}
+.col-xs-pull-1 {
+    right: 8.33333333%
+}
+.col-xs-pull-0 {
+    right: auto
+}
+.col-xs-push-12 {
+    left: 100%
+}
+.col-xs-push-11 {
+    left: 91.66666667%
+}
+.col-xs-push-10 {
+    left: 83.33333333%
+}
+.col-xs-push-9 {
+    left: 75%
+}
+.col-xs-push-8 {
+    left: 66.66666667%
+}
+.col-xs-push-7 {
+    left: 58.33333333%
+}
+.col-xs-push-6 {
+    left: 50%
+}
+.col-xs-push-5 {
+    left: 41.66666667%
+}
+.col-xs-push-4 {
+    left: 33.33333333%
+}
+.col-xs-push-3 {
+    left: 25%
+}
+.col-xs-push-2 {
+    left: 16.66666667%
+}
+.col-xs-push-1 {
+    left: 8.33333333%
+}
+.col-xs-push-0 {
+    left: auto
+}
+.col-xs-offset-12 {
+    margin-left: 100%
+}
+.col-xs-offset-11 {
+    margin-left: 91.66666667%
+}
+.col-xs-offset-10 {
+    margin-left: 83.33333333%
+}
+.col-xs-offset-9 {
+    margin-left: 75%
+}
+.col-xs-offset-8 {
+    margin-left: 66.66666667%
+}
+.col-xs-offset-7 {
+    margin-left: 58.33333333%
+}
+.col-xs-offset-6 {
+    margin-left: 50%
+}
+.col-xs-offset-5 {
+    margin-left: 41.66666667%
+}
+.col-xs-offset-4 {
+    margin-left: 33.33333333%
+}
+.col-xs-offset-3 {
+    margin-left: 25%
+}
+.col-xs-offset-2 {
+    margin-left: 16.66666667%
+}
+.col-xs-offset-1 {
+    margin-left: 8.33333333%
+}
+.col-xs-offset-0 {
+    margin-left: 0
+}
+@media (min-width: 768px) {
+    .col-sm-1,
+    .col-sm-10,
+    .col-sm-11,
+    .col-sm-12,
+    .col-sm-2,
+    .col-sm-3,
+    .col-sm-4,
+    .col-sm-5,
+    .col-sm-6,
+    .col-sm-7,
+    .col-sm-8,
+    .col-sm-9 {
+        float: left
+    }
+    .col-sm-12 {
+        width: 100%
+    }
+    .col-sm-11 {
+        width: 91.66666667%
+    }
+    .col-sm-10 {
+        width: 83.33333333%
+    }
+    .col-sm-9 {
+        width: 75%
+    }
+    .col-sm-8 {
+        width: 66.66666667%
+    }
+    .col-sm-7 {
+        width: 58.33333333%
+    }
+    .col-sm-6 {
+        width: 50%
+    }
+    .col-sm-5 {
+        width: 41.66666667%
+    }
+    .col-sm-4 {
+        width: 33.33333333%
+    }
+    .col-sm-3 {
+        width: 25%
+    }
+    .col-sm-2 {
+        width: 16.66666667%
+    }
+    .col-sm-1 {
+        width: 8.33333333%
+    }
+    .col-sm-pull-12 {
+        right: 100%
+    }
+    .col-sm-pull-11 {
+        right: 91.66666667%
+    }
+    .col-sm-pull-10 {
+        right: 83.33333333%
+    }
+    .col-sm-pull-9 {
+        right: 75%
+    }
+    .col-sm-pull-8 {
+        right: 66.66666667%
+    }
+    .col-sm-pull-7 {
+        right: 58.33333333%
+    }
+    .col-sm-pull-6 {
+        right: 50%
+    }
+    .col-sm-pull-5 {
+        right: 41.66666667%
+    }
+    .col-sm-pull-4 {
+        right: 33.33333333%
+    }
+    .col-sm-pull-3 {
+        right: 25%
+    }
+    .col-sm-pull-2 {
+        right: 16.66666667%
+    }
+    .col-sm-pull-1 {
+        right: 8.33333333%
+    }
+    .col-sm-pull-0 {
+        right: auto
+    }
+    .col-sm-push-12 {
+        left: 100%
+    }
+    .col-sm-push-11 {
+        left: 91.66666667%
+    }
+    .col-sm-push-10 {
+        left: 83.33333333%
+    }
+    .col-sm-push-9 {
+        left: 75%
+    }
+    .col-sm-push-8 {
+        left: 66.66666667%
+    }
+    .col-sm-push-7 {
+        left: 58.33333333%
+    }
+    .col-sm-push-6 {
+        left: 50%
+    }
+    .col-sm-push-5 {
+        left: 41.66666667%
+    }
+    .col-sm-push-4 {
+        left: 33.33333333%
+    }
+    .col-sm-push-3 {
+        left: 25%
+    }
+    .col-sm-push-2 {
+        left: 16.66666667%
+    }
+    .col-sm-push-1 {
+        left: 8.33333333%
+    }
+    .col-sm-push-0 {
+        left: auto
+    }
+    .col-sm-offset-12 {
+        margin-left: 100%
+    }
+    .col-sm-offset-11 {
+        margin-left: 91.66666667%
+    }
+    .col-sm-offset-10 {
+        margin-left: 83.33333333%
+    }
+    .col-sm-offset-9 {
+        margin-left: 75%
+    }
+    .col-sm-offset-8 {
+        margin-left: 66.66666667%
+    }
+    .col-sm-offset-7 {
+        margin-left: 58.33333333%
+    }
+    .col-sm-offset-6 {
+        margin-left: 50%
+    }
+    .col-sm-offset-5 {
+        margin-left: 41.66666667%
+    }
+    .col-sm-offset-4 {
+        margin-left: 33.33333333%
+    }
+    .col-sm-offset-3 {
+        margin-left: 25%
+    }
+    .col-sm-offset-2 {
+        margin-left: 16.66666667%
+    }
+    .col-sm-offset-1 {
+        margin-left: 8.33333333%
+    }
+    .col-sm-offset-0 {
+        margin-left: 0
+    }
+}
+@media (min-width: 992px) {
+    .col-md-1,
+    .col-md-10,
+    .col-md-11,
+    .col-md-12,
+    .col-md-2,
+    .col-md-3,
+    .col-md-4,
+    .col-md-5,
+    .col-md-6,
+    .col-md-7,
+    .col-md-8,
+    .col-md-9 {
+        float: left
+    }
+    .col-md-12 {
+        width: 100%
+    }
+    .col-md-11 {
+        width: 91.66666667%
+    }
+    .col-md-10 {
+        width: 83.33333333%
+    }
+    .col-md-9 {
+        width: 75%
+    }
+    .col-md-8 {
+        width: 66.66666667%
+    }
+    .col-md-7 {
+        width: 58.33333333%
+    }
+    .col-md-6 {
+        width: 50%
+    }
+    .col-md-5 {
+        width: 41.66666667%
+    }
+    .col-md-4 {
+        width: 33.33333333%
+    }
+    .col-md-3 {
+        width: 25%
+    }
+    .col-md-2 {
+        width: 16.66666667%
+    }
+    .col-md-1 {
+        width: 8.33333333%
+    }
+    .col-md-pull-12 {
+        right: 100%
+    }
+    .col-md-pull-11 {
+        right: 91.66666667%
+    }
+    .col-md-pull-10 {
+        right: 83.33333333%
+    }
+    .col-md-pull-9 {
+        right: 75%
+    }
+    .col-md-pull-8 {
+        right: 66.66666667%
+    }
+    .col-md-pull-7 {
+        right: 58.33333333%
+    }
+    .col-md-pull-6 {
+        right: 50%
+    }
+    .col-md-pull-5 {
+        right: 41.66666667%
+    }
+    .col-md-pull-4 {
+        right: 33.33333333%
+    }
+    .col-md-pull-3 {
+        right: 25%
+    }
+    .col-md-pull-2 {
+        right: 16.66666667%
+    }
+    .col-md-pull-1 {
+        right: 8.33333333%
+    }
+    .col-md-pull-0 {
+        right: auto
+    }
+    .col-md-push-12 {
+        left: 100%
+    }
+    .col-md-push-11 {
+        left: 91.66666667%
+    }
+    .col-md-push-10 {
+        left: 83.33333333%
+    }
+    .col-md-push-9 {
+        left: 75%
+    }
+    .col-md-push-8 {
+        left: 66.66666667%
+    }
+    .col-md-push-7 {
+        left: 58.33333333%
+    }
+    .col-md-push-6 {
+        left: 50%
+    }
+    .col-md-push-5 {
+        left: 41.66666667%
+    }
+    .col-md-push-4 {
+        left: 33.33333333%
+    }
+    .col-md-push-3 {
+        left: 25%
+    }
+    .col-md-push-2 {
+        left: 16.66666667%
+    }
+    .col-md-push-1 {
+        left: 8.33333333%
+    }
+    .col-md-push-0 {
+        left: auto
+    }
+    .col-md-offset-12 {
+        margin-left: 100%
+    }
+    .col-md-offset-11 {
+        margin-left: 91.66666667%
+    }
+    .col-md-offset-10 {
+        margin-left: 83.33333333%
+    }
+    .col-md-offset-9 {
+        margin-left: 75%
+    }
+    .col-md-offset-8 {
+        margin-left: 66.66666667%
+    }
+    .col-md-offset-7 {
+        margin-left: 58.33333333%
+    }
+    .col-md-offset-6 {
+        margin-left: 50%
+    }
+    .col-md-offset-5 {
+        margin-left: 41.66666667%
+    }
+    .col-md-offset-4 {
+        margin-left: 33.33333333%
+    }
+    .col-md-offset-3 {
+        margin-left: 25%
+    }
+    .col-md-offset-2 {
+        margin-left: 16.66666667%
+    }
+    .col-md-offset-1 {
+        margin-left: 8.33333333%
+    }
+    .col-md-offset-0 {
+        margin-left: 0
+    }
+}
+@media (min-width: 1200px) {
+    .col-lg-1,
+    .col-lg-10,
+    .col-lg-11,
+    .col-lg-12,
+    .col-lg-2,
+    .col-lg-3,
+    .col-lg-4,
+    .col-lg-5,
+    .col-lg-6,
+    .col-lg-7,
+    .col-lg-8,
+    .col-lg-9 {
+        float: left
+    }
+    .col-lg-12 {
+        width: 100%
+    }
+    .col-lg-11 {
+        width: 91.66666667%
+    }
+    .col-lg-10 {
+        width: 83.33333333%
+    }
+    .col-lg-9 {
+        width: 75%
+    }
+    .col-lg-8 {
+        width: 66.66666667%
+    }
+    .col-lg-7 {
+        width: 58.33333333%
+    }
+    .col-lg-6 {
+        width: 50%
+    }
+    .col-lg-5 {
+        width: 41.66666667%
+    }
+    .col-lg-4 {
+        width: 33.33333333%
+    }
+    .col-lg-3 {
+        width: 25%
+    }
+    .col-lg-2 {
+        width: 16.66666667%
+    }
+    .col-lg-1 {
+        width: 8.33333333%
+    }
+    .col-lg-pull-12 {
+        right: 100%
+    }
+    .col-lg-pull-11 {
+        right: 91.66666667%
+    }
+    .col-lg-pull-10 {
+        right: 83.33333333%
+    }
+    .col-lg-pull-9 {
+        right: 75%
+    }
+    .col-lg-pull-8 {
+        right: 66.66666667%
+    }
+    .col-lg-pull-7 {
+        right: 58.33333333%
+    }
+    .col-lg-pull-6 {
+        right: 50%
+    }
+    .col-lg-pull-5 {
+        right: 41.66666667%
+    }
+    .col-lg-pull-4 {
+        right: 33.33333333%
+    }
+    .col-lg-pull-3 {
+        right: 25%
+    }
+    .col-lg-pull-2 {
+        right: 16.66666667%
+    }
+    .col-lg-pull-1 {
+        right: 8.33333333%
+    }
+    .col-lg-pull-0 {
+        right: auto
+    }
+    .col-lg-push-12 {
+        left: 100%
+    }
+    .col-lg-push-11 {
+        left: 91.66666667%
+    }
+    .col-lg-push-10 {
+        left: 83.33333333%
+    }
+    .col-lg-push-9 {
+        left: 75%
+    }
+    .col-lg-push-8 {
+        left: 66.66666667%
+    }
+    .col-lg-push-7 {
+        left: 58.33333333%
+    }
+    .col-lg-push-6 {
+        left: 50%
+    }
+    .col-lg-push-5 {
+        left: 41.66666667%
+    }
+    .col-lg-push-4 {
+        left: 33.33333333%
+    }
+    .col-lg-push-3 {
+        left: 25%
+    }
+    .col-lg-push-2 {
+        left: 16.66666667%
+    }
+    .col-lg-push-1 {
+        left: 8.33333333%
+    }
+    .col-lg-push-0 {
+        left: auto
+    }
+    .col-lg-offset-12 {
+        margin-left: 100%
+    }
+    .col-lg-offset-11 {
+        margin-left: 91.66666667%
+    }
+    .col-lg-offset-10 {
+        margin-left: 83.33333333%
+    }
+    .col-lg-offset-9 {
+        margin-left: 75%
+    }
+    .col-lg-offset-8 {
+        margin-left: 66.66666667%
+    }
+    .col-lg-offset-7 {
+        margin-left: 58.33333333%
+    }
+    .col-lg-offset-6 {
+        margin-left: 50%
+    }
+    .col-lg-offset-5 {
+        margin-left: 41.66666667%
+    }
+    .col-lg-offset-4 {
+        margin-left: 33.33333333%
+    }
+    .col-lg-offset-3 {
+        margin-left: 25%
+    }
+    .col-lg-offset-2 {
+        margin-left: 16.66666667%
+    }
+    .col-lg-offset-1 {
+        margin-left: 8.33333333%
+    }
+    .col-lg-offset-0 {
+        margin-left: 0
+    }
+}
+th {
+    text-align: left
+}
+.table {
+    width: 100%;
+    max-width: 100%;
+    margin-bottom: 20px
+}
+.table>tbody>tr>td,
+.table>tbody>tr>th,
+.table>tfoot>tr>td,
+.table>tfoot>tr>th,
+.table>thead>tr>td,
+.table>thead>tr>th {
+    padding: 8px;
+    line-height: 1.42857143;
+    vertical-align: top;
+    border-top: 1px solid #ddd
+}
+.table>thead>tr>th {
+    vertical-align: bottom;
+    border-bottom: 2px solid #ddd
+}
+.table>caption+thead>tr:first-child>td,
+.table>caption+thead>tr:first-child>th,
+.table>colgroup+thead>tr:first-child>td,
+.table>colgroup+thead>tr:first-child>th,
+.table>thead:first-child>tr:first-child>td,
+.table>thead:first-child>tr:first-child>th {
+    border-top: 0
+}
+.table>tbody+tbody {
+    border-top: 2px solid #ddd
+}
+.table .table {
+    background-color: #fff
+}
+.table-condensed>tbody>tr>td,
+.table-condensed>tbody>tr>th,
+.table-condensed>tfoot>tr>td,
+.table-condensed>tfoot>tr>th,
+.table-condensed>thead>tr>td,
+.table-condensed>thead>tr>th {
+    padding: 5px
+}
+.table-bordered,
+.table-bordered>tbody>tr>td,
+.table-bordered>tbody>tr>th,
+.table-bordered>tfoot>tr>td,
+.table-bordered>tfoot>tr>th,
+.table-bordered>thead>tr>td,
+.table-bordered>thead>tr>th {
+    border: 1px solid #ddd
+}
+.table-bordered>thead>tr>td,
+.table-bordered>thead>tr>th {
+    border-bottom-width: 2px
+}
+.table-striped>tbody>tr:nth-child(odd)>td,
+.table-striped>tbody>tr:nth-child(odd)>th {
+    background-color: #f9f9f9
+}
+.table-hover>tbody>tr:hover>td,
+.table-hover>tbody>tr:hover>th,
+.table>tbody>tr.active>td,
+.table>tbody>tr.active>th,
+.table>tbody>tr>td.active,
+.table>tbody>tr>th.active,
+.table>tfoot>tr.active>td,
+.table>tfoot>tr.active>th,
+.table>tfoot>tr>td.active,
+.table>tfoot>tr>th.active,
+.table>thead>tr.active>td,
+.table>thead>tr.active>th,
+.table>thead>tr>td.active,
+.table>thead>tr>th.active {
+    background-color: #f5f5f5
+}
+table col[class*=col-] {
+    position: static;
+    float: none;
+    display: table-column
+}
+table td[class*=col-],
+table th[class*=col-] {
+    position: static;
+    float: none;
+    display: table-cell
+}
+.table-hover>tbody>tr.active:hover>td,
+.table-hover>tbody>tr.active:hover>th,
+.table-hover>tbody>tr:hover>.active,
+.table-hover>tbody>tr>td.active:hover,
+.table-hover>tbody>tr>th.active:hover {
+    background-color: #e8e8e8
+}
+.table>tbody>tr.success>td,
+.table>tbody>tr.success>th,
+.table>tbody>tr>td.success,
+.table>tbody>tr>th.success,
+.table>tfoot>tr.success>td,
+.table>tfoot>tr.success>th,
+.table>tfoot>tr>td.success,
+.table>tfoot>tr>th.success,
+.table>thead>tr.success>td,
+.table>thead>tr.success>th,
+.table>thead>tr>td.success,
+.table>thead>tr>th.success {
+    background-color: #dff0d8
+}
+.table-hover>tbody>tr.success:hover>td,
+.table-hover>tbody>tr.success:hover>th,
+.table-hover>tbody>tr:hover>.success,
+.table-hover>tbody>tr>td.success:hover,
+.table-hover>tbody>tr>th.success:hover {
+    background-color: #d0e9c6
+}
+.table>tbody>tr.info>td,
+.table>tbody>tr.info>th,
+.table>tbody>tr>td.info,
+.table>tbody>tr>th.info,
+.table>tfoot>tr.info>td,
+.table>tfoot>tr.info>th,
+.table>tfoot>tr>td.info,
+.table>tfoot>tr>th.info,
+.table>thead>tr.info>td,
+.table>thead>tr.info>th,
+.table>thead>tr>td.info,
+.table>thead>tr>th.info {
+    background-color: #d9edf7
+}
+.table-hover>tbody>tr.info:hover>td,
+.table-hover>tbody>tr.info:hover>th,
+.table-hover>tbody>tr:hover>.info,
+.table-hover>tbody>tr>td.info:hover,
+.table-hover>tbody>tr>th.info:hover {
+    background-color: #c4e3f3
+}
+.table>tbody>tr.warning>td,
+.table>tbody>tr.warning>th,
+.table>tbody>tr>td.warning,
+.table>tbody>tr>th.warning,
+.table>tfoot>tr.warning>td,
+.table>tfoot>tr.warning>th,
+.table>tfoot>tr>td.warning,
+.table>tfoot>tr>th.warning,
+.table>thead>tr.warning>td,
+.table>thead>tr.warning>th,
+.table>thead>tr>td.warning,
+.table>thead>tr>th.warning {
+    background-color: #fcf8e3
+}
+.table-hover>tbody>tr.warning:hover>td,
+.table-hover>tbody>tr.warning:hover>th,
+.table-hover>tbody>tr:hover>.warning,
+.table-hover>tbody>tr>td.warning:hover,
+.table-hover>tbody>tr>th.warning:hover {
+    background-color: #faf2cc
+}
+.table>tbody>tr.danger>td,
+.table>tbody>tr.danger>th,
+.table>tbody>tr>td.danger,
+.table>tbody>tr>th.danger,
+.table>tfoot>tr.danger>td,
+.table>tfoot>tr.danger>th,
+.table>tfoot>tr>td.danger,
+.table>tfoot>tr>th.danger,
+.table>thead>tr.danger>td,
+.table>thead>tr.danger>th,
+.table>thead>tr>td.danger,
+.table>thead>tr>th.danger {
+    background-color: #f2dede
+}
+.table-hover>tbody>tr.danger:hover>td,
+.table-hover>tbody>tr.danger:hover>th,
+.table-hover>tbody>tr:hover>.danger,
+.table-hover>tbody>tr>td.danger:hover,
+.table-hover>tbody>tr>th.danger:hover {
+    background-color: #ebcccc
+}
+@media screen and (max-width: 767px) {
+    .table-responsive {
+        width: 100%;
+        margin-bottom: 15px;
+        overflow-y: hidden;
+        overflow-x: auto;
+        -ms-overflow-style: -ms-autohiding-scrollbar;
+        border: 1px solid #ddd;
+        -webkit-overflow-scrolling: touch
+    }
+    .table-responsive>.table {
+        margin-bottom: 0
+    }
+    .table-responsive>.table>tbody>tr>td,
+    .table-responsive>.table>tbody>tr>th,
+    .table-responsive>.table>tfoot>tr>td,
+    .table-responsive>.table>tfoot>tr>th,
+    .table-responsive>.table>thead>tr>td,
+    .table-responsive>.table>thead>tr>th {
+        white-space: nowrap
+    }
+    .table-responsive>.table-bordered {
+        border: 0
+    }
+    .table-responsive>.table-bordered>tbody>tr>td:first-child,
+    .table-responsive>.table-bordered>tbody>tr>th:first-child,
+    .table-responsive>.table-bordered>tfoot>tr>td:first-child,
+    .table-responsive>.table-bordered>tfoot>tr>th:first-child,
+    .table-responsive>.table-bordered>thead>tr>td:first-child,
+    .table-responsive>.table-bordered>thead>tr>th:first-child {
+        border-left: 0
+    }
+    .table-responsive>.table-bordered>tbody>tr>td:last-child,
+    .table-responsive>.table-bordered>tbody>tr>th:last-child,
+    .table-responsive>.table-bordered>tfoot>tr>td:last-child,
+    .table-responsive>.table-bordered>tfoot>tr>th:last-child,
+    .table-responsive>.table-bordered>thead>tr>td:last-child,
+    .table-responsive>.table-bordered>thead>tr>th:last-child {
+        border-right: 0
+    }
+    .table-responsive>.table-bordered>tbody>tr:last-child>td,
+    .table-responsive>.table-bordered>tbody>tr:last-child>th,
+    .table-responsive>.table-bordered>tfoot>tr:last-child>td,
+    .table-responsive>.table-bordered>tfoot>tr:last-child>th {
+        border-bottom: 0
+    }
+}
+fieldset,
+legend {
+    padding: 0;
+    border: 0
+}
+fieldset {
+    margin: 0;
+    min-width: 0
+}
+legend {
+    width: 100%;
+    margin-bottom: 20px;
+    font-size: 21px;
+    line-height: inherit;
+    border-bottom: 1px solid #e5e5e5
+}
+label {
+    display: inline-block;
+    max-width: 100%;
+    margin-bottom: 5px
+}
+input[type=search] {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    -webkit-appearance: none
+}
+input[type=checkbox],
+input[type=radio] {
+    margin: 4px 0 0;
+    margin-top: 1px\9;
+    line-height: normal
+}
+.form-control,
+output {
+    font-size: 14px;
+    line-height: 1.42857143;
+    color: #555;
+    display: block
+}
+input[type=file] {
+    display: block
+}
+input[type=range] {
+    display: block;
+    width: 100%
+}
+select[multiple],
+select[size] {
+    height: auto
+}
+input[type=file]:focus,
+input[type=checkbox]:focus,
+input[type=radio]:focus {
+    outline: dotted thin;
+    outline: -webkit-focus-ring-color auto 5px;
+    outline-offset: -2px
+}
+output {
+    padding-top: 7px
+}
+.form-control {
+    width: 100%;
+    height: 34px;
+    padding: 6px 9pt;
+    background-color: #fff;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+    -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+    -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
+    transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s
+}
+.form-control:focus {
+    border-color: #66afe9;
+    outline: 0;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
+}
+.form-control::-moz-placeholder {
+    color: #777;
+    opacity: 1
+}
+.form-control:-ms-input-placeholder {
+    color: #777
+}
+.form-control::-webkit-input-placeholder {
+    color: #777
+}
+.has-success .checkbox,
+.has-success .checkbox-inline,
+.has-success .control-label,
+.has-success .form-control-feedback,
+.has-success .help-block,
+.has-success .radio,
+.has-success .radio-inline {
+    color: #3c763d
+}
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+    cursor: not-allowed;
+    background-color: #eee;
+    opacity: 1
+}
+textarea.form-control {
+    height: auto
+}
+input[type=date],
+input[type=time],
+input[type=datetime-local],
+input[type=month] {
+    line-height: 34px;
+    line-height: 1.42857143\9
+}
+input[type=date].input-sm,
+input[type=time].input-sm,
+input[type=datetime-local].input-sm,
+input[type=month].input-sm {
+    line-height: 30px
+}
+input[type=date].input-lg,
+input[type=time].input-lg,
+input[type=datetime-local].input-lg,
+input[type=month].input-lg {
+    line-height: 46px
+}
+.form-group {
+    margin-bottom: 15px
+}
+.checkbox,
+.radio {
+    position: relative;
+    display: block;
+    min-height: 20px;
+    margin-top: 10px;
+    margin-bottom: 10px
+}
+.checkbox label,
+.radio label {
+    padding-left: 20px;
+    margin-bottom: 0;
+    font-weight: 400;
+    cursor: pointer
+}
+.checkbox input[type=checkbox],
+.checkbox-inline input[type=checkbox],
+.radio input[type=radio],
+.radio-inline input[type=radio] {
+    position: absolute;
+    margin-left: -20px;
+    margin-top: 4px\9
+}
+.checkbox+.checkbox,
+.radio+.radio {
+    margin-top: -5px
+}
+.checkbox-inline,
+.radio-inline {
+    display: inline-block;
+    padding-left: 20px;
+    margin-bottom: 0;
+    vertical-align: middle;
+    font-weight: 400;
+    cursor: pointer
+}
+.checkbox-inline+.checkbox-inline,
+.radio-inline+.radio-inline {
+    margin-top: 0;
+    margin-left: 10px
+}
+.checkbox-inline.disabled,
+.checkbox.disabled label,
+.radio-inline.disabled,
+.radio.disabled label,
+fieldset[disabled] .checkbox label,
+fieldset[disabled] .checkbox-inline,
+fieldset[disabled] .radio label,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] input[type=checkbox],
+fieldset[disabled] input[type=radio],
+input[type=checkbox].disabled,
+input[type=checkbox][disabled],
+input[type=radio].disabled,
+input[type=radio][disabled] {
+    cursor: not-allowed
+}
+.form-control-static {
+    padding-top: 7px;
+    padding-bottom: 7px;
+    margin-bottom: 0
+}
+.form-control-static.input-lg,
+.form-control-static.input-sm {
+    padding-left: 0;
+    padding-right: 0
+}
+.form-horizontal .form-group-sm .form-control,
+.input-sm {
+    height: 30px;
+    padding: 5px 10px;
+    font-size: 9pt;
+    line-height: 1.5;
+    border-radius: 3px
+}
+select.input-sm {
+    height: 30px;
+    line-height: 30px
+}
+select[multiple].input-sm,
+textarea.input-sm {
+    height: auto
+}
+.form-horizontal .form-group-lg .form-control,
+.input-lg {
+    height: 46px;
+    padding: 10px 1pc;
+    font-size: 18px;
+    line-height: 1.33;
+    border-radius: 6px
+}
+select.input-lg {
+    height: 46px;
+    line-height: 46px
+}
+select[multiple].input-lg,
+textarea.input-lg {
+    height: auto
+}
+.has-feedback {
+    position: relative
+}
+.has-feedback .form-control {
+    padding-right: 42.5px
+}
+.form-control-feedback {
+    position: absolute;
+    top: 25px;
+    right: 0;
+    z-index: 2;
+    display: block;
+    width: 34px;
+    height: 34px;
+    line-height: 34px;
+    text-align: center
+}
+.collapsing,
+.dropdown {
+    position: relative
+}
+.input-lg+.form-control-feedback {
+    width: 46px;
+    height: 46px;
+    line-height: 46px
+}
+.input-sm+.form-control-feedback {
+    width: 30px;
+    height: 30px;
+    line-height: 30px
+}
+.has-success .form-control {
+    border-color: #3c763d;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
+}
+.has-success .form-control:focus {
+    border-color: #2b542c;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168
+}
+.has-success .input-group-addon {
+    color: #3c763d;
+    border-color: #3c763d;
+    background-color: #dff0d8
+}
+.has-warning .checkbox,
+.has-warning .checkbox-inline,
+.has-warning .control-label,
+.has-warning .form-control-feedback,
+.has-warning .help-block,
+.has-warning .radio,
+.has-warning .radio-inline {
+    color: #8a6d3b
+}
+.has-warning .form-control {
+    border-color: #8a6d3b;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
+}
+.has-warning .form-control:focus {
+    border-color: #66512c;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b
+}
+.has-warning .input-group-addon {
+    color: #8a6d3b;
+    border-color: #8a6d3b;
+    background-color: #fcf8e3
+}
+.has-error .checkbox,
+.has-error .checkbox-inline,
+.has-error .control-label,
+.has-error .form-control-feedback,
+.has-error .help-block,
+.has-error .radio,
+.has-error .radio-inline {
+    color: #a94442
+}
+.has-error .form-control {
+    border-color: #a94442;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075)
+}
+.has-error .form-control:focus {
+    border-color: #843534;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483;
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483
+}
+.has-error .input-group-addon {
+    color: #a94442;
+    border-color: #a94442;
+    background-color: #f2dede
+}
+.has-feedback label.sr-only~.form-control-feedback {
+    top: 0
+}
+.help-block {
+    display: block;
+    margin-top: 5px;
+    margin-bottom: 10px;
+    color: #737373
+}
+@media (min-width: 768px) {
+    .form-inline .control-label,
+    .form-inline .form-group {
+        margin-bottom: 0;
+        vertical-align: middle
+    }
+    .form-inline .form-group {
+        display: inline-block
+    }
+    .form-inline .form-control {
+        display: inline-block;
+        width: auto;
+        vertical-align: middle
+    }
+    .form-inline .input-group {
+        display: inline-table;
+        vertical-align: middle
+    }
+    .form-inline .input-group .form-control,
+    .form-inline .input-group .input-group-addon,
+    .form-inline .input-group .input-group-btn {
+        width: auto
+    }
+    .form-inline .input-group>.form-control {
+        width: 100%
+    }
+    .form-inline .checkbox,
+    .form-inline .radio {
+        display: inline-block;
+        margin-top: 0;
+        margin-bottom: 0;
+        vertical-align: middle
+    }
+    .form-inline .checkbox label,
+    .form-inline .radio label {
+        padding-left: 0
+    }
+    .form-inline .checkbox input[type=checkbox],
+    .form-inline .radio input[type=radio] {
+        position: relative;
+        margin-left: 0
+    }
+    .form-inline .has-feedback .form-control-feedback {
+        top: 0
+    }
+    .form-horizontal .control-label {
+        text-align: right;
+        margin-bottom: 0;
+        padding-top: 7px
+    }
+}
+.form-horizontal .checkbox,
+.form-horizontal .checkbox-inline,
+.form-horizontal .radio,
+.form-horizontal .radio-inline {
+    margin-top: 0;
+    margin-bottom: 0;
+    padding-top: 7px
+}
+.form-horizontal .checkbox,
+.form-horizontal .radio {
+    min-height: 27px
+}
+.form-horizontal .form-group {
+    margin-left: -15px;
+    margin-right: -15px
+}
+.form-horizontal .has-feedback .form-control-feedback {
+    top: 0;
+    right: 15px
+}
+@media (min-width: 768px) {
+    .form-horizontal .form-group-lg .control-label {
+        padding-top: 14.3px
+    }
+    .form-horizontal .form-group-sm .control-label {
+        padding-top: 6px
+    }
+}
+.btn {
+    display: inline-block;
+    margin-bottom: 0;
+    font-weight: 400;
+    text-align: center;
+    vertical-align: middle;
+    cursor: pointer;
+    border: 1px solid transparent;
+    white-space: nowrap;
+    padding: 6px 9pt;
+    font-size: 14px;
+    line-height: 1.42857143;
+    border-radius: 4px;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none
+}
+.btn.active:focus,
+.btn:active:focus,
+.btn:focus {
+    outline: dotted thin;
+    outline: -webkit-focus-ring-color auto 5px;
+    outline-offset: -2px
+}
+.btn:focus,
+.btn:hover {
+    color: #333;
+    text-decoration: none
+}
+.btn.active,
+.btn:active {
+    outline: 0;
+    -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+    box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125)
+}
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+    cursor: not-allowed;
+    pointer-events: none;
+    opacity: .65;
+    filter: alpha(opacity=65);
+    -webkit-box-shadow: none;
+    box-shadow: none
+}
+.btn-default {
+    color: #333;
+    background-color: #fff;
+    border-color: #ccc
+}
+.btn-default.active,
+.btn-default:active,
+.btn-default:focus,
+.btn-default:hover,
+.open>.dropdown-toggle.btn-default {
+    color: #333;
+    background-color: #e6e6e6;
+    border-color: #adadad
+}
+.btn-default.disabled,
+.btn-default.disabled.active,
+.btn-default.disabled:active,
+.btn-default.disabled:focus,
+.btn-default.disabled:hover,
+.btn-default[disabled],
+.btn-default[disabled].active,
+.btn-default[disabled]:active,
+.btn-default[disabled]:focus,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default,
+fieldset[disabled] .btn-default.active,
+fieldset[disabled] .btn-default:active,
+fieldset[disabled] .btn-default:focus,
+fieldset[disabled] .btn-default:hover {
+    background-color: #fff;
+    border-color: #ccc
+}
+.btn-default .badge {
+    color: #fff;
+    background-color: #333
+}
+.btn-primary {
+    color: #fff;
+    background-color: #428bca;
+    border-color: #357ebd
+}
+.btn-primary.active,
+.btn-primary:active,
+.btn-primary:focus,
+.btn-primary:hover,
+.open>.dropdown-toggle.btn-primary {
+    color: #fff;
+    background-color: #3071a9;
+    border-color: #285e8e
+}
+.btn-primary.disabled,
+.btn-primary.disabled.active,
+.btn-primary.disabled:active,
+.btn-primary.disabled:focus,
+.btn-primary.disabled:hover,
+.btn-primary[disabled],
+.btn-primary[disabled].active,
+.btn-primary[disabled]:active,
+.btn-primary[disabled]:focus,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary,
+fieldset[disabled] .btn-primary.active,
+fieldset[disabled] .btn-primary:active,
+fieldset[disabled] .btn-primary:focus,
+fieldset[disabled] .btn-primary:hover {
+    background-color: #428bca;
+    border-color: #357ebd
+}
+.btn-primary .badge {
+    color: #428bca;
+    background-color: #fff
+}
+.btn-success {
+    color: #fff;
+    background-color: #5cb85c;
+    border-color: #4cae4c
+}
+.btn-success.active,
+.btn-success:active,
+.btn-success:focus,
+.btn-success:hover,
+.open>.dropdown-toggle.btn-success {
+    color: #fff;
+    background-color: #449d44;
+    border-color: #398439
+}
+.btn-success.active,
+.btn-success:active,
+.open>.dropdown-toggle.btn-success {
+    background-image: none
+}
+.btn-success.disabled,
+.btn-success.disabled.active,
+.btn-success.disabled:active,
+.btn-success.disabled:focus,
+.btn-success.disabled:hover,
+.btn-success[disabled],
+.btn-success[disabled].active,
+.btn-success[disabled]:active,
+.btn-success[disabled]:focus,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success,
+fieldset[disabled] .btn-success.active,
+fieldset[disabled] .btn-success:active,
+fieldset[disabled] .btn-success:focus,
+fieldset[disabled] .btn-success:hover {
+    background-color: #5cb85c;
+    border-color: #4cae4c
+}
+.btn-success .badge {
+    color: #5cb85c;
+    background-color: #fff
+}
+.btn-info {
+    color: #fff;
+    background-color: #5bc0de;
+    border-color: #46b8da
+}
+.btn-info.active,
+.btn-info:active,
+.btn-info:focus,
+.btn-info:hover,
+.open>.dropdown-toggle.btn-info {
+    color: #fff;
+    background-color: #31b0d5;
+    border-color: #269abc
+}
+.btn-info.disabled,
+.btn-info.disabled.active,
+.btn-info.disabled:active,
+.btn-info.disabled:focus,
+.btn-info.disabled:hover,
+.btn-info[disabled],
+.btn-info[disabled].active,
+.btn-info[disabled]:active,
+.btn-info[disabled]:focus,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info,
+fieldset[disabled] .btn-info.active,
+fieldset[disabled] .btn-info:active,
+fieldset[disabled] .btn-info:focus,
+fieldset[disabled] .btn-info:hover {
+    background-color: #5bc0de;
+    border-color: #46b8da
+}
+.btn-info .badge {
+    color: #5bc0de;
+    background-color: #fff
+}
+.btn-warning {
+    color: #fff;
+    background-color: #f0ad4e;
+    border-color: #eea236
+}
+.btn-warning.active,
+.btn-warning:active,
+.btn-warning:focus,
+.btn-warning:hover,
+.open>.dropdown-toggle.btn-warning {
+    color: #fff;
+    background-color: #ec971f;
+    border-color: #d58512
+}
+.btn-warning.disabled,
+.btn-warning.disabled.active,
+.btn-warning.disabled:active,
+.btn-warning.disabled:focus,
+.btn-warning.disabled:hover,
+.btn-warning[disabled],
+.btn-warning[disabled].active,
+.btn-warning[disabled]:active,
+.btn-warning[disabled]:focus,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning,
+fieldset[disabled] .btn-warning.active,
+fieldset[disabled] .btn-warning:active,
+fieldset[disabled] .btn-warning:focus,
+fieldset[disabled] .btn-warning:hover {
+    background-color: #f0ad4e;
+    border-color: #eea236
+}
+.btn-warning .badge {
+    color: #f0ad4e;
+    background-color: #fff
+}
+.btn-danger {
+    color: #fff;
+    background-color: #d9534f;
+    border-color: #d43f3a
+}
+.btn-danger.active,
+.btn-danger:active,
+.btn-danger:focus,
+.btn-danger:hover,
+.open>.dropdown-toggle.btn-danger {
+    color: #fff;
+    background-color: #c9302c;
+    border-color: #ac2925
+}
+.btn-danger.disabled,
+.btn-danger.disabled.active,
+.btn-danger.disabled:active,
+.btn-danger.disabled:focus,
+.btn-danger.disabled:hover,
+.btn-danger[disabled],
+.btn-danger[disabled].active,
+.btn-danger[disabled]:active,
+.btn-danger[disabled]:focus,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger,
+fieldset[disabled] .btn-danger.active,
+fieldset[disabled] .btn-danger:active,
+fieldset[disabled] .btn-danger:focus,
+fieldset[disabled] .btn-danger:hover {
+    background-color: #d9534f;
+    border-color: #d43f3a
+}
+.btn-danger .badge {
+    color: #d9534f;
+    background-color: #fff
+}
+.btn-link {
+    color: #428bca;
+    font-weight: 400;
+    cursor: pointer;
+    border-radius: 0
+}
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+    background-color: transparent;
+    -webkit-box-shadow: none;
+    box-shadow: none
+}
+.btn-link,
+.btn-link:active,
+.btn-link:focus,
+.btn-link:hover {
+    border-color: transparent
+}
+.btn-link:focus,
+.btn-link:hover {
+    color: #2a6496;
+    text-decoration: underline;
+    background-color: transparent
+}
+.btn-link[disabled]:focus,
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:focus,
+fieldset[disabled] .btn-link:hover {
+    color: #777;
+    text-decoration: none
+}
+.btn-group-lg>.btn,
+.btn-lg {
+    padding: 10px 1pc;
+    font-size: 18px;
+    line-height: 1.33;
+    border-radius: 6px
+}
+.btn-group-sm>.btn,
+.btn-sm {
+    padding: 5px 10px;
+    font-size: 9pt;
+    line-height: 1.5;
+    border-radius: 3px
+}
+.btn-group-xs>.btn,
+.btn-xs {
+    padding: 1px 5px;
+    font-size: 9pt;
+    line-height: 1.5;
+    border-radius: 3px
+}
+.btn-block {
+    display: block;
+    width: 100%
+}
+.btn-block+.btn-block {
+    margin-top: 5px
+}
+input[type=button].btn-block,
+input[type=reset].btn-block,
+input[type=submit].btn-block {
+    width: 100%
+}
+.fade {
+    opacity: 0;
+    -webkit-transition: opacity .15s linear;
+    -o-transition: opacity .15s linear;
+    transition: opacity .15s linear
+}
+.fade.in {
+    opacity: 1
+}
+.collapse {
+    display: none
+}
+.collapse.in {
+    display: block
+}
+tr.collapse.in {
+    display: table-row
+}
+tbody.collapse.in {
+    display: table-row-group
+}
+.collapsing {
+    height: 0;
+    overflow: hidden;
+    -webkit-transition: height .35s ease;
+    -o-transition: height .35s ease;
+    transition: height .35s ease
+}
+.caret {
+    display: inline-block;
+    width: 0;
+    height: 0;
+    margin-left: 2px;
+    vertical-align: middle;
+    border-top: 4px solid;
+    border-right: 4px solid transparent;
+    border-left: 4px solid transparent
+}
+.dropdown-toggle:focus {
+    outline: 0
+}
+.dropdown-menu {
+    position: absolute;
+    top: 100%;
+    left: 0;
+    z-index: 1000;
+    display: none;
+    min-width: 10pc;
+    padding: 5px 0;
+    margin: 2px 0 0;
+    list-style: none;
+    font-size: 14px;
+    text-align: left;
+    background-color: #fff;
+    border: 1px solid #ccc;
+    border: 1px solid rgba(0, 0, 0, .15);
+    border-radius: 4px;
+    -webkit-box-shadow: 0 6px 9pt rgba(0, 0, 0, .175);
+    box-shadow: 0 6px 9pt rgba(0, 0, 0, .175);
+    background-clip: padding-box
+}
+.dropdown-menu-right,
+.dropdown-menu.pull-right {
+    left: auto;
+    right: 0
+}
+.dropdown-header,
+.dropdown-menu>li>a {
+    display: block;
+    padding: 3px 20px;
+    line-height: 1.42857143;
+    white-space: nowrap
+}
+.btn-group-vertical>.btn:not(:first-child):not(:last-child),
+.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,
+.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+    border-radius: 0
+}
+.dropdown-menu .divider {
+    height: 1px;
+    margin: 9px 0;
+    overflow: hidden;
+    background-color: #e5e5e5
+}
+.dropdown-menu>li>a {
+    clear: both;
+    font-weight: 400;
+    color: #333
+}
+.dropdown-menu>li>a:focus,
+.dropdown-menu>li>a:hover {
+    text-decoration: none;
+    color: #262626;
+    background-color: #f5f5f5
+}
+.dropdown-menu>.active>a,
+.dropdown-menu>.active>a:focus,
+.dropdown-menu>.active>a:hover {
+    color: #fff;
+    text-decoration: none;
+    outline: 0;
+    background-color: #428bca
+}
+.dropdown-menu>.disabled>a,
+.dropdown-menu>.disabled>a:focus,
+.dropdown-menu>.disabled>a:hover {
+    color: #777
+}
+.dropdown-menu>.disabled>a:focus,
+.dropdown-menu>.disabled>a:hover {
+    text-decoration: none;
+    background-color: transparent;
+    filter: progid: DXImageTransform.Microsoft.gradient(enabled=false);
+    cursor: not-allowed
+}
+.open>.dropdown-menu {
+    display: block
+}
+.open>a {
+    outline: 0
+}
+.dropdown-menu-left {
+    left: 0;
+    right: auto
+}
+.dropdown-header {
+    font-size: 9pt;
+    color: #777
+}
+.dropdown-backdrop {
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    top: 0;
+    z-index: 990
+}
+.nav-justified>.dropdown .dropdown-menu,
+.nav-tabs.nav-justified>.dropdown .dropdown-menu {
+    top: auto;
+    left: auto
+}
+.pull-right>.dropdown-menu {
+    right: 0;
+    left: auto
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+    border-top: 0;
+    border-bottom: 4px solid;
+    content: ""
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+    top: auto;
+    bottom: 100%;
+    margin-bottom: 1px
+}
+@media (min-width: 768px) {
+    .navbar-right .dropdown-menu {
+        left: auto;
+        right: 0
+    }
+    .navbar-right .dropdown-menu-left {
+        left: 0;
+        right: auto
+    }
+}
+.btn-group,
+.btn-group-vertical {
+    position: relative;
+    display: inline-block;
+    vertical-align: middle
+}
+.btn-group-vertical>.btn,
+.btn-group>.btn {
+    position: relative;
+    float: left
+}
+.btn-group-vertical>.btn.active,
+.btn-group-vertical>.btn:active,
+.btn-group-vertical>.btn:focus,
+.btn-group-vertical>.btn:hover,
+.btn-group>.btn.active,
+.btn-group>.btn:active,
+.btn-group>.btn:focus,
+.btn-group>.btn:hover {
+    z-index: 2
+}
+.btn-group-vertical>.btn:focus,
+.btn-group>.btn:focus {
+    outline: 0
+}
+.btn-group .btn+.btn,
+.btn-group .btn+.btn-group,
+.btn-group .btn-group+.btn,
+.btn-group .btn-group+.btn-group {
+    margin-left: -1px
+}
+.btn-toolbar {
+    margin-left: -5px
+}
+.btn-toolbar>.btn,
+.btn-toolbar>.btn-group,
+.btn-toolbar>.input-group {
+    margin-left: 5px
+}
+.btn .caret,
+.btn-group>.btn:first-child {
+    margin-left: 0
+}
+.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle) {
+    border-bottom-right-radius: 0;
+    border-top-right-radius: 0
+}
+.btn-group>.btn:last-child:not(:first-child),
+.btn-group>.dropdown-toggle:not(:first-child) {
+    border-bottom-left-radius: 0;
+    border-top-left-radius: 0
+}
+.btn-group>.btn-group:first-child>.btn:last-child,
+.btn-group>.btn-group:first-child>.dropdown-toggle {
+    border-bottom-right-radius: 0;
+    border-top-right-radius: 0
+}
+.btn-group>.btn-group:last-child>.btn:first-child {
+    border-bottom-left-radius: 0;
+    border-top-left-radius: 0
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+    outline: 0
+}
+.btn-group>.btn+.dropdown-toggle {
+    padding-left: 8px;
+    padding-right: 8px
+}
+.btn-group>.btn-lg+.dropdown-toggle {
+    padding-left: 9pt;
+    padding-right: 9pt
+}
+.btn-group.open .dropdown-toggle {
+    -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
+    box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125)
+}
+.btn-group.open .dropdown-toggle.btn-link {
+    -webkit-box-shadow: none;
+    box-shadow: none
+}
+.btn-lg .caret {
+    border-width: 5px 5px 0
+}
+.dropup .btn-lg .caret {
+    border-width: 0 5px 5px
+}
+.btn-group-vertical>.btn,
+.btn-group-vertical>.btn-group,
+.btn-group-vertical>.btn-group>.btn {
+    display: block;
+    float: none;
+    width: 100%;
+    max-width: 100%
+}
+.btn-group-vertical>.btn-group>.btn {
+    float: none
+}
+.btn-group-vertical>.btn+.btn,
+.btn-group-vertical>.btn+.btn-group,
+.btn-group-vertical>.btn-group+.btn,
+.btn-group-vertical>.btn-group+.btn-group {
+    margin-top: -1px;
+    margin-left: 0
+}
+.input-group-btn:last-child>.btn,
+.input-group-btn:last-child>.btn-group,
+.input-group-btn>.btn+.btn {
+    margin-left: -1px
+}
+.btn-group-vertical>.btn:first-child:not(:last-child) {
+    border-top-right-radius: 4px;
+    border-bottom-right-radius: 0;
+    border-bottom-left-radius: 0
+}
+.btn-group-vertical>.btn:last-child:not(:first-child) {
+    border-bottom-left-radius: 4px;
+    border-top-right-radius: 0;
+    border-top-left-radius: 0
+}
+.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn {
+    border-radius: 0
+}
+.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,
+.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle {
+    border-bottom-right-radius: 0;
+    border-bottom-left-radius: 0
+}
+.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child {
+    border-top-right-radius: 0;
+    border-top-left-radius: 0
+}
+.btn-group-justified {
+    display: table;
+    width: 100%;
+    table-layout: fixed;
+    border-collapse: separate
+}
+.btn-group-justified>.btn,
+.btn-group-justified>.btn-group {
+    float: none;
+    display: table-cell;
+    width: 1%
+}
+.btn-group-justified>.btn-group .btn {
+    width: 100%
+}
+.btn-group-justified>.btn-group .dropdown-menu {
+    left: auto
+}
+[data-toggle=buttons]>.btn>input[type=checkbox],
+[data-toggle=buttons]>.btn>input[type=radio] {
+    position: absolute;
+    z-index: -1;
+    opacity: 0;
+    filter: alpha(opacity=0)
+}
+.input-group {
+    position: relative;
+    display: table;
+    border-collapse: separate
+}
+.input-group[class*=col-] {
+    float: none;
+    padding-left: 0;
+    padding-right: 0
+}
+.input-group .form-control {
+    position: relative;
+    z-index: 2;
+    float: left;
+    width: 100%;
+    margin-bottom: 0
+}
+.input-group-lg>.form-control,
+.input-group-lg>.input-group-addon,
+.input-group-lg>.input-group-btn>.btn {
+    height: 46px;
+    padding: 10px 1pc;
+    font-size: 18px;
+    line-height: 1.33;
+    border-radius: 6px
+}
+select.input-group-lg>.form-control,
+select.input-group-lg>.input-group-addon,
+select.input-group-lg>.input-group-btn>.btn {
+    height: 46px;
+    line-height: 46px
+}
+select[multiple].input-group-lg>.form-control,
+select[multiple].input-group-lg>.input-group-addon,
+select[multiple].input-group-lg>.input-group-btn>.btn,
+textarea.input-group-lg>.form-control,
+textarea.input-group-lg>.input-group-addon,
+textarea.input-group-lg>.input-group-btn>.btn {
+    height: auto
+}
+.input-group-sm>.form-control,
+.input-group-sm>.input-group-addon,
+.input-group-sm>.input-group-btn>.btn {
+    height: 30px;
+    padding: 5px 10px;
+    font-size: 9pt;
+    line-height: 1.5;
+    border-radius: 3px
+}
+select.input-group-sm>.form-control,
+select.input-group-sm>.input-group-addon,
+select.input-group-sm>.input-group-btn>.btn {
+    height: 30px;
+    line-height: 30px
+}
+select[multiple].input-group-sm>.form-control,
+select[multiple].input-group-sm>.input-group-addon,
+select[multiple].input-group-sm>.input-group-btn>.btn,
+textarea.input-group-sm>.form-control,
+textarea.input-group-sm>.input-group-addon,
+textarea.input-group-sm>.input-group-btn>.btn {
+    height: auto
+}
+.input-group .form-control,
+.input-group-addon,
+.input-group-btn {
+    display: table-cell
+}
+.nav>li,
+.nav>li>a {
+    display: block;
+    position: relative
+}
+.input-group .form-control:not(:first-child):not(:last-child),
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child) {
+    border-radius: 0
+}
+.input-group-addon,
+.input-group-btn {
+    width: 1%;
+    white-space: nowrap;
+    vertical-align: middle
+}
+.input-group-addon {
+    padding: 6px 9pt;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 1;
+    color: #555;
+    text-align: center;
+    background-color: #eee;
+    border: 1px solid #ccc;
+    border-radius: 4px
+}
+.input-group-addon.input-sm {
+    padding: 5px 10px;
+    font-size: 9pt;
+    border-radius: 3px
+}
+.input-group-addon.input-lg {
+    padding: 10px 1pc;
+    font-size: 18px;
+    border-radius: 6px
+}
+.input-group-addon input[type=checkbox],
+.input-group-addon input[type=radio] {
+    margin-top: 0
+}
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child>.btn,
+.input-group-btn:first-child>.btn-group>.btn,
+.input-group-btn:first-child>.dropdown-toggle,
+.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,
+.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle) {
+    border-bottom-right-radius: 0;
+    border-top-right-radius: 0
+}
+.input-group-addon:first-child {
+    border-right: 0
+}
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,
+.input-group-btn:first-child>.btn:not(:first-child),
+.input-group-btn:last-child>.btn,
+.input-group-btn:last-child>.btn-group>.btn,
+.input-group-btn:last-child>.dropdown-toggle {
+    border-bottom-left-radius: 0;
+    border-top-left-radius: 0
+}
+.input-group-addon:last-child {
+    border-left: 0
+}
+.input-group-btn {
+    position: relative;
+    font-size: 0;
+    white-space: nowrap
+}
+.input-group-btn>.btn {
+    position: relative
+}
+.input-group-btn>.btn:active,
+.input-group-btn>.btn:focus,
+.input-group-btn>.btn:hover {
+    z-index: 2
+}
+.input-group-btn:first-child>.btn,
+.input-group-btn:first-child>.btn-group {
+    margin-right: -1px
+}
+.nav {
+    margin-bottom: 0;
+    padding-left: 0;
+    list-style: none
+}
+.nav>li>a {
+    padding: 10px 15px
+}
+.nav>li>a:focus,
+.nav>li>a:hover {
+    text-decoration: none;
+    background-color: #eee
+}
+.nav>li.disabled>a {
+    color: #777
+}
+.nav>li.disabled>a:focus,
+.nav>li.disabled>a:hover {
+    color: #777;
+    text-decoration: none;
+    background-color: transparent;
+    cursor: not-allowed
+}
+.nav .open>a,
+.nav .open>a:focus,
+.nav .open>a:hover {
+    background-color: #eee;
+    border-color: #428bca
+}
+.nav .nav-divider {
+    height: 1px;
+    margin: 9px 0;
+    overflow: hidden;
+    background-color: #e5e5e5
+}
+.nav>li>a>img {
+    max-width: none
+}
+.nav-tabs {
+    border-bottom: 1px solid #ddd
+}
+.nav-tabs>li {
+    float: left;
+    margin-bottom: -1px
+}
+.nav-tabs>li>a {
+    margin-right: 2px;
+    line-height: 1.42857143;
+    border: 1px solid transparent;
+    border-radius: 4px 4px 0 0
+}
+.nav-tabs>li>a:hover {
+    border-color: #eee #eee #ddd
+}
+.nav-tabs>li.active>a,
+.nav-tabs>li.active>a:focus,
+.nav-tabs>li.active>a:hover {
+    color: #555;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    border-bottom-color: transparent;
+    cursor: default
+}
+.nav-tabs.nav-justified {
+    width: 100%;
+    border-bottom: 0
+}
+.nav-tabs.nav-justified>li {
+    float: none
+}
+.nav-tabs.nav-justified>li>a {
+    text-align: center;
+    margin-bottom: 5px;
+    margin-right: 0;
+    border-radius: 4px
+}
+.nav-tabs.nav-justified>.active>a,
+.nav-tabs.nav-justified>.active>a:focus,
+.nav-tabs.nav-justified>.active>a:hover {
+    border: 1px solid #ddd
+}
+@media (min-width: 768px) {
+    .nav-tabs.nav-justified>li {
+        display: table-cell;
+        width: 1%
+    }
+    .nav-tabs.nav-justified>li>a {
+        margin-bottom: 0;
+        border-bottom: 1px solid #ddd;
+        border-radius: 4px 4px 0 0
+    }
+    .nav-tabs.nav-justified>.active>a,
+    .nav-tabs.nav-justified>.active>a:focus,
+    .nav-tabs.nav-justified>.active>a:hover {
+        border-bottom-color: #fff
+    }
+}
+.nav-pills>li {
+    float: left
+}
+.nav-justified>li,
+.nav-stacked>li {
+    float: none
+}
+.nav-pills>li>a {
+    border-radius: 4px
+}
+.nav-pills>li+li {
+    margin-left: 2px
+}
+.nav-pills>li.active>a,
+.nav-pills>li.active>a:focus,
+.nav-pills>li.active>a:hover {
+    color: #fff;
+    background-color: #428bca
+}
+.nav-stacked>li+li {
+    margin-top: 2px;
+    margin-left: 0
+}
+.nav-justified {
+    width: 100%
+}
+.nav-justified>li>a {
+    text-align: center;
+    margin-bottom: 5px
+}
+.nav-tabs-justified {
+    border-bottom: 0
+}
+.nav-tabs-justified>li>a {
+    margin-right: 0;
+    border-radius: 4px
+}
+.nav-tabs-justified>.active>a,
+.nav-tabs-justified>.active>a:focus,
+.nav-tabs-justified>.active>a:hover {
+    border: 1px solid #ddd
+}
+@media (min-width: 768px) {
+    .nav-justified>li {
+        display: table-cell;
+        width: 1%
+    }
+    .nav-justified>li>a {
+        margin-bottom: 0
+    }
+    .nav-tabs-justified>li>a {
+        border-bottom: 1px solid #ddd;
+        border-radius: 4px 4px 0 0
+    }
+    .nav-tabs-justified>.active>a,
+    .nav-tabs-justified>.active>a:focus,
+    .nav-tabs-justified>.active>a:hover {
+        border-bottom-color: #fff
+    }
+}
+.tab-content>.tab-pane {
+    display: none
+}
+.tab-content>.active {
+    display: block
+}
+.nav-tabs .dropdown-menu {
+    margin-top: -1px;
+    border-top-right-radius: 0;
+    border-top-left-radius: 0
+}
+.navbar {
+    position: relative;
+    min-height: 50px;
+    margin-bottom: 20px;
+    border: 1px solid transparent
+}
+.navbar-collapse {
+    overflow-x: visible;
+    padding-right: 15px;
+    padding-left: 15px;
+    border-top: 1px solid transparent;
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1);
+    -webkit-overflow-scrolling: touch
+}
+.navbar-collapse.in {
+    overflow-y: auto
+}
+@media (min-width: 768px) {
+    .navbar {
+        border-radius: 4px
+    }
+    .navbar-header {
+        float: left
+    }
+    .navbar-collapse {
+        width: auto;
+        border-top: 0;
+        box-shadow: none
+    }
+    .navbar-collapse.collapse {
+        display: block!important;
+        height: auto!important;
+        padding-bottom: 0;
+        overflow: visible!important
+    }
+    .navbar-collapse.in {
+        overflow-y: visible
+    }
+    .navbar-fixed-bottom .navbar-collapse,
+    .navbar-fixed-top .navbar-collapse,
+    .navbar-static-top .navbar-collapse {
+        padding-left: 0;
+        padding-right: 0
+    }
+}
+.carousel-inner,
+.embed-responsive,
+.modal,
+.modal-open,
+.progress {
+    overflow: hidden
+}
+@media (max-width: 480px) and (orientation: landscape) {
+    .navbar-fixed-bottom .navbar-collapse,
+    .navbar-fixed-top .navbar-collapse {
+        max-height: 200px
+    }
+}
+.container-fluid>.navbar-collapse,
+.container-fluid>.navbar-header,
+.container>.navbar-collapse,
+.container>.navbar-header {
+    margin-right: -15px;
+    margin-left: -15px
+}
+.navbar-static-top {
+    z-index: 1000;
+    border-width: 0 0 1px
+}
+.navbar-fixed-bottom,
+.navbar-fixed-top {
+    position: fixed;
+    right: 0;
+    left: 0;
+    z-index: 1030;
+    -webkit-transform: translate3d(0, 0, 0);
+    transform: translate3d(0, 0, 0)
+}
+.navbar-fixed-top {
+    top: 0;
+    border-width: 0 0 1px
+}
+.navbar-fixed-bottom {
+    bottom: 0;
+    margin-bottom: 0;
+    border-width: 1px 0 0
+}
+.navbar-brand {
+    float: left;
+    padding: 15px;
+    font-size: 18px;
+    line-height: 20px;
+    height: 50px
+}
+.navbar-brand:focus,
+.navbar-brand:hover {
+    text-decoration: none
+}
+@media (min-width: 768px) {
+    .container-fluid>.navbar-collapse,
+    .container-fluid>.navbar-header,
+    .container>.navbar-collapse,
+    .container>.navbar-header {
+        margin-right: 0;
+        margin-left: 0
+    }
+    .navbar-fixed-bottom,
+    .navbar-fixed-top,
+    .navbar-static-top {
+        border-radius: 0
+    }
+    .navbar>.container .navbar-brand,
+    .navbar>.container-fluid .navbar-brand {
+        margin-left: -15px
+    }
+}
+.navbar-toggle {
+    position: relative;
+    float: right;
+    margin-right: 15px;
+    padding: 9px 10px;
+    margin-top: 8px;
+    margin-bottom: 8px;
+    background-color: transparent;
+    border: 1px solid transparent;
+    border-radius: 4px
+}
+.navbar-toggle:focus {
+    outline: 0
+}
+.navbar-toggle .icon-bar {
+    display: block;
+    width: 22px;
+    height: 2px;
+    border-radius: 1px
+}
+.navbar-toggle .icon-bar+.icon-bar {
+    margin-top: 4px
+}
+.navbar-nav {
+    margin: 7.5px -15px
+}
+.navbar-nav>li>a {
+    padding-top: 10px;
+    padding-bottom: 10px;
+    line-height: 20px
+}
+@media (max-width: 767px) {
+    .navbar-nav .open .dropdown-menu {
+        position: static;
+        float: none;
+        width: auto;
+        margin-top: 0;
+        background-color: transparent;
+        border: 0;
+        box-shadow: none
+    }
+    .navbar-nav .open .dropdown-menu .dropdown-header,
+    .navbar-nav .open .dropdown-menu>li>a {
+        padding: 5px 15px 5px 25px
+    }
+    .navbar-nav .open .dropdown-menu>li>a {
+        line-height: 20px
+    }
+    .navbar-nav .open .dropdown-menu>li>a:focus,
+    .navbar-nav .open .dropdown-menu>li>a:hover {
+        background-image: none
+    }
+}
+.progress-bar-striped,
+.progress-striped .progress-bar,
+.progress-striped .progress-bar-success {
+    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+@media (min-width: 768px) {
+    .navbar-toggle {
+        display: none
+    }
+    .navbar-nav {
+        float: left;
+        margin: 0
+    }
+    .navbar-nav>li {
+        float: left
+    }
+    .navbar-nav>li>a {
+        padding-top: 15px;
+        padding-bottom: 15px
+    }
+    .navbar-nav.navbar-right:last-child {
+        margin-right: -15px
+    }
+    .navbar-left {
+        float: left!important
+    }
+    .navbar-right {
+        float: right!important
+    }
+}
+.navbar-form {
+    padding: 10px 15px;
+    border-top: 1px solid transparent;
+    border-bottom: 1px solid transparent;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
+    margin: 8px -15px
+}
+@media (min-width: 768px) {
+    .navbar-form .control-label,
+    .navbar-form .form-group {
+        margin-bottom: 0;
+        vertical-align: middle
+    }
+    .navbar-form .form-group {
+        display: inline-block
+    }
+    .navbar-form .form-control {
+        display: inline-block;
+        width: auto;
+        vertical-align: middle
+    }
+    .navbar-form .input-group {
+        display: inline-table;
+        vertical-align: middle
+    }
+    .navbar-form .input-group .form-control,
+    .navbar-form .input-group .input-group-addon,
+    .navbar-form .input-group .input-group-btn {
+        width: auto
+    }
+    .navbar-form .input-group>.form-control {
+        width: 100%
+    }
+    .navbar-form .checkbox,
+    .navbar-form .radio {
+        display: inline-block;
+        margin-top: 0;
+        margin-bottom: 0;
+        vertical-align: middle
+    }
+    .navbar-form .checkbox label,
+    .navbar-form .radio label {
+        padding-left: 0
+    }
+    .navbar-form .checkbox input[type=checkbox],
+    .navbar-form .radio input[type=radio] {
+        position: relative;
+        margin-left: 0
+    }
+    .navbar-form .has-feedback .form-control-feedback {
+        top: 0
+    }
+    .navbar-form {
+        width: auto;
+        border: 0;
+        margin-left: 0;
+        margin-right: 0;
+        padding-top: 0;
+        padding-bottom: 0;
+        -webkit-box-shadow: none;
+        box-shadow: none
+    }
+    .navbar-form.navbar-right:last-child {
+        margin-right: -15px
+    }
+}
+.breadcrumb>li,
+.pagination {
+    display: inline-block
+}
+@media (max-width: 767px) {
+    .navbar-form .form-group {
+        margin-bottom: 5px
+    }
+}
+.navbar-nav>li>.dropdown-menu {
+    margin-top: 0;
+    border-top-right-radius: 0;
+    border-top-left-radius: 0
+}
+.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu {
+    border-bottom-right-radius: 0;
+    border-bottom-left-radius: 0
+}
+.navbar-btn {
+    margin-top: 8px;
+    margin-bottom: 8px
+}
+.navbar-btn.btn-sm {
+    margin-top: 10px;
+    margin-bottom: 10px
+}
+.navbar-btn.btn-xs {
+    margin-top: 14px;
+    margin-bottom: 14px
+}
+.navbar-text {
+    margin-top: 15px;
+    margin-bottom: 15px
+}
+@media (min-width: 768px) {
+    .navbar-text {
+        float: left;
+        margin-left: 15px;
+        margin-right: 15px
+    }
+    .navbar-text.navbar-right:last-child {
+        margin-right: 0
+    }
+}
+.navbar-default {
+    background-color: #f8f8f8;
+    border-color: #e7e7e7
+}
+.navbar-default .navbar-brand {
+    color: #777
+}
+.navbar-default .navbar-brand:focus,
+.navbar-default .navbar-brand:hover {
+    color: #5e5e5e;
+    background-color: transparent
+}
+.navbar-default .navbar-nav>li>a,
+.navbar-default .navbar-text {
+    color: #777
+}
+.navbar-default .navbar-nav>li>a:focus,
+.navbar-default .navbar-nav>li>a:hover {
+    color: #333;
+    background-color: transparent
+}
+.navbar-default .navbar-nav>.active>a,
+.navbar-default .navbar-nav>.active>a:focus,
+.navbar-default .navbar-nav>.active>a:hover {
+    color: #555;
+    background-color: #e7e7e7
+}
+.navbar-default .navbar-nav>.disabled>a,
+.navbar-default .navbar-nav>.disabled>a:focus,
+.navbar-default .navbar-nav>.disabled>a:hover {
+    color: #ccc;
+    background-color: transparent
+}
+.navbar-default .navbar-toggle {
+    border-color: #ddd
+}
+.navbar-default .navbar-toggle:focus,
+.navbar-default .navbar-toggle:hover {
+    background-color: #ddd
+}
+.navbar-default .navbar-toggle .icon-bar {
+    background-color: #888
+}
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+    border-color: #e7e7e7
+}
+.navbar-default .navbar-nav>.open>a,
+.navbar-default .navbar-nav>.open>a:focus,
+.navbar-default .navbar-nav>.open>a:hover {
+    background-color: #e7e7e7;
+    color: #555
+}
+@media (max-width: 767px) {
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a {
+        color: #777
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,
+    .navbar-default .navbar-nav .open .dropdown-menu>li>a:hover {
+        color: #333;
+        background-color: transparent
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>.active>a,
+    .navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,
+    .navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover {
+        color: #555;
+        background-color: #e7e7e7
+    }
+    .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,
+    .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,
+    .navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover {
+        color: #ccc;
+        background-color: transparent
+    }
+}
+.navbar-default .navbar-link {
+    color: #777
+}
+.navbar-default .navbar-link:hover {
+    color: #333
+}
+.navbar-default .btn-link {
+    color: #777
+}
+.navbar-default .btn-link:focus,
+.navbar-default .btn-link:hover {
+    color: #333
+}
+.navbar-default .btn-link[disabled]:focus,
+.navbar-default .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-default .btn-link:focus,
+fieldset[disabled] .navbar-default .btn-link:hover {
+    color: #ccc
+}
+.navbar-inverse {
+    background-color: #222;
+    border-color: #080808
+}
+.navbar-inverse .navbar-brand {
+    color: #777
+}
+.navbar-inverse .navbar-brand:focus,
+.navbar-inverse .navbar-brand:hover {
+    color: #fff;
+    background-color: transparent
+}
+.navbar-inverse .navbar-nav>li>a,
+.navbar-inverse .navbar-text {
+    color: #777
+}
+.navbar-inverse .navbar-nav>li>a:focus,
+.navbar-inverse .navbar-nav>li>a:hover {
+    color: #fff;
+    background-color: transparent
+}
+.navbar-inverse .navbar-nav>.active>a,
+.navbar-inverse .navbar-nav>.active>a:focus,
+.navbar-inverse .navbar-nav>.active>a:hover {
+    color: #fff;
+    background-color: #080808
+}
+.navbar-inverse .navbar-nav>.disabled>a,
+.navbar-inverse .navbar-nav>.disabled>a:focus,
+.navbar-inverse .navbar-nav>.disabled>a:hover {
+    color: #444;
+    background-color: transparent
+}
+.navbar-inverse .navbar-toggle {
+    border-color: #333
+}
+.navbar-inverse .navbar-toggle:focus,
+.navbar-inverse .navbar-toggle:hover {
+    background-color: #333
+}
+.navbar-inverse .navbar-toggle .icon-bar {
+    background-color: #fff
+}
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+    border-color: #101010
+}
+.navbar-inverse .navbar-nav>.open>a,
+.navbar-inverse .navbar-nav>.open>a:focus,
+.navbar-inverse .navbar-nav>.open>a:hover {
+    background-color: #080808;
+    color: #fff
+}
+@media (max-width: 767px) {
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header {
+        border-color: #080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu .divider {
+        background-color: #080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a {
+        color: #777
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,
+    .navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover {
+        color: #fff;
+        background-color: transparent
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover {
+        color: #fff;
+        background-color: #080808
+    }
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,
+    .navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover {
+        color: #444;
+        background-color: transparent
+    }
+}
+.navbar-inverse .navbar-link {
+    color: #777
+}
+.navbar-inverse .navbar-link:hover {
+    color: #fff
+}
+.navbar-inverse .btn-link {
+    color: #777
+}
+.navbar-inverse .btn-link:focus,
+.navbar-inverse .btn-link:hover {
+    color: #fff
+}
+.navbar-inverse .btn-link[disabled]:focus,
+.navbar-inverse .btn-link[disabled]:hover,
+fieldset[disabled] .navbar-inverse .btn-link:focus,
+fieldset[disabled] .navbar-inverse .btn-link:hover {
+    color: #444
+}
+.breadcrumb {
+    padding: 8px 15px;
+    margin-bottom: 20px;
+    list-style: none;
+    background-color: #f5f5f5;
+    border-radius: 4px
+}
+.breadcrumb>li+li:before {
+    content: "/\00a0";
+    padding: 0 5px;
+    color: #ccc
+}
+.breadcrumb>.active {
+    color: #777
+}
+.pagination {
+    padding-left: 0;
+    margin: 20px 0;
+    border-radius: 4px
+}
+.pager li,
+.pagination>li {
+    display: inline
+}
+.pagination>li>a,
+.pagination>li>span {
+    position: relative;
+    float: left;
+    padding: 6px 9pt;
+    line-height: 1.0;
+    text-decoration: none;
+    color: #428bca;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    margin-left: -1px
+}
+.badge,
+.label {
+    font-weight: 700;
+    line-height: 1;
+    vertical-align: baseline;
+    white-space: nowrap;
+    text-align: center
+}
+.pagination>li:first-child>a,
+.pagination>li:first-child>span {
+    margin-left: 0;
+    border-bottom-left-radius: 4px;
+    border-top-left-radius: 4px
+}
+.pagination>li:last-child>a,
+.pagination>li:last-child>span {
+    border-bottom-right-radius: 4px;
+    border-top-right-radius: 4px
+}
+.pagination>li>a:focus,
+.pagination>li>a:hover,
+.pagination>li>span:focus,
+.pagination>li>span:hover {
+    color: #2a6496;
+    background-color: #eee;
+    border-color: #ddd
+}
+.pagination>.active>a,
+.pagination>.active>a:focus,
+.pagination>.active>a:hover,
+.pagination>.active>span,
+.pagination>.active>span:focus,
+.pagination>.active>span:hover {
+    z-index: 2;
+    color: #fff;
+    background-color: #428bca;
+    border-color: #428bca;
+    cursor: default
+}
+.pagination>.disabled>a,
+.pagination>.disabled>a:focus,
+.pagination>.disabled>a:hover,
+.pagination>.disabled>span,
+.pagination>.disabled>span:focus,
+.pagination>.disabled>span:hover {
+    color: #777;
+    background-color: #fff;
+    border-color: #ddd;
+    cursor: not-allowed
+}
+.pagination-lg>li>a,
+.pagination-lg>li>span {
+    padding: 10px 1pc;
+    font-size: 18px
+}
+.pagination-lg>li:first-child>a,
+.pagination-lg>li:first-child>span {
+    border-bottom-left-radius: 6px;
+    border-top-left-radius: 6px
+}
+.pagination-lg>li:last-child>a,
+.pagination-lg>li:last-child>span {
+    border-bottom-right-radius: 6px;
+    border-top-right-radius: 6px
+}
+.pagination-sm>li>a,
+.pagination-sm>li>span {
+    padding: 5px 10px;
+    font-size: 9pt
+}
+.pagination-sm>li:first-child>a,
+.pagination-sm>li:first-child>span {
+    border-bottom-left-radius: 3px;
+    border-top-left-radius: 3px
+}
+.pagination-sm>li:last-child>a,
+.pagination-sm>li:last-child>span {
+    border-bottom-right-radius: 3px;
+    border-top-right-radius: 3px
+}
+.pager {
+    padding-left: 0;
+    margin: 20px 0;
+    list-style: none;
+    text-align: center
+}
+.pager li>a,
+.pager li>span {
+    display: inline-block;
+    padding: 5px 14px;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    border-radius: 15px
+}
+.pager li>a:focus,
+.pager li>a:hover {
+    text-decoration: none;
+    background-color: #eee
+}
+.pager .next>a,
+.pager .next>span {
+    float: right
+}
+.pager .previous>a,
+.pager .previous>span {
+    float: left
+}
+.pager .disabled>a,
+.pager .disabled>a:focus,
+.pager .disabled>a:hover,
+.pager .disabled>span {
+    color: #777;
+    background-color: #fff;
+    cursor: not-allowed
+}
+.label {
+    display: inline;
+    padding: .2em .6em .3em;
+    font-size: 75%;
+    color: #fff;
+    border-radius: .25em
+}
+a.label:focus,
+a.label:hover {
+    color: #fff;
+    text-decoration: none;
+    cursor: pointer
+}
+.label:empty {
+    display: none
+}
+.btn .label {
+    position: relative;
+    top: -1px
+}
+.label-default {
+    background-color: #777
+}
+.label-default[href]:focus,
+.label-default[href]:hover {
+    background-color: #5e5e5e
+}
+.label-primary {
+    background-color: #428bca
+}
+.label-primary[href]:focus,
+.label-primary[href]:hover {
+    background-color: #3071a9
+}
+.label-success {
+    background-color: #5cb85c
+}
+.label-success[href]:focus,
+.label-success[href]:hover {
+    background-color: #449d44
+}
+.label-info {
+    background-color: #5bc0de
+}
+.label-info[href]:focus,
+.label-info[href]:hover {
+    background-color: #31b0d5
+}
+.label-warning {
+    background-color: #f0ad4e
+}
+.label-warning[href]:focus,
+.label-warning[href]:hover {
+    background-color: #ec971f
+}
+.label-danger {
+    background-color: #d9534f
+}
+.label-danger[href]:focus,
+.label-danger[href]:hover {
+    background-color: #c9302c
+}
+.badge {
+    display: inline-block;
+    min-width: 10px;
+    padding: 3px 7px;
+    font-size: 9pt;
+    color: #fff;
+    background-color: #777;
+    border-radius: 10px
+}
+.badge:empty {
+    display: none
+}
+.list-group-item,
+.media-object,
+.thumbnail {
+    display: block
+}
+.btn .badge {
+    position: relative;
+    top: -1px
+}
+.btn-xs .badge {
+    top: 0;
+    padding: 1px 5px
+}
+a.badge:focus,
+a.badge:hover {
+    color: #fff;
+    text-decoration: none;
+    cursor: pointer
+}
+.nav-pills>.active>a>.badge,
+a.list-group-item.active>.badge {
+    color: #428bca;
+    background-color: #fff
+}
+.jumbotron,
+.jumbotron .h1,
+.jumbotron h1 {
+    color: inherit
+}
+.nav-pills>li>a>.badge {
+    margin-left: 3px
+}
+.jumbotron {
+    padding: 30px;
+    margin-bottom: 30px;
+    background-color: #eee
+}
+.jumbotron p {
+    margin-bottom: 15px;
+    font-size: 21px;
+    font-weight: 200
+}
+.alert,
+.thumbnail {
+    margin-bottom: 20px
+}
+.alert .alert-link,
+.close {
+    font-weight: 700
+}
+.jumbotron>hr {
+    border-top-color: #d5d5d5
+}
+.container .jumbotron {
+    border-radius: 6px
+}
+.jumbotron .container {
+    max-width: 100%
+}
+@media screen and (min-width: 768px) {
+    .jumbotron {
+        padding-top: 3pc;
+        padding-bottom: 3pc
+    }
+    .container .jumbotron {
+        padding-left: 60px;
+        padding-right: 60px
+    }
+    .jumbotron .h1,
+    .jumbotron h1 {
+        font-size: 63px
+    }
+}
+.thumbnail {
+    padding: 4px;
+    line-height: 1.42857143;
+    background-color: #fff;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    -o-transition: all .2s ease-in-out;
+    transition: all .2s ease-in-out
+}
+.thumbnail a>img,
+.thumbnail>img {
+    margin-left: auto;
+    margin-right: auto
+}
+a.thumbnail.active,
+a.thumbnail:focus,
+a.thumbnail:hover {
+    border-color: #428bca
+}
+.thumbnail .caption {
+    padding: 9px;
+    color: #333
+}
+.alert {
+    padding: 15px;
+    border: 1px solid transparent;
+    border-radius: 4px
+}
+.alert h4 {
+    margin-top: 0;
+    color: inherit
+}
+.alert>p,
+.alert>ul {
+    margin-bottom: 0
+}
+.alert>p+p {
+    margin-top: 5px
+}
+.alert-dismissable,
+.alert-dismissible {
+    padding-right: 35px
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+    position: relative;
+    top: -2px;
+    right: -21px;
+    color: inherit
+}
+.modal,
+.modal-backdrop {
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0
+}
+.alert-success {
+    background-color: #dff0d8;
+    border-color: #d6e9c6;
+    color: #3c763d
+}
+.alert-success hr {
+    border-top-color: #c9e2b3
+}
+.alert-success .alert-link {
+    color: #2b542c
+}
+.alert-info {
+    background-color: #d9edf7;
+    border-color: #bce8f1;
+    color: #31708f
+}
+.alert-info hr {
+    border-top-color: #a6e1ec
+}
+.alert-info .alert-link {
+    color: #245269
+}
+.alert-warning {
+    background-color: #fcf8e3;
+    border-color: #faebcc;
+    color: #8a6d3b
+}
+.alert-warning hr {
+    border-top-color: #f7e1b5
+}
+.alert-warning .alert-link {
+    color: #66512c
+}
+.alert-danger {
+    background-color: #f2dede;
+    border-color: #ebccd1;
+    color: #a94442
+}
+.alert-danger hr {
+    border-top-color: #e4b9c0
+}
+.alert-danger .alert-link {
+    color: #843534
+}
+@-webkit-keyframes progress-bar-stripes {
+    from {
+        background-position: 40px 0
+    }
+    to {
+        background-position: 0 0
+    }
+}
+@keyframes progress-bar-stripes {
+    from {
+        background-position: 40px 0
+    }
+    to {
+        background-position: 0 0
+    }
+}
+.progress {
+    height: 20px;
+    margin-bottom: 20px;
+    background-color: #f5f5f5;
+    border-radius: 4px;
+    -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
+    box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1)
+}
+.progress-bar {
+    float: left;
+    width: 0;
+    height: 100%;
+    font-size: 9pt;
+    line-height: 20px;
+    color: #fff;
+    text-align: center;
+    background-color: #428bca;
+    -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+    box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
+    -webkit-transition: width .6s ease;
+    -o-transition: width .6s ease;
+    transition: width .6s ease
+}
+.close,
+.list-group-item>.badge {
+    float: right
+}
+.progress-bar-striped,
+.progress-striped .progress-bar {
+    background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+    background-size: 40px 40px
+}
+.progress-bar.active,
+.progress.active .progress-bar {
+    -webkit-animation: progress-bar-stripes 2s linear infinite;
+    -o-animation: progress-bar-stripes 2s linear infinite;
+    animation: progress-bar-stripes 2s linear infinite
+}
+.progress-bar[aria-valuenow="1"],
+.progress-bar[aria-valuenow="2"] {
+    min-width: 30px
+}
+.progress-bar[aria-valuenow="0"] {
+    color: #777;
+    min-width: 30px;
+    background-color: transparent;
+    background-image: none;
+    box-shadow: none
+}
+.progress-bar-success {
+    background-color: #5cb85c
+}
+.progress-striped .progress-bar-success {
+    background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-striped .progress-bar-info,
+.progress-striped .progress-bar-warning {
+    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-bar-info {
+    background-color: #5bc0de
+}
+.progress-striped .progress-bar-info {
+    background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-bar-warning {
+    background-color: #f0ad4e
+}
+.progress-striped .progress-bar-warning {
+    background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.progress-bar-danger {
+    background-color: #d9534f
+}
+.progress-striped .progress-bar-danger {
+    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
+    background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent)
+}
+.media,
+.media-body {
+    overflow: hidden;
+    zoom: 1
+}
+.media,
+.media .media {
+    margin-top: 15px
+}
+.media:first-child {
+    margin-top: 0
+}
+.media-heading {
+    margin: 0 0 5px
+}
+.media>.pull-left {
+    margin-right: 10px
+}
+.media>.pull-right {
+    margin-left: 10px
+}
+.media-list {
+    padding-left: 0;
+    list-style: none
+}
+.list-group {
+    margin-bottom: 20px;
+    padding-left: 0
+}
+.list-group-item {
+    position: relative;
+    padding: 10px 15px;
+    margin-bottom: -1px;
+    background-color: #fff;
+    border: 1px solid #ddd
+}
+.list-group-item:first-child {
+    border-top-right-radius: 4px;
+    border-top-left-radius: 4px
+}
+.list-group-item:last-child {
+    margin-bottom: 0;
+    border-bottom-right-radius: 4px;
+    border-bottom-left-radius: 4px
+}
+.list-group-item>.badge+.badge {
+    margin-right: 5px
+}
+a.list-group-item {
+    color: #555
+}
+a.list-group-item .list-group-item-heading {
+    color: #333
+}
+a.list-group-item:focus,
+a.list-group-item:hover {
+    text-decoration: none;
+    color: #555;
+    background-color: #f5f5f5
+}
+.list-group-item.disabled,
+.list-group-item.disabled:focus,
+.list-group-item.disabled:hover {
+    background-color: #eee;
+    color: #777
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading {
+    color: inherit
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text {
+    color: #777
+}
+.list-group-item.active,
+.list-group-item.active:focus,
+.list-group-item.active:hover {
+    z-index: 2;
+    color: #fff;
+    background-color: #428bca;
+    border-color: #428bca
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active .list-group-item-heading>.small,
+.list-group-item.active .list-group-item-heading>small,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading>.small,
+.list-group-item.active:focus .list-group-item-heading>small,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading>.small,
+.list-group-item.active:hover .list-group-item-heading>small {
+    color: inherit
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text {
+    color: #e1edf7
+}
+.list-group-item-success {
+    color: #3c763d;
+    background-color: #dff0d8
+}
+a.list-group-item-success {
+    color: #3c763d
+}
+a.list-group-item-success .list-group-item-heading {
+    color: inherit
+}
+a.list-group-item-success:focus,
+a.list-group-item-success:hover {
+    color: #3c763d;
+    background-color: #d0e9c6
+}
+a.list-group-item-success.active,
+a.list-group-item-success.active:focus,
+a.list-group-item-success.active:hover {
+    color: #fff;
+    background-color: #3c763d;
+    border-color: #3c763d
+}
+.list-group-item-info {
+    color: #31708f;
+    background-color: #d9edf7
+}
+a.list-group-item-info {
+    color: #31708f
+}
+a.list-group-item-info .list-group-item-heading {
+    color: inherit
+}
+a.list-group-item-info:focus,
+a.list-group-item-info:hover {
+    color: #31708f;
+    background-color: #c4e3f3
+}
+a.list-group-item-info.active,
+a.list-group-item-info.active:focus,
+a.list-group-item-info.active:hover {
+    color: #fff;
+    background-color: #31708f;
+    border-color: #31708f
+}
+.list-group-item-warning {
+    color: #8a6d3b;
+    background-color: #fcf8e3
+}
+a.list-group-item-warning {
+    color: #8a6d3b
+}
+a.list-group-item-warning .list-group-item-heading {
+    color: inherit
+}
+a.list-group-item-warning:focus,
+a.list-group-item-warning:hover {
+    color: #8a6d3b;
+    background-color: #faf2cc
+}
+a.list-group-item-warning.active,
+a.list-group-item-warning.active:focus,
+a.list-group-item-warning.active:hover {
+    color: #fff;
+    background-color: #8a6d3b;
+    border-color: #8a6d3b
+}
+.list-group-item-danger {
+    color: #a94442;
+    background-color: #f2dede
+}
+a.list-group-item-danger {
+    color: #a94442
+}
+a.list-group-item-danger .list-group-item-heading {
+    color: inherit
+}
+a.list-group-item-danger:focus,
+a.list-group-item-danger:hover {
+    color: #a94442;
+    background-color: #ebcccc
+}
+a.list-group-item-danger.active,
+a.list-group-item-danger.active:focus,
+a.list-group-item-danger.active:hover {
+    color: #fff;
+    background-color: #a94442;
+    border-color: #a94442
+}
+.panel-heading>.dropdown .dropdown-toggle,
+.panel-title,
+.panel-title>a {
+    color: inherit
+}
+.list-group-item-heading {
+    margin-top: 0;
+    margin-bottom: 5px
+}
+.list-group-item-text {
+    margin-bottom: 0;
+    line-height: 1.3
+}
+.panel {
+    margin-bottom: 20px;
+    background-color: #fff;
+    border: 1px solid transparent;
+    border-radius: 4px;
+    -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
+    box-shadow: 0 1px 1px rgba(0, 0, 0, .05)
+}
+.panel-title,
+.panel>.list-group,
+.panel>.panel-collapse>.table,
+.panel>.table,
+.panel>.table-responsive>.table {
+    margin-bottom: 0
+}
+.panel-body {
+    padding: 15px
+}
+.panel-heading {
+    padding: 10px 15px;
+    border-bottom: 1px solid transparent;
+    border-top-right-radius: 3px;
+    border-top-left-radius: 3px
+}
+.panel-group .panel-heading,
+.panel>.list-group:last-child .list-group-item:last-child,
+.panel>.table-bordered>tbody>tr:first-child>td,
+.panel>.table-bordered>tbody>tr:first-child>th,
+.panel>.table-bordered>tbody>tr:last-child>td,
+.panel>.table-bordered>tbody>tr:last-child>th,
+.panel>.table-bordered>tfoot>tr:last-child>td,
+.panel>.table-bordered>tfoot>tr:last-child>th,
+.panel>.table-bordered>thead>tr:first-child>td,
+.panel>.table-bordered>thead>tr:first-child>th,
+.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,
+.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,
+.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,
+.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,
+.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,
+.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,
+.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,
+.panel>.table-responsive>.table-bordered>thead>tr:first-child>th {
+    border-bottom: 0
+}
+.panel-title {
+    margin-top: 0;
+    font-size: 1pc
+}
+.panel-footer {
+    padding: 10px 15px;
+    background-color: #f5f5f5;
+    border-top: 1px solid #ddd;
+    border-bottom-right-radius: 3px;
+    border-bottom-left-radius: 3px
+}
+.panel>.list-group .list-group-item {
+    border-width: 1px 0;
+    border-radius: 0
+}
+.panel>.list-group:last-child .list-group-item:last-child,
+.panel>.table-responsive:last-child>.table:last-child,
+.panel>.table:last-child {
+    border-bottom-left-radius: 3px;
+    border-bottom-right-radius: 3px
+}
+.panel>.list-group:first-child .list-group-item:first-child {
+    border-top: 0;
+    border-top-right-radius: 3px;
+    border-top-left-radius: 3px
+}
+.list-group+.panel-footer,
+.panel-heading+.list-group .list-group-item:first-child {
+    border-top-width: 0
+}
+.panel>.table-responsive:first-child>.table:first-child,
+.panel>.table:first-child {
+    border-top-right-radius: 3px;
+    border-top-left-radius: 3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,
+.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,
+.panel>.table:first-child>thead:first-child>tr:first-child th:first-child {
+    border-top-left-radius: 3px
+}
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,
+.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,
+.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,
+.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,
+.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,
+.panel>.table:first-child>thead:first-child>tr:first-child th:last-child {
+    border-top-right-radius: 3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child {
+    border-bottom-left-radius: 3px
+}
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,
+.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,
+.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,
+.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,
+.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child {
+    border-bottom-right-radius: 3px
+}
+.panel>.panel-body+.table,
+.panel>.panel-body+.table-responsive {
+    border-top: 1px solid #ddd
+}
+.panel>.table>tbody:first-child>tr:first-child td,
+.panel>.table>tbody:first-child>tr:first-child th {
+    border-top: 0
+}
+.panel>.table-bordered,
+.panel>.table-responsive>.table-bordered {
+    border: 0
+}
+.panel>.table-bordered>tbody>tr>td:first-child,
+.panel>.table-bordered>tbody>tr>th:first-child,
+.panel>.table-bordered>tfoot>tr>td:first-child,
+.panel>.table-bordered>tfoot>tr>th:first-child,
+.panel>.table-bordered>thead>tr>td:first-child,
+.panel>.table-bordered>thead>tr>th:first-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,
+.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,
+.panel>.table-responsive>.table-bordered>thead>tr>th:first-child {
+    border-left: 0
+}
+.panel>.table-bordered>tbody>tr>td:last-child,
+.panel>.table-bordered>tbody>tr>th:last-child,
+.panel>.table-bordered>tfoot>tr>td:last-child,
+.panel>.table-bordered>tfoot>tr>th:last-child,
+.panel>.table-bordered>thead>tr>td:last-child,
+.panel>.table-bordered>thead>tr>th:last-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,
+.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,
+.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,
+.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,
+.panel>.table-responsive>.table-bordered>thead>tr>th:last-child {
+    border-right: 0
+}
+.panel>.table-responsive {
+    border: 0;
+    margin-bottom: 0
+}
+.panel-group {
+    margin-bottom: 20px
+}
+.panel-group .panel {
+    margin-bottom: 0;
+    border-radius: 4px
+}
+.panel-group .panel+.panel {
+    margin-top: 5px
+}
+.panel-group .panel-heading+.panel-collapse>.panel-body {
+    border-top: 1px solid #ddd
+}
+.panel-group .panel-footer {
+    border-top: 0
+}
+.panel-group .panel-footer+.panel-collapse .panel-body {
+    border-bottom: 1px solid #ddd
+}
+.panel-default {
+    border-color: #ddd
+}
+.panel-default>.panel-heading {
+    color: #333;
+    background-color: #f5f5f5;
+    border-color: #ddd
+}
+.panel-default>.panel-heading+.panel-collapse>.panel-body {
+    border-top-color: #ddd
+}
+.panel-default>.panel-heading .badge {
+    color: #f5f5f5;
+    background-color: #333
+}
+.panel-default>.panel-footer+.panel-collapse>.panel-body {
+    border-bottom-color: #ddd
+}
+.panel-primary {
+    border-color: #428bca
+}
+.panel-primary>.panel-heading {
+    color: #fff;
+    background-color: #428bca;
+    border-color: #428bca
+}
+.panel-primary>.panel-heading+.panel-collapse>.panel-body {
+    border-top-color: #428bca
+}
+.panel-primary>.panel-heading .badge {
+    color: #428bca;
+    background-color: #fff
+}
+.panel-primary>.panel-footer+.panel-collapse>.panel-body {
+    border-bottom-color: #428bca
+}
+.panel-success {
+    border-color: #d6e9c6
+}
+.panel-success>.panel-heading {
+    color: #3c763d;
+    background-color: #dff0d8;
+    border-color: #d6e9c6
+}
+.panel-success>.panel-heading+.panel-collapse>.panel-body {
+    border-top-color: #d6e9c6
+}
+.panel-success>.panel-heading .badge {
+    color: #dff0d8;
+    background-color: #3c763d
+}
+.panel-success>.panel-footer+.panel-collapse>.panel-body {
+    border-bottom-color: #d6e9c6
+}
+.panel-info {
+    border-color: #bce8f1
+}
+.panel-info>.panel-heading {
+    color: #31708f;
+    background-color: #d9edf7;
+    border-color: #bce8f1
+}
+.panel-info>.panel-heading+.panel-collapse>.panel-body {
+    border-top-color: #bce8f1
+}
+.panel-info>.panel-heading .badge {
+    color: #d9edf7;
+    background-color: #31708f
+}
+.panel-info>.panel-footer+.panel-collapse>.panel-body {
+    border-bottom-color: #bce8f1
+}
+.panel-warning {
+    border-color: #faebcc
+}
+.panel-warning>.panel-heading {
+    color: #8a6d3b;
+    background-color: #fcf8e3;
+    border-color: #faebcc
+}
+.panel-warning>.panel-heading+.panel-collapse>.panel-body {
+    border-top-color: #faebcc
+}
+.panel-warning>.panel-heading .badge {
+    color: #fcf8e3;
+    background-color: #8a6d3b
+}
+.panel-warning>.panel-footer+.panel-collapse>.panel-body {
+    border-bottom-color: #faebcc
+}
+.panel-danger {
+    border-color: #ebccd1
+}
+.panel-danger>.panel-heading {
+    color: #a94442;
+    background-color: #f2dede;
+    border-color: #ebccd1
+}
+.panel-danger>.panel-heading+.panel-collapse>.panel-body {
+    border-top-color: #ebccd1
+}
+.panel-danger>.panel-heading .badge {
+    color: #f2dede;
+    background-color: #a94442
+}
+.panel-danger>.panel-footer+.panel-collapse>.panel-body {
+    border-bottom-color: #ebccd1
+}
+.embed-responsive {
+    position: relative;
+    display: block;
+    height: 0;
+    padding: 0
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive embed,
+.embed-responsive iframe,
+.embed-responsive object {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    height: 100%;
+    width: 100%;
+    border: 0
+}
+.embed-responsive.embed-responsive-16by9 {
+    padding-bottom: 56.25%
+}
+.embed-responsive.embed-responsive-4by3 {
+    padding-bottom: 75%
+}
+.well {
+    min-height: 20px;
+    padding: 19px;
+    margin-bottom: 20px;
+    background-color: #f5f5f5;
+    border: 1px solid #e3e3e3;
+    border-radius: 4px;
+    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
+    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05)
+}
+.well blockquote {
+    border-color: #ddd;
+    border-color: rgba(0, 0, 0, .15)
+}
+.well-lg {
+    padding: 24px;
+    border-radius: 6px
+}
+.well-sm {
+    padding: 9px;
+    border-radius: 3px
+}
+.close {
+    font-size: 21px;
+    line-height: 1;
+    color: #000;
+    text-shadow: 0 1px 0 #fff;
+    opacity: .2;
+    filter: alpha(opacity=20)
+}
+.carousel-caption,
+.carousel-control {
+    text-shadow: 0 1px 2px rgba(0, 0, 0, .6)
+}
+.close:focus,
+.close:hover {
+    color: #000;
+    text-decoration: none;
+    cursor: pointer;
+    opacity: .5;
+    filter: alpha(opacity=50)
+}
+button.close {
+    padding: 0;
+    cursor: pointer;
+    background: 0 0;
+    border: 0;
+    -webkit-appearance: none
+}
+.modal-content,
+.popover {
+    background-clip: padding-box
+}
+.modal {
+    display: none;
+    position: fixed;
+    z-index: 1050;
+    -webkit-overflow-scrolling: touch;
+    outline: 0
+}
+.modal.fade .modal-dialog {
+    -webkit-transform: translate3d(0, -25%, 0);
+    transform: translate3d(0, -25%, 0);
+    -webkit-transition: -webkit-transform .3s ease-out;
+    -moz-transition: -moz-transform .3s ease-out;
+    -o-transition: -o-transform .3s ease-out;
+    transition: transform .3s ease-out
+}
+.modal.in .modal-dialog {
+    -webkit-transform: translate3d(0, 0, 0);
+    transform: translate3d(0, 0, 0)
+}
+.modal-open .modal {
+    overflow-x: hidden;
+    overflow-y: auto
+}
+.modal-dialog {
+    position: relative;
+    width: auto;
+    margin: 10px
+}
+.modal-content {
+    position: relative;
+    background-color: #fff;
+    border: 1px solid #999;
+    border: 1px solid rgba(0, 0, 0, .2);
+    border-radius: 6px;
+    -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+    box-shadow: 0 3px 9px rgba(0, 0, 0, .5);
+    outline: 0
+}
+.modal-backdrop {
+    position: fixed;
+    z-index: 1040;
+    background-color: #000
+}
+.modal-backdrop.fade {
+    opacity: 0;
+    filter: alpha(opacity=0)
+}
+.modal-backdrop.in {
+    opacity: .5;
+    filter: alpha(opacity=50)
+}
+.modal-header {
+    padding: 15px;
+    border-bottom: 1px solid #e5e5e5;
+    min-height: 16.43px
+}
+.modal-header .close {
+    margin-top: -2px
+}
+.modal-title {
+    margin: 0;
+    line-height: 1.42857143
+}
+.modal-body {
+    position: relative;
+    padding: 15px
+}
+.modal-footer {
+    padding: 15px;
+    text-align: right;
+    border-top: 1px solid #e5e5e5
+}
+.tooltip.top .tooltip-arrow,
+.tooltip.top-left .tooltip-arrow,
+.tooltip.top-right .tooltip-arrow {
+    bottom: 0;
+    border-width: 5px 5px 0;
+    border-top-color: #000
+}
+.modal-footer .btn+.btn {
+    margin-left: 5px;
+    margin-bottom: 0
+}
+.modal-footer .btn-group .btn+.btn {
+    margin-left: -1px
+}
+.modal-footer .btn-block+.btn-block {
+    margin-left: 0
+}
+.modal-scrollbar-measure {
+    position: absolute;
+    top: -9999px;
+    width: 50px;
+    height: 50px;
+    overflow: scroll
+}
+@media (min-width: 768px) {
+    .modal-dialog {
+        width: 600px;
+        margin: 30px auto
+    }
+    .modal-content {
+        -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
+        box-shadow: 0 5px 15px rgba(0, 0, 0, .5)
+    }
+    .modal-sm {
+        width: 300px
+    }
+}
+@media (min-width: 992px) {
+    .modal-lg {
+        width: 900px
+    }
+}
+.tooltip {
+    position: absolute;
+    z-index: 1070;
+    display: block;
+    visibility: visible;
+    font-size: 9pt;
+    line-height: 1.4;
+    opacity: 0;
+    filter: alpha(opacity=0)
+}
+.tooltip.in {
+    opacity: .9;
+    filter: alpha(opacity=90)
+}
+.tooltip.top {
+    margin-top: -3px;
+    padding: 5px 0
+}
+.tooltip.right {
+    margin-left: 3px;
+    padding: 0 5px
+}
+.tooltip.bottom {
+    margin-top: 3px;
+    padding: 5px 0
+}
+.tooltip.left {
+    margin-left: -3px;
+    padding: 0 5px
+}
+.tooltip-inner {
+    max-width: 200px;
+    padding: 3px 8px;
+    color: #fff;
+    text-align: center;
+    text-decoration: none;
+    background-color: #000;
+    border-radius: 4px
+}
+.tooltip-arrow {
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid
+}
+.tooltip.top .tooltip-arrow {
+    left: 50%;
+    margin-left: -5px
+}
+.tooltip.top-left .tooltip-arrow {
+    left: 5px
+}
+.tooltip.top-right .tooltip-arrow {
+    right: 5px
+}
+.tooltip.right .tooltip-arrow {
+    top: 50%;
+    left: 0;
+    margin-top: -5px;
+    border-width: 5px 5px 5px 0;
+    border-right-color: #000
+}
+.tooltip.left .tooltip-arrow {
+    top: 50%;
+    right: 0;
+    margin-top: -5px;
+    border-width: 5px 0 5px 5px;
+    border-left-color: #000
+}
+.tooltip.bottom .tooltip-arrow,
+.tooltip.bottom-left .tooltip-arrow,
+.tooltip.bottom-right .tooltip-arrow {
+    border-width: 0 5px 5px;
+    border-bottom-color: #000;
+    top: 0
+}
+.tooltip.bottom .tooltip-arrow {
+    left: 50%;
+    margin-left: -5px
+}
+.tooltip.bottom-left .tooltip-arrow {
+    left: 5px
+}
+.tooltip.bottom-right .tooltip-arrow {
+    right: 5px
+}
+.popover {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 1060;
+    display: none;
+    max-width: 276px;
+    padding: 1px;
+    text-align: left;
+    background-color: #fff;
+    border: 1px solid #ccc;
+    border: 1px solid rgba(0, 0, 0, .2);
+    border-radius: 6px;
+    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+    box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+    white-space: normal
+}
+.popover.top {
+    margin-top: -10px
+}
+.popover.right {
+    margin-left: 10px
+}
+.popover.bottom {
+    margin-top: 10px
+}
+.popover.left {
+    margin-left: -10px
+}
+.popover-title {
+    margin: 0;
+    padding: 8px 14px;
+    font-size: 14px;
+    font-weight: 400;
+    line-height: 18px;
+    background-color: #f7f7f7;
+    border-bottom: 1px solid #ebebeb;
+    border-radius: 5px 5px 0 0
+}
+.popover-content {
+    padding: 9px 14px
+}
+.popover>.arrow,
+.popover>.arrow:after {
+    position: absolute;
+    display: block;
+    width: 0;
+    height: 0;
+    border-color: transparent;
+    border-style: solid
+}
+.carousel,
+.carousel-inner {
+    position: relative
+}
+.popover>.arrow {
+    border-width: 11px
+}
+.popover>.arrow:after {
+    border-width: 10px
+}
+.popover.top>.arrow {
+    left: 50%;
+    margin-left: -11px;
+    border-bottom-width: 0;
+    border-top-color: #999;
+    border-top-color: rgba(0, 0, 0, .25);
+    bottom: -11px
+}
+.popover.top>.arrow:after {
+    content: " ";
+    bottom: 1px;
+    margin-left: -10px;
+    border-bottom-width: 0;
+    border-top-color: #fff
+}
+.popover.left>.arrow:after,
+.popover.right>.arrow:after {
+    content: " ";
+    bottom: -10px
+}
+.popover.right>.arrow {
+    top: 50%;
+    left: -11px;
+    margin-top: -11px;
+    border-left-width: 0;
+    border-right-color: #999;
+    border-right-color: rgba(0, 0, 0, .25)
+}
+.popover.right>.arrow:after {
+    left: 1px;
+    border-left-width: 0;
+    border-right-color: #fff
+}
+.popover.bottom>.arrow {
+    left: 50%;
+    margin-left: -11px;
+    border-top-width: 0;
+    border-bottom-color: #999;
+    border-bottom-color: rgba(0, 0, 0, .25);
+    top: -11px
+}
+.popover.bottom>.arrow:after {
+    content: " ";
+    top: 1px;
+    margin-left: -10px;
+    border-top-width: 0;
+    border-bottom-color: #fff
+}
+.popover.left>.arrow {
+    top: 50%;
+    right: -11px;
+    margin-top: -11px;
+    border-right-width: 0;
+    border-left-color: #999;
+    border-left-color: rgba(0, 0, 0, .25)
+}
+.popover.left>.arrow:after {
+    right: 1px;
+    border-right-width: 0;
+    border-left-color: #fff
+}
+.carousel-inner {
+    width: 100%
+}
+.carousel-inner>.item {
+    display: none;
+    position: relative;
+    -webkit-transition: .6s ease-in-out left;
+    -o-transition: .6s ease-in-out left;
+    transition: .6s ease-in-out left
+}
+.carousel-inner>.item>a>img,
+.carousel-inner>.item>img {
+    line-height: 1
+}
+.carousel-inner>.active,
+.carousel-inner>.next,
+.carousel-inner>.prev {
+    display: block
+}
+.carousel-inner>.active {
+    left: 0
+}
+.carousel-inner>.next,
+.carousel-inner>.prev {
+    position: absolute;
+    top: 0;
+    width: 100%
+}
+.carousel-inner>.next {
+    left: 100%
+}
+.carousel-inner>.prev {
+    left: -100%
+}
+.carousel-inner>.next.left,
+.carousel-inner>.prev.right {
+    left: 0
+}
+.carousel-inner>.active.left {
+    left: -100%
+}
+.carousel-inner>.active.right {
+    left: 100%
+}
+.carousel-control {
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+    width: 15%;
+    opacity: .5;
+    filter: alpha(opacity=50);
+    font-size: 20px;
+    color: #fff;
+    text-align: center
+}
+.carousel-control.left {
+    background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%);
+    background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%);
+    background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0, rgba(0, 0, 0, .0001) 100%);
+    background-repeat: repeat-x;
+    filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)
+}
+.carousel-control.right {
+    left: auto;
+    right: 0;
+    background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%);
+    background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%);
+    background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0, rgba(0, 0, 0, .5) 100%);
+    background-repeat: repeat-x;
+    filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)
+}
+.carousel-control:focus,
+.carousel-control:hover {
+    outline: 0;
+    color: #fff;
+    text-decoration: none;
+    opacity: .9;
+    filter: alpha(opacity=90)
+}
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right,
+.carousel-control .icon-next,
+.carousel-control .icon-prev {
+    position: absolute;
+    top: 50%;
+    z-index: 5;
+    display: inline-block
+}
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .icon-prev {
+    left: 50%;
+    margin-left: -10px
+}
+.carousel-control .glyphicon-chevron-right,
+.carousel-control .icon-next {
+    right: 50%;
+    margin-right: -10px
+}
+.carousel-control .icon-next,
+.carousel-control .icon-prev {
+    width: 20px;
+    height: 20px;
+    margin-top: -10px;
+    font-family: serif
+}
+.carousel-control .icon-prev:before {
+    content: '\2039'
+}
+.carousel-control .icon-next:before {
+    content: '\203a'
+}
+.carousel-indicators {
+    position: absolute;
+    bottom: 10px;
+    left: 50%;
+    z-index: 15;
+    width: 60%;
+    margin-left: -30%;
+    padding-left: 0;
+    list-style: none;
+    text-align: center
+}
+.carousel-indicators li {
+    display: inline-block;
+    width: 10px;
+    height: 10px;
+    margin: 1px;
+    text-indent: -999px;
+    border: 1px solid #fff;
+    border-radius: 10px;
+    cursor: pointer;
+    background-color: #000\9;
+    background-color: transparent
+}
+.carousel-indicators .active {
+    margin: 0;
+    width: 9pt;
+    height: 9pt;
+    background-color: #fff
+}
+.carousel-caption {
+    position: absolute;
+    left: 15%;
+    right: 15%;
+    bottom: 20px;
+    z-index: 10;
+    padding-top: 20px;
+    padding-bottom: 20px;
+    color: #fff;
+    text-align: center
+}
+.carousel-caption .btn,
+.text-hide {
+    text-shadow: none
+}
+@media screen and (min-width: 768px) {
+    .carousel-control .glyphicon-chevron-left,
+    .carousel-control .glyphicon-chevron-right,
+    .carousel-control .icon-next,
+    .carousel-control .icon-prev {
+        width: 30px;
+        height: 30px;
+        margin-top: -15px;
+        font-size: 30px
+    }
+    .carousel-control .glyphicon-chevron-left,
+    .carousel-control .icon-prev {
+        margin-left: -15px
+    }
+    .carousel-control .glyphicon-chevron-right,
+    .carousel-control .icon-next {
+        margin-right: -15px
+    }
+    .carousel-caption {
+        left: 20%;
+        right: 20%;
+        padding-bottom: 30px
+    }
+    .carousel-indicators {
+        bottom: 20px
+    }
+}
+.btn-group-vertical>.btn-group:after,
+.btn-group-vertical>.btn-group:before,
+.btn-toolbar:after,
+.btn-toolbar:before,
+.clearfix:after,
+.clearfix:before,
+.container-fluid:after,
+.container-fluid:before,
+.container:after,
+.container:before,
+.dl-horizontal dd:after,
+.dl-horizontal dd:before,
+.form-horizontal .form-group:after,
+.form-horizontal .form-group:before,
+.modal-footer:after,
+.modal-footer:before,
+.nav:after,
+.nav:before,
+.navbar-collapse:after,
+.navbar-collapse:before,
+.navbar-header:after,
+.navbar-header:before,
+.navbar:after,
+.navbar:before,
+.pager:after,
+.pager:before,
+.panel-body:after,
+.panel-body:before,
+.row:after,
+.row:before {
+    content: " ";
+    display: table
+}
+.btn-group-vertical>.btn-group:after,
+.btn-toolbar:after,
+.clearfix:after,
+.container-fluid:after,
+.container:after,
+.dl-horizontal dd:after,
+.form-horizontal .form-group:after,
+.modal-footer:after,
+.nav:after,
+.navbar-collapse:after,
+.navbar-header:after,
+.navbar:after,
+.pager:after,
+.panel-body:after,
+.row:after {
+    clear: both
+}
+.center-block {
+    display: block;
+    margin-left: auto;
+    margin-right: auto
+}
+.pull-right {
+    float: right!important
+}
+.pull-left {
+    float: left!important
+}
+.hide {
+    display: none!important
+}
+.show {
+    display: block!important
+}
+.hidden,
+.visible-lg,
+.visible-lg-block,
+.visible-lg-inline,
+.visible-lg-inline-block,
+.visible-md,
+.visible-md-block,
+.visible-md-inline,
+.visible-md-inline-block,
+.visible-sm,
+.visible-sm-block,
+.visible-sm-inline,
+.visible-sm-inline-block,
+.visible-xs,
+.visible-xs-block,
+.visible-xs-inline,
+.visible-xs-inline-block {
+    display: none!important
+}
+.invisible {
+    visibility: hidden
+}
+.text-hide {
+    font: 0/0 a;
+    color: transparent;
+    background-color: transparent;
+    border: 0
+}
+.hidden {
+    visibility: hidden!important
+}
+.affix {
+    position: fixed;
+    -webkit-transform: translate3d(0, 0, 0);
+    transform: translate3d(0, 0, 0)
+}
+@-ms-viewport {
+    width: device-width
+}
+@media (max-width: 767px) {
+    .visible-xs {
+        display: block!important
+    }
+    table.visible-xs {
+        display: table
+    }
+    tr.visible-xs {
+        display: table-row!important
+    }
+    td.visible-xs,
+    th.visible-xs {
+        display: table-cell!important
+    }
+    .visible-xs-block {
+        display: block!important
+    }
+    .visible-xs-inline {
+        display: inline!important
+    }
+    .visible-xs-inline-block {
+        display: inline-block!important
+    }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+    .visible-sm {
+        display: block!important
+    }
+    table.visible-sm {
+        display: table
+    }
+    tr.visible-sm {
+        display: table-row!important
+    }
+    td.visible-sm,
+    th.visible-sm {
+        display: table-cell!important
+    }
+    .visible-sm-block {
+        display: block!important
+    }
+    .visible-sm-inline {
+        display: inline!important
+    }
+    .visible-sm-inline-block {
+        display: inline-block!important
+    }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+    .visible-md {
+        display: block!important
+    }
+    table.visible-md {
+        display: table
+    }
+    tr.visible-md {
+        display: table-row!important
+    }
+    td.visible-md,
+    th.visible-md {
+        display: table-cell!important
+    }
+    .visible-md-block {
+        display: block!important
+    }
+    .visible-md-inline {
+        display: inline!important
+    }
+    .visible-md-inline-block {
+        display: inline-block!important
+    }
+}
+@media (min-width: 1200px) {
+    .visible-lg {
+        display: block!important
+    }
+    table.visible-lg {
+        display: table
+    }
+    tr.visible-lg {
+        display: table-row!important
+    }
+    td.visible-lg,
+    th.visible-lg {
+        display: table-cell!important
+    }
+    .visible-lg-block {
+        display: block!important
+    }
+    .visible-lg-inline {
+        display: inline!important
+    }
+    .visible-lg-inline-block {
+        display: inline-block!important
+    }
+    .hidden-lg {
+        display: none!important
+    }
+}
+@media (max-width: 767px) {
+    .hidden-xs {
+        display: none!important
+    }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+    .hidden-sm {
+        display: none!important
+    }
+}
+@media (min-width: 992px) and (max-width: 1199px) {
+    .hidden-md {
+        display: none!important
+    }
+}
+.visible-print {
+    display: none!important
+}
+@media print {
+    .visible-print {
+        display: block!important
+    }
+    table.visible-print {
+        display: table
+    }
+    tr.visible-print {
+        display: table-row!important
+    }
+    td.visible-print,
+    th.visible-print {
+        display: table-cell!important
+    }
+}
+.visible-print-block {
+    display: none!important
+}
+@media print {
+    .visible-print-block {
+        display: block!important
+    }
+}
+.visible-print-inline {
+    display: none!important
+}
+@media print {
+    .visible-print-inline {
+        display: inline!important
+    }
+}
+.visible-print-inline-block {
+    display: none!important
+}
+@media print {
+    .visible-print-inline-block {
+        display: inline-block!important
+    }
+    .hidden-print {
+        display: none!important
+    }
+}
\ No newline at end of file
diff --git a/tools/infra-dashboard/css/dataTables.bootstrap.min.css b/tools/infra-dashboard/css/dataTables.bootstrap.min.css
new file mode 100644 (file)
index 0000000..745f299
--- /dev/null
@@ -0,0 +1 @@
+table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable{border-collapse:separate !important}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0}
diff --git a/tools/infra-dashboard/css/font-awesome.css b/tools/infra-dashboard/css/font-awesome.css
new file mode 100644 (file)
index 0000000..2dcdc22
--- /dev/null
@@ -0,0 +1,1801 @@
+/*!
+ *  Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+@font-face {
+  font-family: 'FontAwesome';
+  src: url('../fonts/fontawesome-webfont.eot?v=4.3.0');
+  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+.fa {
+  display: inline-block;
+  font: normal normal normal 14px/1 FontAwesome;
+  font-size: inherit;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  transform: translate(0, 0);
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+  font-size: 1.33333333em;
+  line-height: 0.75em;
+  vertical-align: -15%;
+}
+.fa-2x {
+  font-size: 2em;
+}
+.fa-3x {
+  font-size: 3em;
+}
+.fa-4x {
+  font-size: 4em;
+}
+.fa-5x {
+  font-size: 5em;
+}
+.fa-fw {
+  width: 1.28571429em;
+  text-align: center;
+}
+.fa-ul {
+  padding-left: 0;
+  margin-left: 2.14285714em;
+  list-style-type: none;
+}
+.fa-ul > li {
+  position: relative;
+}
+.fa-li {
+  position: absolute;
+  left: -2.14285714em;
+  width: 2.14285714em;
+  top: 0.14285714em;
+  text-align: center;
+}
+.fa-li.fa-lg {
+  left: -1.85714286em;
+}
+.fa-border {
+  padding: .2em .25em .15em;
+  border: solid 0.08em #eeeeee;
+  border-radius: .1em;
+}
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.fa.pull-left {
+  margin-right: .3em;
+}
+.fa.pull-right {
+  margin-left: .3em;
+}
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+  animation: fa-spin 2s infinite linear;
+}
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+  animation: fa-spin 1s infinite steps(8);
+}
+@-webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+@keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+.fa-rotate-90 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+  -webkit-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.fa-rotate-180 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  -webkit-transform: rotate(180deg);
+  -ms-transform: rotate(180deg);
+  transform: rotate(180deg);
+}
+.fa-rotate-270 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+  -webkit-transform: rotate(270deg);
+  -ms-transform: rotate(270deg);
+  transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
+  -webkit-transform: scale(-1, 1);
+  -ms-transform: scale(-1, 1);
+  transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
+  -webkit-transform: scale(1, -1);
+  -ms-transform: scale(1, -1);
+  transform: scale(1, -1);
+}
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical {
+  filter: none;
+}
+.fa-stack {
+  position: relative;
+  display: inline-block;
+  width: 2em;
+  height: 2em;
+  line-height: 2em;
+  vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  text-align: center;
+}
+.fa-stack-1x {
+  line-height: inherit;
+}
+.fa-stack-2x {
+  font-size: 2em;
+}
+.fa-inverse {
+  color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+   readers do not read off random characters that represent icons */
+.fa-glass:before {
+  content: "\f000";
+}
+.fa-music:before {
+  content: "\f001";
+}
+.fa-search:before {
+  content: "\f002";
+}
+.fa-envelope-o:before {
+  content: "\f003";
+}
+.fa-heart:before {
+  content: "\f004";
+}
+.fa-star:before {
+  content: "\f005";
+}
+.fa-star-o:before {
+  content: "\f006";
+}
+.fa-user:before {
+  content: "\f007";
+}
+.fa-film:before {
+  content: "\f008";
+}
+.fa-th-large:before {
+  content: "\f009";
+}
+.fa-th:before {
+  content: "\f00a";
+}
+.fa-th-list:before {
+  content: "\f00b";
+}
+.fa-check:before {
+  content: "\f00c";
+}
+.fa-remove:before,
+.fa-close:before,
+.fa-times:before {
+  content: "\f00d";
+}
+.fa-search-plus:before {
+  content: "\f00e";
+}
+.fa-search-minus:before {
+  content: "\f010";
+}
+.fa-power-off:before {
+  content: "\f011";
+}
+.fa-signal:before {
+  content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+  content: "\f013";
+}
+.fa-trash-o:before {
+  content: "\f014";
+}
+.fa-home:before {
+  content: "\f015";
+}
+.fa-file-o:before {
+  content: "\f016";
+}
+.fa-clock-o:before {
+  content: "\f017";
+}
+.fa-road:before {
+  content: "\f018";
+}
+.fa-download:before {
+  content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+  content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+  content: "\f01b";
+}
+.fa-inbox:before {
+  content: "\f01c";
+}
+.fa-play-circle-o:before {
+  content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+  content: "\f01e";
+}
+.fa-refresh:before {
+  content: "\f021";
+}
+.fa-list-alt:before {
+  content: "\f022";
+}
+.fa-lock:before {
+  content: "\f023";
+}
+.fa-flag:before {
+  content: "\f024";
+}
+.fa-headphones:before {
+  content: "\f025";
+}
+.fa-volume-off:before {
+  content: "\f026";
+}
+.fa-volume-down:before {
+  content: "\f027";
+}
+.fa-volume-up:before {
+  content: "\f028";
+}
+.fa-qrcode:before {
+  content: "\f029";
+}
+.fa-barcode:before {
+  content: "\f02a";
+}
+.fa-tag:before {
+  content: "\f02b";
+}
+.fa-tags:before {
+  content: "\f02c";
+}
+.fa-book:before {
+  content: "\f02d";
+}
+.fa-bookmark:before {
+  content: "\f02e";
+}
+.fa-print:before {
+  content: "\f02f";
+}
+.fa-camera:before {
+  content: "\f030";
+}
+.fa-font:before {
+  content: "\f031";
+}
+.fa-bold:before {
+  content: "\f032";
+}
+.fa-italic:before {
+  content: "\f033";
+}
+.fa-text-height:before {
+  content: "\f034";
+}
+.fa-text-width:before {
+  content: "\f035";
+}
+.fa-align-left:before {
+  content: "\f036";
+}
+.fa-align-center:before {
+  content: "\f037";
+}
+.fa-align-right:before {
+  content: "\f038";
+}
+.fa-align-justify:before {
+  content: "\f039";
+}
+.fa-list:before {
+  content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+  content: "\f03b";
+}
+.fa-indent:before {
+  content: "\f03c";
+}
+.fa-video-camera:before {
+  content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+  content: "\f03e";
+}
+.fa-pencil:before {
+  content: "\f040";
+}
+.fa-map-marker:before {
+  content: "\f041";
+}
+.fa-adjust:before {
+  content: "\f042";
+}
+.fa-tint:before {
+  content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+  content: "\f044";
+}
+.fa-share-square-o:before {
+  content: "\f045";
+}
+.fa-check-square-o:before {
+  content: "\f046";
+}
+.fa-arrows:before {
+  content: "\f047";
+}
+.fa-step-backward:before {
+  content: "\f048";
+}
+.fa-fast-backward:before {
+  content: "\f049";
+}
+.fa-backward:before {
+  content: "\f04a";
+}
+.fa-play:before {
+  content: "\f04b";
+}
+.fa-pause:before {
+  content: "\f04c";
+}
+.fa-stop:before {
+  content: "\f04d";
+}
+.fa-forward:before {
+  content: "\f04e";
+}
+.fa-fast-forward:before {
+  content: "\f050";
+}
+.fa-step-forward:before {
+  content: "\f051";
+}
+.fa-eject:before {
+  content: "\f052";
+}
+.fa-chevron-left:before {
+  content: "\f053";
+}
+.fa-chevron-right:before {
+  content: "\f054";
+}
+.fa-plus-circle:before {
+  content: "\f055";
+}
+.fa-minus-circle:before {
+  content: "\f056";
+}
+.fa-times-circle:before {
+  content: "\f057";
+}
+.fa-check-circle:before {
+  content: "\f058";
+}
+.fa-question-circle:before {
+  content: "\f059";
+}
+.fa-info-circle:before {
+  content: "\f05a";
+}
+.fa-crosshairs:before {
+  content: "\f05b";
+}
+.fa-times-circle-o:before {
+  content: "\f05c";
+}
+.fa-check-circle-o:before {
+  content: "\f05d";
+}
+.fa-ban:before {
+  content: "\f05e";
+}
+.fa-arrow-left:before {
+  content: "\f060";
+}
+.fa-arrow-right:before {
+  content: "\f061";
+}
+.fa-arrow-up:before {
+  content: "\f062";
+}
+.fa-arrow-down:before {
+  content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+  content: "\f064";
+}
+.fa-expand:before {
+  content: "\f065";
+}
+.fa-compress:before {
+  content: "\f066";
+}
+.fa-plus:before {
+  content: "\f067";
+}
+.fa-minus:before {
+  content: "\f068";
+}
+.fa-asterisk:before {
+  content: "\f069";
+}
+.fa-exclamation-circle:before {
+  content: "\f06a";
+}
+.fa-gift:before {
+  content: "\f06b";
+}
+.fa-leaf:before {
+  content: "\f06c";
+}
+.fa-fire:before {
+  content: "\f06d";
+}
+.fa-eye:before {
+  content: "\f06e";
+}
+.fa-eye-slash:before {
+  content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+  content: "\f071";
+}
+.fa-plane:before {
+  content: "\f072";
+}
+.fa-calendar:before {
+  content: "\f073";
+}
+.fa-random:before {
+  content: "\f074";
+}
+.fa-comment:before {
+  content: "\f075";
+}
+.fa-magnet:before {
+  content: "\f076";
+}
+.fa-chevron-up:before {
+  content: "\f077";
+}
+.fa-chevron-down:before {
+  content: "\f078";
+}
+.fa-retweet:before {
+  content: "\f079";
+}
+.fa-shopping-cart:before {
+  content: "\f07a";
+}
+.fa-folder:before {
+  content: "\f07b";
+}
+.fa-folder-open:before {
+  content: "\f07c";
+}
+.fa-arrows-v:before {
+  content: "\f07d";
+}
+.fa-arrows-h:before {
+  content: "\f07e";
+}
+.fa-bar-chart-o:before,
+.fa-bar-chart:before {
+  content: "\f080";
+}
+.fa-twitter-square:before {
+  content: "\f081";
+}
+.fa-facebook-square:before {
+  content: "\f082";
+}
+.fa-camera-retro:before {
+  content: "\f083";
+}
+.fa-key:before {
+  content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+  content: "\f085";
+}
+.fa-comments:before {
+  content: "\f086";
+}
+.fa-thumbs-o-up:before {
+  content: "\f087";
+}
+.fa-thumbs-o-down:before {
+  content: "\f088";
+}
+.fa-star-half:before {
+  content: "\f089";
+}
+.fa-heart-o:before {
+  content: "\f08a";
+}
+.fa-sign-out:before {
+  content: "\f08b";
+}
+.fa-linkedin-square:before {
+  content: "\f08c";
+}
+.fa-thumb-tack:before {
+  content: "\f08d";
+}
+.fa-external-link:before {
+  content: "\f08e";
+}
+.fa-sign-in:before {
+  content: "\f090";
+}
+.fa-trophy:before {
+  content: "\f091";
+}
+.fa-github-square:before {
+  content: "\f092";
+}
+.fa-upload:before {
+  content: "\f093";
+}
+.fa-lemon-o:before {
+  content: "\f094";
+}
+.fa-phone:before {
+  content: "\f095";
+}
+.fa-square-o:before {
+  content: "\f096";
+}
+.fa-bookmark-o:before {
+  content: "\f097";
+}
+.fa-phone-square:before {
+  content: "\f098";
+}
+.fa-twitter:before {
+  content: "\f099";
+}
+.fa-facebook-f:before,
+.fa-facebook:before {
+  content: "\f09a";
+}
+.fa-github:before {
+  content: "\f09b";
+}
+.fa-unlock:before {
+  content: "\f09c";
+}
+.fa-credit-card:before {
+  content: "\f09d";
+}
+.fa-rss:before {
+  content: "\f09e";
+}
+.fa-hdd-o:before {
+  content: "\f0a0";
+}
+.fa-bullhorn:before {
+  content: "\f0a1";
+}
+.fa-bell:before {
+  content: "\f0f3";
+}
+.fa-certificate:before {
+  content: "\f0a3";
+}
+.fa-hand-o-right:before {
+  content: "\f0a4";
+}
+.fa-hand-o-left:before {
+  content: "\f0a5";
+}
+.fa-hand-o-up:before {
+  content: "\f0a6";
+}
+.fa-hand-o-down:before {
+  content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+  content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+  content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+  content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+  content: "\f0ab";
+}
+.fa-globe:before {
+  content: "\f0ac";
+}
+.fa-wrench:before {
+  content: "\f0ad";
+}
+.fa-tasks:before {
+  content: "\f0ae";
+}
+.fa-filter:before {
+  content: "\f0b0";
+}
+.fa-briefcase:before {
+  content: "\f0b1";
+}
+.fa-arrows-alt:before {
+  content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+  content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+  content: "\f0c1";
+}
+.fa-cloud:before {
+  content: "\f0c2";
+}
+.fa-flask:before {
+  content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+  content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+  content: "\f0c5";
+}
+.fa-paperclip:before {
+  content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+  content: "\f0c7";
+}
+.fa-square:before {
+  content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+  content: "\f0c9";
+}
+.fa-list-ul:before {
+  content: "\f0ca";
+}
+.fa-list-ol:before {
+  content: "\f0cb";
+}
+.fa-strikethrough:before {
+  content: "\f0cc";
+}
+.fa-underline:before {
+  content: "\f0cd";
+}
+.fa-table:before {
+  content: "\f0ce";
+}
+.fa-magic:before {
+  content: "\f0d0";
+}
+.fa-truck:before {
+  content: "\f0d1";
+}
+.fa-pinterest:before {
+  content: "\f0d2";
+}
+.fa-pinterest-square:before {
+  content: "\f0d3";
+}
+.fa-google-plus-square:before {
+  content: "\f0d4";
+}
+.fa-google-plus:before {
+  content: "\f0d5";
+}
+.fa-money:before {
+  content: "\f0d6";
+}
+.fa-caret-down:before {
+  content: "\f0d7";
+}
+.fa-caret-up:before {
+  content: "\f0d8";
+}
+.fa-caret-left:before {
+  content: "\f0d9";
+}
+.fa-caret-right:before {
+  content: "\f0da";
+}
+.fa-columns:before {
+  content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+  content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+  content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+  content: "\f0de";
+}
+.fa-envelope:before {
+  content: "\f0e0";
+}
+.fa-linkedin:before {
+  content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+  content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+  content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+  content: "\f0e4";
+}
+.fa-comment-o:before {
+  content: "\f0e5";
+}
+.fa-comments-o:before {
+  content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+  content: "\f0e7";
+}
+.fa-sitemap:before {
+  content: "\f0e8";
+}
+.fa-umbrella:before {
+  content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+  content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+  content: "\f0eb";
+}
+.fa-exchange:before {
+  content: "\f0ec";
+}
+.fa-cloud-download:before {
+  content: "\f0ed";
+}
+.fa-cloud-upload:before {
+  content: "\f0ee";
+}
+.fa-user-md:before {
+  content: "\f0f0";
+}
+.fa-stethoscope:before {
+  content: "\f0f1";
+}
+.fa-suitcase:before {
+  content: "\f0f2";
+}
+.fa-bell-o:before {
+  content: "\f0a2";
+}
+.fa-coffee:before {
+  content: "\f0f4";
+}
+.fa-cutlery:before {
+  content: "\f0f5";
+}
+.fa-file-text-o:before {
+  content: "\f0f6";
+}
+.fa-building-o:before {
+  content: "\f0f7";
+}
+.fa-hospital-o:before {
+  content: "\f0f8";
+}
+.fa-ambulance:before {
+  content: "\f0f9";
+}
+.fa-medkit:before {
+  content: "\f0fa";
+}
+.fa-fighter-jet:before {
+  content: "\f0fb";
+}
+.fa-beer:before {
+  content: "\f0fc";
+}
+.fa-h-square:before {
+  content: "\f0fd";
+}
+.fa-plus-square:before {
+  content: "\f0fe";
+}
+.fa-angle-double-left:before {
+  content: "\f100";
+}
+.fa-angle-double-right:before {
+  content: "\f101";
+}
+.fa-angle-double-up:before {
+  content: "\f102";
+}
+.fa-angle-double-down:before {
+  content: "\f103";
+}
+.fa-angle-left:before {
+  content: "\f104";
+}
+.fa-angle-right:before {
+  content: "\f105";
+}
+.fa-angle-up:before {
+  content: "\f106";
+}
+.fa-angle-down:before {
+  content: "\f107";
+}
+.fa-desktop:before {
+  content: "\f108";
+}
+.fa-laptop:before {
+  content: "\f109";
+}
+.fa-tablet:before {
+  content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+  content: "\f10b";
+}
+.fa-circle-o:before {
+  content: "\f10c";
+}
+.fa-quote-left:before {
+  content: "\f10d";
+}
+.fa-quote-right:before {
+  content: "\f10e";
+}
+.fa-spinner:before {
+  content: "\f110";
+}
+.fa-circle:before {
+  content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+  content: "\f112";
+}
+.fa-github-alt:before {
+  content: "\f113";
+}
+.fa-folder-o:before {
+  content: "\f114";
+}
+.fa-folder-open-o:before {
+  content: "\f115";
+}
+.fa-smile-o:before {
+  content: "\f118";
+}
+.fa-frown-o:before {
+  content: "\f119";
+}
+.fa-meh-o:before {
+  content: "\f11a";
+}
+.fa-gamepad:before {
+  content: "\f11b";
+}
+.fa-keyboard-o:before {
+  content: "\f11c";
+}
+.fa-flag-o:before {
+  content: "\f11d";
+}
+.fa-flag-checkered:before {
+  content: "\f11e";
+}
+.fa-terminal:before {
+  content: "\f120";
+}
+.fa-code:before {
+  content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+  content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+  content: "\f123";
+}
+.fa-location-arrow:before {
+  content: "\f124";
+}
+.fa-crop:before {
+  content: "\f125";
+}
+.fa-code-fork:before {
+  content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+  content: "\f127";
+}
+.fa-question:before {
+  content: "\f128";
+}
+.fa-info:before {
+  content: "\f129";
+}
+.fa-exclamation:before {
+  content: "\f12a";
+}
+.fa-superscript:before {
+  content: "\f12b";
+}
+.fa-subscript:before {
+  content: "\f12c";
+}
+.fa-eraser:before {
+  content: "\f12d";
+}
+.fa-puzzle-piece:before {
+  content: "\f12e";
+}
+.fa-microphone:before {
+  content: "\f130";
+}
+.fa-microphone-slash:before {
+  content: "\f131";
+}
+.fa-shield:before {
+  content: "\f132";
+}
+.fa-calendar-o:before {
+  content: "\f133";
+}
+.fa-fire-extinguisher:before {
+  content: "\f134";
+}
+.fa-rocket:before {
+  content: "\f135";
+}
+.fa-maxcdn:before {
+  content: "\f136";
+}
+.fa-chevron-circle-left:before {
+  content: "\f137";
+}
+.fa-chevron-circle-right:before {
+  content: "\f138";
+}
+.fa-chevron-circle-up:before {
+  content: "\f139";
+}
+.fa-chevron-circle-down:before {
+  content: "\f13a";
+}
+.fa-html5:before {
+  content: "\f13b";
+}
+.fa-css3:before {
+  content: "\f13c";
+}
+.fa-anchor:before {
+  content: "\f13d";
+}
+.fa-unlock-alt:before {
+  content: "\f13e";
+}
+.fa-bullseye:before {
+  content: "\f140";
+}
+.fa-ellipsis-h:before {
+  content: "\f141";
+}
+.fa-ellipsis-v:before {
+  content: "\f142";
+}
+.fa-rss-square:before {
+  content: "\f143";
+}
+.fa-play-circle:before {
+  content: "\f144";
+}
+.fa-ticket:before {
+  content: "\f145";
+}
+.fa-minus-square:before {
+  content: "\f146";
+}
+.fa-minus-square-o:before {
+  content: "\f147";
+}
+.fa-level-up:before {
+  content: "\f148";
+}
+.fa-level-down:before {
+  content: "\f149";
+}
+.fa-check-square:before {
+  content: "\f14a";
+}
+.fa-pencil-square:before {
+  content: "\f14b";
+}
+.fa-external-link-square:before {
+  content: "\f14c";
+}
+.fa-share-square:before {
+  content: "\f14d";
+}
+.fa-compass:before {
+  content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+  content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+  content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+  content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+  content: "\f153";
+}
+.fa-gbp:before {
+  content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+  content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+  content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+  content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+  content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+  content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+  content: "\f15a";
+}
+.fa-file:before {
+  content: "\f15b";
+}
+.fa-file-text:before {
+  content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+  content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+  content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+  content: "\f160";
+}
+.fa-sort-amount-desc:before {
+  content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+  content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+  content: "\f163";
+}
+.fa-thumbs-up:before {
+  content: "\f164";
+}
+.fa-thumbs-down:before {
+  content: "\f165";
+}
+.fa-youtube-square:before {
+  content: "\f166";
+}
+.fa-youtube:before {
+  content: "\f167";
+}
+.fa-xing:before {
+  content: "\f168";
+}
+.fa-xing-square:before {
+  content: "\f169";
+}
+.fa-youtube-play:before {
+  content: "\f16a";
+}
+.fa-dropbox:before {
+  content: "\f16b";
+}
+.fa-stack-overflow:before {
+  content: "\f16c";
+}
+.fa-instagram:before {
+  content: "\f16d";
+}
+.fa-flickr:before {
+  content: "\f16e";
+}
+.fa-adn:before {
+  content: "\f170";
+}
+.fa-bitbucket:before {
+  content: "\f171";
+}
+.fa-bitbucket-square:before {
+  content: "\f172";
+}
+.fa-tumblr:before {
+  content: "\f173";
+}
+.fa-tumblr-square:before {
+  content: "\f174";
+}
+.fa-long-arrow-down:before {
+  content: "\f175";
+}
+.fa-long-arrow-up:before {
+  content: "\f176";
+}
+.fa-long-arrow-left:before {
+  content: "\f177";
+}
+.fa-long-arrow-right:before {
+  content: "\f178";
+}
+.fa-apple:before {
+  content: "\f179";
+}
+.fa-windows:before {
+  content: "\f17a";
+}
+.fa-android:before {
+  content: "\f17b";
+}
+.fa-linux:before {
+  content: "\f17c";
+}
+.fa-dribbble:before {
+  content: "\f17d";
+}
+.fa-skype:before {
+  content: "\f17e";
+}
+.fa-foursquare:before {
+  content: "\f180";
+}
+.fa-trello:before {
+  content: "\f181";
+}
+.fa-female:before {
+  content: "\f182";
+}
+.fa-male:before {
+  content: "\f183";
+}
+.fa-gittip:before,
+.fa-gratipay:before {
+  content: "\f184";
+}
+.fa-sun-o:before {
+  content: "\f185";
+}
+.fa-moon-o:before {
+  content: "\f186";
+}
+.fa-archive:before {
+  content: "\f187";
+}
+.fa-bug:before {
+  content: "\f188";
+}
+.fa-vk:before {
+  content: "\f189";
+}
+.fa-weibo:before {
+  content: "\f18a";
+}
+.fa-renren:before {
+  content: "\f18b";
+}
+.fa-pagelines:before {
+  content: "\f18c";
+}
+.fa-stack-exchange:before {
+  content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+  content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+  content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+  content: "\f191";
+}
+.fa-dot-circle-o:before {
+  content: "\f192";
+}
+.fa-wheelchair:before {
+  content: "\f193";
+}
+.fa-vimeo-square:before {
+  content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+  content: "\f195";
+}
+.fa-plus-square-o:before {
+  content: "\f196";
+}
+.fa-space-shuttle:before {
+  content: "\f197";
+}
+.fa-slack:before {
+  content: "\f198";
+}
+.fa-envelope-square:before {
+  content: "\f199";
+}
+.fa-wordpress:before {
+  content: "\f19a";
+}
+.fa-openid:before {
+  content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+  content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+  content: "\f19d";
+}
+.fa-yahoo:before {
+  content: "\f19e";
+}
+.fa-google:before {
+  content: "\f1a0";
+}
+.fa-reddit:before {
+  content: "\f1a1";
+}
+.fa-reddit-square:before {
+  content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+  content: "\f1a3";
+}
+.fa-stumbleupon:before {
+  content: "\f1a4";
+}
+.fa-delicious:before {
+  content: "\f1a5";
+}
+.fa-digg:before {
+  content: "\f1a6";
+}
+.fa-pied-piper:before {
+  content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+  content: "\f1a8";
+}
+.fa-drupal:before {
+  content: "\f1a9";
+}
+.fa-joomla:before {
+  content: "\f1aa";
+}
+.fa-language:before {
+  content: "\f1ab";
+}
+.fa-fax:before {
+  content: "\f1ac";
+}
+.fa-building:before {
+  content: "\f1ad";
+}
+.fa-child:before {
+  content: "\f1ae";
+}
+.fa-paw:before {
+  content: "\f1b0";
+}
+.fa-spoon:before {
+  content: "\f1b1";
+}
+.fa-cube:before {
+  content: "\f1b2";
+}
+.fa-cubes:before {
+  content: "\f1b3";
+}
+.fa-behance:before {
+  content: "\f1b4";
+}
+.fa-behance-square:before {
+  content: "\f1b5";
+}
+.fa-steam:before {
+  content: "\f1b6";
+}
+.fa-steam-square:before {
+  content: "\f1b7";
+}
+.fa-recycle:before {
+  content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+  content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+  content: "\f1ba";
+}
+.fa-tree:before {
+  content: "\f1bb";
+}
+.fa-spotify:before {
+  content: "\f1bc";
+}
+.fa-deviantart:before {
+  content: "\f1bd";
+}
+.fa-soundcloud:before {
+  content: "\f1be";
+}
+.fa-database:before {
+  content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+  content: "\f1c1";
+}
+.fa-file-word-o:before {
+  content: "\f1c2";
+}
+.fa-file-excel-o:before {
+  content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+  content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+  content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+  content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+  content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+  content: "\f1c8";
+}
+.fa-file-code-o:before {
+  content: "\f1c9";
+}
+.fa-vine:before {
+  content: "\f1ca";
+}
+.fa-codepen:before {
+  content: "\f1cb";
+}
+.fa-jsfiddle:before {
+  content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-buoy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+  content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+  content: "\f1ce";
+}
+.fa-ra:before,
+.fa-rebel:before {
+  content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+  content: "\f1d1";
+}
+.fa-git-square:before {
+  content: "\f1d2";
+}
+.fa-git:before {
+  content: "\f1d3";
+}
+.fa-hacker-news:before {
+  content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+  content: "\f1d5";
+}
+.fa-qq:before {
+  content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+  content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+  content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+  content: "\f1d9";
+}
+.fa-history:before {
+  content: "\f1da";
+}
+.fa-genderless:before,
+.fa-circle-thin:before {
+  content: "\f1db";
+}
+.fa-header:before {
+  content: "\f1dc";
+}
+.fa-paragraph:before {
+  content: "\f1dd";
+}
+.fa-sliders:before {
+  content: "\f1de";
+}
+.fa-share-alt:before {
+  content: "\f1e0";
+}
+.fa-share-alt-square:before {
+  content: "\f1e1";
+}
+.fa-bomb:before {
+  content: "\f1e2";
+}
+.fa-soccer-ball-o:before,
+.fa-futbol-o:before {
+  content: "\f1e3";
+}
+.fa-tty:before {
+  content: "\f1e4";
+}
+.fa-binoculars:before {
+  content: "\f1e5";
+}
+.fa-plug:before {
+  content: "\f1e6";
+}
+.fa-slideshare:before {
+  content: "\f1e7";
+}
+.fa-twitch:before {
+  content: "\f1e8";
+}
+.fa-yelp:before {
+  content: "\f1e9";
+}
+.fa-newspaper-o:before {
+  content: "\f1ea";
+}
+.fa-wifi:before {
+  content: "\f1eb";
+}
+.fa-calculator:before {
+  content: "\f1ec";
+}
+.fa-paypal:before {
+  content: "\f1ed";
+}
+.fa-google-wallet:before {
+  content: "\f1ee";
+}
+.fa-cc-visa:before {
+  content: "\f1f0";
+}
+.fa-cc-mastercard:before {
+  content: "\f1f1";
+}
+.fa-cc-discover:before {
+  content: "\f1f2";
+}
+.fa-cc-amex:before {
+  content: "\f1f3";
+}
+.fa-cc-paypal:before {
+  content: "\f1f4";
+}
+.fa-cc-stripe:before {
+  content: "\f1f5";
+}
+.fa-bell-slash:before {
+  content: "\f1f6";
+}
+.fa-bell-slash-o:before {
+  content: "\f1f7";
+}
+.fa-trash:before {
+  content: "\f1f8";
+}
+.fa-copyright:before {
+  content: "\f1f9";
+}
+.fa-at:before {
+  content: "\f1fa";
+}
+.fa-eyedropper:before {
+  content: "\f1fb";
+}
+.fa-paint-brush:before {
+  content: "\f1fc";
+}
+.fa-birthday-cake:before {
+  content: "\f1fd";
+}
+.fa-area-chart:before {
+  content: "\f1fe";
+}
+.fa-pie-chart:before {
+  content: "\f200";
+}
+.fa-line-chart:before {
+  content: "\f201";
+}
+.fa-lastfm:before {
+  content: "\f202";
+}
+.fa-lastfm-square:before {
+  content: "\f203";
+}
+.fa-toggle-off:before {
+  content: "\f204";
+}
+.fa-toggle-on:before {
+  content: "\f205";
+}
+.fa-bicycle:before {
+  content: "\f206";
+}
+.fa-bus:before {
+  content: "\f207";
+}
+.fa-ioxhost:before {
+  content: "\f208";
+}
+.fa-angellist:before {
+  content: "\f209";
+}
+.fa-cc:before {
+  content: "\f20a";
+}
+.fa-shekel:before,
+.fa-sheqel:before,
+.fa-ils:before {
+  content: "\f20b";
+}
+.fa-meanpath:before {
+  content: "\f20c";
+}
+.fa-buysellads:before {
+  content: "\f20d";
+}
+.fa-connectdevelop:before {
+  content: "\f20e";
+}
+.fa-dashcube:before {
+  content: "\f210";
+}
+.fa-forumbee:before {
+  content: "\f211";
+}
+.fa-leanpub:before {
+  content: "\f212";
+}
+.fa-sellsy:before {
+  content: "\f213";
+}
+.fa-shirtsinbulk:before {
+  content: "\f214";
+}
+.fa-simplybuilt:before {
+  content: "\f215";
+}
+.fa-skyatlas:before {
+  content: "\f216";
+}
+.fa-cart-plus:before {
+  content: "\f217";
+}
+.fa-cart-arrow-down:before {
+  content: "\f218";
+}
+.fa-diamond:before {
+  content: "\f219";
+}
+.fa-ship:before {
+  content: "\f21a";
+}
+.fa-user-secret:before {
+  content: "\f21b";
+}
+.fa-motorcycle:before {
+  content: "\f21c";
+}
+.fa-street-view:before {
+  content: "\f21d";
+}
+.fa-heartbeat:before {
+  content: "\f21e";
+}
+.fa-venus:before {
+  content: "\f221";
+}
+.fa-mars:before {
+  content: "\f222";
+}
+.fa-mercury:before {
+  content: "\f223";
+}
+.fa-transgender:before {
+  content: "\f224";
+}
+.fa-transgender-alt:before {
+  content: "\f225";
+}
+.fa-venus-double:before {
+  content: "\f226";
+}
+.fa-mars-double:before {
+  content: "\f227";
+}
+.fa-venus-mars:before {
+  content: "\f228";
+}
+.fa-mars-stroke:before {
+  content: "\f229";
+}
+.fa-mars-stroke-v:before {
+  content: "\f22a";
+}
+.fa-mars-stroke-h:before {
+  content: "\f22b";
+}
+.fa-neuter:before {
+  content: "\f22c";
+}
+.fa-facebook-official:before {
+  content: "\f230";
+}
+.fa-pinterest-p:before {
+  content: "\f231";
+}
+.fa-whatsapp:before {
+  content: "\f232";
+}
+.fa-server:before {
+  content: "\f233";
+}
+.fa-user-plus:before {
+  content: "\f234";
+}
+.fa-user-times:before {
+  content: "\f235";
+}
+.fa-hotel:before,
+.fa-bed:before {
+  content: "\f236";
+}
+.fa-viacoin:before {
+  content: "\f237";
+}
+.fa-train:before {
+  content: "\f238";
+}
+.fa-subway:before {
+  content: "\f239";
+}
+.fa-medium:before {
+  content: "\f23a";
+}
diff --git a/tools/infra-dashboard/css/fullcalendar.css b/tools/infra-dashboard/css/fullcalendar.css
new file mode 100644 (file)
index 0000000..89684cb
--- /dev/null
@@ -0,0 +1,1260 @@
+/*!
+ * FullCalendar v2.7.2 Stylesheet
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+
+
+.fc {
+       direction: ltr;
+       text-align: left;
+}
+
+.fc-rtl {
+       text-align: right;
+}
+
+body .fc { /* extra precedence to overcome jqui */
+       font-size: 1em;
+}
+
+
+/* Colors
+--------------------------------------------------------------------------------------------------*/
+
+.fc-unthemed th,
+.fc-unthemed td,
+.fc-unthemed thead,
+.fc-unthemed tbody,
+.fc-unthemed .fc-divider,
+.fc-unthemed .fc-row,
+.fc-unthemed .fc-content, /* for gutter border */
+.fc-unthemed .fc-popover {
+       border-color: #ddd;
+}
+
+.fc-unthemed .fc-popover {
+       background-color: #fff;
+}
+
+.fc-unthemed .fc-divider,
+.fc-unthemed .fc-popover .fc-header {
+       background: #eee;
+}
+
+.fc-unthemed .fc-popover .fc-header .fc-close {
+       color: #666;
+}
+
+.fc-unthemed .fc-today {
+       background: #fcf8e3;
+}
+
+.fc-highlight { /* when user is selecting cells */
+       background: #bce8f1;
+       opacity: .3;
+       filter: alpha(opacity=30); /* for IE */
+}
+
+.fc-bgevent { /* default look for background events */
+       background: rgb(143, 223, 130);
+       opacity: .3;
+       filter: alpha(opacity=30); /* for IE */
+}
+
+.fc-nonbusiness { /* default look for non-business-hours areas */
+       /* will inherit .fc-bgevent's styles */
+       background: #d7d7d7;
+}
+
+
+/* Icons (inline elements with styled text that mock arrow icons)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-icon {
+       display: inline-block;
+       height: 1em;
+       line-height: 1em;
+       font-size: 1em;
+       text-align: center;
+       overflow: hidden;
+       font-family: "Courier New", Courier, monospace;
+
+       /* don't allow browser text-selection */
+       -webkit-touch-callout: none;
+       -webkit-user-select: none;
+       -khtml-user-select: none;
+       -moz-user-select: none;
+       -ms-user-select: none;
+       user-select: none;
+       }
+
+/*
+Acceptable font-family overrides for individual icons:
+       "Arial", sans-serif
+       "Times New Roman", serif
+
+NOTE: use percentage font sizes or else old IE chokes
+*/
+
+.fc-icon:after {
+       position: relative;
+}
+
+.fc-icon-left-single-arrow:after {
+       content: "\02039";
+       font-weight: bold;
+       font-size: 200%;
+       top: -7%;
+}
+
+.fc-icon-right-single-arrow:after {
+       content: "\0203A";
+       font-weight: bold;
+       font-size: 200%;
+       top: -7%;
+}
+
+.fc-icon-left-double-arrow:after {
+       content: "\000AB";
+       font-size: 160%;
+       top: -7%;
+}
+
+.fc-icon-right-double-arrow:after {
+       content: "\000BB";
+       font-size: 160%;
+       top: -7%;
+}
+
+.fc-icon-left-triangle:after {
+       content: "\25C4";
+       font-size: 125%;
+       top: 3%;
+}
+
+.fc-icon-right-triangle:after {
+       content: "\25BA";
+       font-size: 125%;
+       top: 3%;
+}
+
+.fc-icon-down-triangle:after {
+       content: "\25BC";
+       font-size: 125%;
+       top: 2%;
+}
+
+.fc-icon-x:after {
+       content: "\000D7";
+       font-size: 200%;
+       top: 6%;
+}
+
+
+/* Buttons (styled <button> tags, normalized to work cross-browser)
+--------------------------------------------------------------------------------------------------*/
+
+.fc button {
+       /* force height to include the border and padding */
+       -moz-box-sizing: border-box;
+       -webkit-box-sizing: border-box;
+       box-sizing: border-box;
+
+       /* dimensions */
+       margin: 0;
+       height: 2.1em;
+       padding: 0 .6em;
+
+       /* text & cursor */
+       font-size: 1em; /* normalize */
+       white-space: nowrap;
+       cursor: pointer;
+}
+
+/* Firefox has an annoying inner border */
+.fc button::-moz-focus-inner { margin: 0; padding: 0; }
+       
+.fc-state-default { /* non-theme */
+       border: 1px solid;
+}
+
+.fc-state-default.fc-corner-left { /* non-theme */
+       border-top-left-radius: 4px;
+       border-bottom-left-radius: 4px;
+}
+
+.fc-state-default.fc-corner-right { /* non-theme */
+       border-top-right-radius: 4px;
+       border-bottom-right-radius: 4px;
+}
+
+/* icons in buttons */
+
+.fc button .fc-icon { /* non-theme */
+       position: relative;
+       top: -0.05em; /* seems to be a good adjustment across browsers */
+       margin: 0 .2em;
+       vertical-align: middle;
+}
+       
+/*
+  button states
+  borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/)
+*/
+
+.fc-state-default {
+       background-color: #f5f5f5;
+       background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+       background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+       background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+       background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+       background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+       background-repeat: repeat-x;
+       border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+       border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+       color: #333;
+       text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+       box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.fc-state-hover,
+.fc-state-down,
+.fc-state-active,
+.fc-state-disabled {
+       color: #333333;
+       background-color: #e6e6e6;
+}
+
+.fc-state-hover {
+       color: #333333;
+       text-decoration: none;
+       background-position: 0 -15px;
+       -webkit-transition: background-position 0.1s linear;
+          -moz-transition: background-position 0.1s linear;
+            -o-transition: background-position 0.1s linear;
+               transition: background-position 0.1s linear;
+}
+
+.fc-state-down,
+.fc-state-active {
+       background-color: #cccccc;
+       background-image: none;
+       box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.fc-state-disabled {
+       cursor: default;
+       background-image: none;
+       opacity: 0.65;
+       filter: alpha(opacity=65);
+       box-shadow: none;
+}
+
+
+/* Buttons Groups
+--------------------------------------------------------------------------------------------------*/
+
+.fc-button-group {
+       display: inline-block;
+}
+
+/*
+every button that is not first in a button group should scootch over one pixel and cover the
+previous button's border...
+*/
+
+.fc .fc-button-group > * { /* extra precedence b/c buttons have margin set to zero */
+       float: left;
+       margin: 0 0 0 -1px;
+}
+
+.fc .fc-button-group > :first-child { /* same */
+       margin-left: 0;
+}
+
+
+/* Popover
+--------------------------------------------------------------------------------------------------*/
+
+.fc-popover {
+       position: absolute;
+       box-shadow: 0 2px 6px rgba(0,0,0,.15);
+}
+
+.fc-popover .fc-header { /* TODO: be more consistent with fc-head/fc-body */
+       padding: 2px 4px;
+}
+
+.fc-popover .fc-header .fc-title {
+       margin: 0 2px;
+}
+
+.fc-popover .fc-header .fc-close {
+       cursor: pointer;
+}
+
+.fc-ltr .fc-popover .fc-header .fc-title,
+.fc-rtl .fc-popover .fc-header .fc-close {
+       float: left;
+}
+
+.fc-rtl .fc-popover .fc-header .fc-title,
+.fc-ltr .fc-popover .fc-header .fc-close {
+       float: right;
+}
+
+/* unthemed */
+
+.fc-unthemed .fc-popover {
+       border-width: 1px;
+       border-style: solid;
+}
+
+.fc-unthemed .fc-popover .fc-header .fc-close {
+       font-size: .9em;
+       margin-top: 2px;
+}
+
+/* jqui themed */
+
+.fc-popover > .ui-widget-header + .ui-widget-content {
+       border-top: 0; /* where they meet, let the header have the border */
+}
+
+
+/* Misc Reusable Components
+--------------------------------------------------------------------------------------------------*/
+
+.fc-divider {
+       border-style: solid;
+       border-width: 1px;
+}
+
+hr.fc-divider {
+       height: 0;
+       margin: 0;
+       padding: 0 0 2px; /* height is unreliable across browsers, so use padding */
+       border-width: 1px 0;
+}
+
+.fc-clear {
+       clear: both;
+}
+
+.fc-bg,
+.fc-bgevent-skeleton,
+.fc-highlight-skeleton,
+.fc-helper-skeleton {
+       /* these element should always cling to top-left/right corners */
+       position: absolute;
+       top: 0;
+       left: 0;
+       right: 0;
+}
+
+.fc-bg {
+       bottom: 0; /* strech bg to bottom edge */
+}
+
+.fc-bg table {
+       height: 100%; /* strech bg to bottom edge */
+}
+
+
+/* Tables
+--------------------------------------------------------------------------------------------------*/
+
+.fc table {
+       width: 100%;
+       table-layout: fixed;
+       border-collapse: collapse;
+       border-spacing: 0;
+       font-size: 1em; /* normalize cross-browser */
+}
+
+.fc th {
+       text-align: center;
+}
+
+.fc th,
+.fc td {
+       border-style: solid;
+       border-width: 1px;
+       padding: 0;
+       vertical-align: top;
+}
+
+.fc td.fc-today {
+       border-style: double; /* overcome neighboring borders */
+}
+
+
+/* Fake Table Rows
+--------------------------------------------------------------------------------------------------*/
+
+.fc .fc-row { /* extra precedence to overcome themes w/ .ui-widget-content forcing a 1px border */
+       /* no visible border by default. but make available if need be (scrollbar width compensation) */
+       border-style: solid;
+       border-width: 0;
+}
+
+.fc-row table {
+       /* don't put left/right border on anything within a fake row.
+          the outer tbody will worry about this */
+       border-left: 0 hidden transparent;
+       border-right: 0 hidden transparent;
+
+       /* no bottom borders on rows */
+       border-bottom: 0 hidden transparent; 
+}
+
+.fc-row:first-child table {
+       border-top: 0 hidden transparent; /* no top border on first row */
+}
+
+
+/* Day Row (used within the header and the DayGrid)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-row {
+       position: relative;
+}
+
+.fc-row .fc-bg {
+       z-index: 1;
+}
+
+/* highlighting cells & background event skeleton */
+
+.fc-row .fc-bgevent-skeleton,
+.fc-row .fc-highlight-skeleton {
+       bottom: 0; /* stretch skeleton to bottom of row */
+}
+
+.fc-row .fc-bgevent-skeleton table,
+.fc-row .fc-highlight-skeleton table {
+       height: 100%; /* stretch skeleton to bottom of row */
+}
+
+.fc-row .fc-highlight-skeleton td,
+.fc-row .fc-bgevent-skeleton td {
+       border-color: transparent;
+}
+
+.fc-row .fc-bgevent-skeleton {
+       z-index: 2;
+
+}
+
+.fc-row .fc-highlight-skeleton {
+       z-index: 3;
+}
+
+/*
+row content (which contains day/week numbers and events) as well as "helper" (which contains
+temporary rendered events).
+*/
+
+.fc-row .fc-content-skeleton {
+       position: relative;
+       z-index: 4;
+       padding-bottom: 2px; /* matches the space above the events */
+}
+
+.fc-row .fc-helper-skeleton {
+       z-index: 5;
+}
+
+.fc-row .fc-content-skeleton td,
+.fc-row .fc-helper-skeleton td {
+       /* see-through to the background below */
+       background: none; /* in case <td>s are globally styled */
+       border-color: transparent;
+
+       /* don't put a border between events and/or the day number */
+       border-bottom: 0;
+}
+
+.fc-row .fc-content-skeleton tbody td, /* cells with events inside (so NOT the day number cell) */
+.fc-row .fc-helper-skeleton tbody td {
+       /* don't put a border between event cells */
+       border-top: 0;
+}
+
+
+/* Scrolling Container
+--------------------------------------------------------------------------------------------------*/
+
+.fc-scroller {
+       -webkit-overflow-scrolling: touch;
+}
+
+/* TODO: move to agenda/basic */
+.fc-scroller > .fc-day-grid,
+.fc-scroller > .fc-time-grid {
+       position: relative; /* re-scope all positions */
+       width: 100%; /* hack to force re-sizing this inner element when scrollbars appear/disappear */
+}
+
+
+/* Global Event Styles
+--------------------------------------------------------------------------------------------------*/
+
+.fc-event {
+       position: relative; /* for resize handle and other inner positioning */
+       display: block; /* make the <a> tag block */
+       font-size: .85em;
+       line-height: 1.3;
+       border-radius: 3px;
+       border: 1px solid #3a87ad; /* default BORDER color */
+       background-color: #3a87ad; /* default BACKGROUND color */
+       font-weight: normal; /* undo jqui's ui-widget-header bold */
+}
+
+/* overpower some of bootstrap's and jqui's styles on <a> tags */
+.fc-event,
+.fc-event:hover,
+.ui-widget .fc-event {
+       color: #fff; /* default TEXT color */
+       text-decoration: none; /* if <a> has an href */
+}
+
+.fc-event[href],
+.fc-event.fc-draggable {
+       cursor: pointer; /* give events with links and draggable events a hand mouse pointer */
+}
+
+.fc-not-allowed, /* causes a "warning" cursor. applied on body */
+.fc-not-allowed .fc-event { /* to override an event's custom cursor */
+       cursor: not-allowed;
+}
+
+.fc-event .fc-bg { /* the generic .fc-bg already does position */
+       z-index: 1;
+       background: #fff;
+       opacity: .25;
+       filter: alpha(opacity=25); /* for IE */
+}
+
+.fc-event .fc-content {
+       position: relative;
+       z-index: 2;
+}
+
+/* resizer (cursor AND touch devices) */
+
+.fc-event .fc-resizer {
+       position: absolute;
+       z-index: 4;
+}
+
+/* resizer (touch devices) */
+
+.fc-event .fc-resizer {
+       display: none;
+}
+
+.fc-event.fc-allow-mouse-resize .fc-resizer,
+.fc-event.fc-selected .fc-resizer {
+       /* only show when hovering or selected (with touch) */
+       display: block;
+}
+
+/* hit area */
+
+.fc-event.fc-selected .fc-resizer:before {
+       /* 40x40 touch area */
+       content: "";
+       position: absolute;
+       z-index: 9999; /* user of this util can scope within a lower z-index */
+       top: 50%;
+       left: 50%;
+       width: 40px;
+       height: 40px;
+       margin-left: -20px;
+       margin-top: -20px;
+}
+
+
+/* Event Selection (only for touch devices)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-event.fc-selected {
+       z-index: 9999 !important; /* overcomes inline z-index */
+       box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+}
+
+.fc-event.fc-selected.fc-dragging {
+       box-shadow: 0 2px 7px rgba(0, 0, 0, 0.3);
+}
+
+
+/* Horizontal Events
+--------------------------------------------------------------------------------------------------*/
+
+/* bigger touch area when selected */
+.fc-h-event.fc-selected:before {
+       content: "";
+       position: absolute;
+       z-index: 3; /* below resizers */
+       top: -10px;
+       bottom: -10px;
+       left: 0;
+       right: 0;
+}
+
+/* events that are continuing to/from another week. kill rounded corners and butt up against edge */
+
+.fc-ltr .fc-h-event.fc-not-start,
+.fc-rtl .fc-h-event.fc-not-end {
+       margin-left: 0;
+       border-left-width: 0;
+       padding-left: 1px; /* replace the border with padding */
+       border-top-left-radius: 0;
+       border-bottom-left-radius: 0;
+}
+
+.fc-ltr .fc-h-event.fc-not-end,
+.fc-rtl .fc-h-event.fc-not-start {
+       margin-right: 0;
+       border-right-width: 0;
+       padding-right: 1px; /* replace the border with padding */
+       border-top-right-radius: 0;
+       border-bottom-right-radius: 0;
+}
+
+/* resizer (cursor AND touch devices) */
+
+/* left resizer  */
+.fc-ltr .fc-h-event .fc-start-resizer,
+.fc-rtl .fc-h-event .fc-end-resizer {
+       cursor: w-resize;
+       left: -1px; /* overcome border */
+}
+
+/* right resizer */
+.fc-ltr .fc-h-event .fc-end-resizer,
+.fc-rtl .fc-h-event .fc-start-resizer {
+       cursor: e-resize;
+       right: -1px; /* overcome border */
+}
+
+/* resizer (mouse devices) */
+
+.fc-h-event.fc-allow-mouse-resize .fc-resizer {
+       width: 7px;
+       top: -1px; /* overcome top border */
+       bottom: -1px; /* overcome bottom border */
+}
+
+/* resizer (touch devices) */
+
+.fc-h-event.fc-selected .fc-resizer {
+       /* 8x8 little dot */
+       border-radius: 4px;
+       border-width: 1px;
+       width: 6px;
+       height: 6px;
+       border-style: solid;
+       border-color: inherit;
+       background: #fff;
+       /* vertically center */
+       top: 50%;
+       margin-top: -4px;
+}
+
+/* left resizer  */
+.fc-ltr .fc-h-event.fc-selected .fc-start-resizer,
+.fc-rtl .fc-h-event.fc-selected .fc-end-resizer {
+       margin-left: -4px; /* centers the 8x8 dot on the left edge */
+}
+
+/* right resizer */
+.fc-ltr .fc-h-event.fc-selected .fc-end-resizer,
+.fc-rtl .fc-h-event.fc-selected .fc-start-resizer {
+       margin-right: -4px; /* centers the 8x8 dot on the right edge */
+}
+
+
+/* DayGrid events
+----------------------------------------------------------------------------------------------------
+We use the full "fc-day-grid-event" class instead of using descendants because the event won't
+be a descendant of the grid when it is being dragged.
+*/
+
+.fc-day-grid-event {
+       margin: 1px 2px 0; /* spacing between events and edges */
+       padding: 0 1px;
+}
+
+.fc-day-grid-event.fc-selected:after {
+       content: "";
+       position: absolute;
+       z-index: 1; /* same z-index as fc-bg, behind text */
+       /* overcome the borders */
+       top: -1px;
+       right: -1px;
+       bottom: -1px;
+       left: -1px;
+       /* darkening effect */
+       background: #000;
+       opacity: .25;
+       filter: alpha(opacity=25); /* for IE */
+}
+
+.fc-day-grid-event .fc-content { /* force events to be one-line tall */
+       white-space: nowrap;
+       overflow: hidden;
+}
+
+.fc-day-grid-event .fc-time {
+       font-weight: bold;
+}
+
+/* resizer (cursor devices) */
+
+/* left resizer  */
+.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer,
+.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer {
+       margin-left: -2px; /* to the day cell's edge */
+}
+
+/* right resizer */
+.fc-ltr .fc-day-grid-event.fc-allow-mouse-resize .fc-end-resizer,
+.fc-rtl .fc-day-grid-event.fc-allow-mouse-resize .fc-start-resizer {
+       margin-right: -2px; /* to the day cell's edge */
+}
+
+
+/* Event Limiting
+--------------------------------------------------------------------------------------------------*/
+
+/* "more" link that represents hidden events */
+
+a.fc-more {
+       margin: 1px 3px;
+       font-size: .85em;
+       cursor: pointer;
+       text-decoration: none;
+}
+
+a.fc-more:hover {
+       text-decoration: underline;
+}
+
+.fc-limited { /* rows and cells that are hidden because of a "more" link */
+       display: none;
+}
+
+/* popover that appears when "more" link is clicked */
+
+.fc-day-grid .fc-row {
+       z-index: 1; /* make the "more" popover one higher than this */
+}
+
+.fc-more-popover {
+       z-index: 2;
+       width: 220px;
+}
+
+.fc-more-popover .fc-event-container {
+       padding: 10px;
+}
+
+
+/* Now Indicator
+--------------------------------------------------------------------------------------------------*/
+
+.fc-now-indicator {
+       position: absolute;
+       border: 0 solid red;
+}
+
+
+/* Utilities
+--------------------------------------------------------------------------------------------------*/
+
+.fc-unselectable {
+       -webkit-user-select: none;
+        -khtml-user-select: none;
+          -moz-user-select: none;
+           -ms-user-select: none;
+               user-select: none;
+       -webkit-touch-callout: none;
+       -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+/* Toolbar
+--------------------------------------------------------------------------------------------------*/
+
+.fc-toolbar {
+       text-align: center;
+       margin-bottom: 1em;
+}
+
+.fc-toolbar .fc-left {
+       float: left;
+}
+
+.fc-toolbar .fc-right {
+       float: right;
+}
+
+.fc-toolbar .fc-center {
+       display: inline-block;
+}
+
+/* the things within each left/right/center section */
+.fc .fc-toolbar > * > * { /* extra precedence to override button border margins */
+       float: left;
+       margin-left: .75em;
+}
+
+/* the first thing within each left/center/right section */
+.fc .fc-toolbar > * > :first-child { /* extra precedence to override button border margins */
+       margin-left: 0;
+}
+       
+/* title text */
+
+.fc-toolbar h2 {
+       margin: 0;
+}
+
+/* button layering (for border precedence) */
+
+.fc-toolbar button {
+       position: relative;
+}
+
+.fc-toolbar .fc-state-hover,
+.fc-toolbar .ui-state-hover {
+       z-index: 2;
+}
+       
+.fc-toolbar .fc-state-down {
+       z-index: 3;
+}
+
+.fc-toolbar .fc-state-active,
+.fc-toolbar .ui-state-active {
+       z-index: 4;
+}
+
+.fc-toolbar button:focus {
+       z-index: 5;
+}
+
+
+/* View Structure
+--------------------------------------------------------------------------------------------------*/
+
+/* undo twitter bootstrap's box-sizing rules. normalizes positioning techniques */
+/* don't do this for the toolbar because we'll want bootstrap to style those buttons as some pt */
+.fc-view-container *,
+.fc-view-container *:before,
+.fc-view-container *:after {
+       -webkit-box-sizing: content-box;
+          -moz-box-sizing: content-box;
+               box-sizing: content-box;
+}
+
+.fc-view, /* scope positioning and z-index's for everything within the view */
+.fc-view > table { /* so dragged elements can be above the view's main element */
+       position: relative;
+       z-index: 1;
+}
+
+/* BasicView
+--------------------------------------------------------------------------------------------------*/
+
+/* day row structure */
+
+.fc-basicWeek-view .fc-content-skeleton,
+.fc-basicDay-view .fc-content-skeleton {
+       /* we are sure there are no day numbers in these views, so... */
+       padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
+       padding-bottom: 1em; /* ensure a space at bottom of cell for user selecting/clicking */
+}
+
+.fc-basic-view .fc-body .fc-row {
+       min-height: 4em; /* ensure that all rows are at least this tall */
+}
+
+/* a "rigid" row will take up a constant amount of height because content-skeleton is absolute */
+
+.fc-row.fc-rigid {
+       overflow: hidden;
+}
+
+.fc-row.fc-rigid .fc-content-skeleton {
+       position: absolute;
+       top: 0;
+       left: 0;
+       right: 0;
+}
+
+/* week and day number styling */
+
+.fc-basic-view .fc-week-number,
+.fc-basic-view .fc-day-number {
+       padding: 0 2px;
+}
+
+.fc-basic-view td.fc-week-number span,
+.fc-basic-view td.fc-day-number {
+       padding-top: 2px;
+       padding-bottom: 2px;
+}
+
+.fc-basic-view .fc-week-number {
+       text-align: center;
+}
+
+.fc-basic-view .fc-week-number span {
+       /* work around the way we do column resizing and ensure a minimum width */
+       display: inline-block;
+       min-width: 1.25em;
+}
+
+.fc-ltr .fc-basic-view .fc-day-number {
+       text-align: right;
+}
+
+.fc-rtl .fc-basic-view .fc-day-number {
+       text-align: left;
+}
+
+.fc-day-number.fc-other-month {
+       opacity: 0.3;
+       filter: alpha(opacity=30); /* for IE */
+       /* opacity with small font can sometimes look too faded
+          might want to set the 'color' property instead
+          making day-numbers bold also fixes the problem */
+}
+
+/* AgendaView all-day area
+--------------------------------------------------------------------------------------------------*/
+
+.fc-agenda-view .fc-day-grid {
+       position: relative;
+       z-index: 2; /* so the "more.." popover will be over the time grid */
+}
+
+.fc-agenda-view .fc-day-grid .fc-row {
+       min-height: 3em; /* all-day section will never get shorter than this */
+}
+
+.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton {
+       padding-top: 1px; /* add a pixel to make sure there are 2px padding above events */
+       padding-bottom: 1em; /* give space underneath events for clicking/selecting days */
+}
+
+
+/* TimeGrid axis running down the side (for both the all-day area and the slot area)
+--------------------------------------------------------------------------------------------------*/
+
+.fc .fc-axis { /* .fc to overcome default cell styles */
+       vertical-align: middle;
+       padding: 0 4px;
+       white-space: nowrap;
+}
+
+.fc-ltr .fc-axis {
+       text-align: right;
+}
+
+.fc-rtl .fc-axis {
+       text-align: left;
+}
+
+.ui-widget td.fc-axis {
+       font-weight: normal; /* overcome jqui theme making it bold */
+}
+
+
+/* TimeGrid Structure
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid-container, /* so scroll container's z-index is below all-day */
+.fc-time-grid { /* so slats/bg/content/etc positions get scoped within here */
+       position: relative;
+       z-index: 1;
+}
+
+.fc-time-grid {
+       min-height: 100%; /* so if height setting is 'auto', .fc-bg stretches to fill height */
+}
+
+.fc-time-grid table { /* don't put outer borders on slats/bg/content/etc */
+       border: 0 hidden transparent;
+}
+
+.fc-time-grid > .fc-bg {
+       z-index: 1;
+}
+
+.fc-time-grid .fc-slats,
+.fc-time-grid > hr { /* the <hr> AgendaView injects when grid is shorter than scroller */
+       position: relative;
+       z-index: 2;
+}
+
+.fc-time-grid .fc-content-col {
+       position: relative; /* because now-indicator lives directly inside */
+}
+
+.fc-time-grid .fc-content-skeleton {
+       position: absolute;
+       z-index: 3;
+       top: 0;
+       left: 0;
+       right: 0;
+}
+
+/* divs within a cell within the fc-content-skeleton */
+
+.fc-time-grid .fc-business-container {
+       position: relative;
+       z-index: 1;
+}
+
+.fc-time-grid .fc-bgevent-container {
+       position: relative;
+       z-index: 2;
+}
+
+.fc-time-grid .fc-highlight-container {
+       position: relative;
+       z-index: 3;
+}
+
+.fc-time-grid .fc-event-container {
+       position: relative;
+       z-index: 4;
+}
+
+.fc-time-grid .fc-now-indicator-line {
+       z-index: 5;
+}
+
+.fc-time-grid .fc-helper-container { /* also is fc-event-container */
+       position: relative;
+       z-index: 6;
+}
+
+
+/* TimeGrid Slats (lines that run horizontally)
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid .fc-slats td {
+       height: 1.5em;
+       border-bottom: 0; /* each cell is responsible for its top border */
+}
+
+.fc-time-grid .fc-slats .fc-minor td {
+       border-top-style: dotted;
+}
+
+.fc-time-grid .fc-slats .ui-widget-content { /* for jqui theme */
+       background: none; /* see through to fc-bg */
+}
+
+
+/* TimeGrid Highlighting Slots
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid .fc-highlight-container { /* a div within a cell within the fc-highlight-skeleton */
+       position: relative; /* scopes the left/right of the fc-highlight to be in the column */
+}
+
+.fc-time-grid .fc-highlight {
+       position: absolute;
+       left: 0;
+       right: 0;
+       /* top and bottom will be in by JS */
+}
+
+
+/* TimeGrid Event Containment
+--------------------------------------------------------------------------------------------------*/
+
+.fc-ltr .fc-time-grid .fc-event-container { /* space on the sides of events for LTR (default) */
+       margin: 0 2.5% 0 2px;
+}
+
+.fc-rtl .fc-time-grid .fc-event-container { /* space on the sides of events for RTL */
+       margin: 0 2px 0 2.5%;
+}
+
+.fc-time-grid .fc-event,
+.fc-time-grid .fc-bgevent {
+       position: absolute;
+       z-index: 1; /* scope inner z-index's */
+}
+
+.fc-time-grid .fc-bgevent {
+       /* background events always span full width */
+       left: 0;
+       right: 0;
+}
+
+
+/* Generic Vertical Event
+--------------------------------------------------------------------------------------------------*/
+
+.fc-v-event.fc-not-start { /* events that are continuing from another day */
+       /* replace space made by the top border with padding */
+       border-top-width: 0;
+       padding-top: 1px;
+
+       /* remove top rounded corners */
+       border-top-left-radius: 0;
+       border-top-right-radius: 0;
+}
+
+.fc-v-event.fc-not-end {
+       /* replace space made by the top border with padding */
+       border-bottom-width: 0;
+       padding-bottom: 1px;
+
+       /* remove bottom rounded corners */
+       border-bottom-left-radius: 0;
+       border-bottom-right-radius: 0;
+}
+
+
+/* TimeGrid Event Styling
+----------------------------------------------------------------------------------------------------
+We use the full "fc-time-grid-event" class instead of using descendants because the event won't
+be a descendant of the grid when it is being dragged.
+*/
+
+.fc-time-grid-event {
+       overflow: hidden; /* don't let the bg flow over rounded corners */
+}
+
+.fc-time-grid-event.fc-selected {
+       /* need to allow touch resizers to extend outside event's bounding box */
+       /* common fc-selected styles hide the fc-bg, so don't need this anyway */
+       overflow: visible;
+}
+
+.fc-time-grid-event.fc-selected .fc-bg {
+       display: none; /* hide semi-white background, to appear darker */
+}
+
+.fc-time-grid-event .fc-content {
+       overflow: hidden; /* for when .fc-selected */
+}
+
+.fc-time-grid-event .fc-time,
+.fc-time-grid-event .fc-title {
+       padding: 0 1px;
+}
+
+.fc-time-grid-event .fc-time {
+       font-size: .85em;
+       white-space: nowrap;
+}
+
+/* short mode, where time and title are on the same line */
+
+.fc-time-grid-event.fc-short .fc-content {
+       /* don't wrap to second line (now that contents will be inline) */
+       white-space: nowrap;
+}
+
+.fc-time-grid-event.fc-short .fc-time,
+.fc-time-grid-event.fc-short .fc-title {
+       /* put the time and title on the same line */
+       display: inline-block;
+       vertical-align: top;
+}
+
+.fc-time-grid-event.fc-short .fc-time span {
+       display: none; /* don't display the full time text... */
+}
+
+.fc-time-grid-event.fc-short .fc-time:before {
+       content: attr(data-start); /* ...instead, display only the start time */
+}
+
+.fc-time-grid-event.fc-short .fc-time:after {
+       content: "\000A0-\000A0"; /* seperate with a dash, wrapped in nbsp's */
+}
+
+.fc-time-grid-event.fc-short .fc-title {
+       font-size: .85em; /* make the title text the same size as the time */
+       padding: 0; /* undo padding from above */
+}
+
+/* resizer (cursor device) */
+
+.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer {
+       left: 0;
+       right: 0;
+       bottom: 0;
+       height: 8px;
+       overflow: hidden;
+       line-height: 8px;
+       font-size: 11px;
+       font-family: monospace;
+       text-align: center;
+       cursor: s-resize;
+}
+
+.fc-time-grid-event.fc-allow-mouse-resize .fc-resizer:after {
+       content: "=";
+}
+
+/* resizer (touch device) */
+
+.fc-time-grid-event.fc-selected .fc-resizer {
+       /* 10x10 dot */
+       border-radius: 5px;
+       border-width: 1px;
+       width: 8px;
+       height: 8px;
+       border-style: solid;
+       border-color: inherit;
+       background: #fff;
+       /* horizontally center */
+       left: 50%;
+       margin-left: -5px;
+       /* center on the bottom edge */
+       bottom: -5px;
+}
+
+
+/* Now Indicator
+--------------------------------------------------------------------------------------------------*/
+
+.fc-time-grid .fc-now-indicator-line {
+       border-top-width: 1px;
+       left: 0;
+       right: 0;
+}
+
+/* arrow on axis */
+
+.fc-time-grid .fc-now-indicator-arrow {
+       margin-top: -5px; /* vertically center on top coordinate */
+}
+
+.fc-ltr .fc-time-grid .fc-now-indicator-arrow {
+       left: 0;
+       /* triangle pointing right... */
+       border-width: 5px 0 5px 6px;
+       border-top-color: transparent;
+       border-bottom-color: transparent;
+}
+
+.fc-rtl .fc-time-grid .fc-now-indicator-arrow {
+       right: 0;
+       /* triangle pointing left... */
+       border-width: 5px 6px 5px 0;
+       border-top-color: transparent;
+       border-bottom-color: transparent;
+}
diff --git a/tools/infra-dashboard/css/fullcalendar.print.css b/tools/infra-dashboard/css/fullcalendar.print.css
new file mode 100644 (file)
index 0000000..af884fc
--- /dev/null
@@ -0,0 +1,208 @@
+/*!
+ * FullCalendar v2.7.2 Print Stylesheet
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+
+/*
+ * Include this stylesheet on your page to get a more printer-friendly calendar.
+ * When including this stylesheet, use the media='print' attribute of the <link> tag.
+ * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css.
+ */
+
+.fc {
+       max-width: 100% !important;
+}
+
+
+/* Global Event Restyling
+--------------------------------------------------------------------------------------------------*/
+
+.fc-event {
+       background: #fff !important;
+       color: #000 !important;
+       page-break-inside: avoid;
+}
+
+.fc-event .fc-resizer {
+       display: none;
+}
+
+
+/* Table & Day-Row Restyling
+--------------------------------------------------------------------------------------------------*/
+
+th,
+td,
+hr,
+thead,
+tbody,
+.fc-row {
+       border-color: #ccc !important;
+       background: #fff !important;
+}
+
+/* kill the overlaid, absolutely-positioned components */
+/* common... */
+.fc-bg,
+.fc-bgevent-skeleton,
+.fc-highlight-skeleton,
+.fc-helper-skeleton,
+/* for timegrid. within cells within table skeletons... */
+.fc-bgevent-container,
+.fc-business-container,
+.fc-highlight-container,
+.fc-helper-container {
+       display: none;
+}
+
+/* don't force a min-height on rows (for DayGrid) */
+.fc tbody .fc-row {
+       height: auto !important; /* undo height that JS set in distributeHeight */
+       min-height: 0 !important; /* undo the min-height from each view's specific stylesheet */
+}
+
+.fc tbody .fc-row .fc-content-skeleton {
+       position: static; /* undo .fc-rigid */
+       padding-bottom: 0 !important; /* use a more border-friendly method for this... */
+}
+
+.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td { /* only works in newer browsers */
+       padding-bottom: 1em; /* ...gives space within the skeleton. also ensures min height in a way */
+}
+
+.fc tbody .fc-row .fc-content-skeleton table {
+       /* provides a min-height for the row, but only effective for IE, which exaggerates this value,
+          making it look more like 3em. for other browers, it will already be this tall */
+       height: 1em;
+}
+
+
+/* Undo month-view event limiting. Display all events and hide the "more" links
+--------------------------------------------------------------------------------------------------*/
+
+.fc-more-cell,
+.fc-more {
+       display: none !important;
+}
+
+.fc tr.fc-limited {
+       display: table-row !important;
+}
+
+.fc td.fc-limited {
+       display: table-cell !important;
+}
+
+.fc-popover {
+       display: none; /* never display the "more.." popover in print mode */
+}
+
+
+/* TimeGrid Restyling
+--------------------------------------------------------------------------------------------------*/
+
+/* undo the min-height 100% trick used to fill the container's height */
+.fc-time-grid {
+       min-height: 0 !important;
+}
+
+/* don't display the side axis at all ("all-day" and time cells) */
+.fc-agenda-view .fc-axis {
+       display: none;
+}
+
+/* don't display the horizontal lines */
+.fc-slats,
+.fc-time-grid hr { /* this hr is used when height is underused and needs to be filled */
+       display: none !important; /* important overrides inline declaration */
+}
+
+/* let the container that holds the events be naturally positioned and create real height */
+.fc-time-grid .fc-content-skeleton {
+       position: static;
+}
+
+/* in case there are no events, we still want some height */
+.fc-time-grid .fc-content-skeleton table {
+       height: 4em;
+}
+
+/* kill the horizontal spacing made by the event container. event margins will be done below */
+.fc-time-grid .fc-event-container {
+       margin: 0 !important;
+}
+
+
+/* TimeGrid *Event* Restyling
+--------------------------------------------------------------------------------------------------*/
+
+/* naturally position events, vertically stacking them */
+.fc-time-grid .fc-event {
+       position: static !important;
+       margin: 3px 2px !important;
+}
+
+/* for events that continue to a future day, give the bottom border back */
+.fc-time-grid .fc-event.fc-not-end {
+       border-bottom-width: 1px !important;
+}
+
+/* indicate the event continues via "..." text */
+.fc-time-grid .fc-event.fc-not-end:after {
+       content: "...";
+}
+
+/* for events that are continuations from previous days, give the top border back */
+.fc-time-grid .fc-event.fc-not-start {
+       border-top-width: 1px !important;
+}
+
+/* indicate the event is a continuation via "..." text */
+.fc-time-grid .fc-event.fc-not-start:before {
+       content: "...";
+}
+
+/* time */
+
+/* undo a previous declaration and let the time text span to a second line */
+.fc-time-grid .fc-event .fc-time {
+       white-space: normal !important;
+}
+
+/* hide the the time that is normally displayed... */
+.fc-time-grid .fc-event .fc-time span {
+       display: none;
+}
+
+/* ...replace it with a more verbose version (includes AM/PM) stored in an html attribute */
+.fc-time-grid .fc-event .fc-time:after {
+       content: attr(data-full);
+}
+
+
+/* Vertical Scroller & Containers
+--------------------------------------------------------------------------------------------------*/
+
+/* kill the scrollbars and allow natural height */
+.fc-scroller,
+.fc-day-grid-container,    /* these divs might be assigned height, which we need to cleared */
+.fc-time-grid-container {  /* */
+       overflow: visible !important;
+       height: auto !important;
+}
+
+/* kill the horizontal border/padding used to compensate for scrollbars */
+.fc-row {
+       border: 0 !important;
+       margin: 0 !important;
+}
+
+
+/* Button Controls
+--------------------------------------------------------------------------------------------------*/
+
+.fc-button-group,
+.fc button {
+       display: none; /* don't display any button-related controls */
+}
diff --git a/tools/infra-dashboard/css/highslide.min.css b/tools/infra-dashboard/css/highslide.min.css
new file mode 100644 (file)
index 0000000..83bac0f
--- /dev/null
@@ -0,0 +1,793 @@
+.highslide-header a,
+.highslide-loading {
+    text-transform: uppercase;
+    text-decoration: none;
+    font-weight: 700
+}
+.highslide-container div {
+    font-family: Verdana, Helvetica;
+    font-size: 10pt
+}
+.highslide-container table {
+    background: 0 0
+}
+.highslide {
+    outline: 0;
+    text-decoration: none
+}
+.highslide img {
+    border: 2px solid silver
+}
+.highslide:hover img {
+    border-color: gray
+}
+.highslide-active-anchor img {
+    visibility: hidden
+}
+.highslide-gallery .highslide-active-anchor img {
+    border-color: #000;
+    visibility: visible;
+    cursor: default
+}
+.highslide-image {
+    border-width: 2px;
+    border-style: solid;
+    border-color: #fff;
+    background: gray
+}
+.highslide-outline,
+.highslide-wrapper {
+    background: #fff
+}
+.glossy-dark {
+    background: #111
+}
+.highslide-number {
+    font-weight: 700;
+    color: gray;
+    font-size: .9em
+}
+.highslide-caption {
+    display: none;
+    font-size: 1em;
+    padding: 5px
+}
+.highslide-heading {
+    display: none;
+    font-weight: 700;
+    margin: .4em
+}
+.highslide-dimming {
+    position: absolute;
+    background: #000
+}
+a.highslide-full-expand {
+    background: url(/media/com_demo/graphics/fullexpand.gif) no-repeat;
+    display: block;
+    margin: 0 10px 10px 0;
+    width: 34px;
+    height: 34px
+}
+.highslide-loading {
+    display: block;
+    color: #000;
+    font-size: 9px;
+    padding: 3px 3px 3px 22px;
+    border: 1px solid #fff;
+    background-color: #fff;
+    background-image: url(./media/loader.white.gif);
+    background-repeat: no-repeat;
+    background-position: 3px 1px
+}
+a.highslide-credits,
+a.highslide-credits i {
+    padding: 2px;
+    color: silver;
+    text-decoration: none;
+    font-size: 10px
+}
+a.highslide-credits:hover,
+a.highslide-credits:hover i {
+    color: #fff;
+    background-color: gray
+}
+.highslide-move,
+.highslide-move * {
+    cursor: move
+}
+.highslide-viewport {
+    display: none;
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    z-index: 1;
+    background: 0 0;
+    left: 0;
+    top: 0
+}
+.hidden-container,
+.highslide-overlay {
+    display: none
+}
+.closebutton {
+    position: relative;
+    top: -15px;
+    left: 15px;
+    width: 30px;
+    height: 30px;
+    cursor: pointer;
+    background: url(/media/com_demo/graphics/close.png)
+}
+.highslide-gallery ul {
+    list-style-type: none;
+    margin: 0;
+    padding: 0
+}
+.highslide-gallery ul li {
+    display: block;
+    position: relative;
+    float: left;
+    width: 106px;
+    height: 106px;
+    border: 1px solid silver;
+    background: #ededed;
+    margin: 2px;
+    line-height: 0;
+    overflow: hidden
+}
+.highslide-gallery ul a {
+    position: absolute;
+    top: 50%;
+    left: 50%
+}
+.highslide-gallery ul img {
+    position: relative;
+    top: -50%;
+    left: -50%
+}
+html>body .highslide-gallery ul li {
+    display: table;
+    text-align: center
+}
+html>body .highslide-gallery ul a {
+    position: static;
+    display: table-cell;
+    vertical-align: middle
+}
+html>body .highslide-gallery ul img {
+    position: static
+}
+.highslide-controls {
+    width: 195px;
+    height: 40px;
+    background: url(/media/com_demo/graphics/controlbar-white.gif) 0 -90px no-repeat;
+    margin: 20px 15px 10px 0
+}
+.highslide-controls ul {
+    position: relative;
+    left: 15px;
+    height: 40px;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    background: url(/media/com_demo/graphics/controlbar-white.gif) right -90px no-repeat
+}
+.highslide-controls li {
+    float: left;
+    padding: 5px 0;
+    margin: 0;
+    list-style: none
+}
+.highslide-controls a {
+    background-image: url(/media/com_demo/graphics/controlbar-white.gif);
+    display: block;
+    float: left;
+    height: 30px;
+    width: 30px;
+    outline: 0
+}
+.highslide-controls a.disabled,
+.highslide-controls a.disabled span {
+    cursor: default
+}
+.highslide-controls a span {
+    display: none;
+    cursor: pointer
+}
+.highslide-controls .highslide-previous a {
+    background-position: 0 0
+}
+.highslide-controls .highslide-previous a:hover {
+    background-position: 0 -30px
+}
+.highslide-controls .highslide-previous a.disabled {
+    background-position: 0 -60px!important
+}
+.highslide-controls .highslide-play a {
+    background-position: -30px 0
+}
+.highslide-controls .highslide-play a:hover {
+    background-position: -30px -30px
+}
+.highslide-controls .highslide-play a.disabled {
+    background-position: -30px -60px!important
+}
+.highslide-controls .highslide-pause a {
+    background-position: -60px 0
+}
+.highslide-controls .highslide-pause a:hover {
+    background-position: -60px -30px
+}
+.highslide-controls .highslide-next a {
+    background-position: -90px 0
+}
+.highslide-controls .highslide-next a:hover {
+    background-position: -90px -30px
+}
+.highslide-controls .highslide-next a.disabled {
+    background-position: -90px -60px!important
+}
+.highslide-controls .highslide-move a {
+    background-position: -90pt 0
+}
+.highslide-controls .highslide-move a:hover {
+    background-position: -90pt -30px
+}
+.highslide-controls .highslide-full-expand a {
+    background-position: -150px 0
+}
+.highslide-controls .highslide-full-expand a:hover {
+    background-position: -150px -30px
+}
+.highslide-controls .highslide-full-expand a.disabled {
+    background-position: -150px -60px!important
+}
+.highslide-controls .highslide-close a {
+    background-position: -180px 0
+}
+.highslide-controls .highslide-close a:hover {
+    background-position: -180px -30px
+}
+.highslide-maincontent {
+    display: none
+}
+.highslide-html {
+    background-color: #fff
+}
+.highslide-html-content {
+    display: none;
+    width: 25pc;
+    padding: 0 5px 5px
+}
+.highslide-header {
+    padding-bottom: 5px
+}
+.highslide-header ul {
+    margin: 0;
+    padding: 0;
+    text-align: right
+}
+.highslide-header ul li {
+    display: inline;
+    padding-left: 1em
+}
+.highslide-header ul li.highslide-next,
+.highslide-header ul li.highslide-previous {
+    display: none
+}
+.highslide-header a {
+    color: gray
+}
+.highslide-header a:hover {
+    color: #000
+}
+.highslide-header .highslide-move a {
+    cursor: move
+}
+.highslide-footer {
+    height: 1pc
+}
+.highslide-footer .highslide-resize {
+    display: block;
+    float: right;
+    margin-top: 5px;
+    height: 11px;
+    width: 11px;
+    background: url(/media/com_demo/graphics/resize.gif) no-repeat
+}
+.highslide-footer .highslide-resize span {
+    display: none
+}
+.highslide-resize {
+    cursor: nw-resize
+}
+.draggable-header .highslide-header {
+    height: 18px;
+    border-bottom: 1px solid #ddd
+}
+.draggable-header .highslide-heading {
+    position: absolute;
+    margin: 2px .4em
+}
+.draggable-header .highslide-header .highslide-move {
+    cursor: move;
+    display: block;
+    height: 1pc;
+    position: absolute;
+    right: 24px;
+    top: 0;
+    width: 100%;
+    z-index: 1
+}
+.draggable-header .highslide-header .highslide-move * {
+    display: none
+}
+.draggable-header .highslide-header .highslide-close {
+    position: absolute;
+    right: 2px;
+    top: 2px;
+    z-index: 5;
+    padding: 0
+}
+.draggable-header .highslide-header .highslide-close a {
+    display: block;
+    height: 1pc;
+    width: 1pc;
+    background-image: url(/media/com_demo/graphics/closeX.png)
+}
+.draggable-header .highslide-header .highslide-close a:hover {
+    background-position: 0 1pc
+}
+.draggable-header .highslide-header .highslide-close span {
+    display: none
+}
+.draggable-header .highslide-maincontent {
+    padding-top: 1em
+}
+.titlebar .highslide-header {
+    height: 18px;
+    border-bottom: 1px solid #ddd
+}
+.titlebar .highslide-heading {
+    position: absolute;
+    width: 90%;
+    margin: 1px 0 1px 5px;
+    color: #666
+}
+.titlebar .highslide-header .highslide-move {
+    cursor: move;
+    display: block;
+    height: 1pc;
+    position: absolute;
+    right: 24px;
+    top: 0;
+    width: 100%;
+    z-index: 1
+}
+.controls-in-heading .highslide-controls .highslide-move,
+.no-footer .highslide-footer,
+.text-controls .highslide-move,
+.titlebar .highslide-header .highslide-move * {
+    display: none
+}
+.titlebar .highslide-header li {
+    position: relative;
+    top: 3px;
+    z-index: 2;
+    padding: 0 0 0 1em
+}
+.titlebar .highslide-maincontent {
+    padding-top: 1em
+}
+.wide-border {
+    background: #fff
+}
+.wide-border .highslide-image {
+    border-width: 10px
+}
+.wide-border .highslide-caption {
+    padding: 0 10px 10px
+}
+.borderless .highslide-image {
+    border: none
+}
+.borderless .highslide-caption {
+    border-bottom: 1px solid #fff;
+    border-top: 1px solid #fff;
+    background: silver
+}
+.outer-glow {
+    background: #444
+}
+.outer-glow .highslide-image {
+    border: 5px solid #444
+}
+.outer-glow .highslide-caption {
+    border: 5px solid #444;
+    border-top: none;
+    padding: 5px;
+    background-color: gray
+}
+.colored-border {
+    background: #fff
+}
+.colored-border .highslide-image {
+    border: 2px solid green
+}
+.colored-border .highslide-caption {
+    border: 2px solid green;
+    border-top: none
+}
+.dark {
+    background: #111
+}
+.dark .highslide-image {
+    border-color: #000 #000 #202020;
+    background: gray
+}
+.dark .highslide-caption {
+    color: #fff;
+    background: #111
+}
+.dark .highslide-controls,
+.dark .highslide-controls a,
+.dark .highslide-controls ul {
+    background-image: url(/media/com_demo/graphics/controlbar-black-border.gif)
+}
+.floating-caption .highslide-caption {
+    position: absolute;
+    padding: 1em 0 0;
+    background: 0 0;
+    color: #fff;
+    border: none;
+    font-weight: 700
+}
+.controls-in-heading .highslide-heading {
+    color: gray;
+    font-weight: 700;
+    height: 20px;
+    overflow: hidden;
+    cursor: default;
+    padding: 0 0 0 22px;
+    margin: 0;
+    background: url(/media/com_demo/graphics/icon.gif) 0 1px no-repeat
+}
+.controls-in-heading .highslide-controls {
+    width: 105px;
+    height: 20px;
+    position: relative;
+    margin: 0;
+    top: -23px;
+    left: 7px;
+    background: 0 0
+}
+.controls-in-heading .highslide-controls ul {
+    position: static;
+    height: 20px;
+    background: 0 0
+}
+.controls-in-heading .highslide-controls li {
+    padding: 0
+}
+.controls-in-heading .highslide-controls a {
+    background-image: url(/media/com_demo/graphics/controlbar-white-small.gif);
+    height: 20px;
+    width: 20px
+}
+.controls-in-heading .highslide-controls .highslide-previous a {
+    background-position: 0 0
+}
+.controls-in-heading .highslide-controls .highslide-previous a:hover {
+    background-position: 0 -20px
+}
+.controls-in-heading .highslide-controls .highslide-previous a.disabled {
+    background-position: 0 -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-play a {
+    background-position: -20px 0
+}
+.controls-in-heading .highslide-controls .highslide-play a:hover {
+    background-position: -20px -20px
+}
+.controls-in-heading .highslide-controls .highslide-play a.disabled {
+    background-position: -20px -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-pause a {
+    background-position: -40px 0
+}
+.controls-in-heading .highslide-controls .highslide-pause a:hover {
+    background-position: -40px -20px
+}
+.controls-in-heading .highslide-controls .highslide-next a {
+    background-position: -60px 0
+}
+.controls-in-heading .highslide-controls .highslide-next a:hover {
+    background-position: -60px -20px
+}
+.controls-in-heading .highslide-controls .highslide-next a.disabled {
+    background-position: -60px -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a {
+    background-position: -75pt 0
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a:hover {
+    background-position: -75pt -20px
+}
+.controls-in-heading .highslide-controls .highslide-full-expand a.disabled {
+    background-position: -75pt -40px!important
+}
+.controls-in-heading .highslide-controls .highslide-close a {
+    background-position: -90pt 0
+}
+.controls-in-heading .highslide-controls .highslide-close a:hover {
+    background-position: -90pt -20px
+}
+.text-controls .highslide-controls {
+    width: auto;
+    height: auto;
+    margin: 0;
+    text-align: center;
+    background: 0 0
+}
+.text-controls ul {
+    position: static;
+    background: 0 0;
+    height: auto;
+    left: 0
+}
+.text-controls li {
+    background-image: url(/media/com_demo/graphics/controlbar-text-buttons.png);
+    background-position: right top!important;
+    padding: 0;
+    margin-left: 15px;
+    display: block;
+    width: auto
+}
+.text-controls a {
+    background: url(/media/com_demo/graphics/controlbar-text-buttons.png) no-repeat;
+    background-position: left top!important;
+    position: relative;
+    left: -10px;
+    display: block;
+    width: auto;
+    height: auto;
+    text-decoration: none!important
+}
+.text-controls a span {
+    background: url(/media/com_demo/graphics/controlbar-text-buttons.png) no-repeat;
+    margin: 1px 2px 1px 10px;
+    display: block;
+    min-width: 4em;
+    height: 18px;
+    line-height: 18px;
+    padding: 1px 0 1px 18px;
+    color: #333;
+    font-family: "Trebuchet MS", Arial, sans-serif;
+    font-size: 9pt;
+    font-weight: 700;
+    white-space: nowrap
+}
+.text-controls .highslide-next {
+    margin-right: 1em
+}
+.text-controls .highslide-full-expand a span {
+    min-width: 0;
+    margin: 1px 0;
+    padding: 1px 0 1px 10px
+}
+.text-controls .highslide-close a span {
+    min-width: 0
+}
+.text-controls a:hover span {
+    color: #000
+}
+.text-controls a.disabled span {
+    color: #999
+}
+.text-controls .highslide-previous span {
+    background-position: 0 -40px
+}
+.text-controls .highslide-previous a.disabled {
+    background-position: left top!important
+}
+.text-controls .highslide-previous a.disabled span {
+    background-position: 0 -140px
+}
+.text-controls .highslide-play span {
+    background-position: 0 -60px
+}
+.text-controls .highslide-play a.disabled {
+    background-position: left top!important
+}
+.text-controls .highslide-play a.disabled span {
+    background-position: 0 -10pc
+}
+.text-controls .highslide-pause span {
+    background-position: 0 -5pc
+}
+.text-controls .highslide-next span {
+    background-position: 0 -75pt
+}
+.text-controls .highslide-next a.disabled {
+    background-position: left top!important
+}
+.text-controls .highslide-next a.disabled span {
+    background-position: 0 -200px
+}
+.text-controls .highslide-full-expand span {
+    background: 0 0
+}
+.text-controls .highslide-full-expand a.disabled {
+    background-position: left top!important
+}
+.text-controls .highslide-close span {
+    background-position: 0 -90pt
+}
+.highslide-thumbstrip {
+    height: 100%
+}
+.highslide-thumbstrip div {
+    overflow: hidden
+}
+.highslide-thumbstrip table {
+    position: relative;
+    padding: 0;
+    border-collapse: collapse
+}
+.highslide-thumbstrip td {
+    padding: 1px
+}
+.highslide-thumbstrip a {
+    outline: 0
+}
+.highslide-thumbstrip img {
+    display: block;
+    border: 1px solid gray;
+    margin: 0 auto
+}
+.highslide-thumbstrip .highslide-active-anchor img {
+    visibility: visible
+}
+.highslide-thumbstrip .highslide-marker {
+    position: absolute;
+    width: 0;
+    height: 0;
+    border-width: 0;
+    border-style: solid;
+    border-color: transparent
+}
+.highslide-thumbstrip-horizontal div {
+    width: auto
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up {
+    display: none;
+    position: absolute;
+    top: 3px;
+    left: 3px;
+    width: 25px;
+    height: 42px
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-up div {
+    margin-bottom: 10px;
+    cursor: pointer;
+    background: url(/media/com_demo/graphics/scrollarrows.png) left center no-repeat;
+    height: 42px
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down {
+    display: none;
+    position: absolute;
+    top: 3px;
+    right: 3px;
+    width: 25px;
+    height: 42px
+}
+.highslide-thumbstrip-horizontal .highslide-scroll-down div {
+    margin-bottom: 10px;
+    cursor: pointer;
+    background: url(/media/com_demo/graphics/scrollarrows.png) center right no-repeat;
+    height: 42px
+}
+.highslide-thumbstrip-horizontal table {
+    margin: 2px 0 10px
+}
+.highslide-viewport .highslide-thumbstrip-horizontal table {
+    margin-left: 10px
+}
+.highslide-thumbstrip-horizontal img {
+    width: auto;
+    height: 40px
+}
+.highslide-thumbstrip-horizontal .highslide-marker {
+    top: 47px;
+    border-left-width: 6px;
+    border-right-width: 6px;
+    border-bottom: 6px solid gray
+}
+.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
+    margin-left: 10px
+}
+.dark .highslide-thumbstrip-horizontal .highslide-marker,
+.highslide-viewport .highslide-thumbstrip-horizontal .highslide-marker {
+    border-bottom-color: #fff!important
+}
+.highslide-thumbstrip-vertical-overlay {
+    overflow: hidden!important
+}
+.highslide-thumbstrip-vertical div {
+    height: 100%
+}
+.highslide-thumbstrip-vertical a {
+    display: block
+}
+.highslide-thumbstrip-vertical .highslide-scroll-up {
+    display: none;
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 25px
+}
+.highslide-thumbstrip-vertical .highslide-scroll-up div {
+    margin-left: 10px;
+    cursor: pointer;
+    background: url(/media/com_demo/graphics/scrollarrows.png) top center no-repeat;
+    height: 25px
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down {
+    display: none;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 25px
+}
+.highslide-thumbstrip-vertical .highslide-scroll-down div {
+    margin-left: 10px;
+    cursor: pointer;
+    background: url(/media/com_demo/graphics/scrollarrows.png) bottom center no-repeat;
+    height: 25px
+}
+.highslide-thumbstrip-vertical table {
+    margin: 10px 0 0 10px
+}
+.highslide-thumbstrip-vertical img {
+    max-width: 60px
+}
+.highslide-thumbstrip-vertical .highslide-marker {
+    left: 0;
+    margin-top: 8px;
+    border-top-width: 6px;
+    border-bottom-width: 6px;
+    border-left: 6px solid gray
+}
+.dark .highslide-thumbstrip-vertical .highslide-marker,
+.highslide-viewport .highslide-thumbstrip-vertical .highslide-marker {
+    border-left-color: #fff
+}
+.highslide-viewport .highslide-thumbstrip-float {
+    overflow: auto
+}
+.highslide-thumbstrip-float ul {
+    margin: 2px 0;
+    padding: 0
+}
+.highslide-thumbstrip-float li {
+    display: block;
+    height: 60px;
+    margin: 0 2px;
+    list-style: none;
+    float: left
+}
+.highslide-thumbstrip-float img {
+    display: inline;
+    border-color: silver;
+    max-height: 56px
+}
+.highslide-thumbstrip-float .highslide-active-anchor img {
+    border-color: #000
+}
+.highslide-thumbstrip-float .highslide-marker,
+.highslide-thumbstrip-float .highslide-scroll-down div,
+.highslide-thumbstrip-float .highslide-scroll-up div {
+    display: none
+}
\ No newline at end of file
diff --git a/tools/infra-dashboard/css/opnfv.css b/tools/infra-dashboard/css/opnfv.css
new file mode 100644 (file)
index 0000000..8b2711a
--- /dev/null
@@ -0,0 +1,2479 @@
+* {
+    margin: 0;
+    padding: 0;
+}
+.clearfix {
+    display: inline-block;
+}
+.clearfix:after {
+    content: ".";
+    display: block;
+    height: 0;
+    clear: both;
+    visibility: hidden;
+}
+* html .clearfix {
+    height: 1%;
+}
+.clearfix {
+    display: block;
+}
+.clearleft,
+.clearl,
+.cleft {
+    clear: left;
+}
+.clearright,
+.clearr,
+.cright {
+    clear: right;
+}
+.clear,
+.clearboth,
+.clearall {
+    clear: both;
+}
+.floatleft,
+.fleft,
+.floatl {
+    float: left;
+    margin: 0 10px 5px 0;
+}
+.floatright,
+.fright,
+.floatr {
+    float: right;
+    margin: 0 0 5px 10px;
+}
+#skip a:link,
+#skip a:hover,
+#skip a:visited {
+    position: absolute;
+    left: -10000px;
+    top: auto;
+    width: 1px;
+    height: 1px;
+    overflow: hidden;
+}
+#skip a:active,
+#skip a:focus {
+    position: static;
+    width: auto;
+    height: auto;
+}
+div.view div.views-admin-links {
+    width: auto;
+}
+div.block {
+    position: relative;
+}
+div.block .edit {
+    display: none;
+    position: absolute;
+    right: -20px;
+    top: -5px;
+    z-index: 40;
+    padding: 3px 8px 0;
+    font-size: 10px;
+    line-height: 16px;
+    background-color: white;
+    border: 1px solid #cccccc;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+    -moz-box-shadow: 0 1px 3px #888888;
+    -webkit-box-shadow: -1px 1px 2px #666666;
+}
+div.block .edit a {
+    display: block;
+    border: 0;
+    padding: 0;
+    margin: 0;
+}
+div.block:hover .edit {
+    display: block;
+}
+* {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+.container {
+    max-width: 68em;
+    margin-left: auto;
+    margin-right: auto;
+    margin-left: auto;
+    margin-right: auto;
+    width: 68em;
+}
+.container:after {
+    content: "";
+    display: table;
+    clear: both;
+}
+@media screen and (max-width: 1088px) {
+    .container {
+        width: auto;
+    }
+}
+.no-sidebars #content {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 100%;
+}
+.no-sidebars #content:last-child {
+    margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+    .no-sidebars #content {
+        float: left;
+        display: block;
+        margin-right: 3.22581%;
+        width: 100%;
+    }
+    .no-sidebars #content:last-child {
+        margin-right: 0;
+    }
+}
+@media screen and (max-width: 480px) {
+    .no-sidebars #content {
+        float: left;
+        display: block;
+        margin-right: 6.66667%;
+        width: 100%;
+    }
+    .no-sidebars #content:last-child {
+        margin-right: 0;
+    }
+}
+.one-sidebar.sidebar-second #content {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 72.88136%;
+}
+.one-sidebar.sidebar-second #content:last-child {
+    margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+    .one-sidebar.sidebar-second #content {
+        float: left;
+        display: block;
+        margin-right: 3.22581%;
+        width: 74.19355%;
+    }
+    .one-sidebar.sidebar-second #content:last-child {
+        margin-right: 0;
+    }
+}
+@media screen and (max-width: 480px) {
+    .one-sidebar.sidebar-second #content {
+        float: left;
+        display: block;
+        margin-right: 6.66667%;
+        width: 100%;
+    }
+    .one-sidebar.sidebar-second #content:last-child {
+        margin-right: 0;
+    }
+}
+.one-sidebar.sidebar-first #content {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 79.66102%;
+    margin-left: 20.33898%;
+}
+.one-sidebar.sidebar-first #content:last-child {
+    margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+    .one-sidebar.sidebar-first #content {
+        float: left;
+        display: block;
+        margin-right: 3.22581%;
+        width: 74.19355%;
+        margin-left: 25.80645%;
+    }
+    .one-sidebar.sidebar-first #content:last-child {
+        margin-right: 0;
+    }
+}
+@media screen and (max-width: 480px) {
+    .one-sidebar.sidebar-first #content {
+        float: left;
+        display: block;
+        margin-right: 6.66667%;
+        width: 100%;
+        margin-left: 0%;
+    }
+    .one-sidebar.sidebar-first #content:last-child {
+        margin-right: 0;
+    }
+}
+.two-sidebars #content {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 52.54237%;
+    margin-left: 20.33898%;
+}
+.two-sidebars #content:last-child {
+    margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+    .two-sidebars #content {
+        float: left;
+        display: block;
+        margin-right: 3.22581%;
+        width: 48.3871%;
+        margin-left: 25.80645%;
+    }
+    .two-sidebars #content:last-child {
+        margin-right: 0;
+    }
+}
+@media screen and (max-width: 480px) {
+    .two-sidebars #content {
+        float: left;
+        display: block;
+        margin-right: 6.66667%;
+        width: 100%;
+        margin-left: 0%;
+    }
+    .two-sidebars #content:last-child {
+        margin-right: 0;
+    }
+}
+#sidebar-first {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 18.64407%;
+    margin-left: -74.57627%;
+}
+#sidebar-first:last-child {
+    margin-right: 0;
+}
+.sidebar-first #sidebar-first {
+    margin-left: -101.69492%;
+}
+@media screen and (max-width: 768px) {
+    #sidebar-first {
+        float: left;
+        display: block;
+        margin-right: 3.22581%;
+        width: 22.58065%;
+        margin-left: -77.41935%;
+    }
+    #sidebar-first:last-child {
+        margin-right: 0;
+    }
+    .sidebar-first #sidebar-first {
+        margin-left: -103.22581%;
+    }
+}
+@media screen and (max-width: 480px) {
+    #sidebar-first {
+        float: left;
+        display: block;
+        margin-right: 6.66667%;
+        width: 100%;
+        margin-left: 0%;
+    }
+    #sidebar-first:last-child {
+        margin-right: 0;
+    }
+    .sidebar-first #sidebar-first {
+        margin-left: 0%;
+    }
+}
+#sidebar-second {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 25.42373%;
+    margin-left: 0%;
+}
+#sidebar-second:last-child {
+    margin-right: 0;
+}
+@media screen and (max-width: 768px) {
+    #sidebar-second {
+        float: left;
+        display: block;
+        margin-right: 3.22581%;
+        width: 22.58065%;
+        margin-left: 0%;
+    }
+    #sidebar-second:last-child {
+        margin-right: 0;
+    }
+}
+@media screen and (max-width: 480px) {
+    #sidebar-second {
+        float: left;
+        display: block;
+        margin-right: 6.66667%;
+        width: 100%;
+        margin-left: 0%;
+    }
+    #sidebar-second:last-child {
+        margin-right: 0;
+    }
+}
+#footer {
+    left: 0;
+    width: 100%;
+    height: 50px;
+    position: fixed;
+    bottom: 0;
+}
+#header,
+#footer,
+.mission,
+.breadcrumb,
+.node {
+    clear: both;
+}
+.inner {
+    padding: 0;
+}
+#navigation li {
+    list-style-type: none;
+    display: inline-block;
+}
+body {
+    margin: 0;
+    font: 14px/1.5em "Helvetica Neue", helvetica, Arial, sans-serif;
+    letter-spacing: 0.03em;
+}
+a:link,
+a:visited {
+    color: blue;
+    text-decoration: none;
+}
+a:hover,
+a:active {
+    color: red;
+    text-decoration: underline;
+}
+#site-name {
+    font-size: 2.2em;
+    line-height: 1.3em;
+    font-weight: 300;
+    padding: 0 0 0.5em;
+    margin: 0;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+    line-height: 1.3em;
+}
+h1 {
+    font-size: 2.2em;
+    font-weight: 300;
+    padding: 0 0 0.5em;
+    margin: 0;
+}
+h2 {
+    font-size: 1.8em;
+    font-weight: 300;
+    margin-bottom: 0.75em;
+}
+h3 {
+    font-size: 1.4em;
+    margin-bottom: 1em;
+    margin-top: 1em;
+}
+h4 {
+    font-size: 1.2em;
+    margin-top: 0.8em;
+    margin-bottom: 0.8em;
+}
+h5 {
+    font-size: 1.1em;
+    font-weight: 600;
+    margin-bottom: 0;
+}
+h6 {
+    font-size: 1em;
+    font-weight: bold;
+}
+p {
+    margin: 0 0 1em 0;
+}
+ul,
+ol {
+    margin-left: 0;
+    padding-left: 0;
+}
+table {
+    width: 99%;
+}
+table tbody {
+    border-top: 0px;
+}
+table tr.even,
+table tr.odd,
+table tr {
+    border-bottom: 1px solid #ccc;
+}
+table tr.even td,
+table tr.odd td,
+table tr td {
+    padding: 10px 5px;
+    vertical-align: top;
+}
+table tr.odd {
+    background-color: white;
+}
+pre,
+code,
+tt {
+    font: 1em "andale mono", "lucida console", monospace;
+    line-height: 1.5;
+}
+pre {
+    background-color: #efefef;
+    display: block;
+    padding: 5px;
+    margin: 5px 0;
+    border: 1px solid #aaaaaa;
+}
+ul {
+    margin-left: 25px;
+    list-style-type: disc;
+}
+ul ul {
+    list-style-type: circle;
+}
+ul ul ul {
+    list-style-type: square;
+}
+ul ul ul ul {
+    list-style-type: circle;
+}
+ol {
+    list-style-type: decimal;
+}
+ol ol {
+    list-style-type: lower-alpha;
+}
+ol ol ol {
+    list-style-type: decimal;
+}
+abbr {
+    border-bottom: 1px dotted #666666;
+    cursor: help;
+    white-space: nowrap;
+}
+#edit-title {
+    font-size: 24px;
+    width: 99%;
+}
+#system-themes-form img {
+    width: 100px;
+}
+.form-item .description {
+    font-style: italic;
+    line-height: 1.2em;
+    font-size: 0.8em;
+    margin-top: 5px;
+    color: #777777;
+}
+#edit-delete {
+    color: #cc0000;
+}
+div.messages {
+    padding: 9px;
+    margin: 1em 0;
+    color: #003366;
+    background: #bbddff;
+    border: 1px solid #aaccee;
+}
+div.warning {
+    color: #884400;
+    background: #ffee66;
+    border-color: #eedd55;
+}
+div.error {
+    color: white;
+    background: #ee6633;
+    border-color: #dd5522;
+}
+div.status {
+    color: #336600;
+    background: #ccff88;
+    border-color: #bbee77;
+}
+#block-views-developer_tools-block {
+    padding-top: 48px;
+    border-top: 1px solid #cccccc;
+}
+#block-views-developer_tools-block .view-content ul {
+    margin: 0;
+    padding: 0;
+    list-style-type: none;
+    list-style-image: none;
+}
+#block-views-developer_tools-block .view-content ul li {
+    margin: 0;
+    padding: 0;
+    height: 100px;
+    list-style-type: none;
+    list-style-image: none;
+    float: left;
+    width: 50%;
+    margin-bottom: 60px;
+}
+#block-views-developer_tools-block .view-content ul li .views-field-title {
+    font-size: 24px;
+    color: #f15922;
+    text-align: center;
+    margin-bottom: 10px;
+    font-weight: 400;
+}
+#block-views-developer_tools-block .view-content ul li .views-field-nothing {
+    text-align: justify;
+}
+#block-views-developer_tools-block .view-content ul li .views-field-nothing a {
+    text-transform: uppercase;
+    font-weight: 400;
+    font-size: 95%;
+}
+#block-views-developer_tools-block .view-content ul .views-row-odd {
+    padding-right: 20px;
+}
+#block-views-developer_tools-block .view-content ul .views-row-even {
+    padding-left: 20px;
+}
+#edit-submit-resources {
+    margin-top: 0px;
+}
+* {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+* {
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+a,
+a:link,
+a:visited {
+    color: #41ba77;
+}
+a:hover {
+    color: #373A36;
+}
+p {
+    color: #373A36;
+}
+#block-menu-menu-social-links li {
+    margin: 0;
+    padding: 0 3px 0 0;
+}
+#block-menu-menu-social-links li a {
+    display: block;
+    width: 27px;
+    height: 29px;
+    text-indent: -9999px;
+    overflow: hidden;
+    background: url(/sites/all/themes/opnfv/images/optimized/social-icons1.png) no-repeat 0 0;
+}
+#block-menu-menu-social-links li a:hover {
+    opacity: 0.7;
+}
+#block-menu-menu-social-links li .twitter {
+    background-position: -103px 0;
+}
+#block-menu-menu-social-links li .linkedin {
+    background-position: -78px 0;
+}
+#block-menu-menu-social-links li .youtube {
+    background-position: -50px 0;
+}
+#block-menu-menu-social-links li .facebook {
+    background-position: -26px 0;
+}
+#block-menu-menu-social-links li .gplus {
+    background-position: 0 0;
+}
+#block-menu-menu-social-links li .slideshare {
+    background-position: -160px 0;
+}
+#block-menu-menu-social-links li .flickr {
+    background-position: -212px 0;
+}
+#block-menu-menu-social-links li .vimeo {
+    background-position: -185px 0;
+}
+.feed-label {
+    vertical-align: text-top;
+    margin-left: 5px;
+}
+.block-menu-block a {
+    color: #2E2925;
+    font-size: 14px;
+}
+.block-menu-block a.active {
+    color: #2E2925;
+}
+#header {
+    position: relative;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+    #header {
+        top: 0px;
+    }
+}
+#header #block-menu-menu-social-links {
+    position: relative;
+    z-index: 50;
+    float: right;
+    right: 200px;
+}
+@media screen and (min-width: 640px) and (max-width: 768px) {
+    #header #block-menu-menu-social-links {
+        top: 62px;
+    }
+}
+@media screen and (min-width: 0) and (max-width: 640px) {
+    #header #block-menu-menu-social-links {
+        right: 0;
+        top: 0;
+    }
+}
+#header #block-search-form,
+#header #search-block-form {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 100%;
+    position: relative;
+    z-index: 100;
+}
+#header #block-search-form:last-child,
+#header #search-block-form:last-child {
+    margin-right: 0;
+}
+#header #block-search-form .block-inner,
+#header #search-block-form .block-inner {
+    text-align: right;
+}
+#header #block-search-form .block-inner .container-inline,
+#header #search-block-form .block-inner .container-inline {
+    position: absolute;
+    top: -25px;
+    right: 9px;
+}
+@media screen and (min-width: 640px) and (max-width: 768px) {
+    #header #block-search-form .block-inner .container-inline,
+    #header #search-block-form .block-inner .container-inline {
+        right: 0;
+        top: 36px;
+    }
+}
+@media screen and (min-width: 0) and (max-width: 640px) {
+    #header #block-search-form .block-inner .container-inline,
+    #header #search-block-form .block-inner .container-inline {
+        top: 0;
+    }
+}
+#header #block-search-form .block-inner .form-text,
+#header #search-block-form .block-inner .form-text {
+    width: 130px;
+}
+@media screen and (min-width: 0) and (max-width: 640px) {
+    #header #block-search-form .block-inner .form-text,
+    #header #search-block-form .block-inner .form-text {
+        width: 84px;
+    }
+}
+#header #block-search-form #edit-actions .form-submit,
+#header #search-block-form #edit-actions .form-submit {
+    background: #ecedee;
+    color: #2E2925;
+    font-size: 10px;
+    border: none;
+    padding: 6px 7px;
+    top: -2px;
+    position: relative;
+}
+#header #block-search-form #edit-actions .form-submit:hover,
+#header #search-block-form #edit-actions .form-submit:hover {
+    background: #aeb2b7;
+}
+#header .menu-block-wrapper {
+    background: none;
+}
+#header .menu-block-wrapper .menu li {
+    list-style: none;
+    margin: 0;
+}
+#header .menu-block-wrapper .menu .menu-level-1 {
+    display: inline-block;
+    vertical-align: top;
+    margin: 0 0.5em 0.5em 0;
+    padding: 0;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+    #header .menu-block-wrapper .menu .menu-level-1 {
+        width: 100%;
+    }
+    #header .menu-block-wrapper .menu .menu-level-1 a {
+        background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #fff;
+    }
+}
+@media screen and (min-width: 500px) and (max-width: 700px) {
+    #header .menu-block-wrapper .menu .menu-level-1 {
+        width: 48%;
+    }
+    #header .menu-block-wrapper .menu .menu-level-1 a {
+        background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #fff;
+    }
+}
+#header .menu-block-wrapper .menu .menu-level-1 > a {
+    display: block;
+    width: 100%;
+    margin: 0 auto 0.25em;
+    font-size: 0.9em;
+    padding: 10px 15px;
+    text-transform: uppercase;
+}
+#header .menu-block-wrapper .menu .menu-level-1 > a.selected,
+#header .menu-block-wrapper .menu .menu-level-1 > a:hover {
+    background-color: #ecedee;
+}
+.no-touch #header {
+    height: 103px;
+}
+.no-touch .site-menus-container {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 72.88136%;
+    margin-left: 27.11864%;
+}
+.no-touch .site-menus-container:last-child {
+    margin-right: 0;
+}
+@media screen and (min-width: 750px) and (max-width: 980px) {
+    .no-touch .site-menus-container {
+        height: 240px;
+    }
+}
+@media screen and (min-width: 490px) and (max-width: 750px) {
+    .no-touch .site-menus-container {
+        height: 280px;
+    }
+}
+.no-touch .logo-container {
+    position: relative;
+    z-index: 2;
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 18.64407%;
+    margin-left: 0%;
+}
+.no-touch .logo-container:last-child {
+    margin-right: 0;
+}
+.no-touch .logo-container-positioning {
+    position: relative;
+    top: 2px;
+    left: 0;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+    .no-touch .logo-container-positioning {
+        top: 0;
+    }
+}
+.no-touch .logo-container-positioning .logo-container {
+    padding-top: 0;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+    .no-touch .logo-container-positioning .logo-container {
+        float: left;
+        display: block;
+        margin-right: 1.69492%;
+        width: 100%;
+    }
+    .no-touch .logo-container-positioning .logo-container:last-child {
+        margin-right: 0;
+    }
+    .no-touch .logo-container-positioning .logo-container #logo {
+        width: 46% !important;
+    }
+    .no-touch .logo-container-positioning .logo-container #logo img {
+        width: 100%;
+    }
+}
+.no-touch .site-menus-container-positioning {
+    position: relative;
+    top: -60px;
+    z-index: 1;
+}
+.no-touch #site-menus .menu {
+    text-align: right;
+    position: relative;
+}
+@media screen and (min-width: 0) and (max-width: 768px) {
+    .no-touch #header {
+        height: 140px;
+    }
+    .no-touch #header #site-menus .menu {
+        text-align: left;
+    }
+    .no-touch #header .site-menus-container {
+        float: left;
+        display: block;
+        margin-right: 1.69492%;
+        width: 100%;
+        margin-left: 0%;
+    }
+    .no-touch #header .site-menus-container:last-child {
+        margin-right: 0;
+    }
+    .no-touch #header .site-menus-container-positioning {
+        position: relative;
+        top: 0;
+        padding-top: 0;
+    }
+}
+@media screen and (min-width: 490px) and (max-width: 700px) {
+    .no-touch #header {
+        height: 260px;
+    }
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+    .no-touch #header {
+        height: auto;
+    }
+}
+.collapsible-menu #header {
+    top: 0;
+}
+.collapsible-menu #header #block-search-form .container-inline,
+.collapsible-menu #header #search-block-form .container-inline {
+    position: absolute;
+    top: 110px;
+    right: 0;
+}
+.collapsible-menu #header #site-name {
+    position: absolute;
+    top: 0;
+}
+.collapsible-menu #header .logo-container-positioning {
+    position: relative;
+    top: -40px;
+    left: -10px;
+    padding: 0 0 25px;
+}
+.collapsible-menu #header .logo-container-positioning .container {
+    padding-top: 15px;
+}
+@media screen and (min-width: 0) and (max-width: 460px) {
+    .collapsible-menu #header .logo-container-positioning #logo {
+        width: 48% !important;
+        margin-top: 25px;
+        height: 55px;
+    }
+    .collapsible-menu #header .logo-container-positioning #logo img {
+        width: 100%;
+    }
+}
+.collapsible-menu #header .menu-block-wrapper .menu .menu-level-1 {
+    border-left: 4px solid #A3D783;
+}
+.collapsible-menu #header .menu-block-wrapper .menu .menu-level-1 > a {
+    background-color: #2E2925;
+    color: #fff;
+    margin-bottom: 0;
+    padding-bottom: 0.5em;
+}
+.collapsible-menu #header .site-menus-container-positioning {
+    background: #ecedee;
+    color: #fff;
+    padding-top: 0;
+}
+.collapsible-menu #header .site-menus-container ul {
+    margin-top: 0.75em;
+}
+.collapsible-menu #header .menu-button-wrapper {
+    position: relative;
+}
+.collapsible-menu #header .menu-button-wrapper .menu-button {
+    position: absolute;
+    right: -15px;
+    top: 10px;
+    border: 1px solid #ccc;
+    background: #ddd;
+    padding: 4px 14px;
+    border-radius: 8px 8px 8px 8px;
+    -moz-border-radius: 8px 8px 8px 8px;
+    -webkit-border-top-left-radius: 8px;
+    -webkit-border-top-right-radius: 8px;
+    -webkit-border-bottom-right-radius: 8px;
+    -webkit-border-bottom-left-radius: 8px;
+    color: #2E2925;
+    cursor: pointer;
+}
+.collapsible-menu #header #block-menu-menu-social-links {
+    position: relative;
+    right: 0;
+    top: 70px;
+}
+.collapsible-menu #header #block-search-form .block-inner .container-inline,
+.collapsible-menu #header #search-block-form .block-inner .container-inline {
+    right: 0;
+    top: 75px;
+}
+#menu-tray {
+    width: 100%;
+    min-height: 20px;
+    position: absolute;
+    z-index: 1000;
+    top: 0;
+    background: #ecedee;
+    color: #fff;
+    opacity: 0;
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+    box-shadow: 0 8px 7px 1px rgba(0, 0, 0, 0.45);
+}
+#menu-tray .initially-hidden {
+    opacity: 0;
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+}
+#menu-tray.menu-tray-transition {
+    -webkit-transition: opacity 0.25s ease-in-out;
+    -moz-transition: opacity 0.25s ease-in-out;
+    transition: opacity 0.25s ease-in-out;
+    pointer-events: auto;
+    opacity: 1;
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
+}
+#menu-tray .menu {
+    padding: 20px;
+}
+#menu-tray.docked {
+    pointer-events: none;
+    opacity: 0;
+    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
+}
+#menu-tray a {
+    color: #fff;
+    word-wrap: break-word;
+}
+#menu-tray .menu .menu-level-2 {
+    display: inline-block;
+    vertical-align: top;
+    width: 25%;
+    margin: 0 0 0.25em 0;
+    padding: 0;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+    #menu-tray .menu .menu-level-2 {
+        width: 100%;
+    }
+}
+@media screen and (min-width: 500px) and (max-width: 700px) {
+    #menu-tray .menu .menu-level-2 {
+        width: 50%;
+    }
+}
+@media screen and (min-width: 700px) and (max-width: 900px) {
+    #menu-tray .menu .menu-level-2 {
+        width: 33.33%;
+    }
+}
+#menu-tray .menu .menu-level-2 > a {
+    display: block;
+    background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #2E2925;
+    border-left: 5px solid #086960;
+    width: 99%;
+    margin: 0 auto 0.25em;
+    padding: 7px;
+}
+#menu-tray .menu .menu-level-2 .link-order-1 {
+    border-left: 5px solid #a4d384;
+}
+#menu-tray .menu .menu-level-2 .link-order-2 {
+    border-left: 5px solid #00adbb;
+}
+#menu-tray .menu .menu-level-2 .menu {
+    font-size: 12px;
+    padding-top: 0;
+    padding-bottom: 0;
+}
+#menu-tray .menu .menu-level-2 .menu li {
+    list-style: none;
+}
+#menu-tray .menu .menu-level-2 .menu a {
+    color: #000;
+    border-left: none;
+}
+#footer .menu-block-wrapper {
+    background: none;
+}
+#footer .menu-block-wrapper .menu li {
+    list-style: none;
+    margin: 0;
+}
+#footer .menu-block-wrapper .menu li a {
+    word-wrap: break-word;
+}
+#footer .menu-block-wrapper .menu .menu-level-1 {
+    display: inline-block;
+    vertical-align: top;
+    width: 25%;
+    margin: 0 0 3em 0;
+    padding: 0;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+    #footer .menu-block-wrapper .menu .menu-level-1 {
+        width: 100%;
+    }
+}
+@media screen and (min-width: 500px) and (max-width: 700px) {
+    #footer .menu-block-wrapper .menu .menu-level-1 {
+        width: 50%;
+    }
+}
+@media screen and (min-width: 700px) and (max-width: 900px) {
+    #footer .menu-block-wrapper .menu .menu-level-1 {
+        width: 33.33%;
+    }
+}
+#footer .menu-block-wrapper .menu .menu-level-1 > a {
+    display: block;
+    width: 95%;
+    margin: 0 auto 0.25em;
+    padding: 7px;
+    text-transform: uppercase;
+    background: url(/sites/all/themes/opnfv/images/optimized/nav-arrow.png) no-repeat right center #0095a2;
+    border-left: 5px solid #007e88;
+    color: #fff;
+}
+#footer .menu-block-wrapper .menu .menu-level-2 {
+    width: 85%;
+    margin: 0 auto;
+    font-size: 15px;
+    list-style: square;
+    color: #009fac;
+}
+#footer .menu-block-wrapper .menu .menu-level-2 a {
+    color: #fff;
+    display: inline-block;
+    padding-bottom: 7px;
+    word-wrap: break-word;
+}
+#footer .menu-block-wrapper .menu .menu-level-2 > .menu {
+    border-bottom: 1px solid #008792;
+    margin-bottom: 1.2em;
+}
+#footer .menu-block-wrapper .menu .menu-level-3 {
+    display: block;
+    width: 100%;
+    padding-left: 1.6em;
+    border-top: 1px solid #009fac;
+    background: #00a8b6;
+}
+#footer .menu-block-wrapper .menu .menu-level-3 a {
+    font-size: 0.75em;
+}
+#block-menu_block-1 ul ul,
+#block-menu_block-1 .menu-level-2,
+#footer #block-menu_block-3 .block-title {
+    display: none;
+}
+#block-menu-menu-social-links li {
+    float: left;
+    list-style: none;
+}
+.view-blogs .view-header {
+    float: right;
+}
+.view-blogs .view-header a {
+    text-decoration: none;
+}
+.view-blogs .view-header img {
+    vertical-align: text-top;
+}
+body {
+    background: #fff;
+}
+#content {
+    padding: 0 20px;
+    margin-bottom: 2em;
+}
+.breadcrumb a {
+    text-transform: uppercase;
+    font-size: 0.8em;
+    padding: 0 0.35em;
+}
+.breadcrumb a:first-child {
+    padding-left: 0;
+}
+.blog_usernames_blog a,
+.node-readmore a {
+    color: #A3D783;
+    text-transform: uppercase;
+}
+#page {
+    margin-bottom: 0;
+    padding-bottom: 0;
+}
+.collaborative-projects {
+    background-image: -webkit-linear-gradient(right center, #D0D1D1 0, #E6E6E6 69%);
+    background-image: linear-gradient(to left, #D0D1D1 0, #E6E6E6 69%);
+    background-color: #D0D1D1;
+    min-height: 30px;
+}
+.collaborative-projects .gray-diagonal {
+    min-height: 30px;
+    width: 100%;
+    background: url(/opnfv-dashboard/media/diagonal-white.png) transparent repeat scroll top left;
+}
+.collaborative-projects #collaborative-projects-logo {
+    margin-top: 10px;
+    height: 14px;
+    background: url(/opnfv-dashboard/media/collaborative-projects-logo.png) no-repeat scroll 10px center transparent;
+    width: 100%;
+    max-width: 400px;
+    float: left;
+    text-indent: -9000px;
+}
+@media screen and (min-width: 0) and (max-width: 500px) {
+    .collaborative-projects #collaborative-projects-logo {
+        width: 95%;
+        position: relative;
+        left: 8px;
+        background-position: 0 center;
+        -webkit-background-size: contain;
+        -moz-background-size: contain;
+        -o-background-size: contain;
+        background-size: contain;
+    }
+}
+.collaborative-projects #footer-copyright {
+    padding: 16px 0px 16px 20px;
+    font-size: 11px;
+    line-height: 16px;
+    font-weight: 300;
+}
+.collaborative-projects #footer-copyright p {
+    margin: 0;
+    font-family: Helvetica, Arial, "Lucida Grande", sans-serif;
+}
+.collaborative-projects #footer-copyright a {
+    text-decoration: underline;
+    color: #393939;
+}
+.collaborative-projects #footer-copyright a:hover {
+    text-decoration: none;
+    color: #0099EE;
+}
+.page-page-not-found #content {
+    margin: 3em 0 5em;
+    text-align: center;
+}
+.page-page-not-found #content p {
+    line-height: 2em;
+}
+#block-views-members-block_2 .views-row,
+#block-views-members-block_5 .views-row,
+#block-views-members-block_6 .views-row {
+    border-bottom: 1px solid #ccc;
+    margin-bottom: 20px;
+    padding-bottom: 20px;
+}
+#block-views-members-block_2 .member-info,
+#block-views-members-block_5 .member-info,
+#block-views-members-block_6 .member-info {
+    margin-bottom: 20px;
+    position: relative;
+}
+#block-views-members-block_2 .member-info:after,
+#block-views-members-block_5 .member-info:after,
+#block-views-members-block_6 .member-info:after {
+    content: "";
+    display: table;
+    clear: both;
+}
+#block-views-members-block_2 .member-info .column-last,
+#block-views-members-block_5 .member-info .column-last,
+#block-views-members-block_6 .member-info .column-last {
+    bottom: -3px;
+    left: 0;
+    padding-left: 120px;
+    position: absolute;
+    width: 100%;
+}
+#block-views-members-block_2 .member-info .column-last *,
+#block-views-members-block_5 .member-info .column-last *,
+#block-views-members-block_6 .member-info .column-last * {
+    line-height: 120%;
+    margin: 0;
+}
+#block-views-members-block_2 .member-info img,
+#block-views-members-block_5 .member-info img,
+#block-views-members-block_6 .member-info img {
+    display: block;
+}
+#block-views-members-block_2 .member-info h3,
+#block-views-members-block_5 .member-info h3,
+#block-views-members-block_6 .member-info h3 {
+    font-size: 1em;
+}
+#block-views-members-block_2 .member-info .board-member-title,
+#block-views-members-block_5 .member-info .board-member-title,
+#block-views-members-block_6 .member-info .board-member-title {
+    font-weight: normal;
+    padding-left: 0;
+}
+#block-views-members-block_2 p:last-child,
+#block-views-members-block_5 p:last-child,
+#block-views-members-block_6 p:last-child {
+    margin-bottom: 0;
+}
+#block-views-members-block_2 .member-website,
+#block-views-members-block_5 .member-website,
+#block-views-members-block_6 .member-website {
+    margin-top: 10px;
+}
+.page-news-resources .views-row,
+#block-views-resources-block_1 .views-row {
+    margin-bottom: 10px;
+}
+.page-news-resources .views-row:after,
+#block-views-resources-block_1 .views-row:after {
+    content: "";
+    display: table;
+    clear: both;
+}
+.page-news-resources .views-row .views-field-nothing,
+#block-views-resources-block_1 .views-row .views-field-nothing {
+    float: left;
+    width: 80%;
+}
+.page-news-resources .views-row .views-field-field-thumbnail,
+#block-views-resources-block_1 .views-row .views-field-field-thumbnail {
+    float: right;
+    margin: 5px 0 20px 0;
+    width: 15%;
+}
+.page-news-resources .views-row .views-field-field-thumbnail img,
+#block-views-resources-block_1 .views-row .views-field-field-thumbnail img {
+    display: block;
+    height: auto;
+    max-width: 100%;
+}
+.page-news-resources .view-empty,
+#block-views-resources-block_1 .view-empty {
+    margin-bottom: 1em;
+    padding: 1em;
+}
+.page-news-resources .more-link,
+#block-views-resources-block_1 .more-link {
+    bottom: 2em;
+    font-size: 0.8em;
+    position: absolute;
+    text-align: center;
+    text-transform: uppercase;
+    width: 100%;
+}
+#block-views-collateral-block_1 .views-row {
+    margin-bottom: 10px;
+}
+#block-views-collateral-block_1 .view-empty {
+    margin-bottom: 1em;
+    padding: 1em;
+}
+#block-views-collateral-block_1 .more-link {
+    bottom: 2em;
+    font-size: 0.8em;
+    position: absolute;
+    text-align: center;
+    text-transform: uppercase;
+    width: 100%;
+}
+.not-front #block-views-news_and_announcements-block,
+.not-front #block-views-news_and_announcements-block_1,
+.not-front #block-twitter_helper-twitter_block_1,
+.not-front #block-views-events-block,
+.not-front #block-views-blogs-block,
+.not-front #block-views-faq-faq_recent,
+.not-front #block-views-resources-block_1,
+.not-front #block-views-collateral-block_1,
+.not-front #block-views-videos-block {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 32.20339%;
+    height: 450px;
+    margin-bottom: 2em;
+    margin-right: 5px;
+    background: #ecedee;
+    z-index: 1;
+}
+.not-front #block-views-news_and_announcements-block:last-child,
+.not-front #block-views-news_and_announcements-block_1:last-child,
+.not-front #block-twitter_helper-twitter_block_1:last-child,
+.not-front #block-views-events-block:last-child,
+.not-front #block-views-blogs-block:last-child,
+.not-front #block-views-faq-faq_recent:last-child,
+.not-front #block-views-resources-block_1:last-child,
+.not-front #block-views-collateral-block_1:last-child,
+.not-front #block-views-videos-block:last-child {
+    margin-right: 0;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .not-front #block-views-news_and_announcements-block,
+    .not-front #block-views-news_and_announcements-block_1,
+    .not-front #block-twitter_helper-twitter_block_1,
+    .not-front #block-views-events-block,
+    .not-front #block-views-blogs-block,
+    .not-front #block-views-faq-faq_recent,
+    .not-front #block-views-resources-block_1,
+    .not-front #block-views-collateral-block_1,
+    .not-front #block-views-videos-block {
+        float: left;
+        display: block;
+        margin-right: 1.69492%;
+        width: 100%;
+        height: auto;
+        min-height: 300px;
+    }
+    .not-front #block-views-news_and_announcements-block:last-child,
+    .not-front #block-views-news_and_announcements-block_1:last-child,
+    .not-front #block-twitter_helper-twitter_block_1:last-child,
+    .not-front #block-views-events-block:last-child,
+    .not-front #block-views-blogs-block:last-child,
+    .not-front #block-views-faq-faq_recent:last-child,
+    .not-front #block-views-resources-block_1:last-child,
+    .not-front #block-views-collateral-block_1:last-child,
+    .not-front #block-views-videos-block:last-child {
+        margin-right: 0;
+    }
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper,
+.not-front #block-views-events-block.block-twitter-helper,
+.not-front #block-views-blogs-block.block-twitter-helper,
+.not-front #block-views-faq-faq_recent.block-twitter-helper,
+.not-front #block-views-resources-block_1.block-twitter-helper,
+.not-front #block-views-collateral-block_1.block-twitter-helper,
+.not-front #block-views-videos-block.block-twitter-helper {
+    margin-right: 0;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper .all-tweets,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper .all-tweets,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper .all-tweets,
+.not-front #block-views-events-block.block-twitter-helper .all-tweets,
+.not-front #block-views-blogs-block.block-twitter-helper .all-tweets,
+.not-front #block-views-faq-faq_recent.block-twitter-helper .all-tweets,
+.not-front #block-views-resources-block_1.block-twitter-helper .all-tweets,
+.not-front #block-views-collateral-block_1.block-twitter-helper .all-tweets,
+.not-front #block-views-videos-block.block-twitter-helper .all-tweets {
+    position: absolute;
+    bottom: 2em;
+    left: 0;
+    width: 100%;
+    text-align: center;
+    font-size: 0.8em;
+    text-transform: uppercase;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .not-front #block-views-news_and_announcements-block.block-twitter-helper .all-tweets,
+    .not-front #block-views-news_and_announcements-block_1.block-twitter-helper .all-tweets,
+    .not-front #block-twitter_helper-twitter_block_1.block-twitter-helper .all-tweets,
+    .not-front #block-views-events-block.block-twitter-helper .all-tweets,
+    .not-front #block-views-blogs-block.block-twitter-helper .all-tweets,
+    .not-front #block-views-faq-faq_recent.block-twitter-helper .all-tweets,
+    .not-front #block-views-resources-block_1.block-twitter-helper .all-tweets,
+    .not-front #block-views-collateral-block_1.block-twitter-helper .all-tweets,
+    .not-front #block-views-videos-block.block-twitter-helper .all-tweets {
+        bottom: 0.5em;
+    }
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper .block-title,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper .block-title,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper .block-title,
+.not-front #block-views-events-block.block-twitter-helper .block-title,
+.not-front #block-views-blogs-block.block-twitter-helper .block-title,
+.not-front #block-views-faq-faq_recent.block-twitter-helper .block-title,
+.not-front #block-views-resources-block_1.block-twitter-helper .block-title,
+.not-front #block-views-collateral-block_1.block-twitter-helper .block-title,
+.not-front #block-views-videos-block.block-twitter-helper .block-title {
+    background: #71c48f;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper ul,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper ul,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper ul,
+.not-front #block-views-events-block.block-twitter-helper ul,
+.not-front #block-views-blogs-block.block-twitter-helper ul,
+.not-front #block-views-faq-faq_recent.block-twitter-helper ul,
+.not-front #block-views-resources-block_1.block-twitter-helper ul,
+.not-front #block-views-collateral-block_1.block-twitter-helper ul,
+.not-front #block-views-videos-block.block-twitter-helper ul {
+    list-style: none;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li,
+.not-front #block-views-events-block.block-twitter-helper li,
+.not-front #block-views-blogs-block.block-twitter-helper li,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li,
+.not-front #block-views-resources-block_1.block-twitter-helper li,
+.not-front #block-views-collateral-block_1.block-twitter-helper li,
+.not-front #block-views-videos-block.block-twitter-helper li {
+    margin-bottom: 1em;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li img,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li img,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li img,
+.not-front #block-views-events-block.block-twitter-helper li img,
+.not-front #block-views-blogs-block.block-twitter-helper li img,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li img,
+.not-front #block-views-resources-block_1.block-twitter-helper li img,
+.not-front #block-views-collateral-block_1.block-twitter-helper li img,
+.not-front #block-views-videos-block.block-twitter-helper li img {
+    float: left;
+    margin-right: 1em;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li .tweet_time,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li .tweet_time,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li .tweet_time,
+.not-front #block-views-events-block.block-twitter-helper li .tweet_time,
+.not-front #block-views-blogs-block.block-twitter-helper li .tweet_time,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li .tweet_time,
+.not-front #block-views-resources-block_1.block-twitter-helper li .tweet_time,
+.not-front #block-views-collateral-block_1.block-twitter-helper li .tweet_time,
+.not-front #block-views-videos-block.block-twitter-helper li .tweet_time {
+    display: block;
+    font-size: 0.8em;
+}
+.not-front #block-views-news_and_announcements-block.block-twitter-helper li .tweet_text,
+.not-front #block-views-news_and_announcements-block_1.block-twitter-helper li .tweet_text,
+.not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li .tweet_text,
+.not-front #block-views-events-block.block-twitter-helper li .tweet_text,
+.not-front #block-views-blogs-block.block-twitter-helper li .tweet_text,
+.not-front #block-views-faq-faq_recent.block-twitter-helper li .tweet_text,
+.not-front #block-views-resources-block_1.block-twitter-helper li .tweet_text,
+.not-front #block-views-collateral-block_1.block-twitter-helper li .tweet_text,
+.not-front #block-views-videos-block.block-twitter-helper li .tweet_text {
+    font-size: 10px;
+}
+@media screen and (min-width: 900px) {
+    .not-front #block-views-news_and_announcements-block.block-twitter-helper li .tweet_text,
+    .not-front #block-views-news_and_announcements-block_1.block-twitter-helper li .tweet_text,
+    .not-front #block-twitter_helper-twitter_block_1.block-twitter-helper li .tweet_text,
+    .not-front #block-views-events-block.block-twitter-helper li .tweet_text,
+    .not-front #block-views-blogs-block.block-twitter-helper li .tweet_text,
+    .not-front #block-views-faq-faq_recent.block-twitter-helper li .tweet_text,
+    .not-front #block-views-resources-block_1.block-twitter-helper li .tweet_text,
+    .not-front #block-views-collateral-block_1.block-twitter-helper li .tweet_text,
+    .not-front #block-views-videos-block.block-twitter-helper li .tweet_text {
+        font-size: 0.9em;
+    }
+}
+.not-front #block-views-news_and_announcements-block.block-even .block-title,
+.not-front #block-views-news_and_announcements-block_1.block-even .block-title,
+.not-front #block-twitter_helper-twitter_block_1.block-even .block-title,
+.not-front #block-views-events-block.block-even .block-title,
+.not-front #block-views-blogs-block.block-even .block-title,
+.not-front #block-views-faq-faq_recent.block-even .block-title,
+.not-front #block-views-resources-block_1.block-even .block-title,
+.not-front #block-views-collateral-block_1.block-even .block-title,
+.not-front #block-views-videos-block.block-even .block-title {
+    background: #177870;
+}
+.not-front #block-views-news_and_announcements-block .block-title,
+.not-front #block-views-news_and_announcements-block_1 .block-title,
+.not-front #block-twitter_helper-twitter_block_1 .block-title,
+.not-front #block-views-events-block .block-title,
+.not-front #block-views-blogs-block .block-title,
+.not-front #block-views-faq-faq_recent .block-title,
+.not-front #block-views-resources-block_1 .block-title,
+.not-front #block-views-collateral-block_1 .block-title,
+.not-front #block-views-videos-block .block-title {
+    background: #00ADBB;
+    color: #fff;
+    font-size: 1.25em;
+    padding: .75em .5em;
+    text-align: center;
+    text-transform: uppercase;
+}
+.not-front #block-views-news_and_announcements-block .block-title a,
+.not-front #block-views-news_and_announcements-block_1 .block-title a,
+.not-front #block-twitter_helper-twitter_block_1 .block-title a,
+.not-front #block-views-events-block .block-title a,
+.not-front #block-views-blogs-block .block-title a,
+.not-front #block-views-faq-faq_recent .block-title a,
+.not-front #block-views-resources-block_1 .block-title a,
+.not-front #block-views-collateral-block_1 .block-title a,
+.not-front #block-views-videos-block .block-title a {
+    color: #fff;
+}
+.not-front #block-views-news_and_announcements-block .view-content,
+.not-front #block-views-news_and_announcements-block_1 .view-content,
+.not-front #block-twitter_helper-twitter_block_1 .view-content,
+.not-front #block-views-events-block .view-content,
+.not-front #block-views-blogs-block .view-content,
+.not-front #block-views-faq-faq_recent .view-content,
+.not-front #block-views-resources-block_1 .view-content,
+.not-front #block-views-collateral-block_1 .view-content,
+.not-front #block-views-videos-block .view-content {
+    margin-bottom: 1em;
+    padding: 1em;
+}
+.not-front #block-views-news_and_announcements-block .view-footer,
+.not-front #block-views-news_and_announcements-block_1 .view-footer,
+.not-front #block-twitter_helper-twitter_block_1 .view-footer,
+.not-front #block-views-events-block .view-footer,
+.not-front #block-views-blogs-block .view-footer,
+.not-front #block-views-faq-faq_recent .view-footer,
+.not-front #block-views-resources-block_1 .view-footer,
+.not-front #block-views-collateral-block_1 .view-footer,
+.not-front #block-views-videos-block .view-footer {
+    text-transform: uppercase;
+    font-size: 0.8em;
+    position: absolute;
+    bottom: 2em;
+    width: 100%;
+    text-align: center;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .not-front #block-views-news_and_announcements-block .view-footer,
+    .not-front #block-views-news_and_announcements-block_1 .view-footer,
+    .not-front #block-twitter_helper-twitter_block_1 .view-footer,
+    .not-front #block-views-events-block .view-footer,
+    .not-front #block-views-blogs-block .view-footer,
+    .not-front #block-views-faq-faq_recent .view-footer,
+    .not-front #block-views-resources-block_1 .view-footer,
+    .not-front #block-views-collateral-block_1 .view-footer,
+    .not-front #block-views-videos-block .view-footer {
+        bottom: 0.5em;
+    }
+}
+.not-front .view-blogs .block-row,
+.not-front .view-events .block-row,
+.not-front .view-news-and-announcements .block-row,
+.not-front .view-videos .block-row {
+    margin-bottom: 1.5em;
+}
+.not-front .view-blogs .views-field,
+.not-front .view-events .views-field,
+.not-front .view-news-and-announcements .views-field,
+.not-front .view-videos .views-field {
+    margin-bottom: 0.25em;
+}
+.not-front .view-blogs .views-field-type,
+.not-front .view-blogs .views-field-field-event-address,
+.not-front .view-events .views-field-type,
+.not-front .view-events .views-field-field-event-address,
+.not-front .view-news-and-announcements .views-field-type,
+.not-front .view-news-and-announcements .views-field-field-event-address,
+.not-front .view-videos .views-field-type,
+.not-front .view-videos .views-field-field-event-address {
+    font-style: italic;
+    font-size: 0.95em;
+}
+.not-front .view-blogs .views-field-title,
+.not-front .view-events .views-field-title,
+.not-front .view-news-and-announcements .views-field-title,
+.not-front .view-videos .views-field-title {
+    font-size: 11px;
+}
+@media screen and (min-width: 900px) {
+    .not-front .view-blogs .views-field-title,
+    .not-front .view-events .views-field-title,
+    .not-front .view-news-and-announcements .views-field-title,
+    .not-front .view-videos .views-field-title {
+        font-size: 1.1em;
+    }
+}
+.not-front .view-blogs .views-field-created,
+.not-front .view-events .views-field-created,
+.not-front .view-news-and-announcements .views-field-created,
+.not-front .view-videos .views-field-created {
+    font-style: italic;
+    font-size: 0.8em;
+}
+.not-front #block-views-faq-faq_recent .more-link {
+    position: absolute;
+    width: 100%;
+    text-align: center;
+    bottom: 30px;
+}
+.not-front .view-videos .views-row {
+    display: block;
+    height: 150px;
+    margin-bottom: 1em;
+}
+.not-front .view-videos .view-empty {
+    padding: 1em;
+}
+.not-front .view-videos .views-field-nothing {
+    position: relative;
+    float: left;
+    margin-right: 10px;
+}
+.not-front .view-videos .views-field-nothing a {
+    text-transform: uppercase;
+}
+.not-front .view-videos .views-field-body {
+    margin-top: 20px;
+    font-size: 10px;
+}
+@media screen and (min-width: 900px) {
+    .not-front .view-videos .views-field-body {
+        font-size: 0.9em;
+    }
+}
+.front .title {
+    display: none;
+}
+.front #content {
+    padding-top: 20px;
+    max-width: 68em;
+    margin-left: auto;
+    margin-right: auto;
+}
+.front #content:after {
+    content: "";
+    display: table;
+    clear: both;
+}
+.front .custom_home_block {
+    background: transparent none repeat scroll 0 0 !important;
+    clear: both !important;
+    color: #000 !important;
+    height: auto !important;
+    width: 100% !important;
+    font-size: 0.8em !important;
+    margin-top: 0px !important;
+    margin-bottom: 5px !important;
+}
+.front .custom_home_block .content {
+    padding: 0px !important;
+}
+.front .custom_home_block .content p {
+    margin: 0px;
+}
+.front .custom_home_block img {
+    width: 100%;
+}
+.front .block-views,
+.front .block-block,
+.front .block-twitter-helper {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 32.20339%;
+    height: 500px;
+    margin-bottom: 2em;
+    background: #ecedee;
+}
+.front .block-views:last-child,
+.front .block-block:last-child,
+.front .block-twitter-helper:last-child {
+    margin-right: 0;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .front .block-views,
+    .front .block-block,
+    .front .block-twitter-helper {
+        float: left;
+        display: block;
+        margin-right: 1.69492%;
+        width: 100%;
+        height: auto;
+    }
+    .front .block-views:last-child,
+    .front .block-block:last-child,
+    .front .block-twitter-helper:last-child {
+        margin-right: 0;
+    }
+}
+.front .block-views.block-twitter-helper,
+.front .block-block.block-twitter-helper,
+.front .block-twitter-helper.block-twitter-helper {
+    margin-right: 0;
+}
+.front .block-views.block-twitter-helper .all-tweets,
+.front .block-block.block-twitter-helper .all-tweets,
+.front .block-twitter-helper.block-twitter-helper .all-tweets {
+    position: absolute;
+    bottom: 2em;
+    left: 0;
+    width: 100%;
+    text-align: center;
+    font-size: 0.8em;
+    text-transform: uppercase;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .front .block-views.block-twitter-helper .all-tweets,
+    .front .block-block.block-twitter-helper .all-tweets,
+    .front .block-twitter-helper.block-twitter-helper .all-tweets {
+        bottom: 0.5em;
+    }
+}
+.front .block-views.block-twitter-helper .block-title,
+.front .block-block.block-twitter-helper .block-title,
+.front .block-twitter-helper.block-twitter-helper .block-title {
+    background: #71c48f;
+}
+.front .block-views.block-twitter-helper ul,
+.front .block-block.block-twitter-helper ul,
+.front .block-twitter-helper.block-twitter-helper ul {
+    list-style: none;
+}
+.front .block-views.block-twitter-helper li,
+.front .block-block.block-twitter-helper li,
+.front .block-twitter-helper.block-twitter-helper li {
+    margin-bottom: 1em;
+}
+.front .block-views.block-twitter-helper li img,
+.front .block-block.block-twitter-helper li img,
+.front .block-twitter-helper.block-twitter-helper li img {
+    float: left;
+    margin-right: 1em;
+}
+.front .block-views.block-twitter-helper li .tweet_time,
+.front .block-block.block-twitter-helper li .tweet_time,
+.front .block-twitter-helper.block-twitter-helper li .tweet_time {
+    display: block;
+    font-size: 0.8em;
+}
+.front .block-views.block-twitter-helper li .tweet_text,
+.front .block-block.block-twitter-helper li .tweet_text,
+.front .block-twitter-helper.block-twitter-helper li .tweet_text {
+    font-size: 10px;
+}
+@media screen and (min-width: 900px) {
+    .front .block-views.block-twitter-helper li .tweet_text,
+    .front .block-block.block-twitter-helper li .tweet_text,
+    .front .block-twitter-helper.block-twitter-helper li .tweet_text {
+        font-size: 0.9em;
+    }
+}
+.front .block-views.block-even .block-title,
+.front .block-block.block-even .block-title,
+.front .block-twitter-helper.block-even .block-title {
+    background: #177870;
+}
+.front .block-views .block-title,
+.front .block-block .block-title,
+.front .block-twitter-helper .block-title {
+    background: #00ADBB;
+    color: #fff;
+    font-size: 1.25em;
+    padding: .75em .5em;
+    text-align: center;
+    text-transform: uppercase;
+}
+.front .block-views .block-title a,
+.front .block-block .block-title a,
+.front .block-twitter-helper .block-title a {
+    color: #fff;
+}
+.front .block-views .content,
+.front .block-block .content,
+.front .block-twitter-helper .content {
+    padding: 1em;
+}
+.front .block-views .view-content,
+.front .block-block .view-content,
+.front .block-twitter-helper .view-content {
+    margin-bottom: 1em;
+}
+.front .block-views .view-footer,
+.front .block-block .view-footer,
+.front .block-twitter-helper .view-footer {
+    text-transform: uppercase;
+    font-size: 0.8em;
+    position: absolute;
+    bottom: 2em;
+    width: 100%;
+    text-align: center;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .front .block-views .view-footer,
+    .front .block-block .view-footer,
+    .front .block-twitter-helper .view-footer {
+        bottom: 0.5em;
+    }
+}
+.front #content-area {
+    display: inline-flex;
+    height: auto;
+    flex-flow: row wrap;
+}
+.front #block-block-1,
+.front #block-block-2,
+.front #block-block-3 {
+    background: none;
+    height: auto;
+    display: inline-flex;
+    margin-bottom: 0;
+}
+@media screen and (min-width: 900px) {
+    .front #block-block-1,
+    .front #block-block-2,
+    .front #block-block-3 {
+        width: 32%;
+    }
+}
+.front #block-block-1 .block-title,
+.front #block-block-2 .block-title,
+.front #block-block-3 .block-title {
+    background: transparent;
+    margin-bottom: 0;
+    padding: 0;
+}
+.front #block-block-1 .block-title-link,
+.front #block-block-2 .block-title-link,
+.front #block-block-3 .block-title-link {
+    background-position: 50% 10px;
+    background-repeat: no-repeat;
+    color: #373A36;
+    display: block;
+    margin: auto;
+    padding-top: 110px;
+    width: 100%;
+}
+.front #block-block-1 .content,
+.front #block-block-2 .content,
+.front #block-block-3 .content {
+    text-align: center;
+}
+.front #block-block-1 .block-title-link {
+    background-image: url(/sites/all/themes/opnfv/images/optimized/OPNFV_Icon_About.png);
+}
+.front #block-block-2 .block-title-link {
+    background-image: url(/sites/all/themes/opnfv/images/optimized/OPNFV_Icon_GettingStarted.png);
+}
+.front #block-block-3 {
+    margin-right: 0;
+}
+.front #block-block-3 .block-title-link {
+    background-image: url(/sites/all/themes/opnfv/images/optimized/OPNFV_Icon_GetInvolved.png);
+}
+.front #block-system-main {
+    clear: both;
+}
+.front .view-blogs .views-row,
+.front .view-events .views-row {
+    margin-bottom: 1.5em;
+}
+.front .view-blogs .views-field,
+.front .view-events .views-field {
+    margin-bottom: 0.25em;
+}
+.front .view-blogs .views-field-type,
+.front .view-blogs .views-field-field-event-address,
+.front .view-events .views-field-type,
+.front .view-events .views-field-field-event-address {
+    font-style: italic;
+    font-size: 0.95em;
+}
+.front .view-blogs .views-field-title,
+.front .view-events .views-field-title {
+    font-size: 1.1em;
+}
+.front .view-blogs .views-field-created,
+.front .view-events .views-field-created {
+    font-style: italic;
+    font-size: 0.8em;
+}
+.front #block-views-members_by_level-block,
+.front #block-views-members_by_level-block_1,
+.front #block-views-members-slider {
+    height: 100%;
+    background-color: transparent;
+    width: 100%;
+}
+.front .flex-control-nav {
+    display: none;
+}
+.front .views-field-field-member-logo .field-content {
+    line-height: 40px;
+    text-align: center;
+    width: 100%;
+}
+.front .views-field-field-member-logo .field-content img {
+    display: inline-block;
+    height: auto;
+    max-width: 100%;
+    vertical-align: middle;
+    width: auto;
+}
+#highlighted {
+    margin-bottom: -221px;
+}
+#highlighted .block-views {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 100%;
+    height: auto;
+    margin-bottom: 0;
+    background: none;
+}
+#highlighted .block-views:last-child {
+    margin-right: 0;
+}
+.logged-in #highlighted {
+    margin-bottom: -216px;
+}
+.flexslider {
+    background: transparent;
+    border: none;
+    border-radius: none;
+    box-shadow: none;
+}
+.flexslider .flex-direction-nav a:before {
+    color: #A3D783;
+}
+.flexslider .flex-direction-nav a:hover {
+    text-decoration: none;
+}
+.flexslider .slides {
+    overflow: hidden;
+}
+.flexslider .slides img {
+    height: auto;
+    margin-left: auto;
+    margin-right: auto;
+    max-width: 100%;
+}
+.flexslider .views-field-nothing {
+    display: none;
+}
+.view-front-page-slideshow {
+    max-height: 180px;
+    overflow: hidden;
+}
+@media screen and (min-width: 981px) {
+    .view-front-page-slideshow .flexslider .slides img {
+        height: 180px;
+    }
+}
+a.frontpage-slider__video-link,
+a.frontpage-slider__video-link:visited {
+    display: block;
+    height: 100%;
+    left: 0;
+    position: absolute;
+    overflow: hidden;
+    text-indent: -999em;
+    top: 0;
+    width: 100%;
+    z-index: 1;
+}
+.view-members-by-level .view-content {
+    text-align: center;
+}
+.view-members-by-level .view-content h3 {
+    text-align: left;
+}
+.view-members-by-level .field-content,
+.view-members-by-level .views-field-field-member-logo,
+.view-members-by-level .views-row {
+    display: inline-block;
+}
+.view-members-by-level .flexslider {
+    background-color: transparent;
+}
+.view-members-by-level img {
+    margin-right: 10px;
+}
+@media screen and (min-width: 0) and (max-width: 670px) {
+    .flexslider .views-field-nothing {
+        position: static;
+        opacity: 0.8;
+    }
+    .touch .flexslider .flex-direction-nav {
+        display: none;
+    }
+}
+.view-events .views-row {
+    margin-bottom: 2em;
+}
+.view-events .views-field {
+    margin-bottom: 0.5em;
+}
+.view-events .learn-more {
+    margin: 0.5em 0;
+}
+.view-events .views-field-title {
+    font-size: 1.25em;
+}
+.view-events .views-field-created {
+    font-style: italic;
+    font-size: 0.8em;
+}
+.view-events .views-field-field-event-address {
+    font-style: italic;
+}
+.node-event img {
+    max-width: 100%;
+    height: auto;
+}
+.node-event .field {
+    margin-bottom: 0.5em;
+}
+.node-event .field-label {
+    margin-right: 0.25em;
+    text-transform: uppercase;
+    font-size: 0.8em;
+}
+.node-event .field-name-field-event-image {
+    margin-bottom: 2em;
+    border-bottom: 1px solid #ecedee;
+}
+.node-event .field-name-body {
+    margin: 2em 0;
+    padding-top: 3em;
+    border-top: 1px solid #ecedee;
+}
+.node-event .field-name-body .field-label {
+    margin-bottom: 1em;
+}
+.view-main-menu-links {
+    max-width: 68em;
+    margin-left: auto;
+    margin-right: auto;
+    margin-top: 2em;
+    border-top: 1px solid #ecedee;
+    padding-top: 4em;
+}
+.view-main-menu-links:after {
+    content: "";
+    display: table;
+    clear: both;
+}
+.view-main-menu-links ul {
+    margin: 0;
+    padding: 0;
+}
+.view-main-menu-links ul li {
+    margin: 0;
+    padding: 0;
+}
+.view-main-menu-links .views-row {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 49.15254%;
+    margin-bottom: 4em;
+    padding-right: 4em;
+}
+.view-main-menu-links .views-row:last-child {
+    margin-right: 0;
+}
+@media screen and (min-width: 0) and (max-width: 700px) {
+    .view-main-menu-links .views-row {
+        float: left;
+        display: block;
+        margin-right: 1.69492%;
+        width: 100%;
+        padding-right: 0;
+    }
+    .view-main-menu-links .views-row:last-child {
+        margin-right: 0;
+    }
+}
+.view-main-menu-links .views-row .views-field-title {
+    font-size: 1.5em;
+    margin-bottom: 1em;
+    text-align: center;
+}
+.view-main-menu-links .views-row .views-field-body {
+    margin-bottom: 0.5em;
+}
+.view-main-menu-links .views-row .views-field-description a {
+    display: block;
+    margin-top: 0.5em;
+}
+.view-main-menu-links .views-row.views-row-even {
+    margin-right: 0;
+}
+.view-main-menu-links .views-row.views-row-odd {
+    clear: left;
+}
+body.node-type-landing-page #content-header {
+    left: 0;
+    position: absolute;
+    top: 20px;
+    width: 100%;
+    z-index: 1;
+}
+.node-landing-page {
+    display: block;
+    height: 100%;
+    left: 0;
+    overflow: auto;
+    position: fixed;
+    top: 0;
+    width: 100%;
+}
+.node-landing-page.with-background {
+    background-color: #000;
+    background-position: center bottom;
+    background-repeat: no-repeat;
+    background-size: cover;
+}
+.node-landing-page .content {
+    color: #fff;
+    margin: 50px auto;
+    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
+}
+.node-landing-page .content:after {
+    content: "";
+    display: table;
+    clear: both;
+}
+@media screen and (min-width: 700px) {
+    .node-landing-page .content {
+        margin-top: 120px;
+        max-width: 68em;
+    }
+}
+.node-landing-page .content a,
+.node-landing-page .content a:visited {
+    color: #00ADBB;
+}
+.node-landing-page .content p {
+    color: #fff;
+}
+.node-landing-page .content h2 {
+    text-align: center;
+}
+.node-landing-page .content h2 a,
+.node-landing-page .content h2 a:visited {
+    color: #fff;
+}
+.node-landing-page .content img {
+    display: inline-block;
+    height: auto;
+    max-width: 100%;
+}
+.node-landing-page .field-name-field-header-first-column,
+.node-landing-page .field-name-field-header-last-column,
+.node-landing-page .field-name-field-content-first-column,
+.node-landing-page .field-name-field-content-last-column {
+    padding: 0 20px;
+}
+@media screen and (min-width: 700px) {
+    .node-landing-page .field-name-field-header-first-column,
+    .node-landing-page .field-name-field-header-last-column,
+    .node-landing-page .field-name-field-content-first-column,
+    .node-landing-page .field-name-field-content-last-column {
+        float: left;
+        width: 49%;
+    }
+}
+@media screen and (min-width: 700px) {
+    .node-landing-page .field-name-field-header-first-column,
+    .node-landing-page .field-name-field-content-last-column {
+        float: right;
+    }
+}
+.node-landing-page .field-name-field-content-first-column {
+    margin-bottom: 40px;
+}
+@media screen and (min-width: 700px) {
+    .node-landing-page .field-name-field-content-first-column {
+        margin-bottom: 0;
+    }
+}
+.page-resources-library .view-resources .views-row {
+    margin-bottom: 2em;
+}
+.page-resources-library .view-resources .opnfv-resources-date {
+    font-style: italic;
+    font-size: 0.8em;
+}
+.page-resources-library .view-resources .opnfv-resources-title {
+    font-size: 1.25em;
+    margin-bottom: 0.5em;
+}
+.page-resources-library .view-resources .views-field-field-thumbnail {
+    float: right;
+    margin: 5px 0 20px 20px;
+}
+.page-resources-library .view-resources .views-field-field-thumbnail img {
+    display: block;
+    height: auto;
+    max-width: 100%;
+}
+.page-blog .view-blogs .views-row,
+.page-news-faq-blog .view-blogs .views-row {
+    margin-bottom: 2em;
+}
+.page-blog .view-blogs .views-field,
+.page-news-faq-blog .view-blogs .views-field {
+    margin-bottom: 0.5em;
+}
+.page-blog .view-blogs .learn-more,
+.page-news-faq-blog .view-blogs .learn-more {
+    margin: 0.5em 0;
+}
+.page-blog .view-blogs .views-field-title,
+.page-news-faq-blog .view-blogs .views-field-title {
+    font-size: 1.25em;
+}
+.page-blog .view-blogs .views-field-created,
+.page-news-faq-blog .view-blogs .views-field-created {
+    font-style: italic;
+    font-size: 0.8em;
+}
+.node-blog {
+    margin-bottom: 3em;
+}
+.node-blog .addtoany_list {
+    display: block;
+    padding-bottom: 20px;
+}
+.node-blog .blog_usernames_blog {
+    display: none;
+}
+#header #header-region {
+    clear: both;
+}
+#header .container {
+    padding: 0px 20px 0;
+}
+#header #logo {
+    float: left;
+    display: block;
+    margin-right: 1.69492%;
+    width: 18.64407%;
+    margin-bottom: 1em;
+}
+#header #logo:last-child {
+    margin-right: 0;
+}
+#header #site-name {
+    margin-top: 20px;
+}
+#header #site-name a {
+    text-indent: -9999px;
+    display: inline-block;
+}
+.banner-top {
+    background-color: #fff;
+    height: 180px;
+}
+@media screen and (min-width: 980px) {
+    .banner-top {
+        background: #00ADBB url(/sites/all/themes/opnfv/images/optimized/banner-bg.png) repeat 50% 50%;
+        height: 180px;
+    }
+}
+.sidebar .block {
+    margin-bottom: 1.5em;
+    background: #ecedee;
+}
+.sidebar .block .content {
+    padding: 0 1em .5em;
+}
+.sidebar .block .view-content {
+    margin-bottom: 1em;
+}
+.sidebar .block .view-footer {
+    text-transform: uppercase;
+    font-size: 0.8em;
+}
+.sidebar .block-title {
+    font-size: 1em;
+    color: #fff;
+    background: #177870;
+    padding: .5em;
+}
+#footer {
+    background: #00ADBB;
+}
+#footer .container {
+    padding: 20px 20px 30px;
+}
+#footer #block-block-4 {
+    display: none;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+    display: block;
+}
+audio,
+canvas,
+video {
+    display: inline-block;
+}
+audio:not([controls]) {
+    display: none;
+    height: 0;
+}
+[hidden] {
+    display: none;
+}
+html {
+    font-family: sans-serif;
+    -webkit-text-size-adjust: 100%;
+    -ms-text-size-adjust: 100%;
+}
+body {
+    margin: 0;
+}
+a:focus {
+    outline: thin dotted;
+}
+a:active,
+a:hover {
+    outline: 0;
+}
+h1 {
+    font-size: 2em;
+    margin: 0.67em 0;
+}
+abbr[title] {
+    border-bottom: 1px dotted;
+}
+b,
+strong {
+    font-weight: bold;
+}
+dfn {
+    font-style: italic;
+}
+hr {
+    -moz-box-sizing: content-box;
+    box-sizing: content-box;
+    height: 0;
+}
+mark {
+    background: #ff0;
+    color: #000;
+}
+code,
+kbd,
+pre,
+samp {
+    font-family: monospace, serif;
+    font-size: 1em;
+}
+pre {
+    white-space: pre-wrap;
+}
+q {
+    quotes: "\201C" "\201D" "\2018" "\2019";
+}
+small {
+    font-size: 80%;
+}
+sub,
+sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline;
+}
+sup {
+    top: -0.5em;
+}
+sub {
+    bottom: -0.25em;
+}
+img {
+    border: 0;
+}
+svg:not(:root) {
+    overflow: hidden;
+}
+figure {
+    margin: 0;
+}
+fieldset {
+    border: 1px solid #c0c0c0;
+    margin: 0 2px;
+    padding: 0.35em 0.625em 0.75em;
+}
+legend {
+    border: 0;
+    padding: 0;
+}
+button,
+input,
+select,
+textarea {
+    font-family: inherit;
+    font-size: 100%;
+    margin: 0;
+}
+button,
+input {
+    line-height: normal;
+}
+button,
+select {
+    text-transform: none;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+    -webkit-appearance: button;
+    cursor: pointer;
+}
+button[disabled],
+html input[disabled] {
+    cursor: default;
+}
+input[type="checkbox"],
+input[type="radio"] {
+    box-sizing: border-box;
+    padding: 0;
+}
+input[type="search"] {
+    -webkit-appearance: textfield;
+    -moz-box-sizing: content-box;
+    -webkit-box-sizing: content-box;
+    box-sizing: content-box;
+}
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+    border: 0;
+    padding: 0;
+}
+textarea {
+    overflow: auto;
+    vertical-align: top;
+}
+table {
+    border-collapse: collapse;
+    border-spacing: 0;
+}
diff --git a/tools/infra-dashboard/css/source-sans-pro.css b/tools/infra-dashboard/css/source-sans-pro.css
new file mode 100644 (file)
index 0000000..91314f0
--- /dev/null
@@ -0,0 +1,96 @@
+/* vietnamese */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 200;
+  src: local('Source Sans Pro ExtraLight'), local('SourceSansPro-ExtraLight'), url(../fonts/SourceSansPro-ExtraLight.ttf) format('truetype');
+  unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 200;
+  src: local('Source Sans Pro ExtraLight'), local('SourceSansPro-ExtraLight'), url(../fonts/SourceSansPro-ExtraLight.ttf) format('truetype');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 200;
+  src: local('Source Sans Pro ExtraLight'), local('SourceSansPro-ExtraLight'), url(../fonts/SourceSansPro-ExtraLight.ttf) format('truetype');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(../fonts/SourceSansPro-Regular.ttf) format('truetype');
+  unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(../fonts/SourceSansPro-Regular.ttf) format('truetype');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(../fonts/SourceSansPro-Regular.ttf) format('truetype');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(../fonts/SourceSansPro-Bold.ttf) format('truetype');
+  unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(../fonts/SourceSansPro-Bold.ttf) format('truetype');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(../fonts/SourceSansPro-Bold.ttf) format('truetype');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
+/* vietnamese */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url(../fonts/SourceSansPro-Italic.ttf) format('truetype');
+  unicode-range: U+0102-0103, U+1EA0-1EF1, U+20AB;
+}
+/* latin-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url(../fonts/SourceSansPro-Italic.ttf) format('truetype');
+  unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Source Sans Pro Italic'), local('SourceSansPro-It'), url(../fonts/SourceSansPro-Italic.ttf) format('truetype');
+  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
+}
diff --git a/tools/infra-dashboard/css/template.css b/tools/infra-dashboard/css/template.css
new file mode 100644 (file)
index 0000000..688ac04
--- /dev/null
@@ -0,0 +1,802 @@
+/*
+*/
+body {
+  background-color: #15151d;
+  color: #313131;
+  font-family: 'Source Sans Pro', sans-serif;
+  font-size: 16px;
+  min-width: 300px;
+}
+h1,
+.h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+  font-weight: bolder;
+  text-transform: uppercase;
+}
+h2,
+.h2 {
+  font-size: 1.5em;
+  margin: 0.83em 0;
+  font-weight: bolder;
+  text-transform: uppercase;
+}
+h3 {
+  font-size: 1.17em;
+  margin: 1em 0;
+  font-weight: bolder;
+  text-transform: uppercase;
+}
+h4 {
+  font-weight: bolder;
+  text-transform: uppercase;
+}
+h5 {
+
+  font-weight: bolder;
+  text-transform: uppercase;
+}
+p {
+  font-size: 16px;
+}
+a {
+  color: #68CDA0;
+  outline: none;
+  text-decoration: none;
+}
+ul,
+ol {
+  padding: 0;
+  margin: 0 0 10px 25px;
+}
+.table-responsive {
+  overflow-x: auto;
+}
+img {
+  max-width: 100%;
+}
+a:focus,
+a:hover {
+  color: #90ef7f;
+  text-decoration: none;
+}
+.btn {
+  text-transform: uppercase;
+  font-weight: bolder;
+}
+.btn-default {
+  background-color: #90ef7f;
+  color: #313131;
+  border: 0;
+  border-radius: 2px;
+}
+.btn-default:hover {
+  background-color: #90ef7f;
+  color: #000000;
+}
+.btn-sm {
+  padding: 13px 18px;
+}
+button,
+.button {
+  font-size: 12px;
+  line-height: 1;
+  display: inline-block;
+  text-transform: uppercase;
+  font-weight: bolder;
+  padding: 16px 18px;
+  background-color: #90ef7f;
+  color: #313131;
+  border: 0;
+  border-radius: 2px;
+  margin: 1px;
+  text-align: center;
+}
+button:hover,
+.button:hover {
+  background-color: #90ef7f;
+  color: #000000;
+}
+.pagination>li>a, .pagination>li>span {
+  color: #FFFFFF;
+  background-color: #0095A2;
+  border: 1px solid silver;
+}
+.pagination>li>a:hover, .pagination>li>span:hover, .pagination>li>a:focus, .pagination>li>span:focus {
+  color: white;
+  background-color: #68CDA0;
+}
+.pagination>li>span:hover, .pagination>li>span:focus {
+  background-color: #0095A2;
+}
+/*
+* Menu
+*/
+#menu {
+  background-color: #007E88;
+  color: #ffffff;
+}
+#menu #menu-second a {
+  font-size: 12px;
+}
+#menu-second .navbar-nav>li>a {
+  padding-bottom: 5px;
+}
+#menu-second .navbar-nav>li>a {
+  padding-top: 10px;
+}
+#menu .navbar-default {
+  background-color: #FFFFFF;
+  color: inherit;
+  border-radius: 0;
+  border: 0;
+  margin-bottom: 0;
+}
+#menu .navbar-brand {
+  height: auto;
+  max-width: 360px;
+  margin: -0 0;
+  padding: 0;
+}
+#menu .navbar-brand img {
+  max-width: 100%;
+  width: 100%;
+  height: auto;
+}
+#menu a {
+  font-size: 20px;
+  font-weight: normal;
+  color: #000000;
+  text-transform: uppercase;
+}
+#menu a:hover {
+  color: #90ef7f;
+}
+#menu .navbar-default .navbar-nav>.active>a,
+#menu .navbar-default .navbar-nav>.active>a:hover,
+#menu .navbar-default .navbar-nav>.active>a:focus {
+  background-color: inherit;
+  color: #68CDA0;
+  font-weight: bolder;
+}
+#menu .navbar-default .navbar-nav>.open>a,
+#menu .navbar-default .navbar-nav>.open>a:hover,
+#menu .navbar-default .navbar-nav>.open>a:focus {
+  background-color: #ECEDEE;
+  color: #000000;
+}
+#menu .dropdown-menu {
+  background-color: #ECEDEE;
+  margin-top: -1px;
+  margin-left: -1px;
+  border-radius: 0;
+}
+#menu .dropdown-menu>li>a:hover,
+#menu .dropdown-menu>li>a:focus {
+  background-color: inherit;
+}
+#menu .dropdown-menu>.active>a,
+#menu .dropdown-menu>.active>a:hover,
+#menu .dropdown-menu>.active>a:focus {
+  background-color: inherit;
+  font-weight: bold;
+}
+#menu .dropdown-menu>li>a {
+  font-size: 16px;
+  text-transform: none;
+}
+
+.highcharts-iframe {
+  border: none;
+}
+
+/*
+*Embedded youtube
+*/
+.video-slot {
+  padding: 2em;
+}
+.video-container-outer {
+  max-width: 800px;
+  margin: 0 auto;
+}
+.video-container {
+  position:relative;
+  padding-bottom:56.25%;
+  height:0;
+  overflow:hidden;
+}
+.video-container iframe {
+  position:absolute;
+  top:0;
+  left:0;
+  width:100%;
+  height:100%;
+}
+/*
+* End: Menu
+*/
+.item-page {
+  max-width: 760px;
+  margin: auto;
+}
+.item-page img {
+  margin-bottom: 30px;
+}
+
+.image-wrapper {
+  background-color: #f9f9f9;
+  padding: 30px;
+  margin-bottom: 30px;
+  text-align: center;
+}
+.image-wrapper img {
+  margin: 0;
+}
+
+.blog img {
+  margin-bottom: 30px;
+}
+
+.article-info {
+  font-size: 14px;
+  color: silver;
+  margin-bottom: 20px;
+}
+
+dl.article-info dt {
+    display: none;
+}
+
+dl.article-info dd {
+    font-size: 14px;
+    display: inline;
+    color: silver;
+    margin-bottom: 20px;
+}
+
+dl.article-info dd:after {
+    content: " \25CF ";
+}
+
+dl.article-info dd:last-child:after {
+    content: none;
+}
+
+hr {
+  width: 100%;
+  border-top: 1px solid silver;
+  display: inline-block;
+}
+
+
+
+/*
+* Component
+#000000
+*/
+#hs-component {
+  background-color: #000000;
+  padding: 30px 0;
+}
+#hs-component .container {
+  background-color: white;
+  padding: 30px;
+}
+/*
+* End: Component
+*/
+/*
+* Footer
+*/
+footer {
+  background-color: #15151d;
+  color: #000000;
+}
+#footer .container {
+  padding: 30px 0;
+}
+#footer a {
+  color: inherit;
+}
+#footer a:hover {
+  color: #90ef7f;
+}
+#footer .socials a {
+  margin-left: 10px;
+}
+/*
+* End: Footer
+*/
+
+/*
+======
+=== Responsive CSS
+======
+*/
+@media screen and (min-width: 768px) {
+  #footer .socials {
+    text-align: right;
+  }
+  #menu .container {
+    padding: 0;
+  }
+  #menu span.toggle-arrow {
+    display: none;
+  }
+  #menu-container {
+    width: 600px;
+    float: right;
+  }
+  #menu .dropdown.active:hover>a,
+  #menu .dropdown.active:hover>a:hover,
+  #menu .dropdown:hover>a {
+    background-color: #ECEDEE;
+    color: #000000;
+  }
+  #menu .dropdown:hover>.dropdown-menu {
+    display: block;
+  }
+  #menu .dropdown-menu {
+    padding: 5px 20px;
+  }
+  #menu .dropdown-menu a {
+    border-bottom: 2px solid #007E88;
+    text-align: right;
+    padding: 10px 0;
+  }
+  #menu .dropdown-menu li:last-child a {
+    border-bottom: 0;
+  }
+  #menu .collapse.navbar-collapse {
+    padding: 0;
+  }
+}
+@media screen and (max-width: 767px) {
+  #menu .container {
+    padding-bottom: 30px;
+    padding-top: 30px;
+  }
+  #menu .navbar-header {
+    position: relative;
+    margin: 0;
+  }
+  #menu .navbar-header .navbar-toggle {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+    margin: 0;
+    height: 40px;
+    width: 40px;
+    font-size: 30px;
+    text-align: center;
+    padding: 0;
+    color: #000000;
+    border: 0;
+    border-radius: 0;
+    cursor: pointer;
+    -moz-transform: rotate(-180deg);
+    -moz-transform: rotate(-180deg);
+    -moz-transition: -moz-transform 250ms ease-out 0s;
+    -ms-transform: rotate(-180deg);
+    -o-transform: rotate(-180deg);
+    -o-transition: -o-transform 250ms ease-out 0s;
+    -webkit-transform: rotate(-180deg);
+    -webkit-transition: -webkit-transform 250ms ease-out 0s;
+    transform: rotate(-180deg);
+    transition: transform 250ms ease-out 0s;
+    background-color: #ECEDEE;
+  }
+  #menu .navbar-header .navbar-toggle:hover {
+    color: #90ef7f;
+  }
+  #menu .navbar-header .navbar-toggle.collapsed {
+    -moz-transform: rotate(0deg);
+    -moz-transition: -moz-transform 250ms ease-out 0s;
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+    -o-transition: -o-transform 250ms ease-out 0s;
+    -webkit-transform: rotate(0deg);
+    -webkit-transition: -webkit-transform 250ms ease-out 0s;
+    transform: rotate(0deg);
+    transition: transform 250ms ease-out 0s;
+    background-color: inherit;
+  }
+  #menu .navbar-header .navbar-toggle.collapsed:after {
+    font-family: "FontAwesome";
+    content: '\f0c9';
+  }
+  #menu .navbar-header .navbar-toggle:after {
+    font-family: "FontAwesome";
+    content: '\f00d';
+  }
+  #menu .navbar-brand {
+    width: 75%;
+  }
+  #menu .dropdown span.toggle-arrow:after {
+    font-family: "FontAwesome";
+    content: '\f105';
+  }
+  #menu .dropdown.open span.toggle-arrow:after {
+    font-family: "FontAwesome";
+    content: '\f107';
+  }
+  #menu .navbar-collapse {
+    border: 0;
+    background-color: #ECEDEE;
+    text-align: right;
+    margin: 0;
+  }
+  #menu ul.navbar-nav {
+    float: none !important;
+  }
+  #menu .dropdown-menu {
+    text-align: right;
+  }
+  #menu .navbar-nav {
+    margin: 0 -15px;
+  }
+  #menu .nav>li {
+    border-bottom: 2px solid #007E88;
+  }
+  #hs-component {
+    padding: 0;
+  }
+  #footer {
+    text-align: center;
+  }
+}
+
+/*
+==================
+=== About Us page
+==================
+* TODO: find better logic for this
+*/
+#about-us h1,
+#about-us h2 {
+  text-align: center;
+}
+#about-us h2 {
+  margin-top: 2em;
+}
+#about-us #company-container img {
+  width: 100%;
+}
+#about-us .image-container {
+  text-align: center;
+}
+/* Company section */
+
+/* Products section */
+#about-us #products-container div > div {
+  margin-top: 20px;
+  margin-bottom: 10px;
+}
+
+/* Numbers section */
+#about-us #numbers-container {
+  text-align: center;
+}
+#about-us #numbers-container p {
+  padding: 15px;
+  background-color: #ddd;
+  width: 220px;
+  min-height: 130px;
+  margin: 15px;
+  display: inline-block;
+  vertical-align: top;
+  text-align: left;
+  box-sizing: content-box;
+}
+
+/* Employee section */
+#about-us #employees-container {
+  text-align: center;
+}
+#about-us .employee {
+  display: inline-block;
+  width: 210px;
+  margin: 15px;
+  vertical-align: top;
+}
+#about-us .employee .image-container {
+  padding: 5px;
+}
+#about-us .employee .image-container.no-image {
+  height: 210px;
+}
+#about-us .employee .image-container img {
+  max-height: 200px;
+  max-width: 200px;
+  background-color: #000000;
+  margin-bottom: 0;
+}
+#about-us .employee .description p {
+  text-align: left;
+}
+#about-us .employee p.name {
+  font-weight: bold;
+  font-size: 18px;
+  text-align: center;
+}
+
+/* Facts section */
+#about-us #facts-container {
+  word-break: break-word;
+  /*text-align: center;*/
+  width: 50%;
+  float: left;
+}
+
+/* Press section */
+#about-us #press-container {
+  width: 50%;
+  float: left;
+}
+@media screen and (max-width: 767px) {
+  #about-us .employee .image-container.no-image {
+    height: auto;
+  }
+  #about-us #facts-container,
+  #about-us #press-container {
+    width: 100%;
+    float: none;
+  }
+  #about-us #numbers-container p {
+    width: auto;
+    min-height: 0px !important;
+  }
+}
+/*
+=======================
+=== END About Us page
+=======================
+*/
+/*
+=======================
+=== Product Pages
+=======================
+*/
+.text-center {
+  text-align: center;
+}
+.product-buttons .button {
+  max-width: 200px;
+  width: 100%;
+}
+/*
+=======================
+=== END Product Pages
+=======================
+*/
+.datatable th, .datatable td {
+  border: 1px solid silver;
+  padding: 2px 5px;
+}
+.datatable th {
+  text-align: left;
+}
+.intro h4 {
+  clear: both;
+  padding-top: 1em;
+}
+.intro img {
+  margin-bottom: 1em;
+  border: 1px solid silver;
+  max-width: 164px;
+}
+.intro img.float-left {
+  margin-right: 2em;
+}
+.intro img.float-right {
+  margin-left: 2em;
+}
+.float-right {
+  float: right;
+}
+.float-left {
+  float: left;
+}
+table.category {
+  width: 100%;
+}
+table.category .cat-list-row1 {
+  background-color: #000000;
+}
+
+/*
+====
+===
+====
+*/
+
+/* Sidebar */
+#hs-component > div.sidebar-container {
+  padding: 0;
+}
+.nav-sidebar {
+  margin: 0 -15px;
+}
+.nav-sidebar > li {
+  display: block;
+  background-color: #007E88;
+  border-top: 1px solid #0095A2;
+  color: white;
+  font-size: 20px;
+  font-weight: 200;
+  text-transform: uppercase;
+}
+.nav-sidebar > li:last-child {
+  border-bottom: 1px solid #0095A2;
+}
+.nav-sidebar > li.active {
+  background-color: #0095A2;
+  font-weight: bold;
+}
+.nav-sidebar > li > div {
+  position: relative;
+  padding: 10px;
+  padding-right: 40px;
+  cursor: pointer;
+}
+/* Toggle */
+.toggle {
+  position: absolute;
+  right: 10px;
+  top: 10px;
+}
+.active > .toggle::after {
+  font-family: "FontAwesome";
+  content: '\f107';
+}
+.toggle::after {
+  font-family: "FontAwesome";
+  content: '\f105';
+}
+.nav-sidebar > li > ul {
+  /*display: none;*/
+}
+.nav-sidebar > li > ul {
+  /*display: block;*/
+  list-style: none;
+  margin: 0;
+}
+.nav-sidebar > li > ul > li {
+  border-top: 1px solid #007E88;
+  text-transform: none;
+  padding: 10px;
+  line-height: 1;
+}
+.nav-sidebar > li.active > ul > li.active::after {
+    content: '';
+    width: 0;
+    height: 0;
+    position: absolute;
+    right: -10px;
+    border-top: 10px solid transparent;
+    border-bottom: 10px solid transparent;
+    border-left: 10px solid #0095A2;
+    z-index: 1;
+}
+.nav-sidebar > li > ul > li > a {
+  color: white;
+  font-size: 16px;
+  font-weight: 100;
+}
+.nav-sidebar > li.active > ul > li.active > a {
+  color: #68CDA0;
+  font-weight: bold;
+}
+.nav-sidebar > li.active > ul > li > a:focus,
+.nav-sidebar > li.active > ul > li > a:hover {
+  color: #90ef7f;
+}
+.sidebar-wrapper {
+  position: relative;
+}
+.sidebar {
+  z-index: 2;
+  background-color: #007E88;
+}
+.sidebar-eq {
+  z-index: 1;
+  background-color: white;
+}
+.sidebar-fill {
+  position: absolute;
+  height: 100%;
+  background-color: #007E88;
+  left: 0;
+  top: 0;
+  z-index: 0;
+}
+.sidebar-eq-fill {
+  position: absolute;
+  height: 100%;
+  background-color: white;
+  right: 0;
+  top: 0;
+  z-index: 0;
+}
+@media screen and (max-width: 500px) {
+  .intro img {
+    display: none;
+  }
+}
+@media screen and (max-width: 768px) {
+  .sidebar-wrapper {
+    position: relative;
+    overflow: hidden;
+  }
+  .sidebar-wrapper .sidebar {
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 9;
+    visibility: hidden;
+    height: 100%;
+    overflow-x: hidden;
+    overflow-y: auto;
+    -webkit-transition: all 0.5s;
+    transition: all 0.5s;
+  }
+  .sidebar-wrapper.toggled .sidebar {
+    visibility: visible;
+    -webkit-transition: -webkit-transform 0.5s;
+    transition: transform 0.5s;
+  }
+  .toggled .nav-sidebar > li.active > ul > li.active::after {
+    display: none;
+  }
+  .sidebar-wrapper .sidebar-eq {
+    position: relative;
+    left: 0;
+    z-index: 10;
+    padding-top: 42px;
+    /*height: 100%;*/
+    -webkit-transition: -webkit-transform 0.5s;
+    transition: transform 0.5s;
+  }
+  .sidebar-wrapper.toggled .sidebar-eq {
+    -webkit-transform: translate3d(75%, 0, 0);
+    transform: translate3d(75%, 0, 0);
+  }
+  .sidebar-wrapper.toggled .sidebar-eq:after {
+    width: 100%;
+    height: 100%;
+    opacity: 1;
+    -webkit-transition: opacity 0.5s;
+    transition: opacity 0.5s;
+  }
+  .sidebar-wrapper .sidebar-eq:after {
+    position: absolute;
+    top: 0;
+    right: 0;
+    width: 0;
+    height: 0;
+    background: rgba(0,0,0,0.2);
+    content: '';
+    opacity: 0;
+    -webkit-transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s;
+    transition: opacity 0.5s, width 0.1s 0.5s, height 0.1s 0.5s;
+  }
+  #sidebar-toggle {
+    position: absolute;
+    top: 0;
+    left: 0;
+    background-color: #007E88;
+    color: white;
+    border-radius: 0;
+    z-index: 100;
+    cursor: pointer;
+    padding: 10px;
+    visibility: visible;
+  }
+  #sidebar-toggle:after {
+    content: "Show Sidebar";
+  }
+  .toggled #sidebar-toggle:after {
+    content: "Hide Sidebar";
+  }
+}
diff --git a/tools/infra-dashboard/css/theme.css b/tools/infra-dashboard/css/theme.css
new file mode 100644 (file)
index 0000000..59ae59f
--- /dev/null
@@ -0,0 +1,387 @@
+.container {
+    width: 100%
+}
+#hs-component {
+    background-color: inherit;
+    padding: 0
+}
+#hs-component .container {
+    padding: 0;
+    width: 100%
+}
+#comp-menu {
+    background-color: #FFFFFF;
+    z-index: 1;
+    padding: 0
+}
+#comp-menu .cont,
+.demo {
+    padding: 0 50px
+}
+#comp-menu h2,
+#comp-menu h2 a {
+    color: #000000;
+    font-weight: lighter;
+    text-transform: inherit
+}
+#comp-menu h2 a:hover {
+    color: #8085e8
+}
+
+.blink_me {
+  animation: blinker 1.5s linear infinite;
+}
+
+@keyframes blinker {
+  20% { opacity: 0.4; }
+}
+
+
+a.btn.btn-theme {
+    color: #eeeaea;
+    background-color: #007E88;
+    border: 1px solid #007E88;
+    border-bottom: 0;
+    border-radius: 0;
+    font-weight: 400;
+    margin: 0 5px 0 0
+}
+a.btn.btn-theme:hover {
+    color: #90ef7f
+}
+
+
+.btn-book {
+    color: #fff;
+    background-color: #337ab7;
+    border-color: #2e6da4;
+    display: inline-block;
+    white-space: nowrap;
+    vertical-align: middle;
+    border: 1px solid transparent;
+    touch-action: manipulation;
+    cursor: pointer;
+    padding: 2px 6px;
+    font-size: 10px;
+    font-weight: bold;
+    line-height: 1.1333333;
+    border-radius: 6px;
+    background-image: linear-gradient(to bottom,#337ab7 0,#265a88 100%);
+}
+
+a.btn.btn-theme.noselected {
+    background-color: #FFF;
+    color: #313131;
+    opacity: 1
+}
+
+a.btn.btn-theme.disabled {
+    background-color: #FFF;
+    color: #313131;
+    opacity: 1
+}
+.demo {
+    background-color: #f6f6f6
+}
+.demo .demo-name {
+    color: #000000;
+    font-weight: lighter;
+    text-transform: none;
+    padding-left: 15px;
+    text-align: center;
+    display: inline;
+    margin: 0 10px
+}
+#chart-switcher {
+    text-align: center;
+    padding: 30px 0
+}
+.demo #chart-switcher #next-example,
+.demo #chart-switcher #previous-example {
+    font-size: 30px;
+    padding: 0 10px;
+    color: #888
+}
+#demo-buttons {
+    text-align: center;
+    padding: 30px 0
+}
+#demo-buttons a {
+    background-color: #ddd
+}
+#demo-buttons a:hover {
+    background-color: #40818b
+}
+.demo .chart-container {
+    position: relative;
+    background-color: #fff;
+    padding: 30px 0
+}
+.demo .chart-container #previous-example {
+    position: absolute;
+    top: 50%;
+    left: -30px;
+    font-size: 70px;
+    color: #888
+}
+.demo .chart-container #next-example {
+    position: absolute;
+    top: 50%;
+    right: -30px;
+    font-size: 70px;
+    color: #888
+}
+.sidebar-eq-fill {
+    background-color: #f6f6f6
+}
+@media screen and (max-width: 400px) {
+    #chart-switcher,
+    .demo .chart-container,
+    .sidebar-wrapper .sidebar-eq {
+        padding: 0
+    }
+    #small-switcher {
+        text-align: center
+    }
+    .demo #chart-switcher #next-example,
+    .demo #chart-switcher #previous-example {
+        font-size: 20px
+    }
+    #sidebar-close {
+        position: absolute;
+        top: 0;
+        left: 0;
+        background-color: #FFFFFF;
+        color: #fff;
+        border-radius: 0;
+        z-index: 100;
+        cursor: pointer;
+        padding: 10px;
+        visibility: hidden
+    }
+    .toggled #sidebar-close {
+        visibility: visible
+    }
+}
+@media screen and (min-width: 400px) and (max-width: 768px) {
+    .demo {
+        padding: 15px
+    }
+    #sidebar-close {
+        position: absolute;
+        top: 0;
+        left: 0;
+        background-color: #FFFFFF;
+        color: #fff;
+        border-radius: 0;
+        z-index: 100;
+        cursor: pointer;
+        padding: 10px;
+        visibility: hidden
+    }
+    .toggled #sidebar-close {
+        visibility: visible
+    }
+}
+#hs-below {
+    background-color: #eeeaea;
+    font-size: 18px;
+    line-height: 26px
+}
+#hs-below h3 {
+    font-size: 30px;
+    line-height: 30px;
+    font-weight: bolder;
+    margin-top: 0;
+    margin-bottom: 15px
+}
+#hs-below p,
+ul {
+    font-size: 1pc
+}
+#hs-below .box {
+    position: relative;
+    background-color: #fff;
+    padding: 14px 22px
+}
+#hs-below .box .box-content {
+    margin-bottom: 40px
+}
+#hs-below .box .button {
+    position: absolute;
+    bottom: 0;
+    margin-bottom: 10px
+}
+#hs-below .box.purple {
+    border-bottom: 8px solid #8085e8
+}
+#hs-below .box.green {
+    border-bottom: 8px solid #90ef7f
+}
+#hs-below .box .book {
+    text-align: center
+}
+#hs-below .box .book img {
+    max-height: 200px
+}
+@media screen and (max-width: 768px) {
+    #hs-below h3 {
+        margin-top: 30px
+    }
+    #hs-below .box-1 h3 {
+        margin-top: 0
+    }
+    #hs-below .box .book {
+        text-align: left
+    }
+}
+@media screen and (min-width: 768px) and (max-width: 992px) {
+    #hs-below h3 {
+        margin-top: 30px
+    }
+    #hs-below .box-1 h3,
+    #hs-below .box-2 h3 {
+        margin-top: 0
+    }
+    #hs-below .box-1 .box,
+    #hs-below .box-2 .box {
+        min-height: 260px
+    }
+    #hs-below .box-3 .box,
+    #hs-below .box-4 .box {
+        min-height: 280px
+    }
+}
+@media screen and (min-width: 992px) and (max-width: 1200px) {
+    #hs-below h3 {
+        margin-top: 30px
+    }
+    #hs-below .box-1 h3,
+    #hs-below .box-2 h3 {
+        margin-top: 0
+    }
+    #hs-below .box-1 .box,
+    #hs-below .box-2 .box {
+        min-height: 255px
+    }
+    #hs-below .box-3 .box,
+    #hs-below .box-4 .box {
+        min-height: 280px
+    }
+}
+@media screen and (min-width: 1200px) {
+    #hs-below .box {
+        min-height: 390px
+    }
+}
+#hs-bottom,
+#hs-bottom a {
+    color: #eeeaea
+}
+#hs-bottom {
+    background-color: #FFFFFF;
+    font-size: 1pc;
+    line-height: 20px
+}
+#hs-bottom a:focus,
+#hs-bottom a:hover {
+    color: #90ef7f
+}
+#hs-bottom a.button {
+    color: #313131
+}
+#hs-bottom a.button:focus,
+#hs-bottom a.button:hover {
+    color: #eeeaea
+}
+#hs-bottom h3 {
+    font-size: 24px;
+    line-height: 24px;
+    font-weight: lighter
+}
+#hs-bottom h4 {
+    font-size: 14px
+}
+#hs-bottom .grayed {
+    color: #d6d1d1;
+    margin: 0
+}
+@media screen and (max-width: 768px) {
+    #hs-bottom [class*=col-] {
+        border-bottom: 1px dotted #eeeaea;
+        padding-bottom: 15px
+    }
+    #hs-bottom [class*=col-]:nth-child(1) h3 {
+        margin-top: 0
+    }
+    #hs-bottom [class*=col-]:last-child {
+        border-bottom: 0;
+        padding-bottom: 0
+    }
+}
+@media screen and (min-width: 768px) and (max-width: 992px) {
+    #hs-bottom [class*=col-] {
+        min-height: 350px;
+        border-right: 1px dotted #eeeaea
+    }
+    #hs-bottom [class*=col-]:last-child,
+    #hs-bottom [class*=col-]:nth-child(2) {
+        border-right: 0
+    }
+}
+@media screen and (min-width: 992px) and (max-width: 1200px) {
+    #hs-bottom [class*=col-] {
+        min-height: 300px;
+        border-right: 1px dotted #eeeaea
+    }
+    #hs-bottom [class*=col-]:last-child,
+    #hs-bottom [class*=col-]:nth-child(2) {
+        border-right: 0
+    }
+}
+@media screen and (min-width: 1200px) {
+    #hs-bottom [class*=col-] {
+        min-height: 25pc;
+        border-right: 1px dotted #eeeaea
+    }
+    #hs-bottom [class*=col-]:last-child {
+        border-right: 0
+    }
+}
+
+
+
+.button{
+  border:1px solid #333;
+  background:#6479fd;
+}
+.button:hover{
+  background:#a4a9fd;
+}
+.dialog{
+  border:5px solid #666;
+  padding:10px;
+  background:#3A3A3A;
+  position:absolute;
+  display:none;
+}
+.dialog label{
+  display:inline-block;
+  color:#cecece;
+}
+input[type=text]{
+  border:1px solid #333;
+  display:inline-block;
+  margin:5px;
+}
+#btnOK{
+  border:1px solid #000;
+  background:#ff9999;
+  margin:5px;
+}
+
+#btnOK:hover{
+  border:1px solid #000;
+  background:#ffacac;
+}
+
diff --git a/tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf b/tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf
new file mode 100644 (file)
index 0000000..62a0a4d
Binary files /dev/null and b/tools/infra-dashboard/fonts/SourceSansPro-Bold.ttf differ
diff --git a/tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf b/tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf
new file mode 100644 (file)
index 0000000..e28a62a
Binary files /dev/null and b/tools/infra-dashboard/fonts/SourceSansPro-ExtraLight.ttf differ
diff --git a/tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf b/tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf
new file mode 100644 (file)
index 0000000..ffe2786
Binary files /dev/null and b/tools/infra-dashboard/fonts/SourceSansPro-Regular.ttf differ
diff --git a/tools/infra-dashboard/fonts/fontawesome-webfont.ttf b/tools/infra-dashboard/fonts/fontawesome-webfont.ttf
new file mode 100644 (file)
index 0000000..26dea79
Binary files /dev/null and b/tools/infra-dashboard/fonts/fontawesome-webfont.ttf differ
diff --git a/tools/infra-dashboard/fonts/fontawesome-webfont.woff b/tools/infra-dashboard/fonts/fontawesome-webfont.woff
new file mode 100644 (file)
index 0000000..dc35ce3
Binary files /dev/null and b/tools/infra-dashboard/fonts/fontawesome-webfont.woff differ
diff --git a/tools/infra-dashboard/fonts/fontawesome-webfont.woff2 b/tools/infra-dashboard/fonts/fontawesome-webfont.woff2
new file mode 100644 (file)
index 0000000..500e517
Binary files /dev/null and b/tools/infra-dashboard/fonts/fontawesome-webfont.woff2 differ
diff --git a/tools/infra-dashboard/fonts/source-sans-pro.black.ttf b/tools/infra-dashboard/fonts/source-sans-pro.black.ttf
new file mode 100644 (file)
index 0000000..f373008
Binary files /dev/null and b/tools/infra-dashboard/fonts/source-sans-pro.black.ttf differ
diff --git a/tools/infra-dashboard/index.php b/tools/infra-dashboard/index.php
new file mode 100644 (file)
index 0000000..56c765a
--- /dev/null
@@ -0,0 +1,353 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+        <title>OPNFV Pharos Dashboard | OPNFV</title>
+        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" type="text/css" />
+        <link rel="stylesheet" href="./css/dataTables.bootstrap.min.css" type="text/css" />
+        <script src="//code.jquery.com/jquery-1.10.2.js"></script>
+        <script src="//code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
+        <script src="http://www.itsyndicate.ca/gssi/jquery/jquery.crypt.js"></script>
+        <script src="https://cdn.datatables.net/1.10.11/js/jquery.dataTables.min.js"></script>
+        <script src="https://cdn.datatables.net/1.10.11/js/dataTables.bootstrap.min.js"></script>
+        <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"/>
+
+        <link rel="stylesheet" href="./css/template.css" type="text/css" />
+        <link rel="stylesheet" href="./css/theme.css" type="text/css" />
+        <link rel="stylesheet" href="./css/opnfv.css" type="text/css" />
+
+        <link href='./css/fullcalendar.css' rel='stylesheet' />
+        <link href='./css/fullcalendar.print.css' rel='stylesheet' media='print' />
+        <script src='./js/moment.min.js'></script>
+        <script src='./js/fullcalendar.js'></script>
+
+
+        <style>
+            fieldset { padding:0; border:0; margin-top:15px; }
+            input.text { margin-bottom:2px; width:90%; padding: .2em; font-size:14px; }
+            input { display:block; font-size:14px; }
+            label {font-size:14px;}
+            .ui-dialog .ui-state-error { padding: .3em; }
+            .validateTips { border: 1px solid transparent; padding: 0.3em; }
+            .booked_day span {
+                color: red !important; /* should only apply to may 6 and 8 */
+            }
+        </style>
+
+        <script type="text/javascript">
+            $(document).ready(function() {
+
+                function getParameterByName(name, url) {
+                    if (!url) url = window.location.href;
+                    name = name.replace(/[\[\]]/g, "\\$&");
+                    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
+                        results = regex.exec(url);
+                    if (!results) return null;
+                    if (!results[2]) return '';
+                    return decodeURIComponent(results[2].replace(/\+/g, " "));
+                }
+
+                function selectTab(name) {
+                    $("#container").empty();
+                    var imgName = './media/ajax-loader.gif';
+                    document.getElementById('container')
+                        .innerHTML = '<img style="position: relative;left: 50%;" src="' + imgName + '" />';
+                    if (name == "devpods") {
+                        $( "#btn_cipods" ).addClass( "noselected" );
+                        $( "#btn_devpods" ).removeClass( "noselected" );
+                        $( "#btn_slaves" ).addClass( "noselected" );
+                        var key = Math.random();
+                        $("#container").load("pages/dev_pods.php?key="+key);
+                        $('#hd_page').attr('value', "devpods");
+                    }
+                    else if (name == "slaves") {
+                        $( "#btn_cipods" ).addClass( "noselected" );
+                        $( "#btn_devpods" ).addClass( "noselected" );
+                        $( "#btn_slaves" ).removeClass( "noselected" );
+                        $("#container").load("pages/slaves.php");
+                        $('#hd_page').attr('value', "slaves");
+                    }
+                    else {
+                        $( "#btn_cipods" ).removeClass( "noselected" );
+                        $( "#btn_devpods" ).addClass( "noselected" );
+                        $( "#btn_slaves" ).addClass( "noselected" );
+                        $("#container").load("pages/ci_pods.php");
+                        $('#hd_page').attr('value', "cipods");
+                    }
+                }
+
+
+                var page = getParameterByName('page');
+                if      (page == "devpods")  selectTab("devpods");
+                else if (page == "slaves")  selectTab("slaves");
+                else                         selectTab("cipods");
+
+
+                $( "#btn_cipods" ).click(function() {
+                    selectTab("cipods");
+                });
+                $( "#btn_devpods" ).click(function() {
+                    selectTab("devpods");
+                });
+                $( "#btn_slaves" ).click(function() {
+                    selectTab("slaves");
+                });
+            } );
+
+
+
+            $(function() {
+                var dialog, form,
+                allFields = $( [] ).add( login_email ).add( login_password );
+                emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
+
+                function checkLength( o, n, min, max ) {
+                  if ( o.val().length > max || o.val().length < min ) {
+                    o.addClass( "ui-state-error" );
+                    return false;
+                  } else {
+                    return true;
+                  }
+                }
+
+                function checkRegexp( o, regexp, n ) {
+                  if ( !( regexp.test( o.val() ) ) ) {
+                    o.addClass( "ui-state-error" );
+                    return false;
+                  } else {
+                    return true;
+                  }
+                }
+
+
+                function login() {
+                    var valid = true;
+                    email = $( "#login_email" );
+                    password = $( "#login_password" );
+                    allFields.removeClass( "ui-state-error" );
+
+                    valid = valid && checkLength( email, "email", 6, 80 );
+                    valid = valid && checkLength( password, "password", 5, 16 );
+
+                    valid = valid && checkRegexp( email, emailRegex, "eg. ui@jquery.com" );
+                    valid = valid && checkRegexp( password, /^([0-9a-zA-Z])+$/, "Password field only allow : a-z 0-9" );
+
+
+                    if ( valid ) {
+                        var email =  $('#login_email').val();
+                        var password = $('#login_password').val();
+                        var passwordMD5 = $().crypt({
+                            method: "md5",
+                            source: password
+                        });
+                        $.ajax({
+                            type: 'POST',
+                            url: "utils/login.php",
+                            data: {action: 'login', email: email, password: passwordMD5},
+                            success: function(data){
+                                //alert(data);
+                                json = JSON.parse(data);
+                                if (json.result == 1) {
+                                    alert("Wrong password.")
+                                } else if (json.result == 2){
+                                    alert("User not registered.")
+                                } else {
+                                    var page =  $('#hd_page').val();
+                                    location.href = location.protocol + '//' + location.host + location.pathname + "?page=" + page;
+                                }
+                            },
+                            error: function(data){
+                                alert(data)
+                            }
+                        });
+
+                    }
+                }
+
+                dialog_login = $( "#dialog-login" ).dialog({
+                    autoOpen: false,
+                    height: 225,
+                    width: 400,
+                    modal: true,
+                    resizable:false,
+                    buttons: {
+                        "Login": login,
+                        Cancel: function() {
+                            dialog_login.dialog( "close" );
+                        }
+                    },
+                    close: function() {
+                        form[ 0 ].reset();
+                        allFields.removeClass( "ui-state-error" );
+                    }
+                });
+
+                form = dialog_login.find( "form" ).on( "submit", function( event ) {
+                    event.preventDefault();
+                    login();
+                });
+
+
+                $( "#login_text" ).on( "click", function() {
+                    dialog_login.dialog( "open" );
+                });
+
+                $( "#logout" ).on( "click", function() {
+                    $.ajax({
+                        type: 'POST',
+                        url: "utils/login.php",
+                        data: {action: 'logout'},
+                        success: function(data){
+                            var page =  $('#hd_page').val();
+                            location.href = location.protocol + '//' + location.host + location.pathname + "?page=" + page;
+                        },
+                        error: function(data){
+                            alert(data)
+                        }
+                    });
+                });
+            });
+        </script>
+    </head>
+
+
+    <body>
+
+        <?php
+            session_start();
+        ?>
+
+        <div class="collaborative-projects">
+            <div class="gray-diagonal">
+                <div class="container">
+                    <a id="collaborative-projects-logo" href="http://collabprojects.linuxfoundation.org">Linux Foundation Collaborative Projects</a>
+                </div>
+            </div>
+        </div>
+
+
+        <div id="menu">
+            <nav class="navbar navbar-default" role="navigation">
+                <div class="container">
+                    <div class="navbar-header">
+                        <a class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
+                            <span class="sr-only">Toggle navigation</span>
+                        </a>
+                        <a class="navbar-brand" href="https://www.opnfv.org/" title="OPNFV">
+                            <img src="https://www.opnfv.org/sites/all/themes/opnfv/logo.png" alt="OPNFV" />
+                        </a>
+                    </div>
+                    <div class="collapse navbar-collapse">
+                        <div id="menu-container">
+                            <div id="menu-second" class="hidden-xs">
+                                <ul class="nav navbar-nav pull-right">
+
+                                    <li class="item-112">
+                                        <a target="_blank" href="https://www.opnfv.org/" >About Us</a>
+                                    </li>
+                                    <li class="item-113 deeper dropdown">
+                                        <a target="_blank" href="#" class="dropdown-toggle" data-toggle="dropdown">Dodumentation
+                                            <span class="toggle-arrow"></span>
+                                        </a>
+                                        <ul class="dropdown-menu" role="menu">
+                                            <li class="item-121">
+                                                <a target="_blank" href="http://artifacts.opnfv.org/pharos/docs/" >Pharos</a>
+                                            </li>
+                                            <li class="item-122">
+                                                <a target="_blank" href="" >Releng</a>
+                                            </li>
+                                        </ul>
+                                    </li>
+                                    <li class="item-218">
+                                        <a target="_blank" href="https://wiki.opnfv.org/" >OPNFV Wiki</a>
+                                    </li>
+                                    <li class="item-114">
+                                        <a target="_blank" href="" >Contact</a>
+                                    </li>
+                                    <li class="item-112">
+                                        <?php
+
+                                            if (isset($_SESSION['user_id'])) {
+                                                echo '<a style="cursor: pointer;" id="logout">Logout</a>';
+                                            }
+                                            else {
+                                                echo '<a style="cursor: pointer;" id="login_text">Login</a>';
+                                            }
+                                        ?>
+
+                                    </li>
+                                </ul>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </nav>
+        </div>
+
+
+        <div id="hs-component">
+            <div class="container">
+                <div id="wrap" class="sidebar-wrapper">
+                    <div id="comp-menu" class="col-lg-12 col-md-12 col-sm-12 col-xs-12 hidden-xs">
+                        <?php
+                            if (isset($_SESSION['user_id'])) {
+                                echo '<div style="float:right;text-align:right;top:0;margin-right:18px">';
+                                echo 'current user: '.$_SESSION['user_name'];
+                                echo '</div>';
+                            }
+                        ?>
+                        <h2 class="demo-name">Pharos Infrastructure</h2>
+
+                        <div class="btn-group theme">
+                            <a id="btn_cipods" class="btn btn-theme noselected">CI PODs</a>
+                            <a id="btn_devpods" class="btn btn-theme noselected">DEVELOPMENT PODs</a>
+                            <a id="btn_slaves" class="btn btn-theme noselected">JENKINS SLAVES</a>
+                        </div>
+
+                        <div style="min-width: 310px; height: 2px; margin: 0 auto; background-color: #007E88"></div>
+                        <div style="min-width: 310px; height: 20px; margin: 0 auto; background-color: #ffffff"></div>
+                    </div>
+
+                    <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
+                </div>
+            </div>
+        </div>
+
+
+
+        <div id="dialog-login" title="Login">
+            <form>
+                <table>
+                    <tr>
+                        <td><label for="login_email">Email</label></td>
+                        <td><input type="text" label="Email" name="login_email" id="login_email" value="" size="30" class="text ui-widget-content ui-corner-all"/></td>
+                    </tr>
+                        <td><label for="login_password">Password</label></td>
+                        <td><input type="password" name="login_password" id="login_password" size="30" value="" class="text ui-widget-content ui-corner-all"/></td>
+                    <tr>
+                    </tr>
+                </table>
+                <input type="submit" tabindex="-1" style="position:absolute; top:-100px"/>
+            </form>
+        </div>
+
+
+        <div id="footer" style="float:bottom">
+            <div class="container">
+                <div class="col-lg-6 col-md-6 col-sm-6 col-xs-12">
+                    <div id="zt-footer-copy">
+                        Maintained by jose.lausuch@ericsson.com.
+                    </div>
+                </div>
+            <div class="socials"></div>
+        </div>
+
+        <?php
+            echo '<input type="hidden" id="hd_user_id" value="'.$_SESSION['user_id'].'"/>';
+            echo '<input type="hidden" id="hd_user_email" value="'.$_SESSION['user_email'].'"/>';
+            echo '<input type="hidden" id="hd_user_name" value="'.$_SESSION['user_name'].'"/>';
+        ?>
+        <input type="hidden" id="hd_page" value="cipods"/>
+
+    </body>
+</html>
diff --git a/tools/infra-dashboard/js/bootstrap.js b/tools/infra-dashboard/js/bootstrap.js
new file mode 100644 (file)
index 0000000..b2ff4e8
--- /dev/null
@@ -0,0 +1,2118 @@
+/*!
+ * Bootstrap v3.2.0 (http://getbootstrap.com)
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/*!
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=e82eeb1f3b04b506268e)
+ * Config saved to config.json and https://gist.github.com/e82eeb1f3b04b506268e
+ */
+if (typeof jQuery === "undefined") { throw new Error("Bootstrap's JavaScript requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.2.0
+ * http://getbootstrap.com/javascript/#alerts
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // ALERT CLASS DEFINITION
+  // ======================
+
+  var dismiss = '[data-dismiss="alert"]'
+  var Alert   = function (el) {
+    $(el).on('click', dismiss, this.close)
+  }
+
+  Alert.VERSION = '3.2.0'
+
+  Alert.prototype.close = function (e) {
+    var $this    = $(this)
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = $(selector)
+
+    if (e) e.preventDefault()
+
+    if (!$parent.length) {
+      $parent = $this.hasClass('alert') ? $this : $this.parent()
+    }
+
+    $parent.trigger(e = $.Event('close.bs.alert'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      // detach from parent, fire event then clean up data
+      $parent.detach().trigger('closed.bs.alert').remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent
+        .one('bsTransitionEnd', removeElement)
+        .emulateTransitionEnd(150) :
+      removeElement()
+  }
+
+
+  // ALERT PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.alert')
+
+      if (!data) $this.data('bs.alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.alert
+
+  $.fn.alert             = Plugin
+  $.fn.alert.Constructor = Alert
+
+
+  // ALERT NO CONFLICT
+  // =================
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+  // ALERT DATA-API
+  // ==============
+
+  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.2.0
+ * http://getbootstrap.com/javascript/#buttons
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // BUTTON PUBLIC CLASS DEFINITION
+  // ==============================
+
+  var Button = function (element, options) {
+    this.$element  = $(element)
+    this.options   = $.extend({}, Button.DEFAULTS, options)
+    this.isLoading = false
+  }
+
+  Button.VERSION  = '3.2.0'
+
+  Button.DEFAULTS = {
+    loadingText: 'loading...'
+  }
+
+  Button.prototype.setState = function (state) {
+    var d    = 'disabled'
+    var $el  = this.$element
+    var val  = $el.is('input') ? 'val' : 'html'
+    var data = $el.data()
+
+    state = state + 'Text'
+
+    if (data.resetText == null) $el.data('resetText', $el[val]())
+
+    $el[val](data[state] == null ? this.options[state] : data[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout($.proxy(function () {
+      if (state == 'loadingText') {
+        this.isLoading = true
+        $el.addClass(d).attr(d, d)
+      } else if (this.isLoading) {
+        this.isLoading = false
+        $el.removeClass(d).removeAttr(d)
+      }
+    }, this), 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var changed = true
+    var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+    if ($parent.length) {
+      var $input = this.$element.find('input')
+      if ($input.prop('type') == 'radio') {
+        if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
+        else $parent.find('.active').removeClass('active')
+      }
+      if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
+    }
+
+    if (changed) this.$element.toggleClass('active')
+  }
+
+
+  // BUTTON PLUGIN DEFINITION
+  // ========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.button')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  var old = $.fn.button
+
+  $.fn.button             = Plugin
+  $.fn.button.Constructor = Button
+
+
+  // BUTTON NO CONFLICT
+  // ==================
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+  // BUTTON DATA-API
+  // ===============
+
+  $(document).on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    Plugin.call($btn, 'toggle')
+    e.preventDefault()
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.2.0
+ * http://getbootstrap.com/javascript/#carousel
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CAROUSEL CLASS DEFINITION
+  // =========================
+
+  var Carousel = function (element, options) {
+    this.$element    = $(element).on('keydown.bs.carousel', $.proxy(this.keydown, this))
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options     = options
+    this.paused      =
+    this.sliding     =
+    this.interval    =
+    this.$active     =
+    this.$items      = null
+
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
+      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
+  }
+
+  Carousel.VERSION  = '3.2.0'
+
+  Carousel.DEFAULTS = {
+    interval: 5000,
+    pause: 'hover',
+    wrap: true
+  }
+
+  Carousel.prototype.keydown = function (e) {
+    switch (e.which) {
+      case 37: this.prev(); break
+      case 39: this.next(); break
+      default: return
+    }
+
+    e.preventDefault()
+  }
+
+  Carousel.prototype.cycle = function (e) {
+    e || (this.paused = false)
+
+    this.interval && clearInterval(this.interval)
+
+    this.options.interval
+      && !this.paused
+      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+    return this
+  }
+
+  Carousel.prototype.getItemIndex = function (item) {
+    this.$items = item.parent().children('.item')
+    return this.$items.index(item || this.$active)
+  }
+
+  Carousel.prototype.to = function (pos) {
+    var that        = this
+    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
+
+    if (pos > (this.$items.length - 1) || pos < 0) return
+
+    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
+    if (activeIndex == pos) return this.pause().cycle()
+
+    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+  }
+
+  Carousel.prototype.pause = function (e) {
+    e || (this.paused = true)
+
+    if (this.$element.find('.next, .prev').length && $.support.transition) {
+      this.$element.trigger($.support.transition.end)
+      this.cycle(true)
+    }
+
+    this.interval = clearInterval(this.interval)
+
+    return this
+  }
+
+  Carousel.prototype.next = function () {
+    if (this.sliding) return
+    return this.slide('next')
+  }
+
+  Carousel.prototype.prev = function () {
+    if (this.sliding) return
+    return this.slide('prev')
+  }
+
+  Carousel.prototype.slide = function (type, next) {
+    var $active   = this.$element.find('.item.active')
+    var $next     = next || $active[type]()
+    var isCycling = this.interval
+    var direction = type == 'next' ? 'left' : 'right'
+    var fallback  = type == 'next' ? 'first' : 'last'
+    var that      = this
+
+    if (!$next.length) {
+      if (!this.options.wrap) return
+      $next = this.$element.find('.item')[fallback]()
+    }
+
+    if ($next.hasClass('active')) return (this.sliding = false)
+
+    var relatedTarget = $next[0]
+    var slideEvent = $.Event('slide.bs.carousel', {
+      relatedTarget: relatedTarget,
+      direction: direction
+    })
+    this.$element.trigger(slideEvent)
+    if (slideEvent.isDefaultPrevented()) return
+
+    this.sliding = true
+
+    isCycling && this.pause()
+
+    if (this.$indicators.length) {
+      this.$indicators.find('.active').removeClass('active')
+      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
+      $nextIndicator && $nextIndicator.addClass('active')
+    }
+
+    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
+    if ($.support.transition && this.$element.hasClass('slide')) {
+      $next.addClass(type)
+      $next[0].offsetWidth // force reflow
+      $active.addClass(direction)
+      $next.addClass(direction)
+      $active
+        .one('bsTransitionEnd', function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () {
+            that.$element.trigger(slidEvent)
+          }, 0)
+        })
+        .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000)
+    } else {
+      $active.removeClass('active')
+      $next.addClass('active')
+      this.sliding = false
+      this.$element.trigger(slidEvent)
+    }
+
+    isCycling && this.cycle()
+
+    return this
+  }
+
+
+  // CAROUSEL PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.carousel')
+      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+      var action  = typeof option == 'string' ? option : options.slide
+
+      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  var old = $.fn.carousel
+
+  $.fn.carousel             = Plugin
+  $.fn.carousel.Constructor = Carousel
+
+
+  // CAROUSEL NO CONFLICT
+  // ====================
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+
+  // CAROUSEL DATA-API
+  // =================
+
+  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var href
+    var $this   = $(this)
+    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
+    if (!$target.hasClass('carousel')) return
+    var options = $.extend({}, $target.data(), $this.data())
+    var slideIndex = $this.attr('data-slide-to')
+    if (slideIndex) options.interval = false
+
+    Plugin.call($target, options)
+
+    if (slideIndex) {
+      $target.data('bs.carousel').to(slideIndex)
+    }
+
+    e.preventDefault()
+  })
+
+  $(window).on('load', function () {
+    $('[data-ride="carousel"]').each(function () {
+      var $carousel = $(this)
+      Plugin.call($carousel, $carousel.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.2.0
+ * http://getbootstrap.com/javascript/#dropdowns
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // DROPDOWN CLASS DEFINITION
+  // =========================
+
+  var backdrop = '.dropdown-backdrop'
+  var toggle   = '[data-toggle="dropdown"]'
+  var Dropdown = function (element) {
+    $(element).on('click.bs.dropdown', this.toggle)
+  }
+
+  Dropdown.VERSION = '3.2.0'
+
+  Dropdown.prototype.toggle = function (e) {
+    var $this = $(this)
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    clearMenus()
+
+    if (!isActive) {
+      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+        // if mobile we use a backdrop because click events don't delegate
+        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
+      }
+
+      var relatedTarget = { relatedTarget: this }
+      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
+
+      if (e.isDefaultPrevented()) return
+
+      $this.trigger('focus')
+
+      $parent
+        .toggleClass('open')
+        .trigger('shown.bs.dropdown', relatedTarget)
+    }
+
+    return false
+  }
+
+  Dropdown.prototype.keydown = function (e) {
+    if (!/(38|40|27)/.test(e.keyCode)) return
+
+    var $this = $(this)
+
+    e.preventDefault()
+    e.stopPropagation()
+
+    if ($this.is('.disabled, :disabled')) return
+
+    var $parent  = getParent($this)
+    var isActive = $parent.hasClass('open')
+
+    if (!isActive || (isActive && e.keyCode == 27)) {
+      if (e.which == 27) $parent.find(toggle).trigger('focus')
+      return $this.trigger('click')
+    }
+
+    var desc = ' li:not(.divider):visible a'
+    var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
+
+    if (!$items.length) return
+
+    var index = $items.index($items.filter(':focus'))
+
+    if (e.keyCode == 38 && index > 0)                 index--                        // up
+    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+    if (!~index)                                      index = 0
+
+    $items.eq(index).trigger('focus')
+  }
+
+  function clearMenus(e) {
+    if (e && e.which === 3) return
+    $(backdrop).remove()
+    $(toggle).each(function () {
+      var $parent = getParent($(this))
+      var relatedTarget = { relatedTarget: this }
+      if (!$parent.hasClass('open')) return
+      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
+      if (e.isDefaultPrevented()) return
+      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    var $parent = selector && $(selector)
+
+    return $parent && $parent.length ? $parent : $this.parent()
+  }
+
+
+  // DROPDOWN PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.dropdown')
+
+      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown             = Plugin
+  $.fn.dropdown.Constructor = Dropdown
+
+
+  // DROPDOWN NO CONFLICT
+  // ====================
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  // APPLY TO STANDARD DROPDOWN ELEMENTS
+  // ===================================
+
+  $(document)
+    .on('click.bs.dropdown.data-api', clearMenus)
+    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
+    .on('keydown.bs.dropdown.data-api', toggle + ', [role="menu"], [role="listbox"]', Dropdown.prototype.keydown)
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.2.0
+ * http://getbootstrap.com/javascript/#modals
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // MODAL CLASS DEFINITION
+  // ======================
+
+  var Modal = function (element, options) {
+    this.options        = options
+    this.$body          = $(document.body)
+    this.$element       = $(element)
+    this.$backdrop      =
+    this.isShown        = null
+    this.scrollbarWidth = 0
+
+    if (this.options.remote) {
+      this.$element
+        .find('.modal-content')
+        .load(this.options.remote, $.proxy(function () {
+          this.$element.trigger('loaded.bs.modal')
+        }, this))
+    }
+  }
+
+  Modal.VERSION  = '3.2.0'
+
+  Modal.DEFAULTS = {
+    backdrop: true,
+    keyboard: true,
+    show: true
+  }
+
+  Modal.prototype.toggle = function (_relatedTarget) {
+    return this.isShown ? this.hide() : this.show(_relatedTarget)
+  }
+
+  Modal.prototype.show = function (_relatedTarget) {
+    var that = this
+    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+    this.$element.trigger(e)
+
+    if (this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = true
+
+    this.checkScrollbar()
+    this.$body.addClass('modal-open')
+
+    this.setScrollbar()
+    this.escape()
+
+    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+    this.backdrop(function () {
+      var transition = $.support.transition && that.$element.hasClass('fade')
+
+      if (!that.$element.parent().length) {
+        that.$element.appendTo(that.$body) // don't move modals dom position
+      }
+
+      that.$element
+        .show()
+        .scrollTop(0)
+
+      if (transition) {
+        that.$element[0].offsetWidth // force reflow
+      }
+
+      that.$element
+        .addClass('in')
+        .attr('aria-hidden', false)
+
+      that.enforceFocus()
+
+      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+      transition ?
+        that.$element.find('.modal-dialog') // wait for modal to slide in
+          .one('bsTransitionEnd', function () {
+            that.$element.trigger('focus').trigger(e)
+          })
+          .emulateTransitionEnd(300) :
+        that.$element.trigger('focus').trigger(e)
+    })
+  }
+
+  Modal.prototype.hide = function (e) {
+    if (e) e.preventDefault()
+
+    e = $.Event('hide.bs.modal')
+
+    this.$element.trigger(e)
+
+    if (!this.isShown || e.isDefaultPrevented()) return
+
+    this.isShown = false
+
+    this.$body.removeClass('modal-open')
+
+    this.resetScrollbar()
+    this.escape()
+
+    $(document).off('focusin.bs.modal')
+
+    this.$element
+      .removeClass('in')
+      .attr('aria-hidden', true)
+      .off('click.dismiss.bs.modal')
+
+    $.support.transition && this.$element.hasClass('fade') ?
+      this.$element
+        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
+        .emulateTransitionEnd(300) :
+      this.hideModal()
+  }
+
+  Modal.prototype.enforceFocus = function () {
+    $(document)
+      .off('focusin.bs.modal') // guard against infinite focus loop
+      .on('focusin.bs.modal', $.proxy(function (e) {
+        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+          this.$element.trigger('focus')
+        }
+      }, this))
+  }
+
+  Modal.prototype.escape = function () {
+    if (this.isShown && this.options.keyboard) {
+      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+        e.which == 27 && this.hide()
+      }, this))
+    } else if (!this.isShown) {
+      this.$element.off('keyup.dismiss.bs.modal')
+    }
+  }
+
+  Modal.prototype.hideModal = function () {
+    var that = this
+    this.$element.hide()
+    this.backdrop(function () {
+      that.$element.trigger('hidden.bs.modal')
+    })
+  }
+
+  Modal.prototype.removeBackdrop = function () {
+    this.$backdrop && this.$backdrop.remove()
+    this.$backdrop = null
+  }
+
+  Modal.prototype.backdrop = function (callback) {
+    var that = this
+    var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+    if (this.isShown && this.options.backdrop) {
+      var doAnimate = $.support.transition && animate
+
+      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+        .appendTo(this.$body)
+
+      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
+        if (e.target !== e.currentTarget) return
+        this.options.backdrop == 'static'
+          ? this.$element[0].focus.call(this.$element[0])
+          : this.hide.call(this)
+      }, this))
+
+      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+      this.$backdrop.addClass('in')
+
+      if (!callback) return
+
+      doAnimate ?
+        this.$backdrop
+          .one('bsTransitionEnd', callback)
+          .emulateTransitionEnd(150) :
+        callback()
+
+    } else if (!this.isShown && this.$backdrop) {
+      this.$backdrop.removeClass('in')
+
+      var callbackRemove = function () {
+        that.removeBackdrop()
+        callback && callback()
+      }
+      $.support.transition && this.$element.hasClass('fade') ?
+        this.$backdrop
+          .one('bsTransitionEnd', callbackRemove)
+          .emulateTransitionEnd(150) :
+        callbackRemove()
+
+    } else if (callback) {
+      callback()
+    }
+  }
+
+  Modal.prototype.checkScrollbar = function () {
+    if (document.body.clientWidth >= window.innerWidth) return
+    this.scrollbarWidth = this.scrollbarWidth || this.measureScrollbar()
+  }
+
+  Modal.prototype.setScrollbar = function () {
+    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
+    if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
+  }
+
+  Modal.prototype.resetScrollbar = function () {
+    this.$body.css('padding-right', '')
+  }
+
+  Modal.prototype.measureScrollbar = function () { // thx walsh
+    var scrollDiv = document.createElement('div')
+    scrollDiv.className = 'modal-scrollbar-measure'
+    this.$body.append(scrollDiv)
+    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
+    this.$body[0].removeChild(scrollDiv)
+    return scrollbarWidth
+  }
+
+
+  // MODAL PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option, _relatedTarget) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.modal')
+      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option](_relatedTarget)
+      else if (options.show) data.show(_relatedTarget)
+    })
+  }
+
+  var old = $.fn.modal
+
+  $.fn.modal             = Plugin
+  $.fn.modal.Constructor = Modal
+
+
+  // MODAL NO CONFLICT
+  // =================
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+  // MODAL DATA-API
+  // ==============
+
+  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this   = $(this)
+    var href    = $this.attr('href')
+    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
+    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+    if ($this.is('a')) e.preventDefault()
+
+    $target.one('show.bs.modal', function (showEvent) {
+      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
+      $target.one('hidden.bs.modal', function () {
+        $this.is(':visible') && $this.trigger('focus')
+      })
+    })
+    Plugin.call($target, option, this)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.2.0
+ * http://getbootstrap.com/javascript/#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TOOLTIP PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Tooltip = function (element, options) {
+    this.type       =
+    this.options    =
+    this.enabled    =
+    this.timeout    =
+    this.hoverState =
+    this.$element   = null
+
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.VERSION  = '3.2.0'
+
+  Tooltip.DEFAULTS = {
+    animation: true,
+    placement: 'top',
+    selector: false,
+    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
+    trigger: 'hover focus',
+    title: '',
+    delay: 0,
+    html: false,
+    container: false,
+    viewport: {
+      selector: 'body',
+      padding: 0
+    }
+  }
+
+  Tooltip.prototype.init = function (type, element, options) {
+    this.enabled   = true
+    this.type      = type
+    this.$element  = $(element)
+    this.options   = this.getOptions(options)
+    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
+
+    var triggers = this.options.trigger.split(' ')
+
+    for (var i = triggers.length; i--;) {
+      var trigger = triggers[i]
+
+      if (trigger == 'click') {
+        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+      } else if (trigger != 'manual') {
+        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
+        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
+
+        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+      }
+    }
+
+    this.options.selector ?
+      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+      this.fixTitle()
+  }
+
+  Tooltip.prototype.getDefaults = function () {
+    return Tooltip.DEFAULTS
+  }
+
+  Tooltip.prototype.getOptions = function (options) {
+    options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+    if (options.delay && typeof options.delay == 'number') {
+      options.delay = {
+        show: options.delay,
+        hide: options.delay
+      }
+    }
+
+    return options
+  }
+
+  Tooltip.prototype.getDelegateOptions = function () {
+    var options  = {}
+    var defaults = this.getDefaults()
+
+    this._options && $.each(this._options, function (key, value) {
+      if (defaults[key] != value) options[key] = value
+    })
+
+    return options
+  }
+
+  Tooltip.prototype.enter = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'in'
+
+    if (!self.options.delay || !self.options.delay.show) return self.show()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'in') self.show()
+    }, self.options.delay.show)
+  }
+
+  Tooltip.prototype.leave = function (obj) {
+    var self = obj instanceof this.constructor ?
+      obj : $(obj.currentTarget).data('bs.' + this.type)
+
+    if (!self) {
+      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
+      $(obj.currentTarget).data('bs.' + this.type, self)
+    }
+
+    clearTimeout(self.timeout)
+
+    self.hoverState = 'out'
+
+    if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+    self.timeout = setTimeout(function () {
+      if (self.hoverState == 'out') self.hide()
+    }, self.options.delay.hide)
+  }
+
+  Tooltip.prototype.show = function () {
+    var e = $.Event('show.bs.' + this.type)
+
+    if (this.hasContent() && this.enabled) {
+      this.$element.trigger(e)
+
+      var inDom = $.contains(document.documentElement, this.$element[0])
+      if (e.isDefaultPrevented() || !inDom) return
+      var that = this
+
+      var $tip = this.tip()
+
+      var tipId = this.getUID(this.type)
+
+      this.setContent()
+      $tip.attr('id', tipId)
+      this.$element.attr('aria-describedby', tipId)
+
+      if (this.options.animation) $tip.addClass('fade')
+
+      var placement = typeof this.options.placement == 'function' ?
+        this.options.placement.call(this, $tip[0], this.$element[0]) :
+        this.options.placement
+
+      var autoToken = /\s?auto?\s?/i
+      var autoPlace = autoToken.test(placement)
+      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+      $tip
+        .detach()
+        .css({ top: 0, left: 0, display: 'block' })
+        .addClass(placement)
+        .data('bs.' + this.type, this)
+
+      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+      var pos          = this.getPosition()
+      var actualWidth  = $tip[0].offsetWidth
+      var actualHeight = $tip[0].offsetHeight
+
+      if (autoPlace) {
+        var orgPlacement = placement
+        var $parent      = this.$element.parent()
+        var parentDim    = this.getPosition($parent)
+
+        placement = placement == 'bottom' && pos.top   + pos.height       + actualHeight - parentDim.scroll > parentDim.height ? 'top'    :
+                    placement == 'top'    && pos.top   - parentDim.scroll - actualHeight < 0                                   ? 'bottom' :
+                    placement == 'right'  && pos.right + actualWidth      > parentDim.width                                    ? 'left'   :
+                    placement == 'left'   && pos.left  - actualWidth      < parentDim.left                                     ? 'right'  :
+                    placement
+
+        $tip
+          .removeClass(orgPlacement)
+          .addClass(placement)
+      }
+
+      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+      this.applyPlacement(calculatedOffset, placement)
+
+      var complete = function () {
+        that.$element.trigger('shown.bs.' + that.type)
+        that.hoverState = null
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        $tip
+          .one('bsTransitionEnd', complete)
+          .emulateTransitionEnd(150) :
+        complete()
+    }
+  }
+
+  Tooltip.prototype.applyPlacement = function (offset, placement) {
+    var $tip   = this.tip()
+    var width  = $tip[0].offsetWidth
+    var height = $tip[0].offsetHeight
+
+    // manually read margins because getBoundingClientRect includes difference
+    var marginTop = parseInt($tip.css('margin-top'), 10)
+    var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+    // we must check for NaN for ie 8/9
+    if (isNaN(marginTop))  marginTop  = 0
+    if (isNaN(marginLeft)) marginLeft = 0
+
+    offset.top  = offset.top  + marginTop
+    offset.left = offset.left + marginLeft
+
+    // $.fn.offset doesn't round pixel values
+    // so we use setOffset directly with our own function B-0
+    $.offset.setOffset($tip[0], $.extend({
+      using: function (props) {
+        $tip.css({
+          top: Math.round(props.top),
+          left: Math.round(props.left)
+        })
+      }
+    }, offset), 0)
+
+    $tip.addClass('in')
+
+    // check to see if placing tip in new offset caused the tip to resize itself
+    var actualWidth  = $tip[0].offsetWidth
+    var actualHeight = $tip[0].offsetHeight
+
+    if (placement == 'top' && actualHeight != height) {
+      offset.top = offset.top + height - actualHeight
+    }
+
+    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
+
+    if (delta.left) offset.left += delta.left
+    else offset.top += delta.top
+
+    var arrowDelta          = delta.left ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
+    var arrowPosition       = delta.left ? 'left'        : 'top'
+    var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight'
+
+    $tip.offset(offset)
+    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition)
+  }
+
+  Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
+    this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
+  }
+
+  Tooltip.prototype.setContent = function () {
+    var $tip  = this.tip()
+    var title = this.getTitle()
+
+    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+    $tip.removeClass('fade in top bottom left right')
+  }
+
+  Tooltip.prototype.hide = function () {
+    var that = this
+    var $tip = this.tip()
+    var e    = $.Event('hide.bs.' + this.type)
+
+    this.$element.removeAttr('aria-describedby')
+
+    function complete() {
+      if (that.hoverState != 'in') $tip.detach()
+      that.$element.trigger('hidden.bs.' + that.type)
+    }
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    $tip.removeClass('in')
+
+    $.support.transition && this.$tip.hasClass('fade') ?
+      $tip
+        .one('bsTransitionEnd', complete)
+        .emulateTransitionEnd(150) :
+      complete()
+
+    this.hoverState = null
+
+    return this
+  }
+
+  Tooltip.prototype.fixTitle = function () {
+    var $e = this.$element
+    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
+      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+    }
+  }
+
+  Tooltip.prototype.hasContent = function () {
+    return this.getTitle()
+  }
+
+  Tooltip.prototype.getPosition = function ($element) {
+    $element   = $element || this.$element
+    var el     = $element[0]
+    var isBody = el.tagName == 'BODY'
+    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : null, {
+      scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(),
+      width:  isBody ? $(window).width()  : $element.outerWidth(),
+      height: isBody ? $(window).height() : $element.outerHeight()
+    }, isBody ? { top: 0, left: 0 } : $element.offset())
+  }
+
+  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
+           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width   }
+
+  }
+
+  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
+    var delta = { top: 0, left: 0 }
+    if (!this.$viewport) return delta
+
+    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
+    var viewportDimensions = this.getPosition(this.$viewport)
+
+    if (/right|left/.test(placement)) {
+      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
+      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
+      if (topEdgeOffset < viewportDimensions.top) { // top overflow
+        delta.top = viewportDimensions.top - topEdgeOffset
+      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
+        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
+      }
+    } else {
+      var leftEdgeOffset  = pos.left - viewportPadding
+      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
+      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
+        delta.left = viewportDimensions.left - leftEdgeOffset
+      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
+        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
+      }
+    }
+
+    return delta
+  }
+
+  Tooltip.prototype.getTitle = function () {
+    var title
+    var $e = this.$element
+    var o  = this.options
+
+    title = $e.attr('data-original-title')
+      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+    return title
+  }
+
+  Tooltip.prototype.getUID = function (prefix) {
+    do prefix += ~~(Math.random() * 1000000)
+    while (document.getElementById(prefix))
+    return prefix
+  }
+
+  Tooltip.prototype.tip = function () {
+    return (this.$tip = this.$tip || $(this.options.template))
+  }
+
+  Tooltip.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
+  }
+
+  Tooltip.prototype.validate = function () {
+    if (!this.$element[0].parentNode) {
+      this.hide()
+      this.$element = null
+      this.options  = null
+    }
+  }
+
+  Tooltip.prototype.enable = function () {
+    this.enabled = true
+  }
+
+  Tooltip.prototype.disable = function () {
+    this.enabled = false
+  }
+
+  Tooltip.prototype.toggleEnabled = function () {
+    this.enabled = !this.enabled
+  }
+
+  Tooltip.prototype.toggle = function (e) {
+    var self = this
+    if (e) {
+      self = $(e.currentTarget).data('bs.' + this.type)
+      if (!self) {
+        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
+        $(e.currentTarget).data('bs.' + this.type, self)
+      }
+    }
+
+    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+  }
+
+  Tooltip.prototype.destroy = function () {
+    clearTimeout(this.timeout)
+    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+  }
+
+
+  // TOOLTIP PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.tooltip')
+      var options = typeof option == 'object' && option
+
+      if (!data && option == 'destroy') return
+      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip             = Plugin
+  $.fn.tooltip.Constructor = Tooltip
+
+
+  // TOOLTIP NO CONFLICT
+  // ===================
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.2.0
+ * http://getbootstrap.com/javascript/#popovers
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // POPOVER PUBLIC CLASS DEFINITION
+  // ===============================
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+  Popover.VERSION  = '3.2.0'
+
+  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
+    placement: 'right',
+    trigger: 'click',
+    content: '',
+    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+  // NOTE: POPOVER EXTENDS tooltip.js
+  // ================================
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+  Popover.prototype.constructor = Popover
+
+  Popover.prototype.getDefaults = function () {
+    return Popover.DEFAULTS
+  }
+
+  Popover.prototype.setContent = function () {
+    var $tip    = this.tip()
+    var title   = this.getTitle()
+    var content = this.getContent()
+
+    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+    $tip.find('.popover-content').empty()[ // we use append for html objects to maintain js events
+      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
+    ](content)
+
+    $tip.removeClass('fade top bottom left right in')
+
+    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+    // this manually by checking the contents.
+    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+  }
+
+  Popover.prototype.hasContent = function () {
+    return this.getTitle() || this.getContent()
+  }
+
+  Popover.prototype.getContent = function () {
+    var $e = this.$element
+    var o  = this.options
+
+    return $e.attr('data-content')
+      || (typeof o.content == 'function' ?
+            o.content.call($e[0]) :
+            o.content)
+  }
+
+  Popover.prototype.arrow = function () {
+    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
+  }
+
+  Popover.prototype.tip = function () {
+    if (!this.$tip) this.$tip = $(this.options.template)
+    return this.$tip
+  }
+
+
+  // POPOVER PLUGIN DEFINITION
+  // =========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.popover')
+      var options = typeof option == 'object' && option
+
+      if (!data && option == 'destroy') return
+      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.popover
+
+  $.fn.popover             = Plugin
+  $.fn.popover.Constructor = Popover
+
+
+  // POPOVER NO CONFLICT
+  // ===================
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.2.0
+ * http://getbootstrap.com/javascript/#tabs
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // TAB CLASS DEFINITION
+  // ====================
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.VERSION = '3.2.0'
+
+  Tab.prototype.show = function () {
+    var $this    = this.element
+    var $ul      = $this.closest('ul:not(.dropdown-menu)')
+    var selector = $this.data('target')
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+    }
+
+    if ($this.parent('li').hasClass('active')) return
+
+    var previous = $ul.find('.active:last a')[0]
+    var e        = $.Event('show.bs.tab', {
+      relatedTarget: previous
+    })
+
+    $this.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    var $target = $(selector)
+
+    this.activate($this.closest('li'), $ul)
+    this.activate($target, $target.parent(), function () {
+      $this.trigger({
+        type: 'shown.bs.tab',
+        relatedTarget: previous
+      })
+    })
+  }
+
+  Tab.prototype.activate = function (element, container, callback) {
+    var $active    = container.find('> .active')
+    var transition = callback
+      && $.support.transition
+      && $active.hasClass('fade')
+
+    function next() {
+      $active
+        .removeClass('active')
+        .find('> .dropdown-menu > .active')
+        .removeClass('active')
+
+      element.addClass('active')
+
+      if (transition) {
+        element[0].offsetWidth // reflow for transition
+        element.addClass('in')
+      } else {
+        element.removeClass('fade')
+      }
+
+      if (element.parent('.dropdown-menu')) {
+        element.closest('li.dropdown').addClass('active')
+      }
+
+      callback && callback()
+    }
+
+    transition ?
+      $active
+        .one('bsTransitionEnd', next)
+        .emulateTransitionEnd(150) :
+      next()
+
+    $active.removeClass('in')
+  }
+
+
+  // TAB PLUGIN DEFINITION
+  // =====================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this = $(this)
+      var data  = $this.data('bs.tab')
+
+      if (!data) $this.data('bs.tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.tab
+
+  $.fn.tab             = Plugin
+  $.fn.tab.Constructor = Tab
+
+
+  // TAB NO CONFLICT
+  // ===============
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+  // TAB DATA-API
+  // ============
+
+  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    Plugin.call($(this), 'show')
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.2.0
+ * http://getbootstrap.com/javascript/#affix
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // AFFIX CLASS DEFINITION
+  // ======================
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, Affix.DEFAULTS, options)
+
+    this.$target = $(this.options.target)
+      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))
+
+    this.$element     = $(element)
+    this.affixed      =
+    this.unpin        =
+    this.pinnedOffset = null
+
+    this.checkPosition()
+  }
+
+  Affix.VERSION  = '3.2.0'
+
+  Affix.RESET    = 'affix affix-top affix-bottom'
+
+  Affix.DEFAULTS = {
+    offset: 0,
+    target: window
+  }
+
+  Affix.prototype.getPinnedOffset = function () {
+    if (this.pinnedOffset) return this.pinnedOffset
+    this.$element.removeClass(Affix.RESET).addClass('affix')
+    var scrollTop = this.$target.scrollTop()
+    var position  = this.$element.offset()
+    return (this.pinnedOffset = position.top - scrollTop)
+  }
+
+  Affix.prototype.checkPositionWithEventLoop = function () {
+    setTimeout($.proxy(this.checkPosition, this), 1)
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+    var scrollTop    = this.$target.scrollTop()
+    var position     = this.$element.offset()
+    var offset       = this.options.offset
+    var offsetTop    = offset.top
+    var offsetBottom = offset.bottom
+
+    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
+
+    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
+                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false
+
+    if (this.affixed === affix) return
+    if (this.unpin != null) this.$element.css('top', '')
+
+    var affixType = 'affix' + (affix ? '-' + affix : '')
+    var e         = $.Event(affixType + '.bs.affix')
+
+    this.$element.trigger(e)
+
+    if (e.isDefaultPrevented()) return
+
+    this.affixed = affix
+    this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
+
+    this.$element
+      .removeClass(Affix.RESET)
+      .addClass(affixType)
+      .trigger($.Event(affixType.replace('affix', 'affixed')))
+
+    if (affix == 'bottom') {
+      this.$element.offset({
+        top: scrollHeight - this.$element.height() - offsetBottom
+      })
+    }
+  }
+
+
+  // AFFIX PLUGIN DEFINITION
+  // =======================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.affix')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.affix
+
+  $.fn.affix             = Plugin
+  $.fn.affix.Constructor = Affix
+
+
+  // AFFIX NO CONFLICT
+  // =================
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+  // AFFIX DATA-API
+  // ==============
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+      var data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+      if (data.offsetTop)    data.offset.top    = data.offsetTop
+
+      Plugin.call($spy, data)
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.2.0
+ * http://getbootstrap.com/javascript/#collapse
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // COLLAPSE PUBLIC CLASS DEFINITION
+  // ================================
+
+  var Collapse = function (element, options) {
+    this.$element      = $(element)
+    this.options       = $.extend({}, Collapse.DEFAULTS, options)
+    this.transitioning = null
+
+    if (this.options.parent) this.$parent = $(this.options.parent)
+    if (this.options.toggle) this.toggle()
+  }
+
+  Collapse.VERSION  = '3.2.0'
+
+  Collapse.DEFAULTS = {
+    toggle: true
+  }
+
+  Collapse.prototype.dimension = function () {
+    var hasWidth = this.$element.hasClass('width')
+    return hasWidth ? 'width' : 'height'
+  }
+
+  Collapse.prototype.show = function () {
+    if (this.transitioning || this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('show.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var actives = this.$parent && this.$parent.find('> .panel > .in')
+
+    if (actives && actives.length) {
+      var hasData = actives.data('bs.collapse')
+      if (hasData && hasData.transitioning) return
+      Plugin.call(actives, 'hide')
+      hasData || actives.data('bs.collapse', null)
+    }
+
+    var dimension = this.dimension()
+
+    this.$element
+      .removeClass('collapse')
+      .addClass('collapsing')[dimension](0)
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.$element
+        .removeClass('collapsing')
+        .addClass('collapse in')[dimension]('')
+      this.transitioning = 0
+      this.$element
+        .trigger('shown.bs.collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+    this.$element
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize])
+  }
+
+  Collapse.prototype.hide = function () {
+    if (this.transitioning || !this.$element.hasClass('in')) return
+
+    var startEvent = $.Event('hide.bs.collapse')
+    this.$element.trigger(startEvent)
+    if (startEvent.isDefaultPrevented()) return
+
+    var dimension = this.dimension()
+
+    this.$element[dimension](this.$element[dimension]())[0].offsetHeight
+
+    this.$element
+      .addClass('collapsing')
+      .removeClass('collapse')
+      .removeClass('in')
+
+    this.transitioning = 1
+
+    var complete = function () {
+      this.transitioning = 0
+      this.$element
+        .trigger('hidden.bs.collapse')
+        .removeClass('collapsing')
+        .addClass('collapse')
+    }
+
+    if (!$.support.transition) return complete.call(this)
+
+    this.$element
+      [dimension](0)
+      .one('bsTransitionEnd', $.proxy(complete, this))
+      .emulateTransitionEnd(350)
+  }
+
+  Collapse.prototype.toggle = function () {
+    this[this.$element.hasClass('in') ? 'hide' : 'show']()
+  }
+
+
+  // COLLAPSE PLUGIN DEFINITION
+  // ==========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.collapse')
+      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+      if (!data && options.toggle && option == 'show') option = !option
+      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.collapse
+
+  $.fn.collapse             = Plugin
+  $.fn.collapse.Constructor = Collapse
+
+
+  // COLLAPSE NO CONFLICT
+  // ====================
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+  // COLLAPSE DATA-API
+  // =================
+
+  $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
+    var href
+    var $this   = $(this)
+    var target  = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
+    var $target = $(target)
+    var data    = $target.data('bs.collapse')
+    var option  = data ? 'toggle' : $this.data()
+    var parent  = $this.attr('data-parent')
+    var $parent = parent && $(parent)
+
+    if (!data || !data.transitioning) {
+      if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed')
+      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    }
+
+    Plugin.call($target, option)
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.2.0
+ * http://getbootstrap.com/javascript/#scrollspy
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // SCROLLSPY CLASS DEFINITION
+  // ==========================
+
+  function ScrollSpy(element, options) {
+    var process  = $.proxy(this.process, this)
+
+    this.$body          = $('body')
+    this.$scrollElement = $(element).is('body') ? $(window) : $(element)
+    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
+    this.selector       = (this.options.target || '') + ' .nav li > a'
+    this.offsets        = []
+    this.targets        = []
+    this.activeTarget   = null
+    this.scrollHeight   = 0
+
+    this.$scrollElement.on('scroll.bs.scrollspy', process)
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.VERSION  = '3.2.0'
+
+  ScrollSpy.DEFAULTS = {
+    offset: 10
+  }
+
+  ScrollSpy.prototype.getScrollHeight = function () {
+    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
+  }
+
+  ScrollSpy.prototype.refresh = function () {
+    var offsetMethod = 'offset'
+    var offsetBase   = 0
+
+    if (!$.isWindow(this.$scrollElement[0])) {
+      offsetMethod = 'position'
+      offsetBase   = this.$scrollElement.scrollTop()
+    }
+
+    this.offsets = []
+    this.targets = []
+    this.scrollHeight = this.getScrollHeight()
+
+    var self     = this
+
+    this.$body
+      .find(this.selector)
+      .map(function () {
+        var $el   = $(this)
+        var href  = $el.data('target') || $el.attr('href')
+        var $href = /^#./.test(href) && $(href)
+
+        return ($href
+          && $href.length
+          && $href.is(':visible')
+          && [[$href[offsetMethod]().top + offsetBase, href]]) || null
+      })
+      .sort(function (a, b) { return a[0] - b[0] })
+      .each(function () {
+        self.offsets.push(this[0])
+        self.targets.push(this[1])
+      })
+  }
+
+  ScrollSpy.prototype.process = function () {
+    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
+    var scrollHeight = this.getScrollHeight()
+    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
+    var offsets      = this.offsets
+    var targets      = this.targets
+    var activeTarget = this.activeTarget
+    var i
+
+    if (this.scrollHeight != scrollHeight) {
+      this.refresh()
+    }
+
+    if (scrollTop >= maxScroll) {
+      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
+    }
+
+    if (activeTarget && scrollTop <= offsets[0]) {
+      return activeTarget != (i = targets[0]) && this.activate(i)
+    }
+
+    for (i = offsets.length; i--;) {
+      activeTarget != targets[i]
+        && scrollTop >= offsets[i]
+        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+        && this.activate(targets[i])
+    }
+  }
+
+  ScrollSpy.prototype.activate = function (target) {
+    this.activeTarget = target
+
+    $(this.selector)
+      .parentsUntil(this.options.target, '.active')
+      .removeClass('active')
+
+    var selector = this.selector +
+        '[data-target="' + target + '"],' +
+        this.selector + '[href="' + target + '"]'
+
+    var active = $(selector)
+      .parents('li')
+      .addClass('active')
+
+    if (active.parent('.dropdown-menu').length) {
+      active = active
+        .closest('li.dropdown')
+        .addClass('active')
+    }
+
+    active.trigger('activate.bs.scrollspy')
+  }
+
+
+  // SCROLLSPY PLUGIN DEFINITION
+  // ===========================
+
+  function Plugin(option) {
+    return this.each(function () {
+      var $this   = $(this)
+      var data    = $this.data('bs.scrollspy')
+      var options = typeof option == 'object' && option
+
+      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy             = Plugin
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+
+  // SCROLLSPY NO CONFLICT
+  // =====================
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+  // SCROLLSPY DATA-API
+  // ==================
+
+  $(window).on('load.bs.scrollspy.data-api', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      Plugin.call($spy, $spy.data())
+    })
+  })
+
+}(jQuery);
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.2.0
+ * http://getbootstrap.com/javascript/#transitions
+ * ========================================================================
+ * Copyright 2011-2014 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * ======================================================================== */
+
+
++function ($) {
+  'use strict';
+
+  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+  // ============================================================
+
+  function transitionEnd() {
+    var el = document.createElement('bootstrap')
+
+    var transEndEventNames = {
+      WebkitTransition : 'webkitTransitionEnd',
+      MozTransition    : 'transitionend',
+      OTransition      : 'oTransitionEnd otransitionend',
+      transition       : 'transitionend'
+    }
+
+    for (var name in transEndEventNames) {
+      if (el.style[name] !== undefined) {
+        return { end: transEndEventNames[name] }
+      }
+    }
+
+    return false // explicit for ie8 (  ._.)
+  }
+
+  // http://blog.alexmaccaw.com/css-transitions
+  $.fn.emulateTransitionEnd = function (duration) {
+    var called = false
+    var $el = this
+    $(this).one('bsTransitionEnd', function () { called = true })
+    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+    setTimeout(callback, duration)
+    return this
+  }
+
+  $(function () {
+    $.support.transition = transitionEnd()
+
+    if (!$.support.transition) return
+
+    $.event.special.bsTransitionEnd = {
+      bindType: $.support.transition.end,
+      delegateType: $.support.transition.end,
+      handle: function (e) {
+        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
+      }
+    }
+  })
+
+}(jQuery);
diff --git a/tools/infra-dashboard/js/fullcalendar.js b/tools/infra-dashboard/js/fullcalendar.js
new file mode 100644 (file)
index 0000000..958ca24
--- /dev/null
@@ -0,0 +1,12670 @@
+/*!
+ * FullCalendar v2.7.2
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+
+(function(factory) {
+       if (typeof define === 'function' && define.amd) {
+               define([ 'jquery', 'moment' ], factory);
+       }
+       else if (typeof exports === 'object') { // Node/CommonJS
+               module.exports = factory(require('jquery'), require('moment'));
+       }
+       else {
+               factory(jQuery, moment);
+       }
+})(function($, moment) {
+
+;;
+
+var FC = $.fullCalendar = {
+       version: "2.7.2",
+       internalApiVersion: 3
+};
+var fcViews = FC.views = {};
+
+
+$.fn.fullCalendar = function(options) {
+       var args = Array.prototype.slice.call(arguments, 1); // for a possible method call
+       var res = this; // what this function will return (this jQuery object by default)
+
+       this.each(function(i, _element) { // loop each DOM element involved
+               var element = $(_element);
+               var calendar = element.data('fullCalendar'); // get the existing calendar object (if any)
+               var singleRes; // the returned value of this single method call
+
+               // a method call
+               if (typeof options === 'string') {
+                       if (calendar && $.isFunction(calendar[options])) {
+                               singleRes = calendar[options].apply(calendar, args);
+                               if (!i) {
+                                       res = singleRes; // record the first method call result
+                               }
+                               if (options === 'destroy') { // for the destroy method, must remove Calendar object data
+                                       element.removeData('fullCalendar');
+                               }
+                       }
+               }
+               // a new calendar initialization
+               else if (!calendar) { // don't initialize twice
+                       calendar = new Calendar(element, options);
+                       element.data('fullCalendar', calendar);
+                       calendar.render();
+               }
+       });
+       
+       return res;
+};
+
+
+var complexOptions = [ // names of options that are objects whose properties should be combined
+       'header',
+       'buttonText',
+       'buttonIcons',
+       'themeButtonIcons'
+];
+
+
+// Merges an array of option objects into a single object
+function mergeOptions(optionObjs) {
+       return mergeProps(optionObjs, complexOptions);
+}
+
+
+// Given options specified for the calendar's constructor, massages any legacy options into a non-legacy form.
+// Converts View-Option-Hashes into the View-Specific-Options format.
+function massageOverrides(input) {
+       var overrides = { views: input.views || {} }; // the output. ensure a `views` hash
+       var subObj;
+
+       // iterate through all option override properties (except `views`)
+       $.each(input, function(name, val) {
+               if (name != 'views') {
+
+                       // could the value be a legacy View-Option-Hash?
+                       if (
+                               $.isPlainObject(val) &&
+                               !/(time|duration|interval)$/i.test(name) && // exclude duration options. might be given as objects
+                               $.inArray(name, complexOptions) == -1 // complex options aren't allowed to be View-Option-Hashes
+                       ) {
+                               subObj = null;
+
+                               // iterate through the properties of this possible View-Option-Hash value
+                               $.each(val, function(subName, subVal) {
+
+                                       // is the property targeting a view?
+                                       if (/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(subName)) {
+                                               if (!overrides.views[subName]) { // ensure the view-target entry exists
+                                                       overrides.views[subName] = {};
+                                               }
+                                               overrides.views[subName][name] = subVal; // record the value in the `views` object
+                                       }
+                                       else { // a non-View-Option-Hash property
+                                               if (!subObj) {
+                                                       subObj = {};
+                                               }
+                                               subObj[subName] = subVal; // accumulate these unrelated values for later
+                                       }
+                               });
+
+                               if (subObj) { // non-View-Option-Hash properties? transfer them as-is
+                                       overrides[name] = subObj;
+                               }
+                       }
+                       else {
+                               overrides[name] = val; // transfer normal options as-is
+                       }
+               }
+       });
+
+       return overrides;
+}
+
+;;
+
+// exports
+FC.intersectRanges = intersectRanges;
+FC.applyAll = applyAll;
+FC.debounce = debounce;
+FC.isInt = isInt;
+FC.htmlEscape = htmlEscape;
+FC.cssToStr = cssToStr;
+FC.proxy = proxy;
+FC.capitaliseFirstLetter = capitaliseFirstLetter;
+
+
+/* FullCalendar-specific DOM Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+
+// Given the scrollbar widths of some other container, create borders/margins on rowEls in order to match the left
+// and right space that was offset by the scrollbars. A 1-pixel border first, then margin beyond that.
+function compensateScroll(rowEls, scrollbarWidths) {
+       if (scrollbarWidths.left) {
+               rowEls.css({
+                       'border-left-width': 1,
+                       'margin-left': scrollbarWidths.left - 1
+               });
+       }
+       if (scrollbarWidths.right) {
+               rowEls.css({
+                       'border-right-width': 1,
+                       'margin-right': scrollbarWidths.right - 1
+               });
+       }
+}
+
+
+// Undoes compensateScroll and restores all borders/margins
+function uncompensateScroll(rowEls) {
+       rowEls.css({
+               'margin-left': '',
+               'margin-right': '',
+               'border-left-width': '',
+               'border-right-width': ''
+       });
+}
+
+
+// Make the mouse cursor express that an event is not allowed in the current area
+function disableCursor() {
+       $('body').addClass('fc-not-allowed');
+}
+
+
+// Returns the mouse cursor to its original look
+function enableCursor() {
+       $('body').removeClass('fc-not-allowed');
+}
+
+
+// Given a total available height to fill, have `els` (essentially child rows) expand to accomodate.
+// By default, all elements that are shorter than the recommended height are expanded uniformly, not considering
+// any other els that are already too tall. if `shouldRedistribute` is on, it considers these tall rows and 
+// reduces the available height.
+function distributeHeight(els, availableHeight, shouldRedistribute) {
+
+       // *FLOORING NOTE*: we floor in certain places because zoom can give inaccurate floating-point dimensions,
+       // and it is better to be shorter than taller, to avoid creating unnecessary scrollbars.
+
+       var minOffset1 = Math.floor(availableHeight / els.length); // for non-last element
+       var minOffset2 = Math.floor(availableHeight - minOffset1 * (els.length - 1)); // for last element *FLOORING NOTE*
+       var flexEls = []; // elements that are allowed to expand. array of DOM nodes
+       var flexOffsets = []; // amount of vertical space it takes up
+       var flexHeights = []; // actual css height
+       var usedHeight = 0;
+
+       undistributeHeight(els); // give all elements their natural height
+
+       // find elements that are below the recommended height (expandable).
+       // important to query for heights in a single first pass (to avoid reflow oscillation).
+       els.each(function(i, el) {
+               var minOffset = i === els.length - 1 ? minOffset2 : minOffset1;
+               var naturalOffset = $(el).outerHeight(true);
+
+               if (naturalOffset < minOffset) {
+                       flexEls.push(el);
+                       flexOffsets.push(naturalOffset);
+                       flexHeights.push($(el).height());
+               }
+               else {
+                       // this element stretches past recommended height (non-expandable). mark the space as occupied.
+                       usedHeight += naturalOffset;
+               }
+       });
+
+       // readjust the recommended height to only consider the height available to non-maxed-out rows.
+       if (shouldRedistribute) {
+               availableHeight -= usedHeight;
+               minOffset1 = Math.floor(availableHeight / flexEls.length);
+               minOffset2 = Math.floor(availableHeight - minOffset1 * (flexEls.length - 1)); // *FLOORING NOTE*
+       }
+
+       // assign heights to all expandable elements
+       $(flexEls).each(function(i, el) {
+               var minOffset = i === flexEls.length - 1 ? minOffset2 : minOffset1;
+               var naturalOffset = flexOffsets[i];
+               var naturalHeight = flexHeights[i];
+               var newHeight = minOffset - (naturalOffset - naturalHeight); // subtract the margin/padding
+
+               if (naturalOffset < minOffset) { // we check this again because redistribution might have changed things
+                       $(el).height(newHeight);
+               }
+       });
+}
+
+
+// Undoes distrubuteHeight, restoring all els to their natural height
+function undistributeHeight(els) {
+       els.height('');
+}
+
+
+// Given `els`, a jQuery set of <td> cells, find the cell with the largest natural width and set the widths of all the
+// cells to be that width.
+// PREREQUISITE: if you want a cell to take up width, it needs to have a single inner element w/ display:inline
+function matchCellWidths(els) {
+       var maxInnerWidth = 0;
+
+       els.find('> span').each(function(i, innerEl) {
+               var innerWidth = $(innerEl).outerWidth();
+               if (innerWidth > maxInnerWidth) {
+                       maxInnerWidth = innerWidth;
+               }
+       });
+
+       maxInnerWidth++; // sometimes not accurate of width the text needs to stay on one line. insurance
+
+       els.width(maxInnerWidth);
+
+       return maxInnerWidth;
+}
+
+
+// Given one element that resides inside another,
+// Subtracts the height of the inner element from the outer element.
+function subtractInnerElHeight(outerEl, innerEl) {
+       var both = outerEl.add(innerEl);
+       var diff;
+
+       // effin' IE8/9/10/11 sometimes returns 0 for dimensions. this weird hack was the only thing that worked
+       both.css({
+               position: 'relative', // cause a reflow, which will force fresh dimension recalculation
+               left: -1 // ensure reflow in case the el was already relative. negative is less likely to cause new scroll
+       });
+       diff = outerEl.outerHeight() - innerEl.outerHeight(); // grab the dimensions
+       both.css({ position: '', left: '' }); // undo hack
+
+       return diff;
+}
+
+
+/* Element Geom Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.getOuterRect = getOuterRect;
+FC.getClientRect = getClientRect;
+FC.getContentRect = getContentRect;
+FC.getScrollbarWidths = getScrollbarWidths;
+
+
+// borrowed from https://github.com/jquery/jquery-ui/blob/1.11.0/ui/core.js#L51
+function getScrollParent(el) {
+       var position = el.css('position'),
+               scrollParent = el.parents().filter(function() {
+                       var parent = $(this);
+                       return (/(auto|scroll)/).test(
+                               parent.css('overflow') + parent.css('overflow-y') + parent.css('overflow-x')
+                       );
+               }).eq(0);
+
+       return position === 'fixed' || !scrollParent.length ? $(el[0].ownerDocument || document) : scrollParent;
+}
+
+
+// Queries the outer bounding area of a jQuery element.
+// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
+// Origin is optional.
+function getOuterRect(el, origin) {
+       var offset = el.offset();
+       var left = offset.left - (origin ? origin.left : 0);
+       var top = offset.top - (origin ? origin.top : 0);
+
+       return {
+               left: left,
+               right: left + el.outerWidth(),
+               top: top,
+               bottom: top + el.outerHeight()
+       };
+}
+
+
+// Queries the area within the margin/border/scrollbars of a jQuery element. Does not go within the padding.
+// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
+// Origin is optional.
+// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
+function getClientRect(el, origin) {
+       var offset = el.offset();
+       var scrollbarWidths = getScrollbarWidths(el);
+       var left = offset.left + getCssFloat(el, 'border-left-width') + scrollbarWidths.left - (origin ? origin.left : 0);
+       var top = offset.top + getCssFloat(el, 'border-top-width') + scrollbarWidths.top - (origin ? origin.top : 0);
+
+       return {
+               left: left,
+               right: left + el[0].clientWidth, // clientWidth includes padding but NOT scrollbars
+               top: top,
+               bottom: top + el[0].clientHeight // clientHeight includes padding but NOT scrollbars
+       };
+}
+
+
+// Queries the area within the margin/border/padding of a jQuery element. Assumed not to have scrollbars.
+// Returns a rectangle with absolute coordinates: left, right (exclusive), top, bottom (exclusive).
+// Origin is optional.
+function getContentRect(el, origin) {
+       var offset = el.offset(); // just outside of border, margin not included
+       var left = offset.left + getCssFloat(el, 'border-left-width') + getCssFloat(el, 'padding-left') -
+               (origin ? origin.left : 0);
+       var top = offset.top + getCssFloat(el, 'border-top-width') + getCssFloat(el, 'padding-top') -
+               (origin ? origin.top : 0);
+
+       return {
+               left: left,
+               right: left + el.width(),
+               top: top,
+               bottom: top + el.height()
+       };
+}
+
+
+// Returns the computed left/right/top/bottom scrollbar widths for the given jQuery element.
+// NOTE: should use clientLeft/clientTop, but very unreliable cross-browser.
+function getScrollbarWidths(el) {
+       var leftRightWidth = el.innerWidth() - el[0].clientWidth; // the paddings cancel out, leaving the scrollbars
+       var widths = {
+               left: 0,
+               right: 0,
+               top: 0,
+               bottom: el.innerHeight() - el[0].clientHeight // the paddings cancel out, leaving the bottom scrollbar
+       };
+
+       if (getIsLeftRtlScrollbars() && el.css('direction') == 'rtl') { // is the scrollbar on the left side?
+               widths.left = leftRightWidth;
+       }
+       else {
+               widths.right = leftRightWidth;
+       }
+
+       return widths;
+}
+
+
+// Logic for determining if, when the element is right-to-left, the scrollbar appears on the left side
+
+var _isLeftRtlScrollbars = null;
+
+function getIsLeftRtlScrollbars() { // responsible for caching the computation
+       if (_isLeftRtlScrollbars === null) {
+               _isLeftRtlScrollbars = computeIsLeftRtlScrollbars();
+       }
+       return _isLeftRtlScrollbars;
+}
+
+function computeIsLeftRtlScrollbars() { // creates an offscreen test element, then removes it
+       var el = $('<div><div/></div>')
+               .css({
+                       position: 'absolute',
+                       top: -1000,
+                       left: 0,
+                       border: 0,
+                       padding: 0,
+                       overflow: 'scroll',
+                       direction: 'rtl'
+               })
+               .appendTo('body');
+       var innerEl = el.children();
+       var res = innerEl.offset().left > el.offset().left; // is the inner div shifted to accommodate a left scrollbar?
+       el.remove();
+       return res;
+}
+
+
+// Retrieves a jQuery element's computed CSS value as a floating-point number.
+// If the queried value is non-numeric (ex: IE can return "medium" for border width), will just return zero.
+function getCssFloat(el, prop) {
+       return parseFloat(el.css(prop)) || 0;
+}
+
+
+/* Mouse / Touch Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.preventDefault = preventDefault;
+
+
+// Returns a boolean whether this was a left mouse click and no ctrl key (which means right click on Mac)
+function isPrimaryMouseButton(ev) {
+       return ev.which == 1 && !ev.ctrlKey;
+}
+
+
+function getEvX(ev) {
+       if (ev.pageX !== undefined) {
+               return ev.pageX;
+       }
+       var touches = ev.originalEvent.touches;
+       if (touches) {
+               return touches[0].pageX;
+       }
+}
+
+
+function getEvY(ev) {
+       if (ev.pageY !== undefined) {
+               return ev.pageY;
+       }
+       var touches = ev.originalEvent.touches;
+       if (touches) {
+               return touches[0].pageY;
+       }
+}
+
+
+function getEvIsTouch(ev) {
+       return /^touch/.test(ev.type);
+}
+
+
+function preventSelection(el) {
+       el.addClass('fc-unselectable')
+               .on('selectstart', preventDefault);
+}
+
+
+// Stops a mouse/touch event from doing it's native browser action
+function preventDefault(ev) {
+       ev.preventDefault();
+}
+
+
+// attach a handler to get called when ANY scroll action happens on the page.
+// this was impossible to do with normal on/off because 'scroll' doesn't bubble.
+// http://stackoverflow.com/a/32954565/96342
+// returns `true` on success.
+function bindAnyScroll(handler) {
+       if (window.addEventListener) {
+               window.addEventListener('scroll', handler, true); // useCapture=true
+               return true;
+       }
+       return false;
+}
+
+
+// undoes bindAnyScroll. must pass in the original function.
+// returns `true` on success.
+function unbindAnyScroll(handler) {
+       if (window.removeEventListener) {
+               window.removeEventListener('scroll', handler, true); // useCapture=true
+               return true;
+       }
+       return false;
+}
+
+
+/* General Geometry Utils
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.intersectRects = intersectRects;
+
+// Returns a new rectangle that is the intersection of the two rectangles. If they don't intersect, returns false
+function intersectRects(rect1, rect2) {
+       var res = {
+               left: Math.max(rect1.left, rect2.left),
+               right: Math.min(rect1.right, rect2.right),
+               top: Math.max(rect1.top, rect2.top),
+               bottom: Math.min(rect1.bottom, rect2.bottom)
+       };
+
+       if (res.left < res.right && res.top < res.bottom) {
+               return res;
+       }
+       return false;
+}
+
+
+// Returns a new point that will have been moved to reside within the given rectangle
+function constrainPoint(point, rect) {
+       return {
+               left: Math.min(Math.max(point.left, rect.left), rect.right),
+               top: Math.min(Math.max(point.top, rect.top), rect.bottom)
+       };
+}
+
+
+// Returns a point that is the center of the given rectangle
+function getRectCenter(rect) {
+       return {
+               left: (rect.left + rect.right) / 2,
+               top: (rect.top + rect.bottom) / 2
+       };
+}
+
+
+// Subtracts point2's coordinates from point1's coordinates, returning a delta
+function diffPoints(point1, point2) {
+       return {
+               left: point1.left - point2.left,
+               top: point1.top - point2.top
+       };
+}
+
+
+/* Object Ordering by Field
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.parseFieldSpecs = parseFieldSpecs;
+FC.compareByFieldSpecs = compareByFieldSpecs;
+FC.compareByFieldSpec = compareByFieldSpec;
+FC.flexibleCompare = flexibleCompare;
+
+
+function parseFieldSpecs(input) {
+       var specs = [];
+       var tokens = [];
+       var i, token;
+
+       if (typeof input === 'string') {
+               tokens = input.split(/\s*,\s*/);
+       }
+       else if (typeof input === 'function') {
+               tokens = [ input ];
+       }
+       else if ($.isArray(input)) {
+               tokens = input;
+       }
+
+       for (i = 0; i < tokens.length; i++) {
+               token = tokens[i];
+
+               if (typeof token === 'string') {
+                       specs.push(
+                               token.charAt(0) == '-' ?
+                                       { field: token.substring(1), order: -1 } :
+                                       { field: token, order: 1 }
+                       );
+               }
+               else if (typeof token === 'function') {
+                       specs.push({ func: token });
+               }
+       }
+
+       return specs;
+}
+
+
+function compareByFieldSpecs(obj1, obj2, fieldSpecs) {
+       var i;
+       var cmp;
+
+       for (i = 0; i < fieldSpecs.length; i++) {
+               cmp = compareByFieldSpec(obj1, obj2, fieldSpecs[i]);
+               if (cmp) {
+                       return cmp;
+               }
+       }
+
+       return 0;
+}
+
+
+function compareByFieldSpec(obj1, obj2, fieldSpec) {
+       if (fieldSpec.func) {
+               return fieldSpec.func(obj1, obj2);
+       }
+       return flexibleCompare(obj1[fieldSpec.field], obj2[fieldSpec.field]) *
+               (fieldSpec.order || 1);
+}
+
+
+function flexibleCompare(a, b) {
+       if (!a && !b) {
+               return 0;
+       }
+       if (b == null) {
+               return -1;
+       }
+       if (a == null) {
+               return 1;
+       }
+       if ($.type(a) === 'string' || $.type(b) === 'string') {
+               return String(a).localeCompare(String(b));
+       }
+       return a - b;
+}
+
+
+/* FullCalendar-specific Misc Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+
+// Computes the intersection of the two ranges. Returns undefined if no intersection.
+// Expects all dates to be normalized to the same timezone beforehand.
+// TODO: move to date section?
+function intersectRanges(subjectRange, constraintRange) {
+       var subjectStart = subjectRange.start;
+       var subjectEnd = subjectRange.end;
+       var constraintStart = constraintRange.start;
+       var constraintEnd = constraintRange.end;
+       var segStart, segEnd;
+       var isStart, isEnd;
+
+       if (subjectEnd > constraintStart && subjectStart < constraintEnd) { // in bounds at all?
+
+               if (subjectStart >= constraintStart) {
+                       segStart = subjectStart.clone();
+                       isStart = true;
+               }
+               else {
+                       segStart = constraintStart.clone();
+                       isStart =  false;
+               }
+
+               if (subjectEnd <= constraintEnd) {
+                       segEnd = subjectEnd.clone();
+                       isEnd = true;
+               }
+               else {
+                       segEnd = constraintEnd.clone();
+                       isEnd = false;
+               }
+
+               return {
+                       start: segStart,
+                       end: segEnd,
+                       isStart: isStart,
+                       isEnd: isEnd
+               };
+       }
+}
+
+
+/* Date Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.computeIntervalUnit = computeIntervalUnit;
+FC.divideRangeByDuration = divideRangeByDuration;
+FC.divideDurationByDuration = divideDurationByDuration;
+FC.multiplyDuration = multiplyDuration;
+FC.durationHasTime = durationHasTime;
+
+var dayIDs = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
+var intervalUnits = [ 'year', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond' ];
+
+
+// Diffs the two moments into a Duration where full-days are recorded first, then the remaining time.
+// Moments will have their timezones normalized.
+function diffDayTime(a, b) {
+       return moment.duration({
+               days: a.clone().stripTime().diff(b.clone().stripTime(), 'days'),
+               ms: a.time() - b.time() // time-of-day from day start. disregards timezone
+       });
+}
+
+
+// Diffs the two moments via their start-of-day (regardless of timezone). Produces whole-day durations.
+function diffDay(a, b) {
+       return moment.duration({
+               days: a.clone().stripTime().diff(b.clone().stripTime(), 'days')
+       });
+}
+
+
+// Diffs two moments, producing a duration, made of a whole-unit-increment of the given unit. Uses rounding.
+function diffByUnit(a, b, unit) {
+       return moment.duration(
+               Math.round(a.diff(b, unit, true)), // returnFloat=true
+               unit
+       );
+}
+
+
+// Computes the unit name of the largest whole-unit period of time.
+// For example, 48 hours will be "days" whereas 49 hours will be "hours".
+// Accepts start/end, a range object, or an original duration object.
+function computeIntervalUnit(start, end) {
+       var i, unit;
+       var val;
+
+       for (i = 0; i < intervalUnits.length; i++) {
+               unit = intervalUnits[i];
+               val = computeRangeAs(unit, start, end);
+
+               if (val >= 1 && isInt(val)) {
+                       break;
+               }
+       }
+
+       return unit; // will be "milliseconds" if nothing else matches
+}
+
+
+// Computes the number of units (like "hours") in the given range.
+// Range can be a {start,end} object, separate start/end args, or a Duration.
+// Results are based on Moment's .as() and .diff() methods, so results can depend on internal handling
+// of month-diffing logic (which tends to vary from version to version).
+function computeRangeAs(unit, start, end) {
+
+       if (end != null) { // given start, end
+               return end.diff(start, unit, true);
+       }
+       else if (moment.isDuration(start)) { // given duration
+               return start.as(unit);
+       }
+       else { // given { start, end } range object
+               return start.end.diff(start.start, unit, true);
+       }
+}
+
+
+// Intelligently divides a range (specified by a start/end params) by a duration
+function divideRangeByDuration(start, end, dur) {
+       var months;
+
+       if (durationHasTime(dur)) {
+               return (end - start) / dur;
+       }
+       months = dur.asMonths();
+       if (Math.abs(months) >= 1 && isInt(months)) {
+               return end.diff(start, 'months', true) / months;
+       }
+       return end.diff(start, 'days', true) / dur.asDays();
+}
+
+
+// Intelligently divides one duration by another
+function divideDurationByDuration(dur1, dur2) {
+       var months1, months2;
+
+       if (durationHasTime(dur1) || durationHasTime(dur2)) {
+               return dur1 / dur2;
+       }
+       months1 = dur1.asMonths();
+       months2 = dur2.asMonths();
+       if (
+               Math.abs(months1) >= 1 && isInt(months1) &&
+               Math.abs(months2) >= 1 && isInt(months2)
+       ) {
+               return months1 / months2;
+       }
+       return dur1.asDays() / dur2.asDays();
+}
+
+
+// Intelligently multiplies a duration by a number
+function multiplyDuration(dur, n) {
+       var months;
+
+       if (durationHasTime(dur)) {
+               return moment.duration(dur * n);
+       }
+       months = dur.asMonths();
+       if (Math.abs(months) >= 1 && isInt(months)) {
+               return moment.duration({ months: months * n });
+       }
+       return moment.duration({ days: dur.asDays() * n });
+}
+
+
+// Returns a boolean about whether the given duration has any time parts (hours/minutes/seconds/ms)
+function durationHasTime(dur) {
+       return Boolean(dur.hours() || dur.minutes() || dur.seconds() || dur.milliseconds());
+}
+
+
+function isNativeDate(input) {
+       return  Object.prototype.toString.call(input) === '[object Date]' || input instanceof Date;
+}
+
+
+// Returns a boolean about whether the given input is a time string, like "06:40:00" or "06:00"
+function isTimeString(str) {
+       return /^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(str);
+}
+
+
+/* Logging and Debug
+----------------------------------------------------------------------------------------------------------------------*/
+
+FC.log = function() {
+       var console = window.console;
+
+       if (console && console.log) {
+               return console.log.apply(console, arguments);
+       }
+};
+
+FC.warn = function() {
+       var console = window.console;
+
+       if (console && console.warn) {
+               return console.warn.apply(console, arguments);
+       }
+       else {
+               return FC.log.apply(FC, arguments);
+       }
+};
+
+
+/* General Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+var hasOwnPropMethod = {}.hasOwnProperty;
+
+
+// Merges an array of objects into a single object.
+// The second argument allows for an array of property names who's object values will be merged together.
+function mergeProps(propObjs, complexProps) {
+       var dest = {};
+       var i, name;
+       var complexObjs;
+       var j, val;
+       var props;
+
+       if (complexProps) {
+               for (i = 0; i < complexProps.length; i++) {
+                       name = complexProps[i];
+                       complexObjs = [];
+
+                       // collect the trailing object values, stopping when a non-object is discovered
+                       for (j = propObjs.length - 1; j >= 0; j--) {
+                               val = propObjs[j][name];
+
+                               if (typeof val === 'object') {
+                                       complexObjs.unshift(val);
+                               }
+                               else if (val !== undefined) {
+                                       dest[name] = val; // if there were no objects, this value will be used
+                                       break;
+                               }
+                       }
+
+                       // if the trailing values were objects, use the merged value
+                       if (complexObjs.length) {
+                               dest[name] = mergeProps(complexObjs);
+                       }
+               }
+       }
+
+       // copy values into the destination, going from last to first
+       for (i = propObjs.length - 1; i >= 0; i--) {
+               props = propObjs[i];
+
+               for (name in props) {
+                       if (!(name in dest)) { // if already assigned by previous props or complex props, don't reassign
+                               dest[name] = props[name];
+                       }
+               }
+       }
+
+       return dest;
+}
+
+
+// Create an object that has the given prototype. Just like Object.create
+function createObject(proto) {
+       var f = function() {};
+       f.prototype = proto;
+       return new f();
+}
+
+
+function copyOwnProps(src, dest) {
+       for (var name in src) {
+               if (hasOwnProp(src, name)) {
+                       dest[name] = src[name];
+               }
+       }
+}
+
+
+// Copies over certain methods with the same names as Object.prototype methods. Overcomes an IE<=8 bug:
+// https://developer.mozilla.org/en-US/docs/ECMAScript_DontEnum_attribute#JScript_DontEnum_Bug
+function copyNativeMethods(src, dest) {
+       var names = [ 'constructor', 'toString', 'valueOf' ];
+       var i, name;
+
+       for (i = 0; i < names.length; i++) {
+               name = names[i];
+
+               if (src[name] !== Object.prototype[name]) {
+                       dest[name] = src[name];
+               }
+       }
+}
+
+
+function hasOwnProp(obj, name) {
+       return hasOwnPropMethod.call(obj, name);
+}
+
+
+// Is the given value a non-object non-function value?
+function isAtomic(val) {
+       return /undefined|null|boolean|number|string/.test($.type(val));
+}
+
+
+function applyAll(functions, thisObj, args) {
+       if ($.isFunction(functions)) {
+               functions = [ functions ];
+       }
+       if (functions) {
+               var i;
+               var ret;
+               for (i=0; i<functions.length; i++) {
+                       ret = functions[i].apply(thisObj, args) || ret;
+               }
+               return ret;
+       }
+}
+
+
+function firstDefined() {
+       for (var i=0; i<arguments.length; i++) {
+               if (arguments[i] !== undefined) {
+                       return arguments[i];
+               }
+       }
+}
+
+
+function htmlEscape(s) {
+       return (s + '').replace(/&/g, '&amp;')
+               .replace(/</g, '&lt;')
+               .replace(/>/g, '&gt;')
+               .replace(/'/g, '&#039;')
+               .replace(/"/g, '&quot;')
+               .replace(/\n/g, '<br />');
+}
+
+
+function stripHtmlEntities(text) {
+       return text.replace(/&.*?;/g, '');
+}
+
+
+// Given a hash of CSS properties, returns a string of CSS.
+// Uses property names as-is (no camel-case conversion). Will not make statements for null/undefined values.
+function cssToStr(cssProps) {
+       var statements = [];
+
+       $.each(cssProps, function(name, val) {
+               if (val != null) {
+                       statements.push(name + ':' + val);
+               }
+       });
+
+       return statements.join(';');
+}
+
+
+function capitaliseFirstLetter(str) {
+       return str.charAt(0).toUpperCase() + str.slice(1);
+}
+
+
+function compareNumbers(a, b) { // for .sort()
+       return a - b;
+}
+
+
+function isInt(n) {
+       return n % 1 === 0;
+}
+
+
+// Returns a method bound to the given object context.
+// Just like one of the jQuery.proxy signatures, but without the undesired behavior of treating the same method with
+// different contexts as identical when binding/unbinding events.
+function proxy(obj, methodName) {
+       var method = obj[methodName];
+
+       return function() {
+               return method.apply(obj, arguments);
+       };
+}
+
+
+// Returns a function, that, as long as it continues to be invoked, will not
+// be triggered. The function will be called after it stops being called for
+// N milliseconds. If `immediate` is passed, trigger the function on the
+// leading edge, instead of the trailing.
+// https://github.com/jashkenas/underscore/blob/1.6.0/underscore.js#L714
+function debounce(func, wait, immediate) {
+       var timeout, args, context, timestamp, result;
+
+       var later = function() {
+               var last = +new Date() - timestamp;
+               if (last < wait) {
+                       timeout = setTimeout(later, wait - last);
+               }
+               else {
+                       timeout = null;
+                       if (!immediate) {
+                               result = func.apply(context, args);
+                               context = args = null;
+                       }
+               }
+       };
+
+       return function() {
+               context = this;
+               args = arguments;
+               timestamp = +new Date();
+               var callNow = immediate && !timeout;
+               if (!timeout) {
+                       timeout = setTimeout(later, wait);
+               }
+               if (callNow) {
+                       result = func.apply(context, args);
+                       context = args = null;
+               }
+               return result;
+       };
+}
+
+;;
+
+var ambigDateOfMonthRegex = /^\s*\d{4}-\d\d$/;
+var ambigTimeOrZoneRegex =
+       /^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/;
+var newMomentProto = moment.fn; // where we will attach our new methods
+var oldMomentProto = $.extend({}, newMomentProto); // copy of original moment methods
+var allowValueOptimization;
+var setUTCValues; // function defined below
+var setLocalValues; // function defined below
+
+
+// Creating
+// -------------------------------------------------------------------------------------------------
+
+// Creates a new moment, similar to the vanilla moment(...) constructor, but with
+// extra features (ambiguous time, enhanced formatting). When given an existing moment,
+// it will function as a clone (and retain the zone of the moment). Anything else will
+// result in a moment in the local zone.
+FC.moment = function() {
+       return makeMoment(arguments);
+};
+
+// Sames as FC.moment, but forces the resulting moment to be in the UTC timezone.
+FC.moment.utc = function() {
+       var mom = makeMoment(arguments, true);
+
+       // Force it into UTC because makeMoment doesn't guarantee it
+       // (if given a pre-existing moment for example)
+       if (mom.hasTime()) { // don't give ambiguously-timed moments a UTC zone
+               mom.utc();
+       }
+
+       return mom;
+};
+
+// Same as FC.moment, but when given an ISO8601 string, the timezone offset is preserved.
+// ISO8601 strings with no timezone offset will become ambiguously zoned.
+FC.moment.parseZone = function() {
+       return makeMoment(arguments, true, true);
+};
+
+// Builds an enhanced moment from args. When given an existing moment, it clones. When given a
+// native Date, or called with no arguments (the current time), the resulting moment will be local.
+// Anything else needs to be "parsed" (a string or an array), and will be affected by:
+//    parseAsUTC - if there is no zone information, should we parse the input in UTC?
+//    parseZone - if there is zone information, should we force the zone of the moment?
+function makeMoment(args, parseAsUTC, parseZone) {
+       var input = args[0];
+       var isSingleString = args.length == 1 && typeof input === 'string';
+       var isAmbigTime;
+       var isAmbigZone;
+       var ambigMatch;
+       var mom;
+
+       if (moment.isMoment(input)) {
+               mom = moment.apply(null, args); // clone it
+               transferAmbigs(input, mom); // the ambig flags weren't transfered with the clone
+       }
+       else if (isNativeDate(input) || input === undefined) {
+               mom = moment.apply(null, args); // will be local
+       }
+       else { // "parsing" is required
+               isAmbigTime = false;
+               isAmbigZone = false;
+
+               if (isSingleString) {
+                       if (ambigDateOfMonthRegex.test(input)) {
+                               // accept strings like '2014-05', but convert to the first of the month
+                               input += '-01';
+                               args = [ input ]; // for when we pass it on to moment's constructor
+                               isAmbigTime = true;
+                               isAmbigZone = true;
+                       }
+                       else if ((ambigMatch = ambigTimeOrZoneRegex.exec(input))) {
+                               isAmbigTime = !ambigMatch[5]; // no time part?
+                               isAmbigZone = true;
+                       }
+               }
+               else if ($.isArray(input)) {
+                       // arrays have no timezone information, so assume ambiguous zone
+                       isAmbigZone = true;
+               }
+               // otherwise, probably a string with a format
+
+               if (parseAsUTC || isAmbigTime) {
+                       mom = moment.utc.apply(moment, args);
+               }
+               else {
+                       mom = moment.apply(null, args);
+               }
+
+               if (isAmbigTime) {
+                       mom._ambigTime = true;
+                       mom._ambigZone = true; // ambiguous time always means ambiguous zone
+               }
+               else if (parseZone) { // let's record the inputted zone somehow
+                       if (isAmbigZone) {
+                               mom._ambigZone = true;
+                       }
+                       else if (isSingleString) {
+                               if (mom.utcOffset) {
+                                       mom.utcOffset(input); // if not a valid zone, will assign UTC
+                               }
+                               else {
+                                       mom.zone(input); // for moment-pre-2.9
+                               }
+                       }
+               }
+       }
+
+       mom._fullCalendar = true; // flag for extended functionality
+
+       return mom;
+}
+
+
+// A clone method that works with the flags related to our enhanced functionality.
+// In the future, use moment.momentProperties
+newMomentProto.clone = function() {
+       var mom = oldMomentProto.clone.apply(this, arguments);
+
+       // these flags weren't transfered with the clone
+       transferAmbigs(this, mom);
+       if (this._fullCalendar) {
+               mom._fullCalendar = true;
+       }
+
+       return mom;
+};
+
+
+// Week Number
+// -------------------------------------------------------------------------------------------------
+
+
+// Returns the week number, considering the locale's custom week number calcuation
+// `weeks` is an alias for `week`
+newMomentProto.week = newMomentProto.weeks = function(input) {
+       var weekCalc = (this._locale || this._lang) // works pre-moment-2.8
+               ._fullCalendar_weekCalc;
+
+       if (input == null && typeof weekCalc === 'function') { // custom function only works for getter
+               return weekCalc(this);
+       }
+       else if (weekCalc === 'ISO') {
+               return oldMomentProto.isoWeek.apply(this, arguments); // ISO getter/setter
+       }
+
+       return oldMomentProto.week.apply(this, arguments); // local getter/setter
+};
+
+
+// Time-of-day
+// -------------------------------------------------------------------------------------------------
+
+// GETTER
+// Returns a Duration with the hours/minutes/seconds/ms values of the moment.
+// If the moment has an ambiguous time, a duration of 00:00 will be returned.
+//
+// SETTER
+// You can supply a Duration, a Moment, or a Duration-like argument.
+// When setting the time, and the moment has an ambiguous time, it then becomes unambiguous.
+newMomentProto.time = function(time) {
+
+       // Fallback to the original method (if there is one) if this moment wasn't created via FullCalendar.
+       // `time` is a generic enough method name where this precaution is necessary to avoid collisions w/ other plugins.
+       if (!this._fullCalendar) {
+               return oldMomentProto.time.apply(this, arguments);
+       }
+
+       if (time == null) { // getter
+               return moment.duration({
+                       hours: this.hours(),
+                       minutes: this.minutes(),
+                       seconds: this.seconds(),
+                       milliseconds: this.milliseconds()
+               });
+       }
+       else { // setter
+
+               this._ambigTime = false; // mark that the moment now has a time
+
+               if (!moment.isDuration(time) && !moment.isMoment(time)) {
+                       time = moment.duration(time);
+               }
+
+               // The day value should cause overflow (so 24 hours becomes 00:00:00 of next day).
+               // Only for Duration times, not Moment times.
+               var dayHours = 0;
+               if (moment.isDuration(time)) {
+                       dayHours = Math.floor(time.asDays()) * 24;
+               }
+
+               // We need to set the individual fields.
+               // Can't use startOf('day') then add duration. In case of DST at start of day.
+               return this.hours(dayHours + time.hours())
+                       .minutes(time.minutes())
+                       .seconds(time.seconds())
+                       .milliseconds(time.milliseconds());
+       }
+};
+
+// Converts the moment to UTC, stripping out its time-of-day and timezone offset,
+// but preserving its YMD. A moment with a stripped time will display no time
+// nor timezone offset when .format() is called.
+newMomentProto.stripTime = function() {
+       var a;
+
+       if (!this._ambigTime) {
+
+               // get the values before any conversion happens
+               a = this.toArray(); // array of y/m/d/h/m/s/ms
+
+               // TODO: use keepLocalTime in the future
+               this.utc(); // set the internal UTC flag (will clear the ambig flags)
+               setUTCValues(this, a.slice(0, 3)); // set the year/month/date. time will be zero
+
+               // Mark the time as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
+               // which clears all ambig flags. Same with setUTCValues with moment-timezone.
+               this._ambigTime = true;
+               this._ambigZone = true; // if ambiguous time, also ambiguous timezone offset
+       }
+
+       return this; // for chaining
+};
+
+// Returns if the moment has a non-ambiguous time (boolean)
+newMomentProto.hasTime = function() {
+       return !this._ambigTime;
+};
+
+
+// Timezone
+// -------------------------------------------------------------------------------------------------
+
+// Converts the moment to UTC, stripping out its timezone offset, but preserving its
+// YMD and time-of-day. A moment with a stripped timezone offset will display no
+// timezone offset when .format() is called.
+// TODO: look into Moment's keepLocalTime functionality
+newMomentProto.stripZone = function() {
+       var a, wasAmbigTime;
+
+       if (!this._ambigZone) {
+
+               // get the values before any conversion happens
+               a = this.toArray(); // array of y/m/d/h/m/s/ms
+               wasAmbigTime = this._ambigTime;
+
+               this.utc(); // set the internal UTC flag (might clear the ambig flags, depending on Moment internals)
+               setUTCValues(this, a); // will set the year/month/date/hours/minutes/seconds/ms
+
+               // the above call to .utc()/.utcOffset() unfortunately might clear the ambig flags, so restore
+               this._ambigTime = wasAmbigTime || false;
+
+               // Mark the zone as ambiguous. This needs to happen after the .utc() call, which might call .utcOffset(),
+               // which clears the ambig flags. Same with setUTCValues with moment-timezone.
+               this._ambigZone = true;
+       }
+
+       return this; // for chaining
+};
+
+// Returns of the moment has a non-ambiguous timezone offset (boolean)
+newMomentProto.hasZone = function() {
+       return !this._ambigZone;
+};
+
+
+// this method implicitly marks a zone
+newMomentProto.local = function() {
+       var a = this.toArray(); // year,month,date,hours,minutes,seconds,ms as an array
+       var wasAmbigZone = this._ambigZone;
+
+       oldMomentProto.local.apply(this, arguments);
+
+       // ensure non-ambiguous
+       // this probably already happened via local() -> utcOffset(), but don't rely on Moment's internals
+       this._ambigTime = false;
+       this._ambigZone = false;
+
+       if (wasAmbigZone) {
+               // If the moment was ambiguously zoned, the date fields were stored as UTC.
+               // We want to preserve these, but in local time.
+               // TODO: look into Moment's keepLocalTime functionality
+               setLocalValues(this, a);
+       }
+
+       return this; // for chaining
+};
+
+
+// implicitly marks a zone
+newMomentProto.utc = function() {
+       oldMomentProto.utc.apply(this, arguments);
+
+       // ensure non-ambiguous
+       // this probably already happened via utc() -> utcOffset(), but don't rely on Moment's internals
+       this._ambigTime = false;
+       this._ambigZone = false;
+
+       return this;
+};
+
+
+// methods for arbitrarily manipulating timezone offset.
+// should clear time/zone ambiguity when called.
+$.each([
+       'zone', // only in moment-pre-2.9. deprecated afterwards
+       'utcOffset'
+], function(i, name) {
+       if (oldMomentProto[name]) { // original method exists?
+
+               // this method implicitly marks a zone (will probably get called upon .utc() and .local())
+               newMomentProto[name] = function(tzo) {
+
+                       if (tzo != null) { // setter
+                               // these assignments needs to happen before the original zone method is called.
+                               // I forget why, something to do with a browser crash.
+                               this._ambigTime = false;
+                               this._ambigZone = false;
+                       }
+
+                       return oldMomentProto[name].apply(this, arguments);
+               };
+       }
+});
+
+
+// Formatting
+// -------------------------------------------------------------------------------------------------
+
+newMomentProto.format = function() {
+       if (this._fullCalendar && arguments[0]) { // an enhanced moment? and a format string provided?
+               return formatDate(this, arguments[0]); // our extended formatting
+       }
+       if (this._ambigTime) {
+               return oldMomentFormat(this, 'YYYY-MM-DD');
+       }
+       if (this._ambigZone) {
+               return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
+       }
+       return oldMomentProto.format.apply(this, arguments);
+};
+
+newMomentProto.toISOString = function() {
+       if (this._ambigTime) {
+               return oldMomentFormat(this, 'YYYY-MM-DD');
+       }
+       if (this._ambigZone) {
+               return oldMomentFormat(this, 'YYYY-MM-DD[T]HH:mm:ss');
+       }
+       return oldMomentProto.toISOString.apply(this, arguments);
+};
+
+
+// Querying
+// -------------------------------------------------------------------------------------------------
+
+// Is the moment within the specified range? `end` is exclusive.
+// FYI, this method is not a standard Moment method, so always do our enhanced logic.
+newMomentProto.isWithin = function(start, end) {
+       var a = commonlyAmbiguate([ this, start, end ]);
+       return a[0] >= a[1] && a[0] < a[2];
+};
+
+// When isSame is called with units, timezone ambiguity is normalized before the comparison happens.
+// If no units specified, the two moments must be identically the same, with matching ambig flags.
+newMomentProto.isSame = function(input, units) {
+       var a;
+
+       // only do custom logic if this is an enhanced moment
+       if (!this._fullCalendar) {
+               return oldMomentProto.isSame.apply(this, arguments);
+       }
+
+       if (units) {
+               a = commonlyAmbiguate([ this, input ], true); // normalize timezones but don't erase times
+               return oldMomentProto.isSame.call(a[0], a[1], units);
+       }
+       else {
+               input = FC.moment.parseZone(input); // normalize input
+               return oldMomentProto.isSame.call(this, input) &&
+                       Boolean(this._ambigTime) === Boolean(input._ambigTime) &&
+                       Boolean(this._ambigZone) === Boolean(input._ambigZone);
+       }
+};
+
+// Make these query methods work with ambiguous moments
+$.each([
+       'isBefore',
+       'isAfter'
+], function(i, methodName) {
+       newMomentProto[methodName] = function(input, units) {
+               var a;
+
+               // only do custom logic if this is an enhanced moment
+               if (!this._fullCalendar) {
+                       return oldMomentProto[methodName].apply(this, arguments);
+               }
+
+               a = commonlyAmbiguate([ this, input ]);
+               return oldMomentProto[methodName].call(a[0], a[1], units);
+       };
+});
+
+
+// Misc Internals
+// -------------------------------------------------------------------------------------------------
+
+// given an array of moment-like inputs, return a parallel array w/ moments similarly ambiguated.
+// for example, of one moment has ambig time, but not others, all moments will have their time stripped.
+// set `preserveTime` to `true` to keep times, but only normalize zone ambiguity.
+// returns the original moments if no modifications are necessary.
+function commonlyAmbiguate(inputs, preserveTime) {
+       var anyAmbigTime = false;
+       var anyAmbigZone = false;
+       var len = inputs.length;
+       var moms = [];
+       var i, mom;
+
+       // parse inputs into real moments and query their ambig flags
+       for (i = 0; i < len; i++) {
+               mom = inputs[i];
+               if (!moment.isMoment(mom)) {
+                       mom = FC.moment.parseZone(mom);
+               }
+               anyAmbigTime = anyAmbigTime || mom._ambigTime;
+               anyAmbigZone = anyAmbigZone || mom._ambigZone;
+               moms.push(mom);
+       }
+
+       // strip each moment down to lowest common ambiguity
+       // use clones to avoid modifying the original moments
+       for (i = 0; i < len; i++) {
+               mom = moms[i];
+               if (!preserveTime && anyAmbigTime && !mom._ambigTime) {
+                       moms[i] = mom.clone().stripTime();
+               }
+               else if (anyAmbigZone && !mom._ambigZone) {
+                       moms[i] = mom.clone().stripZone();
+               }
+       }
+
+       return moms;
+}
+
+// Transfers all the flags related to ambiguous time/zone from the `src` moment to the `dest` moment
+// TODO: look into moment.momentProperties for this.
+function transferAmbigs(src, dest) {
+       if (src._ambigTime) {
+               dest._ambigTime = true;
+       }
+       else if (dest._ambigTime) {
+               dest._ambigTime = false;
+       }
+
+       if (src._ambigZone) {
+               dest._ambigZone = true;
+       }
+       else if (dest._ambigZone) {
+               dest._ambigZone = false;
+       }
+}
+
+
+// Sets the year/month/date/etc values of the moment from the given array.
+// Inefficient because it calls each individual setter.
+function setMomentValues(mom, a) {
+       mom.year(a[0] || 0)
+               .month(a[1] || 0)
+               .date(a[2] || 0)
+               .hours(a[3] || 0)
+               .minutes(a[4] || 0)
+               .seconds(a[5] || 0)
+               .milliseconds(a[6] || 0);
+}
+
+// Can we set the moment's internal date directly?
+allowValueOptimization = '_d' in moment() && 'updateOffset' in moment;
+
+// Utility function. Accepts a moment and an array of the UTC year/month/date/etc values to set.
+// Assumes the given moment is already in UTC mode.
+setUTCValues = allowValueOptimization ? function(mom, a) {
+       // simlate what moment's accessors do
+       mom._d.setTime(Date.UTC.apply(Date, a));
+       moment.updateOffset(mom, false); // keepTime=false
+} : setMomentValues;
+
+// Utility function. Accepts a moment and an array of the local year/month/date/etc values to set.
+// Assumes the given moment is already in local mode.
+setLocalValues = allowValueOptimization ? function(mom, a) {
+       // simlate what moment's accessors do
+       mom._d.setTime(+new Date( // FYI, there is now way to apply an array of args to a constructor
+               a[0] || 0,
+               a[1] || 0,
+               a[2] || 0,
+               a[3] || 0,
+               a[4] || 0,
+               a[5] || 0,
+               a[6] || 0
+       ));
+       moment.updateOffset(mom, false); // keepTime=false
+} : setMomentValues;
+
+;;
+
+// Single Date Formatting
+// -------------------------------------------------------------------------------------------------
+
+
+// call this if you want Moment's original format method to be used
+function oldMomentFormat(mom, formatStr) {
+       return oldMomentProto.format.call(mom, formatStr); // oldMomentProto defined in moment-ext.js
+}
+
+
+// Formats `date` with a Moment formatting string, but allow our non-zero areas and
+// additional token.
+function formatDate(date, formatStr) {
+       return formatDateWithChunks(date, getFormatStringChunks(formatStr));
+}
+
+
+function formatDateWithChunks(date, chunks) {
+       var s = '';
+       var i;
+
+       for (i=0; i<chunks.length; i++) {
+               s += formatDateWithChunk(date, chunks[i]);
+       }
+
+       return s;
+}
+
+
+// addition formatting tokens we want recognized
+var tokenOverrides = {
+       t: function(date) { // "a" or "p"
+               return oldMomentFormat(date, 'a').charAt(0);
+       },
+       T: function(date) { // "A" or "P"
+               return oldMomentFormat(date, 'A').charAt(0);
+       }
+};
+
+
+function formatDateWithChunk(date, chunk) {
+       var token;
+       var maybeStr;
+
+       if (typeof chunk === 'string') { // a literal string
+               return chunk;
+       }
+       else if ((token = chunk.token)) { // a token, like "YYYY"
+               if (tokenOverrides[token]) {
+                       return tokenOverrides[token](date); // use our custom token
+               }
+               return oldMomentFormat(date, token);
+       }
+       else if (chunk.maybe) { // a grouping of other chunks that must be non-zero
+               maybeStr = formatDateWithChunks(date, chunk.maybe);
+               if (maybeStr.match(/[1-9]/)) {
+                       return maybeStr;
+               }
+       }
+
+       return '';
+}
+
+
+// Date Range Formatting
+// -------------------------------------------------------------------------------------------------
+// TODO: make it work with timezone offset
+
+// Using a formatting string meant for a single date, generate a range string, like
+// "Sep 2 - 9 2013", that intelligently inserts a separator where the dates differ.
+// If the dates are the same as far as the format string is concerned, just return a single
+// rendering of one date, without any separator.
+function formatRange(date1, date2, formatStr, separator, isRTL) {
+       var localeData;
+
+       date1 = FC.moment.parseZone(date1);
+       date2 = FC.moment.parseZone(date2);
+
+       localeData = (date1.localeData || date1.lang).call(date1); // works with moment-pre-2.8
+
+       // Expand localized format strings, like "LL" -> "MMMM D YYYY"
+       formatStr = localeData.longDateFormat(formatStr) || formatStr;
+       // BTW, this is not important for `formatDate` because it is impossible to put custom tokens
+       // or non-zero areas in Moment's localized format strings.
+
+       separator = separator || ' - ';
+
+       return formatRangeWithChunks(
+               date1,
+               date2,
+               getFormatStringChunks(formatStr),
+               separator,
+               isRTL
+       );
+}
+FC.formatRange = formatRange; // expose
+
+
+function formatRangeWithChunks(date1, date2, chunks, separator, isRTL) {
+       var unzonedDate1 = date1.clone().stripZone(); // for formatSimilarChunk
+       var unzonedDate2 = date2.clone().stripZone(); // "
+       var chunkStr; // the rendering of the chunk
+       var leftI;
+       var leftStr = '';
+       var rightI;
+       var rightStr = '';
+       var middleI;
+       var middleStr1 = '';
+       var middleStr2 = '';
+       var middleStr = '';
+
+       // Start at the leftmost side of the formatting string and continue until you hit a token
+       // that is not the same between dates.
+       for (leftI=0; leftI<chunks.length; leftI++) {
+               chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunks[leftI]);
+               if (chunkStr === false) {
+                       break;
+               }
+               leftStr += chunkStr;
+       }
+
+       // Similarly, start at the rightmost side of the formatting string and move left
+       for (rightI=chunks.length-1; rightI>leftI; rightI--) {
+               chunkStr = formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2,  chunks[rightI]);
+               if (chunkStr === false) {
+                       break;
+               }
+               rightStr = chunkStr + rightStr;
+       }
+
+       // The area in the middle is different for both of the dates.
+       // Collect them distinctly so we can jam them together later.
+       for (middleI=leftI; middleI<=rightI; middleI++) {
+               middleStr1 += formatDateWithChunk(date1, chunks[middleI]);
+               middleStr2 += formatDateWithChunk(date2, chunks[middleI]);
+       }
+
+       if (middleStr1 || middleStr2) {
+               if (isRTL) {
+                       middleStr = middleStr2 + separator + middleStr1;
+               }
+               else {
+                       middleStr = middleStr1 + separator + middleStr2;
+               }
+       }
+
+       return leftStr + middleStr + rightStr;
+}
+
+
+var similarUnitMap = {
+       Y: 'year',
+       M: 'month',
+       D: 'day', // day of month
+       d: 'day', // day of week
+       // prevents a separator between anything time-related...
+       A: 'second', // AM/PM
+       a: 'second', // am/pm
+       T: 'second', // A/P
+       t: 'second', // a/p
+       H: 'second', // hour (24)
+       h: 'second', // hour (12)
+       m: 'second', // minute
+       s: 'second' // second
+};
+// TODO: week maybe?
+
+
+// Given a formatting chunk, and given that both dates are similar in the regard the
+// formatting chunk is concerned, format date1 against `chunk`. Otherwise, return `false`.
+function formatSimilarChunk(date1, date2, unzonedDate1, unzonedDate2, chunk) {
+       var token;
+       var unit;
+
+       if (typeof chunk === 'string') { // a literal string
+               return chunk;
+       }
+       else if ((token = chunk.token)) {
+               unit = similarUnitMap[token.charAt(0)];
+
+               // are the dates the same for this unit of measurement?
+               // use the unzoned dates for this calculation because unreliable when near DST (bug #2396)
+               if (unit && unzonedDate1.isSame(unzonedDate2, unit)) {
+                       return oldMomentFormat(date1, token); // would be the same if we used `date2`
+                       // BTW, don't support custom tokens
+               }
+       }
+
+       return false; // the chunk is NOT the same for the two dates
+       // BTW, don't support splitting on non-zero areas
+}
+
+
+// Chunking Utils
+// -------------------------------------------------------------------------------------------------
+
+
+var formatStringChunkCache = {};
+
+
+function getFormatStringChunks(formatStr) {
+       if (formatStr in formatStringChunkCache) {
+               return formatStringChunkCache[formatStr];
+       }
+       return (formatStringChunkCache[formatStr] = chunkFormatString(formatStr));
+}
+
+
+// Break the formatting string into an array of chunks
+function chunkFormatString(formatStr) {
+       var chunks = [];
+       var chunker = /\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g; // TODO: more descrimination
+       var match;
+
+       while ((match = chunker.exec(formatStr))) {
+               if (match[1]) { // a literal string inside [ ... ]
+                       chunks.push(match[1]);
+               }
+               else if (match[2]) { // non-zero formatting inside ( ... )
+                       chunks.push({ maybe: chunkFormatString(match[2]) });
+               }
+               else if (match[3]) { // a formatting token
+                       chunks.push({ token: match[3] });
+               }
+               else if (match[5]) { // an unenclosed literal string
+                       chunks.push(match[5]);
+               }
+       }
+
+       return chunks;
+}
+
+;;
+
+FC.Class = Class; // export
+
+// Class that all other classes will inherit from
+function Class() { }
+
+
+// Called on a class to create a subclass.
+// Last argument contains instance methods. Any argument before the last are considered mixins.
+Class.extend = function() {
+       var len = arguments.length;
+       var i;
+       var members;
+
+       for (i = 0; i < len; i++) {
+               members = arguments[i];
+               if (i < len - 1) { // not the last argument?
+                       mixIntoClass(this, members);
+               }
+       }
+
+       return extendClass(this, members || {}); // members will be undefined if no arguments
+};
+
+
+// Adds new member variables/methods to the class's prototype.
+// Can be called with another class, or a plain object hash containing new members.
+Class.mixin = function(members) {
+       mixIntoClass(this, members);
+};
+
+
+function extendClass(superClass, members) {
+       var subClass;
+
+       // ensure a constructor for the subclass, forwarding all arguments to the super-constructor if it doesn't exist
+       if (hasOwnProp(members, 'constructor')) {
+               subClass = members.constructor;
+       }
+       if (typeof subClass !== 'function') {
+               subClass = members.constructor = function() {
+                       superClass.apply(this, arguments);
+               };
+       }
+
+       // build the base prototype for the subclass, which is an new object chained to the superclass's prototype
+       subClass.prototype = createObject(superClass.prototype);
+
+       // copy each member variable/method onto the the subclass's prototype
+       copyOwnProps(members, subClass.prototype);
+       copyNativeMethods(members, subClass.prototype); // hack for IE8
+
+       // copy over all class variables/methods to the subclass, such as `extend` and `mixin`
+       copyOwnProps(superClass, subClass);
+
+       return subClass;
+}
+
+
+function mixIntoClass(theClass, members) {
+       copyOwnProps(members, theClass.prototype); // TODO: copyNativeMethods?
+}
+;;
+
+var EmitterMixin = FC.EmitterMixin = {
+
+       callbackHash: null,
+
+
+       on: function(name, callback) {
+               this.loopCallbacks(name, 'add', [ callback ]);
+
+               return this; // for chaining
+       },
+
+
+       off: function(name, callback) {
+               this.loopCallbacks(name, 'remove', [ callback ]);
+
+               return this; // for chaining
+       },
+
+
+       trigger: function(name) { // args...
+               var args = Array.prototype.slice.call(arguments, 1);
+
+               this.triggerWith(name, this, args);
+
+               return this; // for chaining
+       },
+
+
+       triggerWith: function(name, context, args) {
+               this.loopCallbacks(name, 'fireWith', [ context, args ]);
+
+               return this; // for chaining
+       },
+
+
+       /*
+       Given an event name string with possible namespaces,
+       call the given methodName on all the internal Callback object with the given arguments.
+       */
+       loopCallbacks: function(name, methodName, args) {
+               var parts = name.split('.'); // "click.namespace" -> [ "click", "namespace" ]
+               var i, part;
+               var callbackObj;
+
+               for (i = 0; i < parts.length; i++) {
+                       part = parts[i];
+                       if (part) { // in case no event name like "click"
+                               callbackObj = this.ensureCallbackObj((i ? '.' : '') + part); // put periods in front of namespaces
+                               callbackObj[methodName].apply(callbackObj, args);
+                       }
+               }
+       },
+
+
+       ensureCallbackObj: function(name) {
+               if (!this.callbackHash) {
+                       this.callbackHash = {};
+               }
+               if (!this.callbackHash[name]) {
+                       this.callbackHash[name] = $.Callbacks();
+               }
+               return this.callbackHash[name];
+       }
+
+};
+;;
+
+/*
+Utility methods for easily listening to events on another object,
+and more importantly, easily unlistening from them.
+*/
+var ListenerMixin = FC.ListenerMixin = (function() {
+       var guid = 0;
+       var ListenerMixin = {
+
+               listenerId: null,
+
+               /*
+               Given an `other` object that has on/off methods, bind the given `callback` to an event by the given name.
+               The `callback` will be called with the `this` context of the object that .listenTo is being called on.
+               Can be called:
+                       .listenTo(other, eventName, callback)
+               OR
+                       .listenTo(other, {
+                               eventName1: callback1,
+                               eventName2: callback2
+                       })
+               */
+               listenTo: function(other, arg, callback) {
+                       if (typeof arg === 'object') { // given dictionary of callbacks
+                               for (var eventName in arg) {
+                                       if (arg.hasOwnProperty(eventName)) {
+                                               this.listenTo(other, eventName, arg[eventName]);
+                                       }
+                               }
+                       }
+                       else if (typeof arg === 'string') {
+                               other.on(
+                                       arg + '.' + this.getListenerNamespace(), // use event namespacing to identify this object
+                                       $.proxy(callback, this) // always use `this` context
+                                               // the usually-undesired jQuery guid behavior doesn't matter,
+                                               // because we always unbind via namespace
+                               );
+                       }
+               },
+
+               /*
+               Causes the current object to stop listening to events on the `other` object.
+               `eventName` is optional. If omitted, will stop listening to ALL events on `other`.
+               */
+               stopListeningTo: function(other, eventName) {
+                       other.off((eventName || '') + '.' + this.getListenerNamespace());
+               },
+
+               /*
+               Returns a string, unique to this object, to be used for event namespacing
+               */
+               getListenerNamespace: function() {
+                       if (this.listenerId == null) {
+                               this.listenerId = guid++;
+                       }
+                       return '_listener' + this.listenerId;
+               }
+
+       };
+       return ListenerMixin;
+})();
+;;
+
+// simple class for toggle a `isIgnoringMouse` flag on delay
+// initMouseIgnoring must first be called, with a millisecond delay setting.
+var MouseIgnorerMixin = {
+
+       isIgnoringMouse: false, // bool
+       delayUnignoreMouse: null, // method
+
+
+       initMouseIgnoring: function(delay) {
+               this.delayUnignoreMouse = debounce(proxy(this, 'unignoreMouse'), delay || 1000);
+       },
+
+
+       // temporarily ignore mouse actions on segments
+       tempIgnoreMouse: function() {
+               this.isIgnoringMouse = true;
+               this.delayUnignoreMouse();
+       },
+
+
+       // delayUnignoreMouse eventually calls this
+       unignoreMouse: function() {
+               this.isIgnoringMouse = false;
+       }
+
+};
+
+;;
+
+/* A rectangular panel that is absolutely positioned over other content
+------------------------------------------------------------------------------------------------------------------------
+Options:
+       - className (string)
+       - content (HTML string or jQuery element set)
+       - parentEl
+       - top
+       - left
+       - right (the x coord of where the right edge should be. not a "CSS" right)
+       - autoHide (boolean)
+       - show (callback)
+       - hide (callback)
+*/
+
+var Popover = Class.extend(ListenerMixin, {
+
+       isHidden: true,
+       options: null,
+       el: null, // the container element for the popover. generated by this object
+       margin: 10, // the space required between the popover and the edges of the scroll container
+
+
+       constructor: function(options) {
+               this.options = options || {};
+       },
+
+
+       // Shows the popover on the specified position. Renders it if not already
+       show: function() {
+               if (this.isHidden) {
+                       if (!this.el) {
+                               this.render();
+                       }
+                       this.el.show();
+                       this.position();
+                       this.isHidden = false;
+                       this.trigger('show');
+               }
+       },
+
+
+       // Hides the popover, through CSS, but does not remove it from the DOM
+       hide: function() {
+               if (!this.isHidden) {
+                       this.el.hide();
+                       this.isHidden = true;
+                       this.trigger('hide');
+               }
+       },
+
+
+       // Creates `this.el` and renders content inside of it
+       render: function() {
+               var _this = this;
+               var options = this.options;
+
+               this.el = $('<div class="fc-popover"/>')
+                       .addClass(options.className || '')
+                       .css({
+                               // position initially to the top left to avoid creating scrollbars
+                               top: 0,
+                               left: 0
+                       })
+                       .append(options.content)
+                       .appendTo(options.parentEl);
+
+               // when a click happens on anything inside with a 'fc-close' className, hide the popover
+               this.el.on('click', '.fc-close', function() {
+                       _this.hide();
+               });
+
+               if (options.autoHide) {
+                       this.listenTo($(document), 'mousedown', this.documentMousedown);
+               }
+       },
+
+
+       // Triggered when the user clicks *anywhere* in the document, for the autoHide feature
+       documentMousedown: function(ev) {
+               // only hide the popover if the click happened outside the popover
+               if (this.el && !$(ev.target).closest(this.el).length) {
+                       this.hide();
+               }
+       },
+
+
+       // Hides and unregisters any handlers
+       removeElement: function() {
+               this.hide();
+
+               if (this.el) {
+                       this.el.remove();
+                       this.el = null;
+               }
+
+               this.stopListeningTo($(document), 'mousedown');
+       },
+
+
+       // Positions the popover optimally, using the top/left/right options
+       position: function() {
+               var options = this.options;
+               var origin = this.el.offsetParent().offset();
+               var width = this.el.outerWidth();
+               var height = this.el.outerHeight();
+               var windowEl = $(window);
+               var viewportEl = getScrollParent(this.el);
+               var viewportTop;
+               var viewportLeft;
+               var viewportOffset;
+               var top; // the "position" (not "offset") values for the popover
+               var left; //
+
+               // compute top and left
+               top = options.top || 0;
+               if (options.left !== undefined) {
+                       left = options.left;
+               }
+               else if (options.right !== undefined) {
+                       left = options.right - width; // derive the left value from the right value
+               }
+               else {
+                       left = 0;
+               }
+
+               if (viewportEl.is(window) || viewportEl.is(document)) { // normalize getScrollParent's result
+                       viewportEl = windowEl;
+                       viewportTop = 0; // the window is always at the top left
+                       viewportLeft = 0; // (and .offset() won't work if called here)
+               }
+               else {
+                       viewportOffset = viewportEl.offset();
+                       viewportTop = viewportOffset.top;
+                       viewportLeft = viewportOffset.left;
+               }
+
+               // if the window is scrolled, it causes the visible area to be further down
+               viewportTop += windowEl.scrollTop();
+               viewportLeft += windowEl.scrollLeft();
+
+               // constrain to the view port. if constrained by two edges, give precedence to top/left
+               if (options.viewportConstrain !== false) {
+                       top = Math.min(top, viewportTop + viewportEl.outerHeight() - height - this.margin);
+                       top = Math.max(top, viewportTop + this.margin);
+                       left = Math.min(left, viewportLeft + viewportEl.outerWidth() - width - this.margin);
+                       left = Math.max(left, viewportLeft + this.margin);
+               }
+
+               this.el.css({
+                       top: top - origin.top,
+                       left: left - origin.left
+               });
+       },
+
+
+       // Triggers a callback. Calls a function in the option hash of the same name.
+       // Arguments beyond the first `name` are forwarded on.
+       // TODO: better code reuse for this. Repeat code
+       trigger: function(name) {
+               if (this.options[name]) {
+                       this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
+               }
+       }
+
+});
+
+;;
+
+/*
+A cache for the left/right/top/bottom/width/height values for one or more elements.
+Works with both offset (from topleft document) and position (from offsetParent).
+
+options:
+- els
+- isHorizontal
+- isVertical
+*/
+var CoordCache = FC.CoordCache = Class.extend({
+
+       els: null, // jQuery set (assumed to be siblings)
+       forcedOffsetParentEl: null, // options can override the natural offsetParent
+       origin: null, // {left,top} position of offsetParent of els
+       boundingRect: null, // constrain cordinates to this rectangle. {left,right,top,bottom} or null
+       isHorizontal: false, // whether to query for left/right/width
+       isVertical: false, // whether to query for top/bottom/height
+
+       // arrays of coordinates (offsets from topleft of document)
+       lefts: null,
+       rights: null,
+       tops: null,
+       bottoms: null,
+
+
+       constructor: function(options) {
+               this.els = $(options.els);
+               this.isHorizontal = options.isHorizontal;
+               this.isVertical = options.isVertical;
+               this.forcedOffsetParentEl = options.offsetParent ? $(options.offsetParent) : null;
+       },
+
+
+       // Queries the els for coordinates and stores them.
+       // Call this method before using and of the get* methods below.
+       build: function() {
+               var offsetParentEl = this.forcedOffsetParentEl || this.els.eq(0).offsetParent();
+
+               this.origin = offsetParentEl.offset();
+               this.boundingRect = this.queryBoundingRect();
+
+               if (this.isHorizontal) {
+                       this.buildElHorizontals();
+               }
+               if (this.isVertical) {
+                       this.buildElVerticals();
+               }
+       },
+
+
+       // Destroys all internal data about coordinates, freeing memory
+       clear: function() {
+               this.origin = null;
+               this.boundingRect = null;
+               this.lefts = null;
+               this.rights = null;
+               this.tops = null;
+               this.bottoms = null;
+       },
+
+
+       // When called, if coord caches aren't built, builds them
+       ensureBuilt: function() {
+               if (!this.origin) {
+                       this.build();
+               }
+       },
+
+
+       // Compute and return what the elements' bounding rectangle is, from the user's perspective.
+       // Right now, only returns a rectangle if constrained by an overflow:scroll element.
+       queryBoundingRect: function() {
+               var scrollParentEl = getScrollParent(this.els.eq(0));
+
+               if (!scrollParentEl.is(document)) {
+                       return getClientRect(scrollParentEl);
+               }
+       },
+
+
+       // Populates the left/right internal coordinate arrays
+       buildElHorizontals: function() {
+               var lefts = [];
+               var rights = [];
+
+               this.els.each(function(i, node) {
+                       var el = $(node);
+                       var left = el.offset().left;
+                       var width = el.outerWidth();
+
+                       lefts.push(left);
+                       rights.push(left + width);
+               });
+
+               this.lefts = lefts;
+               this.rights = rights;
+       },
+
+
+       // Populates the top/bottom internal coordinate arrays
+       buildElVerticals: function() {
+               var tops = [];
+               var bottoms = [];
+
+               this.els.each(function(i, node) {
+                       var el = $(node);
+                       var top = el.offset().top;
+                       var height = el.outerHeight();
+
+                       tops.push(top);
+                       bottoms.push(top + height);
+               });
+
+               this.tops = tops;
+               this.bottoms = bottoms;
+       },
+
+
+       // Given a left offset (from document left), returns the index of the el that it horizontally intersects.
+       // If no intersection is made, or outside of the boundingRect, returns undefined.
+       getHorizontalIndex: function(leftOffset) {
+               this.ensureBuilt();
+
+               var boundingRect = this.boundingRect;
+               var lefts = this.lefts;
+               var rights = this.rights;
+               var len = lefts.length;
+               var i;
+
+               if (!boundingRect || (leftOffset >= boundingRect.left && leftOffset < boundingRect.right)) {
+                       for (i = 0; i < len; i++) {
+                               if (leftOffset >= lefts[i] && leftOffset < rights[i]) {
+                                       return i;
+                               }
+                       }
+               }
+       },
+
+
+       // Given a top offset (from document top), returns the index of the el that it vertically intersects.
+       // If no intersection is made, or outside of the boundingRect, returns undefined.
+       getVerticalIndex: function(topOffset) {
+               this.ensureBuilt();
+
+               var boundingRect = this.boundingRect;
+               var tops = this.tops;
+               var bottoms = this.bottoms;
+               var len = tops.length;
+               var i;
+
+               if (!boundingRect || (topOffset >= boundingRect.top && topOffset < boundingRect.bottom)) {
+                       for (i = 0; i < len; i++) {
+                               if (topOffset >= tops[i] && topOffset < bottoms[i]) {
+                                       return i;
+                               }
+                       }
+               }
+       },
+
+
+       // Gets the left offset (from document left) of the element at the given index
+       getLeftOffset: function(leftIndex) {
+               this.ensureBuilt();
+               return this.lefts[leftIndex];
+       },
+
+
+       // Gets the left position (from offsetParent left) of the element at the given index
+       getLeftPosition: function(leftIndex) {
+               this.ensureBuilt();
+               return this.lefts[leftIndex] - this.origin.left;
+       },
+
+
+       // Gets the right offset (from document left) of the element at the given index.
+       // This value is NOT relative to the document's right edge, like the CSS concept of "right" would be.
+       getRightOffset: function(leftIndex) {
+               this.ensureBuilt();
+               return this.rights[leftIndex];
+       },
+
+
+       // Gets the right position (from offsetParent left) of the element at the given index.
+       // This value is NOT relative to the offsetParent's right edge, like the CSS concept of "right" would be.
+       getRightPosition: function(leftIndex) {
+               this.ensureBuilt();
+               return this.rights[leftIndex] - this.origin.left;
+       },
+
+
+       // Gets the width of the element at the given index
+       getWidth: function(leftIndex) {
+               this.ensureBuilt();
+               return this.rights[leftIndex] - this.lefts[leftIndex];
+       },
+
+
+       // Gets the top offset (from document top) of the element at the given index
+       getTopOffset: function(topIndex) {
+               this.ensureBuilt();
+               return this.tops[topIndex];
+       },
+
+
+       // Gets the top position (from offsetParent top) of the element at the given position
+       getTopPosition: function(topIndex) {
+               this.ensureBuilt();
+               return this.tops[topIndex] - this.origin.top;
+       },
+
+       // Gets the bottom offset (from the document top) of the element at the given index.
+       // This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be.
+       getBottomOffset: function(topIndex) {
+               this.ensureBuilt();
+               return this.bottoms[topIndex];
+       },
+
+
+       // Gets the bottom position (from the offsetParent top) of the element at the given index.
+       // This value is NOT relative to the offsetParent's bottom edge, like the CSS concept of "bottom" would be.
+       getBottomPosition: function(topIndex) {
+               this.ensureBuilt();
+               return this.bottoms[topIndex] - this.origin.top;
+       },
+
+
+       // Gets the height of the element at the given index
+       getHeight: function(topIndex) {
+               this.ensureBuilt();
+               return this.bottoms[topIndex] - this.tops[topIndex];
+       }
+
+});
+
+;;
+
+/* Tracks a drag's mouse movement, firing various handlers
+----------------------------------------------------------------------------------------------------------------------*/
+// TODO: use Emitter
+
+var DragListener = FC.DragListener = Class.extend(ListenerMixin, MouseIgnorerMixin, {
+
+       options: null,
+
+       // for IE8 bug-fighting behavior
+       subjectEl: null,
+       subjectHref: null,
+
+       // coordinates of the initial mousedown
+       originX: null,
+       originY: null,
+
+       // the wrapping element that scrolls, or MIGHT scroll if there's overflow.
+       // TODO: do this for wrappers that have overflow:hidden as well.
+       scrollEl: null,
+
+       isInteracting: false,
+       isDistanceSurpassed: false,
+       isDelayEnded: false,
+       isDragging: false,
+       isTouch: false,
+
+       delay: null,
+       delayTimeoutId: null,
+       minDistance: null,
+
+       handleTouchScrollProxy: null, // calls handleTouchScroll, always bound to `this`
+
+
+       constructor: function(options) {
+               this.options = options || {};
+               this.handleTouchScrollProxy = proxy(this, 'handleTouchScroll');
+               this.initMouseIgnoring(500);
+       },
+
+
+       // Interaction (high-level)
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       startInteraction: function(ev, extraOptions) {
+               var isTouch = getEvIsTouch(ev);
+
+               if (ev.type === 'mousedown') {
+                       if (this.isIgnoringMouse) {
+                               return;
+                       }
+                       else if (!isPrimaryMouseButton(ev)) {
+                               return;
+                       }
+                       else {
+                               ev.preventDefault(); // prevents native selection in most browsers
+                       }
+               }
+
+               if (!this.isInteracting) {
+
+                       // process options
+                       extraOptions = extraOptions || {};
+                       this.delay = firstDefined(extraOptions.delay, this.options.delay, 0);
+                       this.minDistance = firstDefined(extraOptions.distance, this.options.distance, 0);
+                       this.subjectEl = this.options.subjectEl;
+
+                       this.isInteracting = true;
+                       this.isTouch = isTouch;
+                       this.isDelayEnded = false;
+                       this.isDistanceSurpassed = false;
+
+                       this.originX = getEvX(ev);
+                       this.originY = getEvY(ev);
+                       this.scrollEl = getScrollParent($(ev.target));
+
+                       this.bindHandlers();
+                       this.initAutoScroll();
+                       this.handleInteractionStart(ev);
+                       this.startDelay(ev);
+
+                       if (!this.minDistance) {
+                               this.handleDistanceSurpassed(ev);
+                       }
+               }
+       },
+
+
+       handleInteractionStart: function(ev) {
+               this.trigger('interactionStart', ev);
+       },
+
+
+       endInteraction: function(ev, isCancelled) {
+               if (this.isInteracting) {
+                       this.endDrag(ev);
+
+                       if (this.delayTimeoutId) {
+                               clearTimeout(this.delayTimeoutId);
+                               this.delayTimeoutId = null;
+                       }
+
+                       this.destroyAutoScroll();
+                       this.unbindHandlers();
+
+                       this.isInteracting = false;
+                       this.handleInteractionEnd(ev, isCancelled);
+
+                       // a touchstart+touchend on the same element will result in the following addition simulated events:
+                       // mouseover + mouseout + click
+                       // let's ignore these bogus events
+                       if (this.isTouch) {
+                               this.tempIgnoreMouse();
+                       }
+               }
+       },
+
+
+       handleInteractionEnd: function(ev, isCancelled) {
+               this.trigger('interactionEnd', ev, isCancelled || false);
+       },
+
+
+       // Binding To DOM
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       bindHandlers: function() {
+               var _this = this;
+               var touchStartIgnores = 1;
+
+               if (this.isTouch) {
+                       this.listenTo($(document), {
+                               touchmove: this.handleTouchMove,
+                               touchend: this.endInteraction,
+                               touchcancel: this.endInteraction,
+
+                               // Sometimes touchend doesn't fire
+                               // (can't figure out why. touchcancel doesn't fire either. has to do with scrolling?)
+                               // If another touchstart happens, we know it's bogus, so cancel the drag.
+                               // touchend will continue to be broken until user does a shorttap/scroll, but this is best we can do.
+                               touchstart: function(ev) {
+                                       if (touchStartIgnores) { // bindHandlers is called from within a touchstart,
+                                               touchStartIgnores--; // and we don't want this to fire immediately, so ignore.
+                                       }
+                                       else {
+                                               _this.endInteraction(ev, true); // isCancelled=true
+                                       }
+                               }
+                       });
+
+                       // listen to ALL scroll actions on the page
+                       if (
+                               !bindAnyScroll(this.handleTouchScrollProxy) && // hopefully this works and short-circuits the rest
+                               this.scrollEl // otherwise, attach a single handler to this
+                       ) {
+                               this.listenTo(this.scrollEl, 'scroll', this.handleTouchScroll);
+                       }
+               }
+               else {
+                       this.listenTo($(document), {
+                               mousemove: this.handleMouseMove,
+                               mouseup: this.endInteraction
+                       });
+               }
+
+               this.listenTo($(document), {
+                       selectstart: preventDefault, // don't allow selection while dragging
+                       contextmenu: preventDefault // long taps would open menu on Chrome dev tools
+               });
+       },
+
+
+       unbindHandlers: function() {
+               this.stopListeningTo($(document));
+
+               // unbind scroll listening
+               unbindAnyScroll(this.handleTouchScrollProxy);
+               if (this.scrollEl) {
+                       this.stopListeningTo(this.scrollEl, 'scroll');
+               }
+       },
+
+
+       // Drag (high-level)
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       // extraOptions ignored if drag already started
+       startDrag: function(ev, extraOptions) {
+               this.startInteraction(ev, extraOptions); // ensure interaction began
+
+               if (!this.isDragging) {
+                       this.isDragging = true;
+                       this.handleDragStart(ev);
+               }
+       },
+
+
+       handleDragStart: function(ev) {
+               this.trigger('dragStart', ev);
+               this.initHrefHack();
+       },
+
+
+       handleMove: function(ev) {
+               var dx = getEvX(ev) - this.originX;
+               var dy = getEvY(ev) - this.originY;
+               var minDistance = this.minDistance;
+               var distanceSq; // current distance from the origin, squared
+
+               if (!this.isDistanceSurpassed) {
+                       distanceSq = dx * dx + dy * dy;
+                       if (distanceSq >= minDistance * minDistance) { // use pythagorean theorem
+                               this.handleDistanceSurpassed(ev);
+                       }
+               }
+
+               if (this.isDragging) {
+                       this.handleDrag(dx, dy, ev);
+               }
+       },
+
+
+       // Called while the mouse is being moved and when we know a legitimate drag is taking place
+       handleDrag: function(dx, dy, ev) {
+               this.trigger('drag', dx, dy, ev);
+               this.updateAutoScroll(ev); // will possibly cause scrolling
+       },
+
+
+       endDrag: function(ev) {
+               if (this.isDragging) {
+                       this.isDragging = false;
+                       this.handleDragEnd(ev);
+               }
+       },
+
+
+       handleDragEnd: function(ev) {
+               this.trigger('dragEnd', ev);
+               this.destroyHrefHack();
+       },
+
+
+       // Delay
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       startDelay: function(initialEv) {
+               var _this = this;
+
+               if (this.delay) {
+                       this.delayTimeoutId = setTimeout(function() {
+                               _this.handleDelayEnd(initialEv);
+                       }, this.delay);
+               }
+               else {
+                       this.handleDelayEnd(initialEv);
+               }
+       },
+
+
+       handleDelayEnd: function(initialEv) {
+               this.isDelayEnded = true;
+
+               if (this.isDistanceSurpassed) {
+                       this.startDrag(initialEv);
+               }
+       },
+
+
+       // Distance
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       handleDistanceSurpassed: function(ev) {
+               this.isDistanceSurpassed = true;
+
+               if (this.isDelayEnded) {
+                       this.startDrag(ev);
+               }
+       },
+
+
+       // Mouse / Touch
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       handleTouchMove: function(ev) {
+               // prevent inertia and touchmove-scrolling while dragging
+               if (this.isDragging) {
+                       ev.preventDefault();
+               }
+
+               this.handleMove(ev);
+       },
+
+
+       handleMouseMove: function(ev) {
+               this.handleMove(ev);
+       },
+
+
+       // Scrolling (unrelated to auto-scroll)
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       handleTouchScroll: function(ev) {
+               // if the drag is being initiated by touch, but a scroll happens before
+               // the drag-initiating delay is over, cancel the drag
+               if (!this.isDragging) {
+                       this.endInteraction(ev, true); // isCancelled=true
+               }
+       },
+
+
+       // <A> HREF Hack
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       initHrefHack: function() {
+               var subjectEl = this.subjectEl;
+
+               // remove a mousedown'd <a>'s href so it is not visited (IE8 bug)
+               if ((this.subjectHref = subjectEl ? subjectEl.attr('href') : null)) {
+                       subjectEl.removeAttr('href');
+               }
+       },
+
+
+       destroyHrefHack: function() {
+               var subjectEl = this.subjectEl;
+               var subjectHref = this.subjectHref;
+
+               // restore a mousedown'd <a>'s href (for IE8 bug)
+               setTimeout(function() { // must be outside of the click's execution
+                       if (subjectHref) {
+                               subjectEl.attr('href', subjectHref);
+                       }
+               }, 0);
+       },
+
+
+       // Utils
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       // Triggers a callback. Calls a function in the option hash of the same name.
+       // Arguments beyond the first `name` are forwarded on.
+       trigger: function(name) {
+               if (this.options[name]) {
+                       this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
+               }
+               // makes _methods callable by event name. TODO: kill this
+               if (this['_' + name]) {
+                       this['_' + name].apply(this, Array.prototype.slice.call(arguments, 1));
+               }
+       }
+
+
+});
+
+;;
+/*
+this.scrollEl is set in DragListener
+*/
+DragListener.mixin({
+
+       isAutoScroll: false,
+
+       scrollBounds: null, // { top, bottom, left, right }
+       scrollTopVel: null, // pixels per second
+       scrollLeftVel: null, // pixels per second
+       scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
+
+       // defaults
+       scrollSensitivity: 30, // pixels from edge for scrolling to start
+       scrollSpeed: 200, // pixels per second, at maximum speed
+       scrollIntervalMs: 50, // millisecond wait between scroll increment
+
+
+       initAutoScroll: function() {
+               var scrollEl = this.scrollEl;
+
+               this.isAutoScroll =
+                       this.options.scroll &&
+                       scrollEl &&
+                       !scrollEl.is(window) &&
+                       !scrollEl.is(document);
+
+               if (this.isAutoScroll) {
+                       // debounce makes sure rapid calls don't happen
+                       this.listenTo(scrollEl, 'scroll', debounce(this.handleDebouncedScroll, 100));
+               }
+       },
+
+
+       destroyAutoScroll: function() {
+               this.endAutoScroll(); // kill any animation loop
+
+               // remove the scroll handler if there is a scrollEl
+               if (this.isAutoScroll) {
+                       this.stopListeningTo(this.scrollEl, 'scroll'); // will probably get removed by unbindHandlers too :(
+               }
+       },
+
+
+       // Computes and stores the bounding rectangle of scrollEl
+       computeScrollBounds: function() {
+               if (this.isAutoScroll) {
+                       this.scrollBounds = getOuterRect(this.scrollEl);
+                       // TODO: use getClientRect in future. but prevents auto scrolling when on top of scrollbars
+               }
+       },
+
+
+       // Called when the dragging is in progress and scrolling should be updated
+       updateAutoScroll: function(ev) {
+               var sensitivity = this.scrollSensitivity;
+               var bounds = this.scrollBounds;
+               var topCloseness, bottomCloseness;
+               var leftCloseness, rightCloseness;
+               var topVel = 0;
+               var leftVel = 0;
+
+               if (bounds) { // only scroll if scrollEl exists
+
+                       // compute closeness to edges. valid range is from 0.0 - 1.0
+                       topCloseness = (sensitivity - (getEvY(ev) - bounds.top)) / sensitivity;
+                       bottomCloseness = (sensitivity - (bounds.bottom - getEvY(ev))) / sensitivity;
+                       leftCloseness = (sensitivity - (getEvX(ev) - bounds.left)) / sensitivity;
+                       rightCloseness = (sensitivity - (bounds.right - getEvX(ev))) / sensitivity;
+
+                       // translate vertical closeness into velocity.
+                       // mouse must be completely in bounds for velocity to happen.
+                       if (topCloseness >= 0 && topCloseness <= 1) {
+                               topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
+                       }
+                       else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
+                               topVel = bottomCloseness * this.scrollSpeed;
+                       }
+
+                       // translate horizontal closeness into velocity
+                       if (leftCloseness >= 0 && leftCloseness <= 1) {
+                               leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
+                       }
+                       else if (rightCloseness >= 0 && rightCloseness <= 1) {
+                               leftVel = rightCloseness * this.scrollSpeed;
+                       }
+               }
+
+               this.setScrollVel(topVel, leftVel);
+       },
+
+
+       // Sets the speed-of-scrolling for the scrollEl
+       setScrollVel: function(topVel, leftVel) {
+
+               this.scrollTopVel = topVel;
+               this.scrollLeftVel = leftVel;
+
+               this.constrainScrollVel(); // massages into realistic values
+
+               // if there is non-zero velocity, and an animation loop hasn't already started, then START
+               if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
+                       this.scrollIntervalId = setInterval(
+                               proxy(this, 'scrollIntervalFunc'), // scope to `this`
+                               this.scrollIntervalMs
+                       );
+               }
+       },
+
+
+       // Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
+       constrainScrollVel: function() {
+               var el = this.scrollEl;
+
+               if (this.scrollTopVel < 0) { // scrolling up?
+                       if (el.scrollTop() <= 0) { // already scrolled all the way up?
+                               this.scrollTopVel = 0;
+                       }
+               }
+               else if (this.scrollTopVel > 0) { // scrolling down?
+                       if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
+                               this.scrollTopVel = 0;
+                       }
+               }
+
+               if (this.scrollLeftVel < 0) { // scrolling left?
+                       if (el.scrollLeft() <= 0) { // already scrolled all the left?
+                               this.scrollLeftVel = 0;
+                       }
+               }
+               else if (this.scrollLeftVel > 0) { // scrolling right?
+                       if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
+                               this.scrollLeftVel = 0;
+                       }
+               }
+       },
+
+
+       // This function gets called during every iteration of the scrolling animation loop
+       scrollIntervalFunc: function() {
+               var el = this.scrollEl;
+               var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by
+
+               // change the value of scrollEl's scroll
+               if (this.scrollTopVel) {
+                       el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
+               }
+               if (this.scrollLeftVel) {
+                       el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
+               }
+
+               this.constrainScrollVel(); // since the scroll values changed, recompute the velocities
+
+               // if scrolled all the way, which causes the vels to be zero, stop the animation loop
+               if (!this.scrollTopVel && !this.scrollLeftVel) {
+                       this.endAutoScroll();
+               }
+       },
+
+
+       // Kills any existing scrolling animation loop
+       endAutoScroll: function() {
+               if (this.scrollIntervalId) {
+                       clearInterval(this.scrollIntervalId);
+                       this.scrollIntervalId = null;
+
+                       this.handleScrollEnd();
+               }
+       },
+
+
+       // Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
+       handleDebouncedScroll: function() {
+               // recompute all coordinates, but *only* if this is *not* part of our scrolling animation
+               if (!this.scrollIntervalId) {
+                       this.handleScrollEnd();
+               }
+       },
+
+
+       // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
+       handleScrollEnd: function() {
+       }
+
+});
+;;
+
+/* Tracks mouse movements over a component and raises events about which hit the mouse is over.
+------------------------------------------------------------------------------------------------------------------------
+options:
+- subjectEl
+- subjectCenter
+*/
+
+var HitDragListener = DragListener.extend({
+
+       component: null, // converts coordinates to hits
+               // methods: prepareHits, releaseHits, queryHit
+
+       origHit: null, // the hit the mouse was over when listening started
+       hit: null, // the hit the mouse is over
+       coordAdjust: null, // delta that will be added to the mouse coordinates when computing collisions
+
+
+       constructor: function(component, options) {
+               DragListener.call(this, options); // call the super-constructor
+
+               this.component = component;
+       },
+
+
+       // Called when drag listening starts (but a real drag has not necessarily began).
+       // ev might be undefined if dragging was started manually.
+       handleInteractionStart: function(ev) {
+               var subjectEl = this.subjectEl;
+               var subjectRect;
+               var origPoint;
+               var point;
+
+               this.computeCoords();
+
+               if (ev) {
+                       origPoint = { left: getEvX(ev), top: getEvY(ev) };
+                       point = origPoint;
+
+                       // constrain the point to bounds of the element being dragged
+                       if (subjectEl) {
+                               subjectRect = getOuterRect(subjectEl); // used for centering as well
+                               point = constrainPoint(point, subjectRect);
+                       }
+
+                       this.origHit = this.queryHit(point.left, point.top);
+
+                       // treat the center of the subject as the collision point?
+                       if (subjectEl && this.options.subjectCenter) {
+
+                               // only consider the area the subject overlaps the hit. best for large subjects.
+                               // TODO: skip this if hit didn't supply left/right/top/bottom
+                               if (this.origHit) {
+                                       subjectRect = intersectRects(this.origHit, subjectRect) ||
+                                               subjectRect; // in case there is no intersection
+                               }
+
+                               point = getRectCenter(subjectRect);
+                       }
+
+                       this.coordAdjust = diffPoints(point, origPoint); // point - origPoint
+               }
+               else {
+                       this.origHit = null;
+                       this.coordAdjust = null;
+               }
+
+               // call the super-method. do it after origHit has been computed
+               DragListener.prototype.handleInteractionStart.apply(this, arguments);
+       },
+
+
+       // Recomputes the drag-critical positions of elements
+       computeCoords: function() {
+               this.component.prepareHits();
+               this.computeScrollBounds(); // why is this here??????
+       },
+
+
+       // Called when the actual drag has started
+       handleDragStart: function(ev) {
+               var hit;
+
+               DragListener.prototype.handleDragStart.apply(this, arguments); // call the super-method
+
+               // might be different from this.origHit if the min-distance is large
+               hit = this.queryHit(getEvX(ev), getEvY(ev));
+
+               // report the initial hit the mouse is over
+               // especially important if no min-distance and drag starts immediately
+               if (hit) {
+                       this.handleHitOver(hit);
+               }
+       },
+
+
+       // Called when the drag moves
+       handleDrag: function(dx, dy, ev) {
+               var hit;
+
+               DragListener.prototype.handleDrag.apply(this, arguments); // call the super-method
+
+               hit = this.queryHit(getEvX(ev), getEvY(ev));
+
+               if (!isHitsEqual(hit, this.hit)) { // a different hit than before?
+                       if (this.hit) {
+                               this.handleHitOut();
+                       }
+                       if (hit) {
+                               this.handleHitOver(hit);
+                       }
+               }
+       },
+
+
+       // Called when dragging has been stopped
+       handleDragEnd: function() {
+               this.handleHitDone();
+               DragListener.prototype.handleDragEnd.apply(this, arguments); // call the super-method
+       },
+
+
+       // Called when a the mouse has just moved over a new hit
+       handleHitOver: function(hit) {
+               var isOrig = isHitsEqual(hit, this.origHit);
+
+               this.hit = hit;
+
+               this.trigger('hitOver', this.hit, isOrig, this.origHit);
+       },
+
+
+       // Called when the mouse has just moved out of a hit
+       handleHitOut: function() {
+               if (this.hit) {
+                       this.trigger('hitOut', this.hit);
+                       this.handleHitDone();
+                       this.hit = null;
+               }
+       },
+
+
+       // Called after a hitOut. Also called before a dragStop
+       handleHitDone: function() {
+               if (this.hit) {
+                       this.trigger('hitDone', this.hit);
+               }
+       },
+
+
+       // Called when the interaction ends, whether there was a real drag or not
+       handleInteractionEnd: function() {
+               DragListener.prototype.handleInteractionEnd.apply(this, arguments); // call the super-method
+
+               this.origHit = null;
+               this.hit = null;
+
+               this.component.releaseHits();
+       },
+
+
+       // Called when scrolling has stopped, whether through auto scroll, or the user scrolling
+       handleScrollEnd: function() {
+               DragListener.prototype.handleScrollEnd.apply(this, arguments); // call the super-method
+
+               this.computeCoords(); // hits' absolute positions will be in new places. recompute
+       },
+
+
+       // Gets the hit underneath the coordinates for the given mouse event
+       queryHit: function(left, top) {
+
+               if (this.coordAdjust) {
+                       left += this.coordAdjust.left;
+                       top += this.coordAdjust.top;
+               }
+
+               return this.component.queryHit(left, top);
+       }
+
+});
+
+
+// Returns `true` if the hits are identically equal. `false` otherwise. Must be from the same component.
+// Two null values will be considered equal, as two "out of the component" states are the same.
+function isHitsEqual(hit0, hit1) {
+
+       if (!hit0 && !hit1) {
+               return true;
+       }
+
+       if (hit0 && hit1) {
+               return hit0.component === hit1.component &&
+                       isHitPropsWithin(hit0, hit1) &&
+                       isHitPropsWithin(hit1, hit0); // ensures all props are identical
+       }
+
+       return false;
+}
+
+
+// Returns true if all of subHit's non-standard properties are within superHit
+function isHitPropsWithin(subHit, superHit) {
+       for (var propName in subHit) {
+               if (!/^(component|left|right|top|bottom)$/.test(propName)) {
+                       if (subHit[propName] !== superHit[propName]) {
+                               return false;
+                       }
+               }
+       }
+       return true;
+}
+
+;;
+
+/* Creates a clone of an element and lets it track the mouse as it moves
+----------------------------------------------------------------------------------------------------------------------*/
+
+var MouseFollower = Class.extend(ListenerMixin, {
+
+       options: null,
+
+       sourceEl: null, // the element that will be cloned and made to look like it is dragging
+       el: null, // the clone of `sourceEl` that will track the mouse
+       parentEl: null, // the element that `el` (the clone) will be attached to
+
+       // the initial position of el, relative to the offset parent. made to match the initial offset of sourceEl
+       top0: null,
+       left0: null,
+
+       // the absolute coordinates of the initiating touch/mouse action
+       y0: null,
+       x0: null,
+
+       // the number of pixels the mouse has moved from its initial position
+       topDelta: null,
+       leftDelta: null,
+
+       isFollowing: false,
+       isHidden: false,
+       isAnimating: false, // doing the revert animation?
+
+       constructor: function(sourceEl, options) {
+               this.options = options = options || {};
+               this.sourceEl = sourceEl;
+               this.parentEl = options.parentEl ? $(options.parentEl) : sourceEl.parent(); // default to sourceEl's parent
+       },
+
+
+       // Causes the element to start following the mouse
+       start: function(ev) {
+               if (!this.isFollowing) {
+                       this.isFollowing = true;
+
+                       this.y0 = getEvY(ev);
+                       this.x0 = getEvX(ev);
+                       this.topDelta = 0;
+                       this.leftDelta = 0;
+
+                       if (!this.isHidden) {
+                               this.updatePosition();
+                       }
+
+                       if (getEvIsTouch(ev)) {
+                               this.listenTo($(document), 'touchmove', this.handleMove);
+                       }
+                       else {
+                               this.listenTo($(document), 'mousemove', this.handleMove);
+                       }
+               }
+       },
+
+
+       // Causes the element to stop following the mouse. If shouldRevert is true, will animate back to original position.
+       // `callback` gets invoked when the animation is complete. If no animation, it is invoked immediately.
+       stop: function(shouldRevert, callback) {
+               var _this = this;
+               var revertDuration = this.options.revertDuration;
+
+               function complete() {
+                       this.isAnimating = false;
+                       _this.removeElement();
+
+                       this.top0 = this.left0 = null; // reset state for future updatePosition calls
+
+                       if (callback) {
+                               callback();
+                       }
+               }
+
+               if (this.isFollowing && !this.isAnimating) { // disallow more than one stop animation at a time
+                       this.isFollowing = false;
+
+                       this.stopListeningTo($(document));
+
+                       if (shouldRevert && revertDuration && !this.isHidden) { // do a revert animation?
+                               this.isAnimating = true;
+                               this.el.animate({
+                                       top: this.top0,
+                                       left: this.left0
+                               }, {
+                                       duration: revertDuration,
+                                       complete: complete
+                               });
+                       }
+                       else {
+                               complete();
+                       }
+               }
+       },
+
+
+       // Gets the tracking element. Create it if necessary
+       getEl: function() {
+               var el = this.el;
+
+               if (!el) {
+                       this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
+                       el = this.el = this.sourceEl.clone()
+                               .addClass(this.options.additionalClass || '')
+                               .css({
+                                       position: 'absolute',
+                                       visibility: '', // in case original element was hidden (commonly through hideEvents())
+                                       display: this.isHidden ? 'none' : '', // for when initially hidden
+                                       margin: 0,
+                                       right: 'auto', // erase and set width instead
+                                       bottom: 'auto', // erase and set height instead
+                                       width: this.sourceEl.width(), // explicit height in case there was a 'right' value
+                                       height: this.sourceEl.height(), // explicit width in case there was a 'bottom' value
+                                       opacity: this.options.opacity || '',
+                                       zIndex: this.options.zIndex
+                               });
+
+                       // we don't want long taps or any mouse interaction causing selection/menus.
+                       // would use preventSelection(), but that prevents selectstart, causing problems.
+                       el.addClass('fc-unselectable');
+
+                       el.appendTo(this.parentEl);
+               }
+
+               return el;
+       },
+
+
+       // Removes the tracking element if it has already been created
+       removeElement: function() {
+               if (this.el) {
+                       this.el.remove();
+                       this.el = null;
+               }
+       },
+
+
+       // Update the CSS position of the tracking element
+       updatePosition: function() {
+               var sourceOffset;
+               var origin;
+
+               this.getEl(); // ensure this.el
+
+               // make sure origin info was computed
+               if (this.top0 === null) {
+                       this.sourceEl.width(); // hack to force IE8 to compute correct bounding box
+                       sourceOffset = this.sourceEl.offset();
+                       origin = this.el.offsetParent().offset();
+                       this.top0 = sourceOffset.top - origin.top;
+                       this.left0 = sourceOffset.left - origin.left;
+               }
+
+               this.el.css({
+                       top: this.top0 + this.topDelta,
+                       left: this.left0 + this.leftDelta
+               });
+       },
+
+
+       // Gets called when the user moves the mouse
+       handleMove: function(ev) {
+               this.topDelta = getEvY(ev) - this.y0;
+               this.leftDelta = getEvX(ev) - this.x0;
+
+               if (!this.isHidden) {
+                       this.updatePosition();
+               }
+       },
+
+
+       // Temporarily makes the tracking element invisible. Can be called before following starts
+       hide: function() {
+               if (!this.isHidden) {
+                       this.isHidden = true;
+                       if (this.el) {
+                               this.el.hide();
+                       }
+               }
+       },
+
+
+       // Show the tracking element after it has been temporarily hidden
+       show: function() {
+               if (this.isHidden) {
+                       this.isHidden = false;
+                       this.updatePosition();
+                       this.getEl().show();
+               }
+       }
+
+});
+
+;;
+
+/* An abstract class comprised of a "grid" of areas that each represent a specific datetime
+----------------------------------------------------------------------------------------------------------------------*/
+
+var Grid = FC.Grid = Class.extend(ListenerMixin, MouseIgnorerMixin, {
+
+       view: null, // a View object
+       isRTL: null, // shortcut to the view's isRTL option
+
+       start: null,
+       end: null,
+
+       el: null, // the containing element
+       elsByFill: null, // a hash of jQuery element sets used for rendering each fill. Keyed by fill name.
+
+       // derived from options
+       eventTimeFormat: null,
+       displayEventTime: null,
+       displayEventEnd: null,
+
+       minResizeDuration: null, // TODO: hack. set by subclasses. minumum event resize duration
+
+       // if defined, holds the unit identified (ex: "year" or "month") that determines the level of granularity
+       // of the date areas. if not defined, assumes to be day and time granularity.
+       // TODO: port isTimeScale into same system?
+       largeUnit: null,
+
+       dayDragListener: null,
+       segDragListener: null,
+       segResizeListener: null,
+       externalDragListener: null,
+
+
+       constructor: function(view) {
+               this.view = view;
+               this.isRTL = view.opt('isRTL');
+               this.elsByFill = {};
+
+               this.dayDragListener = this.buildDayDragListener();
+               this.initMouseIgnoring();
+       },
+
+
+       /* Options
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Generates the format string used for event time text, if not explicitly defined by 'timeFormat'
+       computeEventTimeFormat: function() {
+               return this.view.opt('smallTimeFormat');
+       },
+
+
+       // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventTime'.
+       // Only applies to non-all-day events.
+       computeDisplayEventTime: function() {
+               return true;
+       },
+
+
+       // Determines whether events should have their end times displayed, if not explicitly defined by 'displayEventEnd'
+       computeDisplayEventEnd: function() {
+               return true;
+       },
+
+
+       /* Dates
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Tells the grid about what period of time to display.
+       // Any date-related internal data should be generated.
+       setRange: function(range) {
+               this.start = range.start.clone();
+               this.end = range.end.clone();
+
+               this.rangeUpdated();
+               this.processRangeOptions();
+       },
+
+
+       // Called when internal variables that rely on the range should be updated
+       rangeUpdated: function() {
+       },
+
+
+       // Updates values that rely on options and also relate to range
+       processRangeOptions: function() {
+               var view = this.view;
+               var displayEventTime;
+               var displayEventEnd;
+
+               this.eventTimeFormat =
+                       view.opt('eventTimeFormat') ||
+                       view.opt('timeFormat') || // deprecated
+                       this.computeEventTimeFormat();
+
+               displayEventTime = view.opt('displayEventTime');
+               if (displayEventTime == null) {
+                       displayEventTime = this.computeDisplayEventTime(); // might be based off of range
+               }
+
+               displayEventEnd = view.opt('displayEventEnd');
+               if (displayEventEnd == null) {
+                       displayEventEnd = this.computeDisplayEventEnd(); // might be based off of range
+               }
+
+               this.displayEventTime = displayEventTime;
+               this.displayEventEnd = displayEventEnd;
+       },
+
+
+       // Converts a span (has unzoned start/end and any other grid-specific location information)
+       // into an array of segments (pieces of events whose format is decided by the grid).
+       spanToSegs: function(span) {
+               // subclasses must implement
+       },
+
+
+       // Diffs the two dates, returning a duration, based on granularity of the grid
+       // TODO: port isTimeScale into this system?
+       diffDates: function(a, b) {
+               if (this.largeUnit) {
+                       return diffByUnit(a, b, this.largeUnit);
+               }
+               else {
+                       return diffDayTime(a, b);
+               }
+       },
+
+
+       /* Hit Area
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Called before one or more queryHit calls might happen. Should prepare any cached coordinates for queryHit
+       prepareHits: function() {
+       },
+
+
+       // Called when queryHit calls have subsided. Good place to clear any coordinate caches.
+       releaseHits: function() {
+       },
+
+
+       // Given coordinates from the topleft of the document, return data about the date-related area underneath.
+       // Can return an object with arbitrary properties (although top/right/left/bottom are encouraged).
+       // Must have a `grid` property, a reference to this current grid. TODO: avoid this
+       // The returned object will be processed by getHitSpan and getHitEl.
+       queryHit: function(leftOffset, topOffset) {
+       },
+
+
+       // Given position-level information about a date-related area within the grid,
+       // should return an object with at least a start/end date. Can provide other information as well.
+       getHitSpan: function(hit) {
+       },
+
+
+       // Given position-level information about a date-related area within the grid,
+       // should return a jQuery element that best represents it. passed to dayClick callback.
+       getHitEl: function(hit) {
+       },
+
+
+       /* Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Sets the container element that the grid should render inside of.
+       // Does other DOM-related initializations.
+       setElement: function(el) {
+               this.el = el;
+               preventSelection(el);
+
+               this.bindDayHandler('touchstart', this.dayTouchStart);
+               this.bindDayHandler('mousedown', this.dayMousedown);
+
+               // attach event-element-related handlers. in Grid.events
+               // same garbage collection note as above.
+               this.bindSegHandlers();
+
+               this.bindGlobalHandlers();
+       },
+
+
+       bindDayHandler: function(name, handler) {
+               var _this = this;
+
+               // attach a handler to the grid's root element.
+               // jQuery will take care of unregistering them when removeElement gets called.
+               this.el.on(name, function(ev) {
+                       if (
+                               !$(ev.target).is('.fc-event-container *, .fc-more') && // not an an event element, or "more.." link
+                               !$(ev.target).closest('.fc-popover').length // not on a popover (like the "more.." events one)
+                       ) {
+                               return handler.call(_this, ev);
+                       }
+               });
+       },
+
+
+       // Removes the grid's container element from the DOM. Undoes any other DOM-related attachments.
+       // DOES NOT remove any content beforehand (doesn't clear events or call unrenderDates), unlike View
+       removeElement: function() {
+               this.unbindGlobalHandlers();
+               this.clearDragListeners();
+
+               this.el.remove();
+
+               // NOTE: we don't null-out this.el for the same reasons we don't do it within View::removeElement
+       },
+
+
+       // Renders the basic structure of grid view before any content is rendered
+       renderSkeleton: function() {
+               // subclasses should implement
+       },
+
+
+       // Renders the grid's date-related content (like areas that represent days/times).
+       // Assumes setRange has already been called and the skeleton has already been rendered.
+       renderDates: function() {
+               // subclasses should implement
+       },
+
+
+       // Unrenders the grid's date-related content
+       unrenderDates: function() {
+               // subclasses should implement
+       },
+
+
+       /* Handlers
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Binds DOM handlers to elements that reside outside the grid, such as the document
+       bindGlobalHandlers: function() {
+               this.listenTo($(document), {
+                       dragstart: this.externalDragStart, // jqui
+                       sortstart: this.externalDragStart // jqui
+               });
+       },
+
+
+       // Unbinds DOM handlers from elements that reside outside the grid
+       unbindGlobalHandlers: function() {
+               this.stopListeningTo($(document));
+       },
+
+
+       // Process a mousedown on an element that represents a day. For day clicking and selecting.
+       dayMousedown: function(ev) {
+               if (!this.isIgnoringMouse) {
+                       this.dayDragListener.startInteraction(ev, {
+                               //distance: 5, // needs more work if we want dayClick to fire correctly
+                       });
+               }
+       },
+
+
+       dayTouchStart: function(ev) {
+               var view = this.view;
+
+               // HACK to prevent a user's clickaway for unselecting a range or an event
+               // from causing a dayClick.
+               if (view.isSelected || view.selectedEvent) {
+                       this.tempIgnoreMouse();
+               }
+
+               this.dayDragListener.startInteraction(ev, {
+                       delay: this.view.opt('longPressDelay')
+               });
+       },
+
+
+       // Creates a listener that tracks the user's drag across day elements.
+       // For day clicking and selecting.
+       buildDayDragListener: function() {
+               var _this = this;
+               var view = this.view;
+               var isSelectable = view.opt('selectable');
+               var dayClickHit; // null if invalid dayClick
+               var selectionSpan; // null if invalid selection
+
+               // this listener tracks a mousedown on a day element, and a subsequent drag.
+               // if the drag ends on the same day, it is a 'dayClick'.
+               // if 'selectable' is enabled, this listener also detects selections.
+               var dragListener = new HitDragListener(this, {
+                       scroll: view.opt('dragScroll'),
+                       interactionStart: function() {
+                               dayClickHit = dragListener.origHit; // for dayClick, where no dragging happens
+                       },
+                       dragStart: function() {
+                               view.unselect(); // since we could be rendering a new selection, we want to clear any old one
+                       },
+                       hitOver: function(hit, isOrig, origHit) {
+                               if (origHit) { // click needs to have started on a hit
+
+                                       // if user dragged to another cell at any point, it can no longer be a dayClick
+                                       if (!isOrig) {
+                                               dayClickHit = null;
+                                       }
+
+                                       if (isSelectable) {
+                                               selectionSpan = _this.computeSelection(
+                                                       _this.getHitSpan(origHit),
+                                                       _this.getHitSpan(hit)
+                                               );
+                                               if (selectionSpan) {
+                                                       _this.renderSelection(selectionSpan);
+                                               }
+                                               else if (selectionSpan === false) {
+                                                       disableCursor();
+                                               }
+                                       }
+                               }
+                       },
+                       hitOut: function() {
+                               dayClickHit = null;
+                               selectionSpan = null;
+                               _this.unrenderSelection();
+                               enableCursor();
+                       },
+                       interactionEnd: function(ev, isCancelled) {
+                               if (!isCancelled) {
+                                       if (
+                                               dayClickHit &&
+                                               !_this.isIgnoringMouse // see hack in dayTouchStart
+                                       ) {
+                                               view.triggerDayClick(
+                                                       _this.getHitSpan(dayClickHit),
+                                                       _this.getHitEl(dayClickHit),
+                                                       ev
+                                               );
+                                       }
+                                       if (selectionSpan) {
+                                               // the selection will already have been rendered. just report it
+                                               view.reportSelection(selectionSpan, ev);
+                                       }
+                                       enableCursor();
+                               }
+                       }
+               });
+
+               return dragListener;
+       },
+
+
+       // Kills all in-progress dragging.
+       // Useful for when public API methods that result in re-rendering are invoked during a drag.
+       // Also useful for when touch devices misbehave and don't fire their touchend.
+       clearDragListeners: function() {
+               this.dayDragListener.endInteraction();
+
+               if (this.segDragListener) {
+                       this.segDragListener.endInteraction(); // will clear this.segDragListener
+               }
+               if (this.segResizeListener) {
+                       this.segResizeListener.endInteraction(); // will clear this.segResizeListener
+               }
+               if (this.externalDragListener) {
+                       this.externalDragListener.endInteraction(); // will clear this.externalDragListener
+               }
+       },
+
+
+       /* Event Helper
+       ------------------------------------------------------------------------------------------------------------------*/
+       // TODO: should probably move this to Grid.events, like we did event dragging / resizing
+
+
+       // Renders a mock event at the given event location, which contains zoned start/end properties.
+       // Returns all mock event elements.
+       renderEventLocationHelper: function(eventLocation, sourceSeg) {
+               var fakeEvent = this.fabricateHelperEvent(eventLocation, sourceSeg);
+
+               return this.renderHelper(fakeEvent, sourceSeg); // do the actual rendering
+       },
+
+
+       // Builds a fake event given zoned event date properties and a segment is should be inspired from.
+       // The range's end can be null, in which case the mock event that is rendered will have a null end time.
+       // `sourceSeg` is the internal segment object involved in the drag. If null, something external is dragging.
+       fabricateHelperEvent: function(eventLocation, sourceSeg) {
+               var fakeEvent = sourceSeg ? createObject(sourceSeg.event) : {}; // mask the original event object if possible
+
+               fakeEvent.start = eventLocation.start.clone();
+               fakeEvent.end = eventLocation.end ? eventLocation.end.clone() : null;
+               fakeEvent.allDay = null; // force it to be freshly computed by normalizeEventDates
+               this.view.calendar.normalizeEventDates(fakeEvent);
+
+               // this extra className will be useful for differentiating real events from mock events in CSS
+               fakeEvent.className = (fakeEvent.className || []).concat('fc-helper');
+
+               // if something external is being dragged in, don't render a resizer
+               if (!sourceSeg) {
+                       fakeEvent.editable = false;
+               }
+
+               return fakeEvent;
+       },
+
+
+       // Renders a mock event. Given zoned event date properties.
+       // Must return all mock event elements.
+       renderHelper: function(eventLocation, sourceSeg) {
+               // subclasses must implement
+       },
+
+
+       // Unrenders a mock event
+       unrenderHelper: function() {
+               // subclasses must implement
+       },
+
+
+       /* Selection
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of a selection. Will highlight by default but can be overridden by subclasses.
+       // Given a span (unzoned start/end and other misc data)
+       renderSelection: function(span) {
+               this.renderHighlight(span);
+       },
+
+
+       // Unrenders any visual indications of a selection. Will unrender a highlight by default.
+       unrenderSelection: function() {
+               this.unrenderHighlight();
+       },
+
+
+       // Given the first and last date-spans of a selection, returns another date-span object.
+       // Subclasses can override and provide additional data in the span object. Will be passed to renderSelection().
+       // Will return false if the selection is invalid and this should be indicated to the user.
+       // Will return null/undefined if a selection invalid but no error should be reported.
+       computeSelection: function(span0, span1) {
+               var span = this.computeSelectionSpan(span0, span1);
+
+               if (span && !this.view.calendar.isSelectionSpanAllowed(span)) {
+                       return false;
+               }
+
+               return span;
+       },
+
+
+       // Given two spans, must return the combination of the two.
+       // TODO: do this separation of concerns (combining VS validation) for event dnd/resize too.
+       computeSelectionSpan: function(span0, span1) {
+               var dates = [ span0.start, span0.end, span1.start, span1.end ];
+
+               dates.sort(compareNumbers); // sorts chronologically. works with Moments
+
+               return { start: dates[0].clone(), end: dates[3].clone() };
+       },
+
+
+       /* Highlight
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders an emphasis on the given date range. Given a span (unzoned start/end and other misc data)
+       renderHighlight: function(span) {
+               this.renderFill('highlight', this.spanToSegs(span));
+       },
+
+
+       // Unrenders the emphasis on a date range
+       unrenderHighlight: function() {
+               this.unrenderFill('highlight');
+       },
+
+
+       // Generates an array of classNames for rendering the highlight. Used by the fill system.
+       highlightSegClasses: function() {
+               return [ 'fc-highlight' ];
+       },
+
+
+       /* Business Hours
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderBusinessHours: function() {
+       },
+
+
+       unrenderBusinessHours: function() {
+       },
+
+
+       /* Now Indicator
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       getNowIndicatorUnit: function() {
+       },
+
+
+       renderNowIndicator: function(date) {
+       },
+
+
+       unrenderNowIndicator: function() {
+       },
+
+
+       /* Fill System (highlight, background events, business hours)
+       --------------------------------------------------------------------------------------------------------------------
+       TODO: remove this system. like we did in TimeGrid
+       */
+
+
+       // Renders a set of rectangles over the given segments of time.
+       // MUST RETURN a subset of segs, the segs that were actually rendered.
+       // Responsible for populating this.elsByFill. TODO: better API for expressing this requirement
+       renderFill: function(type, segs) {
+               // subclasses must implement
+       },
+
+
+       // Unrenders a specific type of fill that is currently rendered on the grid
+       unrenderFill: function(type) {
+               var el = this.elsByFill[type];
+
+               if (el) {
+                       el.remove();
+                       delete this.elsByFill[type];
+               }
+       },
+
+
+       // Renders and assigns an `el` property for each fill segment. Generic enough to work with different types.
+       // Only returns segments that successfully rendered.
+       // To be harnessed by renderFill (implemented by subclasses).
+       // Analagous to renderFgSegEls.
+       renderFillSegEls: function(type, segs) {
+               var _this = this;
+               var segElMethod = this[type + 'SegEl'];
+               var html = '';
+               var renderedSegs = [];
+               var i;
+
+               if (segs.length) {
+
+                       // build a large concatenation of segment HTML
+                       for (i = 0; i < segs.length; i++) {
+                               html += this.fillSegHtml(type, segs[i]);
+                       }
+
+                       // Grab individual elements from the combined HTML string. Use each as the default rendering.
+                       // Then, compute the 'el' for each segment.
+                       $(html).each(function(i, node) {
+                               var seg = segs[i];
+                               var el = $(node);
+
+                               // allow custom filter methods per-type
+                               if (segElMethod) {
+                                       el = segElMethod.call(_this, seg, el);
+                               }
+
+                               if (el) { // custom filters did not cancel the render
+                                       el = $(el); // allow custom filter to return raw DOM node
+
+                                       // correct element type? (would be bad if a non-TD were inserted into a table for example)
+                                       if (el.is(_this.fillSegTag)) {
+                                               seg.el = el;
+                                               renderedSegs.push(seg);
+                                       }
+                               }
+                       });
+               }
+
+               return renderedSegs;
+       },
+
+
+       fillSegTag: 'div', // subclasses can override
+
+
+       // Builds the HTML needed for one fill segment. Generic enought o work with different types.
+       fillSegHtml: function(type, seg) {
+
+               // custom hooks per-type
+               var classesMethod = this[type + 'SegClasses'];
+               var cssMethod = this[type + 'SegCss'];
+
+               var classes = classesMethod ? classesMethod.call(this, seg) : [];
+               var css = cssToStr(cssMethod ? cssMethod.call(this, seg) : {});
+
+               return '<' + this.fillSegTag +
+                       (classes.length ? ' class="' + classes.join(' ') + '"' : '') +
+                       (css ? ' style="' + css + '"' : '') +
+                       ' />';
+       },
+
+
+
+       /* Generic rendering utilities for subclasses
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes HTML classNames for a single-day element
+       getDayClasses: function(date) {
+               var view = this.view;
+               var today = view.calendar.getNow();
+               var classes = [ 'fc-' + dayIDs[date.day()] ];
+
+               if (
+                       view.intervalDuration.as('months') == 1 &&
+                       date.month() != view.intervalStart.month()
+               ) {
+                       classes.push('fc-other-month');
+               }
+
+               if (date.isSame(today, 'day')) {
+                       classes.push(
+                               'fc-today',
+                               view.highlightStateClass
+                       );
+               }
+               else if (date < today) {
+                       classes.push('fc-past');
+               }
+               else {
+                       classes.push('fc-future');
+               }
+
+               return classes;
+       }
+
+});
+
+;;
+
+/* Event-rendering and event-interaction methods for the abstract Grid class
+----------------------------------------------------------------------------------------------------------------------*/
+
+Grid.mixin({
+
+       mousedOverSeg: null, // the segment object the user's mouse is over. null if over nothing
+       isDraggingSeg: false, // is a segment being dragged? boolean
+       isResizingSeg: false, // is a segment being resized? boolean
+       isDraggingExternal: false, // jqui-dragging an external element? boolean
+       segs: null, // the *event* segments currently rendered in the grid. TODO: rename to `eventSegs`
+
+
+       // Renders the given events onto the grid
+       renderEvents: function(events) {
+               var bgEvents = [];
+               var fgEvents = [];
+               var i;
+
+               for (i = 0; i < events.length; i++) {
+                       (isBgEvent(events[i]) ? bgEvents : fgEvents).push(events[i]);
+               }
+
+               this.segs = [].concat( // record all segs
+                       this.renderBgEvents(bgEvents),
+                       this.renderFgEvents(fgEvents)
+               );
+       },
+
+
+       renderBgEvents: function(events) {
+               var segs = this.eventsToSegs(events);
+
+               // renderBgSegs might return a subset of segs, segs that were actually rendered
+               return this.renderBgSegs(segs) || segs;
+       },
+
+
+       renderFgEvents: function(events) {
+               var segs = this.eventsToSegs(events);
+
+               // renderFgSegs might return a subset of segs, segs that were actually rendered
+               return this.renderFgSegs(segs) || segs;
+       },
+
+
+       // Unrenders all events currently rendered on the grid
+       unrenderEvents: function() {
+               this.handleSegMouseout(); // trigger an eventMouseout if user's mouse is over an event
+               this.clearDragListeners();
+
+               this.unrenderFgSegs();
+               this.unrenderBgSegs();
+
+               this.segs = null;
+       },
+
+
+       // Retrieves all rendered segment objects currently rendered on the grid
+       getEventSegs: function() {
+               return this.segs || [];
+       },
+
+
+       /* Foreground Segment Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders foreground event segments onto the grid. May return a subset of segs that were rendered.
+       renderFgSegs: function(segs) {
+               // subclasses must implement
+       },
+
+
+       // Unrenders all currently rendered foreground segments
+       unrenderFgSegs: function() {
+               // subclasses must implement
+       },
+
+
+       // Renders and assigns an `el` property for each foreground event segment.
+       // Only returns segments that successfully rendered.
+       // A utility that subclasses may use.
+       renderFgSegEls: function(segs, disableResizing) {
+               var view = this.view;
+               var html = '';
+               var renderedSegs = [];
+               var i;
+
+               if (segs.length) { // don't build an empty html string
+
+                       // build a large concatenation of event segment HTML
+                       for (i = 0; i < segs.length; i++) {
+                               html += this.fgSegHtml(segs[i], disableResizing);
+                       }
+
+                       // Grab individual elements from the combined HTML string. Use each as the default rendering.
+                       // Then, compute the 'el' for each segment. An el might be null if the eventRender callback returned false.
+                       $(html).each(function(i, node) {
+                               var seg = segs[i];
+                               var el = view.resolveEventEl(seg.event, $(node));
+
+                               if (el) {
+                                       el.data('fc-seg', seg); // used by handlers
+                                       seg.el = el;
+                                       renderedSegs.push(seg);
+                               }
+                       });
+               }
+
+               return renderedSegs;
+       },
+
+
+       // Generates the HTML for the default rendering of a foreground event segment. Used by renderFgSegEls()
+       fgSegHtml: function(seg, disableResizing) {
+               // subclasses should implement
+       },
+
+
+       /* Background Segment Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders the given background event segments onto the grid.
+       // Returns a subset of the segs that were actually rendered.
+       renderBgSegs: function(segs) {
+               return this.renderFill('bgEvent', segs);
+       },
+
+
+       // Unrenders all the currently rendered background event segments
+       unrenderBgSegs: function() {
+               this.unrenderFill('bgEvent');
+       },
+
+
+       // Renders a background event element, given the default rendering. Called by the fill system.
+       bgEventSegEl: function(seg, el) {
+               return this.view.resolveEventEl(seg.event, el); // will filter through eventRender
+       },
+
+
+       // Generates an array of classNames to be used for the default rendering of a background event.
+       // Called by the fill system.
+       bgEventSegClasses: function(seg) {
+               var event = seg.event;
+               var source = event.source || {};
+
+               return [ 'fc-bgevent' ].concat(
+                       event.className,
+                       source.className || []
+               );
+       },
+
+
+       // Generates a semicolon-separated CSS string to be used for the default rendering of a background event.
+       // Called by the fill system.
+       bgEventSegCss: function(seg) {
+               return {
+                       'background-color': this.getSegSkinCss(seg)['background-color']
+               };
+       },
+
+
+       // Generates an array of classNames to be used for the rendering business hours overlay. Called by the fill system.
+       businessHoursSegClasses: function(seg) {
+               return [ 'fc-nonbusiness', 'fc-bgevent' ];
+       },
+
+
+       /* Handlers
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Attaches event-element-related handlers to the container element and leverage bubbling
+       bindSegHandlers: function() {
+               this.bindSegHandler('touchstart', this.handleSegTouchStart);
+               this.bindSegHandler('touchend', this.handleSegTouchEnd);
+               this.bindSegHandler('mouseenter', this.handleSegMouseover);
+               this.bindSegHandler('mouseleave', this.handleSegMouseout);
+               this.bindSegHandler('mousedown', this.handleSegMousedown);
+               this.bindSegHandler('click', this.handleSegClick);
+       },
+
+
+       // Executes a handler for any a user-interaction on a segment.
+       // Handler gets called with (seg, ev), and with the `this` context of the Grid
+       bindSegHandler: function(name, handler) {
+               var _this = this;
+
+               this.el.on(name, '.fc-event-container > *', function(ev) {
+                       var seg = $(this).data('fc-seg'); // grab segment data. put there by View::renderEvents
+
+                       // only call the handlers if there is not a drag/resize in progress
+                       if (seg && !_this.isDraggingSeg && !_this.isResizingSeg) {
+                               return handler.call(_this, seg, ev); // context will be the Grid
+                       }
+               });
+       },
+
+
+       handleSegClick: function(seg, ev) {
+               return this.view.trigger('eventClick', seg.el[0], seg.event, ev); // can return `false` to cancel
+       },
+
+
+       // Updates internal state and triggers handlers for when an event element is moused over
+       handleSegMouseover: function(seg, ev) {
+               if (
+                       !this.isIgnoringMouse &&
+                       !this.mousedOverSeg
+               ) {
+                       this.mousedOverSeg = seg;
+                       seg.el.addClass('fc-allow-mouse-resize');
+                       this.view.trigger('eventMouseover', seg.el[0], seg.event, ev);
+               }
+       },
+
+
+       // Updates internal state and triggers handlers for when an event element is moused out.
+       // Can be given no arguments, in which case it will mouseout the segment that was previously moused over.
+       handleSegMouseout: function(seg, ev) {
+               ev = ev || {}; // if given no args, make a mock mouse event
+
+               if (this.mousedOverSeg) {
+                       seg = seg || this.mousedOverSeg; // if given no args, use the currently moused-over segment
+                       this.mousedOverSeg = null;
+                       seg.el.removeClass('fc-allow-mouse-resize');
+                       this.view.trigger('eventMouseout', seg.el[0], seg.event, ev);
+               }
+       },
+
+
+       handleSegMousedown: function(seg, ev) {
+               var isResizing = this.startSegResize(seg, ev, { distance: 5 });
+
+               if (!isResizing && this.view.isEventDraggable(seg.event)) {
+                       this.buildSegDragListener(seg)
+                               .startInteraction(ev, {
+                                       distance: 5
+                               });
+               }
+       },
+
+
+       handleSegTouchStart: function(seg, ev) {
+               var view = this.view;
+               var event = seg.event;
+               var isSelected = view.isEventSelected(event);
+               var isDraggable = view.isEventDraggable(event);
+               var isResizable = view.isEventResizable(event);
+               var isResizing = false;
+               var dragListener;
+
+               if (isSelected && isResizable) {
+                       // only allow resizing of the event is selected
+                       isResizing = this.startSegResize(seg, ev);
+               }
+
+               if (!isResizing && (isDraggable || isResizable)) { // allowed to be selected?
+
+                       dragListener = isDraggable ?
+                               this.buildSegDragListener(seg) :
+                               this.buildSegSelectListener(seg); // seg isn't draggable, but still needs to be selected
+
+                       dragListener.startInteraction(ev, { // won't start if already started
+                               delay: isSelected ? 0 : this.view.opt('longPressDelay') // do delay if not already selected
+                       });
+               }
+
+               // a long tap simulates a mouseover. ignore this bogus mouseover.
+               this.tempIgnoreMouse();
+       },
+
+
+       handleSegTouchEnd: function(seg, ev) {
+               // touchstart+touchend = click, which simulates a mouseover.
+               // ignore this bogus mouseover.
+               this.tempIgnoreMouse();
+       },
+
+
+       // returns boolean whether resizing actually started or not.
+       // assumes the seg allows resizing.
+       // `dragOptions` are optional.
+       startSegResize: function(seg, ev, dragOptions) {
+               if ($(ev.target).is('.fc-resizer')) {
+                       this.buildSegResizeListener(seg, $(ev.target).is('.fc-start-resizer'))
+                               .startInteraction(ev, dragOptions);
+                       return true;
+               }
+               return false;
+       },
+
+
+
+       /* Event Dragging
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Builds a listener that will track user-dragging on an event segment.
+       // Generic enough to work with any type of Grid.
+       // Has side effect of setting/unsetting `segDragListener`
+       buildSegDragListener: function(seg) {
+               var _this = this;
+               var view = this.view;
+               var calendar = view.calendar;
+               var el = seg.el;
+               var event = seg.event;
+               var isDragging;
+               var mouseFollower; // A clone of the original element that will move with the mouse
+               var dropLocation; // zoned event date properties
+
+               if (this.segDragListener) {
+                       return this.segDragListener;
+               }
+
+               // Tracks mouse movement over the *view's* coordinate map. Allows dragging and dropping between subcomponents
+               // of the view.
+               var dragListener = this.segDragListener = new HitDragListener(view, {
+                       scroll: view.opt('dragScroll'),
+                       subjectEl: el,
+                       subjectCenter: true,
+                       interactionStart: function(ev) {
+                               isDragging = false;
+                               mouseFollower = new MouseFollower(seg.el, {
+                                       additionalClass: 'fc-dragging',
+                                       parentEl: view.el,
+                                       opacity: dragListener.isTouch ? null : view.opt('dragOpacity'),
+                                       revertDuration: view.opt('dragRevertDuration'),
+                                       zIndex: 2 // one above the .fc-view
+                               });
+                               mouseFollower.hide(); // don't show until we know this is a real drag
+                               mouseFollower.start(ev);
+                       },
+                       dragStart: function(ev) {
+                               if (dragListener.isTouch && !view.isEventSelected(event)) {
+                                       // if not previously selected, will fire after a delay. then, select the event
+                                       view.selectEvent(event);
+                               }
+                               isDragging = true;
+                               _this.handleSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
+                               _this.segDragStart(seg, ev);
+                               view.hideEvent(event); // hide all event segments. our mouseFollower will take over
+                       },
+                       hitOver: function(hit, isOrig, origHit) {
+                               var dragHelperEls;
+
+                               // starting hit could be forced (DayGrid.limit)
+                               if (seg.hit) {
+                                       origHit = seg.hit;
+                               }
+
+                               // since we are querying the parent view, might not belong to this grid
+                               dropLocation = _this.computeEventDrop(
+                                       origHit.component.getHitSpan(origHit),
+                                       hit.component.getHitSpan(hit),
+                                       event
+                               );
+
+                               if (dropLocation && !calendar.isEventSpanAllowed(_this.eventToSpan(dropLocation), event)) {
+                                       disableCursor();
+                                       dropLocation = null;
+                               }
+
+                               // if a valid drop location, have the subclass render a visual indication
+                               if (dropLocation && (dragHelperEls = view.renderDrag(dropLocation, seg))) {
+
+                                       dragHelperEls.addClass('fc-dragging');
+                                       if (!dragListener.isTouch) {
+                                               _this.applyDragOpacity(dragHelperEls);
+                                       }
+
+                                       mouseFollower.hide(); // if the subclass is already using a mock event "helper", hide our own
+                               }
+                               else {
+                                       mouseFollower.show(); // otherwise, have the helper follow the mouse (no snapping)
+                               }
+
+                               if (isOrig) {
+                                       dropLocation = null; // needs to have moved hits to be a valid drop
+                               }
+                       },
+                       hitOut: function() { // called before mouse moves to a different hit OR moved out of all hits
+                               view.unrenderDrag(); // unrender whatever was done in renderDrag
+                               mouseFollower.show(); // show in case we are moving out of all hits
+                               dropLocation = null;
+                       },
+                       hitDone: function() { // Called after a hitOut OR before a dragEnd
+                               enableCursor();
+                       },
+                       interactionEnd: function(ev) {
+                               // do revert animation if hasn't changed. calls a callback when finished (whether animation or not)
+                               mouseFollower.stop(!dropLocation, function() {
+                                       if (isDragging) {
+                                               view.unrenderDrag();
+                                               view.showEvent(event);
+                                               _this.segDragStop(seg, ev);
+                                       }
+                                       if (dropLocation) {
+                                               view.reportEventDrop(event, dropLocation, this.largeUnit, el, ev);
+                                       }
+                               });
+                               _this.segDragListener = null;
+                       }
+               });
+
+               return dragListener;
+       },
+
+
+       // seg isn't draggable, but let's use a generic DragListener
+       // simply for the delay, so it can be selected.
+       // Has side effect of setting/unsetting `segDragListener`
+       buildSegSelectListener: function(seg) {
+               var _this = this;
+               var view = this.view;
+               var event = seg.event;
+
+               if (this.segDragListener) {
+                       return this.segDragListener;
+               }
+
+               var dragListener = this.segDragListener = new DragListener({
+                       dragStart: function(ev) {
+                               if (dragListener.isTouch && !view.isEventSelected(event)) {
+                                       // if not previously selected, will fire after a delay. then, select the event
+                                       view.selectEvent(event);
+                               }
+                       },
+                       interactionEnd: function(ev) {
+                               _this.segDragListener = null;
+                       }
+               });
+
+               return dragListener;
+       },
+
+
+       // Called before event segment dragging starts
+       segDragStart: function(seg, ev) {
+               this.isDraggingSeg = true;
+               this.view.trigger('eventDragStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+       },
+
+
+       // Called after event segment dragging stops
+       segDragStop: function(seg, ev) {
+               this.isDraggingSeg = false;
+               this.view.trigger('eventDragStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+       },
+
+
+       // Given the spans an event drag began, and the span event was dropped, calculates the new zoned start/end/allDay
+       // values for the event. Subclasses may override and set additional properties to be used by renderDrag.
+       // A falsy returned value indicates an invalid drop.
+       // DOES NOT consider overlap/constraint.
+       computeEventDrop: function(startSpan, endSpan, event) {
+               var calendar = this.view.calendar;
+               var dragStart = startSpan.start;
+               var dragEnd = endSpan.start;
+               var delta;
+               var dropLocation; // zoned event date properties
+
+               if (dragStart.hasTime() === dragEnd.hasTime()) {
+                       delta = this.diffDates(dragEnd, dragStart);
+
+                       // if an all-day event was in a timed area and it was dragged to a different time,
+                       // guarantee an end and adjust start/end to have times
+                       if (event.allDay && durationHasTime(delta)) {
+                               dropLocation = {
+                                       start: event.start.clone(),
+                                       end: calendar.getEventEnd(event), // will be an ambig day
+                                       allDay: false // for normalizeEventTimes
+                               };
+                               calendar.normalizeEventTimes(dropLocation);
+                       }
+                       // othewise, work off existing values
+                       else {
+                               dropLocation = {
+                                       start: event.start.clone(),
+                                       end: event.end ? event.end.clone() : null,
+                                       allDay: event.allDay // keep it the same
+                               };
+                       }
+
+                       dropLocation.start.add(delta);
+                       if (dropLocation.end) {
+                               dropLocation.end.add(delta);
+                       }
+               }
+               else {
+                       // if switching from day <-> timed, start should be reset to the dropped date, and the end cleared
+                       dropLocation = {
+                               start: dragEnd.clone(),
+                               end: null, // end should be cleared
+                               allDay: !dragEnd.hasTime()
+                       };
+               }
+
+               return dropLocation;
+       },
+
+
+       // Utility for apply dragOpacity to a jQuery set
+       applyDragOpacity: function(els) {
+               var opacity = this.view.opt('dragOpacity');
+
+               if (opacity != null) {
+                       els.each(function(i, node) {
+                               // Don't use jQuery (will set an IE filter), do it the old fashioned way.
+                               // In IE8, a helper element will disappears if there's a filter.
+                               node.style.opacity = opacity;
+                       });
+               }
+       },
+
+
+       /* External Element Dragging
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Called when a jQuery UI drag is initiated anywhere in the DOM
+       externalDragStart: function(ev, ui) {
+               var view = this.view;
+               var el;
+               var accept;
+
+               if (view.opt('droppable')) { // only listen if this setting is on
+                       el = $((ui ? ui.item : null) || ev.target);
+
+                       // Test that the dragged element passes the dropAccept selector or filter function.
+                       // FYI, the default is "*" (matches all)
+                       accept = view.opt('dropAccept');
+                       if ($.isFunction(accept) ? accept.call(el[0], el) : el.is(accept)) {
+                               if (!this.isDraggingExternal) { // prevent double-listening if fired twice
+                                       this.listenToExternalDrag(el, ev, ui);
+                               }
+                       }
+               }
+       },
+
+
+       // Called when a jQuery UI drag starts and it needs to be monitored for dropping
+       listenToExternalDrag: function(el, ev, ui) {
+               var _this = this;
+               var calendar = this.view.calendar;
+               var meta = getDraggedElMeta(el); // extra data about event drop, including possible event to create
+               var dropLocation; // a null value signals an unsuccessful drag
+
+               // listener that tracks mouse movement over date-associated pixel regions
+               var dragListener = _this.externalDragListener = new HitDragListener(this, {
+                       interactionStart: function() {
+                               _this.isDraggingExternal = true;
+                       },
+                       hitOver: function(hit) {
+                               dropLocation = _this.computeExternalDrop(
+                                       hit.component.getHitSpan(hit), // since we are querying the parent view, might not belong to this grid
+                                       meta
+                               );
+
+                               if ( // invalid hit?
+                                       dropLocation &&
+                                       !calendar.isExternalSpanAllowed(_this.eventToSpan(dropLocation), dropLocation, meta.eventProps)
+                               ) {
+                                       disableCursor();
+                                       dropLocation = null;
+                               }
+
+                               if (dropLocation) {
+                                       _this.renderDrag(dropLocation); // called without a seg parameter
+                               }
+                       },
+                       hitOut: function() {
+                               dropLocation = null; // signal unsuccessful
+                       },
+                       hitDone: function() { // Called after a hitOut OR before a dragEnd
+                               enableCursor();
+                               _this.unrenderDrag();
+                       },
+                       interactionEnd: function(ev) {
+                               if (dropLocation) { // element was dropped on a valid hit
+                                       _this.view.reportExternalDrop(meta, dropLocation, el, ev, ui);
+                               }
+                               _this.isDraggingExternal = false;
+                               _this.externalDragListener = null;
+                       }
+               });
+
+               dragListener.startDrag(ev); // start listening immediately
+       },
+
+
+       // Given a hit to be dropped upon, and misc data associated with the jqui drag (guaranteed to be a plain object),
+       // returns the zoned start/end dates for the event that would result from the hypothetical drop. end might be null.
+       // Returning a null value signals an invalid drop hit.
+       // DOES NOT consider overlap/constraint.
+       computeExternalDrop: function(span, meta) {
+               var calendar = this.view.calendar;
+               var dropLocation = {
+                       start: calendar.applyTimezone(span.start), // simulate a zoned event start date
+                       end: null
+               };
+
+               // if dropped on an all-day span, and element's metadata specified a time, set it
+               if (meta.startTime && !dropLocation.start.hasTime()) {
+                       dropLocation.start.time(meta.startTime);
+               }
+
+               if (meta.duration) {
+                       dropLocation.end = dropLocation.start.clone().add(meta.duration);
+               }
+
+               return dropLocation;
+       },
+
+
+
+       /* Drag Rendering (for both events and an external elements)
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of an event or external element being dragged.
+       // `dropLocation` contains hypothetical start/end/allDay values the event would have if dropped. end can be null.
+       // `seg` is the internal segment object that is being dragged. If dragging an external element, `seg` is null.
+       // A truthy returned value indicates this method has rendered a helper element.
+       // Must return elements used for any mock events.
+       renderDrag: function(dropLocation, seg) {
+               // subclasses must implement
+       },
+
+
+       // Unrenders a visual indication of an event or external element being dragged
+       unrenderDrag: function() {
+               // subclasses must implement
+       },
+
+
+       /* Resizing
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Creates a listener that tracks the user as they resize an event segment.
+       // Generic enough to work with any type of Grid.
+       buildSegResizeListener: function(seg, isStart) {
+               var _this = this;
+               var view = this.view;
+               var calendar = view.calendar;
+               var el = seg.el;
+               var event = seg.event;
+               var eventEnd = calendar.getEventEnd(event);
+               var isDragging;
+               var resizeLocation; // zoned event date properties. falsy if invalid resize
+
+               // Tracks mouse movement over the *grid's* coordinate map
+               var dragListener = this.segResizeListener = new HitDragListener(this, {
+                       scroll: view.opt('dragScroll'),
+                       subjectEl: el,
+                       interactionStart: function() {
+                               isDragging = false;
+                       },
+                       dragStart: function(ev) {
+                               isDragging = true;
+                               _this.handleSegMouseout(seg, ev); // ensure a mouseout on the manipulated event has been reported
+                               _this.segResizeStart(seg, ev);
+                       },
+                       hitOver: function(hit, isOrig, origHit) {
+                               var origHitSpan = _this.getHitSpan(origHit);
+                               var hitSpan = _this.getHitSpan(hit);
+
+                               resizeLocation = isStart ?
+                                       _this.computeEventStartResize(origHitSpan, hitSpan, event) :
+                                       _this.computeEventEndResize(origHitSpan, hitSpan, event);
+
+                               if (resizeLocation) {
+                                       if (!calendar.isEventSpanAllowed(_this.eventToSpan(resizeLocation), event)) {
+                                               disableCursor();
+                                               resizeLocation = null;
+                                       }
+                                       // no change? (TODO: how does this work with timezones?)
+                                       else if (resizeLocation.start.isSame(event.start) && resizeLocation.end.isSame(eventEnd)) {
+                                               resizeLocation = null;
+                                       }
+                               }
+
+                               if (resizeLocation) {
+                                       view.hideEvent(event);
+                                       _this.renderEventResize(resizeLocation, seg);
+                               }
+                       },
+                       hitOut: function() { // called before mouse moves to a different hit OR moved out of all hits
+                               resizeLocation = null;
+                       },
+                       hitDone: function() { // resets the rendering to show the original event
+                               _this.unrenderEventResize();
+                               view.showEvent(event);
+                               enableCursor();
+                       },
+                       interactionEnd: function(ev) {
+                               if (isDragging) {
+                                       _this.segResizeStop(seg, ev);
+                               }
+                               if (resizeLocation) { // valid date to resize to?
+                                       view.reportEventResize(event, resizeLocation, this.largeUnit, el, ev);
+                               }
+                               _this.segResizeListener = null;
+                       }
+               });
+
+               return dragListener;
+       },
+
+
+       // Called before event segment resizing starts
+       segResizeStart: function(seg, ev) {
+               this.isResizingSeg = true;
+               this.view.trigger('eventResizeStart', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+       },
+
+
+       // Called after event segment resizing stops
+       segResizeStop: function(seg, ev) {
+               this.isResizingSeg = false;
+               this.view.trigger('eventResizeStop', seg.el[0], seg.event, ev, {}); // last argument is jqui dummy
+       },
+
+
+       // Returns new date-information for an event segment being resized from its start
+       computeEventStartResize: function(startSpan, endSpan, event) {
+               return this.computeEventResize('start', startSpan, endSpan, event);
+       },
+
+
+       // Returns new date-information for an event segment being resized from its end
+       computeEventEndResize: function(startSpan, endSpan, event) {
+               return this.computeEventResize('end', startSpan, endSpan, event);
+       },
+
+
+       // Returns new zoned date information for an event segment being resized from its start OR end
+       // `type` is either 'start' or 'end'.
+       // DOES NOT consider overlap/constraint.
+       computeEventResize: function(type, startSpan, endSpan, event) {
+               var calendar = this.view.calendar;
+               var delta = this.diffDates(endSpan[type], startSpan[type]);
+               var resizeLocation; // zoned event date properties
+               var defaultDuration;
+
+               // build original values to work from, guaranteeing a start and end
+               resizeLocation = {
+                       start: event.start.clone(),
+                       end: calendar.getEventEnd(event),
+                       allDay: event.allDay
+               };
+
+               // if an all-day event was in a timed area and was resized to a time, adjust start/end to have times
+               if (resizeLocation.allDay && durationHasTime(delta)) {
+                       resizeLocation.allDay = false;
+                       calendar.normalizeEventTimes(resizeLocation);
+               }
+
+               resizeLocation[type].add(delta); // apply delta to start or end
+
+               // if the event was compressed too small, find a new reasonable duration for it
+               if (!resizeLocation.start.isBefore(resizeLocation.end)) {
+
+                       defaultDuration =
+                               this.minResizeDuration || // TODO: hack
+                               (event.allDay ?
+                                       calendar.defaultAllDayEventDuration :
+                                       calendar.defaultTimedEventDuration);
+
+                       if (type == 'start') { // resizing the start?
+                               resizeLocation.start = resizeLocation.end.clone().subtract(defaultDuration);
+                       }
+                       else { // resizing the end?
+                               resizeLocation.end = resizeLocation.start.clone().add(defaultDuration);
+                       }
+               }
+
+               return resizeLocation;
+       },
+
+
+       // Renders a visual indication of an event being resized.
+       // `range` has the updated dates of the event. `seg` is the original segment object involved in the drag.
+       // Must return elements used for any mock events.
+       renderEventResize: function(range, seg) {
+               // subclasses must implement
+       },
+
+
+       // Unrenders a visual indication of an event being resized.
+       unrenderEventResize: function() {
+               // subclasses must implement
+       },
+
+
+       /* Rendering Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Compute the text that should be displayed on an event's element.
+       // `range` can be the Event object itself, or something range-like, with at least a `start`.
+       // If event times are disabled, or the event has no time, will return a blank string.
+       // If not specified, formatStr will default to the eventTimeFormat setting,
+       // and displayEnd will default to the displayEventEnd setting.
+       getEventTimeText: function(range, formatStr, displayEnd) {
+
+               if (formatStr == null) {
+                       formatStr = this.eventTimeFormat;
+               }
+
+               if (displayEnd == null) {
+                       displayEnd = this.displayEventEnd;
+               }
+
+               if (this.displayEventTime && range.start.hasTime()) {
+                       if (displayEnd && range.end) {
+                               return this.view.formatRange(range, formatStr);
+                       }
+                       else {
+                               return range.start.format(formatStr);
+                       }
+               }
+
+               return '';
+       },
+
+
+       // Generic utility for generating the HTML classNames for an event segment's element
+       getSegClasses: function(seg, isDraggable, isResizable) {
+               var view = this.view;
+               var event = seg.event;
+               var classes = [
+                       'fc-event',
+                       seg.isStart ? 'fc-start' : 'fc-not-start',
+                       seg.isEnd ? 'fc-end' : 'fc-not-end'
+               ].concat(
+                       event.className,
+                       event.source ? event.source.className : []
+               );
+
+               if (isDraggable) {
+                       classes.push('fc-draggable');
+               }
+               if (isResizable) {
+                       classes.push('fc-resizable');
+               }
+
+               // event is currently selected? attach a className.
+               if (view.isEventSelected(event)) {
+                       classes.push('fc-selected');
+               }
+
+               return classes;
+       },
+
+
+       // Utility for generating event skin-related CSS properties
+       getSegSkinCss: function(seg) {
+               var event = seg.event;
+               var view = this.view;
+               var source = event.source || {};
+               var eventColor = event.color;
+               var sourceColor = source.color;
+               var optionColor = view.opt('eventColor');
+
+               return {
+                       'background-color':
+                               event.backgroundColor ||
+                               eventColor ||
+                               source.backgroundColor ||
+                               sourceColor ||
+                               view.opt('eventBackgroundColor') ||
+                               optionColor,
+                       'border-color':
+                               event.borderColor ||
+                               eventColor ||
+                               source.borderColor ||
+                               sourceColor ||
+                               view.opt('eventBorderColor') ||
+                               optionColor,
+                       color:
+                               event.textColor ||
+                               source.textColor ||
+                               view.opt('eventTextColor')
+               };
+       },
+
+
+       /* Converting events -> eventRange -> eventSpan -> eventSegs
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Generates an array of segments for the given single event
+       // Can accept an event "location" as well (which only has start/end and no allDay)
+       eventToSegs: function(event) {
+               return this.eventsToSegs([ event ]);
+       },
+
+
+       eventToSpan: function(event) {
+               return this.eventToSpans(event)[0];
+       },
+
+
+       // Generates spans (always unzoned) for the given event.
+       // Does not do any inverting for inverse-background events.
+       // Can accept an event "location" as well (which only has start/end and no allDay)
+       eventToSpans: function(event) {
+               var range = this.eventToRange(event);
+               return this.eventRangeToSpans(range, event);
+       },
+
+
+
+       // Converts an array of event objects into an array of event segment objects.
+       // A custom `segSliceFunc` may be given for arbitrarily slicing up events.
+       // Doesn't guarantee an order for the resulting array.
+       eventsToSegs: function(allEvents, segSliceFunc) {
+               var _this = this;
+               var eventsById = groupEventsById(allEvents);
+               var segs = [];
+
+               $.each(eventsById, function(id, events) {
+                       var ranges = [];
+                       var i;
+
+                       for (i = 0; i < events.length; i++) {
+                               ranges.push(_this.eventToRange(events[i]));
+                       }
+
+                       // inverse-background events (utilize only the first event in calculations)
+                       if (isInverseBgEvent(events[0])) {
+                               ranges = _this.invertRanges(ranges);
+
+                               for (i = 0; i < ranges.length; i++) {
+                                       segs.push.apply(segs, // append to
+                                               _this.eventRangeToSegs(ranges[i], events[0], segSliceFunc));
+                               }
+                       }
+                       // normal event ranges
+                       else {
+                               for (i = 0; i < ranges.length; i++) {
+                                       segs.push.apply(segs, // append to
+                                               _this.eventRangeToSegs(ranges[i], events[i], segSliceFunc));
+                               }
+                       }
+               });
+
+               return segs;
+       },
+
+
+       // Generates the unzoned start/end dates an event appears to occupy
+       // Can accept an event "location" as well (which only has start/end and no allDay)
+       eventToRange: function(event) {
+               return {
+                       start: event.start.clone().stripZone(),
+                       end: (
+                               event.end ?
+                                       event.end.clone() :
+                                       // derive the end from the start and allDay. compute allDay if necessary
+                                       this.view.calendar.getDefaultEventEnd(
+                                               event.allDay != null ?
+                                                       event.allDay :
+                                                       !event.start.hasTime(),
+                                               event.start
+                                       )
+                       ).stripZone()
+               };
+       },
+
+
+       // Given an event's range (unzoned start/end), and the event itself,
+       // slice into segments (using the segSliceFunc function if specified)
+       eventRangeToSegs: function(range, event, segSliceFunc) {
+               var spans = this.eventRangeToSpans(range, event);
+               var segs = [];
+               var i;
+
+               for (i = 0; i < spans.length; i++) {
+                       segs.push.apply(segs, // append to
+                               this.eventSpanToSegs(spans[i], event, segSliceFunc));
+               }
+
+               return segs;
+       },
+
+
+       // Given an event's unzoned date range, return an array of "span" objects.
+       // Subclasses can override.
+       eventRangeToSpans: function(range, event) {
+               return [ $.extend({}, range) ]; // copy into a single-item array
+       },
+
+
+       // Given an event's span (unzoned start/end and other misc data), and the event itself,
+       // slices into segments and attaches event-derived properties to them.
+       eventSpanToSegs: function(span, event, segSliceFunc) {
+               var segs = segSliceFunc ? segSliceFunc(span) : this.spanToSegs(span);
+               var i, seg;
+
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       seg.event = event;
+                       seg.eventStartMS = +span.start; // TODO: not the best name after making spans unzoned
+                       seg.eventDurationMS = span.end - span.start;
+               }
+
+               return segs;
+       },
+
+
+       // Produces a new array of range objects that will cover all the time NOT covered by the given ranges.
+       // SIDE EFFECT: will mutate the given array and will use its date references.
+       invertRanges: function(ranges) {
+               var view = this.view;
+               var viewStart = view.start.clone(); // need a copy
+               var viewEnd = view.end.clone(); // need a copy
+               var inverseRanges = [];
+               var start = viewStart; // the end of the previous range. the start of the new range
+               var i, range;
+
+               // ranges need to be in order. required for our date-walking algorithm
+               ranges.sort(compareRanges);
+
+               for (i = 0; i < ranges.length; i++) {
+                       range = ranges[i];
+
+                       // add the span of time before the event (if there is any)
+                       if (range.start > start) { // compare millisecond time (skip any ambig logic)
+                               inverseRanges.push({
+                                       start: start,
+                                       end: range.start
+                               });
+                       }
+
+                       start = range.end;
+               }
+
+               // add the span of time after the last event (if there is any)
+               if (start < viewEnd) { // compare millisecond time (skip any ambig logic)
+                       inverseRanges.push({
+                               start: start,
+                               end: viewEnd
+                       });
+               }
+
+               return inverseRanges;
+       },
+
+
+       sortEventSegs: function(segs) {
+               segs.sort(proxy(this, 'compareEventSegs'));
+       },
+
+
+       // A cmp function for determining which segments should take visual priority
+       compareEventSegs: function(seg1, seg2) {
+               return seg1.eventStartMS - seg2.eventStartMS || // earlier events go first
+                       seg2.eventDurationMS - seg1.eventDurationMS || // tie? longer events go first
+                       seg2.event.allDay - seg1.event.allDay || // tie? put all-day events first (booleans cast to 0/1)
+                       compareByFieldSpecs(seg1.event, seg2.event, this.view.eventOrderSpecs);
+       }
+
+});
+
+
+/* Utilities
+----------------------------------------------------------------------------------------------------------------------*/
+
+
+function isBgEvent(event) { // returns true if background OR inverse-background
+       var rendering = getEventRendering(event);
+       return rendering === 'background' || rendering === 'inverse-background';
+}
+FC.isBgEvent = isBgEvent; // export
+
+
+function isInverseBgEvent(event) {
+       return getEventRendering(event) === 'inverse-background';
+}
+
+
+function getEventRendering(event) {
+       return firstDefined((event.source || {}).rendering, event.rendering);
+}
+
+
+function groupEventsById(events) {
+       var eventsById = {};
+       var i, event;
+
+       for (i = 0; i < events.length; i++) {
+               event = events[i];
+               (eventsById[event._id] || (eventsById[event._id] = [])).push(event);
+       }
+
+       return eventsById;
+}
+
+
+// A cmp function for determining which non-inverted "ranges" (see above) happen earlier
+function compareRanges(range1, range2) {
+       return range1.start - range2.start; // earlier ranges go first
+}
+
+
+/* External-Dragging-Element Data
+----------------------------------------------------------------------------------------------------------------------*/
+
+// Require all HTML5 data-* attributes used by FullCalendar to have this prefix.
+// A value of '' will query attributes like data-event. A value of 'fc' will query attributes like data-fc-event.
+FC.dataAttrPrefix = '';
+
+// Given a jQuery element that might represent a dragged FullCalendar event, returns an intermediate data structure
+// to be used for Event Object creation.
+// A defined `.eventProps`, even when empty, indicates that an event should be created.
+function getDraggedElMeta(el) {
+       var prefix = FC.dataAttrPrefix;
+       var eventProps; // properties for creating the event, not related to date/time
+       var startTime; // a Duration
+       var duration;
+       var stick;
+
+       if (prefix) { prefix += '-'; }
+       eventProps = el.data(prefix + 'event') || null;
+
+       if (eventProps) {
+               if (typeof eventProps === 'object') {
+                       eventProps = $.extend({}, eventProps); // make a copy
+               }
+               else { // something like 1 or true. still signal event creation
+                       eventProps = {};
+               }
+
+               // pluck special-cased date/time properties
+               startTime = eventProps.start;
+               if (startTime == null) { startTime = eventProps.time; } // accept 'time' as well
+               duration = eventProps.duration;
+               stick = eventProps.stick;
+               delete eventProps.start;
+               delete eventProps.time;
+               delete eventProps.duration;
+               delete eventProps.stick;
+       }
+
+       // fallback to standalone attribute values for each of the date/time properties
+       if (startTime == null) { startTime = el.data(prefix + 'start'); }
+       if (startTime == null) { startTime = el.data(prefix + 'time'); } // accept 'time' as well
+       if (duration == null) { duration = el.data(prefix + 'duration'); }
+       if (stick == null) { stick = el.data(prefix + 'stick'); }
+
+       // massage into correct data types
+       startTime = startTime != null ? moment.duration(startTime) : null;
+       duration = duration != null ? moment.duration(duration) : null;
+       stick = Boolean(stick);
+
+       return { eventProps: eventProps, startTime: startTime, duration: duration, stick: stick };
+}
+
+
+;;
+
+/*
+A set of rendering and date-related methods for a visual component comprised of one or more rows of day columns.
+Prerequisite: the object being mixed into needs to be a *Grid*
+*/
+var DayTableMixin = FC.DayTableMixin = {
+
+       breakOnWeeks: false, // should create a new row for each week?
+       dayDates: null, // whole-day dates for each column. left to right
+       dayIndices: null, // for each day from start, the offset
+       daysPerRow: null,
+       rowCnt: null,
+       colCnt: null,
+       colHeadFormat: null,
+
+
+       // Populates internal variables used for date calculation and rendering
+       updateDayTable: function() {
+               var view = this.view;
+               var date = this.start.clone();
+               var dayIndex = -1;
+               var dayIndices = [];
+               var dayDates = [];
+               var daysPerRow;
+               var firstDay;
+               var rowCnt;
+
+               while (date.isBefore(this.end)) { // loop each day from start to end
+                       if (view.isHiddenDay(date)) {
+                               dayIndices.push(dayIndex + 0.5); // mark that it's between indices
+                       }
+                       else {
+                               dayIndex++;
+                               dayIndices.push(dayIndex);
+                               dayDates.push(date.clone());
+                       }
+                       date.add(1, 'days');
+               }
+
+               if (this.breakOnWeeks) {
+                       // count columns until the day-of-week repeats
+                       firstDay = dayDates[0].day();
+                       for (daysPerRow = 1; daysPerRow < dayDates.length; daysPerRow++) {
+                               if (dayDates[daysPerRow].day() == firstDay) {
+                                       break;
+                               }
+                       }
+                       rowCnt = Math.ceil(dayDates.length / daysPerRow);
+               }
+               else {
+                       rowCnt = 1;
+                       daysPerRow = dayDates.length;
+               }
+
+               this.dayDates = dayDates;
+               this.dayIndices = dayIndices;
+               this.daysPerRow = daysPerRow;
+               this.rowCnt = rowCnt;
+               
+               this.updateDayTableCols();
+       },
+
+
+       // Computes and assigned the colCnt property and updates any options that may be computed from it
+       updateDayTableCols: function() {
+               this.colCnt = this.computeColCnt();
+               this.colHeadFormat = this.view.opt('columnFormat') || this.computeColHeadFormat();
+       },
+
+
+       // Determines how many columns there should be in the table
+       computeColCnt: function() {
+               return this.daysPerRow;
+       },
+
+
+       // Computes the ambiguously-timed moment for the given cell
+       getCellDate: function(row, col) {
+               return this.dayDates[
+                               this.getCellDayIndex(row, col)
+                       ].clone();
+       },
+
+
+       // Computes the ambiguously-timed date range for the given cell
+       getCellRange: function(row, col) {
+               var start = this.getCellDate(row, col);
+               var end = start.clone().add(1, 'days');
+
+               return { start: start, end: end };
+       },
+
+
+       // Returns the number of day cells, chronologically, from the first of the grid (0-based)
+       getCellDayIndex: function(row, col) {
+               return row * this.daysPerRow + this.getColDayIndex(col);
+       },
+
+
+       // Returns the numner of day cells, chronologically, from the first cell in *any given row*
+       getColDayIndex: function(col) {
+               if (this.isRTL) {
+                       return this.colCnt - 1 - col;
+               }
+               else {
+                       return col;
+               }
+       },
+
+
+       // Given a date, returns its chronolocial cell-index from the first cell of the grid.
+       // If the date lies between cells (because of hiddenDays), returns a floating-point value between offsets.
+       // If before the first offset, returns a negative number.
+       // If after the last offset, returns an offset past the last cell offset.
+       // Only works for *start* dates of cells. Will not work for exclusive end dates for cells.
+       getDateDayIndex: function(date) {
+               var dayIndices = this.dayIndices;
+               var dayOffset = date.diff(this.start, 'days');
+
+               if (dayOffset < 0) {
+                       return dayIndices[0] - 1;
+               }
+               else if (dayOffset >= dayIndices.length) {
+                       return dayIndices[dayIndices.length - 1] + 1;
+               }
+               else {
+                       return dayIndices[dayOffset];
+               }
+       },
+
+
+       /* Options
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes a default column header formatting string if `colFormat` is not explicitly defined
+       computeColHeadFormat: function() {
+               // if more than one week row, or if there are a lot of columns with not much space,
+               // put just the day numbers will be in each cell
+               if (this.rowCnt > 1 || this.colCnt > 10) {
+                       return 'ddd'; // "Sat"
+               }
+               // multiple days, so full single date string WON'T be in title text
+               else if (this.colCnt > 1) {
+                       return this.view.opt('dayOfMonthFormat'); // "Sat 12/10"
+               }
+               // single day, so full single date string will probably be in title text
+               else {
+                       return 'dddd'; // "Saturday"
+               }
+       },
+
+
+       /* Slicing
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Slices up a date range into a segment for every week-row it intersects with
+       sliceRangeByRow: function(range) {
+               var daysPerRow = this.daysPerRow;
+               var normalRange = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold
+               var rangeFirst = this.getDateDayIndex(normalRange.start); // inclusive first index
+               var rangeLast = this.getDateDayIndex(normalRange.end.clone().subtract(1, 'days')); // inclusive last index
+               var segs = [];
+               var row;
+               var rowFirst, rowLast; // inclusive day-index range for current row
+               var segFirst, segLast; // inclusive day-index range for segment
+
+               for (row = 0; row < this.rowCnt; row++) {
+                       rowFirst = row * daysPerRow;
+                       rowLast = rowFirst + daysPerRow - 1;
+
+                       // intersect segment's offset range with the row's
+                       segFirst = Math.max(rangeFirst, rowFirst);
+                       segLast = Math.min(rangeLast, rowLast);
+
+                       // deal with in-between indices
+                       segFirst = Math.ceil(segFirst); // in-between starts round to next cell
+                       segLast = Math.floor(segLast); // in-between ends round to prev cell
+
+                       if (segFirst <= segLast) { // was there any intersection with the current row?
+                               segs.push({
+                                       row: row,
+
+                                       // normalize to start of row
+                                       firstRowDayIndex: segFirst - rowFirst,
+                                       lastRowDayIndex: segLast - rowFirst,
+
+                                       // must be matching integers to be the segment's start/end
+                                       isStart: segFirst === rangeFirst,
+                                       isEnd: segLast === rangeLast
+                               });
+                       }
+               }
+
+               return segs;
+       },
+
+
+       // Slices up a date range into a segment for every day-cell it intersects with.
+       // TODO: make more DRY with sliceRangeByRow somehow.
+       sliceRangeByDay: function(range) {
+               var daysPerRow = this.daysPerRow;
+               var normalRange = this.view.computeDayRange(range); // make whole-day range, considering nextDayThreshold
+               var rangeFirst = this.getDateDayIndex(normalRange.start); // inclusive first index
+               var rangeLast = this.getDateDayIndex(normalRange.end.clone().subtract(1, 'days')); // inclusive last index
+               var segs = [];
+               var row;
+               var rowFirst, rowLast; // inclusive day-index range for current row
+               var i;
+               var segFirst, segLast; // inclusive day-index range for segment
+
+               for (row = 0; row < this.rowCnt; row++) {
+                       rowFirst = row * daysPerRow;
+                       rowLast = rowFirst + daysPerRow - 1;
+
+                       for (i = rowFirst; i <= rowLast; i++) {
+
+                               // intersect segment's offset range with the row's
+                               segFirst = Math.max(rangeFirst, i);
+                               segLast = Math.min(rangeLast, i);
+
+                               // deal with in-between indices
+                               segFirst = Math.ceil(segFirst); // in-between starts round to next cell
+                               segLast = Math.floor(segLast); // in-between ends round to prev cell
+
+                               if (segFirst <= segLast) { // was there any intersection with the current row?
+                                       segs.push({
+                                               row: row,
+
+                                               // normalize to start of row
+                                               firstRowDayIndex: segFirst - rowFirst,
+                                               lastRowDayIndex: segLast - rowFirst,
+
+                                               // must be matching integers to be the segment's start/end
+                                               isStart: segFirst === rangeFirst,
+                                               isEnd: segLast === rangeLast
+                                       });
+                               }
+                       }
+               }
+
+               return segs;
+       },
+
+
+       /* Header Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderHeadHtml: function() {
+               var view = this.view;
+
+               return '' +
+                       '<div class="fc-row ' + view.widgetHeaderClass + '">' +
+                               '<table>' +
+                                       '<thead>' +
+                                               this.renderHeadTrHtml() +
+                                       '</thead>' +
+                               '</table>' +
+                       '</div>';
+       },
+
+
+       renderHeadIntroHtml: function() {
+               return this.renderIntroHtml(); // fall back to generic
+       },
+
+
+       renderHeadTrHtml: function() {
+               return '' +
+                       '<tr>' +
+                               (this.isRTL ? '' : this.renderHeadIntroHtml()) +
+                               this.renderHeadDateCellsHtml() +
+                               (this.isRTL ? this.renderHeadIntroHtml() : '') +
+                       '</tr>';
+       },
+
+
+       renderHeadDateCellsHtml: function() {
+               var htmls = [];
+               var col, date;
+
+               for (col = 0; col < this.colCnt; col++) {
+                       date = this.getCellDate(0, col);
+                       htmls.push(this.renderHeadDateCellHtml(date));
+               }
+
+               return htmls.join('');
+       },
+
+
+       // TODO: when internalApiVersion, accept an object for HTML attributes
+       // (colspan should be no different)
+       renderHeadDateCellHtml: function(date, colspan, otherAttrs) {
+               var view = this.view;
+
+               return '' +
+                       '<th class="fc-day-header ' + view.widgetHeaderClass + ' fc-' + dayIDs[date.day()] + '"' +
+                               (this.rowCnt == 1 ?
+                                       ' data-date="' + date.format('YYYY-MM-DD') + '"' :
+                                       '') +
+                               (colspan > 1 ?
+                                       ' colspan="' + colspan + '"' :
+                                       '') +
+                               (otherAttrs ?
+                                       ' ' + otherAttrs :
+                                       '') +
+                       '>' +
+                               htmlEscape(date.format(this.colHeadFormat)) +
+                       '</th>';
+       },
+
+
+       /* Background Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderBgTrHtml: function(row) {
+               return '' +
+                       '<tr>' +
+                               (this.isRTL ? '' : this.renderBgIntroHtml(row)) +
+                               this.renderBgCellsHtml(row) +
+                               (this.isRTL ? this.renderBgIntroHtml(row) : '') +
+                       '</tr>';
+       },
+
+
+       renderBgIntroHtml: function(row) {
+               return this.renderIntroHtml(); // fall back to generic
+       },
+
+
+       renderBgCellsHtml: function(row) {
+               var htmls = [];
+               var col, date;
+
+               for (col = 0; col < this.colCnt; col++) {
+                       date = this.getCellDate(row, col);
+                       htmls.push(this.renderBgCellHtml(date));
+               }
+
+               return htmls.join('');
+       },
+
+
+       renderBgCellHtml: function(date, otherAttrs) {
+               var view = this.view;
+               var classes = this.getDayClasses(date);
+
+               classes.unshift('fc-day', view.widgetContentClass);
+
+               return '<td class="' + classes.join(' ') + '"' +
+                       ' data-date="' + date.format('YYYY-MM-DD') + '"' + // if date has a time, won't format it
+                       (otherAttrs ?
+                               ' ' + otherAttrs :
+                               '') +
+                       '></td>';
+       },
+
+
+       /* Generic
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Generates the default HTML intro for any row. User classes should override
+       renderIntroHtml: function() {
+       },
+
+
+       // TODO: a generic method for dealing with <tr>, RTL, intro
+       // when increment internalApiVersion
+       // wrapTr (scheduler)
+
+
+       /* Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Applies the generic "intro" and "outro" HTML to the given cells.
+       // Intro means the leftmost cell when the calendar is LTR and the rightmost cell when RTL. Vice-versa for outro.
+       bookendCells: function(trEl) {
+               var introHtml = this.renderIntroHtml();
+
+               if (introHtml) {
+                       if (this.isRTL) {
+                               trEl.append(introHtml);
+                       }
+                       else {
+                               trEl.prepend(introHtml);
+                       }
+               }
+       }
+
+};
+
+;;
+
+/* A component that renders a grid of whole-days that runs horizontally. There can be multiple rows, one per week.
+----------------------------------------------------------------------------------------------------------------------*/
+
+var DayGrid = FC.DayGrid = Grid.extend(DayTableMixin, {
+
+       numbersVisible: false, // should render a row for day/week numbers? set by outside view. TODO: make internal
+       bottomCoordPadding: 0, // hack for extending the hit area for the last row of the coordinate grid
+
+       rowEls: null, // set of fake row elements
+       cellEls: null, // set of whole-day elements comprising the row's background
+       helperEls: null, // set of cell skeleton elements for rendering the mock event "helper"
+
+       rowCoordCache: null,
+       colCoordCache: null,
+
+
+       // Renders the rows and columns into the component's `this.el`, which should already be assigned.
+       // isRigid determins whether the individual rows should ignore the contents and be a constant height.
+       // Relies on the view's colCnt and rowCnt. In the future, this component should probably be self-sufficient.
+       renderDates: function(isRigid) {
+               var view = this.view;
+               var rowCnt = this.rowCnt;
+               var colCnt = this.colCnt;
+               var html = '';
+               var row;
+               var col;
+
+               for (row = 0; row < rowCnt; row++) {
+                       html += this.renderDayRowHtml(row, isRigid);
+               }
+               this.el.html(html);
+
+               this.rowEls = this.el.find('.fc-row');
+               this.cellEls = this.el.find('.fc-day');
+
+               this.rowCoordCache = new CoordCache({
+                       els: this.rowEls,
+                       isVertical: true
+               });
+               this.colCoordCache = new CoordCache({
+                       els: this.cellEls.slice(0, this.colCnt), // only the first row
+                       isHorizontal: true
+               });
+
+               // trigger dayRender with each cell's element
+               for (row = 0; row < rowCnt; row++) {
+                       for (col = 0; col < colCnt; col++) {
+                               view.trigger(
+                                       'dayRender',
+                                       null,
+                                       this.getCellDate(row, col),
+                                       this.getCellEl(row, col)
+                               );
+                       }
+               }
+       },
+
+
+       unrenderDates: function() {
+               this.removeSegPopover();
+       },
+
+
+       renderBusinessHours: function() {
+               var events = this.view.calendar.getBusinessHoursEvents(true); // wholeDay=true
+               var segs = this.eventsToSegs(events);
+
+               this.renderFill('businessHours', segs, 'bgevent');
+       },
+
+
+       // Generates the HTML for a single row, which is a div that wraps a table.
+       // `row` is the row number.
+       renderDayRowHtml: function(row, isRigid) {
+               var view = this.view;
+               var classes = [ 'fc-row', 'fc-week', view.widgetContentClass ];
+
+               if (isRigid) {
+                       classes.push('fc-rigid');
+               }
+
+               return '' +
+                       '<div class="' + classes.join(' ') + '">' +
+                               '<div class="fc-bg">' +
+                                       '<table>' +
+                                               this.renderBgTrHtml(row) +
+                                       '</table>' +
+                               '</div>' +
+                               '<div class="fc-content-skeleton">' +
+                                       '<table>' +
+                                               (this.numbersVisible ?
+                                                       '<thead>' +
+                                                               this.renderNumberTrHtml(row) +
+                                                       '</thead>' :
+                                                       ''
+                                                       ) +
+                                       '</table>' +
+                               '</div>' +
+                       '</div>';
+       },
+
+
+       /* Grid Number Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderNumberTrHtml: function(row) {
+               return '' +
+                       '<tr>' +
+                               (this.isRTL ? '' : this.renderNumberIntroHtml(row)) +
+                               this.renderNumberCellsHtml(row) +
+                               (this.isRTL ? this.renderNumberIntroHtml(row) : '') +
+                       '</tr>';
+       },
+
+
+       renderNumberIntroHtml: function(row) {
+               return this.renderIntroHtml();
+       },
+
+
+       renderNumberCellsHtml: function(row) {
+               var htmls = [];
+               var col, date;
+
+               for (col = 0; col < this.colCnt; col++) {
+                       date = this.getCellDate(row, col);
+                       htmls.push(this.renderNumberCellHtml(date));
+               }
+
+               return htmls.join('');
+       },
+
+
+       // Generates the HTML for the <td>s of the "number" row in the DayGrid's content skeleton.
+       // The number row will only exist if either day numbers or week numbers are turned on.
+       renderNumberCellHtml: function(date) {
+               var classes;
+
+               if (!this.view.dayNumbersVisible) { // if there are week numbers but not day numbers
+                       return '<td/>'; //  will create an empty space above events :(
+               }
+
+               classes = this.getDayClasses(date);
+               classes.unshift('fc-day-number');
+
+               return '' +
+                       '<td class="' + classes.join(' ') + '" data-date="' + date.format() + '">' +
+                               date.date() +
+                       '</td>';
+       },
+
+
+       /* Options
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes a default event time formatting string if `timeFormat` is not explicitly defined
+       computeEventTimeFormat: function() {
+               return this.view.opt('extraSmallTimeFormat'); // like "6p" or "6:30p"
+       },
+
+
+       // Computes a default `displayEventEnd` value if one is not expliclty defined
+       computeDisplayEventEnd: function() {
+               return this.colCnt == 1; // we'll likely have space if there's only one day
+       },
+
+
+       /* Dates
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       rangeUpdated: function() {
+               this.updateDayTable();
+       },
+
+
+       // Slices up the given span (unzoned start/end with other misc data) into an array of segments
+       spanToSegs: function(span) {
+               var segs = this.sliceRangeByRow(span);
+               var i, seg;
+
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       if (this.isRTL) {
+                               seg.leftCol = this.daysPerRow - 1 - seg.lastRowDayIndex;
+                               seg.rightCol = this.daysPerRow - 1 - seg.firstRowDayIndex;
+                       }
+                       else {
+                               seg.leftCol = seg.firstRowDayIndex;
+                               seg.rightCol = seg.lastRowDayIndex;
+                       }
+               }
+
+               return segs;
+       },
+
+
+       /* Hit System
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       prepareHits: function() {
+               this.colCoordCache.build();
+               this.rowCoordCache.build();
+               this.rowCoordCache.bottoms[this.rowCnt - 1] += this.bottomCoordPadding; // hack
+       },
+
+
+       releaseHits: function() {
+               this.colCoordCache.clear();
+               this.rowCoordCache.clear();
+       },
+
+
+       queryHit: function(leftOffset, topOffset) {
+               var col = this.colCoordCache.getHorizontalIndex(leftOffset);
+               var row = this.rowCoordCache.getVerticalIndex(topOffset);
+
+               if (row != null && col != null) {
+                       return this.getCellHit(row, col);
+               }
+       },
+
+
+       getHitSpan: function(hit) {
+               return this.getCellRange(hit.row, hit.col);
+       },
+
+
+       getHitEl: function(hit) {
+               return this.getCellEl(hit.row, hit.col);
+       },
+
+
+       /* Cell System
+       ------------------------------------------------------------------------------------------------------------------*/
+       // FYI: the first column is the leftmost column, regardless of date
+
+
+       getCellHit: function(row, col) {
+               return {
+                       row: row,
+                       col: col,
+                       component: this, // needed unfortunately :(
+                       left: this.colCoordCache.getLeftOffset(col),
+                       right: this.colCoordCache.getRightOffset(col),
+                       top: this.rowCoordCache.getTopOffset(row),
+                       bottom: this.rowCoordCache.getBottomOffset(row)
+               };
+       },
+
+
+       getCellEl: function(row, col) {
+               return this.cellEls.eq(row * this.colCnt + col);
+       },
+
+
+       /* Event Drag Visualization
+       ------------------------------------------------------------------------------------------------------------------*/
+       // TODO: move to DayGrid.event, similar to what we did with Grid's drag methods
+
+
+       // Renders a visual indication of an event or external element being dragged.
+       // `eventLocation` has zoned start and end (optional)
+       renderDrag: function(eventLocation, seg) {
+
+               // always render a highlight underneath
+               this.renderHighlight(this.eventToSpan(eventLocation));
+
+               // if a segment from the same calendar but another component is being dragged, render a helper event
+               if (seg && !seg.el.closest(this.el).length) {
+
+                       return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
+               }
+       },
+
+
+       // Unrenders any visual indication of a hovering event
+       unrenderDrag: function() {
+               this.unrenderHighlight();
+               this.unrenderHelper();
+       },
+
+
+       /* Event Resize Visualization
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of an event being resized
+       renderEventResize: function(eventLocation, seg) {
+               this.renderHighlight(this.eventToSpan(eventLocation));
+               return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
+       },
+
+
+       // Unrenders a visual indication of an event being resized
+       unrenderEventResize: function() {
+               this.unrenderHighlight();
+               this.unrenderHelper();
+       },
+
+
+       /* Event Helper
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a mock "helper" event. `sourceSeg` is the associated internal segment object. It can be null.
+       renderHelper: function(event, sourceSeg) {
+               var helperNodes = [];
+               var segs = this.eventToSegs(event);
+               var rowStructs;
+
+               segs = this.renderFgSegEls(segs); // assigns each seg's el and returns a subset of segs that were rendered
+               rowStructs = this.renderSegRows(segs);
+
+               // inject each new event skeleton into each associated row
+               this.rowEls.each(function(row, rowNode) {
+                       var rowEl = $(rowNode); // the .fc-row
+                       var skeletonEl = $('<div class="fc-helper-skeleton"><table/></div>'); // will be absolutely positioned
+                       var skeletonTop;
+
+                       // If there is an original segment, match the top position. Otherwise, put it at the row's top level
+                       if (sourceSeg && sourceSeg.row === row) {
+                               skeletonTop = sourceSeg.el.position().top;
+                       }
+                       else {
+                               skeletonTop = rowEl.find('.fc-content-skeleton tbody').position().top;
+                       }
+
+                       skeletonEl.css('top', skeletonTop)
+                               .find('table')
+                                       .append(rowStructs[row].tbodyEl);
+
+                       rowEl.append(skeletonEl);
+                       helperNodes.push(skeletonEl[0]);
+               });
+
+               return ( // must return the elements rendered
+                       this.helperEls = $(helperNodes) // array -> jQuery set
+               );
+       },
+
+
+       // Unrenders any visual indication of a mock helper event
+       unrenderHelper: function() {
+               if (this.helperEls) {
+                       this.helperEls.remove();
+                       this.helperEls = null;
+               }
+       },
+
+
+       /* Fill System (highlight, background events, business hours)
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       fillSegTag: 'td', // override the default tag name
+
+
+       // Renders a set of rectangles over the given segments of days.
+       // Only returns segments that successfully rendered.
+       renderFill: function(type, segs, className) {
+               var nodes = [];
+               var i, seg;
+               var skeletonEl;
+
+               segs = this.renderFillSegEls(type, segs); // assignes `.el` to each seg. returns successfully rendered segs
+
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       skeletonEl = this.renderFillRow(type, seg, className);
+                       this.rowEls.eq(seg.row).append(skeletonEl);
+                       nodes.push(skeletonEl[0]);
+               }
+
+               this.elsByFill[type] = $(nodes);
+
+               return segs;
+       },
+
+
+       // Generates the HTML needed for one row of a fill. Requires the seg's el to be rendered.
+       renderFillRow: function(type, seg, className) {
+               var colCnt = this.colCnt;
+               var startCol = seg.leftCol;
+               var endCol = seg.rightCol + 1;
+               var skeletonEl;
+               var trEl;
+
+               className = className || type.toLowerCase();
+
+               skeletonEl = $(
+                       '<div class="fc-' + className + '-skeleton">' +
+                               '<table><tr/></table>' +
+                       '</div>'
+               );
+               trEl = skeletonEl.find('tr');
+
+               if (startCol > 0) {
+                       trEl.append('<td colspan="' + startCol + '"/>');
+               }
+
+               trEl.append(
+                       seg.el.attr('colspan', endCol - startCol)
+               );
+
+               if (endCol < colCnt) {
+                       trEl.append('<td colspan="' + (colCnt - endCol) + '"/>');
+               }
+
+               this.bookendCells(trEl);
+
+               return skeletonEl;
+       }
+
+});
+
+;;
+
+/* Event-rendering methods for the DayGrid class
+----------------------------------------------------------------------------------------------------------------------*/
+
+DayGrid.mixin({
+
+       rowStructs: null, // an array of objects, each holding information about a row's foreground event-rendering
+
+
+       // Unrenders all events currently rendered on the grid
+       unrenderEvents: function() {
+               this.removeSegPopover(); // removes the "more.." events popover
+               Grid.prototype.unrenderEvents.apply(this, arguments); // calls the super-method
+       },
+
+
+       // Retrieves all rendered segment objects currently rendered on the grid
+       getEventSegs: function() {
+               return Grid.prototype.getEventSegs.call(this) // get the segments from the super-method
+                       .concat(this.popoverSegs || []); // append the segments from the "more..." popover
+       },
+
+
+       // Renders the given background event segments onto the grid
+       renderBgSegs: function(segs) {
+
+               // don't render timed background events
+               var allDaySegs = $.grep(segs, function(seg) {
+                       return seg.event.allDay;
+               });
+
+               return Grid.prototype.renderBgSegs.call(this, allDaySegs); // call the super-method
+       },
+
+
+       // Renders the given foreground event segments onto the grid
+       renderFgSegs: function(segs) {
+               var rowStructs;
+
+               // render an `.el` on each seg
+               // returns a subset of the segs. segs that were actually rendered
+               segs = this.renderFgSegEls(segs);
+
+               rowStructs = this.rowStructs = this.renderSegRows(segs);
+
+               // append to each row's content skeleton
+               this.rowEls.each(function(i, rowNode) {
+                       $(rowNode).find('.fc-content-skeleton > table').append(
+                               rowStructs[i].tbodyEl
+                       );
+               });
+
+               return segs; // return only the segs that were actually rendered
+       },
+
+
+       // Unrenders all currently rendered foreground event segments
+       unrenderFgSegs: function() {
+               var rowStructs = this.rowStructs || [];
+               var rowStruct;
+
+               while ((rowStruct = rowStructs.pop())) {
+                       rowStruct.tbodyEl.remove();
+               }
+
+               this.rowStructs = null;
+       },
+
+
+       // Uses the given events array to generate <tbody> elements that should be appended to each row's content skeleton.
+       // Returns an array of rowStruct objects (see the bottom of `renderSegRow`).
+       // PRECONDITION: each segment shoud already have a rendered and assigned `.el`
+       renderSegRows: function(segs) {
+               var rowStructs = [];
+               var segRows;
+               var row;
+
+               segRows = this.groupSegRows(segs); // group into nested arrays
+
+               // iterate each row of segment groupings
+               for (row = 0; row < segRows.length; row++) {
+                       rowStructs.push(
+                               this.renderSegRow(row, segRows[row])
+                       );
+               }
+
+               return rowStructs;
+       },
+
+
+       // Builds the HTML to be used for the default element for an individual segment
+       fgSegHtml: function(seg, disableResizing) {
+               var view = this.view;
+               var event = seg.event;
+               var isDraggable = view.isEventDraggable(event);
+               var isResizableFromStart = !disableResizing && event.allDay &&
+                       seg.isStart && view.isEventResizableFromStart(event);
+               var isResizableFromEnd = !disableResizing && event.allDay &&
+                       seg.isEnd && view.isEventResizableFromEnd(event);
+               var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
+               var skinCss = cssToStr(this.getSegSkinCss(seg));
+               var timeHtml = '';
+               var timeText;
+               var titleHtml;
+
+               classes.unshift('fc-day-grid-event', 'fc-h-event');
+
+               // Only display a timed events time if it is the starting segment
+               if (seg.isStart) {
+                       timeText = this.getEventTimeText(event);
+                       if (timeText) {
+                               timeHtml = '<span class="fc-time">' + htmlEscape(timeText) + '</span>';
+                       }
+               }
+
+               titleHtml =
+                       '<span class="fc-title">' +
+                               (htmlEscape(event.title || '') || '&nbsp;') + // we always want one line of height
+                       '</span>';
+               
+               return '<a class="' + classes.join(' ') + '"' +
+                               (event.url ?
+                                       ' href="' + htmlEscape(event.url) + '"' :
+                                       ''
+                                       ) +
+                               (skinCss ?
+                                       ' style="' + skinCss + '"' :
+                                       ''
+                                       ) +
+                       '>' +
+                               '<div class="fc-content">' +
+                                       (this.isRTL ?
+                                               titleHtml + ' ' + timeHtml : // put a natural space in between
+                                               timeHtml + ' ' + titleHtml   //
+                                               ) +
+                               '</div>' +
+                               (isResizableFromStart ?
+                                       '<div class="fc-resizer fc-start-resizer" />' :
+                                       ''
+                                       ) +
+                               (isResizableFromEnd ?
+                                       '<div class="fc-resizer fc-end-resizer" />' :
+                                       ''
+                                       ) +
+                       '</a>';
+       },
+
+
+       // Given a row # and an array of segments all in the same row, render a <tbody> element, a skeleton that contains
+       // the segments. Returns object with a bunch of internal data about how the render was calculated.
+       // NOTE: modifies rowSegs
+       renderSegRow: function(row, rowSegs) {
+               var colCnt = this.colCnt;
+               var segLevels = this.buildSegLevels(rowSegs); // group into sub-arrays of levels
+               var levelCnt = Math.max(1, segLevels.length); // ensure at least one level
+               var tbody = $('<tbody/>');
+               var segMatrix = []; // lookup for which segments are rendered into which level+col cells
+               var cellMatrix = []; // lookup for all <td> elements of the level+col matrix
+               var loneCellMatrix = []; // lookup for <td> elements that only take up a single column
+               var i, levelSegs;
+               var col;
+               var tr;
+               var j, seg;
+               var td;
+
+               // populates empty cells from the current column (`col`) to `endCol`
+               function emptyCellsUntil(endCol) {
+                       while (col < endCol) {
+                               // try to grab a cell from the level above and extend its rowspan. otherwise, create a fresh cell
+                               td = (loneCellMatrix[i - 1] || [])[col];
+                               if (td) {
+                                       td.attr(
+                                               'rowspan',
+                                               parseInt(td.attr('rowspan') || 1, 10) + 1
+                                       );
+                               }
+                               else {
+                                       td = $('<td/>');
+                                       tr.append(td);
+                               }
+                               cellMatrix[i][col] = td;
+                               loneCellMatrix[i][col] = td;
+                               col++;
+                       }
+               }
+
+               for (i = 0; i < levelCnt; i++) { // iterate through all levels
+                       levelSegs = segLevels[i];
+                       col = 0;
+                       tr = $('<tr/>');
+
+                       segMatrix.push([]);
+                       cellMatrix.push([]);
+                       loneCellMatrix.push([]);
+
+                       // levelCnt might be 1 even though there are no actual levels. protect against this.
+                       // this single empty row is useful for styling.
+                       if (levelSegs) {
+                               for (j = 0; j < levelSegs.length; j++) { // iterate through segments in level
+                                       seg = levelSegs[j];
+
+                                       emptyCellsUntil(seg.leftCol);
+
+                                       // create a container that occupies or more columns. append the event element.
+                                       td = $('<td class="fc-event-container"/>').append(seg.el);
+                                       if (seg.leftCol != seg.rightCol) {
+                                               td.attr('colspan', seg.rightCol - seg.leftCol + 1);
+                                       }
+                                       else { // a single-column segment
+                                               loneCellMatrix[i][col] = td;
+                                       }
+
+                                       while (col <= seg.rightCol) {
+                                               cellMatrix[i][col] = td;
+                                               segMatrix[i][col] = seg;
+                                               col++;
+                                       }
+
+                                       tr.append(td);
+                               }
+                       }
+
+                       emptyCellsUntil(colCnt); // finish off the row
+                       this.bookendCells(tr);
+                       tbody.append(tr);
+               }
+
+               return { // a "rowStruct"
+                       row: row, // the row number
+                       tbodyEl: tbody,
+                       cellMatrix: cellMatrix,
+                       segMatrix: segMatrix,
+                       segLevels: segLevels,
+                       segs: rowSegs
+               };
+       },
+
+
+       // Stacks a flat array of segments, which are all assumed to be in the same row, into subarrays of vertical levels.
+       // NOTE: modifies segs
+       buildSegLevels: function(segs) {
+               var levels = [];
+               var i, seg;
+               var j;
+
+               // Give preference to elements with certain criteria, so they have
+               // a chance to be closer to the top.
+               this.sortEventSegs(segs);
+               
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+
+                       // loop through levels, starting with the topmost, until the segment doesn't collide with other segments
+                       for (j = 0; j < levels.length; j++) {
+                               if (!isDaySegCollision(seg, levels[j])) {
+                                       break;
+                               }
+                       }
+                       // `j` now holds the desired subrow index
+                       seg.level = j;
+
+                       // create new level array if needed and append segment
+                       (levels[j] || (levels[j] = [])).push(seg);
+               }
+
+               // order segments left-to-right. very important if calendar is RTL
+               for (j = 0; j < levels.length; j++) {
+                       levels[j].sort(compareDaySegCols);
+               }
+
+               return levels;
+       },
+
+
+       // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's row
+       groupSegRows: function(segs) {
+               var segRows = [];
+               var i;
+
+               for (i = 0; i < this.rowCnt; i++) {
+                       segRows.push([]);
+               }
+
+               for (i = 0; i < segs.length; i++) {
+                       segRows[segs[i].row].push(segs[i]);
+               }
+
+               return segRows;
+       }
+
+});
+
+
+// Computes whether two segments' columns collide. They are assumed to be in the same row.
+function isDaySegCollision(seg, otherSegs) {
+       var i, otherSeg;
+
+       for (i = 0; i < otherSegs.length; i++) {
+               otherSeg = otherSegs[i];
+
+               if (
+                       otherSeg.leftCol <= seg.rightCol &&
+                       otherSeg.rightCol >= seg.leftCol
+               ) {
+                       return true;
+               }
+       }
+
+       return false;
+}
+
+
+// A cmp function for determining the leftmost event
+function compareDaySegCols(a, b) {
+       return a.leftCol - b.leftCol;
+}
+
+;;
+
+/* Methods relate to limiting the number events for a given day on a DayGrid
+----------------------------------------------------------------------------------------------------------------------*/
+// NOTE: all the segs being passed around in here are foreground segs
+
+DayGrid.mixin({
+
+       segPopover: null, // the Popover that holds events that can't fit in a cell. null when not visible
+       popoverSegs: null, // an array of segment objects that the segPopover holds. null when not visible
+
+
+       removeSegPopover: function() {
+               if (this.segPopover) {
+                       this.segPopover.hide(); // in handler, will call segPopover's removeElement
+               }
+       },
+
+
+       // Limits the number of "levels" (vertically stacking layers of events) for each row of the grid.
+       // `levelLimit` can be false (don't limit), a number, or true (should be computed).
+       limitRows: function(levelLimit) {
+               var rowStructs = this.rowStructs || [];
+               var row; // row #
+               var rowLevelLimit;
+
+               for (row = 0; row < rowStructs.length; row++) {
+                       this.unlimitRow(row);
+
+                       if (!levelLimit) {
+                               rowLevelLimit = false;
+                       }
+                       else if (typeof levelLimit === 'number') {
+                               rowLevelLimit = levelLimit;
+                       }
+                       else {
+                               rowLevelLimit = this.computeRowLevelLimit(row);
+                       }
+
+                       if (rowLevelLimit !== false) {
+                               this.limitRow(row, rowLevelLimit);
+                       }
+               }
+       },
+
+
+       // Computes the number of levels a row will accomodate without going outside its bounds.
+       // Assumes the row is "rigid" (maintains a constant height regardless of what is inside).
+       // `row` is the row number.
+       computeRowLevelLimit: function(row) {
+               var rowEl = this.rowEls.eq(row); // the containing "fake" row div
+               var rowHeight = rowEl.height(); // TODO: cache somehow?
+               var trEls = this.rowStructs[row].tbodyEl.children();
+               var i, trEl;
+               var trHeight;
+
+               function iterInnerHeights(i, childNode) {
+                       trHeight = Math.max(trHeight, $(childNode).outerHeight());
+               }
+
+               // Reveal one level <tr> at a time and stop when we find one out of bounds
+               for (i = 0; i < trEls.length; i++) {
+                       trEl = trEls.eq(i).removeClass('fc-limited'); // reset to original state (reveal)
+
+                       // with rowspans>1 and IE8, trEl.outerHeight() would return the height of the largest cell,
+                       // so instead, find the tallest inner content element.
+                       trHeight = 0;
+                       trEl.find('> td > :first-child').each(iterInnerHeights);
+
+                       if (trEl.position().top + trHeight > rowHeight) {
+                               return i;
+                       }
+               }
+
+               return false; // should not limit at all
+       },
+
+
+       // Limits the given grid row to the maximum number of levels and injects "more" links if necessary.
+       // `row` is the row number.
+       // `levelLimit` is a number for the maximum (inclusive) number of levels allowed.
+       limitRow: function(row, levelLimit) {
+               var _this = this;
+               var rowStruct = this.rowStructs[row];
+               var moreNodes = []; // array of "more" <a> links and <td> DOM nodes
+               var col = 0; // col #, left-to-right (not chronologically)
+               var levelSegs; // array of segment objects in the last allowable level, ordered left-to-right
+               var cellMatrix; // a matrix (by level, then column) of all <td> jQuery elements in the row
+               var limitedNodes; // array of temporarily hidden level <tr> and segment <td> DOM nodes
+               var i, seg;
+               var segsBelow; // array of segment objects below `seg` in the current `col`
+               var totalSegsBelow; // total number of segments below `seg` in any of the columns `seg` occupies
+               var colSegsBelow; // array of segment arrays, below seg, one for each column (offset from segs's first column)
+               var td, rowspan;
+               var segMoreNodes; // array of "more" <td> cells that will stand-in for the current seg's cell
+               var j;
+               var moreTd, moreWrap, moreLink;
+
+               // Iterates through empty level cells and places "more" links inside if need be
+               function emptyCellsUntil(endCol) { // goes from current `col` to `endCol`
+                       while (col < endCol) {
+                               segsBelow = _this.getCellSegs(row, col, levelLimit);
+                               if (segsBelow.length) {
+                                       td = cellMatrix[levelLimit - 1][col];
+                                       moreLink = _this.renderMoreLink(row, col, segsBelow);
+                                       moreWrap = $('<div/>').append(moreLink);
+                                       td.append(moreWrap);
+                                       moreNodes.push(moreWrap[0]);
+                               }
+                               col++;
+                       }
+               }
+
+               if (levelLimit && levelLimit < rowStruct.segLevels.length) { // is it actually over the limit?
+                       levelSegs = rowStruct.segLevels[levelLimit - 1];
+                       cellMatrix = rowStruct.cellMatrix;
+
+                       limitedNodes = rowStruct.tbodyEl.children().slice(levelLimit) // get level <tr> elements past the limit
+                               .addClass('fc-limited').get(); // hide elements and get a simple DOM-nodes array
+
+                       // iterate though segments in the last allowable level
+                       for (i = 0; i < levelSegs.length; i++) {
+                               seg = levelSegs[i];
+                               emptyCellsUntil(seg.leftCol); // process empty cells before the segment
+
+                               // determine *all* segments below `seg` that occupy the same columns
+                               colSegsBelow = [];
+                               totalSegsBelow = 0;
+                               while (col <= seg.rightCol) {
+                                       segsBelow = this.getCellSegs(row, col, levelLimit);
+                                       colSegsBelow.push(segsBelow);
+                                       totalSegsBelow += segsBelow.length;
+                                       col++;
+                               }
+
+                               if (totalSegsBelow) { // do we need to replace this segment with one or many "more" links?
+                                       td = cellMatrix[levelLimit - 1][seg.leftCol]; // the segment's parent cell
+                                       rowspan = td.attr('rowspan') || 1;
+                                       segMoreNodes = [];
+
+                                       // make a replacement <td> for each column the segment occupies. will be one for each colspan
+                                       for (j = 0; j < colSegsBelow.length; j++) {
+                                               moreTd = $('<td class="fc-more-cell"/>').attr('rowspan', rowspan);
+                                               segsBelow = colSegsBelow[j];
+                                               moreLink = this.renderMoreLink(
+                                                       row,
+                                                       seg.leftCol + j,
+                                                       [ seg ].concat(segsBelow) // count seg as hidden too
+                                               );
+                                               moreWrap = $('<div/>').append(moreLink);
+                                               moreTd.append(moreWrap);
+                                               segMoreNodes.push(moreTd[0]);
+                                               moreNodes.push(moreTd[0]);
+                                       }
+
+                                       td.addClass('fc-limited').after($(segMoreNodes)); // hide original <td> and inject replacements
+                                       limitedNodes.push(td[0]);
+                               }
+                       }
+
+                       emptyCellsUntil(this.colCnt); // finish off the level
+                       rowStruct.moreEls = $(moreNodes); // for easy undoing later
+                       rowStruct.limitedEls = $(limitedNodes); // for easy undoing later
+               }
+       },
+
+
+       // Reveals all levels and removes all "more"-related elements for a grid's row.
+       // `row` is a row number.
+       unlimitRow: function(row) {
+               var rowStruct = this.rowStructs[row];
+
+               if (rowStruct.moreEls) {
+                       rowStruct.moreEls.remove();
+                       rowStruct.moreEls = null;
+               }
+
+               if (rowStruct.limitedEls) {
+                       rowStruct.limitedEls.removeClass('fc-limited');
+                       rowStruct.limitedEls = null;
+               }
+       },
+
+
+       // Renders an <a> element that represents hidden event element for a cell.
+       // Responsible for attaching click handler as well.
+       renderMoreLink: function(row, col, hiddenSegs) {
+               var _this = this;
+               var view = this.view;
+
+               return $('<a class="fc-more"/>')
+                       .text(
+                               this.getMoreLinkText(hiddenSegs.length)
+                       )
+                       .on('click', function(ev) {
+                               var clickOption = view.opt('eventLimitClick');
+                               var date = _this.getCellDate(row, col);
+                               var moreEl = $(this);
+                               var dayEl = _this.getCellEl(row, col);
+                               var allSegs = _this.getCellSegs(row, col);
+
+                               // rescope the segments to be within the cell's date
+                               var reslicedAllSegs = _this.resliceDaySegs(allSegs, date);
+                               var reslicedHiddenSegs = _this.resliceDaySegs(hiddenSegs, date);
+
+                               if (typeof clickOption === 'function') {
+                                       // the returned value can be an atomic option
+                                       clickOption = view.trigger('eventLimitClick', null, {
+                                               date: date,
+                                               dayEl: dayEl,
+                                               moreEl: moreEl,
+                                               segs: reslicedAllSegs,
+                                               hiddenSegs: reslicedHiddenSegs
+                                       }, ev);
+                               }
+
+                               if (clickOption === 'popover') {
+                                       _this.showSegPopover(row, col, moreEl, reslicedAllSegs);
+                               }
+                               else if (typeof clickOption === 'string') { // a view name
+                                       view.calendar.zoomTo(date, clickOption);
+                               }
+                       });
+       },
+
+
+       // Reveals the popover that displays all events within a cell
+       showSegPopover: function(row, col, moreLink, segs) {
+               var _this = this;
+               var view = this.view;
+               var moreWrap = moreLink.parent(); // the <div> wrapper around the <a>
+               var topEl; // the element we want to match the top coordinate of
+               var options;
+
+               if (this.rowCnt == 1) {
+                       topEl = view.el; // will cause the popover to cover any sort of header
+               }
+               else {
+                       topEl = this.rowEls.eq(row); // will align with top of row
+               }
+
+               options = {
+                       className: 'fc-more-popover',
+                       content: this.renderSegPopoverContent(row, col, segs),
+                       parentEl: this.el,
+                       top: topEl.offset().top,
+                       autoHide: true, // when the user clicks elsewhere, hide the popover
+                       viewportConstrain: view.opt('popoverViewportConstrain'),
+                       hide: function() {
+                               // kill everything when the popover is hidden
+                               _this.segPopover.removeElement();
+                               _this.segPopover = null;
+                               _this.popoverSegs = null;
+                       }
+               };
+
+               // Determine horizontal coordinate.
+               // We use the moreWrap instead of the <td> to avoid border confusion.
+               if (this.isRTL) {
+                       options.right = moreWrap.offset().left + moreWrap.outerWidth() + 1; // +1 to be over cell border
+               }
+               else {
+                       options.left = moreWrap.offset().left - 1; // -1 to be over cell border
+               }
+
+               this.segPopover = new Popover(options);
+               this.segPopover.show();
+       },
+
+
+       // Builds the inner DOM contents of the segment popover
+       renderSegPopoverContent: function(row, col, segs) {
+               var view = this.view;
+               var isTheme = view.opt('theme');
+               var title = this.getCellDate(row, col).format(view.opt('dayPopoverFormat'));
+               var content = $(
+                       '<div class="fc-header ' + view.widgetHeaderClass + '">' +
+                               '<span class="fc-close ' +
+                                       (isTheme ? 'ui-icon ui-icon-closethick' : 'fc-icon fc-icon-x') +
+                               '"></span>' +
+                               '<span class="fc-title">' +
+                                       htmlEscape(title) +
+                               '</span>' +
+                               '<div class="fc-clear"/>' +
+                       '</div>' +
+                       '<div class="fc-body ' + view.widgetContentClass + '">' +
+                               '<div class="fc-event-container"></div>' +
+                       '</div>'
+               );
+               var segContainer = content.find('.fc-event-container');
+               var i;
+
+               // render each seg's `el` and only return the visible segs
+               segs = this.renderFgSegEls(segs, true); // disableResizing=true
+               this.popoverSegs = segs;
+
+               for (i = 0; i < segs.length; i++) {
+
+                       // because segments in the popover are not part of a grid coordinate system, provide a hint to any
+                       // grids that want to do drag-n-drop about which cell it came from
+                       this.prepareHits();
+                       segs[i].hit = this.getCellHit(row, col);
+                       this.releaseHits();
+
+                       segContainer.append(segs[i].el);
+               }
+
+               return content;
+       },
+
+
+       // Given the events within an array of segment objects, reslice them to be in a single day
+       resliceDaySegs: function(segs, dayDate) {
+
+               // build an array of the original events
+               var events = $.map(segs, function(seg) {
+                       return seg.event;
+               });
+
+               var dayStart = dayDate.clone();
+               var dayEnd = dayStart.clone().add(1, 'days');
+               var dayRange = { start: dayStart, end: dayEnd };
+
+               // slice the events with a custom slicing function
+               segs = this.eventsToSegs(
+                       events,
+                       function(range) {
+                               var seg = intersectRanges(range, dayRange); // undefind if no intersection
+                               return seg ? [ seg ] : []; // must return an array of segments
+                       }
+               );
+
+               // force an order because eventsToSegs doesn't guarantee one
+               this.sortEventSegs(segs);
+
+               return segs;
+       },
+
+
+       // Generates the text that should be inside a "more" link, given the number of events it represents
+       getMoreLinkText: function(num) {
+               var opt = this.view.opt('eventLimitText');
+
+               if (typeof opt === 'function') {
+                       return opt(num);
+               }
+               else {
+                       return '+' + num + ' ' + opt;
+               }
+       },
+
+
+       // Returns segments within a given cell.
+       // If `startLevel` is specified, returns only events including and below that level. Otherwise returns all segs.
+       getCellSegs: function(row, col, startLevel) {
+               var segMatrix = this.rowStructs[row].segMatrix;
+               var level = startLevel || 0;
+               var segs = [];
+               var seg;
+
+               while (level < segMatrix.length) {
+                       seg = segMatrix[level][col];
+                       if (seg) {
+                               segs.push(seg);
+                       }
+                       level++;
+               }
+
+               return segs;
+       }
+
+});
+
+;;
+
+/* A component that renders one or more columns of vertical time slots
+----------------------------------------------------------------------------------------------------------------------*/
+// We mixin DayTable, even though there is only a single row of days
+
+var TimeGrid = FC.TimeGrid = Grid.extend(DayTableMixin, {
+
+       slotDuration: null, // duration of a "slot", a distinct time segment on given day, visualized by lines
+       snapDuration: null, // granularity of time for dragging and selecting
+       snapsPerSlot: null,
+       minTime: null, // Duration object that denotes the first visible time of any given day
+       maxTime: null, // Duration object that denotes the exclusive visible end time of any given day
+       labelFormat: null, // formatting string for times running along vertical axis
+       labelInterval: null, // duration of how often a label should be displayed for a slot
+
+       colEls: null, // cells elements in the day-row background
+       slatContainerEl: null, // div that wraps all the slat rows
+       slatEls: null, // elements running horizontally across all columns
+       nowIndicatorEls: null,
+
+       colCoordCache: null,
+       slatCoordCache: null,
+
+
+       constructor: function() {
+               Grid.apply(this, arguments); // call the super-constructor
+
+               this.processOptions();
+       },
+
+
+       // Renders the time grid into `this.el`, which should already be assigned.
+       // Relies on the view's colCnt. In the future, this component should probably be self-sufficient.
+       renderDates: function() {
+               this.el.html(this.renderHtml());
+               this.colEls = this.el.find('.fc-day');
+               this.slatContainerEl = this.el.find('.fc-slats');
+               this.slatEls = this.slatContainerEl.find('tr');
+
+               this.colCoordCache = new CoordCache({
+                       els: this.colEls,
+                       isHorizontal: true
+               });
+               this.slatCoordCache = new CoordCache({
+                       els: this.slatEls,
+                       isVertical: true
+               });
+
+               this.renderContentSkeleton();
+       },
+
+
+       // Renders the basic HTML skeleton for the grid
+       renderHtml: function() {
+               return '' +
+                       '<div class="fc-bg">' +
+                               '<table>' +
+                                       this.renderBgTrHtml(0) + // row=0
+                               '</table>' +
+                       '</div>' +
+                       '<div class="fc-slats">' +
+                               '<table>' +
+                                       this.renderSlatRowHtml() +
+                               '</table>' +
+                       '</div>';
+       },
+
+
+       // Generates the HTML for the horizontal "slats" that run width-wise. Has a time axis on a side. Depends on RTL.
+       renderSlatRowHtml: function() {
+               var view = this.view;
+               var isRTL = this.isRTL;
+               var html = '';
+               var slotTime = moment.duration(+this.minTime); // wish there was .clone() for durations
+               var slotDate; // will be on the view's first day, but we only care about its time
+               var isLabeled;
+               var axisHtml;
+
+               // Calculate the time for each slot
+               while (slotTime < this.maxTime) {
+                       slotDate = this.start.clone().time(slotTime);
+                       isLabeled = isInt(divideDurationByDuration(slotTime, this.labelInterval));
+
+                       axisHtml =
+                               '<td class="fc-axis fc-time ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' +
+                                       (isLabeled ?
+                                               '<span>' + // for matchCellWidths
+                                                       htmlEscape(slotDate.format(this.labelFormat)) +
+                                               '</span>' :
+                                               ''
+                                               ) +
+                               '</td>';
+
+                       html +=
+                               '<tr data-time="' + slotDate.format('HH:mm:ss') + '"' +
+                                       (isLabeled ? '' : ' class="fc-minor"') +
+                                       '>' +
+                                       (!isRTL ? axisHtml : '') +
+                                       '<td class="' + view.widgetContentClass + '"/>' +
+                                       (isRTL ? axisHtml : '') +
+                               "</tr>";
+
+                       slotTime.add(this.slotDuration);
+               }
+
+               return html;
+       },
+
+
+       /* Options
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Parses various options into properties of this object
+       processOptions: function() {
+               var view = this.view;
+               var slotDuration = view.opt('slotDuration');
+               var snapDuration = view.opt('snapDuration');
+               var input;
+
+               slotDuration = moment.duration(slotDuration);
+               snapDuration = snapDuration ? moment.duration(snapDuration) : slotDuration;
+
+               this.slotDuration = slotDuration;
+               this.snapDuration = snapDuration;
+               this.snapsPerSlot = slotDuration / snapDuration; // TODO: ensure an integer multiple?
+
+               this.minResizeDuration = snapDuration; // hack
+
+               this.minTime = moment.duration(view.opt('minTime'));
+               this.maxTime = moment.duration(view.opt('maxTime'));
+
+               // might be an array value (for TimelineView).
+               // if so, getting the most granular entry (the last one probably).
+               input = view.opt('slotLabelFormat');
+               if ($.isArray(input)) {
+                       input = input[input.length - 1];
+               }
+
+               this.labelFormat =
+                       input ||
+                       view.opt('axisFormat') || // deprecated
+                       view.opt('smallTimeFormat'); // the computed default
+
+               input = view.opt('slotLabelInterval');
+               this.labelInterval = input ?
+                       moment.duration(input) :
+                       this.computeLabelInterval(slotDuration);
+       },
+
+
+       // Computes an automatic value for slotLabelInterval
+       computeLabelInterval: function(slotDuration) {
+               var i;
+               var labelInterval;
+               var slotsPerLabel;
+
+               // find the smallest stock label interval that results in more than one slots-per-label
+               for (i = AGENDA_STOCK_SUB_DURATIONS.length - 1; i >= 0; i--) {
+                       labelInterval = moment.duration(AGENDA_STOCK_SUB_DURATIONS[i]);
+                       slotsPerLabel = divideDurationByDuration(labelInterval, slotDuration);
+                       if (isInt(slotsPerLabel) && slotsPerLabel > 1) {
+                               return labelInterval;
+                       }
+               }
+
+               return moment.duration(slotDuration); // fall back. clone
+       },
+
+
+       // Computes a default event time formatting string if `timeFormat` is not explicitly defined
+       computeEventTimeFormat: function() {
+               return this.view.opt('noMeridiemTimeFormat'); // like "6:30" (no AM/PM)
+       },
+
+
+       // Computes a default `displayEventEnd` value if one is not expliclty defined
+       computeDisplayEventEnd: function() {
+               return true;
+       },
+
+
+       /* Hit System
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       prepareHits: function() {
+               this.colCoordCache.build();
+               this.slatCoordCache.build();
+       },
+
+
+       releaseHits: function() {
+               this.colCoordCache.clear();
+               // NOTE: don't clear slatCoordCache because we rely on it for computeTimeTop
+       },
+
+
+       queryHit: function(leftOffset, topOffset) {
+               var snapsPerSlot = this.snapsPerSlot;
+               var colCoordCache = this.colCoordCache;
+               var slatCoordCache = this.slatCoordCache;
+               var colIndex = colCoordCache.getHorizontalIndex(leftOffset);
+               var slatIndex = slatCoordCache.getVerticalIndex(topOffset);
+
+               if (colIndex != null && slatIndex != null) {
+                       var slatTop = slatCoordCache.getTopOffset(slatIndex);
+                       var slatHeight = slatCoordCache.getHeight(slatIndex);
+                       var partial = (topOffset - slatTop) / slatHeight; // floating point number between 0 and 1
+                       var localSnapIndex = Math.floor(partial * snapsPerSlot); // the snap # relative to start of slat
+                       var snapIndex = slatIndex * snapsPerSlot + localSnapIndex;
+                       var snapTop = slatTop + (localSnapIndex / snapsPerSlot) * slatHeight;
+                       var snapBottom = slatTop + ((localSnapIndex + 1) / snapsPerSlot) * slatHeight;
+
+                       return {
+                               col: colIndex,
+                               snap: snapIndex,
+                               component: this, // needed unfortunately :(
+                               left: colCoordCache.getLeftOffset(colIndex),
+                               right: colCoordCache.getRightOffset(colIndex),
+                               top: snapTop,
+                               bottom: snapBottom
+                       };
+               }
+       },
+
+
+       getHitSpan: function(hit) {
+               var start = this.getCellDate(0, hit.col); // row=0
+               var time = this.computeSnapTime(hit.snap); // pass in the snap-index
+               var end;
+
+               start.time(time);
+               end = start.clone().add(this.snapDuration);
+
+               return { start: start, end: end };
+       },
+
+
+       getHitEl: function(hit) {
+               return this.colEls.eq(hit.col);
+       },
+
+
+       /* Dates
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       rangeUpdated: function() {
+               this.updateDayTable();
+       },
+
+
+       // Given a row number of the grid, representing a "snap", returns a time (Duration) from its start-of-day
+       computeSnapTime: function(snapIndex) {
+               return moment.duration(this.minTime + this.snapDuration * snapIndex);
+       },
+
+
+       // Slices up the given span (unzoned start/end with other misc data) into an array of segments
+       spanToSegs: function(span) {
+               var segs = this.sliceRangeByTimes(span);
+               var i;
+
+               for (i = 0; i < segs.length; i++) {
+                       if (this.isRTL) {
+                               segs[i].col = this.daysPerRow - 1 - segs[i].dayIndex;
+                       }
+                       else {
+                               segs[i].col = segs[i].dayIndex;
+                       }
+               }
+
+               return segs;
+       },
+
+
+       sliceRangeByTimes: function(range) {
+               var segs = [];
+               var seg;
+               var dayIndex;
+               var dayDate;
+               var dayRange;
+
+               for (dayIndex = 0; dayIndex < this.daysPerRow; dayIndex++) {
+                       dayDate = this.dayDates[dayIndex].clone(); // TODO: better API for this?
+                       dayRange = {
+                               start: dayDate.clone().time(this.minTime),
+                               end: dayDate.clone().time(this.maxTime)
+                       };
+                       seg = intersectRanges(range, dayRange); // both will be ambig timezone
+                       if (seg) {
+                               seg.dayIndex = dayIndex;
+                               segs.push(seg);
+                       }
+               }
+
+               return segs;
+       },
+
+
+       /* Coordinates
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       updateSize: function(isResize) { // NOT a standard Grid method
+               this.slatCoordCache.build();
+
+               if (isResize) {
+                       this.updateSegVerticals(
+                               [].concat(this.fgSegs || [], this.bgSegs || [], this.businessSegs || [])
+                       );
+               }
+       },
+
+
+       getTotalSlatHeight: function() {
+               return this.slatContainerEl.outerHeight();
+       },
+
+
+       // Computes the top coordinate, relative to the bounds of the grid, of the given date.
+       // A `startOfDayDate` must be given for avoiding ambiguity over how to treat midnight.
+       computeDateTop: function(date, startOfDayDate) {
+               return this.computeTimeTop(
+                       moment.duration(
+                               date - startOfDayDate.clone().stripTime()
+                       )
+               );
+       },
+
+
+       // Computes the top coordinate, relative to the bounds of the grid, of the given time (a Duration).
+       computeTimeTop: function(time) {
+               var len = this.slatEls.length;
+               var slatCoverage = (time - this.minTime) / this.slotDuration; // floating-point value of # of slots covered
+               var slatIndex;
+               var slatRemainder;
+
+               // compute a floating-point number for how many slats should be progressed through.
+               // from 0 to number of slats (inclusive)
+               // constrained because minTime/maxTime might be customized.
+               slatCoverage = Math.max(0, slatCoverage);
+               slatCoverage = Math.min(len, slatCoverage);
+
+               // an integer index of the furthest whole slat
+               // from 0 to number slats (*exclusive*, so len-1)
+               slatIndex = Math.floor(slatCoverage);
+               slatIndex = Math.min(slatIndex, len - 1);
+
+               // how much further through the slatIndex slat (from 0.0-1.0) must be covered in addition.
+               // could be 1.0 if slatCoverage is covering *all* the slots
+               slatRemainder = slatCoverage - slatIndex;
+
+               return this.slatCoordCache.getTopPosition(slatIndex) +
+                       this.slatCoordCache.getHeight(slatIndex) * slatRemainder;
+       },
+
+
+
+       /* Event Drag Visualization
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of an event being dragged over the specified date(s).
+       // A returned value of `true` signals that a mock "helper" event has been rendered.
+       renderDrag: function(eventLocation, seg) {
+
+               if (seg) { // if there is event information for this drag, render a helper event
+
+                       // returns mock event elements
+                       // signal that a helper has been rendered
+                       return this.renderEventLocationHelper(eventLocation, seg);
+               }
+               else {
+                       // otherwise, just render a highlight
+                       this.renderHighlight(this.eventToSpan(eventLocation));
+               }
+       },
+
+
+       // Unrenders any visual indication of an event being dragged
+       unrenderDrag: function() {
+               this.unrenderHelper();
+               this.unrenderHighlight();
+       },
+
+
+       /* Event Resize Visualization
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of an event being resized
+       renderEventResize: function(eventLocation, seg) {
+               return this.renderEventLocationHelper(eventLocation, seg); // returns mock event elements
+       },
+
+
+       // Unrenders any visual indication of an event being resized
+       unrenderEventResize: function() {
+               this.unrenderHelper();
+       },
+
+
+       /* Event Helper
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a mock "helper" event. `sourceSeg` is the original segment object and might be null (an external drag)
+       renderHelper: function(event, sourceSeg) {
+               return this.renderHelperSegs(this.eventToSegs(event), sourceSeg); // returns mock event elements
+       },
+
+
+       // Unrenders any mock helper event
+       unrenderHelper: function() {
+               this.unrenderHelperSegs();
+       },
+
+
+       /* Business Hours
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderBusinessHours: function() {
+               var events = this.view.calendar.getBusinessHoursEvents();
+               var segs = this.eventsToSegs(events);
+
+               this.renderBusinessSegs(segs);
+       },
+
+
+       unrenderBusinessHours: function() {
+               this.unrenderBusinessSegs();
+       },
+
+
+       /* Now Indicator
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       getNowIndicatorUnit: function() {
+               return 'minute'; // will refresh on the minute
+       },
+
+
+       renderNowIndicator: function(date) {
+               // seg system might be overkill, but it handles scenario where line needs to be rendered
+               //  more than once because of columns with the same date (resources columns for example)
+               var segs = this.spanToSegs({ start: date, end: date });
+               var top = this.computeDateTop(date, date);
+               var nodes = [];
+               var i;
+
+               // render lines within the columns
+               for (i = 0; i < segs.length; i++) {
+                       nodes.push($('<div class="fc-now-indicator fc-now-indicator-line"></div>')
+                               .css('top', top)
+                               .appendTo(this.colContainerEls.eq(segs[i].col))[0]);
+               }
+
+               // render an arrow over the axis
+               if (segs.length > 0) { // is the current time in view?
+                       nodes.push($('<div class="fc-now-indicator fc-now-indicator-arrow"></div>')
+                               .css('top', top)
+                               .appendTo(this.el.find('.fc-content-skeleton'))[0]);
+               }
+
+               this.nowIndicatorEls = $(nodes);
+       },
+
+
+       unrenderNowIndicator: function() {
+               if (this.nowIndicatorEls) {
+                       this.nowIndicatorEls.remove();
+                       this.nowIndicatorEls = null;
+               }
+       },
+
+
+       /* Selection
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of a selection. Overrides the default, which was to simply render a highlight.
+       renderSelection: function(span) {
+               if (this.view.opt('selectHelper')) { // this setting signals that a mock helper event should be rendered
+
+                       // normally acceps an eventLocation, span has a start/end, which is good enough
+                       this.renderEventLocationHelper(span);
+               }
+               else {
+                       this.renderHighlight(span);
+               }
+       },
+
+
+       // Unrenders any visual indication of a selection
+       unrenderSelection: function() {
+               this.unrenderHelper();
+               this.unrenderHighlight();
+       },
+
+
+       /* Highlight
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderHighlight: function(span) {
+               this.renderHighlightSegs(this.spanToSegs(span));
+       },
+
+
+       unrenderHighlight: function() {
+               this.unrenderHighlightSegs();
+       }
+
+});
+
+;;
+
+/* Methods for rendering SEGMENTS, pieces of content that live on the view
+ ( this file is no longer just for events )
+----------------------------------------------------------------------------------------------------------------------*/
+
+TimeGrid.mixin({
+
+       colContainerEls: null, // containers for each column
+
+       // inner-containers for each column where different types of segs live
+       fgContainerEls: null,
+       bgContainerEls: null,
+       helperContainerEls: null,
+       highlightContainerEls: null,
+       businessContainerEls: null,
+
+       // arrays of different types of displayed segments
+       fgSegs: null,
+       bgSegs: null,
+       helperSegs: null,
+       highlightSegs: null,
+       businessSegs: null,
+
+
+       // Renders the DOM that the view's content will live in
+       renderContentSkeleton: function() {
+               var cellHtml = '';
+               var i;
+               var skeletonEl;
+
+               for (i = 0; i < this.colCnt; i++) {
+                       cellHtml +=
+                               '<td>' +
+                                       '<div class="fc-content-col">' +
+                                               '<div class="fc-event-container fc-helper-container"></div>' +
+                                               '<div class="fc-event-container"></div>' +
+                                               '<div class="fc-highlight-container"></div>' +
+                                               '<div class="fc-bgevent-container"></div>' +
+                                               '<div class="fc-business-container"></div>' +
+                                       '</div>' +
+                               '</td>';
+               }
+
+               skeletonEl = $(
+                       '<div class="fc-content-skeleton">' +
+                               '<table>' +
+                                       '<tr>' + cellHtml + '</tr>' +
+                               '</table>' +
+                       '</div>'
+               );
+
+               this.colContainerEls = skeletonEl.find('.fc-content-col');
+               this.helperContainerEls = skeletonEl.find('.fc-helper-container');
+               this.fgContainerEls = skeletonEl.find('.fc-event-container:not(.fc-helper-container)');
+               this.bgContainerEls = skeletonEl.find('.fc-bgevent-container');
+               this.highlightContainerEls = skeletonEl.find('.fc-highlight-container');
+               this.businessContainerEls = skeletonEl.find('.fc-business-container');
+
+               this.bookendCells(skeletonEl.find('tr')); // TODO: do this on string level
+               this.el.append(skeletonEl);
+       },
+
+
+       /* Foreground Events
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderFgSegs: function(segs) {
+               segs = this.renderFgSegsIntoContainers(segs, this.fgContainerEls);
+               this.fgSegs = segs;
+               return segs; // needed for Grid::renderEvents
+       },
+
+
+       unrenderFgSegs: function() {
+               this.unrenderNamedSegs('fgSegs');
+       },
+
+
+       /* Foreground Helper Events
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderHelperSegs: function(segs, sourceSeg) {
+               var helperEls = [];
+               var i, seg;
+               var sourceEl;
+
+               segs = this.renderFgSegsIntoContainers(segs, this.helperContainerEls);
+
+               // Try to make the segment that is in the same row as sourceSeg look the same
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       if (sourceSeg && sourceSeg.col === seg.col) {
+                               sourceEl = sourceSeg.el;
+                               seg.el.css({
+                                       left: sourceEl.css('left'),
+                                       right: sourceEl.css('right'),
+                                       'margin-left': sourceEl.css('margin-left'),
+                                       'margin-right': sourceEl.css('margin-right')
+                               });
+                       }
+                       helperEls.push(seg.el[0]);
+               }
+
+               this.helperSegs = segs;
+
+               return $(helperEls); // must return rendered helpers
+       },
+
+
+       unrenderHelperSegs: function() {
+               this.unrenderNamedSegs('helperSegs');
+       },
+
+
+       /* Background Events
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderBgSegs: function(segs) {
+               segs = this.renderFillSegEls('bgEvent', segs); // TODO: old fill system
+               this.updateSegVerticals(segs);
+               this.attachSegsByCol(this.groupSegsByCol(segs), this.bgContainerEls);
+               this.bgSegs = segs;
+               return segs; // needed for Grid::renderEvents
+       },
+
+
+       unrenderBgSegs: function() {
+               this.unrenderNamedSegs('bgSegs');
+       },
+
+
+       /* Highlight
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderHighlightSegs: function(segs) {
+               segs = this.renderFillSegEls('highlight', segs); // TODO: old fill system
+               this.updateSegVerticals(segs);
+               this.attachSegsByCol(this.groupSegsByCol(segs), this.highlightContainerEls);
+               this.highlightSegs = segs;
+       },
+
+
+       unrenderHighlightSegs: function() {
+               this.unrenderNamedSegs('highlightSegs');
+       },
+
+
+       /* Business Hours
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderBusinessSegs: function(segs) {
+               segs = this.renderFillSegEls('businessHours', segs); // TODO: old fill system
+               this.updateSegVerticals(segs);
+               this.attachSegsByCol(this.groupSegsByCol(segs), this.businessContainerEls);
+               this.businessSegs = segs;
+       },
+
+
+       unrenderBusinessSegs: function() {
+               this.unrenderNamedSegs('businessSegs');
+       },
+
+
+       /* Seg Rendering Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Given a flat array of segments, return an array of sub-arrays, grouped by each segment's col
+       groupSegsByCol: function(segs) {
+               var segsByCol = [];
+               var i;
+
+               for (i = 0; i < this.colCnt; i++) {
+                       segsByCol.push([]);
+               }
+
+               for (i = 0; i < segs.length; i++) {
+                       segsByCol[segs[i].col].push(segs[i]);
+               }
+
+               return segsByCol;
+       },
+
+
+       // Given segments grouped by column, insert the segments' elements into a parallel array of container
+       // elements, each living within a column.
+       attachSegsByCol: function(segsByCol, containerEls) {
+               var col;
+               var segs;
+               var i;
+
+               for (col = 0; col < this.colCnt; col++) { // iterate each column grouping
+                       segs = segsByCol[col];
+
+                       for (i = 0; i < segs.length; i++) {
+                               containerEls.eq(col).append(segs[i].el);
+                       }
+               }
+       },
+
+
+       // Given the name of a property of `this` object, assumed to be an array of segments,
+       // loops through each segment and removes from DOM. Will null-out the property afterwards.
+       unrenderNamedSegs: function(propName) {
+               var segs = this[propName];
+               var i;
+
+               if (segs) {
+                       for (i = 0; i < segs.length; i++) {
+                               segs[i].el.remove();
+                       }
+                       this[propName] = null;
+               }
+       },
+
+
+
+       /* Foreground Event Rendering Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Given an array of foreground segments, render a DOM element for each, computes position,
+       // and attaches to the column inner-container elements.
+       renderFgSegsIntoContainers: function(segs, containerEls) {
+               var segsByCol;
+               var col;
+
+               segs = this.renderFgSegEls(segs); // will call fgSegHtml
+               segsByCol = this.groupSegsByCol(segs);
+
+               for (col = 0; col < this.colCnt; col++) {
+                       this.updateFgSegCoords(segsByCol[col]);
+               }
+
+               this.attachSegsByCol(segsByCol, containerEls);
+
+               return segs;
+       },
+
+
+       // Renders the HTML for a single event segment's default rendering
+       fgSegHtml: function(seg, disableResizing) {
+               var view = this.view;
+               var event = seg.event;
+               var isDraggable = view.isEventDraggable(event);
+               var isResizableFromStart = !disableResizing && seg.isStart && view.isEventResizableFromStart(event);
+               var isResizableFromEnd = !disableResizing && seg.isEnd && view.isEventResizableFromEnd(event);
+               var classes = this.getSegClasses(seg, isDraggable, isResizableFromStart || isResizableFromEnd);
+               var skinCss = cssToStr(this.getSegSkinCss(seg));
+               var timeText;
+               var fullTimeText; // more verbose time text. for the print stylesheet
+               var startTimeText; // just the start time text
+
+               classes.unshift('fc-time-grid-event', 'fc-v-event');
+
+               if (view.isMultiDayEvent(event)) { // if the event appears to span more than one day...
+                       // Don't display time text on segments that run entirely through a day.
+                       // That would appear as midnight-midnight and would look dumb.
+                       // Otherwise, display the time text for the *segment's* times (like 6pm-midnight or midnight-10am)
+                       if (seg.isStart || seg.isEnd) {
+                               timeText = this.getEventTimeText(seg);
+                               fullTimeText = this.getEventTimeText(seg, 'LT');
+                               startTimeText = this.getEventTimeText(seg, null, false); // displayEnd=false
+                       }
+               } else {
+                       // Display the normal time text for the *event's* times
+                       timeText = this.getEventTimeText(event);
+                       fullTimeText = this.getEventTimeText(event, 'LT');
+                       startTimeText = this.getEventTimeText(event, null, false); // displayEnd=false
+               }
+
+               return '<a class="' + classes.join(' ') + '"' +
+                       (event.url ?
+                               ' href="' + htmlEscape(event.url) + '"' :
+                               ''
+                               ) +
+                       (skinCss ?
+                               ' style="' + skinCss + '"' :
+                               ''
+                               ) +
+                       '>' +
+                               '<div class="fc-content">' +
+                                       (timeText ?
+                                               '<div class="fc-time"' +
+                                               ' data-start="' + htmlEscape(startTimeText) + '"' +
+                                               ' data-full="' + htmlEscape(fullTimeText) + '"' +
+                                               '>' +
+                                                       '<span>' + htmlEscape(timeText) + '</span>' +
+                                               '</div>' :
+                                               ''
+                                               ) +
+                                       (event.title ?
+                                               '<div class="fc-title">' +
+                                                       htmlEscape(event.title) +
+                                               '</div>' :
+                                               ''
+                                               ) +
+                               '</div>' +
+                               '<div class="fc-bg"/>' +
+                               /* TODO: write CSS for this
+                               (isResizableFromStart ?
+                                       '<div class="fc-resizer fc-start-resizer" />' :
+                                       ''
+                                       ) +
+                               */
+                               (isResizableFromEnd ?
+                                       '<div class="fc-resizer fc-end-resizer" />' :
+                                       ''
+                                       ) +
+                       '</a>';
+       },
+
+
+       /* Seg Position Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Refreshes the CSS top/bottom coordinates for each segment element.
+       // Works when called after initial render, after a window resize/zoom for example.
+       updateSegVerticals: function(segs) {
+               this.computeSegVerticals(segs);
+               this.assignSegVerticals(segs);
+       },
+
+
+       // For each segment in an array, computes and assigns its top and bottom properties
+       computeSegVerticals: function(segs) {
+               var i, seg;
+
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       seg.top = this.computeDateTop(seg.start, seg.start);
+                       seg.bottom = this.computeDateTop(seg.end, seg.start);
+               }
+       },
+
+
+       // Given segments that already have their top/bottom properties computed, applies those values to
+       // the segments' elements.
+       assignSegVerticals: function(segs) {
+               var i, seg;
+
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       seg.el.css(this.generateSegVerticalCss(seg));
+               }
+       },
+
+
+       // Generates an object with CSS properties for the top/bottom coordinates of a segment element
+       generateSegVerticalCss: function(seg) {
+               return {
+                       top: seg.top,
+                       bottom: -seg.bottom // flipped because needs to be space beyond bottom edge of event container
+               };
+       },
+
+
+       /* Foreground Event Positioning Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Given segments that are assumed to all live in the *same column*,
+       // compute their verical/horizontal coordinates and assign to their elements.
+       updateFgSegCoords: function(segs) {
+               this.computeSegVerticals(segs); // horizontals relies on this
+               this.computeFgSegHorizontals(segs); // compute horizontal coordinates, z-index's, and reorder the array
+               this.assignSegVerticals(segs);
+               this.assignFgSegHorizontals(segs);
+       },
+
+
+       // Given an array of segments that are all in the same column, sets the backwardCoord and forwardCoord on each.
+       // NOTE: Also reorders the given array by date!
+       computeFgSegHorizontals: function(segs) {
+               var levels;
+               var level0;
+               var i;
+
+               this.sortEventSegs(segs); // order by certain criteria
+               levels = buildSlotSegLevels(segs);
+               computeForwardSlotSegs(levels);
+
+               if ((level0 = levels[0])) {
+
+                       for (i = 0; i < level0.length; i++) {
+                               computeSlotSegPressures(level0[i]);
+                       }
+
+                       for (i = 0; i < level0.length; i++) {
+                               this.computeFgSegForwardBack(level0[i], 0, 0);
+                       }
+               }
+       },
+
+
+       // Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
+       // from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
+       // seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
+       //
+       // The segment might be part of a "series", which means consecutive segments with the same pressure
+       // who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
+       // segments behind this one in the current series, and `seriesBackwardCoord` is the starting
+       // coordinate of the first segment in the series.
+       computeFgSegForwardBack: function(seg, seriesBackwardPressure, seriesBackwardCoord) {
+               var forwardSegs = seg.forwardSegs;
+               var i;
+
+               if (seg.forwardCoord === undefined) { // not already computed
+
+                       if (!forwardSegs.length) {
+
+                               // if there are no forward segments, this segment should butt up against the edge
+                               seg.forwardCoord = 1;
+                       }
+                       else {
+
+                               // sort highest pressure first
+                               this.sortForwardSegs(forwardSegs);
+
+                               // this segment's forwardCoord will be calculated from the backwardCoord of the
+                               // highest-pressure forward segment.
+                               this.computeFgSegForwardBack(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
+                               seg.forwardCoord = forwardSegs[0].backwardCoord;
+                       }
+
+                       // calculate the backwardCoord from the forwardCoord. consider the series
+                       seg.backwardCoord = seg.forwardCoord -
+                               (seg.forwardCoord - seriesBackwardCoord) / // available width for series
+                               (seriesBackwardPressure + 1); // # of segments in the series
+
+                       // use this segment's coordinates to computed the coordinates of the less-pressurized
+                       // forward segments
+                       for (i=0; i<forwardSegs.length; i++) {
+                               this.computeFgSegForwardBack(forwardSegs[i], 0, seg.forwardCoord);
+                       }
+               }
+       },
+
+
+       sortForwardSegs: function(forwardSegs) {
+               forwardSegs.sort(proxy(this, 'compareForwardSegs'));
+       },
+
+
+       // A cmp function for determining which forward segment to rely on more when computing coordinates.
+       compareForwardSegs: function(seg1, seg2) {
+               // put higher-pressure first
+               return seg2.forwardPressure - seg1.forwardPressure ||
+                       // put segments that are closer to initial edge first (and favor ones with no coords yet)
+                       (seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
+                       // do normal sorting...
+                       this.compareEventSegs(seg1, seg2);
+       },
+
+
+       // Given foreground event segments that have already had their position coordinates computed,
+       // assigns position-related CSS values to their elements.
+       assignFgSegHorizontals: function(segs) {
+               var i, seg;
+
+               for (i = 0; i < segs.length; i++) {
+                       seg = segs[i];
+                       seg.el.css(this.generateFgSegHorizontalCss(seg));
+
+                       // if the height is short, add a className for alternate styling
+                       if (seg.bottom - seg.top < 30) {
+                               seg.el.addClass('fc-short');
+                       }
+               }
+       },
+
+
+       // Generates an object with CSS properties/values that should be applied to an event segment element.
+       // Contains important positioning-related properties that should be applied to any event element, customized or not.
+       generateFgSegHorizontalCss: function(seg) {
+               var shouldOverlap = this.view.opt('slotEventOverlap');
+               var backwardCoord = seg.backwardCoord; // the left side if LTR. the right side if RTL. floating-point
+               var forwardCoord = seg.forwardCoord; // the right side if LTR. the left side if RTL. floating-point
+               var props = this.generateSegVerticalCss(seg); // get top/bottom first
+               var left; // amount of space from left edge, a fraction of the total width
+               var right; // amount of space from right edge, a fraction of the total width
+
+               if (shouldOverlap) {
+                       // double the width, but don't go beyond the maximum forward coordinate (1.0)
+                       forwardCoord = Math.min(1, backwardCoord + (forwardCoord - backwardCoord) * 2);
+               }
+
+               if (this.isRTL) {
+                       left = 1 - forwardCoord;
+                       right = backwardCoord;
+               }
+               else {
+                       left = backwardCoord;
+                       right = 1 - forwardCoord;
+               }
+
+               props.zIndex = seg.level + 1; // convert from 0-base to 1-based
+               props.left = left * 100 + '%';
+               props.right = right * 100 + '%';
+
+               if (shouldOverlap && seg.forwardPressure) {
+                       // add padding to the edge so that forward stacked events don't cover the resizer's icon
+                       props[this.isRTL ? 'marginLeft' : 'marginRight'] = 10 * 2; // 10 is a guesstimate of the icon's width
+               }
+
+               return props;
+       }
+
+});
+
+
+// Builds an array of segments "levels". The first level will be the leftmost tier of segments if the calendar is
+// left-to-right, or the rightmost if the calendar is right-to-left. Assumes the segments are already ordered by date.
+function buildSlotSegLevels(segs) {
+       var levels = [];
+       var i, seg;
+       var j;
+
+       for (i=0; i<segs.length; i++) {
+               seg = segs[i];
+
+               // go through all the levels and stop on the first level where there are no collisions
+               for (j=0; j<levels.length; j++) {
+                       if (!computeSlotSegCollisions(seg, levels[j]).length) {
+                               break;
+                       }
+               }
+
+               seg.level = j;
+
+               (levels[j] || (levels[j] = [])).push(seg);
+       }
+
+       return levels;
+}
+
+
+// For every segment, figure out the other segments that are in subsequent
+// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
+function computeForwardSlotSegs(levels) {
+       var i, level;
+       var j, seg;
+       var k;
+
+       for (i=0; i<levels.length; i++) {
+               level = levels[i];
+
+               for (j=0; j<level.length; j++) {
+                       seg = level[j];
+
+                       seg.forwardSegs = [];
+                       for (k=i+1; k<levels.length; k++) {
+                               computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
+                       }
+               }
+       }
+}
+
+
+// Figure out which path forward (via seg.forwardSegs) results in the longest path until
+// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
+function computeSlotSegPressures(seg) {
+       var forwardSegs = seg.forwardSegs;
+       var forwardPressure = 0;
+       var i, forwardSeg;
+
+       if (seg.forwardPressure === undefined) { // not already computed
+
+               for (i=0; i<forwardSegs.length; i++) {
+                       forwardSeg = forwardSegs[i];
+
+                       // figure out the child's maximum forward path
+                       computeSlotSegPressures(forwardSeg);
+
+                       // either use the existing maximum, or use the child's forward pressure
+                       // plus one (for the forwardSeg itself)
+                       forwardPressure = Math.max(
+                               forwardPressure,
+                               1 + forwardSeg.forwardPressure
+                       );
+               }
+
+               seg.forwardPressure = forwardPressure;
+       }
+}
+
+
+// Find all the segments in `otherSegs` that vertically collide with `seg`.
+// Append into an optionally-supplied `results` array and return.
+function computeSlotSegCollisions(seg, otherSegs, results) {
+       results = results || [];
+
+       for (var i=0; i<otherSegs.length; i++) {
+               if (isSlotSegCollision(seg, otherSegs[i])) {
+                       results.push(otherSegs[i]);
+               }
+       }
+
+       return results;
+}
+
+
+// Do these segments occupy the same vertical space?
+function isSlotSegCollision(seg1, seg2) {
+       return seg1.bottom > seg2.top && seg1.top < seg2.bottom;
+}
+
+;;
+
+/* An abstract class from which other views inherit from
+----------------------------------------------------------------------------------------------------------------------*/
+
+var View = FC.View = Class.extend(EmitterMixin, ListenerMixin, {
+
+       type: null, // subclass' view name (string)
+       name: null, // deprecated. use `type` instead
+       title: null, // the text that will be displayed in the header's title
+
+       calendar: null, // owner Calendar object
+       options: null, // hash containing all options. already merged with view-specific-options
+       el: null, // the view's containing element. set by Calendar
+
+       displaying: null, // a promise representing the state of rendering. null if no render requested
+       isSkeletonRendered: false,
+       isEventsRendered: false,
+
+       // range the view is actually displaying (moments)
+       start: null,
+       end: null, // exclusive
+
+       // range the view is formally responsible for (moments)
+       // may be different from start/end. for example, a month view might have 1st-31st, excluding padded dates
+       intervalStart: null,
+       intervalEnd: null, // exclusive
+       intervalDuration: null,
+       intervalUnit: null, // name of largest unit being displayed, like "month" or "week"
+
+       isRTL: false,
+       isSelected: false, // boolean whether a range of time is user-selected or not
+       selectedEvent: null,
+
+       eventOrderSpecs: null, // criteria for ordering events when they have same date/time
+
+       // classNames styled by jqui themes
+       widgetHeaderClass: null,
+       widgetContentClass: null,
+       highlightStateClass: null,
+
+       // for date utils, computed from options
+       nextDayThreshold: null,
+       isHiddenDayHash: null,
+
+       // now indicator
+       isNowIndicatorRendered: null,
+       initialNowDate: null, // result first getNow call
+       initialNowQueriedMs: null, // ms time the getNow was called
+       nowIndicatorTimeoutID: null, // for refresh timing of now indicator
+       nowIndicatorIntervalID: null, // "
+
+
+       constructor: function(calendar, type, options, intervalDuration) {
+
+               this.calendar = calendar;
+               this.type = this.name = type; // .name is deprecated
+               this.options = options;
+               this.intervalDuration = intervalDuration || moment.duration(1, 'day');
+
+               this.nextDayThreshold = moment.duration(this.opt('nextDayThreshold'));
+               this.initThemingProps();
+               this.initHiddenDays();
+               this.isRTL = this.opt('isRTL');
+
+               this.eventOrderSpecs = parseFieldSpecs(this.opt('eventOrder'));
+
+               this.initialize();
+       },
+
+
+       // A good place for subclasses to initialize member variables
+       initialize: function() {
+               // subclasses can implement
+       },
+
+
+       // Retrieves an option with the given name
+       opt: function(name) {
+               return this.options[name];
+       },
+
+
+       // Triggers handlers that are view-related. Modifies args before passing to calendar.
+       trigger: function(name, thisObj) { // arguments beyond thisObj are passed along
+               var calendar = this.calendar;
+
+               return calendar.trigger.apply(
+                       calendar,
+                       [name, thisObj || this].concat(
+                               Array.prototype.slice.call(arguments, 2), // arguments beyond thisObj
+                               [ this ] // always make the last argument a reference to the view. TODO: deprecate
+                       )
+               );
+       },
+
+
+       /* Dates
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Updates all internal dates to center around the given current unzoned date.
+       setDate: function(date) {
+               this.setRange(this.computeRange(date));
+       },
+
+
+       // Updates all internal dates for displaying the given unzoned range.
+       setRange: function(range) {
+               $.extend(this, range); // assigns every property to this object's member variables
+               this.updateTitle();
+       },
+
+
+       // Given a single current unzoned date, produce information about what range to display.
+       // Subclasses can override. Must return all properties.
+       computeRange: function(date) {
+               var intervalUnit = computeIntervalUnit(this.intervalDuration);
+               var intervalStart = date.clone().startOf(intervalUnit);
+               var intervalEnd = intervalStart.clone().add(this.intervalDuration);
+               var start, end;
+
+               // normalize the range's time-ambiguity
+               if (/year|month|week|day/.test(intervalUnit)) { // whole-days?
+                       intervalStart.stripTime();
+                       intervalEnd.stripTime();
+               }
+               else { // needs to have a time?
+                       if (!intervalStart.hasTime()) {
+                               intervalStart = this.calendar.time(0); // give 00:00 time
+                       }
+                       if (!intervalEnd.hasTime()) {
+                               intervalEnd = this.calendar.time(0); // give 00:00 time
+                       }
+               }
+
+               start = intervalStart.clone();
+               start = this.skipHiddenDays(start);
+               end = intervalEnd.clone();
+               end = this.skipHiddenDays(end, -1, true); // exclusively move backwards
+
+               return {
+                       intervalUnit: intervalUnit,
+                       intervalStart: intervalStart,
+                       intervalEnd: intervalEnd,
+                       start: start,
+                       end: end
+               };
+       },
+
+
+       // Computes the new date when the user hits the prev button, given the current date
+       computePrevDate: function(date) {
+               return this.massageCurrentDate(
+                       date.clone().startOf(this.intervalUnit).subtract(this.intervalDuration), -1
+               );
+       },
+
+
+       // Computes the new date when the user hits the next button, given the current date
+       computeNextDate: function(date) {
+               return this.massageCurrentDate(
+                       date.clone().startOf(this.intervalUnit).add(this.intervalDuration)
+               );
+       },
+
+
+       // Given an arbitrarily calculated current date of the calendar, returns a date that is ensured to be completely
+       // visible. `direction` is optional and indicates which direction the current date was being
+       // incremented or decremented (1 or -1).
+       massageCurrentDate: function(date, direction) {
+               if (this.intervalDuration.as('days') <= 1) { // if the view displays a single day or smaller
+                       if (this.isHiddenDay(date)) {
+                               date = this.skipHiddenDays(date, direction);
+                               date.startOf('day');
+                       }
+               }
+
+               return date;
+       },
+
+
+       /* Title and Date Formatting
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Sets the view's title property to the most updated computed value
+       updateTitle: function() {
+               this.title = this.computeTitle();
+       },
+
+
+       // Computes what the title at the top of the calendar should be for this view
+       computeTitle: function() {
+               return this.formatRange(
+                       {
+                               // in case intervalStart/End has a time, make sure timezone is correct
+                               start: this.calendar.applyTimezone(this.intervalStart),
+                               end: this.calendar.applyTimezone(this.intervalEnd)
+                       },
+                       this.opt('titleFormat') || this.computeTitleFormat(),
+                       this.opt('titleRangeSeparator')
+               );
+       },
+
+
+       // Generates the format string that should be used to generate the title for the current date range.
+       // Attempts to compute the most appropriate format if not explicitly specified with `titleFormat`.
+       computeTitleFormat: function() {
+               if (this.intervalUnit == 'year') {
+                       return 'YYYY';
+               }
+               else if (this.intervalUnit == 'month') {
+                       return this.opt('monthYearFormat'); // like "September 2014"
+               }
+               else if (this.intervalDuration.as('days') > 1) {
+                       return 'll'; // multi-day range. shorter, like "Sep 9 - 10 2014"
+               }
+               else {
+                       return 'LL'; // one day. longer, like "September 9 2014"
+               }
+       },
+
+
+       // Utility for formatting a range. Accepts a range object, formatting string, and optional separator.
+       // Displays all-day ranges naturally, with an inclusive end. Takes the current isRTL into account.
+       // The timezones of the dates within `range` will be respected.
+       formatRange: function(range, formatStr, separator) {
+               var end = range.end;
+
+               if (!end.hasTime()) { // all-day?
+                       end = end.clone().subtract(1); // convert to inclusive. last ms of previous day
+               }
+
+               return formatRange(range.start, end, formatStr, separator, this.opt('isRTL'));
+       },
+
+
+       /* Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Sets the container element that the view should render inside of.
+       // Does other DOM-related initializations.
+       setElement: function(el) {
+               this.el = el;
+               this.bindGlobalHandlers();
+       },
+
+
+       // Removes the view's container element from the DOM, clearing any content beforehand.
+       // Undoes any other DOM-related attachments.
+       removeElement: function() {
+               this.clear(); // clears all content
+
+               // clean up the skeleton
+               if (this.isSkeletonRendered) {
+                       this.unrenderSkeleton();
+                       this.isSkeletonRendered = false;
+               }
+
+               this.unbindGlobalHandlers();
+
+               this.el.remove();
+
+               // NOTE: don't null-out this.el in case the View was destroyed within an API callback.
+               // We don't null-out the View's other jQuery element references upon destroy,
+               //  so we shouldn't kill this.el either.
+       },
+
+
+       // Does everything necessary to display the view centered around the given unzoned date.
+       // Does every type of rendering EXCEPT rendering events.
+       // Is asychronous and returns a promise.
+       display: function(date) {
+               var _this = this;
+               var scrollState = null;
+
+               if (this.displaying) {
+                       scrollState = this.queryScroll();
+               }
+
+               this.calendar.freezeContentHeight();
+
+               return this.clear().then(function() { // clear the content first (async)
+                       return (
+                               _this.displaying =
+                                       $.when(_this.displayView(date)) // displayView might return a promise
+                                               .then(function() {
+                                                       _this.forceScroll(_this.computeInitialScroll(scrollState));
+                                                       _this.calendar.unfreezeContentHeight();
+                                                       _this.triggerRender();
+                                               })
+                       );
+               });
+       },
+
+
+       // Does everything necessary to clear the content of the view.
+       // Clears dates and events. Does not clear the skeleton.
+       // Is asychronous and returns a promise.
+       clear: function() {
+               var _this = this;
+               var displaying = this.displaying;
+
+               if (displaying) { // previously displayed, or in the process of being displayed?
+                       return displaying.then(function() { // wait for the display to finish
+                               _this.displaying = null;
+                               _this.clearEvents();
+                               return _this.clearView(); // might return a promise. chain it
+                       });
+               }
+               else {
+                       return $.when(); // an immediately-resolved promise
+               }
+       },
+
+
+       // Displays the view's non-event content, such as date-related content or anything required by events.
+       // Renders the view's non-content skeleton if necessary.
+       // Can be asynchronous and return a promise.
+       displayView: function(date) {
+               if (!this.isSkeletonRendered) {
+                       this.renderSkeleton();
+                       this.isSkeletonRendered = true;
+               }
+               if (date) {
+                       this.setDate(date);
+               }
+               if (this.render) {
+                       this.render(); // TODO: deprecate
+               }
+               this.renderDates();
+               this.updateSize();
+               this.renderBusinessHours(); // might need coordinates, so should go after updateSize()
+               this.startNowIndicator();
+       },
+
+
+       // Unrenders the view content that was rendered in displayView.
+       // Can be asynchronous and return a promise.
+       clearView: function() {
+               this.unselect();
+               this.stopNowIndicator();
+               this.triggerUnrender();
+               this.unrenderBusinessHours();
+               this.unrenderDates();
+               if (this.destroy) {
+                       this.destroy(); // TODO: deprecate
+               }
+       },
+
+
+       // Renders the basic structure of the view before any content is rendered
+       renderSkeleton: function() {
+               // subclasses should implement
+       },
+
+
+       // Unrenders the basic structure of the view
+       unrenderSkeleton: function() {
+               // subclasses should implement
+       },
+
+
+       // Renders the view's date-related content.
+       // Assumes setRange has already been called and the skeleton has already been rendered.
+       renderDates: function() {
+               // subclasses should implement
+       },
+
+
+       // Unrenders the view's date-related content
+       unrenderDates: function() {
+               // subclasses should override
+       },
+
+
+       // Signals that the view's content has been rendered
+       triggerRender: function() {
+               this.trigger('viewRender', this, this, this.el);
+       },
+
+
+       // Signals that the view's content is about to be unrendered
+       triggerUnrender: function() {
+               this.trigger('viewDestroy', this, this, this.el);
+       },
+
+
+       // Binds DOM handlers to elements that reside outside the view container, such as the document
+       bindGlobalHandlers: function() {
+               this.listenTo($(document), 'mousedown', this.handleDocumentMousedown);
+               this.listenTo($(document), 'touchstart', this.processUnselect);
+       },
+
+
+       // Unbinds DOM handlers from elements that reside outside the view container
+       unbindGlobalHandlers: function() {
+               this.stopListeningTo($(document));
+       },
+
+
+       // Initializes internal variables related to theming
+       initThemingProps: function() {
+               var tm = this.opt('theme') ? 'ui' : 'fc';
+
+               this.widgetHeaderClass = tm + '-widget-header';
+               this.widgetContentClass = tm + '-widget-content';
+               this.highlightStateClass = tm + '-state-highlight';
+       },
+
+
+       /* Business Hours
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders business-hours onto the view. Assumes updateSize has already been called.
+       renderBusinessHours: function() {
+               // subclasses should implement
+       },
+
+
+       // Unrenders previously-rendered business-hours
+       unrenderBusinessHours: function() {
+               // subclasses should implement
+       },
+
+
+       /* Now Indicator
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Immediately render the current time indicator and begins re-rendering it at an interval,
+       // which is defined by this.getNowIndicatorUnit().
+       // TODO: somehow do this for the current whole day's background too
+       startNowIndicator: function() {
+               var _this = this;
+               var unit;
+               var update;
+               var delay; // ms wait value
+
+               if (this.opt('nowIndicator')) {
+                       unit = this.getNowIndicatorUnit();
+                       if (unit) {
+                               update = proxy(this, 'updateNowIndicator'); // bind to `this`
+
+                               this.initialNowDate = this.calendar.getNow();
+                               this.initialNowQueriedMs = +new Date();
+                               this.renderNowIndicator(this.initialNowDate);
+                               this.isNowIndicatorRendered = true;
+
+                               // wait until the beginning of the next interval
+                               delay = this.initialNowDate.clone().startOf(unit).add(1, unit) - this.initialNowDate;
+                               this.nowIndicatorTimeoutID = setTimeout(function() {
+                                       _this.nowIndicatorTimeoutID = null;
+                                       update();
+                                       delay = +moment.duration(1, unit);
+                                       delay = Math.max(100, delay); // prevent too frequent
+                                       _this.nowIndicatorIntervalID = setInterval(update, delay); // update every interval
+                               }, delay);
+                       }
+               }
+       },
+
+
+       // rerenders the now indicator, computing the new current time from the amount of time that has passed
+       // since the initial getNow call.
+       updateNowIndicator: function() {
+               if (this.isNowIndicatorRendered) {
+                       this.unrenderNowIndicator();
+                       this.renderNowIndicator(
+                               this.initialNowDate.clone().add(new Date() - this.initialNowQueriedMs) // add ms
+                       );
+               }
+       },
+
+
+       // Immediately unrenders the view's current time indicator and stops any re-rendering timers.
+       // Won't cause side effects if indicator isn't rendered.
+       stopNowIndicator: function() {
+               if (this.isNowIndicatorRendered) {
+
+                       if (this.nowIndicatorTimeoutID) {
+                               clearTimeout(this.nowIndicatorTimeoutID);
+                               this.nowIndicatorTimeoutID = null;
+                       }
+                       if (this.nowIndicatorIntervalID) {
+                               clearTimeout(this.nowIndicatorIntervalID);
+                               this.nowIndicatorIntervalID = null;
+                       }
+
+                       this.unrenderNowIndicator();
+                       this.isNowIndicatorRendered = false;
+               }
+       },
+
+
+       // Returns a string unit, like 'second' or 'minute' that defined how often the current time indicator
+       // should be refreshed. If something falsy is returned, no time indicator is rendered at all.
+       getNowIndicatorUnit: function() {
+               // subclasses should implement
+       },
+
+
+       // Renders a current time indicator at the given datetime
+       renderNowIndicator: function(date) {
+               // subclasses should implement
+       },
+
+
+       // Undoes the rendering actions from renderNowIndicator
+       unrenderNowIndicator: function() {
+               // subclasses should implement
+       },
+
+
+       /* Dimensions
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Refreshes anything dependant upon sizing of the container element of the grid
+       updateSize: function(isResize) {
+               var scrollState;
+
+               if (isResize) {
+                       scrollState = this.queryScroll();
+               }
+
+               this.updateHeight(isResize);
+               this.updateWidth(isResize);
+               this.updateNowIndicator();
+
+               if (isResize) {
+                       this.setScroll(scrollState);
+               }
+       },
+
+
+       // Refreshes the horizontal dimensions of the calendar
+       updateWidth: function(isResize) {
+               // subclasses should implement
+       },
+
+
+       // Refreshes the vertical dimensions of the calendar
+       updateHeight: function(isResize) {
+               var calendar = this.calendar; // we poll the calendar for height information
+
+               this.setHeight(
+                       calendar.getSuggestedViewHeight(),
+                       calendar.isHeightAuto()
+               );
+       },
+
+
+       // Updates the vertical dimensions of the calendar to the specified height.
+       // if `isAuto` is set to true, height becomes merely a suggestion and the view should use its "natural" height.
+       setHeight: function(height, isAuto) {
+               // subclasses should implement
+       },
+
+
+       /* Scroller
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes the initial pre-configured scroll state prior to allowing the user to change it.
+       // Given the scroll state from the previous rendering. If first time rendering, given null.
+       computeInitialScroll: function(previousScrollState) {
+               return 0;
+       },
+
+
+       // Retrieves the view's current natural scroll state. Can return an arbitrary format.
+       queryScroll: function() {
+               // subclasses must implement
+       },
+
+
+       // Sets the view's scroll state. Will accept the same format computeInitialScroll and queryScroll produce.
+       setScroll: function(scrollState) {
+               // subclasses must implement
+       },
+
+
+       // Sets the scroll state, making sure to overcome any predefined scroll value the browser has in mind
+       forceScroll: function(scrollState) {
+               var _this = this;
+
+               this.setScroll(scrollState);
+               setTimeout(function() {
+                       _this.setScroll(scrollState);
+               }, 0);
+       },
+
+
+       /* Event Elements / Segments
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Does everything necessary to display the given events onto the current view
+       displayEvents: function(events) {
+               var scrollState = this.queryScroll();
+
+               this.clearEvents();
+               this.renderEvents(events);
+               this.isEventsRendered = true;
+               this.setScroll(scrollState);
+               this.triggerEventRender();
+       },
+
+
+       // Does everything necessary to clear the view's currently-rendered events
+       clearEvents: function() {
+               var scrollState;
+
+               if (this.isEventsRendered) {
+
+                       // TODO: optimize: if we know this is part of a displayEvents call, don't queryScroll/setScroll
+                       scrollState = this.queryScroll();
+
+                       this.triggerEventUnrender();
+                       if (this.destroyEvents) {
+                               this.destroyEvents(); // TODO: deprecate
+                       }
+                       this.unrenderEvents();
+                       this.setScroll(scrollState);
+                       this.isEventsRendered = false;
+               }
+       },
+
+
+       // Renders the events onto the view.
+       renderEvents: function(events) {
+               // subclasses should implement
+       },
+
+
+       // Removes event elements from the view.
+       unrenderEvents: function() {
+               // subclasses should implement
+       },
+
+
+       // Signals that all events have been rendered
+       triggerEventRender: function() {
+               this.renderedEventSegEach(function(seg) {
+                       this.trigger('eventAfterRender', seg.event, seg.event, seg.el);
+               });
+               this.trigger('eventAfterAllRender');
+       },
+
+
+       // Signals that all event elements are about to be removed
+       triggerEventUnrender: function() {
+               this.renderedEventSegEach(function(seg) {
+                       this.trigger('eventDestroy', seg.event, seg.event, seg.el);
+               });
+       },
+
+
+       // Given an event and the default element used for rendering, returns the element that should actually be used.
+       // Basically runs events and elements through the eventRender hook.
+       resolveEventEl: function(event, el) {
+               var custom = this.trigger('eventRender', event, event, el);
+
+               if (custom === false) { // means don't render at all
+                       el = null;
+               }
+               else if (custom && custom !== true) {
+                       el = $(custom);
+               }
+
+               return el;
+       },
+
+
+       // Hides all rendered event segments linked to the given event
+       showEvent: function(event) {
+               this.renderedEventSegEach(function(seg) {
+                       seg.el.css('visibility', '');
+               }, event);
+       },
+
+
+       // Shows all rendered event segments linked to the given event
+       hideEvent: function(event) {
+               this.renderedEventSegEach(function(seg) {
+                       seg.el.css('visibility', 'hidden');
+               }, event);
+       },
+
+
+       // Iterates through event segments that have been rendered (have an el). Goes through all by default.
+       // If the optional `event` argument is specified, only iterates through segments linked to that event.
+       // The `this` value of the callback function will be the view.
+       renderedEventSegEach: function(func, event) {
+               var segs = this.getEventSegs();
+               var i;
+
+               for (i = 0; i < segs.length; i++) {
+                       if (!event || segs[i].event._id === event._id) {
+                               if (segs[i].el) {
+                                       func.call(this, segs[i]);
+                               }
+                       }
+               }
+       },
+
+
+       // Retrieves all the rendered segment objects for the view
+       getEventSegs: function() {
+               // subclasses must implement
+               return [];
+       },
+
+
+       /* Event Drag-n-Drop
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes if the given event is allowed to be dragged by the user
+       isEventDraggable: function(event) {
+               var source = event.source || {};
+
+               return firstDefined(
+                       event.startEditable,
+                       source.startEditable,
+                       this.opt('eventStartEditable'),
+                       event.editable,
+                       source.editable,
+                       this.opt('editable')
+               );
+       },
+
+
+       // Must be called when an event in the view is dropped onto new location.
+       // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
+       reportEventDrop: function(event, dropLocation, largeUnit, el, ev) {
+               var calendar = this.calendar;
+               var mutateResult = calendar.mutateEvent(event, dropLocation, largeUnit);
+               var undoFunc = function() {
+                       mutateResult.undo();
+                       calendar.reportEventChange();
+               };
+
+               this.triggerEventDrop(event, mutateResult.dateDelta, undoFunc, el, ev);
+               calendar.reportEventChange(); // will rerender events
+       },
+
+
+       // Triggers event-drop handlers that have subscribed via the API
+       triggerEventDrop: function(event, dateDelta, undoFunc, el, ev) {
+               this.trigger('eventDrop', el[0], event, dateDelta, undoFunc, ev, {}); // {} = jqui dummy
+       },
+
+
+       /* External Element Drag-n-Drop
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Must be called when an external element, via jQuery UI, has been dropped onto the calendar.
+       // `meta` is the parsed data that has been embedded into the dragging event.
+       // `dropLocation` is an object that contains the new zoned start/end/allDay values for the event.
+       reportExternalDrop: function(meta, dropLocation, el, ev, ui) {
+               var eventProps = meta.eventProps;
+               var eventInput;
+               var event;
+
+               // Try to build an event object and render it. TODO: decouple the two
+               if (eventProps) {
+                       eventInput = $.extend({}, eventProps, dropLocation);
+                       event = this.calendar.renderEvent(eventInput, meta.stick)[0]; // renderEvent returns an array
+               }
+
+               this.triggerExternalDrop(event, dropLocation, el, ev, ui);
+       },
+
+
+       // Triggers external-drop handlers that have subscribed via the API
+       triggerExternalDrop: function(event, dropLocation, el, ev, ui) {
+
+               // trigger 'drop' regardless of whether element represents an event
+               this.trigger('drop', el[0], dropLocation.start, ev, ui);
+
+               if (event) {
+                       this.trigger('eventReceive', null, event); // signal an external event landed
+               }
+       },
+
+
+       /* Drag-n-Drop Rendering (for both events and external elements)
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of a event or external-element drag over the given drop zone.
+       // If an external-element, seg will be `null`.
+       // Must return elements used for any mock events.
+       renderDrag: function(dropLocation, seg) {
+               // subclasses must implement
+       },
+
+
+       // Unrenders a visual indication of an event or external-element being dragged.
+       unrenderDrag: function() {
+               // subclasses must implement
+       },
+
+
+       /* Event Resizing
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes if the given event is allowed to be resized from its starting edge
+       isEventResizableFromStart: function(event) {
+               return this.opt('eventResizableFromStart') && this.isEventResizable(event);
+       },
+
+
+       // Computes if the given event is allowed to be resized from its ending edge
+       isEventResizableFromEnd: function(event) {
+               return this.isEventResizable(event);
+       },
+
+
+       // Computes if the given event is allowed to be resized by the user at all
+       isEventResizable: function(event) {
+               var source = event.source || {};
+
+               return firstDefined(
+                       event.durationEditable,
+                       source.durationEditable,
+                       this.opt('eventDurationEditable'),
+                       event.editable,
+                       source.editable,
+                       this.opt('editable')
+               );
+       },
+
+
+       // Must be called when an event in the view has been resized to a new length
+       reportEventResize: function(event, resizeLocation, largeUnit, el, ev) {
+               var calendar = this.calendar;
+               var mutateResult = calendar.mutateEvent(event, resizeLocation, largeUnit);
+               var undoFunc = function() {
+                       mutateResult.undo();
+                       calendar.reportEventChange();
+               };
+
+               this.triggerEventResize(event, mutateResult.durationDelta, undoFunc, el, ev);
+               calendar.reportEventChange(); // will rerender events
+       },
+
+
+       // Triggers event-resize handlers that have subscribed via the API
+       triggerEventResize: function(event, durationDelta, undoFunc, el, ev) {
+               this.trigger('eventResize', el[0], event, durationDelta, undoFunc, ev, {}); // {} = jqui dummy
+       },
+
+
+       /* Selection (time range)
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Selects a date span on the view. `start` and `end` are both Moments.
+       // `ev` is the native mouse event that begin the interaction.
+       select: function(span, ev) {
+               this.unselect(ev);
+               this.renderSelection(span);
+               this.reportSelection(span, ev);
+       },
+
+
+       // Renders a visual indication of the selection
+       renderSelection: function(span) {
+               // subclasses should implement
+       },
+
+
+       // Called when a new selection is made. Updates internal state and triggers handlers.
+       reportSelection: function(span, ev) {
+               this.isSelected = true;
+               this.triggerSelect(span, ev);
+       },
+
+
+       // Triggers handlers to 'select'
+       triggerSelect: function(span, ev) {
+               this.trigger(
+                       'select',
+                       null,
+                       this.calendar.applyTimezone(span.start), // convert to calendar's tz for external API
+                       this.calendar.applyTimezone(span.end), // "
+                       ev
+               );
+       },
+
+
+       // Undoes a selection. updates in the internal state and triggers handlers.
+       // `ev` is the native mouse event that began the interaction.
+       unselect: function(ev) {
+               if (this.isSelected) {
+                       this.isSelected = false;
+                       if (this.destroySelection) {
+                               this.destroySelection(); // TODO: deprecate
+                       }
+                       this.unrenderSelection();
+                       this.trigger('unselect', null, ev);
+               }
+       },
+
+
+       // Unrenders a visual indication of selection
+       unrenderSelection: function() {
+               // subclasses should implement
+       },
+
+
+       /* Event Selection
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       selectEvent: function(event) {
+               if (!this.selectedEvent || this.selectedEvent !== event) {
+                       this.unselectEvent();
+                       this.renderedEventSegEach(function(seg) {
+                               seg.el.addClass('fc-selected');
+                       }, event);
+                       this.selectedEvent = event;
+               }
+       },
+
+
+       unselectEvent: function() {
+               if (this.selectedEvent) {
+                       this.renderedEventSegEach(function(seg) {
+                               seg.el.removeClass('fc-selected');
+                       }, this.selectedEvent);
+                       this.selectedEvent = null;
+               }
+       },
+
+
+       isEventSelected: function(event) {
+               // event references might change on refetchEvents(), while selectedEvent doesn't,
+               // so compare IDs
+               return this.selectedEvent && this.selectedEvent._id === event._id;
+       },
+
+
+       /* Mouse / Touch Unselecting (time range & event unselection)
+       ------------------------------------------------------------------------------------------------------------------*/
+       // TODO: move consistently to down/start or up/end?
+       // TODO: don't kill previous selection if touch scrolling
+
+
+       handleDocumentMousedown: function(ev) {
+               if (isPrimaryMouseButton(ev)) {
+                       this.processUnselect(ev);
+               }
+       },
+
+
+       processUnselect: function(ev) {
+               this.processRangeUnselect(ev);
+               this.processEventUnselect(ev);
+       },
+
+
+       processRangeUnselect: function(ev) {
+               var ignore;
+
+               // is there a time-range selection?
+               if (this.isSelected && this.opt('unselectAuto')) {
+                       // only unselect if the clicked element is not identical to or inside of an 'unselectCancel' element
+                       ignore = this.opt('unselectCancel');
+                       if (!ignore || !$(ev.target).closest(ignore).length) {
+                               this.unselect(ev);
+                       }
+               }
+       },
+
+
+       processEventUnselect: function(ev) {
+               if (this.selectedEvent) {
+                       if (!$(ev.target).closest('.fc-selected').length) {
+                               this.unselectEvent();
+                       }
+               }
+       },
+
+
+       /* Day Click
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Triggers handlers to 'dayClick'
+       // Span has start/end of the clicked area. Only the start is useful.
+       triggerDayClick: function(span, dayEl, ev) {
+               this.trigger(
+                       'dayClick',
+                       dayEl,
+                       this.calendar.applyTimezone(span.start), // convert to calendar's timezone for external API
+                       ev
+               );
+       },
+
+
+       /* Date Utils
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Initializes internal variables related to calculating hidden days-of-week
+       initHiddenDays: function() {
+               var hiddenDays = this.opt('hiddenDays') || []; // array of day-of-week indices that are hidden
+               var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
+               var dayCnt = 0;
+               var i;
+
+               if (this.opt('weekends') === false) {
+                       hiddenDays.push(0, 6); // 0=sunday, 6=saturday
+               }
+
+               for (i = 0; i < 7; i++) {
+                       if (
+                               !(isHiddenDayHash[i] = $.inArray(i, hiddenDays) !== -1)
+                       ) {
+                               dayCnt++;
+                       }
+               }
+
+               if (!dayCnt) {
+                       throw 'invalid hiddenDays'; // all days were hidden? bad.
+               }
+
+               this.isHiddenDayHash = isHiddenDayHash;
+       },
+
+
+       // Is the current day hidden?
+       // `day` is a day-of-week index (0-6), or a Moment
+       isHiddenDay: function(day) {
+               if (moment.isMoment(day)) {
+                       day = day.day();
+               }
+               return this.isHiddenDayHash[day];
+       },
+
+
+       // Incrementing the current day until it is no longer a hidden day, returning a copy.
+       // If the initial value of `date` is not a hidden day, don't do anything.
+       // Pass `isExclusive` as `true` if you are dealing with an end date.
+       // `inc` defaults to `1` (increment one day forward each time)
+       skipHiddenDays: function(date, inc, isExclusive) {
+               var out = date.clone();
+               inc = inc || 1;
+               while (
+                       this.isHiddenDayHash[(out.day() + (isExclusive ? inc : 0) + 7) % 7]
+               ) {
+                       out.add(inc, 'days');
+               }
+               return out;
+       },
+
+
+       // Returns the date range of the full days the given range visually appears to occupy.
+       // Returns a new range object.
+       computeDayRange: function(range) {
+               var startDay = range.start.clone().stripTime(); // the beginning of the day the range starts
+               var end = range.end;
+               var endDay = null;
+               var endTimeMS;
+
+               if (end) {
+                       endDay = end.clone().stripTime(); // the beginning of the day the range exclusively ends
+                       endTimeMS = +end.time(); // # of milliseconds into `endDay`
+
+                       // If the end time is actually inclusively part of the next day and is equal to or
+                       // beyond the next day threshold, adjust the end to be the exclusive end of `endDay`.
+                       // Otherwise, leaving it as inclusive will cause it to exclude `endDay`.
+                       if (endTimeMS && endTimeMS >= this.nextDayThreshold) {
+                               endDay.add(1, 'days');
+                       }
+               }
+
+               // If no end was specified, or if it is within `startDay` but not past nextDayThreshold,
+               // assign the default duration of one day.
+               if (!end || endDay <= startDay) {
+                       endDay = startDay.clone().add(1, 'days');
+               }
+
+               return { start: startDay, end: endDay };
+       },
+
+
+       // Does the given event visually appear to occupy more than one day?
+       isMultiDayEvent: function(event) {
+               var range = this.computeDayRange(event); // event is range-ish
+
+               return range.end.diff(range.start, 'days') > 1;
+       }
+
+});
+
+;;
+
+/*
+Embodies a div that has potential scrollbars
+*/
+var Scroller = FC.Scroller = Class.extend({
+
+       el: null, // the guaranteed outer element
+       scrollEl: null, // the element with the scrollbars
+       overflowX: null,
+       overflowY: null,
+
+
+       constructor: function(options) {
+               options = options || {};
+               this.overflowX = options.overflowX || options.overflow || 'auto';
+               this.overflowY = options.overflowY || options.overflow || 'auto';
+       },
+
+
+       render: function() {
+               this.el = this.renderEl();
+               this.applyOverflow();
+       },
+
+
+       renderEl: function() {
+               return (this.scrollEl = $('<div class="fc-scroller"></div>'));
+       },
+
+
+       // sets to natural height, unlocks overflow
+       clear: function() {
+               this.setHeight('auto');
+               this.applyOverflow();
+       },
+
+
+       destroy: function() {
+               this.el.remove();
+       },
+
+
+       // Overflow
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       applyOverflow: function() {
+               this.scrollEl.css({
+                       'overflow-x': this.overflowX,
+                       'overflow-y': this.overflowY
+               });
+       },
+
+
+       // Causes any 'auto' overflow values to resolves to 'scroll' or 'hidden'.
+       // Useful for preserving scrollbar widths regardless of future resizes.
+       // Can pass in scrollbarWidths for optimization.
+       lockOverflow: function(scrollbarWidths) {
+               var overflowX = this.overflowX;
+               var overflowY = this.overflowY;
+
+               scrollbarWidths = scrollbarWidths || this.getScrollbarWidths();
+
+               if (overflowX === 'auto') {
+                       overflowX = (
+                                       scrollbarWidths.top || scrollbarWidths.bottom || // horizontal scrollbars?
+                                       // OR scrolling pane with massless scrollbars?
+                                       this.scrollEl[0].scrollWidth - 1 > this.scrollEl[0].clientWidth
+                                               // subtract 1 because of IE off-by-one issue
+                               ) ? 'scroll' : 'hidden';
+               }
+
+               if (overflowY === 'auto') {
+                       overflowY = (
+                                       scrollbarWidths.left || scrollbarWidths.right || // vertical scrollbars?
+                                       // OR scrolling pane with massless scrollbars?
+                                       this.scrollEl[0].scrollHeight - 1 > this.scrollEl[0].clientHeight
+                                               // subtract 1 because of IE off-by-one issue
+                               ) ? 'scroll' : 'hidden';
+               }
+
+               this.scrollEl.css({ 'overflow-x': overflowX, 'overflow-y': overflowY });
+       },
+
+
+       // Getters / Setters
+       // -----------------------------------------------------------------------------------------------------------------
+
+
+       setHeight: function(height) {
+               this.scrollEl.height(height);
+       },
+
+
+       getScrollTop: function() {
+               return this.scrollEl.scrollTop();
+       },
+
+
+       setScrollTop: function(top) {
+               this.scrollEl.scrollTop(top);
+       },
+
+
+       getClientWidth: function() {
+               return this.scrollEl[0].clientWidth;
+       },
+
+
+       getClientHeight: function() {
+               return this.scrollEl[0].clientHeight;
+       },
+
+
+       getScrollbarWidths: function() {
+               return getScrollbarWidths(this.scrollEl);
+       }
+
+});
+
+;;
+
+var Calendar = FC.Calendar = Class.extend({
+
+       dirDefaults: null, // option defaults related to LTR or RTL
+       langDefaults: null, // option defaults related to current locale
+       overrides: null, // option overrides given to the fullCalendar constructor
+       options: null, // all defaults combined with overrides
+       viewSpecCache: null, // cache of view definitions
+       view: null, // current View object
+       header: null,
+       loadingLevel: 0, // number of simultaneous loading tasks
+
+
+       // a lot of this class' OOP logic is scoped within this constructor function,
+       // but in the future, write individual methods on the prototype.
+       constructor: Calendar_constructor,
+
+
+       // Subclasses can override this for initialization logic after the constructor has been called
+       initialize: function() {
+       },
+
+
+       // Initializes `this.options` and other important options-related objects
+       initOptions: function(overrides) {
+               var lang, langDefaults;
+               var isRTL, dirDefaults;
+
+               // converts legacy options into non-legacy ones.
+               // in the future, when this is removed, don't use `overrides` reference. make a copy.
+               overrides = massageOverrides(overrides);
+
+               lang = overrides.lang;
+               langDefaults = langOptionHash[lang];
+               if (!langDefaults) {
+                       lang = Calendar.defaults.lang;
+                       langDefaults = langOptionHash[lang] || {};
+               }
+
+               isRTL = firstDefined(
+                       overrides.isRTL,
+                       langDefaults.isRTL,
+                       Calendar.defaults.isRTL
+               );
+               dirDefaults = isRTL ? Calendar.rtlDefaults : {};
+
+               this.dirDefaults = dirDefaults;
+               this.langDefaults = langDefaults;
+               this.overrides = overrides;
+               this.options = mergeOptions([ // merge defaults and overrides. lowest to highest precedence
+                       Calendar.defaults, // global defaults
+                       dirDefaults,
+                       langDefaults,
+                       overrides
+               ]);
+               populateInstanceComputableOptions(this.options);
+
+               this.viewSpecCache = {}; // somewhat unrelated
+       },
+
+
+       // Gets information about how to create a view. Will use a cache.
+       getViewSpec: function(viewType) {
+               var cache = this.viewSpecCache;
+
+               return cache[viewType] || (cache[viewType] = this.buildViewSpec(viewType));
+       },
+
+
+       // Given a duration singular unit, like "week" or "day", finds a matching view spec.
+       // Preference is given to views that have corresponding buttons.
+       getUnitViewSpec: function(unit) {
+               var viewTypes;
+               var i;
+               var spec;
+
+               if ($.inArray(unit, intervalUnits) != -1) {
+
+                       // put views that have buttons first. there will be duplicates, but oh well
+                       viewTypes = this.header.getViewsWithButtons();
+                       $.each(FC.views, function(viewType) { // all views
+                               viewTypes.push(viewType);
+                       });
+
+                       for (i = 0; i < viewTypes.length; i++) {
+                               spec = this.getViewSpec(viewTypes[i]);
+                               if (spec) {
+                                       if (spec.singleUnit == unit) {
+                                               return spec;
+                                       }
+                               }
+                       }
+               }
+       },
+
+
+       // Builds an object with information on how to create a given view
+       buildViewSpec: function(requestedViewType) {
+               var viewOverrides = this.overrides.views || {};
+               var specChain = []; // for the view. lowest to highest priority
+               var defaultsChain = []; // for the view. lowest to highest priority
+               var overridesChain = []; // for the view. lowest to highest priority
+               var viewType = requestedViewType;
+               var spec; // for the view
+               var overrides; // for the view
+               var duration;
+               var unit;
+
+               // iterate from the specific view definition to a more general one until we hit an actual View class
+               while (viewType) {
+                       spec = fcViews[viewType];
+                       overrides = viewOverrides[viewType];
+                       viewType = null; // clear. might repopulate for another iteration
+
+                       if (typeof spec === 'function') { // TODO: deprecate
+                               spec = { 'class': spec };
+                       }
+
+                       if (spec) {
+                               specChain.unshift(spec);
+                               defaultsChain.unshift(spec.defaults || {});
+                               duration = duration || spec.duration;
+                               viewType = viewType || spec.type;
+                       }
+
+                       if (overrides) {
+                               overridesChain.unshift(overrides); // view-specific option hashes have options at zero-level
+                               duration = duration || overrides.duration;
+                               viewType = viewType || overrides.type;
+                       }
+               }
+
+               spec = mergeProps(specChain);
+               spec.type = requestedViewType;
+               if (!spec['class']) {
+                       return false;
+               }
+
+               if (duration) {
+                       duration = moment.duration(duration);
+                       if (duration.valueOf()) { // valid?
+                               spec.duration = duration;
+                               unit = computeIntervalUnit(duration);
+
+                               // view is a single-unit duration, like "week" or "day"
+                               // incorporate options for this. lowest priority
+                               if (duration.as(unit) === 1) {
+                                       spec.singleUnit = unit;
+                                       overridesChain.unshift(viewOverrides[unit] || {});
+                               }
+                       }
+               }
+
+               spec.defaults = mergeOptions(defaultsChain);
+               spec.overrides = mergeOptions(overridesChain);
+
+               this.buildViewSpecOptions(spec);
+               this.buildViewSpecButtonText(spec, requestedViewType);
+
+               return spec;
+       },
+
+
+       // Builds and assigns a view spec's options object from its already-assigned defaults and overrides
+       buildViewSpecOptions: function(spec) {
+               spec.options = mergeOptions([ // lowest to highest priority
+                       Calendar.defaults, // global defaults
+                       spec.defaults, // view's defaults (from ViewSubclass.defaults)
+                       this.dirDefaults,
+                       this.langDefaults, // locale and dir take precedence over view's defaults!
+                       this.overrides, // calendar's overrides (options given to constructor)
+                       spec.overrides // view's overrides (view-specific options)
+               ]);
+               populateInstanceComputableOptions(spec.options);
+       },
+
+
+       // Computes and assigns a view spec's buttonText-related options
+       buildViewSpecButtonText: function(spec, requestedViewType) {
+
+               // given an options object with a possible `buttonText` hash, lookup the buttonText for the
+               // requested view, falling back to a generic unit entry like "week" or "day"
+               function queryButtonText(options) {
+                       var buttonText = options.buttonText || {};
+                       return buttonText[requestedViewType] ||
+                               (spec.singleUnit ? buttonText[spec.singleUnit] : null);
+               }
+
+               // highest to lowest priority
+               spec.buttonTextOverride =
+                       queryButtonText(this.overrides) || // constructor-specified buttonText lookup hash takes precedence
+                       spec.overrides.buttonText; // `buttonText` for view-specific options is a string
+
+               // highest to lowest priority. mirrors buildViewSpecOptions
+               spec.buttonTextDefault =
+                       queryButtonText(this.langDefaults) ||
+                       queryButtonText(this.dirDefaults) ||
+                       spec.defaults.buttonText || // a single string. from ViewSubclass.defaults
+                       queryButtonText(Calendar.defaults) ||
+                       (spec.duration ? this.humanizeDuration(spec.duration) : null) || // like "3 days"
+                       requestedViewType; // fall back to given view name
+       },
+
+
+       // Given a view name for a custom view or a standard view, creates a ready-to-go View object
+       instantiateView: function(viewType) {
+               var spec = this.getViewSpec(viewType);
+
+               return new spec['class'](this, viewType, spec.options, spec.duration);
+       },
+
+
+       // Returns a boolean about whether the view is okay to instantiate at some point
+       isValidViewType: function(viewType) {
+               return Boolean(this.getViewSpec(viewType));
+       },
+
+
+       // Should be called when any type of async data fetching begins
+       pushLoading: function() {
+               if (!(this.loadingLevel++)) {
+                       this.trigger('loading', null, true, this.view);
+               }
+       },
+
+
+       // Should be called when any type of async data fetching completes
+       popLoading: function() {
+               if (!(--this.loadingLevel)) {
+                       this.trigger('loading', null, false, this.view);
+               }
+       },
+
+
+       // Given arguments to the select method in the API, returns a span (unzoned start/end and other info)
+       buildSelectSpan: function(zonedStartInput, zonedEndInput) {
+               var start = this.moment(zonedStartInput).stripZone();
+               var end;
+
+               if (zonedEndInput) {
+                       end = this.moment(zonedEndInput).stripZone();
+               }
+               else if (start.hasTime()) {
+                       end = start.clone().add(this.defaultTimedEventDuration);
+               }
+               else {
+                       end = start.clone().add(this.defaultAllDayEventDuration);
+               }
+
+               return { start: start, end: end };
+       }
+
+});
+
+
+Calendar.mixin(EmitterMixin);
+
+
+function Calendar_constructor(element, overrides) {
+       var t = this;
+
+
+       t.initOptions(overrides || {});
+       var options = this.options;
+
+       
+       // Exports
+       // -----------------------------------------------------------------------------------
+
+       t.render = render;
+       t.destroy = destroy;
+       t.refetchEvents = refetchEvents;
+       t.reportEvents = reportEvents;
+       t.reportEventChange = reportEventChange;
+       t.rerenderEvents = renderEvents; // `renderEvents` serves as a rerender. an API method
+       t.changeView = renderView; // `renderView` will switch to another view
+       t.select = select;
+       t.unselect = unselect;
+       t.prev = prev;
+       t.next = next;
+       t.prevYear = prevYear;
+       t.nextYear = nextYear;
+       t.today = today;
+       t.gotoDate = gotoDate;
+       t.incrementDate = incrementDate;
+       t.zoomTo = zoomTo;
+       t.getDate = getDate;
+       t.getCalendar = getCalendar;
+       t.getView = getView;
+       t.option = option;
+       t.trigger = trigger;
+
+
+
+       // Language-data Internals
+       // -----------------------------------------------------------------------------------
+       // Apply overrides to the current language's data
+
+
+       var localeData = createObject( // make a cheap copy
+               getMomentLocaleData(options.lang) // will fall back to en
+       );
+
+       if (options.monthNames) {
+               localeData._months = options.monthNames;
+       }
+       if (options.monthNamesShort) {
+               localeData._monthsShort = options.monthNamesShort;
+       }
+       if (options.dayNames) {
+               localeData._weekdays = options.dayNames;
+       }
+       if (options.dayNamesShort) {
+               localeData._weekdaysShort = options.dayNamesShort;
+       }
+       if (options.firstDay != null) {
+               var _week = createObject(localeData._week); // _week: { dow: # }
+               _week.dow = options.firstDay;
+               localeData._week = _week;
+       }
+
+       // assign a normalized value, to be used by our .week() moment extension
+       localeData._fullCalendar_weekCalc = (function(weekCalc) {
+               if (typeof weekCalc === 'function') {
+                       return weekCalc;
+               }
+               else if (weekCalc === 'local') {
+                       return weekCalc;
+               }
+               else if (weekCalc === 'iso' || weekCalc === 'ISO') {
+                       return 'ISO';
+               }
+       })(options.weekNumberCalculation);
+
+
+
+       // Calendar-specific Date Utilities
+       // -----------------------------------------------------------------------------------
+
+
+       t.defaultAllDayEventDuration = moment.duration(options.defaultAllDayEventDuration);
+       t.defaultTimedEventDuration = moment.duration(options.defaultTimedEventDuration);
+
+
+       // Builds a moment using the settings of the current calendar: timezone and language.
+       // Accepts anything the vanilla moment() constructor accepts.
+       t.moment = function() {
+               var mom;
+
+               if (options.timezone === 'local') {
+                       mom = FC.moment.apply(null, arguments);
+
+                       // Force the moment to be local, because FC.moment doesn't guarantee it.
+                       if (mom.hasTime()) { // don't give ambiguously-timed moments a local zone
+                               mom.local();
+                       }
+               }
+               else if (options.timezone === 'UTC') {
+                       mom = FC.moment.utc.apply(null, arguments); // process as UTC
+               }
+               else {
+                       mom = FC.moment.parseZone.apply(null, arguments); // let the input decide the zone
+               }
+
+               if ('_locale' in mom) { // moment 2.8 and above
+                       mom._locale = localeData;
+               }
+               else { // pre-moment-2.8
+                       mom._lang = localeData;
+               }
+
+               return mom;
+       };
+
+
+       // Returns a boolean about whether or not the calendar knows how to calculate
+       // the timezone offset of arbitrary dates in the current timezone.
+       t.getIsAmbigTimezone = function() {
+               return options.timezone !== 'local' && options.timezone !== 'UTC';
+       };
+
+
+       // Returns a copy of the given date in the current timezone. Has no effect on dates without times.
+       t.applyTimezone = function(date) {
+               if (!date.hasTime()) {
+                       return date.clone();
+               }
+
+               var zonedDate = t.moment(date.toArray());
+               var timeAdjust = date.time() - zonedDate.time();
+               var adjustedZonedDate;
+
+               // Safari sometimes has problems with this coersion when near DST. Adjust if necessary. (bug #2396)
+               if (timeAdjust) { // is the time result different than expected?
+                       adjustedZonedDate = zonedDate.clone().add(timeAdjust); // add milliseconds
+                       if (date.time() - adjustedZonedDate.time() === 0) { // does it match perfectly now?
+                               zonedDate = adjustedZonedDate;
+                       }
+               }
+
+               return zonedDate;
+       };
+
+
+       // Returns a moment for the current date, as defined by the client's computer or from the `now` option.
+       // Will return an moment with an ambiguous timezone.
+       t.getNow = function() {
+               var now = options.now;
+               if (typeof now === 'function') {
+                       now = now();
+               }
+               return t.moment(now).stripZone();
+       };
+
+
+       // Get an event's normalized end date. If not present, calculate it from the defaults.
+       t.getEventEnd = function(event) {
+               if (event.end) {
+                       return event.end.clone();
+               }
+               else {
+                       return t.getDefaultEventEnd(event.allDay, event.start);
+               }
+       };
+
+
+       // Given an event's allDay status and start date, return what its fallback end date should be.
+       // TODO: rename to computeDefaultEventEnd
+       t.getDefaultEventEnd = function(allDay, zonedStart) {
+               var end = zonedStart.clone();
+
+               if (allDay) {
+                       end.stripTime().add(t.defaultAllDayEventDuration);
+               }
+               else {
+                       end.add(t.defaultTimedEventDuration);
+               }
+
+               if (t.getIsAmbigTimezone()) {
+                       end.stripZone(); // we don't know what the tzo should be
+               }
+
+               return end;
+       };
+
+
+       // Produces a human-readable string for the given duration.
+       // Side-effect: changes the locale of the given duration.
+       t.humanizeDuration = function(duration) {
+               return (duration.locale || duration.lang).call(duration, options.lang) // works moment-pre-2.8
+                       .humanize();
+       };
+
+
+       
+       // Imports
+       // -----------------------------------------------------------------------------------
+
+
+       EventManager.call(t, options);
+       var isFetchNeeded = t.isFetchNeeded;
+       var fetchEvents = t.fetchEvents;
+
+
+
+       // Locals
+       // -----------------------------------------------------------------------------------
+
+
+       var _element = element[0];
+       var header;
+       var headerElement;
+       var content;
+       var tm; // for making theme classes
+       var currentView; // NOTE: keep this in sync with this.view
+       var viewsByType = {}; // holds all instantiated view instances, current or not
+       var suggestedViewHeight;
+       var windowResizeProxy; // wraps the windowResize function
+       var ignoreWindowResize = 0;
+       var events = [];
+       var date; // unzoned
+       
+       
+       
+       // Main Rendering
+       // -----------------------------------------------------------------------------------
+
+
+       // compute the initial ambig-timezone date
+       if (options.defaultDate != null) {
+               date = t.moment(options.defaultDate).stripZone();
+       }
+       else {
+               date = t.getNow(); // getNow already returns unzoned
+       }
+       
+       
+       function render() {
+               if (!content) {
+                       initialRender();
+               }
+               else if (elementVisible()) {
+                       // mainly for the public API
+                       calcSize();
+                       renderView();
+               }
+       }
+       
+       
+       function initialRender() {
+               tm = options.theme ? 'ui' : 'fc';
+               element.addClass('fc');
+
+               if (options.isRTL) {
+                       element.addClass('fc-rtl');
+               }
+               else {
+                       element.addClass('fc-ltr');
+               }
+
+               if (options.theme) {
+                       element.addClass('ui-widget');
+               }
+               else {
+                       element.addClass('fc-unthemed');
+               }
+
+               content = $("<div class='fc-view-container'/>").prependTo(element);
+
+               header = t.header = new Header(t, options);
+               headerElement = header.render();
+               if (headerElement) {
+                       element.prepend(headerElement);
+               }
+
+               renderView(options.defaultView);
+
+               if (options.handleWindowResize) {
+                       windowResizeProxy = debounce(windowResize, options.windowResizeDelay); // prevents rapid calls
+                       $(window).resize(windowResizeProxy);
+               }
+       }
+       
+       
+       function destroy() {
+
+               if (currentView) {
+                       currentView.removeElement();
+
+                       // NOTE: don't null-out currentView/t.view in case API methods are called after destroy.
+                       // It is still the "current" view, just not rendered.
+               }
+
+               header.removeElement();
+               content.remove();
+               element.removeClass('fc fc-ltr fc-rtl fc-unthemed ui-widget');
+
+               if (windowResizeProxy) {
+                       $(window).unbind('resize', windowResizeProxy);
+               }
+       }
+       
+       
+       function elementVisible() {
+               return element.is(':visible');
+       }
+       
+       
+
+       // View Rendering
+       // -----------------------------------------------------------------------------------
+
+
+       // Renders a view because of a date change, view-type change, or for the first time.
+       // If not given a viewType, keep the current view but render different dates.
+       function renderView(viewType) {
+               ignoreWindowResize++;
+
+               // if viewType is changing, remove the old view's rendering
+               if (currentView && viewType && currentView.type !== viewType) {
+                       header.deactivateButton(currentView.type);
+                       freezeContentHeight(); // prevent a scroll jump when view element is removed
+                       currentView.removeElement();
+                       currentView = t.view = null;
+               }
+
+               // if viewType changed, or the view was never created, create a fresh view
+               if (!currentView && viewType) {
+                       currentView = t.view =
+                               viewsByType[viewType] ||
+                               (viewsByType[viewType] = t.instantiateView(viewType));
+
+                       currentView.setElement(
+                               $("<div class='fc-view fc-" + viewType + "-view' />").appendTo(content)
+                       );
+                       header.activateButton(viewType);
+               }
+
+               if (currentView) {
+
+                       // in case the view should render a period of time that is completely hidden
+                       date = currentView.massageCurrentDate(date);
+
+                       // render or rerender the view
+                       if (
+                               !currentView.displaying ||
+                               !date.isWithin(currentView.intervalStart, currentView.intervalEnd) // implicit date window change
+                       ) {
+                               if (elementVisible()) {
+
+                                       currentView.display(date); // will call freezeContentHeight
+                                       unfreezeContentHeight(); // immediately unfreeze regardless of whether display is async
+
+                                       // need to do this after View::render, so dates are calculated
+                                       updateHeaderTitle();
+                                       updateTodayButton();
+
+                                       getAndRenderEvents();
+                               }
+                       }
+               }
+
+               unfreezeContentHeight(); // undo any lone freezeContentHeight calls
+               ignoreWindowResize--;
+       }
+
+       
+
+       // Resizing
+       // -----------------------------------------------------------------------------------
+
+
+       t.getSuggestedViewHeight = function() {
+               if (suggestedViewHeight === undefined) {
+                       calcSize();
+               }
+               return suggestedViewHeight;
+       };
+
+
+       t.isHeightAuto = function() {
+               return options.contentHeight === 'auto' || options.height === 'auto';
+       };
+       
+       
+       function updateSize(shouldRecalc) {
+               if (elementVisible()) {
+
+                       if (shouldRecalc) {
+                               _calcSize();
+                       }
+
+                       ignoreWindowResize++;
+                       currentView.updateSize(true); // isResize=true. will poll getSuggestedViewHeight() and isHeightAuto()
+                       ignoreWindowResize--;
+
+                       return true; // signal success
+               }
+       }
+
+
+       function calcSize() {
+               if (elementVisible()) {
+                       _calcSize();
+               }
+       }
+       
+       
+       function _calcSize() { // assumes elementVisible
+               if (typeof options.contentHeight === 'number') { // exists and not 'auto'
+                       suggestedViewHeight = options.contentHeight;
+               }
+               else if (typeof options.height === 'number') { // exists and not 'auto'
+                       suggestedViewHeight = options.height - (headerElement ? headerElement.outerHeight(true) : 0);
+               }
+               else {
+                       suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
+               }
+       }
+       
+       
+       function windowResize(ev) {
+               if (
+                       !ignoreWindowResize &&
+                       ev.target === window && // so we don't process jqui "resize" events that have bubbled up
+                       currentView.start // view has already been rendered
+               ) {
+                       if (updateSize(true)) {
+                               currentView.trigger('windowResize', _element);
+                       }
+               }
+       }
+       
+       
+       
+       /* Event Fetching/Rendering
+       -----------------------------------------------------------------------------*/
+       // TODO: going forward, most of this stuff should be directly handled by the view
+
+
+       function refetchEvents() { // can be called as an API method
+               destroyEvents(); // so that events are cleared before user starts waiting for AJAX
+               fetchAndRenderEvents();
+       }
+
+
+       function renderEvents() { // destroys old events if previously rendered
+               if (elementVisible()) {
+                       freezeContentHeight();
+                       currentView.displayEvents(events);
+                       unfreezeContentHeight();
+               }
+       }
+
+
+       function destroyEvents() {
+               freezeContentHeight();
+               currentView.clearEvents();
+               unfreezeContentHeight();
+       }
+       
+
+       function getAndRenderEvents() {
+               if (!options.lazyFetching || isFetchNeeded(currentView.start, currentView.end)) {
+                       fetchAndRenderEvents();
+               }
+               else {
+                       renderEvents();
+               }
+       }
+
+
+       function fetchAndRenderEvents() {
+               fetchEvents(currentView.start, currentView.end);
+                       // ... will call reportEvents
+                       // ... which will call renderEvents
+       }
+
+       
+       // called when event data arrives
+       function reportEvents(_events) {
+               events = _events;
+               renderEvents();
+       }
+
+
+       // called when a single event's data has been changed
+       function reportEventChange() {
+               renderEvents();
+       }
+
+
+
+       /* Header Updating
+       -----------------------------------------------------------------------------*/
+
+
+       function updateHeaderTitle() {
+               header.updateTitle(currentView.title);
+       }
+
+
+       function updateTodayButton() {
+               var now = t.getNow();
+               if (now.isWithin(currentView.intervalStart, currentView.intervalEnd)) {
+                       header.disableButton('today');
+               }
+               else {
+                       header.enableButton('today');
+               }
+       }
+       
+
+
+       /* Selection
+       -----------------------------------------------------------------------------*/
+       
+
+       // this public method receives start/end dates in any format, with any timezone
+       function select(zonedStartInput, zonedEndInput) {
+               currentView.select(
+                       t.buildSelectSpan.apply(t, arguments)
+               );
+       }
+       
+
+       function unselect() { // safe to be called before renderView
+               if (currentView) {
+                       currentView.unselect();
+               }
+       }
+       
+       
+       
+       /* Date
+       -----------------------------------------------------------------------------*/
+       
+       
+       function prev() {
+               date = currentView.computePrevDate(date);
+               renderView();
+       }
+       
+       
+       function next() {
+               date = currentView.computeNextDate(date);
+               renderView();
+       }
+       
+       
+       function prevYear() {
+               date.add(-1, 'years');
+               renderView();
+       }
+       
+       
+       function nextYear() {
+               date.add(1, 'years');
+               renderView();
+       }
+       
+       
+       function today() {
+               date = t.getNow();
+               renderView();
+       }
+       
+       
+       function gotoDate(zonedDateInput) {
+               date = t.moment(zonedDateInput).stripZone();
+               renderView();
+       }
+       
+       
+       function incrementDate(delta) {
+               date.add(moment.duration(delta));
+               renderView();
+       }
+
+
+       // Forces navigation to a view for the given date.
+       // `viewType` can be a specific view name or a generic one like "week" or "day".
+       function zoomTo(newDate, viewType) {
+               var spec;
+
+               viewType = viewType || 'day'; // day is default zoom
+               spec = t.getViewSpec(viewType) || t.getUnitViewSpec(viewType);
+
+               date = newDate.clone();
+               renderView(spec ? spec.type : null);
+       }
+       
+       
+       // for external API
+       function getDate() {
+               return t.applyTimezone(date); // infuse the calendar's timezone
+       }
+
+
+
+       /* Height "Freezing"
+       -----------------------------------------------------------------------------*/
+       // TODO: move this into the view
+
+       t.freezeContentHeight = freezeContentHeight;
+       t.unfreezeContentHeight = unfreezeContentHeight;
+
+
+       function freezeContentHeight() {
+               content.css({
+                       width: '100%',
+                       height: content.height(),
+                       overflow: 'hidden'
+               });
+       }
+
+
+       function unfreezeContentHeight() {
+               content.css({
+                       width: '',
+                       height: '',
+                       overflow: ''
+               });
+       }
+       
+       
+       
+       /* Misc
+       -----------------------------------------------------------------------------*/
+       
+
+       function getCalendar() {
+               return t;
+       }
+
+       
+       function getView() {
+               return currentView;
+       }
+       
+       
+       function option(name, value) {
+               if (value === undefined) {
+                       return options[name];
+               }
+               if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
+                       options[name] = value;
+                       updateSize(true); // true = allow recalculation of height
+               }
+       }
+       
+       
+       function trigger(name, thisObj) { // overrides the Emitter's trigger method :(
+               var args = Array.prototype.slice.call(arguments, 2);
+
+               thisObj = thisObj || _element;
+               this.triggerWith(name, thisObj, args); // Emitter's method
+
+               if (options[name]) {
+                       return options[name].apply(thisObj, args);
+               }
+       }
+
+       t.initialize();
+}
+
+;;
+
+Calendar.defaults = {
+
+       titleRangeSeparator: ' \u2014 ', // emphasized dash
+       monthYearFormat: 'MMMM YYYY', // required for en. other languages rely on datepicker computable option
+
+       defaultTimedEventDuration: '02:00:00',
+       defaultAllDayEventDuration: { days: 1 },
+       forceEventDuration: false,
+       nextDayThreshold: '09:00:00', // 9am
+
+       // display
+       defaultView: 'month',
+       aspectRatio: 1.35,
+       header: {
+               left: 'title',
+               center: '',
+               right: 'today prev,next'
+       },
+       weekends: true,
+       weekNumbers: false,
+
+       weekNumberTitle: 'W',
+       weekNumberCalculation: 'local',
+       
+       //editable: false,
+
+       //nowIndicator: false,
+
+       scrollTime: '06:00:00',
+       
+       // event ajax
+       lazyFetching: true,
+       startParam: 'start',
+       endParam: 'end',
+       timezoneParam: 'timezone',
+
+       timezone: false,
+
+       //allDayDefault: undefined,
+
+       // locale
+       isRTL: false,
+       buttonText: {
+               prev: "prev",
+               next: "next",
+               prevYear: "prev year",
+               nextYear: "next year",
+               year: 'year', // TODO: locale files need to specify this
+               today: 'today',
+               month: 'month',
+               week: 'week',
+               day: 'day'
+       },
+
+       buttonIcons: {
+               prev: 'left-single-arrow',
+               next: 'right-single-arrow',
+               prevYear: 'left-double-arrow',
+               nextYear: 'right-double-arrow'
+       },
+       
+       // jquery-ui theming
+       theme: false,
+       themeButtonIcons: {
+               prev: 'circle-triangle-w',
+               next: 'circle-triangle-e',
+               prevYear: 'seek-prev',
+               nextYear: 'seek-next'
+       },
+
+       //eventResizableFromStart: false,
+       dragOpacity: .75,
+       dragRevertDuration: 500,
+       dragScroll: true,
+       
+       //selectable: false,
+       unselectAuto: true,
+       
+       dropAccept: '*',
+
+       eventOrder: 'title',
+
+       eventLimit: false,
+       eventLimitText: 'more',
+       eventLimitClick: 'popover',
+       dayPopoverFormat: 'LL',
+       
+       handleWindowResize: true,
+       windowResizeDelay: 200, // milliseconds before an updateSize happens
+
+       longPressDelay: 1000
+       
+};
+
+
+Calendar.englishDefaults = { // used by lang.js
+       dayPopoverFormat: 'dddd, MMMM D'
+};
+
+
+Calendar.rtlDefaults = { // right-to-left defaults
+       header: { // TODO: smarter solution (first/center/last ?)
+               left: 'next,prev today',
+               center: '',
+               right: 'title'
+       },
+       buttonIcons: {
+               prev: 'right-single-arrow',
+               next: 'left-single-arrow',
+               prevYear: 'right-double-arrow',
+               nextYear: 'left-double-arrow'
+       },
+       themeButtonIcons: {
+               prev: 'circle-triangle-e',
+               next: 'circle-triangle-w',
+               nextYear: 'seek-prev',
+               prevYear: 'seek-next'
+       }
+};
+
+;;
+
+var langOptionHash = FC.langs = {}; // initialize and expose
+
+
+// TODO: document the structure and ordering of a FullCalendar lang file
+// TODO: rename everything "lang" to "locale", like what the moment project did
+
+
+// Initialize jQuery UI datepicker translations while using some of the translations
+// Will set this as the default language for datepicker.
+FC.datepickerLang = function(langCode, dpLangCode, dpOptions) {
+
+       // get the FullCalendar internal option hash for this language. create if necessary
+       var fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
+
+       // transfer some simple options from datepicker to fc
+       fcOptions.isRTL = dpOptions.isRTL;
+       fcOptions.weekNumberTitle = dpOptions.weekHeader;
+
+       // compute some more complex options from datepicker
+       $.each(dpComputableOptions, function(name, func) {
+               fcOptions[name] = func(dpOptions);
+       });
+
+       // is jQuery UI Datepicker is on the page?
+       if ($.datepicker) {
+
+               // Register the language data.
+               // FullCalendar and MomentJS use language codes like "pt-br" but Datepicker
+               // does it like "pt-BR" or if it doesn't have the language, maybe just "pt".
+               // Make an alias so the language can be referenced either way.
+               $.datepicker.regional[dpLangCode] =
+                       $.datepicker.regional[langCode] = // alias
+                               dpOptions;
+
+               // Alias 'en' to the default language data. Do this every time.
+               $.datepicker.regional.en = $.datepicker.regional[''];
+
+               // Set as Datepicker's global defaults.
+               $.datepicker.setDefaults(dpOptions);
+       }
+};
+
+
+// Sets FullCalendar-specific translations. Will set the language as the global default.
+FC.lang = function(langCode, newFcOptions) {
+       var fcOptions;
+       var momOptions;
+
+       // get the FullCalendar internal option hash for this language. create if necessary
+       fcOptions = langOptionHash[langCode] || (langOptionHash[langCode] = {});
+
+       // provided new options for this language? merge them in
+       if (newFcOptions) {
+               fcOptions = langOptionHash[langCode] = mergeOptions([ fcOptions, newFcOptions ]);
+       }
+
+       // compute language options that weren't defined.
+       // always do this. newFcOptions can be undefined when initializing from i18n file,
+       // so no way to tell if this is an initialization or a default-setting.
+       momOptions = getMomentLocaleData(langCode); // will fall back to en
+       $.each(momComputableOptions, function(name, func) {
+               if (fcOptions[name] == null) {
+                       fcOptions[name] = func(momOptions, fcOptions);
+               }
+       });
+
+       // set it as the default language for FullCalendar
+       Calendar.defaults.lang = langCode;
+};
+
+
+// NOTE: can't guarantee any of these computations will run because not every language has datepicker
+// configs, so make sure there are English fallbacks for these in the defaults file.
+var dpComputableOptions = {
+
+       buttonText: function(dpOptions) {
+               return {
+                       // the translations sometimes wrongly contain HTML entities
+                       prev: stripHtmlEntities(dpOptions.prevText),
+                       next: stripHtmlEntities(dpOptions.nextText),
+                       today: stripHtmlEntities(dpOptions.currentText)
+               };
+       },
+
+       // Produces format strings like "MMMM YYYY" -> "September 2014"
+       monthYearFormat: function(dpOptions) {
+               return dpOptions.showMonthAfterYear ?
+                       'YYYY[' + dpOptions.yearSuffix + '] MMMM' :
+                       'MMMM YYYY[' + dpOptions.yearSuffix + ']';
+       }
+
+};
+
+var momComputableOptions = {
+
+       // Produces format strings like "ddd M/D" -> "Fri 9/15"
+       dayOfMonthFormat: function(momOptions, fcOptions) {
+               var format = momOptions.longDateFormat('l'); // for the format like "M/D/YYYY"
+
+               // strip the year off the edge, as well as other misc non-whitespace chars
+               format = format.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g, '');
+
+               if (fcOptions.isRTL) {
+                       format += ' ddd'; // for RTL, add day-of-week to end
+               }
+               else {
+                       format = 'ddd ' + format; // for LTR, add day-of-week to beginning
+               }
+               return format;
+       },
+
+       // Produces format strings like "h:mma" -> "6:00pm"
+       mediumTimeFormat: function(momOptions) { // can't be called `timeFormat` because collides with option
+               return momOptions.longDateFormat('LT')
+                       .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+       },
+
+       // Produces format strings like "h(:mm)a" -> "6pm" / "6:30pm"
+       smallTimeFormat: function(momOptions) {
+               return momOptions.longDateFormat('LT')
+                       .replace(':mm', '(:mm)')
+                       .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+                       .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+       },
+
+       // Produces format strings like "h(:mm)t" -> "6p" / "6:30p"
+       extraSmallTimeFormat: function(momOptions) {
+               return momOptions.longDateFormat('LT')
+                       .replace(':mm', '(:mm)')
+                       .replace(/(\Wmm)$/, '($1)') // like above, but for foreign langs
+                       .replace(/\s*a$/i, 't'); // convert to AM/PM/am/pm to lowercase one-letter. remove any spaces beforehand
+       },
+
+       // Produces format strings like "ha" / "H" -> "6pm" / "18"
+       hourFormat: function(momOptions) {
+               return momOptions.longDateFormat('LT')
+                       .replace(':mm', '')
+                       .replace(/(\Wmm)$/, '') // like above, but for foreign langs
+                       .replace(/\s*a$/i, 'a'); // convert AM/PM/am/pm to lowercase. remove any spaces beforehand
+       },
+
+       // Produces format strings like "h:mm" -> "6:30" (with no AM/PM)
+       noMeridiemTimeFormat: function(momOptions) {
+               return momOptions.longDateFormat('LT')
+                       .replace(/\s*a$/i, ''); // remove trailing AM/PM
+       }
+
+};
+
+
+// options that should be computed off live calendar options (considers override options)
+// TODO: best place for this? related to lang?
+// TODO: flipping text based on isRTL is a bad idea because the CSS `direction` might want to handle it
+var instanceComputableOptions = {
+
+       // Produces format strings for results like "Mo 16"
+       smallDayDateFormat: function(options) {
+               return options.isRTL ?
+                       'D dd' :
+                       'dd D';
+       },
+
+       // Produces format strings for results like "Wk 5"
+       weekFormat: function(options) {
+               return options.isRTL ?
+                       'w[ ' + options.weekNumberTitle + ']' :
+                       '[' + options.weekNumberTitle + ' ]w';
+       },
+
+       // Produces format strings for results like "Wk5"
+       smallWeekFormat: function(options) {
+               return options.isRTL ?
+                       'w[' + options.weekNumberTitle + ']' :
+                       '[' + options.weekNumberTitle + ']w';
+       }
+
+};
+
+function populateInstanceComputableOptions(options) {
+       $.each(instanceComputableOptions, function(name, func) {
+               if (options[name] == null) {
+                       options[name] = func(options);
+               }
+       });
+}
+
+
+// Returns moment's internal locale data. If doesn't exist, returns English.
+// Works with moment-pre-2.8
+function getMomentLocaleData(langCode) {
+       var func = moment.localeData || moment.langData;
+       return func.call(moment, langCode) ||
+               func.call(moment, 'en'); // the newer localData could return null, so fall back to en
+}
+
+
+// Initialize English by forcing computation of moment-derived options.
+// Also, sets it as the default.
+FC.lang('en', Calendar.englishDefaults);
+
+;;
+
+/* Top toolbar area with buttons and title
+----------------------------------------------------------------------------------------------------------------------*/
+// TODO: rename all header-related things to "toolbar"
+
+function Header(calendar, options) {
+       var t = this;
+       
+       // exports
+       t.render = render;
+       t.removeElement = removeElement;
+       t.updateTitle = updateTitle;
+       t.activateButton = activateButton;
+       t.deactivateButton = deactivateButton;
+       t.disableButton = disableButton;
+       t.enableButton = enableButton;
+       t.getViewsWithButtons = getViewsWithButtons;
+       
+       // locals
+       var el = $();
+       var viewsWithButtons = [];
+       var tm;
+
+
+       function render() {
+               var sections = options.header;
+
+               tm = options.theme ? 'ui' : 'fc';
+
+               if (sections) {
+                       el = $("<div class='fc-toolbar'/>")
+                               .append(renderSection('left'))
+                               .append(renderSection('right'))
+                               .append(renderSection('center'))
+                               .append('<div class="fc-clear"/>');
+
+                       return el;
+               }
+       }
+       
+       
+       function removeElement() {
+               el.remove();
+               el = $();
+       }
+       
+       
+       function renderSection(position) {
+               var sectionEl = $('<div class="fc-' + position + '"/>');
+               var buttonStr = options.header[position];
+
+               if (buttonStr) {
+                       $.each(buttonStr.split(' '), function(i) {
+                               var groupChildren = $();
+                               var isOnlyButtons = true;
+                               var groupEl;
+
+                               $.each(this.split(','), function(j, buttonName) {
+                                       var customButtonProps;
+                                       var viewSpec;
+                                       var buttonClick;
+                                       var overrideText; // text explicitly set by calendar's constructor options. overcomes icons
+                                       var defaultText;
+                                       var themeIcon;
+                                       var normalIcon;
+                                       var innerHtml;
+                                       var classes;
+                                       var button; // the element
+
+                                       if (buttonName == 'title') {
+                                               groupChildren = groupChildren.add($('<h2>&nbsp;</h2>')); // we always want it to take up height
+                                               isOnlyButtons = false;
+                                       }
+                                       else {
+                                               if ((customButtonProps = (calendar.options.customButtons || {})[buttonName])) {
+                                                       buttonClick = function(ev) {
+                                                               if (customButtonProps.click) {
+                                                                       customButtonProps.click.call(button[0], ev);
+                                                               }
+                                                       };
+                                                       overrideText = ''; // icons will override text
+                                                       defaultText = customButtonProps.text;
+                                               }
+                                               else if ((viewSpec = calendar.getViewSpec(buttonName))) {
+                                                       buttonClick = function() {
+                                                               calendar.changeView(buttonName);
+                                                       };
+                                                       viewsWithButtons.push(buttonName);
+                                                       overrideText = viewSpec.buttonTextOverride;
+                                                       defaultText = viewSpec.buttonTextDefault;
+                                               }
+                                               else if (calendar[buttonName]) { // a calendar method
+                                                       buttonClick = function() {
+                                                               calendar[buttonName]();
+                                                       };
+                                                       overrideText = (calendar.overrides.buttonText || {})[buttonName];
+                                                       defaultText = options.buttonText[buttonName]; // everything else is considered default
+                                               }
+
+                                               if (buttonClick) {
+
+                                                       themeIcon =
+                                                               customButtonProps ?
+                                                                       customButtonProps.themeIcon :
+                                                                       options.themeButtonIcons[buttonName];
+
+                                                       normalIcon =
+                                                               customButtonProps ?
+                                                                       customButtonProps.icon :
+                                                                       options.buttonIcons[buttonName];
+
+                                                       if (overrideText) {
+                                                               innerHtml = htmlEscape(overrideText);
+                                                       }
+                                                       else if (themeIcon && options.theme) {
+                                                               innerHtml = "<span class='ui-icon ui-icon-" + themeIcon + "'></span>";
+                                                       }
+                                                       else if (normalIcon && !options.theme) {
+                                                               innerHtml = "<span class='fc-icon fc-icon-" + normalIcon + "'></span>";
+                                                       }
+                                                       else {
+                                                               innerHtml = htmlEscape(defaultText);
+                                                       }
+
+                                                       classes = [
+                                                               'fc-' + buttonName + '-button',
+                                                               tm + '-button',
+                                                               tm + '-state-default'
+                                                       ];
+
+                                                       button = $( // type="button" so that it doesn't submit a form
+                                                               '<button type="button" class="' + classes.join(' ') + '">' +
+                                                                       innerHtml +
+                                                               '</button>'
+                                                               )
+                                                               .click(function(ev) {
+                                                                       // don't process clicks for disabled buttons
+                                                                       if (!button.hasClass(tm + '-state-disabled')) {
+
+                                                                               buttonClick(ev);
+
+                                                                               // after the click action, if the button becomes the "active" tab, or disabled,
+                                                                               // it should never have a hover class, so remove it now.
+                                                                               if (
+                                                                                       button.hasClass(tm + '-state-active') ||
+                                                                                       button.hasClass(tm + '-state-disabled')
+                                                                               ) {
+                                                                                       button.removeClass(tm + '-state-hover');
+                                                                               }
+                                                                       }
+                                                               })
+                                                               .mousedown(function() {
+                                                                       // the *down* effect (mouse pressed in).
+                                                                       // only on buttons that are not the "active" tab, or disabled
+                                                                       button
+                                                                               .not('.' + tm + '-state-active')
+                                                                               .not('.' + tm + '-state-disabled')
+                                                                               .addClass(tm + '-state-down');
+                                                               })
+                                                               .mouseup(function() {
+                                                                       // undo the *down* effect
+                                                                       button.removeClass(tm + '-state-down');
+                                                               })
+                                                               .hover(
+                                                                       function() {
+                                                                               // the *hover* effect.
+                                                                               // only on buttons that are not the "active" tab, or disabled
+                                                                               button
+                                                                                       .not('.' + tm + '-state-active')
+                                                                                       .not('.' + tm + '-state-disabled')
+                                                                                       .addClass(tm + '-state-hover');
+                                                                       },
+                                                                       function() {
+                                                                               // undo the *hover* effect
+                                                                               button
+                                                                                       .removeClass(tm + '-state-hover')
+                                                                                       .removeClass(tm + '-state-down'); // if mouseleave happens before mouseup
+                                                                       }
+                                                               );
+
+                                                       groupChildren = groupChildren.add(button);
+                                               }
+                                       }
+                               });
+
+                               if (isOnlyButtons) {
+                                       groupChildren
+                                               .first().addClass(tm + '-corner-left').end()
+                                               .last().addClass(tm + '-corner-right').end();
+                               }
+
+                               if (groupChildren.length > 1) {
+                                       groupEl = $('<div/>');
+                                       if (isOnlyButtons) {
+                                               groupEl.addClass('fc-button-group');
+                                       }
+                                       groupEl.append(groupChildren);
+                                       sectionEl.append(groupEl);
+                               }
+                               else {
+                                       sectionEl.append(groupChildren); // 1 or 0 children
+                               }
+                       });
+               }
+
+               return sectionEl;
+       }
+       
+       
+       function updateTitle(text) {
+               el.find('h2').text(text);
+       }
+       
+       
+       function activateButton(buttonName) {
+               el.find('.fc-' + buttonName + '-button')
+                       .addClass(tm + '-state-active');
+       }
+       
+       
+       function deactivateButton(buttonName) {
+               el.find('.fc-' + buttonName + '-button')
+                       .removeClass(tm + '-state-active');
+       }
+       
+       
+       function disableButton(buttonName) {
+               el.find('.fc-' + buttonName + '-button')
+                       .attr('disabled', 'disabled')
+                       .addClass(tm + '-state-disabled');
+       }
+       
+       
+       function enableButton(buttonName) {
+               el.find('.fc-' + buttonName + '-button')
+                       .removeAttr('disabled')
+                       .removeClass(tm + '-state-disabled');
+       }
+
+
+       function getViewsWithButtons() {
+               return viewsWithButtons;
+       }
+
+}
+
+;;
+
+FC.sourceNormalizers = [];
+FC.sourceFetchers = [];
+
+var ajaxDefaults = {
+       dataType: 'json',
+       cache: false
+};
+
+var eventGUID = 1;
+
+
+function EventManager(options) { // assumed to be a calendar
+       var t = this;
+       
+       
+       // exports
+       t.isFetchNeeded = isFetchNeeded;
+       t.fetchEvents = fetchEvents;
+       t.addEventSource = addEventSource;
+       t.removeEventSource = removeEventSource;
+       t.updateEvent = updateEvent;
+       t.renderEvent = renderEvent;
+       t.removeEvents = removeEvents;
+       t.clientEvents = clientEvents;
+       t.mutateEvent = mutateEvent;
+       t.normalizeEventDates = normalizeEventDates;
+       t.normalizeEventTimes = normalizeEventTimes;
+       
+       
+       // imports
+       var reportEvents = t.reportEvents;
+       
+       
+       // locals
+       var stickySource = { events: [] };
+       var sources = [ stickySource ];
+       var rangeStart, rangeEnd;
+       var currentFetchID = 0;
+       var pendingSourceCnt = 0;
+       var cache = []; // holds events that have already been expanded
+
+
+       $.each(
+               (options.events ? [ options.events ] : []).concat(options.eventSources || []),
+               function(i, sourceInput) {
+                       var source = buildEventSource(sourceInput);
+                       if (source) {
+                               sources.push(source);
+                       }
+               }
+       );
+       
+       
+       
+       /* Fetching
+       -----------------------------------------------------------------------------*/
+
+
+       // start and end are assumed to be unzoned
+       function isFetchNeeded(start, end) {
+               return !rangeStart || // nothing has been fetched yet?
+                       start < rangeStart || end > rangeEnd; // is part of the new range outside of the old range?
+       }
+       
+       
+       function fetchEvents(start, end) {
+               rangeStart = start;
+               rangeEnd = end;
+               cache = [];
+               var fetchID = ++currentFetchID;
+               var len = sources.length;
+               pendingSourceCnt = len;
+               for (var i=0; i<len; i++) {
+                       fetchEventSource(sources[i], fetchID);
+               }
+       }
+       
+       
+       function fetchEventSource(source, fetchID) {
+               _fetchEventSource(source, function(eventInputs) {
+                       var isArraySource = $.isArray(source.events);
+                       var i, eventInput;
+                       var abstractEvent;
+
+                       if (fetchID == currentFetchID) {
+
+                               if (eventInputs) {
+                                       for (i = 0; i < eventInputs.length; i++) {
+                                               eventInput = eventInputs[i];
+
+                                               if (isArraySource) { // array sources have already been convert to Event Objects
+                                                       abstractEvent = eventInput;
+                                               }
+                                               else {
+                                                       abstractEvent = buildEventFromInput(eventInput, source);
+                                               }
+
+                                               if (abstractEvent) { // not false (an invalid event)
+                                                       cache.push.apply(
+                                                               cache,
+                                                               expandEvent(abstractEvent) // add individual expanded events to the cache
+                                                       );
+                                               }
+                                       }
+                               }
+
+                               pendingSourceCnt--;
+                               if (!pendingSourceCnt) {
+                                       reportEvents(cache);
+                               }
+                       }
+               });
+       }
+       
+       
+       function _fetchEventSource(source, callback) {
+               var i;
+               var fetchers = FC.sourceFetchers;
+               var res;
+
+               for (i=0; i<fetchers.length; i++) {
+                       res = fetchers[i].call(
+                               t, // this, the Calendar object
+                               source,
+                               rangeStart.clone(),
+                               rangeEnd.clone(),
+                               options.timezone,
+                               callback
+                       );
+
+                       if (res === true) {
+                               // the fetcher is in charge. made its own async request
+                               return;
+                       }
+                       else if (typeof res == 'object') {
+                               // the fetcher returned a new source. process it
+                               _fetchEventSource(res, callback);
+                               return;
+                       }
+               }
+
+               var events = source.events;
+               if (events) {
+                       if ($.isFunction(events)) {
+                               t.pushLoading();
+                               events.call(
+                                       t, // this, the Calendar object
+                                       rangeStart.clone(),
+                                       rangeEnd.clone(),
+                                       options.timezone,
+                                       function(events) {
+                                               callback(events);
+                                               t.popLoading();
+                                       }
+                               );
+                       }
+                       else if ($.isArray(events)) {
+                               callback(events);
+                       }
+                       else {
+                               callback();
+                       }
+               }else{
+                       var url = source.url;
+                       if (url) {
+                               var success = source.success;
+                               var error = source.error;
+                               var complete = source.complete;
+
+                               // retrieve any outbound GET/POST $.ajax data from the options
+                               var customData;
+                               if ($.isFunction(source.data)) {
+                                       // supplied as a function that returns a key/value object
+                                       customData = source.data();
+                               }
+                               else {
+                                       // supplied as a straight key/value object
+                                       customData = source.data;
+                               }
+
+                               // use a copy of the custom data so we can modify the parameters
+                               // and not affect the passed-in object.
+                               var data = $.extend({}, customData || {});
+
+                               var startParam = firstDefined(source.startParam, options.startParam);
+                               var endParam = firstDefined(source.endParam, options.endParam);
+                               var timezoneParam = firstDefined(source.timezoneParam, options.timezoneParam);
+
+                               if (startParam) {
+                                       data[startParam] = rangeStart.format();
+                               }
+                               if (endParam) {
+                                       data[endParam] = rangeEnd.format();
+                               }
+                               if (options.timezone && options.timezone != 'local') {
+                                       data[timezoneParam] = options.timezone;
+                               }
+
+                               t.pushLoading();
+                               $.ajax($.extend({}, ajaxDefaults, source, {
+                                       data: data,
+                                       success: function(events) {
+                                               events = events || [];
+                                               var res = applyAll(success, this, arguments);
+                                               if ($.isArray(res)) {
+                                                       events = res;
+                                               }
+                                               callback(events);
+                                       },
+                                       error: function() {
+                                               applyAll(error, this, arguments);
+                                               callback();
+                                       },
+                                       complete: function() {
+                                               applyAll(complete, this, arguments);
+                                               t.popLoading();
+                                       }
+                               }));
+                       }else{
+                               callback();
+                       }
+               }
+       }
+       
+       
+       
+       /* Sources
+       -----------------------------------------------------------------------------*/
+       
+
+       function addEventSource(sourceInput) {
+               var source = buildEventSource(sourceInput);
+               if (source) {
+                       sources.push(source);
+                       pendingSourceCnt++;
+                       fetchEventSource(source, currentFetchID); // will eventually call reportEvents
+               }
+       }
+
+
+       function buildEventSource(sourceInput) { // will return undefined if invalid source
+               var normalizers = FC.sourceNormalizers;
+               var source;
+               var i;
+
+               if ($.isFunction(sourceInput) || $.isArray(sourceInput)) {
+                       source = { events: sourceInput };
+               }
+               else if (typeof sourceInput === 'string') {
+                       source = { url: sourceInput };
+               }
+               else if (typeof sourceInput === 'object') {
+                       source = $.extend({}, sourceInput); // shallow copy
+               }
+
+               if (source) {
+
+                       // TODO: repeat code, same code for event classNames
+                       if (source.className) {
+                               if (typeof source.className === 'string') {
+                                       source.className = source.className.split(/\s+/);
+                               }
+                               // otherwise, assumed to be an array
+                       }
+                       else {
+                               source.className = [];
+                       }
+
+                       // for array sources, we convert to standard Event Objects up front
+                       if ($.isArray(source.events)) {
+                               source.origArray = source.events; // for removeEventSource
+                               source.events = $.map(source.events, function(eventInput) {
+                                       return buildEventFromInput(eventInput, source);
+                               });
+                       }
+
+                       for (i=0; i<normalizers.length; i++) {
+                               normalizers[i].call(t, source);
+                       }
+
+                       return source;
+               }
+       }
+
+
+       function removeEventSource(source) {
+               sources = $.grep(sources, function(src) {
+                       return !isSourcesEqual(src, source);
+               });
+               // remove all client events from that source
+               cache = $.grep(cache, function(e) {
+                       return !isSourcesEqual(e.source, source);
+               });
+               reportEvents(cache);
+       }
+
+
+       function isSourcesEqual(source1, source2) {
+               return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
+       }
+
+
+       function getSourcePrimitive(source) {
+               return (
+                       (typeof source === 'object') ? // a normalized event source?
+                               (source.origArray || source.googleCalendarId || source.url || source.events) : // get the primitive
+                               null
+               ) ||
+               source; // the given argument *is* the primitive
+       }
+       
+       
+       
+       /* Manipulation
+       -----------------------------------------------------------------------------*/
+
+
+       // Only ever called from the externally-facing API
+       function updateEvent(event) {
+
+               // massage start/end values, even if date string values
+               event.start = t.moment(event.start);
+               if (event.end) {
+                       event.end = t.moment(event.end);
+               }
+               else {
+                       event.end = null;
+               }
+
+               mutateEvent(event, getMiscEventProps(event)); // will handle start/end/allDay normalization
+               reportEvents(cache); // reports event modifications (so we can redraw)
+       }
+
+
+       // Returns a hash of misc event properties that should be copied over to related events.
+       function getMiscEventProps(event) {
+               var props = {};
+
+               $.each(event, function(name, val) {
+                       if (isMiscEventPropName(name)) {
+                               if (val !== undefined && isAtomic(val)) { // a defined non-object
+                                       props[name] = val;
+                               }
+                       }
+               });
+
+               return props;
+       }
+
+       // non-date-related, non-id-related, non-secret
+       function isMiscEventPropName(name) {
+               return !/^_|^(id|allDay|start|end)$/.test(name);
+       }
+
+       
+       // returns the expanded events that were created
+       function renderEvent(eventInput, stick) {
+               var abstractEvent = buildEventFromInput(eventInput);
+               var events;
+               var i, event;
+
+               if (abstractEvent) { // not false (a valid input)
+                       events = expandEvent(abstractEvent);
+
+                       for (i = 0; i < events.length; i++) {
+                               event = events[i];
+
+                               if (!event.source) {
+                                       if (stick) {
+                                               stickySource.events.push(event);
+                                               event.source = stickySource;
+                                       }
+                                       cache.push(event);
+                               }
+                       }
+
+                       reportEvents(cache);
+
+                       return events;
+               }
+
+               return [];
+       }
+       
+       
+       function removeEvents(filter) {
+               var eventID;
+               var i;
+
+               if (filter == null) { // null or undefined. remove all events
+                       filter = function() { return true; }; // will always match
+               }
+               else if (!$.isFunction(filter)) { // an event ID
+                       eventID = filter + '';
+                       filter = function(event) {
+                               return event._id == eventID;
+                       };
+               }
+
+               // Purge event(s) from our local cache
+               cache = $.grep(cache, filter, true); // inverse=true
+
+               // Remove events from array sources.
+               // This works because they have been converted to official Event Objects up front.
+               // (and as a result, event._id has been calculated).
+               for (i=0; i<sources.length; i++) {
+                       if ($.isArray(sources[i].events)) {
+                               sources[i].events = $.grep(sources[i].events, filter, true);
+                       }
+               }
+
+               reportEvents(cache);
+       }
+       
+       
+       function clientEvents(filter) {
+               if ($.isFunction(filter)) {
+                       return $.grep(cache, filter);
+               }
+               else if (filter != null) { // not null, not undefined. an event ID
+                       filter += '';
+                       return $.grep(cache, function(e) {
+                               return e._id == filter;
+                       });
+               }
+               return cache; // else, return all
+       }
+       
+       
+       
+       /* Event Normalization
+       -----------------------------------------------------------------------------*/
+
+
+       // Given a raw object with key/value properties, returns an "abstract" Event object.
+       // An "abstract" event is an event that, if recurring, will not have been expanded yet.
+       // Will return `false` when input is invalid.
+       // `source` is optional
+       function buildEventFromInput(input, source) {
+               var out = {};
+               var start, end;
+               var allDay;
+
+               if (options.eventDataTransform) {
+                       input = options.eventDataTransform(input);
+               }
+               if (source && source.eventDataTransform) {
+                       input = source.eventDataTransform(input);
+               }
+
+               // Copy all properties over to the resulting object.
+               // The special-case properties will be copied over afterwards.
+               $.extend(out, input);
+
+               if (source) {
+                       out.source = source;
+               }
+
+               out._id = input._id || (input.id === undefined ? '_fc' + eventGUID++ : input.id + '');
+
+               if (input.className) {
+                       if (typeof input.className == 'string') {
+                               out.className = input.className.split(/\s+/);
+                       }
+                       else { // assumed to be an array
+                               out.className = input.className;
+                       }
+               }
+               else {
+                       out.className = [];
+               }
+
+               start = input.start || input.date; // "date" is an alias for "start"
+               end = input.end;
+
+               // parse as a time (Duration) if applicable
+               if (isTimeString(start)) {
+                       start = moment.duration(start);
+               }
+               if (isTimeString(end)) {
+                       end = moment.duration(end);
+               }
+
+               if (input.dow || moment.isDuration(start) || moment.isDuration(end)) {
+
+                       // the event is "abstract" (recurring) so don't calculate exact start/end dates just yet
+                       out.start = start ? moment.duration(start) : null; // will be a Duration or null
+                       out.end = end ? moment.duration(end) : null; // will be a Duration or null
+                       out._recurring = true; // our internal marker
+               }
+               else {
+
+                       if (start) {
+                               start = t.moment(start);
+                               if (!start.isValid()) {
+                                       return false;
+                               }
+                       }
+
+                       if (end) {
+                               end = t.moment(end);
+                               if (!end.isValid()) {
+                                       end = null; // let defaults take over
+                               }
+                       }
+
+                       allDay = input.allDay;
+                       if (allDay === undefined) { // still undefined? fallback to default
+                               allDay = firstDefined(
+                                       source ? source.allDayDefault : undefined,
+                                       options.allDayDefault
+                               );
+                               // still undefined? normalizeEventDates will calculate it
+                       }
+
+                       assignDatesToEvent(start, end, allDay, out);
+               }
+
+               return out;
+       }
+
+
+       // Normalizes and assigns the given dates to the given partially-formed event object.
+       // NOTE: mutates the given start/end moments. does not make a copy.
+       function assignDatesToEvent(start, end, allDay, event) {
+               event.start = start;
+               event.end = end;
+               event.allDay = allDay;
+               normalizeEventDates(event);
+               backupEventDates(event);
+       }
+
+
+       // Ensures proper values for allDay/start/end. Accepts an Event object, or a plain object with event-ish properties.
+       // NOTE: Will modify the given object.
+       function normalizeEventDates(eventProps) {
+
+               normalizeEventTimes(eventProps);
+
+               if (eventProps.end && !eventProps.end.isAfter(eventProps.start)) {
+                       eventProps.end = null;
+               }
+
+               if (!eventProps.end) {
+                       if (options.forceEventDuration) {
+                               eventProps.end = t.getDefaultEventEnd(eventProps.allDay, eventProps.start);
+                       }
+                       else {
+                               eventProps.end = null;
+                       }
+               }
+       }
+
+
+       // Ensures the allDay property exists and the timeliness of the start/end dates are consistent
+       function normalizeEventTimes(eventProps) {
+               if (eventProps.allDay == null) {
+                       eventProps.allDay = !(eventProps.start.hasTime() || (eventProps.end && eventProps.end.hasTime()));
+               }
+
+               if (eventProps.allDay) {
+                       eventProps.start.stripTime();
+                       if (eventProps.end) {
+                               // TODO: consider nextDayThreshold here? If so, will require a lot of testing and adjustment
+                               eventProps.end.stripTime();
+                       }
+               }
+               else {
+                       if (!eventProps.start.hasTime()) {
+                               eventProps.start = t.applyTimezone(eventProps.start.time(0)); // will assign a 00:00 time
+                       }
+                       if (eventProps.end && !eventProps.end.hasTime()) {
+                               eventProps.end = t.applyTimezone(eventProps.end.time(0)); // will assign a 00:00 time
+                       }
+               }
+       }
+
+
+       // If the given event is a recurring event, break it down into an array of individual instances.
+       // If not a recurring event, return an array with the single original event.
+       // If given a falsy input (probably because of a failed buildEventFromInput call), returns an empty array.
+       // HACK: can override the recurring window by providing custom rangeStart/rangeEnd (for businessHours).
+       function expandEvent(abstractEvent, _rangeStart, _rangeEnd) {
+               var events = [];
+               var dowHash;
+               var dow;
+               var i;
+               var date;
+               var startTime, endTime;
+               var start, end;
+               var event;
+
+               _rangeStart = _rangeStart || rangeStart;
+               _rangeEnd = _rangeEnd || rangeEnd;
+
+               if (abstractEvent) {
+                       if (abstractEvent._recurring) {
+
+                               // make a boolean hash as to whether the event occurs on each day-of-week
+                               if ((dow = abstractEvent.dow)) {
+                                       dowHash = {};
+                                       for (i = 0; i < dow.length; i++) {
+                                               dowHash[dow[i]] = true;
+                                       }
+                               }
+
+                               // iterate through every day in the current range
+                               date = _rangeStart.clone().stripTime(); // holds the date of the current day
+                               while (date.isBefore(_rangeEnd)) {
+
+                                       if (!dowHash || dowHash[date.day()]) { // if everyday, or this particular day-of-week
+
+                                               startTime = abstractEvent.start; // the stored start and end properties are times (Durations)
+                                               endTime = abstractEvent.end; // "
+                                               start = date.clone();
+                                               end = null;
+
+                                               if (startTime) {
+                                                       start = start.time(startTime);
+                                               }
+                                               if (endTime) {
+                                                       end = date.clone().time(endTime);
+                                               }
+
+                                               event = $.extend({}, abstractEvent); // make a copy of the original
+                                               assignDatesToEvent(
+                                                       start, end,
+                                                       !startTime && !endTime, // allDay?
+                                                       event
+                                               );
+                                               events.push(event);
+                                       }
+
+                                       date.add(1, 'days');
+                               }
+                       }
+                       else {
+                               events.push(abstractEvent); // return the original event. will be a one-item array
+                       }
+               }
+
+               return events;
+       }
+
+
+
+       /* Event Modification Math
+       -----------------------------------------------------------------------------------------*/
+
+
+       // Modifies an event and all related events by applying the given properties.
+       // Special date-diffing logic is used for manipulation of dates.
+       // If `props` does not contain start/end dates, the updated values are assumed to be the event's current start/end.
+       // All date comparisons are done against the event's pristine _start and _end dates.
+       // Returns an object with delta information and a function to undo all operations.
+       // For making computations in a granularity greater than day/time, specify largeUnit.
+       // NOTE: The given `newProps` might be mutated for normalization purposes.
+       function mutateEvent(event, newProps, largeUnit) {
+               var miscProps = {};
+               var oldProps;
+               var clearEnd;
+               var startDelta;
+               var endDelta;
+               var durationDelta;
+               var undoFunc;
+
+               // diffs the dates in the appropriate way, returning a duration
+               function diffDates(date1, date0) { // date1 - date0
+                       if (largeUnit) {
+                               return diffByUnit(date1, date0, largeUnit);
+                       }
+                       else if (newProps.allDay) {
+                               return diffDay(date1, date0);
+                       }
+                       else {
+                               return diffDayTime(date1, date0);
+                       }
+               }
+
+               newProps = newProps || {};
+
+               // normalize new date-related properties
+               if (!newProps.start) {
+                       newProps.start = event.start.clone();
+               }
+               if (newProps.end === undefined) {
+                       newProps.end = event.end ? event.end.clone() : null;
+               }
+               if (newProps.allDay == null) { // is null or undefined?
+                       newProps.allDay = event.allDay;
+               }
+               normalizeEventDates(newProps);
+
+               // create normalized versions of the original props to compare against
+               // need a real end value, for diffing
+               oldProps = {
+                       start: event._start.clone(),
+                       end: event._end ? event._end.clone() : t.getDefaultEventEnd(event._allDay, event._start),
+                       allDay: newProps.allDay // normalize the dates in the same regard as the new properties
+               };
+               normalizeEventDates(oldProps);
+
+               // need to clear the end date if explicitly changed to null
+               clearEnd = event._end !== null && newProps.end === null;
+
+               // compute the delta for moving the start date
+               startDelta = diffDates(newProps.start, oldProps.start);
+
+               // compute the delta for moving the end date
+               if (newProps.end) {
+                       endDelta = diffDates(newProps.end, oldProps.end);
+                       durationDelta = endDelta.subtract(startDelta);
+               }
+               else {
+                       durationDelta = null;
+               }
+
+               // gather all non-date-related properties
+               $.each(newProps, function(name, val) {
+                       if (isMiscEventPropName(name)) {
+                               if (val !== undefined) {
+                                       miscProps[name] = val;
+                               }
+                       }
+               });
+
+               // apply the operations to the event and all related events
+               undoFunc = mutateEvents(
+                       clientEvents(event._id), // get events with this ID
+                       clearEnd,
+                       newProps.allDay,
+                       startDelta,
+                       durationDelta,
+                       miscProps
+               );
+
+               return {
+                       dateDelta: startDelta,
+                       durationDelta: durationDelta,
+                       undo: undoFunc
+               };
+       }
+
+
+       // Modifies an array of events in the following ways (operations are in order):
+       // - clear the event's `end`
+       // - convert the event to allDay
+       // - add `dateDelta` to the start and end
+       // - add `durationDelta` to the event's duration
+       // - assign `miscProps` to the event
+       //
+       // Returns a function that can be called to undo all the operations.
+       //
+       // TODO: don't use so many closures. possible memory issues when lots of events with same ID.
+       //
+       function mutateEvents(events, clearEnd, allDay, dateDelta, durationDelta, miscProps) {
+               var isAmbigTimezone = t.getIsAmbigTimezone();
+               var undoFunctions = [];
+
+               // normalize zero-length deltas to be null
+               if (dateDelta && !dateDelta.valueOf()) { dateDelta = null; }
+               if (durationDelta && !durationDelta.valueOf()) { durationDelta = null; }
+
+               $.each(events, function(i, event) {
+                       var oldProps;
+                       var newProps;
+
+                       // build an object holding all the old values, both date-related and misc.
+                       // for the undo function.
+                       oldProps = {
+                               start: event.start.clone(),
+                               end: event.end ? event.end.clone() : null,
+                               allDay: event.allDay
+                       };
+                       $.each(miscProps, function(name) {
+                               oldProps[name] = event[name];
+                       });
+
+                       // new date-related properties. work off the original date snapshot.
+                       // ok to use references because they will be thrown away when backupEventDates is called.
+                       newProps = {
+                               start: event._start,
+                               end: event._end,
+                               allDay: allDay // normalize the dates in the same regard as the new properties
+                       };
+                       normalizeEventDates(newProps); // massages start/end/allDay
+
+                       // strip or ensure the end date
+                       if (clearEnd) {
+                               newProps.end = null;
+                       }
+                       else if (durationDelta && !newProps.end) { // the duration translation requires an end date
+                               newProps.end = t.getDefaultEventEnd(newProps.allDay, newProps.start);
+                       }
+
+                       if (dateDelta) {
+                               newProps.start.add(dateDelta);
+                               if (newProps.end) {
+                                       newProps.end.add(dateDelta);
+                               }
+                       }
+
+                       if (durationDelta) {
+                               newProps.end.add(durationDelta); // end already ensured above
+                       }
+
+                       // if the dates have changed, and we know it is impossible to recompute the
+                       // timezone offsets, strip the zone.
+                       if (
+                               isAmbigTimezone &&
+                               !newProps.allDay &&
+                               (dateDelta || durationDelta)
+                       ) {
+                               newProps.start.stripZone();
+                               if (newProps.end) {
+                                       newProps.end.stripZone();
+                               }
+                       }
+
+                       $.extend(event, miscProps, newProps); // copy over misc props, then date-related props
+                       backupEventDates(event); // regenerate internal _start/_end/_allDay
+
+                       undoFunctions.push(function() {
+                               $.extend(event, oldProps);
+                               backupEventDates(event); // regenerate internal _start/_end/_allDay
+                       });
+               });
+
+               return function() {
+                       for (var i = 0; i < undoFunctions.length; i++) {
+                               undoFunctions[i]();
+                       }
+               };
+       }
+
+
+       /* Business Hours
+       -----------------------------------------------------------------------------------------*/
+
+       t.getBusinessHoursEvents = getBusinessHoursEvents;
+
+
+       // Returns an array of events as to when the business hours occur in the given view.
+       // Abuse of our event system :(
+       function getBusinessHoursEvents(wholeDay) {
+               var optionVal = options.businessHours;
+               var defaultVal = {
+                       className: 'fc-nonbusiness',
+                       start: '09:00',
+                       end: '17:00',
+                       dow: [ 1, 2, 3, 4, 5 ], // monday - friday
+                       rendering: 'inverse-background'
+               };
+               var view = t.getView();
+               var eventInput;
+
+               if (optionVal) { // `true` (which means "use the defaults") or an override object
+                       eventInput = $.extend(
+                               {}, // copy to a new object in either case
+                               defaultVal,
+                               typeof optionVal === 'object' ? optionVal : {} // override the defaults
+                       );
+               }
+
+               if (eventInput) {
+
+                       // if a whole-day series is requested, clear the start/end times
+                       if (wholeDay) {
+                               eventInput.start = null;
+                               eventInput.end = null;
+                       }
+
+                       return expandEvent(
+                               buildEventFromInput(eventInput),
+                               view.start,
+                               view.end
+                       );
+               }
+
+               return [];
+       }
+
+
+       /* Overlapping / Constraining
+       -----------------------------------------------------------------------------------------*/
+
+       t.isEventSpanAllowed = isEventSpanAllowed;
+       t.isExternalSpanAllowed = isExternalSpanAllowed;
+       t.isSelectionSpanAllowed = isSelectionSpanAllowed;
+
+
+       // Determines if the given event can be relocated to the given span (unzoned start/end with other misc data)
+       function isEventSpanAllowed(span, event) {
+               var source = event.source || {};
+               var constraint = firstDefined(
+                       event.constraint,
+                       source.constraint,
+                       options.eventConstraint
+               );
+               var overlap = firstDefined(
+                       event.overlap,
+                       source.overlap,
+                       options.eventOverlap
+               );
+               return isSpanAllowed(span, constraint, overlap, event);
+       }
+
+
+       // Determines if an external event can be relocated to the given span (unzoned start/end with other misc data)
+       function isExternalSpanAllowed(eventSpan, eventLocation, eventProps) {
+               var eventInput;
+               var event;
+
+               // note: very similar logic is in View's reportExternalDrop
+               if (eventProps) {
+                       eventInput = $.extend({}, eventProps, eventLocation);
+                       event = expandEvent(buildEventFromInput(eventInput))[0];
+               }
+
+               if (event) {
+                       return isEventSpanAllowed(eventSpan, event);
+               }
+               else { // treat it as a selection
+
+                       return isSelectionSpanAllowed(eventSpan);
+               }
+       }
+
+
+       // Determines the given span (unzoned start/end with other misc data) can be selected.
+       function isSelectionSpanAllowed(span) {
+               return isSpanAllowed(span, options.selectConstraint, options.selectOverlap);
+       }
+
+
+       // Returns true if the given span (caused by an event drop/resize or a selection) is allowed to exist
+       // according to the constraint/overlap settings.
+       // `event` is not required if checking a selection.
+       function isSpanAllowed(span, constraint, overlap, event) {
+               var constraintEvents;
+               var anyContainment;
+               var peerEvents;
+               var i, peerEvent;
+               var peerOverlap;
+
+               // the range must be fully contained by at least one of produced constraint events
+               if (constraint != null) {
+
+                       // not treated as an event! intermediate data structure
+                       // TODO: use ranges in the future
+                       constraintEvents = constraintToEvents(constraint);
+
+                       anyContainment = false;
+                       for (i = 0; i < constraintEvents.length; i++) {
+                               if (eventContainsRange(constraintEvents[i], span)) {
+                                       anyContainment = true;
+                                       break;
+                               }
+                       }
+
+                       if (!anyContainment) {
+                               return false;
+                       }
+               }
+
+               peerEvents = t.getPeerEvents(span, event);
+
+               for (i = 0; i < peerEvents.length; i++)  {
+                       peerEvent = peerEvents[i];
+
+                       // there needs to be an actual intersection before disallowing anything
+                       if (eventIntersectsRange(peerEvent, span)) {
+
+                               // evaluate overlap for the given range and short-circuit if necessary
+                               if (overlap === false) {
+                                       return false;
+                               }
+                               // if the event's overlap is a test function, pass the peer event in question as the first param
+                               else if (typeof overlap === 'function' && !overlap(peerEvent, event)) {
+                                       return false;
+                               }
+
+                               // if we are computing if the given range is allowable for an event, consider the other event's
+                               // EventObject-specific or Source-specific `overlap` property
+                               if (event) {
+                                       peerOverlap = firstDefined(
+                                               peerEvent.overlap,
+                                               (peerEvent.source || {}).overlap
+                                               // we already considered the global `eventOverlap`
+                                       );
+                                       if (peerOverlap === false) {
+                                               return false;
+                                       }
+                                       // if the peer event's overlap is a test function, pass the subject event as the first param
+                                       if (typeof peerOverlap === 'function' && !peerOverlap(event, peerEvent)) {
+                                               return false;
+                                       }
+                               }
+                       }
+               }
+
+               return true;
+       }
+
+
+       // Given an event input from the API, produces an array of event objects. Possible event inputs:
+       // 'businessHours'
+       // An event ID (number or string)
+       // An object with specific start/end dates or a recurring event (like what businessHours accepts)
+       function constraintToEvents(constraintInput) {
+
+               if (constraintInput === 'businessHours') {
+                       return getBusinessHoursEvents();
+               }
+
+               if (typeof constraintInput === 'object') {
+                       return expandEvent(buildEventFromInput(constraintInput));
+               }
+
+               return clientEvents(constraintInput); // probably an ID
+       }
+
+
+       // Does the event's date range fully contain the given range?
+       // start/end already assumed to have stripped zones :(
+       function eventContainsRange(event, range) {
+               var eventStart = event.start.clone().stripZone();
+               var eventEnd = t.getEventEnd(event).stripZone();
+
+               return range.start >= eventStart && range.end <= eventEnd;
+       }
+
+
+       // Does the event's date range intersect with the given range?
+       // start/end already assumed to have stripped zones :(
+       function eventIntersectsRange(event, range) {
+               var eventStart = event.start.clone().stripZone();
+               var eventEnd = t.getEventEnd(event).stripZone();
+
+               return range.start < eventEnd && range.end > eventStart;
+       }
+
+
+       t.getEventCache = function() {
+               return cache;
+       };
+
+}
+
+
+// Returns a list of events that the given event should be compared against when being considered for a move to
+// the specified span. Attached to the Calendar's prototype because EventManager is a mixin for a Calendar.
+Calendar.prototype.getPeerEvents = function(span, event) {
+       var cache = this.getEventCache();
+       var peerEvents = [];
+       var i, otherEvent;
+
+       for (i = 0; i < cache.length; i++) {
+               otherEvent = cache[i];
+               if (
+                       !event ||
+                       event._id !== otherEvent._id // don't compare the event to itself or other related [repeating] events
+               ) {
+                       peerEvents.push(otherEvent);
+               }
+       }
+
+       return peerEvents;
+};
+
+
+// updates the "backup" properties, which are preserved in order to compute diffs later on.
+function backupEventDates(event) {
+       event._allDay = event.allDay;
+       event._start = event.start.clone();
+       event._end = event.end ? event.end.clone() : null;
+}
+
+;;
+
+/* An abstract class for the "basic" views, as well as month view. Renders one or more rows of day cells.
+----------------------------------------------------------------------------------------------------------------------*/
+// It is a manager for a DayGrid subcomponent, which does most of the heavy lifting.
+// It is responsible for managing width/height.
+
+var BasicView = FC.BasicView = View.extend({
+
+       scroller: null,
+
+       dayGridClass: DayGrid, // class the dayGrid will be instantiated from (overridable by subclasses)
+       dayGrid: null, // the main subcomponent that does most of the heavy lifting
+
+       dayNumbersVisible: false, // display day numbers on each day cell?
+       weekNumbersVisible: false, // display week numbers along the side?
+
+       weekNumberWidth: null, // width of all the week-number cells running down the side
+
+       headContainerEl: null, // div that hold's the dayGrid's rendered date header
+       headRowEl: null, // the fake row element of the day-of-week header
+
+
+       initialize: function() {
+               this.dayGrid = this.instantiateDayGrid();
+
+               this.scroller = new Scroller({
+                       overflowX: 'hidden',
+                       overflowY: 'auto'
+               });
+       },
+
+
+       // Generates the DayGrid object this view needs. Draws from this.dayGridClass
+       instantiateDayGrid: function() {
+               // generate a subclass on the fly with BasicView-specific behavior
+               // TODO: cache this subclass
+               var subclass = this.dayGridClass.extend(basicDayGridMethods);
+
+               return new subclass(this);
+       },
+
+
+       // Sets the display range and computes all necessary dates
+       setRange: function(range) {
+               View.prototype.setRange.call(this, range); // call the super-method
+
+               this.dayGrid.breakOnWeeks = /year|month|week/.test(this.intervalUnit); // do before setRange
+               this.dayGrid.setRange(range);
+       },
+
+
+       // Compute the value to feed into setRange. Overrides superclass.
+       computeRange: function(date) {
+               var range = View.prototype.computeRange.call(this, date); // get value from the super-method
+
+               // year and month views should be aligned with weeks. this is already done for week
+               if (/year|month/.test(range.intervalUnit)) {
+                       range.start.startOf('week');
+                       range.start = this.skipHiddenDays(range.start);
+
+                       // make end-of-week if not already
+                       if (range.end.weekday()) {
+                               range.end.add(1, 'week').startOf('week');
+                               range.end = this.skipHiddenDays(range.end, -1, true); // exclusively move backwards
+                       }
+               }
+
+               return range;
+       },
+
+
+       // Renders the view into `this.el`, which should already be assigned
+       renderDates: function() {
+
+               this.dayNumbersVisible = this.dayGrid.rowCnt > 1; // TODO: make grid responsible
+               this.weekNumbersVisible = this.opt('weekNumbers');
+               this.dayGrid.numbersVisible = this.dayNumbersVisible || this.weekNumbersVisible;
+
+               this.el.addClass('fc-basic-view').html(this.renderSkeletonHtml());
+               this.renderHead();
+
+               this.scroller.render();
+               var dayGridContainerEl = this.scroller.el.addClass('fc-day-grid-container');
+               var dayGridEl = $('<div class="fc-day-grid" />').appendTo(dayGridContainerEl);
+               this.el.find('.fc-body > tr > td').append(dayGridContainerEl);
+
+               this.dayGrid.setElement(dayGridEl);
+               this.dayGrid.renderDates(this.hasRigidRows());
+       },
+
+
+       // render the day-of-week headers
+       renderHead: function() {
+               this.headContainerEl =
+                       this.el.find('.fc-head-container')
+                               .html(this.dayGrid.renderHeadHtml());
+               this.headRowEl = this.headContainerEl.find('.fc-row');
+       },
+
+
+       // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
+       // always completely kill the dayGrid's rendering.
+       unrenderDates: function() {
+               this.dayGrid.unrenderDates();
+               this.dayGrid.removeElement();
+               this.scroller.destroy();
+       },
+
+
+       renderBusinessHours: function() {
+               this.dayGrid.renderBusinessHours();
+       },
+
+
+       // Builds the HTML skeleton for the view.
+       // The day-grid component will render inside of a container defined by this HTML.
+       renderSkeletonHtml: function() {
+               return '' +
+                       '<table>' +
+                               '<thead class="fc-head">' +
+                                       '<tr>' +
+                                               '<td class="fc-head-container ' + this.widgetHeaderClass + '"></td>' +
+                                       '</tr>' +
+                               '</thead>' +
+                               '<tbody class="fc-body">' +
+                                       '<tr>' +
+                                               '<td class="' + this.widgetContentClass + '"></td>' +
+                                       '</tr>' +
+                               '</tbody>' +
+                       '</table>';
+       },
+
+
+       // Generates an HTML attribute string for setting the width of the week number column, if it is known
+       weekNumberStyleAttr: function() {
+               if (this.weekNumberWidth !== null) {
+                       return 'style="width:' + this.weekNumberWidth + 'px"';
+               }
+               return '';
+       },
+
+
+       // Determines whether each row should have a constant height
+       hasRigidRows: function() {
+               var eventLimit = this.opt('eventLimit');
+               return eventLimit && typeof eventLimit !== 'number';
+       },
+
+
+       /* Dimensions
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Refreshes the horizontal dimensions of the view
+       updateWidth: function() {
+               if (this.weekNumbersVisible) {
+                       // Make sure all week number cells running down the side have the same width.
+                       // Record the width for cells created later.
+                       this.weekNumberWidth = matchCellWidths(
+                               this.el.find('.fc-week-number')
+                       );
+               }
+       },
+
+
+       // Adjusts the vertical dimensions of the view to the specified values
+       setHeight: function(totalHeight, isAuto) {
+               var eventLimit = this.opt('eventLimit');
+               var scrollerHeight;
+               var scrollbarWidths;
+
+               // reset all heights to be natural
+               this.scroller.clear();
+               uncompensateScroll(this.headRowEl);
+
+               this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
+
+               // is the event limit a constant level number?
+               if (eventLimit && typeof eventLimit === 'number') {
+                       this.dayGrid.limitRows(eventLimit); // limit the levels first so the height can redistribute after
+               }
+
+               // distribute the height to the rows
+               // (totalHeight is a "recommended" value if isAuto)
+               scrollerHeight = this.computeScrollerHeight(totalHeight);
+               this.setGridHeight(scrollerHeight, isAuto);
+
+               // is the event limit dynamically calculated?
+               if (eventLimit && typeof eventLimit !== 'number') {
+                       this.dayGrid.limitRows(eventLimit); // limit the levels after the grid's row heights have been set
+               }
+
+               if (!isAuto) { // should we force dimensions of the scroll container?
+
+                       this.scroller.setHeight(scrollerHeight);
+                       scrollbarWidths = this.scroller.getScrollbarWidths();
+
+                       if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
+
+                               compensateScroll(this.headRowEl, scrollbarWidths);
+
+                               // doing the scrollbar compensation might have created text overflow which created more height. redo
+                               scrollerHeight = this.computeScrollerHeight(totalHeight);
+                               this.scroller.setHeight(scrollerHeight);
+                       }
+
+                       // guarantees the same scrollbar widths
+                       this.scroller.lockOverflow(scrollbarWidths);
+               }
+       },
+
+
+       // given a desired total height of the view, returns what the height of the scroller should be
+       computeScrollerHeight: function(totalHeight) {
+               return totalHeight -
+                       subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
+       },
+
+
+       // Sets the height of just the DayGrid component in this view
+       setGridHeight: function(height, isAuto) {
+               if (isAuto) {
+                       undistributeHeight(this.dayGrid.rowEls); // let the rows be their natural height with no expanding
+               }
+               else {
+                       distributeHeight(this.dayGrid.rowEls, height, true); // true = compensate for height-hogging rows
+               }
+       },
+
+
+       /* Scroll
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       queryScroll: function() {
+               return this.scroller.getScrollTop();
+       },
+
+
+       setScroll: function(top) {
+               this.scroller.setScrollTop(top);
+       },
+
+
+       /* Hit Areas
+       ------------------------------------------------------------------------------------------------------------------*/
+       // forward all hit-related method calls to dayGrid
+
+
+       prepareHits: function() {
+               this.dayGrid.prepareHits();
+       },
+
+
+       releaseHits: function() {
+               this.dayGrid.releaseHits();
+       },
+
+
+       queryHit: function(left, top) {
+               return this.dayGrid.queryHit(left, top);
+       },
+
+
+       getHitSpan: function(hit) {
+               return this.dayGrid.getHitSpan(hit);
+       },
+
+
+       getHitEl: function(hit) {
+               return this.dayGrid.getHitEl(hit);
+       },
+
+
+       /* Events
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders the given events onto the view and populates the segments array
+       renderEvents: function(events) {
+               this.dayGrid.renderEvents(events);
+
+               this.updateHeight(); // must compensate for events that overflow the row
+       },
+
+
+       // Retrieves all segment objects that are rendered in the view
+       getEventSegs: function() {
+               return this.dayGrid.getEventSegs();
+       },
+
+
+       // Unrenders all event elements and clears internal segment data
+       unrenderEvents: function() {
+               this.dayGrid.unrenderEvents();
+
+               // we DON'T need to call updateHeight() because:
+               // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
+               // B) in IE8, this causes a flash whenever events are rerendered
+       },
+
+
+       /* Dragging (for both events and external elements)
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // A returned value of `true` signals that a mock "helper" event has been rendered.
+       renderDrag: function(dropLocation, seg) {
+               return this.dayGrid.renderDrag(dropLocation, seg);
+       },
+
+
+       unrenderDrag: function() {
+               this.dayGrid.unrenderDrag();
+       },
+
+
+       /* Selection
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of a selection
+       renderSelection: function(span) {
+               this.dayGrid.renderSelection(span);
+       },
+
+
+       // Unrenders a visual indications of a selection
+       unrenderSelection: function() {
+               this.dayGrid.unrenderSelection();
+       }
+
+});
+
+
+// Methods that will customize the rendering behavior of the BasicView's dayGrid
+var basicDayGridMethods = {
+
+
+       // Generates the HTML that will go before the day-of week header cells
+       renderHeadIntroHtml: function() {
+               var view = this.view;
+
+               if (view.weekNumbersVisible) {
+                       return '' +
+                               '<th class="fc-week-number ' + view.widgetHeaderClass + '" ' + view.weekNumberStyleAttr() + '>' +
+                                       '<span>' + // needed for matchCellWidths
+                                               htmlEscape(view.opt('weekNumberTitle')) +
+                                       '</span>' +
+                               '</th>';
+               }
+
+               return '';
+       },
+
+
+       // Generates the HTML that will go before content-skeleton cells that display the day/week numbers
+       renderNumberIntroHtml: function(row) {
+               var view = this.view;
+
+               if (view.weekNumbersVisible) {
+                       return '' +
+                               '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '>' +
+                                       '<span>' + // needed for matchCellWidths
+                                               this.getCellDate(row, 0).format('w') +
+                                       '</span>' +
+                               '</td>';
+               }
+
+               return '';
+       },
+
+
+       // Generates the HTML that goes before the day bg cells for each day-row
+       renderBgIntroHtml: function() {
+               var view = this.view;
+
+               if (view.weekNumbersVisible) {
+                       return '<td class="fc-week-number ' + view.widgetContentClass + '" ' +
+                               view.weekNumberStyleAttr() + '></td>';
+               }
+
+               return '';
+       },
+
+
+       // Generates the HTML that goes before every other type of row generated by DayGrid.
+       // Affects helper-skeleton and highlight-skeleton rows.
+       renderIntroHtml: function() {
+               var view = this.view;
+
+               if (view.weekNumbersVisible) {
+                       return '<td class="fc-week-number" ' + view.weekNumberStyleAttr() + '></td>';
+               }
+
+               return '';
+       }
+
+};
+
+;;
+
+/* A month view with day cells running in rows (one-per-week) and columns
+----------------------------------------------------------------------------------------------------------------------*/
+
+var MonthView = FC.MonthView = BasicView.extend({
+
+       // Produces information about what range to display
+       computeRange: function(date) {
+               var range = BasicView.prototype.computeRange.call(this, date); // get value from super-method
+               var rowCnt;
+
+               // ensure 6 weeks
+               if (this.isFixedWeeks()) {
+                       rowCnt = Math.ceil(range.end.diff(range.start, 'weeks', true)); // could be partial weeks due to hiddenDays
+                       range.end.add(6 - rowCnt, 'weeks');
+               }
+
+               return range;
+       },
+
+
+       // Overrides the default BasicView behavior to have special multi-week auto-height logic
+       setGridHeight: function(height, isAuto) {
+
+               isAuto = isAuto || this.opt('weekMode') === 'variable'; // LEGACY: weekMode is deprecated
+
+               // if auto, make the height of each row the height that it would be if there were 6 weeks
+               if (isAuto) {
+                       height *= this.rowCnt / 6;
+               }
+
+               distributeHeight(this.dayGrid.rowEls, height, !isAuto); // if auto, don't compensate for height-hogging rows
+       },
+
+
+       isFixedWeeks: function() {
+               var weekMode = this.opt('weekMode'); // LEGACY: weekMode is deprecated
+               if (weekMode) {
+                       return weekMode === 'fixed'; // if any other type of weekMode, assume NOT fixed
+               }
+
+               return this.opt('fixedWeekCount');
+       }
+
+});
+
+;;
+
+fcViews.basic = {
+       'class': BasicView
+};
+
+fcViews.basicDay = {
+       type: 'basic',
+       duration: { days: 1 }
+};
+
+fcViews.basicWeek = {
+       type: 'basic',
+       duration: { weeks: 1 }
+};
+
+fcViews.month = {
+       'class': MonthView,
+       duration: { months: 1 }, // important for prev/next
+       defaults: {
+               fixedWeekCount: true
+       }
+};
+;;
+
+/* An abstract class for all agenda-related views. Displays one more columns with time slots running vertically.
+----------------------------------------------------------------------------------------------------------------------*/
+// Is a manager for the TimeGrid subcomponent and possibly the DayGrid subcomponent (if allDaySlot is on).
+// Responsible for managing width/height.
+
+var AgendaView = FC.AgendaView = View.extend({
+
+       scroller: null,
+
+       timeGridClass: TimeGrid, // class used to instantiate the timeGrid. subclasses can override
+       timeGrid: null, // the main time-grid subcomponent of this view
+
+       dayGridClass: DayGrid, // class used to instantiate the dayGrid. subclasses can override
+       dayGrid: null, // the "all-day" subcomponent. if all-day is turned off, this will be null
+
+       axisWidth: null, // the width of the time axis running down the side
+
+       headContainerEl: null, // div that hold's the timeGrid's rendered date header
+       noScrollRowEls: null, // set of fake row elements that must compensate when scroller has scrollbars
+
+       // when the time-grid isn't tall enough to occupy the given height, we render an <hr> underneath
+       bottomRuleEl: null,
+
+
+       initialize: function() {
+               this.timeGrid = this.instantiateTimeGrid();
+
+               if (this.opt('allDaySlot')) { // should we display the "all-day" area?
+                       this.dayGrid = this.instantiateDayGrid(); // the all-day subcomponent of this view
+               }
+
+               this.scroller = new Scroller({
+                       overflowX: 'hidden',
+                       overflowY: 'auto'
+               });
+       },
+
+
+       // Instantiates the TimeGrid object this view needs. Draws from this.timeGridClass
+       instantiateTimeGrid: function() {
+               var subclass = this.timeGridClass.extend(agendaTimeGridMethods);
+
+               return new subclass(this);
+       },
+
+
+       // Instantiates the DayGrid object this view might need. Draws from this.dayGridClass
+       instantiateDayGrid: function() {
+               var subclass = this.dayGridClass.extend(agendaDayGridMethods);
+
+               return new subclass(this);
+       },
+
+
+       /* Rendering
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Sets the display range and computes all necessary dates
+       setRange: function(range) {
+               View.prototype.setRange.call(this, range); // call the super-method
+
+               this.timeGrid.setRange(range);
+               if (this.dayGrid) {
+                       this.dayGrid.setRange(range);
+               }
+       },
+
+
+       // Renders the view into `this.el`, which has already been assigned
+       renderDates: function() {
+
+               this.el.addClass('fc-agenda-view').html(this.renderSkeletonHtml());
+               this.renderHead();
+
+               this.scroller.render();
+               var timeGridWrapEl = this.scroller.el.addClass('fc-time-grid-container');
+               var timeGridEl = $('<div class="fc-time-grid" />').appendTo(timeGridWrapEl);
+               this.el.find('.fc-body > tr > td').append(timeGridWrapEl);
+
+               this.timeGrid.setElement(timeGridEl);
+               this.timeGrid.renderDates();
+
+               // the <hr> that sometimes displays under the time-grid
+               this.bottomRuleEl = $('<hr class="fc-divider ' + this.widgetHeaderClass + '"/>')
+                       .appendTo(this.timeGrid.el); // inject it into the time-grid
+
+               if (this.dayGrid) {
+                       this.dayGrid.setElement(this.el.find('.fc-day-grid'));
+                       this.dayGrid.renderDates();
+
+                       // have the day-grid extend it's coordinate area over the <hr> dividing the two grids
+                       this.dayGrid.bottomCoordPadding = this.dayGrid.el.next('hr').outerHeight();
+               }
+
+               this.noScrollRowEls = this.el.find('.fc-row:not(.fc-scroller *)'); // fake rows not within the scroller
+       },
+
+
+       // render the day-of-week headers
+       renderHead: function() {
+               this.headContainerEl =
+                       this.el.find('.fc-head-container')
+                               .html(this.timeGrid.renderHeadHtml());
+       },
+
+
+       // Unrenders the content of the view. Since we haven't separated skeleton rendering from date rendering,
+       // always completely kill each grid's rendering.
+       unrenderDates: function() {
+               this.timeGrid.unrenderDates();
+               this.timeGrid.removeElement();
+
+               if (this.dayGrid) {
+                       this.dayGrid.unrenderDates();
+                       this.dayGrid.removeElement();
+               }
+
+               this.scroller.destroy();
+       },
+
+
+       // Builds the HTML skeleton for the view.
+       // The day-grid and time-grid components will render inside containers defined by this HTML.
+       renderSkeletonHtml: function() {
+               return '' +
+                       '<table>' +
+                               '<thead class="fc-head">' +
+                                       '<tr>' +
+                                               '<td class="fc-head-container ' + this.widgetHeaderClass + '"></td>' +
+                                       '</tr>' +
+                               '</thead>' +
+                               '<tbody class="fc-body">' +
+                                       '<tr>' +
+                                               '<td class="' + this.widgetContentClass + '">' +
+                                                       (this.dayGrid ?
+                                                               '<div class="fc-day-grid"/>' +
+                                                               '<hr class="fc-divider ' + this.widgetHeaderClass + '"/>' :
+                                                               ''
+                                                               ) +
+                                               '</td>' +
+                                       '</tr>' +
+                               '</tbody>' +
+                       '</table>';
+       },
+
+
+       // Generates an HTML attribute string for setting the width of the axis, if it is known
+       axisStyleAttr: function() {
+               if (this.axisWidth !== null) {
+                        return 'style="width:' + this.axisWidth + 'px"';
+               }
+               return '';
+       },
+
+
+       /* Business Hours
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       renderBusinessHours: function() {
+               this.timeGrid.renderBusinessHours();
+
+               if (this.dayGrid) {
+                       this.dayGrid.renderBusinessHours();
+               }
+       },
+
+
+       unrenderBusinessHours: function() {
+               this.timeGrid.unrenderBusinessHours();
+
+               if (this.dayGrid) {
+                       this.dayGrid.unrenderBusinessHours();
+               }
+       },
+
+
+       /* Now Indicator
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       getNowIndicatorUnit: function() {
+               return this.timeGrid.getNowIndicatorUnit();
+       },
+
+
+       renderNowIndicator: function(date) {
+               this.timeGrid.renderNowIndicator(date);
+       },
+
+
+       unrenderNowIndicator: function() {
+               this.timeGrid.unrenderNowIndicator();
+       },
+
+
+       /* Dimensions
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       updateSize: function(isResize) {
+               this.timeGrid.updateSize(isResize);
+
+               View.prototype.updateSize.call(this, isResize); // call the super-method
+       },
+
+
+       // Refreshes the horizontal dimensions of the view
+       updateWidth: function() {
+               // make all axis cells line up, and record the width so newly created axis cells will have it
+               this.axisWidth = matchCellWidths(this.el.find('.fc-axis'));
+       },
+
+
+       // Adjusts the vertical dimensions of the view to the specified values
+       setHeight: function(totalHeight, isAuto) {
+               var eventLimit;
+               var scrollerHeight;
+               var scrollbarWidths;
+
+               // reset all dimensions back to the original state
+               this.bottomRuleEl.hide(); // .show() will be called later if this <hr> is necessary
+               this.scroller.clear(); // sets height to 'auto' and clears overflow
+               uncompensateScroll(this.noScrollRowEls);
+
+               // limit number of events in the all-day area
+               if (this.dayGrid) {
+                       this.dayGrid.removeSegPopover(); // kill the "more" popover if displayed
+
+                       eventLimit = this.opt('eventLimit');
+                       if (eventLimit && typeof eventLimit !== 'number') {
+                               eventLimit = AGENDA_ALL_DAY_EVENT_LIMIT; // make sure "auto" goes to a real number
+                       }
+                       if (eventLimit) {
+                               this.dayGrid.limitRows(eventLimit);
+                       }
+               }
+
+               if (!isAuto) { // should we force dimensions of the scroll container?
+
+                       scrollerHeight = this.computeScrollerHeight(totalHeight);
+                       this.scroller.setHeight(scrollerHeight);
+                       scrollbarWidths = this.scroller.getScrollbarWidths();
+
+                       if (scrollbarWidths.left || scrollbarWidths.right) { // using scrollbars?
+
+                               // make the all-day and header rows lines up
+                               compensateScroll(this.noScrollRowEls, scrollbarWidths);
+
+                               // the scrollbar compensation might have changed text flow, which might affect height, so recalculate
+                               // and reapply the desired height to the scroller.
+                               scrollerHeight = this.computeScrollerHeight(totalHeight);
+                               this.scroller.setHeight(scrollerHeight);
+                       }
+
+                       // guarantees the same scrollbar widths
+                       this.scroller.lockOverflow(scrollbarWidths);
+
+                       // if there's any space below the slats, show the horizontal rule.
+                       // this won't cause any new overflow, because lockOverflow already called.
+                       if (this.timeGrid.getTotalSlatHeight() < scrollerHeight) {
+                               this.bottomRuleEl.show();
+                       }
+               }
+       },
+
+
+       // given a desired total height of the view, returns what the height of the scroller should be
+       computeScrollerHeight: function(totalHeight) {
+               return totalHeight -
+                       subtractInnerElHeight(this.el, this.scroller.el); // everything that's NOT the scroller
+       },
+
+
+       /* Scroll
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Computes the initial pre-configured scroll state prior to allowing the user to change it
+       computeInitialScroll: function() {
+               var scrollTime = moment.duration(this.opt('scrollTime'));
+               var top = this.timeGrid.computeTimeTop(scrollTime);
+
+               // zoom can give weird floating-point values. rather scroll a little bit further
+               top = Math.ceil(top);
+
+               if (top) {
+                       top++; // to overcome top border that slots beyond the first have. looks better
+               }
+
+               return top;
+       },
+
+
+       queryScroll: function() {
+               return this.scroller.getScrollTop();
+       },
+
+
+       setScroll: function(top) {
+               this.scroller.setScrollTop(top);
+       },
+
+
+       /* Hit Areas
+       ------------------------------------------------------------------------------------------------------------------*/
+       // forward all hit-related method calls to the grids (dayGrid might not be defined)
+
+
+       prepareHits: function() {
+               this.timeGrid.prepareHits();
+               if (this.dayGrid) {
+                       this.dayGrid.prepareHits();
+               }
+       },
+
+
+       releaseHits: function() {
+               this.timeGrid.releaseHits();
+               if (this.dayGrid) {
+                       this.dayGrid.releaseHits();
+               }
+       },
+
+
+       queryHit: function(left, top) {
+               var hit = this.timeGrid.queryHit(left, top);
+
+               if (!hit && this.dayGrid) {
+                       hit = this.dayGrid.queryHit(left, top);
+               }
+
+               return hit;
+       },
+
+
+       getHitSpan: function(hit) {
+               // TODO: hit.component is set as a hack to identify where the hit came from
+               return hit.component.getHitSpan(hit);
+       },
+
+
+       getHitEl: function(hit) {
+               // TODO: hit.component is set as a hack to identify where the hit came from
+               return hit.component.getHitEl(hit);
+       },
+
+
+       /* Events
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders events onto the view and populates the View's segment array
+       renderEvents: function(events) {
+               var dayEvents = [];
+               var timedEvents = [];
+               var daySegs = [];
+               var timedSegs;
+               var i;
+
+               // separate the events into all-day and timed
+               for (i = 0; i < events.length; i++) {
+                       if (events[i].allDay) {
+                               dayEvents.push(events[i]);
+                       }
+                       else {
+                               timedEvents.push(events[i]);
+                       }
+               }
+
+               // render the events in the subcomponents
+               timedSegs = this.timeGrid.renderEvents(timedEvents);
+               if (this.dayGrid) {
+                       daySegs = this.dayGrid.renderEvents(dayEvents);
+               }
+
+               // the all-day area is flexible and might have a lot of events, so shift the height
+               this.updateHeight();
+       },
+
+
+       // Retrieves all segment objects that are rendered in the view
+       getEventSegs: function() {
+               return this.timeGrid.getEventSegs().concat(
+                       this.dayGrid ? this.dayGrid.getEventSegs() : []
+               );
+       },
+
+
+       // Unrenders all event elements and clears internal segment data
+       unrenderEvents: function() {
+
+               // unrender the events in the subcomponents
+               this.timeGrid.unrenderEvents();
+               if (this.dayGrid) {
+                       this.dayGrid.unrenderEvents();
+               }
+
+               // we DON'T need to call updateHeight() because:
+               // A) a renderEvents() call always happens after this, which will eventually call updateHeight()
+               // B) in IE8, this causes a flash whenever events are rerendered
+       },
+
+
+       /* Dragging (for events and external elements)
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // A returned value of `true` signals that a mock "helper" event has been rendered.
+       renderDrag: function(dropLocation, seg) {
+               if (dropLocation.start.hasTime()) {
+                       return this.timeGrid.renderDrag(dropLocation, seg);
+               }
+               else if (this.dayGrid) {
+                       return this.dayGrid.renderDrag(dropLocation, seg);
+               }
+       },
+
+
+       unrenderDrag: function() {
+               this.timeGrid.unrenderDrag();
+               if (this.dayGrid) {
+                       this.dayGrid.unrenderDrag();
+               }
+       },
+
+
+       /* Selection
+       ------------------------------------------------------------------------------------------------------------------*/
+
+
+       // Renders a visual indication of a selection
+       renderSelection: function(span) {
+               if (span.start.hasTime() || span.end.hasTime()) {
+                       this.timeGrid.renderSelection(span);
+               }
+               else if (this.dayGrid) {
+                       this.dayGrid.renderSelection(span);
+               }
+       },
+
+
+       // Unrenders a visual indications of a selection
+       unrenderSelection: function() {
+               this.timeGrid.unrenderSelection();
+               if (this.dayGrid) {
+                       this.dayGrid.unrenderSelection();
+               }
+       }
+
+});
+
+
+// Methods that will customize the rendering behavior of the AgendaView's timeGrid
+// TODO: move into TimeGrid
+var agendaTimeGridMethods = {
+
+
+       // Generates the HTML that will go before the day-of week header cells
+       renderHeadIntroHtml: function() {
+               var view = this.view;
+               var weekText;
+
+               if (view.opt('weekNumbers')) {
+                       weekText = this.start.format(view.opt('smallWeekFormat'));
+
+                       return '' +
+                               '<th class="fc-axis fc-week-number ' + view.widgetHeaderClass + '" ' + view.axisStyleAttr() + '>' +
+                                       '<span>' + // needed for matchCellWidths
+                                               htmlEscape(weekText) +
+                                       '</span>' +
+                               '</th>';
+               }
+               else {
+                       return '<th class="fc-axis ' + view.widgetHeaderClass + '" ' + view.axisStyleAttr() + '></th>';
+               }
+       },
+
+
+       // Generates the HTML that goes before the bg of the TimeGrid slot area. Long vertical column.
+       renderBgIntroHtml: function() {
+               var view = this.view;
+
+               return '<td class="fc-axis ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '></td>';
+       },
+
+
+       // Generates the HTML that goes before all other types of cells.
+       // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
+       renderIntroHtml: function() {
+               var view = this.view;
+
+               return '<td class="fc-axis" ' + view.axisStyleAttr() + '></td>';
+       }
+
+};
+
+
+// Methods that will customize the rendering behavior of the AgendaView's dayGrid
+var agendaDayGridMethods = {
+
+
+       // Generates the HTML that goes before the all-day cells
+       renderBgIntroHtml: function() {
+               var view = this.view;
+
+               return '' +
+                       '<td class="fc-axis ' + view.widgetContentClass + '" ' + view.axisStyleAttr() + '>' +
+                               '<span>' + // needed for matchCellWidths
+                                       (view.opt('allDayHtml') || htmlEscape(view.opt('allDayText'))) +
+                               '</span>' +
+                       '</td>';
+       },
+
+
+       // Generates the HTML that goes before all other types of cells.
+       // Affects content-skeleton, helper-skeleton, highlight-skeleton for both the time-grid and day-grid.
+       renderIntroHtml: function() {
+               var view = this.view;
+
+               return '<td class="fc-axis" ' + view.axisStyleAttr() + '></td>';
+       }
+
+};
+
+;;
+
+var AGENDA_ALL_DAY_EVENT_LIMIT = 5;
+
+// potential nice values for the slot-duration and interval-duration
+// from largest to smallest
+var AGENDA_STOCK_SUB_DURATIONS = [
+       { hours: 1 },
+       { minutes: 30 },
+       { minutes: 15 },
+       { seconds: 30 },
+       { seconds: 15 }
+];
+
+fcViews.agenda = {
+       'class': AgendaView,
+       defaults: {
+               allDaySlot: true,
+               allDayText: 'all-day',
+               slotDuration: '00:30:00',
+               minTime: '00:00:00',
+               maxTime: '24:00:00',
+               slotEventOverlap: true // a bad name. confused with overlap/constraint system
+       }
+};
+
+fcViews.agendaDay = {
+       type: 'agenda',
+       duration: { days: 1 }
+};
+
+fcViews.agendaWeek = {
+       type: 'agenda',
+       duration: { weeks: 1 }
+};
+;;
+
+return FC; // export for Node/CommonJS
+});
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/fullcalendar.min.js b/tools/infra-dashboard/js/fullcalendar.min.js
new file mode 100644 (file)
index 0000000..de0babc
--- /dev/null
@@ -0,0 +1,9 @@
+/*!
+ * FullCalendar v2.7.2
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */
+!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):"object"==typeof exports?module.exports=a(require("jquery"),require("moment")):a(jQuery,moment)}(function(a,b){function c(a){return W(a,Xa)}function d(b){var c,d={views:b.views||{}};return a.each(b,function(b,e){"views"!=b&&(a.isPlainObject(e)&&!/(time|duration|interval)$/i.test(b)&&-1==a.inArray(b,Xa)?(c=null,a.each(e,function(a,e){/^(month|week|day|default|basic(Week|Day)?|agenda(Week|Day)?)$/.test(a)?(d.views[a]||(d.views[a]={}),d.views[a][b]=e):(c||(c={}),c[a]=e)}),c&&(d[b]=c)):d[b]=e)}),d}function e(a,b){b.left&&a.css({"border-left-width":1,"margin-left":b.left-1}),b.right&&a.css({"border-right-width":1,"margin-right":b.right-1})}function f(a){a.css({"margin-left":"","margin-right":"","border-left-width":"","border-right-width":""})}function g(){a("body").addClass("fc-not-allowed")}function h(){a("body").removeClass("fc-not-allowed")}function i(b,c,d){var e=Math.floor(c/b.length),f=Math.floor(c-e*(b.length-1)),g=[],h=[],i=[],k=0;j(b),b.each(function(c,d){var j=c===b.length-1?f:e,l=a(d).outerHeight(!0);j>l?(g.push(d),h.push(l),i.push(a(d).height())):k+=l}),d&&(c-=k,e=Math.floor(c/g.length),f=Math.floor(c-e*(g.length-1))),a(g).each(function(b,c){var d=b===g.length-1?f:e,j=h[b],k=i[b],l=d-(j-k);d>j&&a(c).height(l)})}function j(a){a.height("")}function k(b){var c=0;return b.find("> span").each(function(b,d){var e=a(d).outerWidth();e>c&&(c=e)}),c++,b.width(c),c}function l(a,b){var c,d=a.add(b);return d.css({position:"relative",left:-1}),c=a.outerHeight()-b.outerHeight(),d.css({position:"",left:""}),c}function m(b){var c=b.css("position"),d=b.parents().filter(function(){var b=a(this);return/(auto|scroll)/.test(b.css("overflow")+b.css("overflow-y")+b.css("overflow-x"))}).eq(0);return"fixed"!==c&&d.length?d:a(b[0].ownerDocument||document)}function n(a,b){var c=a.offset(),d=c.left-(b?b.left:0),e=c.top-(b?b.top:0);return{left:d,right:d+a.outerWidth(),top:e,bottom:e+a.outerHeight()}}function o(a,b){var c=a.offset(),d=q(a),e=c.left+t(a,"border-left-width")+d.left-(b?b.left:0),f=c.top+t(a,"border-top-width")+d.top-(b?b.top:0);return{left:e,right:e+a[0].clientWidth,top:f,bottom:f+a[0].clientHeight}}function p(a,b){var c=a.offset(),d=c.left+t(a,"border-left-width")+t(a,"padding-left")-(b?b.left:0),e=c.top+t(a,"border-top-width")+t(a,"padding-top")-(b?b.top:0);return{left:d,right:d+a.width(),top:e,bottom:e+a.height()}}function q(a){var b=a.innerWidth()-a[0].clientWidth,c={left:0,right:0,top:0,bottom:a.innerHeight()-a[0].clientHeight};return r()&&"rtl"==a.css("direction")?c.left=b:c.right=b,c}function r(){return null===Ya&&(Ya=s()),Ya}function s(){var b=a("<div><div/></div>").css({position:"absolute",top:-1e3,left:0,border:0,padding:0,overflow:"scroll",direction:"rtl"}).appendTo("body"),c=b.children(),d=c.offset().left>b.offset().left;return b.remove(),d}function t(a,b){return parseFloat(a.css(b))||0}function u(a){return 1==a.which&&!a.ctrlKey}function v(a){if(void 0!==a.pageX)return a.pageX;var b=a.originalEvent.touches;return b?b[0].pageX:void 0}function w(a){if(void 0!==a.pageY)return a.pageY;var b=a.originalEvent.touches;return b?b[0].pageY:void 0}function x(a){return/^touch/.test(a.type)}function y(a){a.addClass("fc-unselectable").on("selectstart",z)}function z(a){a.preventDefault()}function A(a){return window.addEventListener?(window.addEventListener("scroll",a,!0),!0):!1}function B(a){return window.removeEventListener?(window.removeEventListener("scroll",a,!0),!0):!1}function C(a,b){var c={left:Math.max(a.left,b.left),right:Math.min(a.right,b.right),top:Math.max(a.top,b.top),bottom:Math.min(a.bottom,b.bottom)};return c.left<c.right&&c.top<c.bottom?c:!1}function D(a,b){return{left:Math.min(Math.max(a.left,b.left),b.right),top:Math.min(Math.max(a.top,b.top),b.bottom)}}function E(a){return{left:(a.left+a.right)/2,top:(a.top+a.bottom)/2}}function F(a,b){return{left:a.left-b.left,top:a.top-b.top}}function G(b){var c,d,e=[],f=[];for("string"==typeof b?f=b.split(/\s*,\s*/):"function"==typeof b?f=[b]:a.isArray(b)&&(f=b),c=0;c<f.length;c++)d=f[c],"string"==typeof d?e.push("-"==d.charAt(0)?{field:d.substring(1),order:-1}:{field:d,order:1}):"function"==typeof d&&e.push({func:d});return e}function H(a,b,c){var d,e;for(d=0;d<c.length;d++)if(e=I(a,b,c[d]))return e;return 0}function I(a,b,c){return c.func?c.func(a,b):J(a[c.field],b[c.field])*(c.order||1)}function J(b,c){return b||c?null==c?-1:null==b?1:"string"===a.type(b)||"string"===a.type(c)?String(b).localeCompare(String(c)):b-c:0}function K(a,b){var c,d,e,f,g=a.start,h=a.end,i=b.start,j=b.end;return h>i&&j>g?(g>=i?(c=g.clone(),e=!0):(c=i.clone(),e=!1),j>=h?(d=h.clone(),f=!0):(d=j.clone(),f=!1),{start:c,end:d,isStart:e,isEnd:f}):void 0}function L(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days"),ms:a.time()-c.time()})}function M(a,c){return b.duration({days:a.clone().stripTime().diff(c.clone().stripTime(),"days")})}function N(a,c,d){return b.duration(Math.round(a.diff(c,d,!0)),d)}function O(a,b){var c,d,e;for(c=0;c<$a.length&&(d=$a[c],e=P(d,a,b),!(e>=1&&ha(e)));c++);return d}function P(a,c,d){return null!=d?d.diff(c,a,!0):b.isDuration(c)?c.as(a):c.end.diff(c.start,a,!0)}function Q(a,b,c){var d;return T(c)?(b-a)/c:(d=c.asMonths(),Math.abs(d)>=1&&ha(d)?b.diff(a,"months",!0)/d:b.diff(a,"days",!0)/c.asDays())}function R(a,b){var c,d;return T(a)||T(b)?a/b:(c=a.asMonths(),d=b.asMonths(),Math.abs(c)>=1&&ha(c)&&Math.abs(d)>=1&&ha(d)?c/d:a.asDays()/b.asDays())}function S(a,c){var d;return T(a)?b.duration(a*c):(d=a.asMonths(),Math.abs(d)>=1&&ha(d)?b.duration({months:d*c}):b.duration({days:a.asDays()*c}))}function T(a){return Boolean(a.hours()||a.minutes()||a.seconds()||a.milliseconds())}function U(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function V(a){return/^\d+\:\d+(?:\:\d+\.?(?:\d{3})?)?$/.test(a)}function W(a,b){var c,d,e,f,g,h,i={};if(b)for(c=0;c<b.length;c++){for(d=b[c],e=[],f=a.length-1;f>=0;f--)if(g=a[f][d],"object"==typeof g)e.unshift(g);else if(void 0!==g){i[d]=g;break}e.length&&(i[d]=W(e))}for(c=a.length-1;c>=0;c--){h=a[c];for(d in h)d in i||(i[d]=h[d])}return i}function X(a){var b=function(){};return b.prototype=a,new b}function Y(a,b){for(var c in a)$(a,c)&&(b[c]=a[c])}function Z(a,b){var c,d,e=["constructor","toString","valueOf"];for(c=0;c<e.length;c++)d=e[c],a[d]!==Object.prototype[d]&&(b[d]=a[d])}function $(a,b){return cb.call(a,b)}function _(b){return/undefined|null|boolean|number|string/.test(a.type(b))}function aa(b,c,d){if(a.isFunction(b)&&(b=[b]),b){var e,f;for(e=0;e<b.length;e++)f=b[e].apply(c,d)||f;return f}}function ba(){for(var a=0;a<arguments.length;a++)if(void 0!==arguments[a])return arguments[a]}function ca(a){return(a+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/'/g,"&#039;").replace(/"/g,"&quot;").replace(/\n/g,"<br />")}function da(a){return a.replace(/&.*?;/g,"")}function ea(b){var c=[];return a.each(b,function(a,b){null!=b&&c.push(a+":"+b)}),c.join(";")}function fa(a){return a.charAt(0).toUpperCase()+a.slice(1)}function ga(a,b){return a-b}function ha(a){return a%1===0}function ia(a,b){var c=a[b];return function(){return c.apply(a,arguments)}}function ja(a,b,c){var d,e,f,g,h,i=function(){var j=+new Date-g;b>j?d=setTimeout(i,b-j):(d=null,c||(h=a.apply(f,e),f=e=null))};return function(){f=this,e=arguments,g=+new Date;var j=c&&!d;return d||(d=setTimeout(i,b)),j&&(h=a.apply(f,e),f=e=null),h}}function ka(c,d,e){var f,g,h,i,j=c[0],k=1==c.length&&"string"==typeof j;return b.isMoment(j)?(i=b.apply(null,c),ma(j,i)):U(j)||void 0===j?i=b.apply(null,c):(f=!1,g=!1,k?db.test(j)?(j+="-01",c=[j],f=!0,g=!0):(h=eb.exec(j))&&(f=!h[5],g=!0):a.isArray(j)&&(g=!0),i=d||f?b.utc.apply(b,c):b.apply(null,c),f?(i._ambigTime=!0,i._ambigZone=!0):e&&(g?i._ambigZone=!0:k&&(i.utcOffset?i.utcOffset(j):i.zone(j)))),i._fullCalendar=!0,i}function la(a,c){var d,e,f=!1,g=!1,h=a.length,i=[];for(d=0;h>d;d++)e=a[d],b.isMoment(e)||(e=Va.moment.parseZone(e)),f=f||e._ambigTime,g=g||e._ambigZone,i.push(e);for(d=0;h>d;d++)e=i[d],c||!f||e._ambigTime?g&&!e._ambigZone&&(i[d]=e.clone().stripZone()):i[d]=e.clone().stripTime();return i}function ma(a,b){a._ambigTime?b._ambigTime=!0:b._ambigTime&&(b._ambigTime=!1),a._ambigZone?b._ambigZone=!0:b._ambigZone&&(b._ambigZone=!1)}function na(a,b){a.year(b[0]||0).month(b[1]||0).date(b[2]||0).hours(b[3]||0).minutes(b[4]||0).seconds(b[5]||0).milliseconds(b[6]||0)}function oa(a,b){return gb.format.call(a,b)}function pa(a,b){return qa(a,va(b))}function qa(a,b){var c,d="";for(c=0;c<b.length;c++)d+=ra(a,b[c]);return d}function ra(a,b){var c,d;return"string"==typeof b?b:(c=b.token)?hb[c]?hb[c](a):oa(a,c):b.maybe&&(d=qa(a,b.maybe),d.match(/[1-9]/))?d:""}function sa(a,b,c,d,e){var f;return a=Va.moment.parseZone(a),b=Va.moment.parseZone(b),f=(a.localeData||a.lang).call(a),c=f.longDateFormat(c)||c,d=d||" - ",ta(a,b,va(c),d,e)}function ta(a,b,c,d,e){var f,g,h,i,j=a.clone().stripZone(),k=b.clone().stripZone(),l="",m="",n="",o="",p="";for(g=0;g<c.length&&(f=ua(a,b,j,k,c[g]),f!==!1);g++)l+=f;for(h=c.length-1;h>g&&(f=ua(a,b,j,k,c[h]),f!==!1);h--)m=f+m;for(i=g;h>=i;i++)n+=ra(a,c[i]),o+=ra(b,c[i]);return(n||o)&&(p=e?o+d+n:n+d+o),l+p+m}function ua(a,b,c,d,e){var f,g;return"string"==typeof e?e:(f=e.token)&&(g=ib[f.charAt(0)],g&&c.isSame(d,g))?oa(a,f):!1}function va(a){return a in jb?jb[a]:jb[a]=wa(a)}function wa(a){for(var b,c=[],d=/\[([^\]]*)\]|\(([^\)]*)\)|(LTS|LT|(\w)\4*o?)|([^\w\[\(]+)/g;b=d.exec(a);)b[1]?c.push(b[1]):b[2]?c.push({maybe:wa(b[2])}):b[3]?c.push({token:b[3]}):b[5]&&c.push(b[5]);return c}function xa(){}function ya(a,b){var c;return $(b,"constructor")&&(c=b.constructor),"function"!=typeof c&&(c=b.constructor=function(){a.apply(this,arguments)}),c.prototype=X(a.prototype),Y(b,c.prototype),Z(b,c.prototype),Y(a,c),c}function za(a,b){Y(b,a.prototype)}function Aa(a,b){return a||b?a&&b?a.component===b.component&&Ba(a,b)&&Ba(b,a):!1:!0}function Ba(a,b){for(var c in a)if(!/^(component|left|right|top|bottom)$/.test(c)&&a[c]!==b[c])return!1;return!0}function Ca(a){var b=Ea(a);return"background"===b||"inverse-background"===b}function Da(a){return"inverse-background"===Ea(a)}function Ea(a){return ba((a.source||{}).rendering,a.rendering)}function Fa(a){var b,c,d={};for(b=0;b<a.length;b++)c=a[b],(d[c._id]||(d[c._id]=[])).push(c);return d}function Ga(a,b){return a.start-b.start}function Ha(c){var d,e,f,g,h=Va.dataAttrPrefix;return h&&(h+="-"),d=c.data(h+"event")||null,d&&(d="object"==typeof d?a.extend({},d):{},e=d.start,null==e&&(e=d.time),f=d.duration,g=d.stick,delete d.start,delete d.time,delete d.duration,delete d.stick),null==e&&(e=c.data(h+"start")),null==e&&(e=c.data(h+"time")),null==f&&(f=c.data(h+"duration")),null==g&&(g=c.data(h+"stick")),e=null!=e?b.duration(e):null,f=null!=f?b.duration(f):null,g=Boolean(g),{eventProps:d,startTime:e,duration:f,stick:g}}function Ia(a,b){var c,d;for(c=0;c<b.length;c++)if(d=b[c],d.leftCol<=a.rightCol&&d.rightCol>=a.leftCol)return!0;return!1}function Ja(a,b){return a.leftCol-b.leftCol}function Ka(a){var b,c,d,e=[];for(b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Na(c,e[d]).length;d++);c.level=d,(e[d]||(e[d]=[])).push(c)}return e}function La(a){var b,c,d,e,f;for(b=0;b<a.length;b++)for(c=a[b],d=0;d<c.length;d++)for(e=c[d],e.forwardSegs=[],f=b+1;f<a.length;f++)Na(e,a[f],e.forwardSegs)}function Ma(a){var b,c,d=a.forwardSegs,e=0;if(void 0===a.forwardPressure){for(b=0;b<d.length;b++)c=d[b],Ma(c),e=Math.max(e,1+c.forwardPressure);a.forwardPressure=e}}function Na(a,b,c){c=c||[];for(var d=0;d<b.length;d++)Oa(a,b[d])&&c.push(b[d]);return c}function Oa(a,b){return a.bottom>b.top&&a.top<b.bottom}function Pa(c,d){function e(){T?h()&&(k(),i()):f()}function f(){U=O.theme?"ui":"fc",c.addClass("fc"),O.isRTL?c.addClass("fc-rtl"):c.addClass("fc-ltr"),O.theme?c.addClass("ui-widget"):c.addClass("fc-unthemed"),T=a("<div class='fc-view-container'/>").prependTo(c),R=N.header=new Sa(N,O),S=R.render(),S&&c.prepend(S),i(O.defaultView),O.handleWindowResize&&(Y=ja(m,O.windowResizeDelay),a(window).resize(Y))}function g(){V&&V.removeElement(),R.removeElement(),T.remove(),c.removeClass("fc fc-ltr fc-rtl fc-unthemed ui-widget"),Y&&a(window).unbind("resize",Y)}function h(){return c.is(":visible")}function i(b){ca++,V&&b&&V.type!==b&&(R.deactivateButton(V.type),H(),V.removeElement(),V=N.view=null),!V&&b&&(V=N.view=ba[b]||(ba[b]=N.instantiateView(b)),V.setElement(a("<div class='fc-view fc-"+b+"-view' />").appendTo(T)),R.activateButton(b)),V&&(Z=V.massageCurrentDate(Z),V.displaying&&Z.isWithin(V.intervalStart,V.intervalEnd)||h()&&(V.display(Z),I(),u(),v(),q())),I(),ca--}function j(a){return h()?(a&&l(),ca++,V.updateSize(!0),ca--,!0):void 0}function k(){h()&&l()}function l(){W="number"==typeof O.contentHeight?O.contentHeight:"number"==typeof O.height?O.height-(S?S.outerHeight(!0):0):Math.round(T.width()/Math.max(O.aspectRatio,.5))}function m(a){!ca&&a.target===window&&V.start&&j(!0)&&V.trigger("windowResize",aa)}function n(){p(),r()}function o(){h()&&(H(),V.displayEvents(da),I())}function p(){H(),V.clearEvents(),I()}function q(){!O.lazyFetching||$(V.start,V.end)?r():o()}function r(){_(V.start,V.end)}function s(a){da=a,o()}function t(){o()}function u(){R.updateTitle(V.title)}function v(){var a=N.getNow();a.isWithin(V.intervalStart,V.intervalEnd)?R.disableButton("today"):R.enableButton("today")}function w(a,b){V.select(N.buildSelectSpan.apply(N,arguments))}function x(){V&&V.unselect()}function y(){Z=V.computePrevDate(Z),i()}function z(){Z=V.computeNextDate(Z),i()}function A(){Z.add(-1,"years"),i()}function B(){Z.add(1,"years"),i()}function C(){Z=N.getNow(),i()}function D(a){Z=N.moment(a).stripZone(),i()}function E(a){Z.add(b.duration(a)),i()}function F(a,b){var c;b=b||"day",c=N.getViewSpec(b)||N.getUnitViewSpec(b),Z=a.clone(),i(c?c.type:null)}function G(){return N.applyTimezone(Z)}function H(){T.css({width:"100%",height:T.height(),overflow:"hidden"})}function I(){T.css({width:"",height:"",overflow:""})}function J(){return N}function K(){return V}function L(a,b){return void 0===b?O[a]:void("height"!=a&&"contentHeight"!=a&&"aspectRatio"!=a||(O[a]=b,j(!0)))}function M(a,b){var c=Array.prototype.slice.call(arguments,2);return b=b||aa,this.triggerWith(a,b,c),O[a]?O[a].apply(b,c):void 0}var N=this;N.initOptions(d||{});var O=this.options;N.render=e,N.destroy=g,N.refetchEvents=n,N.reportEvents=s,N.reportEventChange=t,N.rerenderEvents=o,N.changeView=i,N.select=w,N.unselect=x,N.prev=y,N.next=z,N.prevYear=A,N.nextYear=B,N.today=C,N.gotoDate=D,N.incrementDate=E,N.zoomTo=F,N.getDate=G,N.getCalendar=J,N.getView=K,N.option=L,N.trigger=M;var P=X(Ra(O.lang));if(O.monthNames&&(P._months=O.monthNames),O.monthNamesShort&&(P._monthsShort=O.monthNamesShort),O.dayNames&&(P._weekdays=O.dayNames),O.dayNamesShort&&(P._weekdaysShort=O.dayNamesShort),null!=O.firstDay){var Q=X(P._week);Q.dow=O.firstDay,P._week=Q}P._fullCalendar_weekCalc=function(a){return"function"==typeof a?a:"local"===a?a:"iso"===a||"ISO"===a?"ISO":void 0}(O.weekNumberCalculation),N.defaultAllDayEventDuration=b.duration(O.defaultAllDayEventDuration),N.defaultTimedEventDuration=b.duration(O.defaultTimedEventDuration),N.moment=function(){var a;return"local"===O.timezone?(a=Va.moment.apply(null,arguments),a.hasTime()&&a.local()):a="UTC"===O.timezone?Va.moment.utc.apply(null,arguments):Va.moment.parseZone.apply(null,arguments),"_locale"in a?a._locale=P:a._lang=P,a},N.getIsAmbigTimezone=function(){return"local"!==O.timezone&&"UTC"!==O.timezone},N.applyTimezone=function(a){if(!a.hasTime())return a.clone();var b,c=N.moment(a.toArray()),d=a.time()-c.time();return d&&(b=c.clone().add(d),a.time()-b.time()===0&&(c=b)),c},N.getNow=function(){var a=O.now;return"function"==typeof a&&(a=a()),N.moment(a).stripZone()},N.getEventEnd=function(a){return a.end?a.end.clone():N.getDefaultEventEnd(a.allDay,a.start)},N.getDefaultEventEnd=function(a,b){var c=b.clone();return a?c.stripTime().add(N.defaultAllDayEventDuration):c.add(N.defaultTimedEventDuration),N.getIsAmbigTimezone()&&c.stripZone(),c},N.humanizeDuration=function(a){return(a.locale||a.lang).call(a,O.lang).humanize()},Ta.call(N,O);var R,S,T,U,V,W,Y,Z,$=N.isFetchNeeded,_=N.fetchEvents,aa=c[0],ba={},ca=0,da=[];Z=null!=O.defaultDate?N.moment(O.defaultDate).stripZone():N.getNow(),N.getSuggestedViewHeight=function(){return void 0===W&&k(),W},N.isHeightAuto=function(){return"auto"===O.contentHeight||"auto"===O.height},N.freezeContentHeight=H,N.unfreezeContentHeight=I,N.initialize()}function Qa(b){a.each(Cb,function(a,c){null==b[a]&&(b[a]=c(b))})}function Ra(a){var c=b.localeData||b.langData;return c.call(b,a)||c.call(b,"en")}function Sa(b,c){function d(){var b=c.header;return n=c.theme?"ui":"fc",b?o=a("<div class='fc-toolbar'/>").append(f("left")).append(f("right")).append(f("center")).append('<div class="fc-clear"/>'):void 0}function e(){o.remove(),o=a()}function f(d){var e=a('<div class="fc-'+d+'"/>'),f=c.header[d];return f&&a.each(f.split(" "),function(d){var f,g=a(),h=!0;a.each(this.split(","),function(d,e){var f,i,j,k,l,m,o,q,r,s;"title"==e?(g=g.add(a("<h2>&nbsp;</h2>")),h=!1):((f=(b.options.customButtons||{})[e])?(j=function(a){f.click&&f.click.call(s[0],a)},k="",l=f.text):(i=b.getViewSpec(e))?(j=function(){b.changeView(e)},p.push(e),k=i.buttonTextOverride,l=i.buttonTextDefault):b[e]&&(j=function(){b[e]()},k=(b.overrides.buttonText||{})[e],l=c.buttonText[e]),j&&(m=f?f.themeIcon:c.themeButtonIcons[e],o=f?f.icon:c.buttonIcons[e],q=k?ca(k):m&&c.theme?"<span class='ui-icon ui-icon-"+m+"'></span>":o&&!c.theme?"<span class='fc-icon fc-icon-"+o+"'></span>":ca(l),r=["fc-"+e+"-button",n+"-button",n+"-state-default"],s=a('<button type="button" class="'+r.join(" ")+'">'+q+"</button>").click(function(a){s.hasClass(n+"-state-disabled")||(j(a),(s.hasClass(n+"-state-active")||s.hasClass(n+"-state-disabled"))&&s.removeClass(n+"-state-hover"))}).mousedown(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-down")}).mouseup(function(){s.removeClass(n+"-state-down")}).hover(function(){s.not("."+n+"-state-active").not("."+n+"-state-disabled").addClass(n+"-state-hover")},function(){s.removeClass(n+"-state-hover").removeClass(n+"-state-down")}),g=g.add(s)))}),h&&g.first().addClass(n+"-corner-left").end().last().addClass(n+"-corner-right").end(),g.length>1?(f=a("<div/>"),h&&f.addClass("fc-button-group"),f.append(g),e.append(f)):e.append(g)}),e}function g(a){o.find("h2").text(a)}function h(a){o.find(".fc-"+a+"-button").addClass(n+"-state-active")}function i(a){o.find(".fc-"+a+"-button").removeClass(n+"-state-active")}function j(a){o.find(".fc-"+a+"-button").attr("disabled","disabled").addClass(n+"-state-disabled")}function k(a){o.find(".fc-"+a+"-button").removeAttr("disabled").removeClass(n+"-state-disabled")}function l(){return p}var m=this;m.render=d,m.removeElement=e,m.updateTitle=g,m.activateButton=h,m.deactivateButton=i,m.disableButton=j,m.enableButton=k,m.getViewsWithButtons=l;var n,o=a(),p=[]}function Ta(c){function d(a,b){return!I||I>a||b>J}function e(a,b){I=a,J=b,S=[];var c=++Q,d=P.length;R=d;for(var e=0;d>e;e++)f(P[e],c)}function f(b,c){g(b,function(d){var e,f,g,h=a.isArray(b.events);if(c==Q){if(d)for(e=0;e<d.length;e++)f=d[e],g=h?f:s(f,b),g&&S.push.apply(S,w(g));R--,R||K(S)}})}function g(b,d){var e,f,h=Va.sourceFetchers;for(e=0;e<h.length;e++){if(f=h[e].call(H,b,I.clone(),J.clone(),c.timezone,d),f===!0)return;if("object"==typeof f)return void g(f,d)}var i=b.events;if(i)a.isFunction(i)?(H.pushLoading(),i.call(H,I.clone(),J.clone(),c.timezone,function(a){d(a),H.popLoading()})):a.isArray(i)?d(i):d();else{var j=b.url;if(j){var k,l=b.success,m=b.error,n=b.complete;k=a.isFunction(b.data)?b.data():b.data;var o=a.extend({},k||{}),p=ba(b.startParam,c.startParam),q=ba(b.endParam,c.endParam),r=ba(b.timezoneParam,c.timezoneParam);p&&(o[p]=I.format()),q&&(o[q]=J.format()),c.timezone&&"local"!=c.timezone&&(o[r]=c.timezone),H.pushLoading(),a.ajax(a.extend({},Db,b,{data:o,success:function(b){b=b||[];var c=aa(l,this,arguments);a.isArray(c)&&(b=c),d(b)},error:function(){aa(m,this,arguments),d()},complete:function(){aa(n,this,arguments),H.popLoading()}}))}else d()}}function h(a){var b=i(a);b&&(P.push(b),R++,f(b,Q))}function i(b){var c,d,e=Va.sourceNormalizers;if(a.isFunction(b)||a.isArray(b)?c={events:b}:"string"==typeof b?c={url:b}:"object"==typeof b&&(c=a.extend({},b)),c){for(c.className?"string"==typeof c.className&&(c.className=c.className.split(/\s+/)):c.className=[],a.isArray(c.events)&&(c.origArray=c.events,c.events=a.map(c.events,function(a){return s(a,c)})),d=0;d<e.length;d++)e[d].call(H,c);return c}}function j(b){P=a.grep(P,function(a){return!k(a,b)}),S=a.grep(S,function(a){return!k(a.source,b)}),K(S)}function k(a,b){return a&&b&&l(a)==l(b)}function l(a){return("object"==typeof a?a.origArray||a.googleCalendarId||a.url||a.events:null)||a}function m(a){a.start=H.moment(a.start),a.end?a.end=H.moment(a.end):a.end=null,x(a,n(a)),K(S)}function n(b){var c={};return a.each(b,function(a,b){o(a)&&void 0!==b&&_(b)&&(c[a]=b)}),c}function o(a){return!/^_|^(id|allDay|start|end)$/.test(a)}function p(a,b){var c,d,e,f=s(a);if(f){for(c=w(f),d=0;d<c.length;d++)e=c[d],e.source||(b&&(O.events.push(e),e.source=O),S.push(e));return K(S),c}return[]}function q(b){var c,d;for(null==b?b=function(){return!0}:a.isFunction(b)||(c=b+"",b=function(a){return a._id==c}),S=a.grep(S,b,!0),d=0;d<P.length;d++)a.isArray(P[d].events)&&(P[d].events=a.grep(P[d].events,b,!0));K(S)}function r(b){return a.isFunction(b)?a.grep(S,b):null!=b?(b+="",a.grep(S,function(a){return a._id==b})):S}function s(d,e){var f,g,h,i={};if(c.eventDataTransform&&(d=c.eventDataTransform(d)),e&&e.eventDataTransform&&(d=e.eventDataTransform(d)),a.extend(i,d),e&&(i.source=e),i._id=d._id||(void 0===d.id?"_fc"+Eb++:d.id+""),d.className?"string"==typeof d.className?i.className=d.className.split(/\s+/):i.className=d.className:i.className=[],f=d.start||d.date,g=d.end,V(f)&&(f=b.duration(f)),V(g)&&(g=b.duration(g)),d.dow||b.isDuration(f)||b.isDuration(g))i.start=f?b.duration(f):null,i.end=g?b.duration(g):null,i._recurring=!0;else{if(f&&(f=H.moment(f),!f.isValid()))return!1;g&&(g=H.moment(g),g.isValid()||(g=null)),h=d.allDay,void 0===h&&(h=ba(e?e.allDayDefault:void 0,c.allDayDefault)),t(f,g,h,i)}return i}function t(a,b,c,d){d.start=a,d.end=b,d.allDay=c,u(d),Ua(d)}function u(a){v(a),a.end&&!a.end.isAfter(a.start)&&(a.end=null),a.end||(c.forceEventDuration?a.end=H.getDefaultEventEnd(a.allDay,a.start):a.end=null)}function v(a){null==a.allDay&&(a.allDay=!(a.start.hasTime()||a.end&&a.end.hasTime())),a.allDay?(a.start.stripTime(),a.end&&a.end.stripTime()):(a.start.hasTime()||(a.start=H.applyTimezone(a.start.time(0))),a.end&&!a.end.hasTime()&&(a.end=H.applyTimezone(a.end.time(0))))}function w(b,c,d){var e,f,g,h,i,j,k,l,m,n=[];if(c=c||I,d=d||J,b)if(b._recurring){if(f=b.dow)for(e={},g=0;g<f.length;g++)e[f[g]]=!0;for(h=c.clone().stripTime();h.isBefore(d);)e&&!e[h.day()]||(i=b.start,j=b.end,k=h.clone(),l=null,i&&(k=k.time(i)),j&&(l=h.clone().time(j)),m=a.extend({},b),t(k,l,!i&&!j,m),n.push(m)),h.add(1,"days")}else n.push(b);return n}function x(b,c,d){function e(a,b){return d?N(a,b,d):c.allDay?M(a,b):L(a,b)}var f,g,h,i,j,k,l={};return c=c||{},c.start||(c.start=b.start.clone()),void 0===c.end&&(c.end=b.end?b.end.clone():null),null==c.allDay&&(c.allDay=b.allDay),u(c),f={start:b._start.clone(),end:b._end?b._end.clone():H.getDefaultEventEnd(b._allDay,b._start),allDay:c.allDay},u(f),g=null!==b._end&&null===c.end,h=e(c.start,f.start),c.end?(i=e(c.end,f.end),j=i.subtract(h)):j=null,a.each(c,function(a,b){o(a)&&void 0!==b&&(l[a]=b)}),k=y(r(b._id),g,c.allDay,h,j,l),{dateDelta:h,durationDelta:j,undo:k}}function y(b,c,d,e,f,g){var h=H.getIsAmbigTimezone(),i=[];return e&&!e.valueOf()&&(e=null),f&&!f.valueOf()&&(f=null),a.each(b,function(b,j){var k,l;k={start:j.start.clone(),end:j.end?j.end.clone():null,allDay:j.allDay},a.each(g,function(a){k[a]=j[a]}),l={start:j._start,end:j._end,allDay:d},u(l),c?l.end=null:f&&!l.end&&(l.end=H.getDefaultEventEnd(l.allDay,l.start)),e&&(l.start.add(e),l.end&&l.end.add(e)),f&&l.end.add(f),h&&!l.allDay&&(e||f)&&(l.start.stripZone(),l.end&&l.end.stripZone()),a.extend(j,g,l),Ua(j),i.push(function(){a.extend(j,k),Ua(j)})}),function(){for(var a=0;a<i.length;a++)i[a]()}}function z(b){var d,e=c.businessHours,f={className:"fc-nonbusiness",start:"09:00",end:"17:00",dow:[1,2,3,4,5],rendering:"inverse-background"},g=H.getView();return e&&(d=a.extend({},f,"object"==typeof e?e:{})),d?(b&&(d.start=null,d.end=null),w(s(d),g.start,g.end)):[]}function A(a,b){var d=b.source||{},e=ba(b.constraint,d.constraint,c.eventConstraint),f=ba(b.overlap,d.overlap,c.eventOverlap);return D(a,e,f,b)}function B(b,c,d){var e,f;return d&&(e=a.extend({},d,c),f=w(s(e))[0]),f?A(b,f):C(b)}function C(a){return D(a,c.selectConstraint,c.selectOverlap)}function D(a,b,c,d){var e,f,g,h,i,j;if(null!=b){for(e=E(b),f=!1,h=0;h<e.length;h++)if(F(e[h],a)){f=!0;break}if(!f)return!1}for(g=H.getPeerEvents(a,d),h=0;h<g.length;h++)if(i=g[h],G(i,a)){if(c===!1)return!1;if("function"==typeof c&&!c(i,d))return!1;if(d){if(j=ba(i.overlap,(i.source||{}).overlap),j===!1)return!1;if("function"==typeof j&&!j(d,i))return!1}}return!0}function E(a){return"businessHours"===a?z():"object"==typeof a?w(s(a)):r(a)}function F(a,b){var c=a.start.clone().stripZone(),d=H.getEventEnd(a).stripZone();return b.start>=c&&b.end<=d}function G(a,b){var c=a.start.clone().stripZone(),d=H.getEventEnd(a).stripZone();return b.start<d&&b.end>c}var H=this;H.isFetchNeeded=d,H.fetchEvents=e,H.addEventSource=h,H.removeEventSource=j,H.updateEvent=m,H.renderEvent=p,H.removeEvents=q,H.clientEvents=r,H.mutateEvent=x,H.normalizeEventDates=u,H.normalizeEventTimes=v;var I,J,K=H.reportEvents,O={events:[]},P=[O],Q=0,R=0,S=[];a.each((c.events?[c.events]:[]).concat(c.eventSources||[]),function(a,b){var c=i(b);c&&P.push(c)}),H.getBusinessHoursEvents=z,H.isEventSpanAllowed=A,H.isExternalSpanAllowed=B,H.isSelectionSpanAllowed=C,H.getEventCache=function(){return S}}function Ua(a){a._allDay=a.allDay,a._start=a.start.clone(),a._end=a.end?a.end.clone():null}var Va=a.fullCalendar={version:"2.7.2",internalApiVersion:3},Wa=Va.views={};a.fn.fullCalendar=function(b){var c=Array.prototype.slice.call(arguments,1),d=this;return this.each(function(e,f){var g,h=a(f),i=h.data("fullCalendar");"string"==typeof b?i&&a.isFunction(i[b])&&(g=i[b].apply(i,c),e||(d=g),"destroy"===b&&h.removeData("fullCalendar")):i||(i=new yb(h,b),h.data("fullCalendar",i),i.render())}),d};var Xa=["header","buttonText","buttonIcons","themeButtonIcons"];Va.intersectRanges=K,Va.applyAll=aa,Va.debounce=ja,Va.isInt=ha,Va.htmlEscape=ca,Va.cssToStr=ea,Va.proxy=ia,Va.capitaliseFirstLetter=fa,Va.getOuterRect=n,Va.getClientRect=o,Va.getContentRect=p,Va.getScrollbarWidths=q;var Ya=null;Va.preventDefault=z,Va.intersectRects=C,Va.parseFieldSpecs=G,Va.compareByFieldSpecs=H,Va.compareByFieldSpec=I,Va.flexibleCompare=J,Va.computeIntervalUnit=O,Va.divideRangeByDuration=Q,Va.divideDurationByDuration=R,Va.multiplyDuration=S,Va.durationHasTime=T;var Za=["sun","mon","tue","wed","thu","fri","sat"],$a=["year","month","week","day","hour","minute","second","millisecond"];Va.log=function(){var a=window.console;return a&&a.log?a.log.apply(a,arguments):void 0},Va.warn=function(){var a=window.console;return a&&a.warn?a.warn.apply(a,arguments):Va.log.apply(Va,arguments)};var _a,ab,bb,cb={}.hasOwnProperty,db=/^\s*\d{4}-\d\d$/,eb=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?)?$/,fb=b.fn,gb=a.extend({},fb);Va.moment=function(){return ka(arguments)},Va.moment.utc=function(){var a=ka(arguments,!0);return a.hasTime()&&a.utc(),a},Va.moment.parseZone=function(){return ka(arguments,!0,!0)},fb.clone=function(){var a=gb.clone.apply(this,arguments);return ma(this,a),this._fullCalendar&&(a._fullCalendar=!0),a},fb.week=fb.weeks=function(a){var b=(this._locale||this._lang)._fullCalendar_weekCalc;return null==a&&"function"==typeof b?b(this):"ISO"===b?gb.isoWeek.apply(this,arguments):gb.week.apply(this,arguments)},fb.time=function(a){if(!this._fullCalendar)return gb.time.apply(this,arguments);if(null==a)return b.duration({hours:this.hours(),minutes:this.minutes(),seconds:this.seconds(),milliseconds:this.milliseconds()});this._ambigTime=!1,b.isDuration(a)||b.isMoment(a)||(a=b.duration(a));var c=0;return b.isDuration(a)&&(c=24*Math.floor(a.asDays())),this.hours(c+a.hours()).minutes(a.minutes()).seconds(a.seconds()).milliseconds(a.milliseconds())},fb.stripTime=function(){var a;return this._ambigTime||(a=this.toArray(),this.utc(),ab(this,a.slice(0,3)),this._ambigTime=!0,this._ambigZone=!0),this},fb.hasTime=function(){return!this._ambigTime},fb.stripZone=function(){var a,b;return this._ambigZone||(a=this.toArray(),b=this._ambigTime,this.utc(),ab(this,a),this._ambigTime=b||!1,this._ambigZone=!0),this},fb.hasZone=function(){return!this._ambigZone},fb.local=function(){var a=this.toArray(),b=this._ambigZone;return gb.local.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,b&&bb(this,a),this},fb.utc=function(){return gb.utc.apply(this,arguments),this._ambigTime=!1,this._ambigZone=!1,this},a.each(["zone","utcOffset"],function(a,b){gb[b]&&(fb[b]=function(a){return null!=a&&(this._ambigTime=!1,this._ambigZone=!1),gb[b].apply(this,arguments)})}),fb.format=function(){return this._fullCalendar&&arguments[0]?pa(this,arguments[0]):this._ambigTime?oa(this,"YYYY-MM-DD"):this._ambigZone?oa(this,"YYYY-MM-DD[T]HH:mm:ss"):gb.format.apply(this,arguments)},fb.toISOString=function(){return this._ambigTime?oa(this,"YYYY-MM-DD"):this._ambigZone?oa(this,"YYYY-MM-DD[T]HH:mm:ss"):gb.toISOString.apply(this,arguments)},fb.isWithin=function(a,b){var c=la([this,a,b]);return c[0]>=c[1]&&c[0]<c[2]},fb.isSame=function(a,b){var c;return this._fullCalendar?b?(c=la([this,a],!0),gb.isSame.call(c[0],c[1],b)):(a=Va.moment.parseZone(a),gb.isSame.call(this,a)&&Boolean(this._ambigTime)===Boolean(a._ambigTime)&&Boolean(this._ambigZone)===Boolean(a._ambigZone)):gb.isSame.apply(this,arguments)},a.each(["isBefore","isAfter"],function(a,b){fb[b]=function(a,c){var d;return this._fullCalendar?(d=la([this,a]),gb[b].call(d[0],d[1],c)):gb[b].apply(this,arguments)}}),_a="_d"in b()&&"updateOffset"in b,ab=_a?function(a,c){a._d.setTime(Date.UTC.apply(Date,c)),b.updateOffset(a,!1)}:na,bb=_a?function(a,c){a._d.setTime(+new Date(c[0]||0,c[1]||0,c[2]||0,c[3]||0,c[4]||0,c[5]||0,c[6]||0)),b.updateOffset(a,!1)}:na;var hb={t:function(a){return oa(a,"a").charAt(0)},T:function(a){return oa(a,"A").charAt(0)}};Va.formatRange=sa;var ib={Y:"year",M:"month",D:"day",d:"day",A:"second",a:"second",T:"second",t:"second",H:"second",h:"second",m:"second",s:"second"},jb={};Va.Class=xa,xa.extend=function(){var a,b,c=arguments.length;for(a=0;c>a;a++)b=arguments[a],c-1>a&&za(this,b);return ya(this,b||{})},xa.mixin=function(a){za(this,a)};var kb=Va.EmitterMixin={callbackHash:null,on:function(a,b){return this.loopCallbacks(a,"add",[b]),this},off:function(a,b){return this.loopCallbacks(a,"remove",[b]),this},trigger:function(a){var b=Array.prototype.slice.call(arguments,1);return this.triggerWith(a,this,b),this},triggerWith:function(a,b,c){return this.loopCallbacks(a,"fireWith",[b,c]),this},loopCallbacks:function(a,b,c){var d,e,f,g=a.split(".");for(d=0;d<g.length;d++)e=g[d],e&&(f=this.ensureCallbackObj((d?".":"")+e),f[b].apply(f,c))},ensureCallbackObj:function(b){return this.callbackHash||(this.callbackHash={}),this.callbackHash[b]||(this.callbackHash[b]=a.Callbacks()),this.callbackHash[b]}},lb=Va.ListenerMixin=function(){var b=0,c={listenerId:null,listenTo:function(b,c,d){if("object"==typeof c)for(var e in c)c.hasOwnProperty(e)&&this.listenTo(b,e,c[e]);else"string"==typeof c&&b.on(c+"."+this.getListenerNamespace(),a.proxy(d,this))},stopListeningTo:function(a,b){a.off((b||"")+"."+this.getListenerNamespace())},getListenerNamespace:function(){return null==this.listenerId&&(this.listenerId=b++),"_listener"+this.listenerId}};return c}(),mb={isIgnoringMouse:!1,delayUnignoreMouse:null,
+initMouseIgnoring:function(a){this.delayUnignoreMouse=ja(ia(this,"unignoreMouse"),a||1e3)},tempIgnoreMouse:function(){this.isIgnoringMouse=!0,this.delayUnignoreMouse()},unignoreMouse:function(){this.isIgnoringMouse=!1}},nb=xa.extend(lb,{isHidden:!0,options:null,el:null,margin:10,constructor:function(a){this.options=a||{}},show:function(){this.isHidden&&(this.el||this.render(),this.el.show(),this.position(),this.isHidden=!1,this.trigger("show"))},hide:function(){this.isHidden||(this.el.hide(),this.isHidden=!0,this.trigger("hide"))},render:function(){var b=this,c=this.options;this.el=a('<div class="fc-popover"/>').addClass(c.className||"").css({top:0,left:0}).append(c.content).appendTo(c.parentEl),this.el.on("click",".fc-close",function(){b.hide()}),c.autoHide&&this.listenTo(a(document),"mousedown",this.documentMousedown)},documentMousedown:function(b){this.el&&!a(b.target).closest(this.el).length&&this.hide()},removeElement:function(){this.hide(),this.el&&(this.el.remove(),this.el=null),this.stopListeningTo(a(document),"mousedown")},position:function(){var b,c,d,e,f,g=this.options,h=this.el.offsetParent().offset(),i=this.el.outerWidth(),j=this.el.outerHeight(),k=a(window),l=m(this.el);e=g.top||0,f=void 0!==g.left?g.left:void 0!==g.right?g.right-i:0,l.is(window)||l.is(document)?(l=k,b=0,c=0):(d=l.offset(),b=d.top,c=d.left),b+=k.scrollTop(),c+=k.scrollLeft(),g.viewportConstrain!==!1&&(e=Math.min(e,b+l.outerHeight()-j-this.margin),e=Math.max(e,b+this.margin),f=Math.min(f,c+l.outerWidth()-i-this.margin),f=Math.max(f,c+this.margin)),this.el.css({top:e-h.top,left:f-h.left})},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1))}}),ob=Va.CoordCache=xa.extend({els:null,forcedOffsetParentEl:null,origin:null,boundingRect:null,isHorizontal:!1,isVertical:!1,lefts:null,rights:null,tops:null,bottoms:null,constructor:function(b){this.els=a(b.els),this.isHorizontal=b.isHorizontal,this.isVertical=b.isVertical,this.forcedOffsetParentEl=b.offsetParent?a(b.offsetParent):null},build:function(){var a=this.forcedOffsetParentEl||this.els.eq(0).offsetParent();this.origin=a.offset(),this.boundingRect=this.queryBoundingRect(),this.isHorizontal&&this.buildElHorizontals(),this.isVertical&&this.buildElVerticals()},clear:function(){this.origin=null,this.boundingRect=null,this.lefts=null,this.rights=null,this.tops=null,this.bottoms=null},ensureBuilt:function(){this.origin||this.build()},queryBoundingRect:function(){var a=m(this.els.eq(0));return a.is(document)?void 0:o(a)},buildElHorizontals:function(){var b=[],c=[];this.els.each(function(d,e){var f=a(e),g=f.offset().left,h=f.outerWidth();b.push(g),c.push(g+h)}),this.lefts=b,this.rights=c},buildElVerticals:function(){var b=[],c=[];this.els.each(function(d,e){var f=a(e),g=f.offset().top,h=f.outerHeight();b.push(g),c.push(g+h)}),this.tops=b,this.bottoms=c},getHorizontalIndex:function(a){this.ensureBuilt();var b,c=this.boundingRect,d=this.lefts,e=this.rights,f=d.length;if(!c||a>=c.left&&a<c.right)for(b=0;f>b;b++)if(a>=d[b]&&a<e[b])return b},getVerticalIndex:function(a){this.ensureBuilt();var b,c=this.boundingRect,d=this.tops,e=this.bottoms,f=d.length;if(!c||a>=c.top&&a<c.bottom)for(b=0;f>b;b++)if(a>=d[b]&&a<e[b])return b},getLeftOffset:function(a){return this.ensureBuilt(),this.lefts[a]},getLeftPosition:function(a){return this.ensureBuilt(),this.lefts[a]-this.origin.left},getRightOffset:function(a){return this.ensureBuilt(),this.rights[a]},getRightPosition:function(a){return this.ensureBuilt(),this.rights[a]-this.origin.left},getWidth:function(a){return this.ensureBuilt(),this.rights[a]-this.lefts[a]},getTopOffset:function(a){return this.ensureBuilt(),this.tops[a]},getTopPosition:function(a){return this.ensureBuilt(),this.tops[a]-this.origin.top},getBottomOffset:function(a){return this.ensureBuilt(),this.bottoms[a]},getBottomPosition:function(a){return this.ensureBuilt(),this.bottoms[a]-this.origin.top},getHeight:function(a){return this.ensureBuilt(),this.bottoms[a]-this.tops[a]}}),pb=Va.DragListener=xa.extend(lb,mb,{options:null,subjectEl:null,subjectHref:null,originX:null,originY:null,scrollEl:null,isInteracting:!1,isDistanceSurpassed:!1,isDelayEnded:!1,isDragging:!1,isTouch:!1,delay:null,delayTimeoutId:null,minDistance:null,handleTouchScrollProxy:null,constructor:function(a){this.options=a||{},this.handleTouchScrollProxy=ia(this,"handleTouchScroll"),this.initMouseIgnoring(500)},startInteraction:function(b,c){var d=x(b);if("mousedown"===b.type){if(this.isIgnoringMouse)return;if(!u(b))return;b.preventDefault()}this.isInteracting||(c=c||{},this.delay=ba(c.delay,this.options.delay,0),this.minDistance=ba(c.distance,this.options.distance,0),this.subjectEl=this.options.subjectEl,this.isInteracting=!0,this.isTouch=d,this.isDelayEnded=!1,this.isDistanceSurpassed=!1,this.originX=v(b),this.originY=w(b),this.scrollEl=m(a(b.target)),this.bindHandlers(),this.initAutoScroll(),this.handleInteractionStart(b),this.startDelay(b),this.minDistance||this.handleDistanceSurpassed(b))},handleInteractionStart:function(a){this.trigger("interactionStart",a)},endInteraction:function(a,b){this.isInteracting&&(this.endDrag(a),this.delayTimeoutId&&(clearTimeout(this.delayTimeoutId),this.delayTimeoutId=null),this.destroyAutoScroll(),this.unbindHandlers(),this.isInteracting=!1,this.handleInteractionEnd(a,b),this.isTouch&&this.tempIgnoreMouse())},handleInteractionEnd:function(a,b){this.trigger("interactionEnd",a,b||!1)},bindHandlers:function(){var b=this,c=1;this.isTouch?(this.listenTo(a(document),{touchmove:this.handleTouchMove,touchend:this.endInteraction,touchcancel:this.endInteraction,touchstart:function(a){c?c--:b.endInteraction(a,!0)}}),!A(this.handleTouchScrollProxy)&&this.scrollEl&&this.listenTo(this.scrollEl,"scroll",this.handleTouchScroll)):this.listenTo(a(document),{mousemove:this.handleMouseMove,mouseup:this.endInteraction}),this.listenTo(a(document),{selectstart:z,contextmenu:z})},unbindHandlers:function(){this.stopListeningTo(a(document)),B(this.handleTouchScrollProxy),this.scrollEl&&this.stopListeningTo(this.scrollEl,"scroll")},startDrag:function(a,b){this.startInteraction(a,b),this.isDragging||(this.isDragging=!0,this.handleDragStart(a))},handleDragStart:function(a){this.trigger("dragStart",a),this.initHrefHack()},handleMove:function(a){var b,c=v(a)-this.originX,d=w(a)-this.originY,e=this.minDistance;this.isDistanceSurpassed||(b=c*c+d*d,b>=e*e&&this.handleDistanceSurpassed(a)),this.isDragging&&this.handleDrag(c,d,a)},handleDrag:function(a,b,c){this.trigger("drag",a,b,c),this.updateAutoScroll(c)},endDrag:function(a){this.isDragging&&(this.isDragging=!1,this.handleDragEnd(a))},handleDragEnd:function(a){this.trigger("dragEnd",a),this.destroyHrefHack()},startDelay:function(a){var b=this;this.delay?this.delayTimeoutId=setTimeout(function(){b.handleDelayEnd(a)},this.delay):this.handleDelayEnd(a)},handleDelayEnd:function(a){this.isDelayEnded=!0,this.isDistanceSurpassed&&this.startDrag(a)},handleDistanceSurpassed:function(a){this.isDistanceSurpassed=!0,this.isDelayEnded&&this.startDrag(a)},handleTouchMove:function(a){this.isDragging&&a.preventDefault(),this.handleMove(a)},handleMouseMove:function(a){this.handleMove(a)},handleTouchScroll:function(a){this.isDragging||this.endInteraction(a,!0)},initHrefHack:function(){var a=this.subjectEl;(this.subjectHref=a?a.attr("href"):null)&&a.removeAttr("href")},destroyHrefHack:function(){var a=this.subjectEl,b=this.subjectHref;setTimeout(function(){b&&a.attr("href",b)},0)},trigger:function(a){this.options[a]&&this.options[a].apply(this,Array.prototype.slice.call(arguments,1)),this["_"+a]&&this["_"+a].apply(this,Array.prototype.slice.call(arguments,1))}});pb.mixin({isAutoScroll:!1,scrollBounds:null,scrollTopVel:null,scrollLeftVel:null,scrollIntervalId:null,scrollSensitivity:30,scrollSpeed:200,scrollIntervalMs:50,initAutoScroll:function(){var a=this.scrollEl;this.isAutoScroll=this.options.scroll&&a&&!a.is(window)&&!a.is(document),this.isAutoScroll&&this.listenTo(a,"scroll",ja(this.handleDebouncedScroll,100))},destroyAutoScroll:function(){this.endAutoScroll(),this.isAutoScroll&&this.stopListeningTo(this.scrollEl,"scroll")},computeScrollBounds:function(){this.isAutoScroll&&(this.scrollBounds=n(this.scrollEl))},updateAutoScroll:function(a){var b,c,d,e,f=this.scrollSensitivity,g=this.scrollBounds,h=0,i=0;g&&(b=(f-(w(a)-g.top))/f,c=(f-(g.bottom-w(a)))/f,d=(f-(v(a)-g.left))/f,e=(f-(g.right-v(a)))/f,b>=0&&1>=b?h=b*this.scrollSpeed*-1:c>=0&&1>=c&&(h=c*this.scrollSpeed),d>=0&&1>=d?i=d*this.scrollSpeed*-1:e>=0&&1>=e&&(i=e*this.scrollSpeed)),this.setScrollVel(h,i)},setScrollVel:function(a,b){this.scrollTopVel=a,this.scrollLeftVel=b,this.constrainScrollVel(),!this.scrollTopVel&&!this.scrollLeftVel||this.scrollIntervalId||(this.scrollIntervalId=setInterval(ia(this,"scrollIntervalFunc"),this.scrollIntervalMs))},constrainScrollVel:function(){var a=this.scrollEl;this.scrollTopVel<0?a.scrollTop()<=0&&(this.scrollTopVel=0):this.scrollTopVel>0&&a.scrollTop()+a[0].clientHeight>=a[0].scrollHeight&&(this.scrollTopVel=0),this.scrollLeftVel<0?a.scrollLeft()<=0&&(this.scrollLeftVel=0):this.scrollLeftVel>0&&a.scrollLeft()+a[0].clientWidth>=a[0].scrollWidth&&(this.scrollLeftVel=0)},scrollIntervalFunc:function(){var a=this.scrollEl,b=this.scrollIntervalMs/1e3;this.scrollTopVel&&a.scrollTop(a.scrollTop()+this.scrollTopVel*b),this.scrollLeftVel&&a.scrollLeft(a.scrollLeft()+this.scrollLeftVel*b),this.constrainScrollVel(),this.scrollTopVel||this.scrollLeftVel||this.endAutoScroll()},endAutoScroll:function(){this.scrollIntervalId&&(clearInterval(this.scrollIntervalId),this.scrollIntervalId=null,this.handleScrollEnd())},handleDebouncedScroll:function(){this.scrollIntervalId||this.handleScrollEnd()},handleScrollEnd:function(){}});var qb=pb.extend({component:null,origHit:null,hit:null,coordAdjust:null,constructor:function(a,b){pb.call(this,b),this.component=a},handleInteractionStart:function(a){var b,c,d,e=this.subjectEl;this.computeCoords(),a?(c={left:v(a),top:w(a)},d=c,e&&(b=n(e),d=D(d,b)),this.origHit=this.queryHit(d.left,d.top),e&&this.options.subjectCenter&&(this.origHit&&(b=C(this.origHit,b)||b),d=E(b)),this.coordAdjust=F(d,c)):(this.origHit=null,this.coordAdjust=null),pb.prototype.handleInteractionStart.apply(this,arguments)},computeCoords:function(){this.component.prepareHits(),this.computeScrollBounds()},handleDragStart:function(a){var b;pb.prototype.handleDragStart.apply(this,arguments),b=this.queryHit(v(a),w(a)),b&&this.handleHitOver(b)},handleDrag:function(a,b,c){var d;pb.prototype.handleDrag.apply(this,arguments),d=this.queryHit(v(c),w(c)),Aa(d,this.hit)||(this.hit&&this.handleHitOut(),d&&this.handleHitOver(d))},handleDragEnd:function(){this.handleHitDone(),pb.prototype.handleDragEnd.apply(this,arguments)},handleHitOver:function(a){var b=Aa(a,this.origHit);this.hit=a,this.trigger("hitOver",this.hit,b,this.origHit)},handleHitOut:function(){this.hit&&(this.trigger("hitOut",this.hit),this.handleHitDone(),this.hit=null)},handleHitDone:function(){this.hit&&this.trigger("hitDone",this.hit)},handleInteractionEnd:function(){pb.prototype.handleInteractionEnd.apply(this,arguments),this.origHit=null,this.hit=null,this.component.releaseHits()},handleScrollEnd:function(){pb.prototype.handleScrollEnd.apply(this,arguments),this.computeCoords()},queryHit:function(a,b){return this.coordAdjust&&(a+=this.coordAdjust.left,b+=this.coordAdjust.top),this.component.queryHit(a,b)}}),rb=xa.extend(lb,{options:null,sourceEl:null,el:null,parentEl:null,top0:null,left0:null,y0:null,x0:null,topDelta:null,leftDelta:null,isFollowing:!1,isHidden:!1,isAnimating:!1,constructor:function(b,c){this.options=c=c||{},this.sourceEl=b,this.parentEl=c.parentEl?a(c.parentEl):b.parent()},start:function(b){this.isFollowing||(this.isFollowing=!0,this.y0=w(b),this.x0=v(b),this.topDelta=0,this.leftDelta=0,this.isHidden||this.updatePosition(),x(b)?this.listenTo(a(document),"touchmove",this.handleMove):this.listenTo(a(document),"mousemove",this.handleMove))},stop:function(b,c){function d(){this.isAnimating=!1,e.removeElement(),this.top0=this.left0=null,c&&c()}var e=this,f=this.options.revertDuration;this.isFollowing&&!this.isAnimating&&(this.isFollowing=!1,this.stopListeningTo(a(document)),b&&f&&!this.isHidden?(this.isAnimating=!0,this.el.animate({top:this.top0,left:this.left0},{duration:f,complete:d})):d())},getEl:function(){var a=this.el;return a||(this.sourceEl.width(),a=this.el=this.sourceEl.clone().addClass(this.options.additionalClass||"").css({position:"absolute",visibility:"",display:this.isHidden?"none":"",margin:0,right:"auto",bottom:"auto",width:this.sourceEl.width(),height:this.sourceEl.height(),opacity:this.options.opacity||"",zIndex:this.options.zIndex}),a.addClass("fc-unselectable"),a.appendTo(this.parentEl)),a},removeElement:function(){this.el&&(this.el.remove(),this.el=null)},updatePosition:function(){var a,b;this.getEl(),null===this.top0&&(this.sourceEl.width(),a=this.sourceEl.offset(),b=this.el.offsetParent().offset(),this.top0=a.top-b.top,this.left0=a.left-b.left),this.el.css({top:this.top0+this.topDelta,left:this.left0+this.leftDelta})},handleMove:function(a){this.topDelta=w(a)-this.y0,this.leftDelta=v(a)-this.x0,this.isHidden||this.updatePosition()},hide:function(){this.isHidden||(this.isHidden=!0,this.el&&this.el.hide())},show:function(){this.isHidden&&(this.isHidden=!1,this.updatePosition(),this.getEl().show())}}),sb=Va.Grid=xa.extend(lb,mb,{view:null,isRTL:null,start:null,end:null,el:null,elsByFill:null,eventTimeFormat:null,displayEventTime:null,displayEventEnd:null,minResizeDuration:null,largeUnit:null,dayDragListener:null,segDragListener:null,segResizeListener:null,externalDragListener:null,constructor:function(a){this.view=a,this.isRTL=a.opt("isRTL"),this.elsByFill={},this.dayDragListener=this.buildDayDragListener(),this.initMouseIgnoring()},computeEventTimeFormat:function(){return this.view.opt("smallTimeFormat")},computeDisplayEventTime:function(){return!0},computeDisplayEventEnd:function(){return!0},setRange:function(a){this.start=a.start.clone(),this.end=a.end.clone(),this.rangeUpdated(),this.processRangeOptions()},rangeUpdated:function(){},processRangeOptions:function(){var a,b,c=this.view;this.eventTimeFormat=c.opt("eventTimeFormat")||c.opt("timeFormat")||this.computeEventTimeFormat(),a=c.opt("displayEventTime"),null==a&&(a=this.computeDisplayEventTime()),b=c.opt("displayEventEnd"),null==b&&(b=this.computeDisplayEventEnd()),this.displayEventTime=a,this.displayEventEnd=b},spanToSegs:function(a){},diffDates:function(a,b){return this.largeUnit?N(a,b,this.largeUnit):L(a,b)},prepareHits:function(){},releaseHits:function(){},queryHit:function(a,b){},getHitSpan:function(a){},getHitEl:function(a){},setElement:function(a){this.el=a,y(a),this.bindDayHandler("touchstart",this.dayTouchStart),this.bindDayHandler("mousedown",this.dayMousedown),this.bindSegHandlers(),this.bindGlobalHandlers()},bindDayHandler:function(b,c){var d=this;this.el.on(b,function(b){return a(b.target).is(".fc-event-container *, .fc-more")||a(b.target).closest(".fc-popover").length?void 0:c.call(d,b)})},removeElement:function(){this.unbindGlobalHandlers(),this.clearDragListeners(),this.el.remove()},renderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},bindGlobalHandlers:function(){this.listenTo(a(document),{dragstart:this.externalDragStart,sortstart:this.externalDragStart})},unbindGlobalHandlers:function(){this.stopListeningTo(a(document))},dayMousedown:function(a){this.isIgnoringMouse||this.dayDragListener.startInteraction(a,{})},dayTouchStart:function(a){var b=this.view;(b.isSelected||b.selectedEvent)&&this.tempIgnoreMouse(),this.dayDragListener.startInteraction(a,{delay:this.view.opt("longPressDelay")})},buildDayDragListener:function(){var a,b,c=this,d=this.view,e=d.opt("selectable"),f=new qb(this,{scroll:d.opt("dragScroll"),interactionStart:function(){a=f.origHit},dragStart:function(){d.unselect()},hitOver:function(d,f,h){h&&(f||(a=null),e&&(b=c.computeSelection(c.getHitSpan(h),c.getHitSpan(d)),b?c.renderSelection(b):b===!1&&g()))},hitOut:function(){a=null,b=null,c.unrenderSelection(),h()},interactionEnd:function(e,f){f||(a&&!c.isIgnoringMouse&&d.triggerDayClick(c.getHitSpan(a),c.getHitEl(a),e),b&&d.reportSelection(b,e),h())}});return f},clearDragListeners:function(){this.dayDragListener.endInteraction(),this.segDragListener&&this.segDragListener.endInteraction(),this.segResizeListener&&this.segResizeListener.endInteraction(),this.externalDragListener&&this.externalDragListener.endInteraction()},renderEventLocationHelper:function(a,b){var c=this.fabricateHelperEvent(a,b);return this.renderHelper(c,b)},fabricateHelperEvent:function(a,b){var c=b?X(b.event):{};return c.start=a.start.clone(),c.end=a.end?a.end.clone():null,c.allDay=null,this.view.calendar.normalizeEventDates(c),c.className=(c.className||[]).concat("fc-helper"),b||(c.editable=!1),c},renderHelper:function(a,b){},unrenderHelper:function(){},renderSelection:function(a){this.renderHighlight(a)},unrenderSelection:function(){this.unrenderHighlight()},computeSelection:function(a,b){var c=this.computeSelectionSpan(a,b);return c&&!this.view.calendar.isSelectionSpanAllowed(c)?!1:c},computeSelectionSpan:function(a,b){var c=[a.start,a.end,b.start,b.end];return c.sort(ga),{start:c[0].clone(),end:c[3].clone()}},renderHighlight:function(a){this.renderFill("highlight",this.spanToSegs(a))},unrenderHighlight:function(){this.unrenderFill("highlight")},highlightSegClasses:function(){return["fc-highlight"]},renderBusinessHours:function(){},unrenderBusinessHours:function(){},getNowIndicatorUnit:function(){},renderNowIndicator:function(a){},unrenderNowIndicator:function(){},renderFill:function(a,b){},unrenderFill:function(a){var b=this.elsByFill[a];b&&(b.remove(),delete this.elsByFill[a])},renderFillSegEls:function(b,c){var d,e=this,f=this[b+"SegEl"],g="",h=[];if(c.length){for(d=0;d<c.length;d++)g+=this.fillSegHtml(b,c[d]);a(g).each(function(b,d){var g=c[b],i=a(d);f&&(i=f.call(e,g,i)),i&&(i=a(i),i.is(e.fillSegTag)&&(g.el=i,h.push(g)))})}return h},fillSegTag:"div",fillSegHtml:function(a,b){var c=this[a+"SegClasses"],d=this[a+"SegCss"],e=c?c.call(this,b):[],f=ea(d?d.call(this,b):{});return"<"+this.fillSegTag+(e.length?' class="'+e.join(" ")+'"':"")+(f?' style="'+f+'"':"")+" />"},getDayClasses:function(a){var b=this.view,c=b.calendar.getNow(),d=["fc-"+Za[a.day()]];return 1==b.intervalDuration.as("months")&&a.month()!=b.intervalStart.month()&&d.push("fc-other-month"),a.isSame(c,"day")?d.push("fc-today",b.highlightStateClass):c>a?d.push("fc-past"):d.push("fc-future"),d}});sb.mixin({mousedOverSeg:null,isDraggingSeg:!1,isResizingSeg:!1,isDraggingExternal:!1,segs:null,renderEvents:function(a){var b,c=[],d=[];for(b=0;b<a.length;b++)(Ca(a[b])?c:d).push(a[b]);this.segs=[].concat(this.renderBgEvents(c),this.renderFgEvents(d))},renderBgEvents:function(a){var b=this.eventsToSegs(a);return this.renderBgSegs(b)||b},renderFgEvents:function(a){var b=this.eventsToSegs(a);return this.renderFgSegs(b)||b},unrenderEvents:function(){this.handleSegMouseout(),this.clearDragListeners(),this.unrenderFgSegs(),this.unrenderBgSegs(),this.segs=null},getEventSegs:function(){return this.segs||[]},renderFgSegs:function(a){},unrenderFgSegs:function(){},renderFgSegEls:function(b,c){var d,e=this.view,f="",g=[];if(b.length){for(d=0;d<b.length;d++)f+=this.fgSegHtml(b[d],c);a(f).each(function(c,d){var f=b[c],h=e.resolveEventEl(f.event,a(d));h&&(h.data("fc-seg",f),f.el=h,g.push(f))})}return g},fgSegHtml:function(a,b){},renderBgSegs:function(a){return this.renderFill("bgEvent",a)},unrenderBgSegs:function(){this.unrenderFill("bgEvent")},bgEventSegEl:function(a,b){return this.view.resolveEventEl(a.event,b)},bgEventSegClasses:function(a){var b=a.event,c=b.source||{};return["fc-bgevent"].concat(b.className,c.className||[])},bgEventSegCss:function(a){return{"background-color":this.getSegSkinCss(a)["background-color"]}},businessHoursSegClasses:function(a){return["fc-nonbusiness","fc-bgevent"]},bindSegHandlers:function(){this.bindSegHandler("touchstart",this.handleSegTouchStart),this.bindSegHandler("touchend",this.handleSegTouchEnd),this.bindSegHandler("mouseenter",this.handleSegMouseover),this.bindSegHandler("mouseleave",this.handleSegMouseout),this.bindSegHandler("mousedown",this.handleSegMousedown),this.bindSegHandler("click",this.handleSegClick)},bindSegHandler:function(b,c){var d=this;this.el.on(b,".fc-event-container > *",function(b){var e=a(this).data("fc-seg");return!e||d.isDraggingSeg||d.isResizingSeg?void 0:c.call(d,e,b)})},handleSegClick:function(a,b){return this.view.trigger("eventClick",a.el[0],a.event,b)},handleSegMouseover:function(a,b){this.isIgnoringMouse||this.mousedOverSeg||(this.mousedOverSeg=a,a.el.addClass("fc-allow-mouse-resize"),this.view.trigger("eventMouseover",a.el[0],a.event,b))},handleSegMouseout:function(a,b){b=b||{},this.mousedOverSeg&&(a=a||this.mousedOverSeg,this.mousedOverSeg=null,a.el.removeClass("fc-allow-mouse-resize"),this.view.trigger("eventMouseout",a.el[0],a.event,b))},handleSegMousedown:function(a,b){var c=this.startSegResize(a,b,{distance:5});!c&&this.view.isEventDraggable(a.event)&&this.buildSegDragListener(a).startInteraction(b,{distance:5})},handleSegTouchStart:function(a,b){var c,d=this.view,e=a.event,f=d.isEventSelected(e),g=d.isEventDraggable(e),h=d.isEventResizable(e),i=!1;f&&h&&(i=this.startSegResize(a,b)),i||!g&&!h||(c=g?this.buildSegDragListener(a):this.buildSegSelectListener(a),c.startInteraction(b,{delay:f?0:this.view.opt("longPressDelay")})),this.tempIgnoreMouse()},handleSegTouchEnd:function(a,b){this.tempIgnoreMouse()},startSegResize:function(b,c,d){return a(c.target).is(".fc-resizer")?(this.buildSegResizeListener(b,a(c.target).is(".fc-start-resizer")).startInteraction(c,d),!0):!1},buildSegDragListener:function(a){var b,c,d,e=this,f=this.view,i=f.calendar,j=a.el,k=a.event;if(this.segDragListener)return this.segDragListener;var l=this.segDragListener=new qb(f,{scroll:f.opt("dragScroll"),subjectEl:j,subjectCenter:!0,interactionStart:function(d){b=!1,c=new rb(a.el,{additionalClass:"fc-dragging",parentEl:f.el,opacity:l.isTouch?null:f.opt("dragOpacity"),revertDuration:f.opt("dragRevertDuration"),zIndex:2}),c.hide(),c.start(d)},dragStart:function(c){l.isTouch&&!f.isEventSelected(k)&&f.selectEvent(k),b=!0,e.handleSegMouseout(a,c),e.segDragStart(a,c),f.hideEvent(k)},hitOver:function(b,h,j){var m;a.hit&&(j=a.hit),d=e.computeEventDrop(j.component.getHitSpan(j),b.component.getHitSpan(b),k),d&&!i.isEventSpanAllowed(e.eventToSpan(d),k)&&(g(),d=null),d&&(m=f.renderDrag(d,a))?(m.addClass("fc-dragging"),l.isTouch||e.applyDragOpacity(m),c.hide()):c.show(),h&&(d=null)},hitOut:function(){f.unrenderDrag(),c.show(),d=null},hitDone:function(){h()},interactionEnd:function(g){c.stop(!d,function(){b&&(f.unrenderDrag(),f.showEvent(k),e.segDragStop(a,g)),d&&f.reportEventDrop(k,d,this.largeUnit,j,g)}),e.segDragListener=null}});return l},buildSegSelectListener:function(a){var b=this,c=this.view,d=a.event;if(this.segDragListener)return this.segDragListener;var e=this.segDragListener=new pb({dragStart:function(a){e.isTouch&&!c.isEventSelected(d)&&c.selectEvent(d)},interactionEnd:function(a){b.segDragListener=null}});return e},segDragStart:function(a,b){this.isDraggingSeg=!0,this.view.trigger("eventDragStart",a.el[0],a.event,b,{})},segDragStop:function(a,b){this.isDraggingSeg=!1,this.view.trigger("eventDragStop",a.el[0],a.event,b,{})},computeEventDrop:function(a,b,c){var d,e,f=this.view.calendar,g=a.start,h=b.start;return g.hasTime()===h.hasTime()?(d=this.diffDates(h,g),c.allDay&&T(d)?(e={start:c.start.clone(),end:f.getEventEnd(c),allDay:!1},f.normalizeEventTimes(e)):e={start:c.start.clone(),end:c.end?c.end.clone():null,allDay:c.allDay},e.start.add(d),e.end&&e.end.add(d)):e={start:h.clone(),end:null,allDay:!h.hasTime()},e},applyDragOpacity:function(a){var b=this.view.opt("dragOpacity");null!=b&&a.each(function(a,c){c.style.opacity=b})},externalDragStart:function(b,c){var d,e,f=this.view;f.opt("droppable")&&(d=a((c?c.item:null)||b.target),e=f.opt("dropAccept"),(a.isFunction(e)?e.call(d[0],d):d.is(e))&&(this.isDraggingExternal||this.listenToExternalDrag(d,b,c)))},listenToExternalDrag:function(a,b,c){var d,e=this,f=this.view.calendar,i=Ha(a),j=e.externalDragListener=new qb(this,{interactionStart:function(){e.isDraggingExternal=!0},hitOver:function(a){d=e.computeExternalDrop(a.component.getHitSpan(a),i),d&&!f.isExternalSpanAllowed(e.eventToSpan(d),d,i.eventProps)&&(g(),d=null),d&&e.renderDrag(d)},hitOut:function(){d=null},hitDone:function(){h(),e.unrenderDrag()},interactionEnd:function(b){d&&e.view.reportExternalDrop(i,d,a,b,c),e.isDraggingExternal=!1,e.externalDragListener=null}});j.startDrag(b)},computeExternalDrop:function(a,b){var c=this.view.calendar,d={start:c.applyTimezone(a.start),end:null};return b.startTime&&!d.start.hasTime()&&d.start.time(b.startTime),b.duration&&(d.end=d.start.clone().add(b.duration)),d},renderDrag:function(a,b){},unrenderDrag:function(){},buildSegResizeListener:function(a,b){var c,d,e=this,f=this.view,i=f.calendar,j=a.el,k=a.event,l=i.getEventEnd(k),m=this.segResizeListener=new qb(this,{scroll:f.opt("dragScroll"),subjectEl:j,interactionStart:function(){c=!1},dragStart:function(b){c=!0,e.handleSegMouseout(a,b),e.segResizeStart(a,b)},hitOver:function(c,h,j){var m=e.getHitSpan(j),n=e.getHitSpan(c);d=b?e.computeEventStartResize(m,n,k):e.computeEventEndResize(m,n,k),d&&(i.isEventSpanAllowed(e.eventToSpan(d),k)?d.start.isSame(k.start)&&d.end.isSame(l)&&(d=null):(g(),d=null)),d&&(f.hideEvent(k),e.renderEventResize(d,a))},hitOut:function(){d=null},hitDone:function(){e.unrenderEventResize(),f.showEvent(k),h()},interactionEnd:function(b){c&&e.segResizeStop(a,b),d&&f.reportEventResize(k,d,this.largeUnit,j,b),e.segResizeListener=null}});return m},segResizeStart:function(a,b){this.isResizingSeg=!0,this.view.trigger("eventResizeStart",a.el[0],a.event,b,{})},segResizeStop:function(a,b){this.isResizingSeg=!1,this.view.trigger("eventResizeStop",a.el[0],a.event,b,{})},computeEventStartResize:function(a,b,c){return this.computeEventResize("start",a,b,c)},computeEventEndResize:function(a,b,c){return this.computeEventResize("end",a,b,c)},computeEventResize:function(a,b,c,d){var e,f,g=this.view.calendar,h=this.diffDates(c[a],b[a]);return e={start:d.start.clone(),end:g.getEventEnd(d),allDay:d.allDay},e.allDay&&T(h)&&(e.allDay=!1,g.normalizeEventTimes(e)),e[a].add(h),e.start.isBefore(e.end)||(f=this.minResizeDuration||(d.allDay?g.defaultAllDayEventDuration:g.defaultTimedEventDuration),"start"==a?e.start=e.end.clone().subtract(f):e.end=e.start.clone().add(f)),e},renderEventResize:function(a,b){},unrenderEventResize:function(){},getEventTimeText:function(a,b,c){return null==b&&(b=this.eventTimeFormat),null==c&&(c=this.displayEventEnd),this.displayEventTime&&a.start.hasTime()?c&&a.end?this.view.formatRange(a,b):a.start.format(b):""},getSegClasses:function(a,b,c){var d=this.view,e=a.event,f=["fc-event",a.isStart?"fc-start":"fc-not-start",a.isEnd?"fc-end":"fc-not-end"].concat(e.className,e.source?e.source.className:[]);return b&&f.push("fc-draggable"),c&&f.push("fc-resizable"),d.isEventSelected(e)&&f.push("fc-selected"),f},getSegSkinCss:function(a){var b=a.event,c=this.view,d=b.source||{},e=b.color,f=d.color,g=c.opt("eventColor");return{"background-color":b.backgroundColor||e||d.backgroundColor||f||c.opt("eventBackgroundColor")||g,"border-color":b.borderColor||e||d.borderColor||f||c.opt("eventBorderColor")||g,color:b.textColor||d.textColor||c.opt("eventTextColor")}},eventToSegs:function(a){return this.eventsToSegs([a])},eventToSpan:function(a){return this.eventToSpans(a)[0]},eventToSpans:function(a){var b=this.eventToRange(a);return this.eventRangeToSpans(b,a)},eventsToSegs:function(b,c){var d=this,e=Fa(b),f=[];return a.each(e,function(a,b){var e,g=[];for(e=0;e<b.length;e++)g.push(d.eventToRange(b[e]));if(Da(b[0]))for(g=d.invertRanges(g),e=0;e<g.length;e++)f.push.apply(f,d.eventRangeToSegs(g[e],b[0],c));else for(e=0;e<g.length;e++)f.push.apply(f,d.eventRangeToSegs(g[e],b[e],c))}),f},eventToRange:function(a){return{start:a.start.clone().stripZone(),end:(a.end?a.end.clone():this.view.calendar.getDefaultEventEnd(null!=a.allDay?a.allDay:!a.start.hasTime(),a.start)).stripZone()}},eventRangeToSegs:function(a,b,c){var d,e=this.eventRangeToSpans(a,b),f=[];for(d=0;d<e.length;d++)f.push.apply(f,this.eventSpanToSegs(e[d],b,c));return f},eventRangeToSpans:function(b,c){return[a.extend({},b)]},eventSpanToSegs:function(a,b,c){var d,e,f=c?c(a):this.spanToSegs(a);for(d=0;d<f.length;d++)e=f[d],e.event=b,e.eventStartMS=+a.start,e.eventDurationMS=a.end-a.start;return f},invertRanges:function(a){var b,c,d=this.view,e=d.start.clone(),f=d.end.clone(),g=[],h=e;for(a.sort(Ga),b=0;b<a.length;b++)c=a[b],c.start>h&&g.push({start:h,end:c.start}),h=c.end;return f>h&&g.push({start:h,end:f}),g},sortEventSegs:function(a){a.sort(ia(this,"compareEventSegs"))},compareEventSegs:function(a,b){return a.eventStartMS-b.eventStartMS||b.eventDurationMS-a.eventDurationMS||b.event.allDay-a.event.allDay||H(a.event,b.event,this.view.eventOrderSpecs)}}),Va.isBgEvent=Ca,Va.dataAttrPrefix="";var tb=Va.DayTableMixin={breakOnWeeks:!1,dayDates:null,dayIndices:null,daysPerRow:null,rowCnt:null,colCnt:null,colHeadFormat:null,updateDayTable:function(){for(var a,b,c,d=this.view,e=this.start.clone(),f=-1,g=[],h=[];e.isBefore(this.end);)d.isHiddenDay(e)?g.push(f+.5):(f++,g.push(f),h.push(e.clone())),e.add(1,"days");if(this.breakOnWeeks){for(b=h[0].day(),a=1;a<h.length&&h[a].day()!=b;a++);c=Math.ceil(h.length/a)}else c=1,a=h.length;this.dayDates=h,this.dayIndices=g,this.daysPerRow=a,this.rowCnt=c,this.updateDayTableCols()},updateDayTableCols:function(){this.colCnt=this.computeColCnt(),this.colHeadFormat=this.view.opt("columnFormat")||this.computeColHeadFormat()},computeColCnt:function(){return this.daysPerRow},getCellDate:function(a,b){return this.dayDates[this.getCellDayIndex(a,b)].clone()},getCellRange:function(a,b){var c=this.getCellDate(a,b),d=c.clone().add(1,"days");return{start:c,end:d}},getCellDayIndex:function(a,b){return a*this.daysPerRow+this.getColDayIndex(b)},getColDayIndex:function(a){return this.isRTL?this.colCnt-1-a:a},getDateDayIndex:function(a){var b=this.dayIndices,c=a.diff(this.start,"days");return 0>c?b[0]-1:c>=b.length?b[b.length-1]+1:b[c]},computeColHeadFormat:function(){return this.rowCnt>1||this.colCnt>10?"ddd":this.colCnt>1?this.view.opt("dayOfMonthFormat"):"dddd"},sliceRangeByRow:function(a){var b,c,d,e,f,g=this.daysPerRow,h=this.view.computeDayRange(a),i=this.getDateDayIndex(h.start),j=this.getDateDayIndex(h.end.clone().subtract(1,"days")),k=[];for(b=0;b<this.rowCnt;b++)c=b*g,d=c+g-1,e=Math.max(i,c),f=Math.min(j,d),e=Math.ceil(e),f=Math.floor(f),f>=e&&k.push({row:b,firstRowDayIndex:e-c,lastRowDayIndex:f-c,isStart:e===i,isEnd:f===j});return k},sliceRangeByDay:function(a){var b,c,d,e,f,g,h=this.daysPerRow,i=this.view.computeDayRange(a),j=this.getDateDayIndex(i.start),k=this.getDateDayIndex(i.end.clone().subtract(1,"days")),l=[];for(b=0;b<this.rowCnt;b++)for(c=b*h,d=c+h-1,e=c;d>=e;e++)f=Math.max(j,e),g=Math.min(k,e),f=Math.ceil(f),g=Math.floor(g),g>=f&&l.push({row:b,firstRowDayIndex:f-c,lastRowDayIndex:g-c,isStart:f===j,isEnd:g===k});return l},renderHeadHtml:function(){var a=this.view;return'<div class="fc-row '+a.widgetHeaderClass+'"><table><thead>'+this.renderHeadTrHtml()+"</thead></table></div>"},renderHeadIntroHtml:function(){return this.renderIntroHtml()},renderHeadTrHtml:function(){return"<tr>"+(this.isRTL?"":this.renderHeadIntroHtml())+this.renderHeadDateCellsHtml()+(this.isRTL?this.renderHeadIntroHtml():"")+"</tr>"},renderHeadDateCellsHtml:function(){var a,b,c=[];for(a=0;a<this.colCnt;a++)b=this.getCellDate(0,a),c.push(this.renderHeadDateCellHtml(b));return c.join("")},renderHeadDateCellHtml:function(a,b,c){var d=this.view;return'<th class="fc-day-header '+d.widgetHeaderClass+" fc-"+Za[a.day()]+'"'+(1==this.rowCnt?' data-date="'+a.format("YYYY-MM-DD")+'"':"")+(b>1?' colspan="'+b+'"':"")+(c?" "+c:"")+">"+ca(a.format(this.colHeadFormat))+"</th>";
+},renderBgTrHtml:function(a){return"<tr>"+(this.isRTL?"":this.renderBgIntroHtml(a))+this.renderBgCellsHtml(a)+(this.isRTL?this.renderBgIntroHtml(a):"")+"</tr>"},renderBgIntroHtml:function(a){return this.renderIntroHtml()},renderBgCellsHtml:function(a){var b,c,d=[];for(b=0;b<this.colCnt;b++)c=this.getCellDate(a,b),d.push(this.renderBgCellHtml(c));return d.join("")},renderBgCellHtml:function(a,b){var c=this.view,d=this.getDayClasses(a);return d.unshift("fc-day",c.widgetContentClass),'<td class="'+d.join(" ")+'" data-date="'+a.format("YYYY-MM-DD")+'"'+(b?" "+b:"")+"></td>"},renderIntroHtml:function(){},bookendCells:function(a){var b=this.renderIntroHtml();b&&(this.isRTL?a.append(b):a.prepend(b))}},ub=Va.DayGrid=sb.extend(tb,{numbersVisible:!1,bottomCoordPadding:0,rowEls:null,cellEls:null,helperEls:null,rowCoordCache:null,colCoordCache:null,renderDates:function(a){var b,c,d=this.view,e=this.rowCnt,f=this.colCnt,g="";for(b=0;e>b;b++)g+=this.renderDayRowHtml(b,a);for(this.el.html(g),this.rowEls=this.el.find(".fc-row"),this.cellEls=this.el.find(".fc-day"),this.rowCoordCache=new ob({els:this.rowEls,isVertical:!0}),this.colCoordCache=new ob({els:this.cellEls.slice(0,this.colCnt),isHorizontal:!0}),b=0;e>b;b++)for(c=0;f>c;c++)d.trigger("dayRender",null,this.getCellDate(b,c),this.getCellEl(b,c))},unrenderDates:function(){this.removeSegPopover()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(!0),b=this.eventsToSegs(a);this.renderFill("businessHours",b,"bgevent")},renderDayRowHtml:function(a,b){var c=this.view,d=["fc-row","fc-week",c.widgetContentClass];return b&&d.push("fc-rigid"),'<div class="'+d.join(" ")+'"><div class="fc-bg"><table>'+this.renderBgTrHtml(a)+'</table></div><div class="fc-content-skeleton"><table>'+(this.numbersVisible?"<thead>"+this.renderNumberTrHtml(a)+"</thead>":"")+"</table></div></div>"},renderNumberTrHtml:function(a){return"<tr>"+(this.isRTL?"":this.renderNumberIntroHtml(a))+this.renderNumberCellsHtml(a)+(this.isRTL?this.renderNumberIntroHtml(a):"")+"</tr>"},renderNumberIntroHtml:function(a){return this.renderIntroHtml()},renderNumberCellsHtml:function(a){var b,c,d=[];for(b=0;b<this.colCnt;b++)c=this.getCellDate(a,b),d.push(this.renderNumberCellHtml(c));return d.join("")},renderNumberCellHtml:function(a){var b;return this.view.dayNumbersVisible?(b=this.getDayClasses(a),b.unshift("fc-day-number"),'<td class="'+b.join(" ")+'" data-date="'+a.format()+'">'+a.date()+"</td>"):"<td/>"},computeEventTimeFormat:function(){return this.view.opt("extraSmallTimeFormat")},computeDisplayEventEnd:function(){return 1==this.colCnt},rangeUpdated:function(){this.updateDayTable()},spanToSegs:function(a){var b,c,d=this.sliceRangeByRow(a);for(b=0;b<d.length;b++)c=d[b],this.isRTL?(c.leftCol=this.daysPerRow-1-c.lastRowDayIndex,c.rightCol=this.daysPerRow-1-c.firstRowDayIndex):(c.leftCol=c.firstRowDayIndex,c.rightCol=c.lastRowDayIndex);return d},prepareHits:function(){this.colCoordCache.build(),this.rowCoordCache.build(),this.rowCoordCache.bottoms[this.rowCnt-1]+=this.bottomCoordPadding},releaseHits:function(){this.colCoordCache.clear(),this.rowCoordCache.clear()},queryHit:function(a,b){var c=this.colCoordCache.getHorizontalIndex(a),d=this.rowCoordCache.getVerticalIndex(b);return null!=d&&null!=c?this.getCellHit(d,c):void 0},getHitSpan:function(a){return this.getCellRange(a.row,a.col)},getHitEl:function(a){return this.getCellEl(a.row,a.col)},getCellHit:function(a,b){return{row:a,col:b,component:this,left:this.colCoordCache.getLeftOffset(b),right:this.colCoordCache.getRightOffset(b),top:this.rowCoordCache.getTopOffset(a),bottom:this.rowCoordCache.getBottomOffset(a)}},getCellEl:function(a,b){return this.cellEls.eq(a*this.colCnt+b)},renderDrag:function(a,b){return this.renderHighlight(this.eventToSpan(a)),b&&!b.el.closest(this.el).length?this.renderEventLocationHelper(a,b):void 0},unrenderDrag:function(){this.unrenderHighlight(),this.unrenderHelper()},renderEventResize:function(a,b){return this.renderHighlight(this.eventToSpan(a)),this.renderEventLocationHelper(a,b)},unrenderEventResize:function(){this.unrenderHighlight(),this.unrenderHelper()},renderHelper:function(b,c){var d,e=[],f=this.eventToSegs(b);return f=this.renderFgSegEls(f),d=this.renderSegRows(f),this.rowEls.each(function(b,f){var g,h=a(f),i=a('<div class="fc-helper-skeleton"><table/></div>');g=c&&c.row===b?c.el.position().top:h.find(".fc-content-skeleton tbody").position().top,i.css("top",g).find("table").append(d[b].tbodyEl),h.append(i),e.push(i[0])}),this.helperEls=a(e)},unrenderHelper:function(){this.helperEls&&(this.helperEls.remove(),this.helperEls=null)},fillSegTag:"td",renderFill:function(b,c,d){var e,f,g,h=[];for(c=this.renderFillSegEls(b,c),e=0;e<c.length;e++)f=c[e],g=this.renderFillRow(b,f,d),this.rowEls.eq(f.row).append(g),h.push(g[0]);return this.elsByFill[b]=a(h),c},renderFillRow:function(b,c,d){var e,f,g=this.colCnt,h=c.leftCol,i=c.rightCol+1;return d=d||b.toLowerCase(),e=a('<div class="fc-'+d+'-skeleton"><table><tr/></table></div>'),f=e.find("tr"),h>0&&f.append('<td colspan="'+h+'"/>'),f.append(c.el.attr("colspan",i-h)),g>i&&f.append('<td colspan="'+(g-i)+'"/>'),this.bookendCells(f),e}});ub.mixin({rowStructs:null,unrenderEvents:function(){this.removeSegPopover(),sb.prototype.unrenderEvents.apply(this,arguments)},getEventSegs:function(){return sb.prototype.getEventSegs.call(this).concat(this.popoverSegs||[])},renderBgSegs:function(b){var c=a.grep(b,function(a){return a.event.allDay});return sb.prototype.renderBgSegs.call(this,c)},renderFgSegs:function(b){var c;return b=this.renderFgSegEls(b),c=this.rowStructs=this.renderSegRows(b),this.rowEls.each(function(b,d){a(d).find(".fc-content-skeleton > table").append(c[b].tbodyEl)}),b},unrenderFgSegs:function(){for(var a,b=this.rowStructs||[];a=b.pop();)a.tbodyEl.remove();this.rowStructs=null},renderSegRows:function(a){var b,c,d=[];for(b=this.groupSegRows(a),c=0;c<b.length;c++)d.push(this.renderSegRow(c,b[c]));return d},fgSegHtml:function(a,b){var c,d,e=this.view,f=a.event,g=e.isEventDraggable(f),h=!b&&f.allDay&&a.isStart&&e.isEventResizableFromStart(f),i=!b&&f.allDay&&a.isEnd&&e.isEventResizableFromEnd(f),j=this.getSegClasses(a,g,h||i),k=ea(this.getSegSkinCss(a)),l="";return j.unshift("fc-day-grid-event","fc-h-event"),a.isStart&&(c=this.getEventTimeText(f),c&&(l='<span class="fc-time">'+ca(c)+"</span>")),d='<span class="fc-title">'+(ca(f.title||"")||"&nbsp;")+"</span>",'<a class="'+j.join(" ")+'"'+(f.url?' href="'+ca(f.url)+'"':"")+(k?' style="'+k+'"':"")+'><div class="fc-content">'+(this.isRTL?d+" "+l:l+" "+d)+"</div>"+(h?'<div class="fc-resizer fc-start-resizer" />':"")+(i?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},renderSegRow:function(b,c){function d(b){for(;b>g;)k=(r[e-1]||[])[g],k?k.attr("rowspan",parseInt(k.attr("rowspan")||1,10)+1):(k=a("<td/>"),h.append(k)),q[e][g]=k,r[e][g]=k,g++}var e,f,g,h,i,j,k,l=this.colCnt,m=this.buildSegLevels(c),n=Math.max(1,m.length),o=a("<tbody/>"),p=[],q=[],r=[];for(e=0;n>e;e++){if(f=m[e],g=0,h=a("<tr/>"),p.push([]),q.push([]),r.push([]),f)for(i=0;i<f.length;i++){for(j=f[i],d(j.leftCol),k=a('<td class="fc-event-container"/>').append(j.el),j.leftCol!=j.rightCol?k.attr("colspan",j.rightCol-j.leftCol+1):r[e][g]=k;g<=j.rightCol;)q[e][g]=k,p[e][g]=j,g++;h.append(k)}d(l),this.bookendCells(h),o.append(h)}return{row:b,tbodyEl:o,cellMatrix:q,segMatrix:p,segLevels:m,segs:c}},buildSegLevels:function(a){var b,c,d,e=[];for(this.sortEventSegs(a),b=0;b<a.length;b++){for(c=a[b],d=0;d<e.length&&Ia(c,e[d]);d++);c.level=d,(e[d]||(e[d]=[])).push(c)}for(d=0;d<e.length;d++)e[d].sort(Ja);return e},groupSegRows:function(a){var b,c=[];for(b=0;b<this.rowCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].row].push(a[b]);return c}}),ub.mixin({segPopover:null,popoverSegs:null,removeSegPopover:function(){this.segPopover&&this.segPopover.hide()},limitRows:function(a){var b,c,d=this.rowStructs||[];for(b=0;b<d.length;b++)this.unlimitRow(b),c=a?"number"==typeof a?a:this.computeRowLevelLimit(b):!1,c!==!1&&this.limitRow(b,c)},computeRowLevelLimit:function(b){function c(b,c){f=Math.max(f,a(c).outerHeight())}var d,e,f,g=this.rowEls.eq(b),h=g.height(),i=this.rowStructs[b].tbodyEl.children();for(d=0;d<i.length;d++)if(e=i.eq(d).removeClass("fc-limited"),f=0,e.find("> td > :first-child").each(c),e.position().top+f>h)return d;return!1},limitRow:function(b,c){function d(d){for(;d>w;)j=t.getCellSegs(b,w,c),j.length&&(m=f[c-1][w],s=t.renderMoreLink(b,w,j),r=a("<div/>").append(s),m.append(r),v.push(r[0])),w++}var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t=this,u=this.rowStructs[b],v=[],w=0;if(c&&c<u.segLevels.length){for(e=u.segLevels[c-1],f=u.cellMatrix,g=u.tbodyEl.children().slice(c).addClass("fc-limited").get(),h=0;h<e.length;h++){for(i=e[h],d(i.leftCol),l=[],k=0;w<=i.rightCol;)j=this.getCellSegs(b,w,c),l.push(j),k+=j.length,w++;if(k){for(m=f[c-1][i.leftCol],n=m.attr("rowspan")||1,o=[],p=0;p<l.length;p++)q=a('<td class="fc-more-cell"/>').attr("rowspan",n),j=l[p],s=this.renderMoreLink(b,i.leftCol+p,[i].concat(j)),r=a("<div/>").append(s),q.append(r),o.push(q[0]),v.push(q[0]);m.addClass("fc-limited").after(a(o)),g.push(m[0])}}d(this.colCnt),u.moreEls=a(v),u.limitedEls=a(g)}},unlimitRow:function(a){var b=this.rowStructs[a];b.moreEls&&(b.moreEls.remove(),b.moreEls=null),b.limitedEls&&(b.limitedEls.removeClass("fc-limited"),b.limitedEls=null)},renderMoreLink:function(b,c,d){var e=this,f=this.view;return a('<a class="fc-more"/>').text(this.getMoreLinkText(d.length)).on("click",function(g){var h=f.opt("eventLimitClick"),i=e.getCellDate(b,c),j=a(this),k=e.getCellEl(b,c),l=e.getCellSegs(b,c),m=e.resliceDaySegs(l,i),n=e.resliceDaySegs(d,i);"function"==typeof h&&(h=f.trigger("eventLimitClick",null,{date:i,dayEl:k,moreEl:j,segs:m,hiddenSegs:n},g)),"popover"===h?e.showSegPopover(b,c,j,m):"string"==typeof h&&f.calendar.zoomTo(i,h)})},showSegPopover:function(a,b,c,d){var e,f,g=this,h=this.view,i=c.parent();e=1==this.rowCnt?h.el:this.rowEls.eq(a),f={className:"fc-more-popover",content:this.renderSegPopoverContent(a,b,d),parentEl:this.el,top:e.offset().top,autoHide:!0,viewportConstrain:h.opt("popoverViewportConstrain"),hide:function(){g.segPopover.removeElement(),g.segPopover=null,g.popoverSegs=null}},this.isRTL?f.right=i.offset().left+i.outerWidth()+1:f.left=i.offset().left-1,this.segPopover=new nb(f),this.segPopover.show()},renderSegPopoverContent:function(b,c,d){var e,f=this.view,g=f.opt("theme"),h=this.getCellDate(b,c).format(f.opt("dayPopoverFormat")),i=a('<div class="fc-header '+f.widgetHeaderClass+'"><span class="fc-close '+(g?"ui-icon ui-icon-closethick":"fc-icon fc-icon-x")+'"></span><span class="fc-title">'+ca(h)+'</span><div class="fc-clear"/></div><div class="fc-body '+f.widgetContentClass+'"><div class="fc-event-container"></div></div>'),j=i.find(".fc-event-container");for(d=this.renderFgSegEls(d,!0),this.popoverSegs=d,e=0;e<d.length;e++)this.prepareHits(),d[e].hit=this.getCellHit(b,c),this.releaseHits(),j.append(d[e].el);return i},resliceDaySegs:function(b,c){var d=a.map(b,function(a){return a.event}),e=c.clone(),f=e.clone().add(1,"days"),g={start:e,end:f};return b=this.eventsToSegs(d,function(a){var b=K(a,g);return b?[b]:[]}),this.sortEventSegs(b),b},getMoreLinkText:function(a){var b=this.view.opt("eventLimitText");return"function"==typeof b?b(a):"+"+a+" "+b},getCellSegs:function(a,b,c){for(var d,e=this.rowStructs[a].segMatrix,f=c||0,g=[];f<e.length;)d=e[f][b],d&&g.push(d),f++;return g}});var vb=Va.TimeGrid=sb.extend(tb,{slotDuration:null,snapDuration:null,snapsPerSlot:null,minTime:null,maxTime:null,labelFormat:null,labelInterval:null,colEls:null,slatContainerEl:null,slatEls:null,nowIndicatorEls:null,colCoordCache:null,slatCoordCache:null,constructor:function(){sb.apply(this,arguments),this.processOptions()},renderDates:function(){this.el.html(this.renderHtml()),this.colEls=this.el.find(".fc-day"),this.slatContainerEl=this.el.find(".fc-slats"),this.slatEls=this.slatContainerEl.find("tr"),this.colCoordCache=new ob({els:this.colEls,isHorizontal:!0}),this.slatCoordCache=new ob({els:this.slatEls,isVertical:!0}),this.renderContentSkeleton()},renderHtml:function(){return'<div class="fc-bg"><table>'+this.renderBgTrHtml(0)+'</table></div><div class="fc-slats"><table>'+this.renderSlatRowHtml()+"</table></div>"},renderSlatRowHtml:function(){for(var a,c,d,e=this.view,f=this.isRTL,g="",h=b.duration(+this.minTime);h<this.maxTime;)a=this.start.clone().time(h),c=ha(R(h,this.labelInterval)),d='<td class="fc-axis fc-time '+e.widgetContentClass+'" '+e.axisStyleAttr()+">"+(c?"<span>"+ca(a.format(this.labelFormat))+"</span>":"")+"</td>",g+='<tr data-time="'+a.format("HH:mm:ss")+'"'+(c?"":' class="fc-minor"')+">"+(f?"":d)+'<td class="'+e.widgetContentClass+'"/>'+(f?d:"")+"</tr>",h.add(this.slotDuration);return g},processOptions:function(){var c,d=this.view,e=d.opt("slotDuration"),f=d.opt("snapDuration");e=b.duration(e),f=f?b.duration(f):e,this.slotDuration=e,this.snapDuration=f,this.snapsPerSlot=e/f,this.minResizeDuration=f,this.minTime=b.duration(d.opt("minTime")),this.maxTime=b.duration(d.opt("maxTime")),c=d.opt("slotLabelFormat"),a.isArray(c)&&(c=c[c.length-1]),this.labelFormat=c||d.opt("axisFormat")||d.opt("smallTimeFormat"),c=d.opt("slotLabelInterval"),this.labelInterval=c?b.duration(c):this.computeLabelInterval(e)},computeLabelInterval:function(a){var c,d,e;for(c=Mb.length-1;c>=0;c--)if(d=b.duration(Mb[c]),e=R(d,a),ha(e)&&e>1)return d;return b.duration(a)},computeEventTimeFormat:function(){return this.view.opt("noMeridiemTimeFormat")},computeDisplayEventEnd:function(){return!0},prepareHits:function(){this.colCoordCache.build(),this.slatCoordCache.build()},releaseHits:function(){this.colCoordCache.clear()},queryHit:function(a,b){var c=this.snapsPerSlot,d=this.colCoordCache,e=this.slatCoordCache,f=d.getHorizontalIndex(a),g=e.getVerticalIndex(b);if(null!=f&&null!=g){var h=e.getTopOffset(g),i=e.getHeight(g),j=(b-h)/i,k=Math.floor(j*c),l=g*c+k,m=h+k/c*i,n=h+(k+1)/c*i;return{col:f,snap:l,component:this,left:d.getLeftOffset(f),right:d.getRightOffset(f),top:m,bottom:n}}},getHitSpan:function(a){var b,c=this.getCellDate(0,a.col),d=this.computeSnapTime(a.snap);return c.time(d),b=c.clone().add(this.snapDuration),{start:c,end:b}},getHitEl:function(a){return this.colEls.eq(a.col)},rangeUpdated:function(){this.updateDayTable()},computeSnapTime:function(a){return b.duration(this.minTime+this.snapDuration*a)},spanToSegs:function(a){var b,c=this.sliceRangeByTimes(a);for(b=0;b<c.length;b++)this.isRTL?c[b].col=this.daysPerRow-1-c[b].dayIndex:c[b].col=c[b].dayIndex;return c},sliceRangeByTimes:function(a){var b,c,d,e,f=[];for(c=0;c<this.daysPerRow;c++)d=this.dayDates[c].clone(),e={start:d.clone().time(this.minTime),end:d.clone().time(this.maxTime)},b=K(a,e),b&&(b.dayIndex=c,f.push(b));return f},updateSize:function(a){this.slatCoordCache.build(),a&&this.updateSegVerticals([].concat(this.fgSegs||[],this.bgSegs||[],this.businessSegs||[]))},getTotalSlatHeight:function(){return this.slatContainerEl.outerHeight()},computeDateTop:function(a,c){return this.computeTimeTop(b.duration(a-c.clone().stripTime()))},computeTimeTop:function(a){var b,c,d=this.slatEls.length,e=(a-this.minTime)/this.slotDuration;return e=Math.max(0,e),e=Math.min(d,e),b=Math.floor(e),b=Math.min(b,d-1),c=e-b,this.slatCoordCache.getTopPosition(b)+this.slatCoordCache.getHeight(b)*c},renderDrag:function(a,b){return b?this.renderEventLocationHelper(a,b):void this.renderHighlight(this.eventToSpan(a))},unrenderDrag:function(){this.unrenderHelper(),this.unrenderHighlight()},renderEventResize:function(a,b){return this.renderEventLocationHelper(a,b)},unrenderEventResize:function(){this.unrenderHelper()},renderHelper:function(a,b){return this.renderHelperSegs(this.eventToSegs(a),b)},unrenderHelper:function(){this.unrenderHelperSegs()},renderBusinessHours:function(){var a=this.view.calendar.getBusinessHoursEvents(),b=this.eventsToSegs(a);this.renderBusinessSegs(b)},unrenderBusinessHours:function(){this.unrenderBusinessSegs()},getNowIndicatorUnit:function(){return"minute"},renderNowIndicator:function(b){var c,d=this.spanToSegs({start:b,end:b}),e=this.computeDateTop(b,b),f=[];for(c=0;c<d.length;c++)f.push(a('<div class="fc-now-indicator fc-now-indicator-line"></div>').css("top",e).appendTo(this.colContainerEls.eq(d[c].col))[0]);d.length>0&&f.push(a('<div class="fc-now-indicator fc-now-indicator-arrow"></div>').css("top",e).appendTo(this.el.find(".fc-content-skeleton"))[0]),this.nowIndicatorEls=a(f)},unrenderNowIndicator:function(){this.nowIndicatorEls&&(this.nowIndicatorEls.remove(),this.nowIndicatorEls=null)},renderSelection:function(a){this.view.opt("selectHelper")?this.renderEventLocationHelper(a):this.renderHighlight(a)},unrenderSelection:function(){this.unrenderHelper(),this.unrenderHighlight()},renderHighlight:function(a){this.renderHighlightSegs(this.spanToSegs(a))},unrenderHighlight:function(){this.unrenderHighlightSegs()}});vb.mixin({colContainerEls:null,fgContainerEls:null,bgContainerEls:null,helperContainerEls:null,highlightContainerEls:null,businessContainerEls:null,fgSegs:null,bgSegs:null,helperSegs:null,highlightSegs:null,businessSegs:null,renderContentSkeleton:function(){var b,c,d="";for(b=0;b<this.colCnt;b++)d+='<td><div class="fc-content-col"><div class="fc-event-container fc-helper-container"></div><div class="fc-event-container"></div><div class="fc-highlight-container"></div><div class="fc-bgevent-container"></div><div class="fc-business-container"></div></div></td>';c=a('<div class="fc-content-skeleton"><table><tr>'+d+"</tr></table></div>"),this.colContainerEls=c.find(".fc-content-col"),this.helperContainerEls=c.find(".fc-helper-container"),this.fgContainerEls=c.find(".fc-event-container:not(.fc-helper-container)"),this.bgContainerEls=c.find(".fc-bgevent-container"),this.highlightContainerEls=c.find(".fc-highlight-container"),this.businessContainerEls=c.find(".fc-business-container"),this.bookendCells(c.find("tr")),this.el.append(c)},renderFgSegs:function(a){return a=this.renderFgSegsIntoContainers(a,this.fgContainerEls),this.fgSegs=a,a},unrenderFgSegs:function(){this.unrenderNamedSegs("fgSegs")},renderHelperSegs:function(b,c){var d,e,f,g=[];for(b=this.renderFgSegsIntoContainers(b,this.helperContainerEls),d=0;d<b.length;d++)e=b[d],c&&c.col===e.col&&(f=c.el,e.el.css({left:f.css("left"),right:f.css("right"),"margin-left":f.css("margin-left"),"margin-right":f.css("margin-right")})),g.push(e.el[0]);return this.helperSegs=b,a(g)},unrenderHelperSegs:function(){this.unrenderNamedSegs("helperSegs")},renderBgSegs:function(a){return a=this.renderFillSegEls("bgEvent",a),this.updateSegVerticals(a),this.attachSegsByCol(this.groupSegsByCol(a),this.bgContainerEls),this.bgSegs=a,a},unrenderBgSegs:function(){this.unrenderNamedSegs("bgSegs")},renderHighlightSegs:function(a){a=this.renderFillSegEls("highlight",a),this.updateSegVerticals(a),this.attachSegsByCol(this.groupSegsByCol(a),this.highlightContainerEls),this.highlightSegs=a},unrenderHighlightSegs:function(){this.unrenderNamedSegs("highlightSegs")},renderBusinessSegs:function(a){a=this.renderFillSegEls("businessHours",a),this.updateSegVerticals(a),this.attachSegsByCol(this.groupSegsByCol(a),this.businessContainerEls),this.businessSegs=a},unrenderBusinessSegs:function(){this.unrenderNamedSegs("businessSegs")},groupSegsByCol:function(a){var b,c=[];for(b=0;b<this.colCnt;b++)c.push([]);for(b=0;b<a.length;b++)c[a[b].col].push(a[b]);return c},attachSegsByCol:function(a,b){var c,d,e;for(c=0;c<this.colCnt;c++)for(d=a[c],e=0;e<d.length;e++)b.eq(c).append(d[e].el)},unrenderNamedSegs:function(a){var b,c=this[a];if(c){for(b=0;b<c.length;b++)c[b].el.remove();this[a]=null}},renderFgSegsIntoContainers:function(a,b){var c,d;for(a=this.renderFgSegEls(a),c=this.groupSegsByCol(a),d=0;d<this.colCnt;d++)this.updateFgSegCoords(c[d]);return this.attachSegsByCol(c,b),a},fgSegHtml:function(a,b){var c,d,e,f=this.view,g=a.event,h=f.isEventDraggable(g),i=!b&&a.isStart&&f.isEventResizableFromStart(g),j=!b&&a.isEnd&&f.isEventResizableFromEnd(g),k=this.getSegClasses(a,h,i||j),l=ea(this.getSegSkinCss(a));return k.unshift("fc-time-grid-event","fc-v-event"),f.isMultiDayEvent(g)?(a.isStart||a.isEnd)&&(c=this.getEventTimeText(a),d=this.getEventTimeText(a,"LT"),e=this.getEventTimeText(a,null,!1)):(c=this.getEventTimeText(g),d=this.getEventTimeText(g,"LT"),e=this.getEventTimeText(g,null,!1)),'<a class="'+k.join(" ")+'"'+(g.url?' href="'+ca(g.url)+'"':"")+(l?' style="'+l+'"':"")+'><div class="fc-content">'+(c?'<div class="fc-time" data-start="'+ca(e)+'" data-full="'+ca(d)+'"><span>'+ca(c)+"</span></div>":"")+(g.title?'<div class="fc-title">'+ca(g.title)+"</div>":"")+'</div><div class="fc-bg"/>'+(j?'<div class="fc-resizer fc-end-resizer" />':"")+"</a>"},updateSegVerticals:function(a){this.computeSegVerticals(a),this.assignSegVerticals(a)},computeSegVerticals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.top=this.computeDateTop(c.start,c.start),c.bottom=this.computeDateTop(c.end,c.start)},assignSegVerticals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.el.css(this.generateSegVerticalCss(c))},generateSegVerticalCss:function(a){return{top:a.top,bottom:-a.bottom}},updateFgSegCoords:function(a){this.computeSegVerticals(a),this.computeFgSegHorizontals(a),this.assignSegVerticals(a),this.assignFgSegHorizontals(a)},computeFgSegHorizontals:function(a){var b,c,d;if(this.sortEventSegs(a),b=Ka(a),La(b),c=b[0]){for(d=0;d<c.length;d++)Ma(c[d]);for(d=0;d<c.length;d++)this.computeFgSegForwardBack(c[d],0,0)}},computeFgSegForwardBack:function(a,b,c){var d,e=a.forwardSegs;if(void 0===a.forwardCoord)for(e.length?(this.sortForwardSegs(e),this.computeFgSegForwardBack(e[0],b+1,c),a.forwardCoord=e[0].backwardCoord):a.forwardCoord=1,a.backwardCoord=a.forwardCoord-(a.forwardCoord-c)/(b+1),d=0;d<e.length;d++)this.computeFgSegForwardBack(e[d],0,a.forwardCoord)},sortForwardSegs:function(a){a.sort(ia(this,"compareForwardSegs"))},compareForwardSegs:function(a,b){return b.forwardPressure-a.forwardPressure||(a.backwardCoord||0)-(b.backwardCoord||0)||this.compareEventSegs(a,b)},assignFgSegHorizontals:function(a){var b,c;for(b=0;b<a.length;b++)c=a[b],c.el.css(this.generateFgSegHorizontalCss(c)),c.bottom-c.top<30&&c.el.addClass("fc-short")},generateFgSegHorizontalCss:function(a){var b,c,d=this.view.opt("slotEventOverlap"),e=a.backwardCoord,f=a.forwardCoord,g=this.generateSegVerticalCss(a);return d&&(f=Math.min(1,e+2*(f-e))),this.isRTL?(b=1-f,c=e):(b=e,c=1-f),g.zIndex=a.level+1,g.left=100*b+"%",g.right=100*c+"%",d&&a.forwardPressure&&(g[this.isRTL?"marginLeft":"marginRight"]=20),g}});var wb=Va.View=xa.extend(kb,lb,{type:null,name:null,title:null,calendar:null,options:null,el:null,displaying:null,isSkeletonRendered:!1,isEventsRendered:!1,start:null,end:null,intervalStart:null,intervalEnd:null,intervalDuration:null,intervalUnit:null,isRTL:!1,isSelected:!1,selectedEvent:null,eventOrderSpecs:null,widgetHeaderClass:null,widgetContentClass:null,highlightStateClass:null,nextDayThreshold:null,isHiddenDayHash:null,isNowIndicatorRendered:null,initialNowDate:null,initialNowQueriedMs:null,nowIndicatorTimeoutID:null,nowIndicatorIntervalID:null,constructor:function(a,c,d,e){this.calendar=a,this.type=this.name=c,this.options=d,this.intervalDuration=e||b.duration(1,"day"),this.nextDayThreshold=b.duration(this.opt("nextDayThreshold")),this.initThemingProps(),this.initHiddenDays(),this.isRTL=this.opt("isRTL"),this.eventOrderSpecs=G(this.opt("eventOrder")),this.initialize()},initialize:function(){},opt:function(a){return this.options[a]},trigger:function(a,b){var c=this.calendar;return c.trigger.apply(c,[a,b||this].concat(Array.prototype.slice.call(arguments,2),[this]))},setDate:function(a){this.setRange(this.computeRange(a))},setRange:function(b){a.extend(this,b),this.updateTitle()},computeRange:function(a){var b,c,d=O(this.intervalDuration),e=a.clone().startOf(d),f=e.clone().add(this.intervalDuration);return/year|month|week|day/.test(d)?(e.stripTime(),f.stripTime()):(e.hasTime()||(e=this.calendar.time(0)),f.hasTime()||(f=this.calendar.time(0))),b=e.clone(),b=this.skipHiddenDays(b),c=f.clone(),c=this.skipHiddenDays(c,-1,!0),{intervalUnit:d,intervalStart:e,intervalEnd:f,start:b,end:c}},computePrevDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).subtract(this.intervalDuration),-1)},computeNextDate:function(a){return this.massageCurrentDate(a.clone().startOf(this.intervalUnit).add(this.intervalDuration))},massageCurrentDate:function(a,b){return this.intervalDuration.as("days")<=1&&this.isHiddenDay(a)&&(a=this.skipHiddenDays(a,b),a.startOf("day")),a},updateTitle:function(){this.title=this.computeTitle()},computeTitle:function(){return this.formatRange({start:this.calendar.applyTimezone(this.intervalStart),end:this.calendar.applyTimezone(this.intervalEnd)},this.opt("titleFormat")||this.computeTitleFormat(),this.opt("titleRangeSeparator"))},computeTitleFormat:function(){return"year"==this.intervalUnit?"YYYY":"month"==this.intervalUnit?this.opt("monthYearFormat"):this.intervalDuration.as("days")>1?"ll":"LL"},formatRange:function(a,b,c){var d=a.end;return d.hasTime()||(d=d.clone().subtract(1)),sa(a.start,d,b,c,this.opt("isRTL"))},setElement:function(a){this.el=a,this.bindGlobalHandlers()},removeElement:function(){this.clear(),this.isSkeletonRendered&&(this.unrenderSkeleton(),this.isSkeletonRendered=!1),this.unbindGlobalHandlers(),this.el.remove()},display:function(b){var c=this,d=null;return this.displaying&&(d=this.queryScroll()),this.calendar.freezeContentHeight(),this.clear().then(function(){return c.displaying=a.when(c.displayView(b)).then(function(){c.forceScroll(c.computeInitialScroll(d)),c.calendar.unfreezeContentHeight(),c.triggerRender()})})},clear:function(){var b=this,c=this.displaying;return c?c.then(function(){return b.displaying=null,b.clearEvents(),b.clearView()}):a.when()},displayView:function(a){this.isSkeletonRendered||(this.renderSkeleton(),this.isSkeletonRendered=!0),a&&this.setDate(a),this.render&&this.render(),this.renderDates(),this.updateSize(),this.renderBusinessHours(),this.startNowIndicator()},clearView:function(){this.unselect(),this.stopNowIndicator(),this.triggerUnrender(),this.unrenderBusinessHours(),this.unrenderDates(),this.destroy&&this.destroy()},renderSkeleton:function(){},unrenderSkeleton:function(){},renderDates:function(){},unrenderDates:function(){},triggerRender:function(){this.trigger("viewRender",this,this,this.el)},triggerUnrender:function(){this.trigger("viewDestroy",this,this,this.el)},bindGlobalHandlers:function(){this.listenTo(a(document),"mousedown",this.handleDocumentMousedown),this.listenTo(a(document),"touchstart",this.processUnselect)},unbindGlobalHandlers:function(){this.stopListeningTo(a(document))},initThemingProps:function(){var a=this.opt("theme")?"ui":"fc";this.widgetHeaderClass=a+"-widget-header",this.widgetContentClass=a+"-widget-content",this.highlightStateClass=a+"-state-highlight"},renderBusinessHours:function(){},unrenderBusinessHours:function(){},startNowIndicator:function(){var a,c,d,e=this;this.opt("nowIndicator")&&(a=this.getNowIndicatorUnit(),a&&(c=ia(this,"updateNowIndicator"),this.initialNowDate=this.calendar.getNow(),this.initialNowQueriedMs=+new Date,this.renderNowIndicator(this.initialNowDate),this.isNowIndicatorRendered=!0,d=this.initialNowDate.clone().startOf(a).add(1,a)-this.initialNowDate,this.nowIndicatorTimeoutID=setTimeout(function(){e.nowIndicatorTimeoutID=null,c(),d=+b.duration(1,a),d=Math.max(100,d),e.nowIndicatorIntervalID=setInterval(c,d)},d)))},updateNowIndicator:function(){this.isNowIndicatorRendered&&(this.unrenderNowIndicator(),this.renderNowIndicator(this.initialNowDate.clone().add(new Date-this.initialNowQueriedMs)))},stopNowIndicator:function(){this.isNowIndicatorRendered&&(this.nowIndicatorTimeoutID&&(clearTimeout(this.nowIndicatorTimeoutID),this.nowIndicatorTimeoutID=null),this.nowIndicatorIntervalID&&(clearTimeout(this.nowIndicatorIntervalID),this.nowIndicatorIntervalID=null),this.unrenderNowIndicator(),this.isNowIndicatorRendered=!1)},getNowIndicatorUnit:function(){},renderNowIndicator:function(a){},unrenderNowIndicator:function(){},updateSize:function(a){var b;a&&(b=this.queryScroll()),this.updateHeight(a),this.updateWidth(a),this.updateNowIndicator(),a&&this.setScroll(b)},updateWidth:function(a){},updateHeight:function(a){var b=this.calendar;this.setHeight(b.getSuggestedViewHeight(),b.isHeightAuto())},setHeight:function(a,b){},computeInitialScroll:function(a){return 0},queryScroll:function(){},setScroll:function(a){},forceScroll:function(a){var b=this;this.setScroll(a),setTimeout(function(){b.setScroll(a)},0)},displayEvents:function(a){var b=this.queryScroll();this.clearEvents(),this.renderEvents(a),this.isEventsRendered=!0,this.setScroll(b),this.triggerEventRender()},clearEvents:function(){var a;this.isEventsRendered&&(a=this.queryScroll(),this.triggerEventUnrender(),this.destroyEvents&&this.destroyEvents(),this.unrenderEvents(),this.setScroll(a),this.isEventsRendered=!1)},renderEvents:function(a){},unrenderEvents:function(){},triggerEventRender:function(){this.renderedEventSegEach(function(a){this.trigger("eventAfterRender",a.event,a.event,a.el)}),this.trigger("eventAfterAllRender")},triggerEventUnrender:function(){this.renderedEventSegEach(function(a){this.trigger("eventDestroy",a.event,a.event,a.el)})},resolveEventEl:function(b,c){var d=this.trigger("eventRender",b,b,c);return d===!1?c=null:d&&d!==!0&&(c=a(d)),c},showEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","")},a)},hideEvent:function(a){this.renderedEventSegEach(function(a){a.el.css("visibility","hidden")},a)},renderedEventSegEach:function(a,b){var c,d=this.getEventSegs();for(c=0;c<d.length;c++)b&&d[c].event._id!==b._id||d[c].el&&a.call(this,d[c])},getEventSegs:function(){return[]},isEventDraggable:function(a){var b=a.source||{};return ba(a.startEditable,b.startEditable,this.opt("eventStartEditable"),a.editable,b.editable,this.opt("editable"))},reportEventDrop:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventDrop(a,g.dateDelta,h,d,e),f.reportEventChange()},triggerEventDrop:function(a,b,c,d,e){this.trigger("eventDrop",d[0],a,b,c,e,{})},reportExternalDrop:function(b,c,d,e,f){var g,h,i=b.eventProps;i&&(g=a.extend({},i,c),h=this.calendar.renderEvent(g,b.stick)[0]),this.triggerExternalDrop(h,c,d,e,f)},triggerExternalDrop:function(a,b,c,d,e){this.trigger("drop",c[0],b.start,d,e),a&&this.trigger("eventReceive",null,a)},renderDrag:function(a,b){},unrenderDrag:function(){},isEventResizableFromStart:function(a){return this.opt("eventResizableFromStart")&&this.isEventResizable(a)},isEventResizableFromEnd:function(a){return this.isEventResizable(a)},isEventResizable:function(a){var b=a.source||{};return ba(a.durationEditable,b.durationEditable,this.opt("eventDurationEditable"),a.editable,b.editable,this.opt("editable"))},reportEventResize:function(a,b,c,d,e){var f=this.calendar,g=f.mutateEvent(a,b,c),h=function(){g.undo(),f.reportEventChange()};this.triggerEventResize(a,g.durationDelta,h,d,e),f.reportEventChange()},triggerEventResize:function(a,b,c,d,e){this.trigger("eventResize",d[0],a,b,c,e,{})},select:function(a,b){this.unselect(b),this.renderSelection(a),this.reportSelection(a,b)},renderSelection:function(a){},reportSelection:function(a,b){this.isSelected=!0,this.triggerSelect(a,b)},triggerSelect:function(a,b){this.trigger("select",null,this.calendar.applyTimezone(a.start),this.calendar.applyTimezone(a.end),b)},unselect:function(a){this.isSelected&&(this.isSelected=!1,this.destroySelection&&this.destroySelection(),this.unrenderSelection(),this.trigger("unselect",null,a))},unrenderSelection:function(){},selectEvent:function(a){this.selectedEvent&&this.selectedEvent===a||(this.unselectEvent(),this.renderedEventSegEach(function(a){a.el.addClass("fc-selected")},a),this.selectedEvent=a)},unselectEvent:function(){this.selectedEvent&&(this.renderedEventSegEach(function(a){a.el.removeClass("fc-selected");
+},this.selectedEvent),this.selectedEvent=null)},isEventSelected:function(a){return this.selectedEvent&&this.selectedEvent._id===a._id},handleDocumentMousedown:function(a){u(a)&&this.processUnselect(a)},processUnselect:function(a){this.processRangeUnselect(a),this.processEventUnselect(a)},processRangeUnselect:function(b){var c;this.isSelected&&this.opt("unselectAuto")&&(c=this.opt("unselectCancel"),c&&a(b.target).closest(c).length||this.unselect(b))},processEventUnselect:function(b){this.selectedEvent&&(a(b.target).closest(".fc-selected").length||this.unselectEvent())},triggerDayClick:function(a,b,c){this.trigger("dayClick",b,this.calendar.applyTimezone(a.start),c)},initHiddenDays:function(){var b,c=this.opt("hiddenDays")||[],d=[],e=0;for(this.opt("weekends")===!1&&c.push(0,6),b=0;7>b;b++)(d[b]=-1!==a.inArray(b,c))||e++;if(!e)throw"invalid hiddenDays";this.isHiddenDayHash=d},isHiddenDay:function(a){return b.isMoment(a)&&(a=a.day()),this.isHiddenDayHash[a]},skipHiddenDays:function(a,b,c){var d=a.clone();for(b=b||1;this.isHiddenDayHash[(d.day()+(c?b:0)+7)%7];)d.add(b,"days");return d},computeDayRange:function(a){var b,c=a.start.clone().stripTime(),d=a.end,e=null;return d&&(e=d.clone().stripTime(),b=+d.time(),b&&b>=this.nextDayThreshold&&e.add(1,"days")),(!d||c>=e)&&(e=c.clone().add(1,"days")),{start:c,end:e}},isMultiDayEvent:function(a){var b=this.computeDayRange(a);return b.end.diff(b.start,"days")>1}}),xb=Va.Scroller=xa.extend({el:null,scrollEl:null,overflowX:null,overflowY:null,constructor:function(a){a=a||{},this.overflowX=a.overflowX||a.overflow||"auto",this.overflowY=a.overflowY||a.overflow||"auto"},render:function(){this.el=this.renderEl(),this.applyOverflow()},renderEl:function(){return this.scrollEl=a('<div class="fc-scroller"></div>')},clear:function(){this.setHeight("auto"),this.applyOverflow()},destroy:function(){this.el.remove()},applyOverflow:function(){this.scrollEl.css({"overflow-x":this.overflowX,"overflow-y":this.overflowY})},lockOverflow:function(a){var b=this.overflowX,c=this.overflowY;a=a||this.getScrollbarWidths(),"auto"===b&&(b=a.top||a.bottom||this.scrollEl[0].scrollWidth-1>this.scrollEl[0].clientWidth?"scroll":"hidden"),"auto"===c&&(c=a.left||a.right||this.scrollEl[0].scrollHeight-1>this.scrollEl[0].clientHeight?"scroll":"hidden"),this.scrollEl.css({"overflow-x":b,"overflow-y":c})},setHeight:function(a){this.scrollEl.height(a)},getScrollTop:function(){return this.scrollEl.scrollTop()},setScrollTop:function(a){this.scrollEl.scrollTop(a)},getClientWidth:function(){return this.scrollEl[0].clientWidth},getClientHeight:function(){return this.scrollEl[0].clientHeight},getScrollbarWidths:function(){return q(this.scrollEl)}}),yb=Va.Calendar=xa.extend({dirDefaults:null,langDefaults:null,overrides:null,options:null,viewSpecCache:null,view:null,header:null,loadingLevel:0,constructor:Pa,initialize:function(){},initOptions:function(a){var b,e,f,g;a=d(a),b=a.lang,e=zb[b],e||(b=yb.defaults.lang,e=zb[b]||{}),f=ba(a.isRTL,e.isRTL,yb.defaults.isRTL),g=f?yb.rtlDefaults:{},this.dirDefaults=g,this.langDefaults=e,this.overrides=a,this.options=c([yb.defaults,g,e,a]),Qa(this.options),this.viewSpecCache={}},getViewSpec:function(a){var b=this.viewSpecCache;return b[a]||(b[a]=this.buildViewSpec(a))},getUnitViewSpec:function(b){var c,d,e;if(-1!=a.inArray(b,$a))for(c=this.header.getViewsWithButtons(),a.each(Va.views,function(a){c.push(a)}),d=0;d<c.length;d++)if(e=this.getViewSpec(c[d]),e&&e.singleUnit==b)return e},buildViewSpec:function(a){for(var d,e,f,g,h=this.overrides.views||{},i=[],j=[],k=[],l=a;l;)d=Wa[l],e=h[l],l=null,"function"==typeof d&&(d={"class":d}),d&&(i.unshift(d),j.unshift(d.defaults||{}),f=f||d.duration,l=l||d.type),e&&(k.unshift(e),f=f||e.duration,l=l||e.type);return d=W(i),d.type=a,d["class"]?(f&&(f=b.duration(f),f.valueOf()&&(d.duration=f,g=O(f),1===f.as(g)&&(d.singleUnit=g,k.unshift(h[g]||{})))),d.defaults=c(j),d.overrides=c(k),this.buildViewSpecOptions(d),this.buildViewSpecButtonText(d,a),d):!1},buildViewSpecOptions:function(a){a.options=c([yb.defaults,a.defaults,this.dirDefaults,this.langDefaults,this.overrides,a.overrides]),Qa(a.options)},buildViewSpecButtonText:function(a,b){function c(c){var d=c.buttonText||{};return d[b]||(a.singleUnit?d[a.singleUnit]:null)}a.buttonTextOverride=c(this.overrides)||a.overrides.buttonText,a.buttonTextDefault=c(this.langDefaults)||c(this.dirDefaults)||a.defaults.buttonText||c(yb.defaults)||(a.duration?this.humanizeDuration(a.duration):null)||b},instantiateView:function(a){var b=this.getViewSpec(a);return new b["class"](this,a,b.options,b.duration)},isValidViewType:function(a){return Boolean(this.getViewSpec(a))},pushLoading:function(){this.loadingLevel++||this.trigger("loading",null,!0,this.view)},popLoading:function(){--this.loadingLevel||this.trigger("loading",null,!1,this.view)},buildSelectSpan:function(a,b){var c,d=this.moment(a).stripZone();return c=b?this.moment(b).stripZone():d.hasTime()?d.clone().add(this.defaultTimedEventDuration):d.clone().add(this.defaultAllDayEventDuration),{start:d,end:c}}});yb.mixin(kb),yb.defaults={titleRangeSeparator:" â€” ",monthYearFormat:"MMMM YYYY",defaultTimedEventDuration:"02:00:00",defaultAllDayEventDuration:{days:1},forceEventDuration:!1,nextDayThreshold:"09:00:00",defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberTitle:"W",weekNumberCalculation:"local",scrollTime:"06:00:00",lazyFetching:!0,startParam:"start",endParam:"end",timezoneParam:"timezone",timezone:!1,isRTL:!1,buttonText:{prev:"prev",next:"next",prevYear:"prev year",nextYear:"next year",year:"year",today:"today",month:"month",week:"week",day:"day"},buttonIcons:{prev:"left-single-arrow",next:"right-single-arrow",prevYear:"left-double-arrow",nextYear:"right-double-arrow"},theme:!1,themeButtonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e",prevYear:"seek-prev",nextYear:"seek-next"},dragOpacity:.75,dragRevertDuration:500,dragScroll:!0,unselectAuto:!0,dropAccept:"*",eventOrder:"title",eventLimit:!1,eventLimitText:"more",eventLimitClick:"popover",dayPopoverFormat:"LL",handleWindowResize:!0,windowResizeDelay:200,longPressDelay:1e3},yb.englishDefaults={dayPopoverFormat:"dddd, MMMM D"},yb.rtlDefaults={header:{left:"next,prev today",center:"",right:"title"},buttonIcons:{prev:"right-single-arrow",next:"left-single-arrow",prevYear:"right-double-arrow",nextYear:"left-double-arrow"},themeButtonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w",nextYear:"seek-prev",prevYear:"seek-next"}};var zb=Va.langs={};Va.datepickerLang=function(b,c,d){var e=zb[b]||(zb[b]={});e.isRTL=d.isRTL,e.weekNumberTitle=d.weekHeader,a.each(Ab,function(a,b){e[a]=b(d)}),a.datepicker&&(a.datepicker.regional[c]=a.datepicker.regional[b]=d,a.datepicker.regional.en=a.datepicker.regional[""],a.datepicker.setDefaults(d))},Va.lang=function(b,d){var e,f;e=zb[b]||(zb[b]={}),d&&(e=zb[b]=c([e,d])),f=Ra(b),a.each(Bb,function(a,b){null==e[a]&&(e[a]=b(f,e))}),yb.defaults.lang=b};var Ab={buttonText:function(a){return{prev:da(a.prevText),next:da(a.nextText),today:da(a.currentText)}},monthYearFormat:function(a){return a.showMonthAfterYear?"YYYY["+a.yearSuffix+"] MMMM":"MMMM YYYY["+a.yearSuffix+"]"}},Bb={dayOfMonthFormat:function(a,b){var c=a.longDateFormat("l");return c=c.replace(/^Y+[^\w\s]*|[^\w\s]*Y+$/g,""),b.isRTL?c+=" ddd":c="ddd "+c,c},mediumTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"a")},smallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"a")},extraSmallTimeFormat:function(a){return a.longDateFormat("LT").replace(":mm","(:mm)").replace(/(\Wmm)$/,"($1)").replace(/\s*a$/i,"t")},hourFormat:function(a){return a.longDateFormat("LT").replace(":mm","").replace(/(\Wmm)$/,"").replace(/\s*a$/i,"a")},noMeridiemTimeFormat:function(a){return a.longDateFormat("LT").replace(/\s*a$/i,"")}},Cb={smallDayDateFormat:function(a){return a.isRTL?"D dd":"dd D"},weekFormat:function(a){return a.isRTL?"w[ "+a.weekNumberTitle+"]":"["+a.weekNumberTitle+" ]w"},smallWeekFormat:function(a){return a.isRTL?"w["+a.weekNumberTitle+"]":"["+a.weekNumberTitle+"]w"}};Va.lang("en",yb.englishDefaults),Va.sourceNormalizers=[],Va.sourceFetchers=[];var Db={dataType:"json",cache:!1},Eb=1;yb.prototype.getPeerEvents=function(a,b){var c,d,e=this.getEventCache(),f=[];for(c=0;c<e.length;c++)d=e[c],b&&b._id===d._id||f.push(d);return f};var Fb=Va.BasicView=wb.extend({scroller:null,dayGridClass:ub,dayGrid:null,dayNumbersVisible:!1,weekNumbersVisible:!1,weekNumberWidth:null,headContainerEl:null,headRowEl:null,initialize:function(){this.dayGrid=this.instantiateDayGrid(),this.scroller=new xb({overflowX:"hidden",overflowY:"auto"})},instantiateDayGrid:function(){var a=this.dayGridClass.extend(Gb);return new a(this)},setRange:function(a){wb.prototype.setRange.call(this,a),this.dayGrid.breakOnWeeks=/year|month|week/.test(this.intervalUnit),this.dayGrid.setRange(a)},computeRange:function(a){var b=wb.prototype.computeRange.call(this,a);return/year|month/.test(b.intervalUnit)&&(b.start.startOf("week"),b.start=this.skipHiddenDays(b.start),b.end.weekday()&&(b.end.add(1,"week").startOf("week"),b.end=this.skipHiddenDays(b.end,-1,!0))),b},renderDates:function(){this.dayNumbersVisible=this.dayGrid.rowCnt>1,this.weekNumbersVisible=this.opt("weekNumbers"),this.dayGrid.numbersVisible=this.dayNumbersVisible||this.weekNumbersVisible,this.el.addClass("fc-basic-view").html(this.renderSkeletonHtml()),this.renderHead(),this.scroller.render();var b=this.scroller.el.addClass("fc-day-grid-container"),c=a('<div class="fc-day-grid" />').appendTo(b);this.el.find(".fc-body > tr > td").append(b),this.dayGrid.setElement(c),this.dayGrid.renderDates(this.hasRigidRows())},renderHead:function(){this.headContainerEl=this.el.find(".fc-head-container").html(this.dayGrid.renderHeadHtml()),this.headRowEl=this.headContainerEl.find(".fc-row")},unrenderDates:function(){this.dayGrid.unrenderDates(),this.dayGrid.removeElement(),this.scroller.destroy()},renderBusinessHours:function(){this.dayGrid.renderBusinessHours()},renderSkeletonHtml:function(){return'<table><thead class="fc-head"><tr><td class="fc-head-container '+this.widgetHeaderClass+'"></td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'"></td></tr></tbody></table>'},weekNumberStyleAttr:function(){return null!==this.weekNumberWidth?'style="width:'+this.weekNumberWidth+'px"':""},hasRigidRows:function(){var a=this.opt("eventLimit");return a&&"number"!=typeof a},updateWidth:function(){this.weekNumbersVisible&&(this.weekNumberWidth=k(this.el.find(".fc-week-number")))},setHeight:function(a,b){var c,d,g=this.opt("eventLimit");this.scroller.clear(),f(this.headRowEl),this.dayGrid.removeSegPopover(),g&&"number"==typeof g&&this.dayGrid.limitRows(g),c=this.computeScrollerHeight(a),this.setGridHeight(c,b),g&&"number"!=typeof g&&this.dayGrid.limitRows(g),b||(this.scroller.setHeight(c),d=this.scroller.getScrollbarWidths(),(d.left||d.right)&&(e(this.headRowEl,d),c=this.computeScrollerHeight(a),this.scroller.setHeight(c)),this.scroller.lockOverflow(d))},computeScrollerHeight:function(a){return a-l(this.el,this.scroller.el)},setGridHeight:function(a,b){b?j(this.dayGrid.rowEls):i(this.dayGrid.rowEls,a,!0)},queryScroll:function(){return this.scroller.getScrollTop()},setScroll:function(a){this.scroller.setScrollTop(a)},prepareHits:function(){this.dayGrid.prepareHits()},releaseHits:function(){this.dayGrid.releaseHits()},queryHit:function(a,b){return this.dayGrid.queryHit(a,b)},getHitSpan:function(a){return this.dayGrid.getHitSpan(a)},getHitEl:function(a){return this.dayGrid.getHitEl(a)},renderEvents:function(a){this.dayGrid.renderEvents(a),this.updateHeight()},getEventSegs:function(){return this.dayGrid.getEventSegs()},unrenderEvents:function(){this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return this.dayGrid.renderDrag(a,b)},unrenderDrag:function(){this.dayGrid.unrenderDrag()},renderSelection:function(a){this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.dayGrid.unrenderSelection()}}),Gb={renderHeadIntroHtml:function(){var a=this.view;return a.weekNumbersVisible?'<th class="fc-week-number '+a.widgetHeaderClass+'" '+a.weekNumberStyleAttr()+"><span>"+ca(a.opt("weekNumberTitle"))+"</span></th>":""},renderNumberIntroHtml:function(a){var b=this.view;return b.weekNumbersVisible?'<td class="fc-week-number" '+b.weekNumberStyleAttr()+"><span>"+this.getCellDate(a,0).format("w")+"</span></td>":""},renderBgIntroHtml:function(){var a=this.view;return a.weekNumbersVisible?'<td class="fc-week-number '+a.widgetContentClass+'" '+a.weekNumberStyleAttr()+"></td>":""},renderIntroHtml:function(){var a=this.view;return a.weekNumbersVisible?'<td class="fc-week-number" '+a.weekNumberStyleAttr()+"></td>":""}},Hb=Va.MonthView=Fb.extend({computeRange:function(a){var b,c=Fb.prototype.computeRange.call(this,a);return this.isFixedWeeks()&&(b=Math.ceil(c.end.diff(c.start,"weeks",!0)),c.end.add(6-b,"weeks")),c},setGridHeight:function(a,b){b=b||"variable"===this.opt("weekMode"),b&&(a*=this.rowCnt/6),i(this.dayGrid.rowEls,a,!b)},isFixedWeeks:function(){var a=this.opt("weekMode");return a?"fixed"===a:this.opt("fixedWeekCount")}});Wa.basic={"class":Fb},Wa.basicDay={type:"basic",duration:{days:1}},Wa.basicWeek={type:"basic",duration:{weeks:1}},Wa.month={"class":Hb,duration:{months:1},defaults:{fixedWeekCount:!0}};var Ib=Va.AgendaView=wb.extend({scroller:null,timeGridClass:vb,timeGrid:null,dayGridClass:ub,dayGrid:null,axisWidth:null,headContainerEl:null,noScrollRowEls:null,bottomRuleEl:null,initialize:function(){this.timeGrid=this.instantiateTimeGrid(),this.opt("allDaySlot")&&(this.dayGrid=this.instantiateDayGrid()),this.scroller=new xb({overflowX:"hidden",overflowY:"auto"})},instantiateTimeGrid:function(){var a=this.timeGridClass.extend(Jb);return new a(this)},instantiateDayGrid:function(){var a=this.dayGridClass.extend(Kb);return new a(this)},setRange:function(a){wb.prototype.setRange.call(this,a),this.timeGrid.setRange(a),this.dayGrid&&this.dayGrid.setRange(a)},renderDates:function(){this.el.addClass("fc-agenda-view").html(this.renderSkeletonHtml()),this.renderHead(),this.scroller.render();var b=this.scroller.el.addClass("fc-time-grid-container"),c=a('<div class="fc-time-grid" />').appendTo(b);this.el.find(".fc-body > tr > td").append(b),this.timeGrid.setElement(c),this.timeGrid.renderDates(),this.bottomRuleEl=a('<hr class="fc-divider '+this.widgetHeaderClass+'"/>').appendTo(this.timeGrid.el),this.dayGrid&&(this.dayGrid.setElement(this.el.find(".fc-day-grid")),this.dayGrid.renderDates(),this.dayGrid.bottomCoordPadding=this.dayGrid.el.next("hr").outerHeight()),this.noScrollRowEls=this.el.find(".fc-row:not(.fc-scroller *)")},renderHead:function(){this.headContainerEl=this.el.find(".fc-head-container").html(this.timeGrid.renderHeadHtml())},unrenderDates:function(){this.timeGrid.unrenderDates(),this.timeGrid.removeElement(),this.dayGrid&&(this.dayGrid.unrenderDates(),this.dayGrid.removeElement()),this.scroller.destroy()},renderSkeletonHtml:function(){return'<table><thead class="fc-head"><tr><td class="fc-head-container '+this.widgetHeaderClass+'"></td></tr></thead><tbody class="fc-body"><tr><td class="'+this.widgetContentClass+'">'+(this.dayGrid?'<div class="fc-day-grid"/><hr class="fc-divider '+this.widgetHeaderClass+'"/>':"")+"</td></tr></tbody></table>"},axisStyleAttr:function(){return null!==this.axisWidth?'style="width:'+this.axisWidth+'px"':""},renderBusinessHours:function(){this.timeGrid.renderBusinessHours(),this.dayGrid&&this.dayGrid.renderBusinessHours()},unrenderBusinessHours:function(){this.timeGrid.unrenderBusinessHours(),this.dayGrid&&this.dayGrid.unrenderBusinessHours()},getNowIndicatorUnit:function(){return this.timeGrid.getNowIndicatorUnit()},renderNowIndicator:function(a){this.timeGrid.renderNowIndicator(a)},unrenderNowIndicator:function(){this.timeGrid.unrenderNowIndicator()},updateSize:function(a){this.timeGrid.updateSize(a),wb.prototype.updateSize.call(this,a)},updateWidth:function(){this.axisWidth=k(this.el.find(".fc-axis"))},setHeight:function(a,b){var c,d,g;this.bottomRuleEl.hide(),this.scroller.clear(),f(this.noScrollRowEls),this.dayGrid&&(this.dayGrid.removeSegPopover(),c=this.opt("eventLimit"),c&&"number"!=typeof c&&(c=Lb),c&&this.dayGrid.limitRows(c)),b||(d=this.computeScrollerHeight(a),this.scroller.setHeight(d),g=this.scroller.getScrollbarWidths(),(g.left||g.right)&&(e(this.noScrollRowEls,g),d=this.computeScrollerHeight(a),this.scroller.setHeight(d)),this.scroller.lockOverflow(g),this.timeGrid.getTotalSlatHeight()<d&&this.bottomRuleEl.show())},computeScrollerHeight:function(a){return a-l(this.el,this.scroller.el)},computeInitialScroll:function(){var a=b.duration(this.opt("scrollTime")),c=this.timeGrid.computeTimeTop(a);return c=Math.ceil(c),c&&c++,c},queryScroll:function(){return this.scroller.getScrollTop()},setScroll:function(a){this.scroller.setScrollTop(a)},prepareHits:function(){this.timeGrid.prepareHits(),this.dayGrid&&this.dayGrid.prepareHits()},releaseHits:function(){this.timeGrid.releaseHits(),this.dayGrid&&this.dayGrid.releaseHits()},queryHit:function(a,b){var c=this.timeGrid.queryHit(a,b);return!c&&this.dayGrid&&(c=this.dayGrid.queryHit(a,b)),c},getHitSpan:function(a){return a.component.getHitSpan(a)},getHitEl:function(a){return a.component.getHitEl(a)},renderEvents:function(a){var b,c,d=[],e=[],f=[];for(c=0;c<a.length;c++)a[c].allDay?d.push(a[c]):e.push(a[c]);b=this.timeGrid.renderEvents(e),this.dayGrid&&(f=this.dayGrid.renderEvents(d)),this.updateHeight()},getEventSegs:function(){return this.timeGrid.getEventSegs().concat(this.dayGrid?this.dayGrid.getEventSegs():[])},unrenderEvents:function(){this.timeGrid.unrenderEvents(),this.dayGrid&&this.dayGrid.unrenderEvents()},renderDrag:function(a,b){return a.start.hasTime()?this.timeGrid.renderDrag(a,b):this.dayGrid?this.dayGrid.renderDrag(a,b):void 0},unrenderDrag:function(){this.timeGrid.unrenderDrag(),this.dayGrid&&this.dayGrid.unrenderDrag()},renderSelection:function(a){a.start.hasTime()||a.end.hasTime()?this.timeGrid.renderSelection(a):this.dayGrid&&this.dayGrid.renderSelection(a)},unrenderSelection:function(){this.timeGrid.unrenderSelection(),this.dayGrid&&this.dayGrid.unrenderSelection()}}),Jb={renderHeadIntroHtml:function(){var a,b=this.view;return b.opt("weekNumbers")?(a=this.start.format(b.opt("smallWeekFormat")),'<th class="fc-axis fc-week-number '+b.widgetHeaderClass+'" '+b.axisStyleAttr()+"><span>"+ca(a)+"</span></th>"):'<th class="fc-axis '+b.widgetHeaderClass+'" '+b.axisStyleAttr()+"></th>"},renderBgIntroHtml:function(){var a=this.view;return'<td class="fc-axis '+a.widgetContentClass+'" '+a.axisStyleAttr()+"></td>"},renderIntroHtml:function(){var a=this.view;return'<td class="fc-axis" '+a.axisStyleAttr()+"></td>"}},Kb={renderBgIntroHtml:function(){var a=this.view;return'<td class="fc-axis '+a.widgetContentClass+'" '+a.axisStyleAttr()+"><span>"+(a.opt("allDayHtml")||ca(a.opt("allDayText")))+"</span></td>"},renderIntroHtml:function(){var a=this.view;return'<td class="fc-axis" '+a.axisStyleAttr()+"></td>"}},Lb=5,Mb=[{hours:1},{minutes:30},{minutes:15},{seconds:30},{seconds:15}];return Wa.agenda={"class":Ib,defaults:{allDaySlot:!0,allDayText:"all-day",slotDuration:"00:30:00",minTime:"00:00:00",maxTime:"24:00:00",slotEventOverlap:!0}},Wa.agendaDay={type:"agenda",duration:{days:1}},Wa.agendaWeek={type:"agenda",duration:{weeks:1}},Va});
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/highslide-full.min.js b/tools/infra-dashboard/js/highslide-full.min.js
new file mode 100644 (file)
index 0000000..03c786f
--- /dev/null
@@ -0,0 +1,3315 @@
+/******************************************************************************
+Name:    Highslide JS
+Version: 4.1.8 (October 27 2009)
+Config:  default +events +unobtrusive +imagemap +slideshow +positioning +transitions +viewport +thumbstrip +inline +ajax +iframe +flash
+Author:  Torstein Hønsi
+Support: http://highslide.com/support
+
+Licence:
+Highslide JS is licensed under a Creative Commons Attribution-NonCommercial 2.5
+License (http://creativecommons.org/licenses/by-nc/2.5/).
+
+You are free:
+       * to copy, distribute, display, and perform the work
+       * to make derivative works
+
+Under the following conditions:
+       * Attribution. You must attribute the work in the manner  specified by  the
+         author or licensor.
+       * Noncommercial. You may not use this work for commercial purposes.
+
+* For  any  reuse  or  distribution, you  must make clear to others the license
+  terms of this work.
+* Any  of  these  conditions  can  be  waived  if  you  get permission from the 
+  copyright holder.
+
+Your fair use and other rights are in no way affected by the above.
+******************************************************************************/
+if (!hs) { var hs = {
+// Language strings
+lang : {
+       cssDirection: 'ltr',
+       loadingText : 'Loading...',
+       loadingTitle : 'Click to cancel',
+       focusTitle : 'Click to bring to front',
+       fullExpandTitle : 'Expand to actual size (f)',
+       creditsText : 'Powered by <i>Highslide JS</i>',
+       creditsTitle : 'Go to the Highslide JS homepage',
+       previousText : 'Previous',
+       nextText : 'Next', 
+       moveText : 'Move',
+       closeText : 'Close', 
+       closeTitle : 'Close (esc)', 
+       resizeTitle : 'Resize',
+       playText : 'Play',
+       playTitle : 'Play slideshow (spacebar)',
+       pauseText : 'Pause',
+       pauseTitle : 'Pause slideshow (spacebar)',
+       previousTitle : 'Previous (arrow left)',
+       nextTitle : 'Next (arrow right)',
+       moveTitle : 'Move',
+       fullExpandText : '1:1',
+       number: 'Image %1 of %2',
+       restoreTitle : 'Click to close image, click and drag to move. Use arrow keys for next and previous.'
+},
+// See http://highslide.com/ref for examples of settings  
+graphicsDir : '../media/',
+expandCursor : 'zoomin.cur', // null disables
+restoreCursor : 'zoomout.cur', // null disables
+expandDuration : 250, // milliseconds
+restoreDuration : 250,
+marginLeft : 15,
+marginRight : 15,
+marginTop : 15,
+marginBottom : 15,
+zIndexCounter : 1001, // adjust to other absolutely positioned elements
+loadingOpacity : 0.75,
+allowMultipleInstances: true,
+numberOfImagesToPreload : 5,
+outlineWhileAnimating : 2, // 0 = never, 1 = always, 2 = HTML only 
+outlineStartOffset : 3, // ends at 10
+padToMinWidth : false, // pad the popup width to make room for wide caption
+fullExpandPosition : 'bottom right',
+fullExpandOpacity : 1,
+showCredits : true, // you can set this to false if you want
+creditsHref : 'http://highslide.com/',
+creditsTarget : '_self',
+enableKeyListener : true,
+openerTagNames : ['a', 'area'], // Add more to allow slideshow indexing
+transitions : [],
+transitionDuration: 250,
+dimmingOpacity: 0, // Lightbox style dimming background
+dimmingDuration: 50, // 0 for instant dimming
+
+allowWidthReduction : false,
+allowHeightReduction : true,
+preserveContent : true, // Preserve changes made to the content and position of HTML popups.
+objectLoadTime : 'before', // Load iframes 'before' or 'after' expansion.
+cacheAjax : true, // Cache ajax popups for instant display. Can be overridden for each popup.
+anchor : 'auto', // where the image expands from
+align : 'auto', // position in the client (overrides anchor)
+targetX: null, // the id of a target element
+targetY: null,
+dragByHeading: true,
+minWidth: 200,
+minHeight: 200,
+allowSizeReduction: true, // allow the image to reduce to fit client size. If false, this overrides minWidth and minHeight
+outlineType : 'drop-shadow', // set null to disable outlines
+skin : {
+       controls:
+               '<div class="highslide-controls"><ul>'+
+                       '<li class="highslide-previous">'+
+                               '<a href="#" title="{hs.lang.previousTitle}">'+
+                               '<span>{hs.lang.previousText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-play">'+
+                               '<a href="#" title="{hs.lang.playTitle}">'+
+                               '<span>{hs.lang.playText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-pause">'+
+                               '<a href="#" title="{hs.lang.pauseTitle}">'+
+                               '<span>{hs.lang.pauseText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-next">'+
+                               '<a href="#" title="{hs.lang.nextTitle}">'+
+                               '<span>{hs.lang.nextText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-move">'+
+                               '<a href="#" title="{hs.lang.moveTitle}">'+
+                               '<span>{hs.lang.moveText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-full-expand">'+
+                               '<a href="#" title="{hs.lang.fullExpandTitle}">'+
+                               '<span>{hs.lang.fullExpandText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-close">'+
+                               '<a href="#" title="{hs.lang.closeTitle}" >'+
+                               '<span>{hs.lang.closeText}</span></a>'+
+                       '</li>'+
+               '</ul></div>'
+       ,
+       contentWrapper:
+               '<div class="highslide-header"><ul>'+
+                       '<li class="highslide-previous">'+
+                               '<a href="#" title="{hs.lang.previousTitle}" onclick="return hs.previous(this)">'+
+                               '<span>{hs.lang.previousText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-next">'+
+                               '<a href="#" title="{hs.lang.nextTitle}" onclick="return hs.next(this)">'+
+                               '<span>{hs.lang.nextText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-move">'+
+                               '<a href="#" title="{hs.lang.moveTitle}" onclick="return false">'+
+                               '<span>{hs.lang.moveText}</span></a>'+
+                       '</li>'+
+                       '<li class="highslide-close">'+
+                               '<a href="#" title="{hs.lang.closeTitle}" onclick="return hs.close(this)">'+
+                               '<span>{hs.lang.closeText}</span></a>'+
+                       '</li>'+
+               '</ul></div>'+
+               '<div class="highslide-body"></div>'+
+               '<div class="highslide-footer"><div>'+
+                       '<span class="highslide-resize" title="{hs.lang.resizeTitle}"><span></span></span>'+
+               '</div></div>'
+},
+// END OF YOUR SETTINGS
+
+
+// declare internal properties
+preloadTheseImages : [],
+continuePreloading: true,
+expanders : [],
+overrides : [
+       'allowSizeReduction',
+       'useBox',
+       'anchor',
+       'align',
+       'targetX',
+       'targetY',
+       'outlineType',
+       'outlineWhileAnimating',
+       'captionId',
+       'captionText',
+       'captionEval',
+       'captionOverlay',
+       'headingId',
+       'headingText',
+       'headingEval',
+       'headingOverlay',
+       'creditsPosition',
+       'dragByHeading',
+       'autoplay',
+       'numberPosition',
+       'transitions',
+       'dimmingOpacity',
+       
+       'width',
+       'height',
+       
+       'contentId',
+       'allowWidthReduction',
+       'allowHeightReduction',
+       'preserveContent',
+       'maincontentId',
+       'maincontentText',
+       'maincontentEval',
+       'objectType',   
+       'cacheAjax',    
+       'objectWidth',
+       'objectHeight',
+       'objectLoadTime',       
+       'swfOptions',
+       'wrapperClassName',
+       'minWidth',
+       'minHeight',
+       'maxWidth',
+       'maxHeight',
+       'pageOrigin',
+       'slideshowGroup',
+       'easing',
+       'easingClose',
+       'fadeInOut',
+       'src'
+],
+overlays : [],
+idCounter : 0,
+oPos : {
+       x: ['leftpanel', 'left', 'center', 'right', 'rightpanel'],
+       y: ['above', 'top', 'middle', 'bottom', 'below']
+},
+mouse: {},
+headingOverlay: {},
+captionOverlay: {},
+swfOptions: { flashvars: {}, params: {}, attributes: {} },
+timers : [],
+
+slideshows : [],
+
+pendingOutlines : {},
+sleeping : [],
+preloadTheseAjax : [],
+cacheBindings : [],
+cachedGets : {},
+clones : {},
+onReady: [],
+uaVersion: /Trident\/4\.0/.test(navigator.userAgent) ? 8 :
+       parseFloat((navigator.userAgent.toLowerCase().match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1]),
+ie : (document.all && !window.opera),
+safari : /Safari/.test(navigator.userAgent),
+geckoMac : /Macintosh.+rv:1\.[0-8].+Gecko/.test(navigator.userAgent),
+
+$ : function (id) {
+       if (id) return document.getElementById(id);
+},
+
+push : function (arr, val) {
+       arr[arr.length] = val;
+},
+
+createElement : function (tag, attribs, styles, parent, nopad) {
+       var el = document.createElement(tag);
+       if (attribs) hs.extend(el, attribs);
+       if (nopad) hs.setStyles(el, {padding: 0, border: 'none', margin: 0});
+       if (styles) hs.setStyles(el, styles);
+       if (parent) parent.appendChild(el);     
+       return el;
+},
+
+extend : function (el, attribs) {
+       for (var x in attribs) el[x] = attribs[x];
+       return el;
+},
+
+setStyles : function (el, styles) {
+       for (var x in styles) {
+               if (hs.ie && x == 'opacity') {
+                       if (styles[x] > 0.99) el.style.removeAttribute('filter');
+                       else el.style.filter = 'alpha(opacity='+ (styles[x] * 100) +')';
+               }
+               else el.style[x] = styles[x];
+       }
+},
+animate: function(el, prop, opt) {
+       var start,
+               end,
+               unit;
+       if (typeof opt != 'object' || opt === null) {
+               var args = arguments;
+               opt = {
+                       duration: args[2],
+                       easing: args[3],
+                       complete: args[4]
+               };
+       }
+       if (typeof opt.duration != 'number') opt.duration = 250;
+       opt.easing = Math[opt.easing] || Math.easeInQuad;
+       opt.curAnim = hs.extend({}, prop);
+       for (var name in prop) {
+               var e = new hs.fx(el, opt , name );
+               
+               start = parseFloat(hs.css(el, name)) || 0;
+               end = parseFloat(prop[name]);
+               unit = name != 'opacity' ? 'px' : '';
+               
+               e.custom( start, end, unit );
+       }       
+},
+css: function(el, prop) {
+       if (el.style[prop]) {
+               return el.style[prop];
+       } else if (document.defaultView) {
+               return document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
+
+       } else {
+               if (prop == 'opacity') prop = 'filter';
+               var val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b){ return b.toUpperCase(); })];
+               if (prop == 'filter') 
+                       val = val.replace(/alpha\(opacity=([0-9]+)\)/, 
+                               function (a, b) { return b / 100 });
+               return val === '' ? 1 : val;
+       } 
+},
+
+getPageSize : function () {
+       var d = document, w = window, iebody = d.compatMode && d.compatMode != 'BackCompat' 
+               ? d.documentElement : d.body;
+       
+       var width = hs.ie ? iebody.clientWidth : 
+                       (d.documentElement.clientWidth || self.innerWidth),
+               height = hs.ie ? iebody.clientHeight : self.innerHeight;
+       
+       hs.page = {
+               width: width,
+               height: height,         
+               scrollLeft: hs.ie ? iebody.scrollLeft : pageXOffset,
+               scrollTop: hs.ie ? iebody.scrollTop : pageYOffset
+       };
+       return hs.page;
+},
+
+getPosition : function(el)     {
+       if (/area/i.test(el.tagName)) {
+               var imgs = document.getElementsByTagName('img');
+               for (var i = 0; i < imgs.length; i++) {
+                       var u = imgs[i].useMap;
+                       if (u && u.replace(/^.*?#/, '') == el.parentNode.name) {
+                               el = imgs[i];
+                               break;
+                       }
+               }
+       }
+       var p = { x: el.offsetLeft, y: el.offsetTop };
+       while (el.offsetParent) {
+               el = el.offsetParent;
+               p.x += el.offsetLeft;
+               p.y += el.offsetTop;
+               if (el != document.body && el != document.documentElement) {
+                       p.x -= el.scrollLeft;
+                       p.y -= el.scrollTop;
+               }
+       }
+       return p;
+},
+
+expand : function(a, params, custom, type) {
+       if (!a) a = hs.createElement('a', null, { display: 'none' }, hs.container);
+       if (typeof a.getParams == 'function') return params;
+       if (type == 'html') {
+               for (var i = 0; i < hs.sleeping.length; i++) {
+                       if (hs.sleeping[i] && hs.sleeping[i].a == a) {
+                               hs.sleeping[i].awake();
+                               hs.sleeping[i] = null;
+                               return false;
+                       }
+               }
+               hs.hasHtmlExpanders = true;
+       }       
+       try {   
+               new hs.Expander(a, params, custom, type);
+               return false;
+       } catch (e) { return true; }
+},
+
+htmlExpand : function(a, params, custom) {
+       return hs.expand(a, params, custom, 'html');
+},
+
+getSelfRendered : function() {
+       return hs.createElement('div', { 
+               className: 'highslide-html-content', 
+               innerHTML: hs.replaceLang(hs.skin.contentWrapper) 
+       });
+},
+getElementByClass : function (el, tagName, className) {
+       var els = el.getElementsByTagName(tagName);
+       for (var i = 0; i < els.length; i++) {
+       if ((new RegExp(className)).test(els[i].className)) {
+                       return els[i];
+               }
+       }
+       return null;
+},
+replaceLang : function(s) {
+       s = s.replace(/\s/g, ' ');
+       var re = /{hs\.lang\.([^}]+)\}/g,
+               matches = s.match(re),
+               lang;
+       if (matches) for (var i = 0; i < matches.length; i++) {
+               lang = matches[i].replace(re, "$1");
+               if (typeof hs.lang[lang] != 'undefined') s = s.replace(matches[i], hs.lang[lang]);
+       }
+       return s;
+},
+
+
+setClickEvents : function () {
+       var els = document.getElementsByTagName('a');
+       for (var i = 0; i < els.length; i++) {
+               var type = hs.isUnobtrusiveAnchor(els[i]);
+               if (type && !els[i].hsHasSetClick) {
+                       (function(){
+                               var t = type;
+                               if (hs.fireEvent(hs, 'onSetClickEvent', { element: els[i], type: t })) {
+                                       els[i].onclick =(type == 'image') ?function() { return hs.expand(this) }:
+                                               function() { return hs.htmlExpand(this, { objectType: t } );};
+                               }
+                       })();
+                       els[i].hsHasSetClick = true;    
+               }
+       }
+       hs.getAnchors();
+},
+isUnobtrusiveAnchor: function(el) {
+       if (el.rel == 'highslide') return 'image';
+       else if (el.rel == 'highslide-ajax') return 'ajax';
+       else if (el.rel == 'highslide-iframe') return 'iframe';
+       else if (el.rel == 'highslide-swf') return 'swf';
+},
+
+getCacheBinding : function (a) {
+       for (var i = 0; i < hs.cacheBindings.length; i++) {
+               if (hs.cacheBindings[i][0] == a) {
+                       var c = hs.cacheBindings[i][1];
+                       hs.cacheBindings[i][1] = c.cloneNode(1);
+                       return c;
+               }
+       }
+       return null;
+},
+
+preloadAjax : function (e) {
+       var arr = hs.getAnchors();
+       for (var i = 0; i < arr.htmls.length; i++) {
+               var a = arr.htmls[i];
+               if (hs.getParam(a, 'objectType') == 'ajax' && hs.getParam(a, 'cacheAjax'))
+                       hs.push(hs.preloadTheseAjax, a);
+       }
+       
+       hs.preloadAjaxElement(0);
+},
+
+preloadAjaxElement : function (i) {
+       if (!hs.preloadTheseAjax[i]) return;
+       var a = hs.preloadTheseAjax[i];
+       var cache = hs.getNode(hs.getParam(a, 'contentId'));
+       if (!cache) cache = hs.getSelfRendered();
+       var ajax = new hs.Ajax(a, cache, 1);    
+       ajax.onError = function () { };
+       ajax.onLoad = function () {
+               hs.push(hs.cacheBindings, [a, cache]);
+               hs.preloadAjaxElement(i + 1);
+       };
+       ajax.run();
+},
+
+focusTopmost : function() {
+       var topZ = 0, 
+               topmostKey = -1,
+               expanders = hs.expanders,
+               exp,
+               zIndex;
+       for (var i = 0; i < expanders.length; i++) {
+               exp = expanders[i];
+               if (exp) {
+                       zIndex = exp.wrapper.style.zIndex;
+                       if (zIndex && zIndex > topZ) {
+                               topZ = zIndex;                          
+                               topmostKey = i;
+                       }
+               }
+       }
+       if (topmostKey == -1) hs.focusKey = -1;
+       else expanders[topmostKey].focus();
+},
+
+getParam : function (a, param) {
+       a.getParams = a.onclick;
+       var p = a.getParams ? a.getParams() : null;
+       a.getParams = null;
+       
+       return (p && typeof p[param] != 'undefined') ? p[param] : 
+               (typeof hs[param] != 'undefined' ? hs[param] : null);
+},
+
+getSrc : function (a) {
+       var src = hs.getParam(a, 'src');
+       if (src) return src;
+       return a.href;
+},
+
+getNode : function (id) {
+       var node = hs.$(id), clone = hs.clones[id], a = {};
+       if (!node && !clone) return null;
+       if (!clone) {
+               clone = node.cloneNode(true);
+               clone.id = '';
+               hs.clones[id] = clone;
+               return node;
+       } else {
+               return clone.cloneNode(true);
+       }
+},
+
+discardElement : function(d) {
+       if (d) hs.garbageBin.appendChild(d);
+       hs.garbageBin.innerHTML = '';
+},
+dim : function(exp) {
+       if (!hs.dimmer) {
+               hs.dimmer = hs.createElement ('div', {
+                               className: 'highslide-dimming highslide-viewport-size',
+                               owner: '',
+                               onclick: function() {
+                                       if (hs.fireEvent(hs, 'onDimmerClick'))
+                                       
+                                               hs.close();
+                               }
+                       }, {
+                visibility: 'visible',
+                               opacity: 0
+                       }, hs.container, true);
+       }
+
+       hs.dimmer.style.display = '';
+
+       hs.dimmer.owner += '|'+ exp.key;
+       if (hs.geckoMac && hs.dimmingGeckoFix)
+               hs.setStyles(hs.dimmer, {
+                       background: 'url('+ hs.graphicsDir + 'geckodimmer.png)',
+                       opacity: 1
+               });
+       else
+               hs.animate(hs.dimmer, { opacity: exp.dimmingOpacity }, hs.dimmingDuration);
+},
+undim : function(key) {
+       if (!hs.dimmer) return;
+       if (typeof key != 'undefined') hs.dimmer.owner = hs.dimmer.owner.replace('|'+ key, '');
+
+       if (
+               (typeof key != 'undefined' && hs.dimmer.owner != '')
+               || (hs.upcoming && hs.getParam(hs.upcoming, 'dimmingOpacity'))
+       ) return;
+
+       if (hs.geckoMac && hs.dimmingGeckoFix) hs.dimmer.style.display = 'none';
+       else hs.animate(hs.dimmer, { opacity: 0 }, hs.dimmingDuration, null, function() {
+               hs.dimmer.style.display = 'none';
+       });
+},
+transit : function (adj, exp) {
+       var last = exp || hs.getExpander();
+       exp = last;
+       if (hs.upcoming) return false;
+       else hs.last = last;
+       hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+       try {
+               hs.upcoming = adj;
+               adj.onclick();          
+       } catch (e){
+               hs.last = hs.upcoming = null;
+       }
+       try {
+               if (!adj || exp.transitions[1] != 'crossfade')
+               exp.close();
+       } catch (e) {}
+       return false;
+},
+
+previousOrNext : function (el, op) {
+       var exp = hs.getExpander(el);
+       if (exp) return hs.transit(exp.getAdjacentAnchor(op), exp);
+       else return false;
+},
+
+previous : function (el) {
+       return hs.previousOrNext(el, -1);
+},
+
+next : function (el) {
+       return hs.previousOrNext(el, 1);        
+},
+
+keyHandler : function(e) {
+       if (!e) e = window.event;
+       if (!e.target) e.target = e.srcElement; // ie
+       if (typeof e.target.form != 'undefined') return true; // form element has focus
+       if (!hs.fireEvent(hs, 'onKeyDown', e)) return true;
+       var exp = hs.getExpander();
+       
+       var op = null;
+       switch (e.keyCode) {
+               case 70: // f
+                       if (exp) exp.doFullExpand();
+                       return true;
+               case 32: // Space
+                       op = 2;
+                       break;
+               case 34: // Page Down
+               case 39: // Arrow right
+               case 40: // Arrow down
+                       op = 1;
+                       break;
+               case 8:  // Backspace
+               case 33: // Page Up
+               case 37: // Arrow left
+               case 38: // Arrow up
+                       op = -1;
+                       break;
+               case 27: // Escape
+               case 13: // Enter
+                       op = 0;
+       }
+       if (op !== null) {
+               hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+               if (!hs.enableKeyListener) return true;
+               
+               if (e.preventDefault) e.preventDefault();
+       else e.returnValue = false;
+       if (exp) {
+                       if (op == 0) {
+                               exp.close();
+                       } else if (op == 2) {
+                               if (exp.slideshow) exp.slideshow.hitSpace();
+                       } else {
+                               if (exp.slideshow) exp.slideshow.pause();
+                               hs.previousOrNext(exp.key, op);
+                       }
+                       return false;
+               }
+       }
+       return true;
+},
+
+
+registerOverlay : function (overlay) {
+       hs.push(hs.overlays, hs.extend(overlay, { hsId: 'hsId'+ hs.idCounter++ } ));
+},
+
+
+addSlideshow : function (options) {
+       var sg = options.slideshowGroup;
+       if (typeof sg == 'object') {
+               for (var i = 0; i < sg.length; i++) {
+                       var o = {};
+                       for (var x in options) o[x] = options[x];
+                       o.slideshowGroup = sg[i];
+                       hs.push(hs.slideshows, o);
+               }
+       } else {
+               hs.push(hs.slideshows, options);
+       }
+},
+
+getWrapperKey : function (element, expOnly) {
+       var el, re = /^highslide-wrapper-([0-9]+)$/;
+       // 1. look in open expanders
+       el = element;
+       while (el.parentNode)   {
+               if (el.hsKey !== undefined) return el.hsKey;
+               if (el.id && re.test(el.id)) return el.id.replace(re, "$1");
+               el = el.parentNode;
+       }
+       // 2. look in thumbnail
+       if (!expOnly) {
+               el = element;
+               while (el.parentNode)   {
+                       if (el.tagName && hs.isHsAnchor(el)) {
+                               for (var key = 0; key < hs.expanders.length; key++) {
+                                       var exp = hs.expanders[key];
+                                       if (exp && exp.a == el) return key;
+                               }
+                       }
+                       el = el.parentNode;
+               }
+       }
+       return null; 
+},
+
+getExpander : function (el, expOnly) {
+       if (typeof el == 'undefined') return hs.expanders[hs.focusKey] || null;
+       if (typeof el == 'number') return hs.expanders[el] || null;
+       if (typeof el == 'string') el = hs.$(el);
+       return hs.expanders[hs.getWrapperKey(el, expOnly)] || null;
+},
+
+isHsAnchor : function (a) {
+       return (a.onclick && a.onclick.toString().replace(/\s/g, ' ').match(/hs.(htmlE|e)xpand/));
+},
+
+reOrder : function () {
+       for (var i = 0; i < hs.expanders.length; i++)
+               if (hs.expanders[i] && hs.expanders[i].isExpanded) hs.focusTopmost();
+},
+fireEvent : function (obj, evt, args) {
+       return obj && obj[evt] ? (obj[evt](obj, args) !== false) : true;
+},
+
+mouseClickHandler : function(e) 
+{      
+       if (!e) e = window.event;
+       if (e.button > 1) return true;
+       if (!e.target) e.target = e.srcElement;
+       
+       var el = e.target;
+       while (el.parentNode
+               && !(/highslide-(image|move|html|resize)/.test(el.className)))
+       {
+               el = el.parentNode;
+       }
+       var exp = hs.getExpander(el);
+       if (exp && (exp.isClosing || !exp.isExpanded)) return true;
+               
+       if (exp && e.type == 'mousedown') {
+               if (e.target.form) return true;
+               var match = el.className.match(/highslide-(image|move|resize)/);
+               if (match) {
+                       hs.dragArgs = { 
+                               exp: exp , 
+                               type: match[1], 
+                               left: exp.x.pos, 
+                               width: exp.x.size, 
+                               top: exp.y.pos, 
+                               height: exp.y.size, 
+                               clickX: e.clientX, 
+                               clickY: e.clientY
+                       };
+                       
+                       
+                       hs.addEventListener(document, 'mousemove', hs.dragHandler);
+                       if (e.preventDefault) e.preventDefault(); // FF
+                       
+                       if (/highslide-(image|html)-blur/.test(exp.content.className)) {
+                               exp.focus();
+                               hs.hasFocused = true;
+                       }
+                       return false;
+               }
+               else if (/highslide-html/.test(el.className) && hs.focusKey != exp.key) {
+                       exp.focus();
+                       exp.doShowHide('hidden');
+               }
+       } else if (e.type == 'mouseup') {
+               
+               hs.removeEventListener(document, 'mousemove', hs.dragHandler);
+               
+               if (hs.dragArgs) {
+                       if (hs.styleRestoreCursor && hs.dragArgs.type == 'image') 
+                               hs.dragArgs.exp.content.style.cursor = hs.styleRestoreCursor;
+                       var hasDragged = hs.dragArgs.hasDragged;
+                       
+                       if (!hasDragged &&!hs.hasFocused && !/(move|resize)/.test(hs.dragArgs.type)) {
+                               if (hs.fireEvent(exp, 'onImageClick'))
+                               exp.close();
+                       } 
+                       else if (hasDragged || (!hasDragged && hs.hasHtmlExpanders)) {
+                               hs.dragArgs.exp.doShowHide('hidden');
+                       }
+                       
+                       if (hs.dragArgs.exp.releaseMask) 
+                               hs.dragArgs.exp.releaseMask.style.display = 'none';
+                       
+                       if (hasDragged) hs.fireEvent(hs.dragArgs.exp, 'onDrop', hs.dragArgs);
+                       hs.hasFocused = false;
+                       hs.dragArgs = null;
+               
+               } else if (/highslide-image-blur/.test(el.className)) {
+                       el.style.cursor = hs.styleRestoreCursor;                
+               }
+       }
+       return false;
+},
+
+dragHandler : function(e)
+{
+       if (!hs.dragArgs) return true;
+       if (!e) e = window.event;
+       var a = hs.dragArgs, exp = a.exp;
+       if (exp.iframe) {               
+               if (!exp.releaseMask) exp.releaseMask = hs.createElement('div', null, 
+                       { position: 'absolute', width: exp.x.size+'px', height: exp.y.size+'px', 
+                               left: exp.x.cb+'px', top: exp.y.cb+'px', zIndex: 4,     background: (hs.ie ? 'white' : 'none'), 
+                               opacity: 0.01 }, 
+                       exp.wrapper, true);
+               if (exp.releaseMask.style.display == 'none')
+                       exp.releaseMask.style.display = '';
+       }
+       
+       a.dX = e.clientX - a.clickX;
+       a.dY = e.clientY - a.clickY;    
+       
+       var distance = Math.sqrt(Math.pow(a.dX, 2) + Math.pow(a.dY, 2));
+       if (!a.hasDragged) a.hasDragged = (a.type != 'image' && distance > 0)
+               || (distance > (hs.dragSensitivity || 5));
+       
+       if (a.hasDragged && e.clientX > 5 && e.clientY > 5) {
+               if (!hs.fireEvent(exp, 'onDrag', a)) return false;
+               
+               if (a.type == 'resize') exp.resize(a);
+               else {
+                       exp.moveTo(a.left + a.dX, a.top + a.dY);
+                       if (a.type == 'image') exp.content.style.cursor = 'move';
+               }
+       }
+       return false;
+},
+
+wrapperMouseHandler : function (e) {
+       try {
+               if (!e) e = window.event;
+               var over = /mouseover/i.test(e.type); 
+               if (!e.target) e.target = e.srcElement; // ie
+               if (hs.ie) e.relatedTarget = 
+                       over ? e.fromElement : e.toElement; // ie
+               var exp = hs.getExpander(e.target);
+               if (!exp.isExpanded) return;
+               if (!exp || !e.relatedTarget || hs.getExpander(e.relatedTarget, true) == exp 
+                       || hs.dragArgs) return;
+               hs.fireEvent(exp, over ? 'onMouseOver' : 'onMouseOut', e);
+               for (var i = 0; i < exp.overlays.length; i++) (function() {
+                       var o = hs.$('hsId'+ exp.overlays[i]);
+                       if (o && o.hideOnMouseOut) {
+                               if (over) hs.setStyles(o, { visibility: 'visible', display: '' });
+                               hs.animate(o, { opacity: over ? o.opacity : 0 }, o.dur);
+                       }
+               })();   
+       } catch (e) {}
+},
+addEventListener : function (el, event, func) {
+       if (el == document && event == 'ready') {
+               hs.push(hs.onReady, func);
+       }
+       try {
+               el.addEventListener(event, func, false);
+       } catch (e) {
+               try {
+                       el.detachEvent('on'+ event, func);
+                       el.attachEvent('on'+ event, func);
+               } catch (e) {
+                       el['on'+ event] = func;
+               }
+       } 
+},
+
+removeEventListener : function (el, event, func) {
+       try {
+               el.removeEventListener(event, func, false);
+       } catch (e) {
+               try {
+                       el.detachEvent('on'+ event, func);
+               } catch (e) {
+                       el['on'+ event] = null;
+               }
+       }
+},
+
+preloadFullImage : function (i) {
+       if (hs.continuePreloading && hs.preloadTheseImages[i] && hs.preloadTheseImages[i] != 'undefined') {
+               var img = document.createElement('img');
+               img.onload = function() { 
+                       img = null;
+                       hs.preloadFullImage(i + 1);
+               };
+               img.src = hs.preloadTheseImages[i];
+       }
+},
+preloadImages : function (number) {
+       if (number && typeof number != 'object') hs.numberOfImagesToPreload = number;
+       
+       var arr = hs.getAnchors();
+       for (var i = 0; i < arr.images.length && i < hs.numberOfImagesToPreload; i++) {
+               hs.push(hs.preloadTheseImages, hs.getSrc(arr.images[i]));
+       }
+       
+       // preload outlines
+       if (hs.outlineType)     new hs.Outline(hs.outlineType, function () { hs.preloadFullImage(0)} );
+       else
+       
+       hs.preloadFullImage(0);
+       
+       // preload cursor
+       if (hs.restoreCursor) var cur = hs.createElement('img', { src: hs.graphicsDir + hs.restoreCursor });
+},
+
+
+init : function () {
+       if (!hs.container) {
+       
+               hs.getPageSize();
+               hs.ieLt7 = hs.ie && hs.uaVersion < 7;
+               hs.ie6SSL = hs.ieLt7 && location.protocol == 'https:';
+               for (var x in hs.langDefaults) {
+                       if (typeof hs[x] != 'undefined') hs.lang[x] = hs[x];
+                       else if (typeof hs.lang[x] == 'undefined' && typeof hs.langDefaults[x] != 'undefined') 
+                               hs.lang[x] = hs.langDefaults[x];
+               }
+               
+               hs.container = hs.createElement('div', {
+                               className: 'highslide-container'
+                       }, {
+                               position: 'absolute',
+                               left: 0, 
+                               top: 0, 
+                               width: '100%', 
+                               zIndex: hs.zIndexCounter,
+                               direction: 'ltr'
+                       }, 
+                       document.body,
+                       true
+               );
+               hs.loading = hs.createElement('a', {
+                               className: 'highslide-loading',
+                               title: hs.lang.loadingTitle,
+                               innerHTML: hs.lang.loadingText,
+                               href: 'javascript:;'
+                       }, {
+                               position: 'absolute',
+                               top: '-9999px',
+                               opacity: hs.loadingOpacity,
+                               zIndex: 1
+                       }, hs.container
+               );
+               hs.garbageBin = hs.createElement('div', null, { display: 'none' }, hs.container);
+               hs.viewport = hs.createElement('div', {
+                               className: 'highslide-viewport highslide-viewport-size'
+                       }, {
+                               visibility: (hs.safari && hs.uaVersion < 525) ? 'visible' : 'hidden'
+                       }, hs.container, 1
+               );
+               hs.clearing = hs.createElement('div', null, 
+                       { clear: 'both', paddingTop: '1px' }, null, true);
+               
+               // http://www.robertpenner.com/easing/ 
+               Math.linearTween = function (t, b, c, d) {
+                       return c*t/d + b;
+               };
+               Math.easeInQuad = function (t, b, c, d) {
+                       return c*(t/=d)*t + b;
+               };
+               Math.easeOutQuad = function (t, b, c, d) {
+                       return -c *(t/=d)*(t-2) + b;
+               };
+               
+               hs.hideSelects = hs.ieLt7;
+               hs.hideIframes = ((window.opera && hs.uaVersion < 9) || navigator.vendor == 'KDE' 
+                       || (hs.ie && hs.uaVersion < 5.5));
+               hs.fireEvent(this, 'onActivate');
+       }
+},
+ready : function() {
+       if (hs.isReady) return;
+       hs.isReady = true;
+       for (var i = 0; i < hs.onReady.length; i++) hs.onReady[i]();
+},
+
+updateAnchors : function() {
+       var el, els, all = [], images = [], htmls = [],groups = {}, re;
+               
+       for (var i = 0; i < hs.openerTagNames.length; i++) {
+               els = document.getElementsByTagName(hs.openerTagNames[i]);
+               for (var j = 0; j < els.length; j++) {
+                       el = els[j];
+                       re = hs.isHsAnchor(el);
+                       if (re) {
+                               hs.push(all, el);
+                               if (re[0] == 'hs.expand') hs.push(images, el);
+                               else if (re[0] == 'hs.htmlExpand') hs.push(htmls, el);
+                               var g = hs.getParam(el, 'slideshowGroup') || 'none';
+                               if (!groups[g]) groups[g] = [];
+                               hs.push(groups[g], el);
+                       }
+               }
+       }
+       hs.anchors = { all: all, groups: groups, images: images, htmls: htmls };
+       return hs.anchors;
+       
+},
+
+getAnchors : function() {
+       return hs.anchors || hs.updateAnchors();
+},
+
+
+close : function(el) {
+       var exp = hs.getExpander(el);
+       if (exp) exp.close();
+       return false;
+}
+}; // end hs object
+hs.fx = function( elem, options, prop ){
+       this.options = options;
+       this.elem = elem;
+       this.prop = prop;
+
+       if (!options.orig) options.orig = {};
+};
+hs.fx.prototype = {
+       update: function(){
+               (hs.fx.step[this.prop] || hs.fx.step._default)(this);
+               
+               if (this.options.step)
+                       this.options.step.call(this.elem, this.now, this);
+
+       },
+       custom: function(from, to, unit){
+               this.startTime = (new Date()).getTime();
+               this.start = from;
+               this.end = to;
+               this.unit = unit;// || this.unit || "px";
+               this.now = this.start;
+               this.pos = this.state = 0;
+
+               var self = this;
+               function t(gotoEnd){
+                       return self.step(gotoEnd);
+               }
+
+               t.elem = this.elem;
+
+               if ( t() && hs.timers.push(t) == 1 ) {
+                       hs.timerId = setInterval(function(){
+                               var timers = hs.timers;
+
+                               for ( var i = 0; i < timers.length; i++ )
+                                       if ( !timers[i]() )
+                                               timers.splice(i--, 1);
+
+                               if ( !timers.length ) {
+                                       clearInterval(hs.timerId);
+                               }
+                       }, 13);
+               }
+       },
+       step: function(gotoEnd){
+               var t = (new Date()).getTime();
+               if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+                       this.now = this.end;
+                       this.pos = this.state = 1;
+                       this.update();
+
+                       this.options.curAnim[ this.prop ] = true;
+
+                       var done = true;
+                       for ( var i in this.options.curAnim )
+                               if ( this.options.curAnim[i] !== true )
+                                       done = false;
+
+                       if ( done ) {
+                               if (this.options.complete) this.options.complete.call(this.elem);
+                       }
+                       return false;
+               } else {
+                       var n = t - this.startTime;
+                       this.state = n / this.options.duration;
+                       this.pos = this.options.easing(n, 0, 1, this.options.duration);
+                       this.now = this.start + ((this.end - this.start) * this.pos);
+                       this.update();
+               }
+               return true;
+       }
+
+};
+
+hs.extend( hs.fx, {
+       step: {
+
+               opacity: function(fx){
+                       hs.setStyles(fx.elem, { opacity: fx.now });
+               },
+
+               _default: function(fx){
+                       try {
+                               if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
+                                       fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+                               else
+                                       fx.elem[ fx.prop ] = fx.now;
+                       } catch (e) {}
+               }
+       }
+});
+
+hs.Outline =  function (outlineType, onLoad) {
+       this.onLoad = onLoad;
+       this.outlineType = outlineType;
+       var v = hs.uaVersion, tr;
+       
+       this.hasAlphaImageLoader = hs.ie && v >= 5.5 && v < 7;
+       if (!outlineType) {
+               if (onLoad) onLoad();
+               return;
+       }
+       
+       hs.init();
+       this.table = hs.createElement(
+               'table', { 
+                       cellSpacing: 0 
+               }, {
+                       visibility: 'hidden',
+                       position: 'absolute',
+                       borderCollapse: 'collapse',
+                       width: 0
+               },
+               hs.container,
+               true
+       );
+       var tbody = hs.createElement('tbody', null, null, this.table, 1);
+       
+       this.td = [];
+       for (var i = 0; i <= 8; i++) {
+               if (i % 3 == 0) tr = hs.createElement('tr', null, { height: 'auto' }, tbody, true);
+               this.td[i] = hs.createElement('td', null, null, tr, true);
+               var style = i != 4 ? { lineHeight: 0, fontSize: 0} : { position : 'relative' };
+               hs.setStyles(this.td[i], style);
+       }
+       this.td[4].className = outlineType +' highslide-outline';
+       
+       this.preloadGraphic(); 
+};
+
+hs.Outline.prototype = {
+preloadGraphic : function () {
+       var src = hs.graphicsDir + (hs.outlinesDir || "outlines/")+ this.outlineType +".png";
+                               
+       var appendTo = hs.safari ? hs.container : null;
+       this.graphic = hs.createElement('img', null, { position: 'absolute', 
+               top: '-9999px' }, appendTo, true); // for onload trigger
+       
+       var pThis = this;
+       this.graphic.onload = function() { pThis.onGraphicLoad(); };
+       
+       this.graphic.src = src;
+},
+
+onGraphicLoad : function () {
+       var o = this.offset = this.graphic.width / 4,
+               pos = [[0,0],[0,-4],[-2,0],[0,-8],0,[-2,-8],[0,-2],[0,-6],[-2,-2]],
+               dim = { height: (2*o) +'px', width: (2*o) +'px' };
+       for (var i = 0; i <= 8; i++) {
+               if (pos[i]) {
+                       if (this.hasAlphaImageLoader) {
+                               var w = (i == 1 || i == 7) ? '100%' : this.graphic.width +'px';
+                               var div = hs.createElement('div', null, { width: '100%', height: '100%', position: 'relative', overflow: 'hidden'}, this.td[i], true);
+                               hs.createElement ('div', null, { 
+                                               filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(sizingMethod=scale, src='"+ this.graphic.src + "')", 
+                                               position: 'absolute',
+                                               width: w, 
+                                               height: this.graphic.height +'px',
+                                               left: (pos[i][0]*o)+'px',
+                                               top: (pos[i][1]*o)+'px'
+                                       }, 
+                               div,
+                               true);
+                       } else {
+                               hs.setStyles(this.td[i], { background: 'url('+ this.graphic.src +') '+ (pos[i][0]*o)+'px '+(pos[i][1]*o)+'px'});
+                       }
+                       
+                       if (window.opera && (i == 3 || i ==5)) 
+                               hs.createElement('div', null, dim, this.td[i], true);
+                       
+                       hs.setStyles (this.td[i], dim);
+               }
+       }
+       this.graphic = null;
+       if (hs.pendingOutlines[this.outlineType]) hs.pendingOutlines[this.outlineType].destroy();
+       hs.pendingOutlines[this.outlineType] = this;
+       if (this.onLoad) this.onLoad();
+},
+       
+setPosition : function (pos, offset, vis, dur, easing) {
+       var exp = this.exp,
+               stl = exp.wrapper.style,
+               offset = offset || 0,
+               pos = pos || {
+                       x: exp.x.pos + offset,
+                       y: exp.y.pos + offset,
+                       w: exp.x.get('wsize') - 2 * offset,
+                       h: exp.y.get('wsize') - 2 * offset
+               };
+       if (vis) this.table.style.visibility = (pos.h >= 4 * this.offset) 
+               ? 'visible' : 'hidden';
+       hs.setStyles(this.table, {
+               left: (pos.x - this.offset) +'px',
+               top: (pos.y - this.offset) +'px',
+               width: (pos.w + 2 * this.offset) +'px'
+       });
+       
+       pos.w -= 2 * this.offset;
+       pos.h -= 2 * this.offset;
+       hs.setStyles (this.td[4], {
+               width: pos.w >= 0 ? pos.w +'px' : 0,
+               height: pos.h >= 0 ? pos.h +'px' : 0
+       });
+       if (this.hasAlphaImageLoader) this.td[3].style.height 
+               = this.td[5].style.height = this.td[4].style.height;    
+       
+},
+       
+destroy : function(hide) {
+       if (hide) this.table.style.visibility = 'hidden';
+       else hs.discardElement(this.table);
+}
+};
+
+hs.Dimension = function(exp, dim) {
+       this.exp = exp;
+       this.dim = dim;
+       this.ucwh = dim == 'x' ? 'Width' : 'Height';
+       this.wh = this.ucwh.toLowerCase();
+       this.uclt = dim == 'x' ? 'Left' : 'Top';
+       this.lt = this.uclt.toLowerCase();
+       this.ucrb = dim == 'x' ? 'Right' : 'Bottom';
+       this.rb = this.ucrb.toLowerCase();
+       this.p1 = this.p2 = 0;
+};
+hs.Dimension.prototype = {
+get : function(key) {
+       switch (key) {
+               case 'loadingPos':
+                       return this.tpos + this.tb + (this.t - hs.loading['offset'+ this.ucwh]) / 2;
+               case 'loadingPosXfade':
+                       return this.pos + this.cb+ this.p1 + (this.size - hs.loading['offset'+ this.ucwh]) / 2;
+               case 'wsize':
+                       return this.size + 2 * this.cb + this.p1 + this.p2;
+               case 'fitsize':
+                       return this.clientSize - this.marginMin - this.marginMax;
+               case 'maxsize':
+                       return this.get('fitsize') - 2 * this.cb - this.p1 - this.p2 ;
+               case 'opos':
+                       return this.pos - (this.exp.outline ? this.exp.outline.offset : 0);
+               case 'osize':
+                       return this.get('wsize') + (this.exp.outline ? 2*this.exp.outline.offset : 0);
+               case 'imgPad':
+                       return this.imgSize ? Math.round((this.size - this.imgSize) / 2) : 0;
+               
+       }
+},
+calcBorders: function() {
+       // correct for borders
+       this.cb = (this.exp.content['offset'+ this.ucwh] - this.t) / 2;
+       
+       this.marginMax = hs['margin'+ this.ucrb];
+},
+calcThumb: function() {
+       this.t = this.exp.el[this.wh] ? parseInt(this.exp.el[this.wh]) : 
+               this.exp.el['offset'+ this.ucwh];
+       this.tpos = this.exp.tpos[this.dim];
+       this.tb = (this.exp.el['offset'+ this.ucwh] - this.t) / 2;
+       if (this.tpos == 0 || this.tpos == -1) {
+               this.tpos = (hs.page[this.wh] / 2) + hs.page['scroll'+ this.uclt];              
+       };
+},
+calcExpanded: function() {
+       var exp = this.exp;
+       this.justify = 'auto';
+       
+       // get alignment
+       if (exp.align == 'center') this.justify = 'center';
+       else if (new RegExp(this.lt).test(exp.anchor)) this.justify = null;
+       else if (new RegExp(this.rb).test(exp.anchor)) this.justify = 'max';
+       
+       
+       // size and position
+       this.pos = this.tpos - this.cb + this.tb;
+       
+       if (this.maxHeight && this.dim == 'x')
+               exp.maxWidth = Math.min(exp.maxWidth || this.full, exp.maxHeight * this.full / exp.y.full); 
+               
+       this.size = Math.min(this.full, exp['max'+ this.ucwh] || this.full);
+       this.minSize = exp.allowSizeReduction ? 
+               Math.min(exp['min'+ this.ucwh], this.full) :this.full;
+       if (exp.isImage && exp.useBox)  {
+               this.size = exp[this.wh];
+               this.imgSize = this.full;
+       }
+       if (this.dim == 'x' && hs.padToMinWidth) this.minSize = exp.minWidth;
+       this.target = exp['target'+ this.dim.toUpperCase()];
+       this.marginMin = hs['margin'+ this.uclt];
+       this.scroll = hs.page['scroll'+ this.uclt];
+       this.clientSize = hs.page[this.wh];
+},
+setSize: function(i) {
+       var exp = this.exp;
+       if (exp.isImage && (exp.useBox || hs.padToMinWidth)) {
+               this.imgSize = i;
+               this.size = Math.max(this.size, this.imgSize);
+               exp.content.style[this.lt] = this.get('imgPad')+'px';
+       } else
+       this.size = i;
+       
+       exp.content.style[this.wh] = i +'px';
+       exp.wrapper.style[this.wh] = this.get('wsize') +'px';
+       if (exp.outline) exp.outline.setPosition();
+       if (exp.releaseMask) exp.releaseMask.style[this.wh] = i +'px';
+       if (this.dim == 'y' && exp.iDoc && exp.body.style.height != 'auto') try {
+               exp.iDoc.body.style.overflow = 'auto';
+       } catch (e) {}
+       if (exp.isHtml) {
+               var d = exp.scrollerDiv;
+               if (this.sizeDiff === undefined)
+                       this.sizeDiff = exp.innerContent['offset'+ this.ucwh] - d['offset'+ this.ucwh];
+               d.style[this.wh] = (this.size - this.sizeDiff) +'px';
+                       
+               if (this.dim == 'x') exp.mediumContent.style.width = 'auto';
+               if (exp.body) exp.body.style[this.wh] = 'auto';
+       }
+       if (this.dim == 'x' && exp.overlayBox) exp.sizeOverlayBox(true);
+       if (this.dim == 'x' && exp.slideshow && exp.isImage) {
+               if (i == this.full) exp.slideshow.disable('full-expand');
+               else exp.slideshow.enable('full-expand');
+       }
+},
+setPos: function(i) {
+       this.pos = i;
+       this.exp.wrapper.style[this.lt] = i +'px';      
+       
+       if (this.exp.outline) this.exp.outline.setPosition();
+       
+}
+};
+
+hs.Expander = function(a, params, custom, contentType) {
+       if (document.readyState && hs.ie && !hs.isReady) {
+               hs.addEventListener(document, 'ready', function() {
+                       new hs.Expander(a, params, custom, contentType);
+               });
+               return;
+       } 
+       this.a = a;
+       this.custom = custom;
+       this.contentType = contentType || 'image';
+       this.isHtml = (contentType == 'html');
+       this.isImage = !this.isHtml;
+       
+       hs.continuePreloading = false;
+       this.overlays = [];
+       this.last = hs.last;
+       hs.last = null;
+       hs.init();
+       var key = this.key = hs.expanders.length;
+       // override inline parameters
+       for (var i = 0; i < hs.overrides.length; i++) {
+               var name = hs.overrides[i];
+               this[name] = params && typeof params[name] != 'undefined' ?
+                       params[name] : hs[name];
+       }
+       if (!this.src) this.src = a.href;
+       
+       // get thumb
+       var el = (params && params.thumbnailId) ? hs.$(params.thumbnailId) : a;
+       el = this.thumb = el.getElementsByTagName('img')[0] || el;
+       this.thumbsUserSetId = el.id || a.id;
+       if (!hs.fireEvent(this, 'onInit')) return true;
+       
+       // check if already open
+       for (var i = 0; i < hs.expanders.length; i++) {
+               if (hs.expanders[i] && hs.expanders[i].a == a 
+                       && !(this.last && this.transitions[1] == 'crossfade')) {
+                       hs.expanders[i].focus();
+                       return false;
+               }
+       }       
+
+       // cancel other
+       if (!hs.allowSimultaneousLoading) for (var i = 0; i < hs.expanders.length; i++) {
+               if (hs.expanders[i] && hs.expanders[i].thumb != el && !hs.expanders[i].onLoadStarted) {
+                       hs.expanders[i].cancelLoading();
+               }
+       }
+       hs.expanders[key] = this;
+       if (!hs.allowMultipleInstances && !hs.upcoming) {
+               if (hs.expanders[key-1]) hs.expanders[key-1].close();
+               if (typeof hs.focusKey != 'undefined' && hs.expanders[hs.focusKey])
+                       hs.expanders[hs.focusKey].close();
+       }
+       
+       // initiate metrics
+       this.el = el;
+       this.tpos = this.pageOrigin || hs.getPosition(el);
+       hs.getPageSize();
+       var x = this.x = new hs.Dimension(this, 'x');
+       x.calcThumb();
+       var y = this.y = new hs.Dimension(this, 'y');
+       y.calcThumb();
+       if (/area/i.test(el.tagName)) this.getImageMapAreaCorrection(el);
+       this.wrapper = hs.createElement(
+               'div', {
+                       id: 'highslide-wrapper-'+ this.key,
+                       className: 'highslide-wrapper '+ this.wrapperClassName
+               }, {
+                       visibility: 'hidden',
+                       position: 'absolute',
+                       zIndex: hs.zIndexCounter += 2
+               }, null, true );
+       
+       this.wrapper.onmouseover = this.wrapper.onmouseout = hs.wrapperMouseHandler;
+       if (this.contentType == 'image' && this.outlineWhileAnimating == 2)
+               this.outlineWhileAnimating = 0;
+       
+       // get the outline
+       if (!this.outlineType 
+               || (this.last && this.isImage && this.transitions[1] == 'crossfade')) {
+               this[this.contentType +'Create']();
+       
+       } else if (hs.pendingOutlines[this.outlineType]) {
+               this.connectOutline();
+               this[this.contentType +'Create']();
+       
+       } else {
+               this.showLoading();
+               var exp = this;
+               new hs.Outline(this.outlineType, 
+                       function () {
+                               exp.connectOutline();
+                               exp[exp.contentType +'Create']();
+                       } 
+               );
+       }
+       return true;
+};
+
+hs.Expander.prototype = {
+error : function(e) {
+       if (hs.debug) alert ('Line '+ e.lineNumber +': '+ e.message);
+       else window.location.href = this.src;
+},
+
+connectOutline : function() {
+       var outline = this.outline = hs.pendingOutlines[this.outlineType];
+       outline.exp = this;
+       outline.table.style.zIndex = this.wrapper.style.zIndex - 1;
+       hs.pendingOutlines[this.outlineType] = null;
+},
+
+showLoading : function() {
+       if (this.onLoadStarted || this.loading) return;
+       
+       this.loading = hs.loading;
+       var exp = this;
+       this.loading.onclick = function() {
+               exp.cancelLoading();
+       };
+       
+       
+       if (!hs.fireEvent(this, 'onShowLoading')) return;
+       var exp = this, 
+               l = this.x.get('loadingPos') +'px',
+               t = this.y.get('loadingPos') +'px';
+       if (!tgt && this.last && this.transitions[1] == 'crossfade') 
+               var tgt = this.last; 
+       if (tgt) {
+               l = tgt.x.get('loadingPosXfade') +'px';
+               t = tgt.y.get('loadingPosXfade') +'px';
+               this.loading.style.zIndex = hs.zIndexCounter++;
+       }
+       setTimeout(function () { 
+               if (exp.loading) hs.setStyles(exp.loading, { left: l, top: t, zIndex: hs.zIndexCounter++ })}
+       , 100);
+},
+
+imageCreate : function() {
+       var exp = this;
+       
+       var img = document.createElement('img');
+    this.content = img;
+    img.onload = function () {
+       if (hs.expanders[exp.key]) exp.contentLoaded(); 
+       };
+    if (hs.blockRightClick) img.oncontextmenu = function() { return false; };
+    img.className = 'highslide-image';
+    hs.setStyles(img, {
+       visibility: 'hidden',
+       display: 'block',
+       position: 'absolute',
+               maxWidth: '9999px',
+               zIndex: 3
+       });
+    img.title = hs.lang.restoreTitle;
+    if (hs.safari) hs.container.appendChild(img);
+    if (hs.ie && hs.flushImgSize) img.src = null;
+       img.src = this.src;
+       
+       this.showLoading();
+},
+
+htmlCreate : function () {
+       if (!hs.fireEvent(this, 'onBeforeGetContent')) return;
+       
+       this.content = hs.getCacheBinding(this.a);
+       if (!this.content) 
+               this.content = hs.getNode(this.contentId);
+       if (!this.content) 
+               this.content = hs.getSelfRendered();
+       this.getInline(['maincontent']);
+       if (this.maincontent) {
+               var body = hs.getElementByClass(this.content, 'div', 'highslide-body');
+               if (body) body.appendChild(this.maincontent);
+               this.maincontent.style.display = 'block';
+       }
+       hs.fireEvent(this, 'onAfterGetContent');
+       
+       var innerContent = this.innerContent = this.content;
+       
+       if (/(swf|iframe)/.test(this.objectType)) this.setObjContainerSize(innerContent);
+       
+       // the content tree
+       hs.container.appendChild(this.wrapper);
+       hs.setStyles( this.wrapper, { 
+               position: 'static',
+               padding: '0 '+ hs.marginRight +'px 0 '+ hs.marginLeft +'px'
+       });
+       this.content = hs.createElement(
+       'div', {
+               className: 'highslide-html' 
+       }, {
+                       position: 'relative',
+                       zIndex: 3,
+                       height: 0,
+                       overflow: 'hidden'
+               },
+               this.wrapper
+       );
+       this.mediumContent = hs.createElement('div', null, null, this.content, 1);
+       this.mediumContent.appendChild(innerContent);
+       
+       hs.setStyles (innerContent, { 
+               position: 'relative',
+               display: 'block',
+               direction: hs.lang.cssDirection || ''
+       });
+       if (this.width) innerContent.style.width = this.width +'px';
+       if (this.height) hs.setStyles(innerContent, {
+               height: this.height +'px',
+               overflow: 'hidden'
+       });
+       if (innerContent.offsetWidth < this.minWidth)
+               innerContent.style.width = this.minWidth +'px';
+               
+       
+    
+       if (this.objectType == 'ajax' && !hs.getCacheBinding(this.a)) {
+               this.showLoading();
+       var exp = this;
+       var ajax = new hs.Ajax(this.a, innerContent);
+               ajax.src = this.src;
+       ajax.onLoad = function () {     if (hs.expanders[exp.key]) exp.contentLoaded(); };
+       ajax.onError = function () { location.href = exp.src; };
+       ajax.run();
+       }
+    else
+    
+    if (this.objectType == 'iframe' && this.objectLoadTime == 'before') {
+               this.writeExtendedContent();
+       }
+    else
+       this.contentLoaded();
+},
+
+contentLoaded : function() {
+       try {   
+               if (!this.content) return;
+               this.content.onload = null;
+               if (this.onLoadStarted) return;
+               else this.onLoadStarted = true;
+               
+               var x = this.x, y = this.y;
+               
+               if (this.loading) {
+                       hs.setStyles(this.loading, { top: '-9999px' });
+                       this.loading = null;
+                       hs.fireEvent(this, 'onHideLoading');
+               }
+               if (this.isImage) {     
+                       x.full = this.content.width;
+                       y.full = this.content.height;
+                       
+                       hs.setStyles(this.content, {
+                               width: x.t +'px',
+                               height: y.t +'px'
+                       });
+                       this.wrapper.appendChild(this.content);
+                       hs.container.appendChild(this.wrapper);
+               } else if (this.htmlGetSize) this.htmlGetSize();
+               
+               x.calcBorders();
+               y.calcBorders();
+               
+               hs.setStyles (this.wrapper, {
+                       left: (x.tpos + x.tb - x.cb) +'px',
+                       top: (y.tpos + x.tb - y.cb) +'px'
+               });
+               
+               
+               this.initSlideshow();
+               this.getOverlays();
+               
+               var ratio = x.full / y.full;
+               x.calcExpanded();
+               this.justify(x);
+               
+               y.calcExpanded();
+               this.justify(y);
+               if (this.isHtml) this.htmlSizeOperations();
+               if (this.overlayBox) this.sizeOverlayBox(0, 1);
+
+               
+               if (this.allowSizeReduction) {
+                       if (this.isImage)
+                               this.correctRatio(ratio);
+                       else this.fitOverlayBox();
+                       var ss = this.slideshow;                        
+                       if (ss && this.last && ss.controls && ss.fixedControls) {
+                               var pos = ss.overlayOptions.position || '', p;
+                               for (var dim in hs.oPos) for (var i = 0; i < 5; i++) {
+                                       p = this[dim];
+                                       if (pos.match(hs.oPos[dim][i])) {
+                                               p.pos = this.last[dim].pos 
+                                                       + (this.last[dim].p1 - p.p1)
+                                                       + (this.last[dim].size - p.size) * [0, 0, .5, 1, 1][i];
+                                               if (ss.fixedControls == 'fit') {
+                                                       if (p.pos + p.size + p.p1 + p.p2 > p.scroll + p.clientSize - p.marginMax)
+                                                               p.pos = p.scroll + p.clientSize - p.size - p.marginMin - p.marginMax - p.p1 - p.p2;
+                                                       if (p.pos < p.scroll + p.marginMin) p.pos = p.scroll + p.marginMin; 
+                                               } 
+                                       }
+                               }
+                       }
+                       if (this.isImage && this.x.full > (this.x.imgSize || this.x.size)) {
+                               this.createFullExpand();
+                               if (this.overlays.length == 1) this.sizeOverlayBox();
+                       }
+               }
+               this.show();
+               
+       } catch (e) {
+               this.error(e);
+       }
+},
+
+
+setObjContainerSize : function(parent, auto) {
+       var c = hs.getElementByClass(parent, 'DIV', 'highslide-body');
+       if (/(iframe|swf)/.test(this.objectType)) {
+               if (this.objectWidth) c.style.width = this.objectWidth +'px';
+               if (this.objectHeight) c.style.height = this.objectHeight +'px';
+       }
+},
+
+writeExtendedContent : function () {
+       if (this.hasExtendedContent) return;
+       var exp = this;
+       this.body = hs.getElementByClass(this.innerContent, 'DIV', 'highslide-body');
+       if (this.objectType == 'iframe') {
+               this.showLoading();
+               var ruler = hs.clearing.cloneNode(1);
+               this.body.appendChild(ruler);
+               this.newWidth = this.innerContent.offsetWidth;
+               if (!this.objectWidth) this.objectWidth = ruler.offsetWidth;
+               var hDiff = this.innerContent.offsetHeight - this.body.offsetHeight,
+                       h = this.objectHeight || hs.page.height - hDiff - hs.marginTop - hs.marginBottom,
+                       onload = this.objectLoadTime == 'before' ? 
+                               ' onload="if (hs.expanders['+ this.key +']) hs.expanders['+ this.key +'].contentLoaded()" ' : '';
+               this.body.innerHTML += '<iframe name="hs'+ (new Date()).getTime() +'" frameborder="0" key="'+ this.key +'" '
+                       +' style="width:'+ this.objectWidth +'px; height:'+ h +'px" '
+                       + onload +' src="'+ this.src +'" ></iframe>';
+               this.ruler = this.body.getElementsByTagName('div')[0];
+               this.iframe = this.body.getElementsByTagName('iframe')[0];
+               
+               if (this.objectLoadTime == 'after') this.correctIframeSize();
+               
+       }
+       if (this.objectType == 'swf') {
+               this.body.id = this.body.id || 'hs-flash-id-' + this.key;
+               var a = this.swfOptions;
+               if (!a.params) a.params = {};
+               if (typeof a.params.wmode == 'undefined') a.params.wmode = 'transparent';
+               if (swfobject) swfobject.embedSWF(this.src, this.body.id, this.objectWidth, this.objectHeight, 
+                       a.version || '7', a.expressInstallSwfurl, a.flashvars, a.params, a.attributes);
+       }
+       this.hasExtendedContent = true;
+},
+htmlGetSize : function() {
+       if (this.iframe && !this.objectHeight) { // loadtime before
+               this.iframe.style.height = this.body.style.height = this.getIframePageHeight() +'px';
+       }
+       this.innerContent.appendChild(hs.clearing);
+       if (!this.x.full) this.x.full = this.innerContent.offsetWidth;
+    this.y.full = this.innerContent.offsetHeight;
+    this.innerContent.removeChild(hs.clearing);
+    if (hs.ie && this.newHeight > parseInt(this.innerContent.currentStyle.height)) { // ie css bug
+               this.newHeight = parseInt(this.innerContent.currentStyle.height);
+       }
+       hs.setStyles( this.wrapper, { position: 'absolute',     padding: '0'});
+       hs.setStyles( this.content, { width: this.x.t +'px', height: this.y.t +'px'});
+       
+},
+
+getIframePageHeight : function() {
+       var h;
+       try {
+               var doc = this.iDoc = this.iframe.contentDocument || this.iframe.contentWindow.document;
+               var clearing = doc.createElement('div');
+               clearing.style.clear = 'both';
+               doc.body.appendChild(clearing);
+               h = clearing.offsetTop;
+               if (hs.ie) h += parseInt(doc.body.currentStyle.marginTop) 
+                       + parseInt(doc.body.currentStyle.marginBottom) - 1;
+       } catch (e) { // other domain
+               h = 300;
+       }
+       return h;
+},
+correctIframeSize : function () {
+       var wDiff = this.innerContent.offsetWidth - this.ruler.offsetWidth;
+       hs.discardElement(this.ruler);
+       if (wDiff < 0) wDiff = 0;
+       
+       var hDiff = this.innerContent.offsetHeight - this.iframe.offsetHeight;
+       if (this.iDoc && !this.objectHeight && !this.height && this.y.size == this.y.full) try {
+               this.iDoc.body.style.overflow = 'hidden';
+       } catch (e) {}
+       hs.setStyles(this.iframe, { 
+               width: Math.abs(this.x.size - wDiff) +'px', 
+               height: Math.abs(this.y.size - hDiff) +'px'
+       });
+    hs.setStyles(this.body, { 
+               width: this.iframe.style.width, 
+       height: this.iframe.style.height
+       });
+       
+    this.scrollingContent = this.iframe;
+    this.scrollerDiv = this.scrollingContent;
+       
+},
+htmlSizeOperations : function () {
+       
+       this.setObjContainerSize(this.innerContent);
+       
+       
+       if (this.objectType == 'swf' && this.objectLoadTime == 'before') this.writeExtendedContent();   
+       
+    // handle minimum size
+    if (this.x.size < this.x.full && !this.allowWidthReduction) this.x.size = this.x.full;
+    if (this.y.size < this.y.full && !this.allowHeightReduction) this.y.size = this.y.full;
+       this.scrollerDiv = this.innerContent;
+    hs.setStyles(this.mediumContent, { 
+               position: 'relative',
+               width: this.x.size +'px'
+       });
+    hs.setStyles(this.innerContent, { 
+       border: 'none',
+       width: 'auto',
+       height: 'auto'
+    });
+       var node = hs.getElementByClass(this.innerContent, 'DIV', 'highslide-body');
+    if (node && !/(iframe|swf)/.test(this.objectType)) {
+       var cNode = node; // wrap to get true size
+       node = hs.createElement(cNode.nodeName, null, {overflow: 'hidden'}, null, true);
+       cNode.parentNode.insertBefore(node, cNode);
+       node.appendChild(hs.clearing); // IE6
+       node.appendChild(cNode);
+       
+       var wDiff = this.innerContent.offsetWidth - node.offsetWidth;
+       var hDiff = this.innerContent.offsetHeight - node.offsetHeight;
+               node.removeChild(hs.clearing);
+       
+       var kdeBugCorr = hs.safari || navigator.vendor == 'KDE' ? 1 : 0; // KDE repainting bug
+       hs.setStyles(node, { 
+                       width: (this.x.size - wDiff - kdeBugCorr) +'px', 
+                       height: (this.y.size - hDiff) +'px',
+                       overflow: 'auto', 
+                       position: 'relative' 
+               } 
+       );
+               if (kdeBugCorr && cNode.offsetHeight > node.offsetHeight)       {
+               node.style.width = (parseInt(node.style.width) + kdeBugCorr) + 'px';
+               }
+       this.scrollingContent = node;
+       this.scrollerDiv = this.scrollingContent;
+       }
+    if (this.iframe && this.objectLoadTime == 'before') this.correctIframeSize();
+    if (!this.scrollingContent && this.y.size < this.mediumContent.offsetHeight) this.scrollerDiv = this.content;
+       
+       if (this.scrollerDiv == this.content && !this.allowWidthReduction && !/(iframe|swf)/.test(this.objectType)) {
+               this.x.size += 17; // room for scrollbars
+       }
+       if (this.scrollerDiv && this.scrollerDiv.offsetHeight > this.scrollerDiv.parentNode.offsetHeight) {
+               setTimeout("try { hs.expanders["+ this.key +"].scrollerDiv.style.overflow = 'auto'; } catch(e) {}",
+                        hs.expandDuration);
+       }
+},
+
+getImageMapAreaCorrection : function(area) {
+       var c = area.coords.split(',');
+       for (var i = 0; i < c.length; i++) c[i] = parseInt(c[i]);
+       
+       if (area.shape.toLowerCase() == 'circle') {
+               this.x.tpos += c[0] - c[2];
+               this.y.tpos += c[1] - c[2];
+               this.x.t = this.y.t = 2 * c[2];
+       } else {
+               var maxX, maxY, minX = maxX = c[0], minY = maxY = c[1];
+               for (var i = 0; i < c.length; i++) {
+                       if (i % 2 == 0) {
+                               minX = Math.min(minX, c[i]);
+                               maxX = Math.max(maxX, c[i]);
+                       } else {
+                               minY = Math.min(minY, c[i]);
+                               maxY = Math.max(maxY, c[i]);
+                       }
+               }
+               this.x.tpos += minX;
+               this.x.t = maxX - minX;
+               this.y.tpos += minY;
+               this.y.t = maxY - minY;
+       }
+},
+justify : function (p, moveOnly) {
+       var tgtArr, tgt = p.target, dim = p == this.x ? 'x' : 'y';
+       
+       if (tgt && tgt.match(/ /)) {
+               tgtArr = tgt.split(' ');
+               tgt = tgtArr[0];
+       }
+       if (tgt && hs.$(tgt)) {
+               p.pos = hs.getPosition(hs.$(tgt))[dim];
+               if (tgtArr && tgtArr[1] && tgtArr[1].match(/^[-]?[0-9]+px$/)) 
+                       p.pos += parseInt(tgtArr[1]);
+               if (p.size < p.minSize) p.size = p.minSize;
+               
+       } else if (p.justify == 'auto' || p.justify == 'center') {
+       
+               var hasMovedMin = false;
+               
+               var allowReduce = p.exp.allowSizeReduction;
+               if (p.justify == 'center')
+                       p.pos = Math.round(p.scroll + (p.clientSize + p.marginMin - p.marginMax - p.get('wsize')) / 2);
+               else
+                       p.pos = Math.round(p.pos - ((p.get('wsize') - p.t) / 2));
+               if (p.pos < p.scroll + p.marginMin) {
+                       p.pos = p.scroll + p.marginMin;
+                       hasMovedMin = true;             
+               }
+               if (!moveOnly && p.size < p.minSize) {
+                       p.size = p.minSize;
+                       allowReduce = false;
+               }
+               if (p.pos + p.get('wsize') > p.scroll + p.clientSize - p.marginMax) {
+                       if (!moveOnly && hasMovedMin && allowReduce) {
+                               p.size = Math.min(p.size, p.get(dim == 'y' ? 'fitsize' : 'maxsize'));
+                       } else if (p.get('wsize') < p.get('fitsize')) {
+                               p.pos = p.scroll + p.clientSize - p.marginMax - p.get('wsize');
+                       } else { // image larger than viewport
+                               p.pos = p.scroll + p.marginMin;
+                               if (!moveOnly && allowReduce) p.size = p.get(dim == 'y' ? 'fitsize' : 'maxsize');
+                       }                       
+               }
+               
+               if (!moveOnly && p.size < p.minSize) {
+                       p.size = p.minSize;
+                       allowReduce = false;
+               }
+               
+       
+       } else if (p.justify == 'max') {
+               p.pos = Math.floor(p.pos - p.size + p.t);
+       }
+       
+               
+       if (p.pos < p.marginMin) {
+               var tmpMin = p.pos;
+               p.pos = p.marginMin; 
+               
+               if (allowReduce && !moveOnly) p.size = p.size - (p.pos - tmpMin);
+               
+       }
+},
+
+correctRatio : function(ratio) {
+       var x = this.x, 
+               y = this.y,
+               changed = false,
+               xSize = Math.min(x.full, x.size),
+               ySize = Math.min(y.full, y.size),
+               useBox = (this.useBox || hs.padToMinWidth);
+       
+       if (xSize / ySize > ratio) { // width greater
+               xSize = ySize * ratio;
+               if (xSize < x.minSize) { // below minWidth
+                       xSize = x.minSize;
+                       ySize = xSize / ratio;
+               }
+               changed = true;
+       
+       } else if (xSize / ySize < ratio) { // height greater
+               ySize = xSize / ratio;
+               changed = true;
+       }
+       
+       if (hs.padToMinWidth && x.full < x.minSize) {
+               x.imgSize = x.full;
+               y.size = y.imgSize = y.full;
+       } else if (this.useBox) {
+               x.imgSize = xSize;
+               y.imgSize = ySize;
+       } else {
+               x.size = xSize;
+               y.size = ySize;
+       }
+       changed = this.fitOverlayBox(useBox ? null : ratio, changed);
+       if (useBox && y.size < y.imgSize) {
+               y.imgSize = y.size;
+               x.imgSize = y.size * ratio;
+       }
+       if (changed || useBox) {
+               x.pos = x.tpos - x.cb + x.tb;
+               x.minSize = x.size;
+               this.justify(x, true);
+       
+               y.pos = y.tpos - y.cb + y.tb;
+               y.minSize = y.size;
+               this.justify(y, true);
+               if (this.overlayBox) this.sizeOverlayBox();
+       }
+},
+fitOverlayBox : function(ratio, changed) {
+       var x = this.x, y = this.y;
+       if (this.overlayBox && (this.isImage || this.allowHeightReduction)) {
+               while (y.size > this.minHeight && x.size > this.minWidth 
+                               &&  y.get('wsize') > y.get('fitsize')) {
+                       y.size -= 10;
+                       if (ratio) x.size = y.size * ratio;
+                       this.sizeOverlayBox(0, 1);
+                       changed = true;
+               }
+       }
+       return changed;
+},
+
+reflow : function () {
+       if (this.scrollerDiv) {
+               var h = /iframe/i.test(this.scrollerDiv.tagName) ? (this.getIframePageHeight() + 1) +'px' : 'auto';
+               if (this.body) this.body.style.height = h;
+               this.scrollerDiv.style.height = h;
+               this.y.setSize(this.innerContent.offsetHeight);
+       }
+},
+
+show : function () {
+       var x = this.x, y = this.y;
+       this.doShowHide('hidden');
+       hs.fireEvent(this, 'onBeforeExpand');
+       if (this.slideshow && this.slideshow.thumbstrip) this.slideshow.thumbstrip.selectThumb();
+       
+       // Apply size change
+       this.changeSize(
+               1, {
+                       wrapper: {
+                               width : x.get('wsize'),
+                               height : y.get('wsize'),
+                               left: x.pos,
+                               top: y.pos
+                       },
+                       content: {
+                               left: x.p1 + x.get('imgPad'),
+                               top: y.p1 + y.get('imgPad'),
+                               width:x.imgSize ||x.size,
+                               height:y.imgSize ||y.size
+                       }
+               },
+               hs.expandDuration
+       );
+},
+
+changeSize : function(up, to, dur) {
+       // transition
+       var trans = this.transitions,
+       other = up ? (this.last ? this.last.a : null) : hs.upcoming,
+       t = (trans[1] && other 
+                       && hs.getParam(other, 'transitions')[1] == trans[1]) ?
+               trans[1] : trans[0];
+               
+       if (this[t] && t != 'expand') {
+               this[t](up, to);
+               return;
+       }
+       
+       if (this.outline && !this.outlineWhileAnimating) {
+               if (up) this.outline.setPosition();
+               else this.outline.destroy(
+                               (this.isHtml && this.preserveContent));
+       }
+       
+       
+       if (!up) this.destroyOverlays();
+       
+       var exp = this,
+               x = exp.x,
+               y = exp.y,
+               easing = this.easing;
+       if (!up) easing = this.easingClose || easing;
+       var after = up ?
+               function() {
+                               
+                       if (exp.outline) exp.outline.table.style.visibility = "visible";
+                       setTimeout(function() {
+                               exp.afterExpand();
+                       }, 50);
+               } :
+               function() {
+                       exp.afterClose();
+               };
+       if (up) hs.setStyles( this.wrapper, {
+               width: x.t +'px',
+               height: y.t +'px'
+       });
+       if (up && this.isHtml) {
+               hs.setStyles(this.wrapper, {
+                       left: (x.tpos - x.cb + x.tb) +'px',
+                       top: (y.tpos - y.cb + y.tb) +'px'
+               });
+       }
+       if (this.fadeInOut) {
+               hs.setStyles(this.wrapper, { opacity: up ? 0 : 1 });
+               hs.extend(to.wrapper, { opacity: up });
+       }
+       hs.animate( this.wrapper, to.wrapper, {
+               duration: dur,
+               easing: easing,
+               step: function(val, args) {
+                       if (exp.outline && exp.outlineWhileAnimating && args.prop == 'top') {
+                               var fac = up ? args.pos : 1 - args.pos;
+                               var pos = {
+                                       w: x.t + (x.get('wsize') - x.t) * fac,
+                                       h: y.t + (y.get('wsize') - y.t) * fac,
+                                       x: x.tpos + (x.pos - x.tpos) * fac,
+                                       y: y.tpos + (y.pos - y.tpos) * fac
+                               };
+                               exp.outline.setPosition(pos, 0, 1);                             
+                       }
+                       if (exp.isHtml) {       
+                               if (args.prop == 'left') 
+                                       exp.mediumContent.style.left = (x.pos - val) +'px';
+                               if (args.prop == 'top') 
+                                       exp.mediumContent.style.top = (y.pos - val) +'px';
+                       }
+               }
+       });
+       hs.animate( this.content, to.content, dur, easing, after);
+       if (up) {
+               this.wrapper.style.visibility = 'visible';
+               this.content.style.visibility = 'visible';
+               if (this.isHtml) this.innerContent.style.visibility = 'visible';
+       }
+},
+
+
+
+fade : function(up, to) {
+       this.outlineWhileAnimating = false;
+       var exp = this, t = up ? hs.expandDuration : 0;
+       
+       if (up) {
+               hs.animate(this.wrapper, to.wrapper, 0);
+               hs.setStyles(this.wrapper, { opacity: 0, visibility: 'visible' });
+               hs.animate(this.content, to.content, 0);
+               this.content.style.visibility = 'visible';
+
+               hs.animate(this.wrapper, { opacity: 1 }, t, null, 
+                       function() { exp.afterExpand(); });
+       }
+       
+       if (this.outline) {
+               this.outline.table.style.zIndex = this.wrapper.style.zIndex;
+               var dir = up || -1, 
+                       offset = this.outline.offset,
+                       startOff = up ? 3 : offset,
+                       endOff = up? offset : 3;
+               for (var i = startOff; dir * i <= dir * endOff; i += dir, t += 25) {
+                       (function() {
+                               var o = up ? endOff - i : startOff - i;
+                               setTimeout(function() {
+                                       exp.outline.setPosition(0, o, 1);
+                               }, t);
+                       })();
+               }
+       }
+       
+       
+       if (up) {}//setTimeout(function() { exp.afterExpand(); }, t+50);
+       else {
+               setTimeout( function() {
+                       if (exp.outline) exp.outline.destroy(exp.preserveContent);
+                       
+                       exp.destroyOverlays();
+       
+                       hs.animate( exp.wrapper, { opacity: 0 }, hs.restoreDuration, null, function(){
+                               exp.afterClose();
+                       });
+               }, t);          
+       }
+},
+crossfade : function (up, to, from) {
+       if (!up) return;
+       var exp = this, 
+               last = this.last,
+               x = this.x,
+               y = this.y,
+               lastX = last.x,
+               lastY = last.y,
+               wrapper = this.wrapper,
+               content = this.content,
+               overlayBox = this.overlayBox;
+       hs.removeEventListener(document, 'mousemove', hs.dragHandler);
+       
+       hs.setStyles(content, { 
+               width: (x.imgSize || x.size) +'px', 
+               height: (y.imgSize || y.size) +'px'             
+       });
+       if (overlayBox) overlayBox.style.overflow = 'visible';
+       this.outline = last.outline;
+       if (this.outline) this.outline.exp = exp;
+       last.outline = null;
+       var fadeBox = hs.createElement('div', {
+                       className: 'highslide-'+ this.contentType
+               }, { 
+                       position: 'absolute', 
+                       zIndex: 4,
+                       overflow: 'hidden',
+                       display: 'none'
+               }
+       );
+       var names = { oldImg: last, newImg: this };
+       for (var n in names) {  
+               this[n] = names[n].content.cloneNode(1);
+               hs.setStyles(this[n], {
+                       position: 'absolute',
+                       border: 0,
+                       visibility: 'visible'
+               });
+               fadeBox.appendChild(this[n]);
+       }
+       wrapper.appendChild(fadeBox);
+       if (this.isHtml) hs.setStyles(this.mediumContent, { 
+               left: 0,
+               top: 0
+       });
+       if (overlayBox) {
+               overlayBox.className = '';
+               wrapper.appendChild(overlayBox);
+       }
+       fadeBox.style.display = '';
+       last.content.style.display = 'none';
+       
+       
+       if (hs.safari) {
+               var match = navigator.userAgent.match(/Safari\/([0-9]{3})/);
+               if (match && parseInt(match[1]) < 525) this.wrapper.style.visibility = 'visible';
+       }
+       hs.animate(wrapper, {
+               width: x.size
+       }, {
+               duration: hs.transitionDuration, 
+               step: function(val, args) {
+                       var pos = args.pos,
+                               invPos = 1 - pos;
+                       var prop,
+                               size = {}, 
+                               props = ['pos', 'size', 'p1', 'p2'];
+                       for (var n in props) {
+                               prop = props[n];
+                               size['x'+ prop] = Math.round(invPos * lastX[prop] + pos * x[prop]);
+                               size['y'+ prop] = Math.round(invPos * lastY[prop] + pos * y[prop]);
+                               size.ximgSize = Math.round(
+                                       invPos * (lastX.imgSize || lastX.size) + pos * (x.imgSize || x.size));
+                               size.ximgPad = Math.round(invPos * lastX.get('imgPad') + pos * x.get('imgPad'));
+                               size.yimgSize = Math.round(
+                                       invPos * (lastY.imgSize || lastY.size) + pos * (y.imgSize || y.size));
+                               size.yimgPad = Math.round(invPos * lastY.get('imgPad') + pos * y.get('imgPad'));
+                       }
+                       if (exp.outline) exp.outline.setPosition({ 
+                               x: size.xpos, 
+                               y: size.ypos, 
+                               w: size.xsize + size.xp1 + size.xp2 + 2 * x.cb, 
+                               h: size.ysize + size.yp1 + size.yp2 + 2 * y.cb
+                       });
+                       last.wrapper.style.clip = 'rect('
+                               + (size.ypos - lastY.pos)+'px, '
+                               + (size.xsize + size.xp1 + size.xp2 + size.xpos + 2 * lastX.cb - lastX.pos) +'px, '
+                               + (size.ysize + size.yp1 + size.yp2 + size.ypos + 2 * lastY.cb - lastY.pos) +'px, '
+                               + (size.xpos - lastX.pos)+'px)';
+                               
+                       hs.setStyles(content, {
+                               top: (size.yp1 + y.get('imgPad')) +'px',
+                               left: (size.xp1 + x.get('imgPad')) +'px',
+                               marginTop: (y.pos - size.ypos) +'px',
+                               marginLeft: (x.pos - size.xpos) +'px'
+                       });
+                       hs.setStyles(wrapper, {
+                               top: size.ypos +'px',
+                               left: size.xpos +'px',
+                               width: (size.xp1 + size.xp2 + size.xsize + 2 * x.cb)+ 'px',
+                               height: (size.yp1 + size.yp2 + size.ysize + 2 * y.cb) + 'px'
+                       });
+                       hs.setStyles(fadeBox, {
+                               width: (size.ximgSize || size.xsize) + 'px',
+                               height: (size.yimgSize || size.ysize) +'px',
+                               left: (size.xp1 + size.ximgPad)  +'px',
+                               top: (size.yp1 + size.yimgPad) +'px',
+                               visibility: 'visible'
+                       });
+                       
+                       hs.setStyles(exp.oldImg, {
+                               top: (lastY.pos - size.ypos + lastY.p1 - size.yp1 + lastY.get('imgPad') - size.yimgPad)+'px',
+                               left: (lastX.pos - size.xpos + lastX.p1 - size.xp1 + lastX.get('imgPad') - size.ximgPad)+'px'
+                       });             
+                       
+                       hs.setStyles(exp.newImg, {
+                               opacity: pos,
+                               top: (y.pos - size.ypos + y.p1 - size.yp1 + y.get('imgPad') - size.yimgPad) +'px',
+                               left: (x.pos - size.xpos + x.p1 - size.xp1 + x.get('imgPad') - size.ximgPad) +'px'
+                       });
+                       if (overlayBox) hs.setStyles(overlayBox, {
+                               width: size.xsize + 'px',
+                               height: size.ysize +'px',
+                               left: (size.xp1 + x.cb)  +'px',
+                               top: (size.yp1 + y.cb) +'px'
+                       });
+               },
+               complete: function () {
+                       wrapper.style.visibility = content.style.visibility = 'visible';
+                       content.style.display = 'block';
+                       hs.discardElement(fadeBox);
+                       exp.afterExpand();
+                       last.afterClose();
+                       exp.last = null;
+               }
+               
+       });
+},
+reuseOverlay : function(o, el) {
+       if (!this.last) return false;
+       for (var i = 0; i < this.last.overlays.length; i++) {
+               var oDiv = hs.$('hsId'+ this.last.overlays[i]);
+               if (oDiv && oDiv.hsId == o.hsId) {
+                       this.genOverlayBox();
+                       oDiv.reuse = this.key;
+                       hs.push(this.overlays, this.last.overlays[i]);
+                       return true;
+               }
+       }
+       return false;
+},
+
+
+afterExpand : function() {
+       this.isExpanded = true;
+       
+       this.a.className += ' highslide-active-anchor'; 
+       this.focus();
+       
+       if (this.isHtml && this.objectLoadTime == 'after') this.writeExtendedContent();
+       if (this.iframe) {
+               try {
+                       var exp = this,
+                               doc = this.iframe.contentDocument || this.iframe.contentWindow.document;
+                       hs.addEventListener(doc, 'mousedown', function () {
+                               if (hs.focusKey != exp.key) exp.focus();
+                       });
+               } catch(e) {}
+               if (hs.ie && typeof this.isClosing != 'boolean') // first open 
+                       this.iframe.style.width = (this.objectWidth - 1) +'px'; // hasLayout
+       }
+       if (this.dimmingOpacity) hs.dim(this);
+       if (hs.upcoming && hs.upcoming == this.a) hs.upcoming = null;
+       this.prepareNextOutline();
+       var p = hs.page, mX = hs.mouse.x + p.scrollLeft, mY = hs.mouse.y + p.scrollTop;
+       this.mouseIsOver = this.x.pos < mX && mX < this.x.pos + this.x.get('wsize')
+               && this.y.pos < mY && mY < this.y.pos + this.y.get('wsize');    
+       if (this.overlayBox) this.showOverlays();
+       hs.fireEvent(this, 'onAfterExpand');
+       
+},
+
+
+prepareNextOutline : function() {
+       var key = this.key;
+       var outlineType = this.outlineType;
+       new hs.Outline(outlineType, 
+               function () { try { hs.expanders[key].preloadNext(); } catch (e) {} });
+},
+
+
+preloadNext : function() {
+       var next = this.getAdjacentAnchor(1);
+       if (next && next.onclick.toString().match(/hs\.expand/)) 
+               var img = hs.createElement('img', { src: hs.getSrc(next) });
+},
+
+
+getAdjacentAnchor : function(op) {
+       var current = this.getAnchorIndex(), as = hs.anchors.groups[this.slideshowGroup || 'none'];
+       
+       /*< ? if ($cfg->slideshow) : ?>s*/
+       if (!as[current + op] && this.slideshow && this.slideshow.repeat) {
+               if (op == 1) return as[0];
+               else if (op == -1) return as[as.length-1];
+       }
+       /*< ? endif ?>s*/
+       return as[current + op] || null;
+},
+
+getAnchorIndex : function() {
+       var arr = hs.getAnchors().groups[this.slideshowGroup || 'none'];
+       if (arr) for (var i = 0; i < arr.length; i++) {
+               if (arr[i] == this.a) return i; 
+       }
+       return null;
+},
+
+
+getNumber : function() {
+       if (this[this.numberPosition]) {
+               var arr = hs.anchors.groups[this.slideshowGroup || 'none'];
+               if (arr) {
+                       var s = hs.lang.number.replace('%1', this.getAnchorIndex() + 1).replace('%2', arr.length);
+                       this[this.numberPosition].innerHTML = 
+                               '<div class="highslide-number">'+ s +'</div>'+ this[this.numberPosition].innerHTML;
+               }
+       }
+},
+initSlideshow : function() {
+       if (!this.last) {
+               for (var i = 0; i < hs.slideshows.length; i++) {
+                       var ss = hs.slideshows[i], sg = ss.slideshowGroup;
+                       if (typeof sg == 'undefined' || sg === null || sg === this.slideshowGroup) 
+                               this.slideshow = new hs.Slideshow(this.key, ss);
+               } 
+       } else {
+               this.slideshow = this.last.slideshow;
+       }
+       var ss = this.slideshow;
+       if (!ss) return;
+       var key = ss.expKey = this.key;
+       
+       ss.checkFirstAndLast();
+       ss.disable('full-expand');
+       if (ss.controls) {
+               this.createOverlay(hs.extend(ss.overlayOptions || {}, {
+                       overlayId: ss.controls,
+                       hsId: 'controls',
+                       zIndex: 5
+               }));
+       }
+       if (ss.thumbstrip) ss.thumbstrip.add(this);
+       if (!this.last && this.autoplay) ss.play(true);
+       if (ss.autoplay) {
+               ss.autoplay = setTimeout(function() {
+                       hs.next(key);
+               }, (ss.interval || 500));
+       }
+},
+
+cancelLoading : function() {
+       hs.discardElement (this.wrapper);
+       hs.expanders[this.key] = null;
+       if (hs.upcoming == this.a) hs.upcoming = null;
+       hs.undim(this.key);
+       if (this.loading) hs.loading.style.left = '-9999px';
+       hs.fireEvent(this, 'onHideLoading');
+},
+
+writeCredits : function () {
+       if (this.credits) return;
+       this.credits = hs.createElement('a', {
+               href: hs.creditsHref,
+               target: hs.creditsTarget,
+               className: 'highslide-credits',
+               innerHTML: hs.lang.creditsText,
+               title: hs.lang.creditsTitle
+       });
+       this.createOverlay({ 
+               overlayId: this.credits, 
+               position: this.creditsPosition || 'top left', 
+               hsId: 'credits' 
+       });
+},
+
+getInline : function(types, addOverlay) {
+       for (var i = 0; i < types.length; i++) {
+               var type = types[i], s = null;
+               if (type == 'caption' && !hs.fireEvent(this, 'onBeforeGetCaption')) return;
+               else if (type == 'heading' && !hs.fireEvent(this, 'onBeforeGetHeading')) return;
+               if (!this[type +'Id'] && this.thumbsUserSetId)  
+                       this[type +'Id'] = type +'-for-'+ this.thumbsUserSetId;
+               if (this[type +'Id']) this[type] = hs.getNode(this[type +'Id']);
+               if (!this[type] && !this[type +'Text'] && this[type +'Eval']) try {
+                       s = eval(this[type +'Eval']);
+               } catch (e) {}
+               if (!this[type] && this[type +'Text']) {
+                       s = this[type +'Text'];
+               }
+               if (!this[type] && !s) {
+                       this[type] = hs.getNode(this.a['_'+ type + 'Id']);
+                       if (!this[type]) {
+                               var next = this.a.nextSibling;
+                               while (next && !hs.isHsAnchor(next)) {
+                                       if ((new RegExp('highslide-'+ type)).test(next.className || null)) {
+                                               if (!next.id) this.a['_'+ type + 'Id'] = next.id = 'hsId'+ hs.idCounter++;
+                                               this[type] = hs.getNode(next.id);
+                                               break;
+                                       }
+                                       next = next.nextSibling;
+                               }
+                       }
+               }
+               if (!this[type] && !s && this.numberPosition == type) s = '\n';
+               
+               if (!this[type] && s) this[type] = hs.createElement('div', 
+                               { className: 'highslide-'+ type, innerHTML: s } );
+               
+               if (addOverlay && this[type]) {
+                       var o = { position: (type == 'heading') ? 'above' : 'below' };
+                       for (var x in this[type+'Overlay']) o[x] = this[type+'Overlay'][x];
+                       o.overlayId = this[type];
+                       this.createOverlay(o);
+               }
+       }
+},
+
+
+// on end move and resize
+doShowHide : function(visibility) {
+       if (hs.hideSelects) this.showHideElements('SELECT', visibility);
+       if (hs.hideIframes) this.showHideElements('IFRAME', visibility);
+       if (hs.geckoMac) this.showHideElements('*', visibility);
+},
+showHideElements : function (tagName, visibility) {
+       var els = document.getElementsByTagName(tagName);
+       var prop = tagName == '*' ? 'overflow' : 'visibility';
+       for (var i = 0; i < els.length; i++) {
+               if (prop == 'visibility' || (document.defaultView.getComputedStyle(
+                               els[i], "").getPropertyValue('overflow') == 'auto'
+                               || els[i].getAttribute('hidden-by') != null)) {
+                       var hiddenBy = els[i].getAttribute('hidden-by');
+                       if (visibility == 'visible' && hiddenBy) {
+                               hiddenBy = hiddenBy.replace('['+ this.key +']', '');
+                               els[i].setAttribute('hidden-by', hiddenBy);
+                               if (!hiddenBy) els[i].style[prop] = els[i].origProp;
+                       } else if (visibility == 'hidden') { // hide if behind
+                               var elPos = hs.getPosition(els[i]);
+                               elPos.w = els[i].offsetWidth;
+                               elPos.h = els[i].offsetHeight;
+                               if (!this.dimmingOpacity) { // hide all if dimming
+                               
+                                       var clearsX = (elPos.x + elPos.w < this.x.get('opos') 
+                                               || elPos.x > this.x.get('opos') + this.x.get('osize'));
+                                       var clearsY = (elPos.y + elPos.h < this.y.get('opos') 
+                                               || elPos.y > this.y.get('opos') + this.y.get('osize'));
+                               }
+                               var wrapperKey = hs.getWrapperKey(els[i]);
+                               if (!clearsX && !clearsY && wrapperKey != this.key) { // element falls behind image
+                                       if (!hiddenBy) {
+                                               els[i].setAttribute('hidden-by', '['+ this.key +']');
+                                               els[i].origProp = els[i].style[prop];
+                                               els[i].style[prop] = 'hidden';
+                                               
+                                       } else if (hiddenBy.indexOf('['+ this.key +']') == -1) {
+                                               els[i].setAttribute('hidden-by', hiddenBy + '['+ this.key +']');
+                                       }
+                               } else if ((hiddenBy == '['+ this.key +']' || hs.focusKey == wrapperKey)
+                                               && wrapperKey != this.key) { // on move
+                                       els[i].setAttribute('hidden-by', '');
+                                       els[i].style[prop] = els[i].origProp || '';
+                               } else if (hiddenBy && hiddenBy.indexOf('['+ this.key +']') > -1) {
+                                       els[i].setAttribute('hidden-by', hiddenBy.replace('['+ this.key +']', ''));
+                               }
+                                               
+                       }
+               }
+       }
+},
+
+focus : function() {
+       this.wrapper.style.zIndex = hs.zIndexCounter += 2;
+       // blur others
+       for (var i = 0; i < hs.expanders.length; i++) {
+               if (hs.expanders[i] && i == hs.focusKey) {
+                       var blurExp = hs.expanders[i];
+                       blurExp.content.className += ' highslide-'+ blurExp.contentType +'-blur';
+                       if (blurExp.isImage) {
+                               blurExp.content.style.cursor = hs.ie ? 'hand' : 'pointer';
+                               blurExp.content.title = hs.lang.focusTitle;     
+                       }       
+                       hs.fireEvent(blurExp, 'onBlur');
+               }
+       }
+       
+       // focus this
+       if (this.outline) this.outline.table.style.zIndex 
+               = this.wrapper.style.zIndex - 1;
+       this.content.className = 'highslide-'+ this.contentType;
+       if (this.isImage) {
+               this.content.title = hs.lang.restoreTitle;
+               
+               if (hs.restoreCursor) {
+                       hs.styleRestoreCursor = window.opera ? 'pointer' : 'url('+ hs.graphicsDir + hs.restoreCursor +'), pointer';
+                       if (hs.ie && hs.uaVersion < 6) hs.styleRestoreCursor = 'hand';
+                       this.content.style.cursor = hs.styleRestoreCursor;
+               }
+       }
+       hs.focusKey = this.key; 
+       hs.addEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);    
+       hs.fireEvent(this, 'onFocus');  
+},
+moveTo: function(x, y) {
+       this.x.setPos(x);
+       this.y.setPos(y);
+},
+resize : function (e) {
+       var w, h, r = e.width / e.height;
+       w = Math.max(e.width + e.dX, Math.min(this.minWidth, this.x.full));
+       if (this.isImage && Math.abs(w - this.x.full) < 12) w = this.x.full;
+       h = this.isHtml ? e.height + e.dY : w / r;
+       if (h < Math.min(this.minHeight, this.y.full)) {
+               h = Math.min(this.minHeight, this.y.full);
+               if (this.isImage) w = h * r;
+       }
+       this.resizeTo(w, h);
+},
+resizeTo: function(w, h) {
+       this.y.setSize(h);
+       this.x.setSize(w);
+       this.wrapper.style.height = this.y.get('wsize') +'px';
+},
+
+close : function() {
+       if (this.isClosing || !this.isExpanded) return;
+       if (this.transitions[1] == 'crossfade' && hs.upcoming) {
+               hs.getExpander(hs.upcoming).cancelLoading();
+               hs.upcoming = null;
+       }
+       if (!hs.fireEvent(this, 'onBeforeClose')) return;
+       this.isClosing = true;
+       if (this.slideshow && !hs.upcoming) this.slideshow.pause();
+       
+       hs.removeEventListener(document, window.opera ? 'keypress' : 'keydown', hs.keyHandler);
+       
+       try {
+               if (this.isHtml) this.htmlPrepareClose();
+               this.content.style.cursor = 'default';
+               this.changeSize(
+                       0, {
+                               wrapper: {
+                                       width : this.x.t,
+                                       height : this.y.t,
+                                       left: this.x.tpos - this.x.cb + this.x.tb,
+                                       top: this.y.tpos - this.y.cb + this.y.tb
+                               },
+                               content: {
+                                       left: 0,
+                                       top: 0,
+                                       width: this.x.t,
+                                       height: this.y.t
+                               }
+                       }, hs.restoreDuration
+               );
+       } catch (e) { this.afterClose(); }
+},
+
+htmlPrepareClose : function() {
+       if (hs.geckoMac) { // bad redraws
+               if (!hs.mask) hs.mask = hs.createElement('div', null, 
+                       { position: 'absolute' }, hs.container);
+               hs.setStyles(hs.mask, { width: this.x.size +'px', height: this.y.size +'px', 
+                       left: this.x.pos +'px', top: this.y.pos +'px', display: 'block' });                     
+       }
+       if (this.objectType == 'swf') try { hs.$(this.body.id).StopPlay(); } catch (e) {}
+       
+       if (this.objectLoadTime == 'after' && !this.preserveContent) this.destroyObject();              
+       if (this.scrollerDiv && this.scrollerDiv != this.scrollingContent) 
+               this.scrollerDiv.style.overflow = 'hidden';
+},
+
+destroyObject : function () {
+       if (hs.ie && this.iframe)
+               try { this.iframe.contentWindow.document.body.innerHTML = ''; } catch (e) {}
+       if (this.objectType == 'swf') swfobject.removeSWF(this.body.id);
+       this.body.innerHTML = '';
+},
+
+sleep : function() {
+       if (this.outline) this.outline.table.style.display = 'none';
+       this.releaseMask = null;
+       this.wrapper.style.display = 'none';
+       hs.push(hs.sleeping, this);
+},
+
+awake : function() {try {
+       
+       hs.expanders[this.key] = this;
+       
+       if (!hs.allowMultipleInstances &&hs.focusKey != this.key) {     
+               try { hs.expanders[hs.focusKey].close(); } catch (e){}
+       }
+       
+       var z = hs.zIndexCounter++, stl = { display: '', zIndex: z };
+       hs.setStyles (this.wrapper, stl);
+       this.isClosing = false;
+       
+       var o = this.outline || 0;
+       if (o) {
+               if (!this.outlineWhileAnimating) stl.visibility = 'hidden';
+               hs.setStyles (o.table, stl);            
+       }
+       if (this.slideshow) {
+               this.initSlideshow();
+       }
+               
+       this.show();
+} catch (e) {}
+
+
+},
+
+createOverlay : function (o) {
+       var el = o.overlayId, 
+               relToVP = (o.relativeTo == 'viewport' && !/panel$/.test(o.position));
+       if (typeof el == 'string') el = hs.getNode(el);
+       if (o.html) el = hs.createElement('div', { innerHTML: o.html });
+       if (!el || typeof el == 'string') return;
+       if (!hs.fireEvent(this, 'onCreateOverlay', { overlay: el })) return;
+       el.style.display = 'block';
+       o.hsId = o.hsId || o.overlayId; 
+       if (this.transitions[1] == 'crossfade' && this.reuseOverlay(o, el)) return;
+       this.genOverlayBox();
+       var width = o.width && /^[0-9]+(px|%)$/.test(o.width) ? o.width : 'auto';
+       if (/^(left|right)panel$/.test(o.position) && !/^[0-9]+px$/.test(o.width)) width = '200px';
+       var overlay = hs.createElement(
+               'div', {
+                       id: 'hsId'+ hs.idCounter++,
+                       hsId: o.hsId
+               }, {
+                       position: 'absolute',
+                       visibility: 'hidden',
+                       width: width,
+                       direction: hs.lang.cssDirection || '',
+                       opacity: 0
+               },
+               relToVP ? hs.viewport :this.overlayBox,
+               true
+       );
+       if (relToVP) overlay.hsKey = this.key;
+       
+       overlay.appendChild(el);
+       hs.extend(overlay, {
+               opacity: 1,
+               offsetX: 0,
+               offsetY: 0,
+               dur: (o.fade === 0 || o.fade === false || (o.fade == 2 && hs.ie)) ? 0 : 250
+       });
+       hs.extend(overlay, o);
+       
+               
+       if (this.gotOverlays) {
+               this.positionOverlay(overlay);
+               if (!overlay.hideOnMouseOut || this.mouseIsOver) 
+                       hs.animate(overlay, { opacity: overlay.opacity }, overlay.dur);
+       }
+       hs.push(this.overlays, hs.idCounter - 1);
+},
+positionOverlay : function(overlay) {
+       var p = overlay.position || 'middle center',
+               relToVP = (overlay.relativeTo == 'viewport'),
+               offX = overlay.offsetX,
+               offY = overlay.offsetY;
+       if (relToVP) {
+               hs.viewport.style.display = 'block';
+               overlay.hsKey = this.key;
+               if (overlay.offsetWidth > overlay.parentNode.offsetWidth)
+                       overlay.style.width = '100%';
+       } else
+       if (overlay.parentNode != this.overlayBox) this.overlayBox.appendChild(overlay);
+       if (/left$/.test(p)) overlay.style.left = offX +'px'; 
+       
+       if (/center$/.test(p))  hs.setStyles (overlay, { 
+               left: '50%',
+               marginLeft: (offX - Math.round(overlay.offsetWidth / 2)) +'px'
+       });     
+       
+       if (/right$/.test(p)) overlay.style.right = - offX +'px';
+               
+       if (/^leftpanel$/.test(p)) { 
+               hs.setStyles(overlay, {
+                       right: '100%',
+                       marginRight: this.x.cb +'px',
+                       top: - this.y.cb +'px',
+                       bottom: - this.y.cb +'px',
+                       overflow: 'auto'
+               });              
+               this.x.p1 = overlay.offsetWidth;
+       
+       } else if (/^rightpanel$/.test(p)) {
+               hs.setStyles(overlay, {
+                       left: '100%',
+                       marginLeft: this.x.cb +'px',
+                       top: - this.y.cb +'px',
+                       bottom: - this.y.cb +'px',
+                       overflow: 'auto'
+               });
+               this.x.p2 = overlay.offsetWidth;
+       }
+       var parOff = overlay.parentNode.offsetHeight;
+       overlay.style.height = 'auto';
+       if (relToVP && overlay.offsetHeight > parOff)
+               overlay.style.height = hs.ieLt7 ? parOff +'px' : '100%';
+
+       if (/^top/.test(p)) overlay.style.top = offY +'px'; 
+       if (/^middle/.test(p))  hs.setStyles (overlay, { 
+               top: '50%', 
+               marginTop: (offY - Math.round(overlay.offsetHeight / 2)) +'px'
+       });     
+       if (/^bottom/.test(p)) overlay.style.bottom = - offY +'px';
+       if (/^above$/.test(p)) {
+               hs.setStyles(overlay, {
+                       left: (- this.x.p1 - this.x.cb) +'px',
+                       right: (- this.x.p2 - this.x.cb) +'px',
+                       bottom: '100%',
+                       marginBottom: this.y.cb +'px',
+                       width: 'auto'
+               });
+               this.y.p1 = overlay.offsetHeight;
+       
+       } else if (/^below$/.test(p)) {
+               hs.setStyles(overlay, {
+                       position: 'relative',
+                       left: (- this.x.p1 - this.x.cb) +'px',
+                       right: (- this.x.p2 - this.x.cb) +'px',
+                       top: '100%',
+                       marginTop: this.y.cb +'px',
+                       width: 'auto'
+               });
+               this.y.p2 = overlay.offsetHeight;
+               overlay.style.position = 'absolute';
+       }
+},
+
+getOverlays : function() {     
+       this.getInline(['heading', 'caption'], true);
+       this.getNumber();
+       if (this.caption) hs.fireEvent(this, 'onAfterGetCaption');
+       if (this.heading) hs.fireEvent(this, 'onAfterGetHeading');
+       if (this.heading && this.dragByHeading) this.heading.className += ' highslide-move';
+       if (hs.showCredits) this.writeCredits();
+       for (var i = 0; i < hs.overlays.length; i++) {
+               var o = hs.overlays[i], tId = o.thumbnailId, sg = o.slideshowGroup;
+               if ((!tId && !sg) || (tId && tId == this.thumbsUserSetId)
+                               || (sg && sg === this.slideshowGroup)) {
+                       if (this.isImage || (this.isHtml && o.useOnHtml))
+                       this.createOverlay(o);
+               }
+       }
+       var os = [];
+       for (var i = 0; i < this.overlays.length; i++) {
+               var o = hs.$('hsId'+ this.overlays[i]);
+               if (/panel$/.test(o.position)) this.positionOverlay(o);
+               else hs.push(os, o);
+       }
+       for (var i = 0; i < os.length; i++) this.positionOverlay(os[i]);
+       this.gotOverlays = true;
+},
+genOverlayBox : function() {
+       if (!this.overlayBox) this.overlayBox = hs.createElement (
+               'div', {
+                       className: this.wrapperClassName
+               }, {
+                       position : 'absolute',
+                       width: (this.x.size || (this.useBox ? this.width : null) 
+                               || this.x.full) +'px',
+                       height: (this.y.size || this.y.full) +'px',
+                       visibility : 'hidden',
+                       overflow : 'hidden',
+                       zIndex : hs.ie ? 4 : 'auto'
+               },
+               hs.container,
+               true
+       );
+},
+sizeOverlayBox : function(doWrapper, doPanels) {
+       var overlayBox = this.overlayBox, 
+               x = this.x,
+               y = this.y;
+       hs.setStyles( overlayBox, {
+               width: x.size +'px', 
+               height: y.size +'px'
+       });
+       if (doWrapper || doPanels) {
+               for (var i = 0; i < this.overlays.length; i++) {
+                       var o = hs.$('hsId'+ this.overlays[i]);
+                       var ie6 = (hs.ieLt7 || document.compatMode == 'BackCompat');
+                       if (o && /^(above|below)$/.test(o.position)) {
+                               if (ie6) {
+                                       o.style.width = (overlayBox.offsetWidth + 2 * x.cb
+                                               + x.p1 + x.p2) +'px';
+                               }
+                               y[o.position == 'above' ? 'p1' : 'p2'] = o.offsetHeight;
+                       }
+                       if (o && ie6 && /^(left|right)panel$/.test(o.position)) {
+                               o.style.height = (overlayBox.offsetHeight + 2* y.cb) +'px';
+                       }
+               }
+       }
+       if (doWrapper) {
+               hs.setStyles(this.content, {
+                       top: y.p1 +'px'
+               });
+               hs.setStyles(overlayBox, {
+                       top: (y.p1 + y.cb) +'px'
+               });
+       }
+},
+
+showOverlays : function() {
+       var b = this.overlayBox;
+       b.className = '';
+       hs.setStyles(b, {
+               top: (this.y.p1 + this.y.cb) +'px',
+               left: (this.x.p1 + this.x.cb) +'px',
+               overflow : 'visible'
+       });
+       if (hs.safari) b.style.visibility = 'visible';
+       this.wrapper.appendChild (b);
+       for (var i = 0; i < this.overlays.length; i++) {
+               var o = hs.$('hsId'+ this.overlays[i]);
+               o.style.zIndex = o.zIndex || 4;
+               if (!o.hideOnMouseOut || this.mouseIsOver) {
+                       o.style.visibility = 'visible';
+                       hs.setStyles(o, { visibility: 'visible', display: '' });
+                       hs.animate(o, { opacity: o.opacity }, o.dur);
+               }
+       }
+},
+
+destroyOverlays : function() {
+       if (!this.overlays.length) return;
+       if (this.slideshow) {
+               var c = this.slideshow.controls;
+               if (c && hs.getExpander(c) == this) c.parentNode.removeChild(c);
+       }
+       for (var i = 0; i < this.overlays.length; i++) {
+               var o = hs.$('hsId'+ this.overlays[i]);
+               if (o && o.parentNode == hs.viewport && hs.getExpander(o) == this) hs.discardElement(o);
+       }
+       if (this.isHtml && this.preserveContent) {
+               this.overlayBox.style.top = '-9999px';
+               hs.container.appendChild(this.overlayBox);
+       } else
+       hs.discardElement(this.overlayBox);
+},
+
+
+
+createFullExpand : function () {
+       if (this.slideshow && this.slideshow.controls) {
+               this.slideshow.enable('full-expand');
+               return;
+       }
+       this.fullExpandLabel = hs.createElement(
+               'a', {
+                       href: 'javascript:hs.expanders['+ this.key +'].doFullExpand();',
+                       title: hs.lang.fullExpandTitle,
+                       className: 'highslide-full-expand'
+               }
+       );
+       if (!hs.fireEvent(this, 'onCreateFullExpand')) return;
+       
+       this.createOverlay({ 
+               overlayId: this.fullExpandLabel, 
+               position: hs.fullExpandPosition, 
+               hideOnMouseOut: true, 
+               opacity: hs.fullExpandOpacity
+       });
+},
+
+doFullExpand : function () {
+       try {
+               if (!hs.fireEvent(this, 'onDoFullExpand')) return;
+               if (this.fullExpandLabel) hs.discardElement(this.fullExpandLabel);
+               
+               this.focus();
+               var xSize = this.x.size;
+               this.resizeTo(this.x.full, this.y.full);
+               
+               var xpos = this.x.pos - (this.x.size - xSize) / 2;
+               if (xpos < hs.marginLeft) xpos = hs.marginLeft;
+               
+               this.moveTo(xpos, this.y.pos);
+               this.doShowHide('hidden');
+       
+       } catch (e) {
+               this.error(e);
+       }
+},
+
+
+afterClose : function () {
+       this.a.className = this.a.className.replace('highslide-active-anchor', '');
+       
+       this.doShowHide('visible');     
+       
+       if (this.isHtml && this.preserveContent
+                        && this.transitions[1] != 'crossfade') {
+               this.sleep();
+       } else {
+               if (this.outline && this.outlineWhileAnimating) this.outline.destroy();
+       
+               hs.discardElement(this.wrapper);
+       }
+       if (hs.mask) hs.mask.style.display = 'none';
+       this.destroyOverlays();
+       if (!hs.viewport.childNodes.length) hs.viewport.style.display = 'none';
+       
+       if (this.dimmingOpacity) hs.undim(this.key);
+       hs.fireEvent(this, 'onAfterClose');
+       hs.expanders[this.key] = null;          
+       hs.reOrder();
+}
+
+};
+
+
+// hs.Ajax object prototype
+hs.Ajax = function (a, content, pre) {
+       this.a = a;
+       this.content = content;
+       this.pre = pre;
+};
+
+hs.Ajax.prototype = {
+run : function () {
+       var xhr;
+       if (!this.src) this.src = hs.getSrc(this.a);
+       if (this.src.match('#')) {
+               var arr = this.src.split('#');
+               this.src = arr[0];
+               this.id = arr[1];
+       }
+       if (hs.cachedGets[this.src]) {
+               this.cachedGet = hs.cachedGets[this.src];
+               if (this.id) this.getElementContent();
+               else this.loadHTML();
+               return;
+       }
+       try { xhr = new XMLHttpRequest(); }
+       catch (e) {
+               try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
+               catch (e) {
+                       try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
+                       catch (e) { this.onError(); }
+               }
+       }
+       var pThis = this; 
+       xhr.onreadystatechange = function() {
+               if(pThis.xhr.readyState == 4) {
+                       if (pThis.id) pThis.getElementContent();
+                       else pThis.loadHTML();
+               }
+       };
+       var src = this.src;
+       this.xhr = xhr;
+       if (hs.forceAjaxReload) 
+               src = src.replace(/$/, (/\?/.test(src) ? '&' : '?') +'dummy='+ (new Date()).getTime());
+       xhr.open('GET', src, true);
+       xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+       xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+       xhr.send(null);
+},
+
+getElementContent : function() {
+       hs.init();
+       var attribs = window.opera || hs.ie6SSL ? { src: 'about:blank' } : null;
+       
+       this.iframe = hs.createElement('iframe', attribs, 
+               { position: 'absolute', top: '-9999px' }, hs.container);
+               
+       this.loadHTML();
+},
+
+loadHTML : function() {
+       var s = this.cachedGet || this.xhr.responseText,
+               regBody;
+       if (this.pre) hs.cachedGets[this.src] = s;
+       if (!hs.ie || hs.uaVersion >= 5.5) {
+               s = s.replace(new RegExp('<link[^>]*>', 'gi'), '')
+                       .replace(new RegExp('<script[^>]*>.*?</script>', 'gi'), '');
+               if (this.iframe) {
+                       var doc = this.iframe.contentDocument;
+                       if (!doc && this.iframe.contentWindow) doc = this.iframe.contentWindow.document;
+                       if (!doc) { // Opera
+                               var pThis = this;
+                               setTimeout(function() { pThis.loadHTML(); }, 25);
+                               return;
+                       }
+                       doc.open();
+                       doc.write(s);
+                       doc.close();
+                       try { s = doc.getElementById(this.id).innerHTML; } catch (e) {
+                               try { s = this.iframe.document.getElementById(this.id).innerHTML; } catch (e) {} // opera
+                       }
+                       hs.discardElement(this.iframe);
+               } else {
+                       regBody = /(<body[^>]*>|<\/body>)/ig;
+                       if (regBody.test(s)) s = s.split(regBody)[hs.ie ? 1 : 2];
+                       
+               }
+       }
+       hs.getElementByClass(this.content, 'DIV', 'highslide-body').innerHTML = s;
+       this.onLoad();
+       for (var x in this) this[x] = null;
+}
+};
+
+
+hs.Slideshow = function (expKey, options) {
+       if (hs.dynamicallyUpdateAnchors !== false) hs.updateAnchors();
+       this.expKey = expKey;
+       for (var x in options) this[x] = options[x];
+       if (this.useControls) this.getControls();
+       if (this.thumbstrip) this.thumbstrip = hs.Thumbstrip(this);
+};
+hs.Slideshow.prototype = {
+getControls: function() {
+       this.controls = hs.createElement('div', { innerHTML: hs.replaceLang(hs.skin.controls) }, 
+               null, hs.container);
+       
+       var buttons = ['play', 'pause', 'previous', 'next', 'move', 'full-expand', 'close'];
+       this.btn = {};
+       var pThis = this;
+       for (var i = 0; i < buttons.length; i++) {
+               this.btn[buttons[i]] = hs.getElementByClass(this.controls, 'li', 'highslide-'+ buttons[i]);
+               this.enable(buttons[i]);
+       }
+       this.btn.pause.style.display = 'none';
+       //this.disable('full-expand');
+},
+checkFirstAndLast: function() {
+       if (this.repeat || !this.controls) return;
+       var exp = hs.expanders[this.expKey],
+               cur = exp.getAnchorIndex(), 
+               re = /disabled$/;
+       if (cur == 0) 
+               this.disable('previous');
+       else if (re.test(this.btn.previous.getElementsByTagName('a')[0].className))
+               this.enable('previous');
+       if (cur + 1 == hs.anchors.groups[exp.slideshowGroup || 'none'].length) {
+               this.disable('next');
+               this.disable('play');
+       } else if (re.test(this.btn.next.getElementsByTagName('a')[0].className)) {
+               this.enable('next');
+               this.enable('play');
+       }
+},
+enable: function(btn) {
+       if (!this.btn) return;
+       var sls = this, a = this.btn[btn].getElementsByTagName('a')[0], re = /disabled$/;
+       a.onclick = function() {
+               sls[btn]();
+               return false;
+       };
+       if (re.test(a.className)) a.className = a.className.replace(re, '');
+},
+disable: function(btn) {
+       if (!this.btn) return;
+       var a = this.btn[btn].getElementsByTagName('a')[0];
+       a.onclick = function() { return false; };
+       if (!/disabled$/.test(a.className)) a.className += ' disabled';
+},
+hitSpace: function() {
+       if (this.autoplay) this.pause();
+       else this.play();
+},
+play: function(wait) {
+       if (this.btn) {
+               this.btn.play.style.display = 'none';
+               this.btn.pause.style.display = '';
+       }
+       
+       this.autoplay = true;   
+       if (!wait) hs.next(this.expKey);
+},
+pause: function() {
+       if (this.btn) {
+               this.btn.pause.style.display = 'none';
+               this.btn.play.style.display = '';
+       }
+       
+       clearTimeout(this.autoplay);
+       this.autoplay = null;
+},
+previous: function() {
+       this.pause();
+       hs.previous(this.btn.previous);
+},
+next: function() {
+       this.pause();
+       hs.next(this.btn.next);
+},
+move: function() {},
+'full-expand': function() {
+       hs.getExpander().doFullExpand();
+},
+close: function() {
+       hs.close(this.btn.close);
+}
+};
+hs.Thumbstrip = function(slideshow) {
+       function add (exp) {
+               hs.extend(options || {}, {
+                       overlayId: dom,
+                       hsId: 'thumbstrip',
+                       className: 'highslide-thumbstrip-'+ mode +'-overlay ' + (options.className || '')
+               });
+               if (hs.ieLt7) options.fade = 0;
+               exp.createOverlay(options);
+               hs.setStyles(dom.parentNode, { overflow: 'hidden' });
+       };
+       
+       function scroll (delta) {       
+               selectThumb(undefined, Math.round(delta * dom[isX ? 'offsetWidth' : 'offsetHeight'] * 0.7));
+       };
+       
+       function selectThumb (i, scrollBy) {
+               if (i === undefined) for (var j = 0; j < group.length; j++) {
+                       if (group[j] == hs.expanders[slideshow.expKey].a) {
+                               i = j;
+                               break;
+                       }
+               }
+               if (i === undefined) return;
+               var as = dom.getElementsByTagName('a'),
+                       active = as[i],
+                       cell = active.parentNode,
+                       left = isX ? 'Left' : 'Top',
+                       right = isX ? 'Right' : 'Bottom',
+                       width = isX ? 'Width' : 'Height',
+                       offsetLeft = 'offset' + left,
+                       offsetWidth = 'offset' + width,
+                       overlayWidth = div.parentNode.parentNode[offsetWidth],
+                       minTblPos = overlayWidth - table[offsetWidth],
+                       curTblPos = parseInt(table.style[isX ? 'left' : 'top']) || 0,
+                       tblPos = curTblPos,
+                       mgnRight = 20;
+               if (scrollBy !== undefined) {
+                       tblPos = curTblPos - scrollBy;
+                       
+                       if (minTblPos > 0) minTblPos = 0;
+                       if (tblPos > 0) tblPos = 0;
+                       if (tblPos < minTblPos) tblPos = minTblPos;
+                       
+       
+               } else {
+                       for (var j = 0; j < as.length; j++) as[j].className = '';
+                       active.className = 'highslide-active-anchor';
+                       var activeLeft = i > 0 ? as[i - 1].parentNode[offsetLeft] : cell[offsetLeft],
+                               activeRight = cell[offsetLeft] + cell[offsetWidth] + 
+                                       (as[i + 1] ? as[i + 1].parentNode[offsetWidth] : 0);
+                       if (activeRight > overlayWidth - curTblPos) tblPos = overlayWidth - activeRight;
+                       else if (activeLeft < -curTblPos) tblPos = -activeLeft;
+               }
+               var markerPos = cell[offsetLeft] + (cell[offsetWidth] - marker[offsetWidth]) / 2 + tblPos;
+               hs.animate(table, isX ? { left: tblPos } : { top: tblPos }, null, 'easeOutQuad');
+               hs.animate(marker, isX ? { left: markerPos } : { top: markerPos }, null, 'easeOutQuad');
+               scrollUp.style.display = tblPos < 0 ? 'block' : 'none';
+               scrollDown.style.display = (tblPos > minTblPos)  ? 'block' : 'none';
+               
+       };
+       
+
+       // initialize
+       var group = hs.anchors.groups[hs.expanders[slideshow.expKey].slideshowGroup || 'none'],
+               options = slideshow.thumbstrip,
+               mode = options.mode || 'horizontal',
+               floatMode = (mode == 'float'),
+               tree = floatMode ? ['div', 'ul', 'li', 'span'] : ['table', 'tbody', 'tr', 'td'],
+               isX = (mode == 'horizontal'),
+               dom = hs.createElement('div', {
+                               className: 'highslide-thumbstrip highslide-thumbstrip-'+ mode,
+                               innerHTML:
+                                       '<div class="highslide-thumbstrip-inner">'+
+                                       '<'+ tree[0] +'><'+ tree[1] +'></'+ tree[1] +'></'+ tree[0] +'></div>'+
+                                       '<div class="highslide-scroll-up"><div></div></div>'+
+                                       '<div class="highslide-scroll-down"><div></div></div>'+
+                                       '<div class="highslide-marker"><div></div></div>'
+                       }, {
+                               display: 'none'
+                       }, hs.container),
+               domCh = dom.childNodes,
+               div = domCh[0],
+               scrollUp = domCh[1],
+               scrollDown = domCh[2],
+               marker = domCh[3],
+               table = div.firstChild,
+               tbody = dom.getElementsByTagName(tree[1])[0],
+               tr;
+       for (var i = 0; i < group.length; i++) {
+               if (i == 0 || !isX) tr = hs.createElement(tree[2], null, null, tbody);
+               (function(){
+                       var a = group[i],
+                               cell = hs.createElement(tree[3], null, null, tr),
+                               pI = i;
+                       hs.createElement('a', {
+                               href: a.href,
+                               onclick: function() {
+                                       hs.getExpander(this).focus();
+                                       return hs.transit(a);
+                               },
+                               innerHTML: hs.stripItemFormatter ? hs.stripItemFormatter(a) : a.innerHTML
+                       }, null, cell);
+               })();
+       }
+       if (!floatMode) {
+               scrollUp.onclick = function () { scroll(-1); };
+               scrollDown.onclick = function() { scroll(1); };
+               hs.addEventListener(tbody, document.onmousewheel !== undefined ? 
+                               'mousewheel' : 'DOMMouseScroll', function(e) {        
+                       var delta = 0;
+               e = e || window.event;
+               if (e.wheelDelta) {
+                               delta = e.wheelDelta/120;
+                               if (hs.opera) delta = -delta;
+               } else if (e.detail) {
+                               delta = -e.detail/3;
+               }
+               if (delta) scroll(-delta * 0.2);
+                       if (e.preventDefault) e.preventDefault();
+                       e.returnValue = false;
+               });
+       }
+       
+       return {
+               add: add,
+               selectThumb: selectThumb
+       }
+};
+hs.langDefaults = hs.lang;
+// history
+var HsExpander = hs.Expander;
+if (hs.ie && window == window.top) {
+       (function () {
+               try {
+                       document.documentElement.doScroll('left');
+               } catch (e) {
+                       setTimeout(arguments.callee, 50);
+                       return;
+               }
+               hs.ready();
+       })();
+}
+hs.addEventListener(document, 'DOMContentLoaded', hs.ready);
+hs.addEventListener(window, 'load', hs.ready);
+
+// set handlers
+hs.addEventListener(document, 'ready', function() {
+       if (hs.expandCursor || hs.dimmingOpacity) {
+               var style = hs.createElement('style', { type: 'text/css' }, null, 
+                       document.getElementsByTagName('HEAD')[0]);
+                       
+               function addRule(sel, dec) {            
+                       if (!hs.ie) {
+                               style.appendChild(document.createTextNode(sel + " {" + dec + "}"));
+                       } else {
+                               var last = document.styleSheets[document.styleSheets.length - 1];
+                               if (typeof(last.addRule) == "object") last.addRule(sel, dec);
+                       }
+               }
+               function fix(prop) {
+                       return 'expression( ( ( ignoreMe = document.documentElement.'+ prop +
+                               ' ? document.documentElement.'+ prop +' : document.body.'+ prop +' ) ) + \'px\' );';
+               }
+               if (hs.expandCursor) addRule ('.highslide img', 
+                       'cursor: url('+ hs.graphicsDir + hs.expandCursor +'), pointer !important;');
+               addRule ('.highslide-viewport-size',
+                       hs.ie && (hs.uaVersion < 7 || document.compatMode == 'BackCompat') ?
+                               'position: absolute; '+
+                               'left:'+ fix('scrollLeft') +
+                               'top:'+ fix('scrollTop') +
+                               'width:'+ fix('clientWidth') +
+                               'height:'+ fix('clientHeight') :
+                               'position: fixed; width: 100%; height: 100%; left: 0; top: 0');
+       }
+});
+hs.addEventListener(window, 'resize', function() {
+       hs.getPageSize();
+       if (hs.viewport) for (var i = 0; i < hs.viewport.childNodes.length; i++) {
+               var node = hs.viewport.childNodes[i],
+                       exp = hs.getExpander(node);
+               exp.positionOverlay(node);
+               if (node.hsId == 'thumbstrip') exp.slideshow.thumbstrip.selectThumb();
+       }
+});
+hs.addEventListener(document, 'mousemove', function(e) {
+       hs.mouse = { x: e.clientX, y: e.clientY };
+});
+hs.addEventListener(document, 'mousedown', hs.mouseClickHandler);
+hs.addEventListener(document, 'mouseup', hs.mouseClickHandler);
+hs.addEventListener(document, 'ready', hs.setClickEvents);
+hs.addEventListener(window, 'load', hs.preloadImages);
+hs.addEventListener(window, 'load', hs.preloadAjax);
+}
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/highslide.config.min.js b/tools/infra-dashboard/js/highslide.config.min.js
new file mode 100644 (file)
index 0000000..4d1a9b3
--- /dev/null
@@ -0,0 +1 @@
+hs.outlineType="rounded-white",hs.wrapperClassName="draggable-header",hs.captionEval="this.a.title",hs.showCredits=!1,hs.marginTop=20,hs.marginRight=20,hs.marginBottom=20,hs.marginLeft=20;
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/jquery-1.7.2.js b/tools/infra-dashboard/js/jquery-1.7.2.js
new file mode 100644 (file)
index 0000000..3774ff9
--- /dev/null
@@ -0,0 +1,9404 @@
+/*!
+ * jQuery JavaScript Library v1.7.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Wed Mar 21 12:46:34 2012 -0700
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+       navigator = window.navigator,
+       location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+               // The jQuery object is actually just the init constructor 'enhanced'
+               return new jQuery.fn.init( selector, context, rootjQuery );
+       },
+
+       // Map over jQuery in case of overwrite
+       _jQuery = window.jQuery,
+
+       // Map over the $ in case of overwrite
+       _$ = window.$,
+
+       // A central reference to the root jQuery(document)
+       rootjQuery,
+
+       // A simple way to check for HTML strings or ID strings
+       // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+       quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+       // Check if a string has a non-whitespace character in it
+       rnotwhite = /\S/,
+
+       // Used for trimming whitespace
+       trimLeft = /^\s+/,
+       trimRight = /\s+$/,
+
+       // Match a standalone tag
+       rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+       // JSON RegExp
+       rvalidchars = /^[\],:{}\s]*$/,
+       rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+       rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+       rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+       // Useragent RegExp
+       rwebkit = /(webkit)[ \/]([\w.]+)/,
+       ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+       rmsie = /(msie) ([\w.]+)/,
+       rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+       // Matches dashed string for camelizing
+       rdashAlpha = /-([a-z]|[0-9])/ig,
+       rmsPrefix = /^-ms-/,
+
+       // Used by jQuery.camelCase as callback to replace()
+       fcamelCase = function( all, letter ) {
+               return ( letter + "" ).toUpperCase();
+       },
+
+       // Keep a UserAgent string for use with jQuery.browser
+       userAgent = navigator.userAgent,
+
+       // For matching the engine and version of the browser
+       browserMatch,
+
+       // The deferred used on DOM ready
+       readyList,
+
+       // The ready event handler
+       DOMContentLoaded,
+
+       // Save a reference to some core methods
+       toString = Object.prototype.toString,
+       hasOwn = Object.prototype.hasOwnProperty,
+       push = Array.prototype.push,
+       slice = Array.prototype.slice,
+       trim = String.prototype.trim,
+       indexOf = Array.prototype.indexOf,
+
+       // [[Class]] -> type pairs
+       class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+       constructor: jQuery,
+       init: function( selector, context, rootjQuery ) {
+               var match, elem, ret, doc;
+
+               // Handle $(""), $(null), or $(undefined)
+               if ( !selector ) {
+                       return this;
+               }
+
+               // Handle $(DOMElement)
+               if ( selector.nodeType ) {
+                       this.context = this[0] = selector;
+                       this.length = 1;
+                       return this;
+               }
+
+               // The body element only exists once, optimize finding it
+               if ( selector === "body" && !context && document.body ) {
+                       this.context = document;
+                       this[0] = document.body;
+                       this.selector = selector;
+                       this.length = 1;
+                       return this;
+               }
+
+               // Handle HTML strings
+               if ( typeof selector === "string" ) {
+                       // Are we dealing with HTML string or an ID?
+                       if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+                               // Assume that strings that start and end with <> are HTML and skip the regex check
+                               match = [ null, selector, null ];
+
+                       } else {
+                               match = quickExpr.exec( selector );
+                       }
+
+                       // Verify a match, and that no context was specified for #id
+                       if ( match && (match[1] || !context) ) {
+
+                               // HANDLE: $(html) -> $(array)
+                               if ( match[1] ) {
+                                       context = context instanceof jQuery ? context[0] : context;
+                                       doc = ( context ? context.ownerDocument || context : document );
+
+                                       // If a single string is passed in and it's a single tag
+                                       // just do a createElement and skip the rest
+                                       ret = rsingleTag.exec( selector );
+
+                                       if ( ret ) {
+                                               if ( jQuery.isPlainObject( context ) ) {
+                                                       selector = [ document.createElement( ret[1] ) ];
+                                                       jQuery.fn.attr.call( selector, context, true );
+
+                                               } else {
+                                                       selector = [ doc.createElement( ret[1] ) ];
+                                               }
+
+                                       } else {
+                                               ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+                                               selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+                                       }
+
+                                       return jQuery.merge( this, selector );
+
+                               // HANDLE: $("#id")
+                               } else {
+                                       elem = document.getElementById( match[2] );
+
+                                       // Check parentNode to catch when Blackberry 4.6 returns
+                                       // nodes that are no longer in the document #6963
+                                       if ( elem && elem.parentNode ) {
+                                               // Handle the case where IE and Opera return items
+                                               // by name instead of ID
+                                               if ( elem.id !== match[2] ) {
+                                                       return rootjQuery.find( selector );
+                                               }
+
+                                               // Otherwise, we inject the element directly into the jQuery object
+                                               this.length = 1;
+                                               this[0] = elem;
+                                       }
+
+                                       this.context = document;
+                                       this.selector = selector;
+                                       return this;
+                               }
+
+                       // HANDLE: $(expr, $(...))
+                       } else if ( !context || context.jquery ) {
+                               return ( context || rootjQuery ).find( selector );
+
+                       // HANDLE: $(expr, context)
+                       // (which is just equivalent to: $(context).find(expr)
+                       } else {
+                               return this.constructor( context ).find( selector );
+                       }
+
+               // HANDLE: $(function)
+               // Shortcut for document ready
+               } else if ( jQuery.isFunction( selector ) ) {
+                       return rootjQuery.ready( selector );
+               }
+
+               if ( selector.selector !== undefined ) {
+                       this.selector = selector.selector;
+                       this.context = selector.context;
+               }
+
+               return jQuery.makeArray( selector, this );
+       },
+
+       // Start with an empty selector
+       selector: "",
+
+       // The current version of jQuery being used
+       jquery: "1.7.2",
+
+       // The default length of a jQuery object is 0
+       length: 0,
+
+       // The number of elements contained in the matched element set
+       size: function() {
+               return this.length;
+       },
+
+       toArray: function() {
+               return slice.call( this, 0 );
+       },
+
+       // Get the Nth element in the matched element set OR
+       // Get the whole matched element set as a clean array
+       get: function( num ) {
+               return num == null ?
+
+                       // Return a 'clean' array
+                       this.toArray() :
+
+                       // Return just the object
+                       ( num < 0 ? this[ this.length + num ] : this[ num ] );
+       },
+
+       // Take an array of elements and push it onto the stack
+       // (returning the new matched element set)
+       pushStack: function( elems, name, selector ) {
+               // Build a new jQuery matched element set
+               var ret = this.constructor();
+
+               if ( jQuery.isArray( elems ) ) {
+                       push.apply( ret, elems );
+
+               } else {
+                       jQuery.merge( ret, elems );
+               }
+
+               // Add the old object onto the stack (as a reference)
+               ret.prevObject = this;
+
+               ret.context = this.context;
+
+               if ( name === "find" ) {
+                       ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+               } else if ( name ) {
+                       ret.selector = this.selector + "." + name + "(" + selector + ")";
+               }
+
+               // Return the newly-formed element set
+               return ret;
+       },
+
+       // Execute a callback for every element in the matched set.
+       // (You can seed the arguments with an array of args, but this is
+       // only used internally.)
+       each: function( callback, args ) {
+               return jQuery.each( this, callback, args );
+       },
+
+       ready: function( fn ) {
+               // Attach the listeners
+               jQuery.bindReady();
+
+               // Add the callback
+               readyList.add( fn );
+
+               return this;
+       },
+
+       eq: function( i ) {
+               i = +i;
+               return i === -1 ?
+                       this.slice( i ) :
+                       this.slice( i, i + 1 );
+       },
+
+       first: function() {
+               return this.eq( 0 );
+       },
+
+       last: function() {
+               return this.eq( -1 );
+       },
+
+       slice: function() {
+               return this.pushStack( slice.apply( this, arguments ),
+                       "slice", slice.call(arguments).join(",") );
+       },
+
+       map: function( callback ) {
+               return this.pushStack( jQuery.map(this, function( elem, i ) {
+                       return callback.call( elem, i, elem );
+               }));
+       },
+
+       end: function() {
+               return this.prevObject || this.constructor(null);
+       },
+
+       // For internal use only.
+       // Behaves like an Array's method, not like a jQuery method.
+       push: push,
+       sort: [].sort,
+       splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+       var options, name, src, copy, copyIsArray, clone,
+               target = arguments[0] || {},
+               i = 1,
+               length = arguments.length,
+               deep = false;
+
+       // Handle a deep copy situation
+       if ( typeof target === "boolean" ) {
+               deep = target;
+               target = arguments[1] || {};
+               // skip the boolean and the target
+               i = 2;
+       }
+
+       // Handle case when target is a string or something (possible in deep copy)
+       if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+               target = {};
+       }
+
+       // extend jQuery itself if only one argument is passed
+       if ( length === i ) {
+               target = this;
+               --i;
+       }
+
+       for ( ; i < length; i++ ) {
+               // Only deal with non-null/undefined values
+               if ( (options = arguments[ i ]) != null ) {
+                       // Extend the base object
+                       for ( name in options ) {
+                               src = target[ name ];
+                               copy = options[ name ];
+
+                               // Prevent never-ending loop
+                               if ( target === copy ) {
+                                       continue;
+                               }
+
+                               // Recurse if we're merging plain objects or arrays
+                               if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+                                       if ( copyIsArray ) {
+                                               copyIsArray = false;
+                                               clone = src && jQuery.isArray(src) ? src : [];
+
+                                       } else {
+                                               clone = src && jQuery.isPlainObject(src) ? src : {};
+                                       }
+
+                                       // Never move original objects, clone them
+                                       target[ name ] = jQuery.extend( deep, clone, copy );
+
+                               // Don't bring in undefined values
+                               } else if ( copy !== undefined ) {
+                                       target[ name ] = copy;
+                               }
+                       }
+               }
+       }
+
+       // Return the modified object
+       return target;
+};
+
+jQuery.extend({
+       noConflict: function( deep ) {
+               if ( window.$ === jQuery ) {
+                       window.$ = _$;
+               }
+
+               if ( deep && window.jQuery === jQuery ) {
+                       window.jQuery = _jQuery;
+               }
+
+               return jQuery;
+       },
+
+       // Is the DOM ready to be used? Set to true once it occurs.
+       isReady: false,
+
+       // A counter to track how many items to wait for before
+       // the ready event fires. See #6781
+       readyWait: 1,
+
+       // Hold (or release) the ready event
+       holdReady: function( hold ) {
+               if ( hold ) {
+                       jQuery.readyWait++;
+               } else {
+                       jQuery.ready( true );
+               }
+       },
+
+       // Handle when the DOM is ready
+       ready: function( wait ) {
+               // Either a released hold or an DOMready/load event and not yet ready
+               if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+                       // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+                       if ( !document.body ) {
+                               return setTimeout( jQuery.ready, 1 );
+                       }
+
+                       // Remember that the DOM is ready
+                       jQuery.isReady = true;
+
+                       // If a normal DOM Ready event fired, decrement, and wait if need be
+                       if ( wait !== true && --jQuery.readyWait > 0 ) {
+                               return;
+                       }
+
+                       // If there are functions bound, to execute
+                       readyList.fireWith( document, [ jQuery ] );
+
+                       // Trigger any bound ready events
+                       if ( jQuery.fn.trigger ) {
+                               jQuery( document ).trigger( "ready" ).off( "ready" );
+                       }
+               }
+       },
+
+       bindReady: function() {
+               if ( readyList ) {
+                       return;
+               }
+
+               readyList = jQuery.Callbacks( "once memory" );
+
+               // Catch cases where $(document).ready() is called after the
+               // browser event has already occurred.
+               if ( document.readyState === "complete" ) {
+                       // Handle it asynchronously to allow scripts the opportunity to delay ready
+                       return setTimeout( jQuery.ready, 1 );
+               }
+
+               // Mozilla, Opera and webkit nightlies currently support this event
+               if ( document.addEventListener ) {
+                       // Use the handy event callback
+                       document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+                       // A fallback to window.onload, that will always work
+                       window.addEventListener( "load", jQuery.ready, false );
+
+               // If IE event model is used
+               } else if ( document.attachEvent ) {
+                       // ensure firing before onload,
+                       // maybe late but safe also for iframes
+                       document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+                       // A fallback to window.onload, that will always work
+                       window.attachEvent( "onload", jQuery.ready );
+
+                       // If IE and not a frame
+                       // continually check to see if the document is ready
+                       var toplevel = false;
+
+                       try {
+                               toplevel = window.frameElement == null;
+                       } catch(e) {}
+
+                       if ( document.documentElement.doScroll && toplevel ) {
+                               doScrollCheck();
+                       }
+               }
+       },
+
+       // See test/unit/core.js for details concerning isFunction.
+       // Since version 1.3, DOM methods and functions like alert
+       // aren't supported. They return false on IE (#2968).
+       isFunction: function( obj ) {
+               return jQuery.type(obj) === "function";
+       },
+
+       isArray: Array.isArray || function( obj ) {
+               return jQuery.type(obj) === "array";
+       },
+
+       isWindow: function( obj ) {
+               return obj != null && obj == obj.window;
+       },
+
+       isNumeric: function( obj ) {
+               return !isNaN( parseFloat(obj) ) && isFinite( obj );
+       },
+
+       type: function( obj ) {
+               return obj == null ?
+                       String( obj ) :
+                       class2type[ toString.call(obj) ] || "object";
+       },
+
+       isPlainObject: function( obj ) {
+               // Must be an Object.
+               // Because of IE, we also have to check the presence of the constructor property.
+               // Make sure that DOM nodes and window objects don't pass through, as well
+               if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+                       return false;
+               }
+
+               try {
+                       // Not own constructor property must be Object
+                       if ( obj.constructor &&
+                               !hasOwn.call(obj, "constructor") &&
+                               !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+                               return false;
+                       }
+               } catch ( e ) {
+                       // IE8,9 Will throw exceptions on certain host objects #9897
+                       return false;
+               }
+
+               // Own properties are enumerated firstly, so to speed up,
+               // if last one is own, then all properties are own.
+
+               var key;
+               for ( key in obj ) {}
+
+               return key === undefined || hasOwn.call( obj, key );
+       },
+
+       isEmptyObject: function( obj ) {
+               for ( var name in obj ) {
+                       return false;
+               }
+               return true;
+       },
+
+       error: function( msg ) {
+               throw new Error( msg );
+       },
+
+       parseJSON: function( data ) {
+               if ( typeof data !== "string" || !data ) {
+                       return null;
+               }
+
+               // Make sure leading/trailing whitespace is removed (IE can't handle it)
+               data = jQuery.trim( data );
+
+               // Attempt to parse using the native JSON parser first
+               if ( window.JSON && window.JSON.parse ) {
+                       return window.JSON.parse( data );
+               }
+
+               // Make sure the incoming data is actual JSON
+               // Logic borrowed from http://json.org/json2.js
+               if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+                       .replace( rvalidtokens, "]" )
+                       .replace( rvalidbraces, "")) ) {
+
+                       return ( new Function( "return " + data ) )();
+
+               }
+               jQuery.error( "Invalid JSON: " + data );
+       },
+
+       // Cross-browser xml parsing
+       parseXML: function( data ) {
+               if ( typeof data !== "string" || !data ) {
+                       return null;
+               }
+               var xml, tmp;
+               try {
+                       if ( window.DOMParser ) { // Standard
+                               tmp = new DOMParser();
+                               xml = tmp.parseFromString( data , "text/xml" );
+                       } else { // IE
+                               xml = new ActiveXObject( "Microsoft.XMLDOM" );
+                               xml.async = "false";
+                               xml.loadXML( data );
+                       }
+               } catch( e ) {
+                       xml = undefined;
+               }
+               if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+                       jQuery.error( "Invalid XML: " + data );
+               }
+               return xml;
+       },
+
+       noop: function() {},
+
+       // Evaluates a script in a global context
+       // Workarounds based on findings by Jim Driscoll
+       // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+       globalEval: function( data ) {
+               if ( data && rnotwhite.test( data ) ) {
+                       // We use execScript on Internet Explorer
+                       // We use an anonymous function so that context is window
+                       // rather than jQuery in Firefox
+                       ( window.execScript || function( data ) {
+                               window[ "eval" ].call( window, data );
+                       } )( data );
+               }
+       },
+
+       // Convert dashed to camelCase; used by the css and data modules
+       // Microsoft forgot to hump their vendor prefix (#9572)
+       camelCase: function( string ) {
+               return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+       },
+
+       nodeName: function( elem, name ) {
+               return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+       },
+
+       // args is for internal usage only
+       each: function( object, callback, args ) {
+               var name, i = 0,
+                       length = object.length,
+                       isObj = length === undefined || jQuery.isFunction( object );
+
+               if ( args ) {
+                       if ( isObj ) {
+                               for ( name in object ) {
+                                       if ( callback.apply( object[ name ], args ) === false ) {
+                                               break;
+                                       }
+                               }
+                       } else {
+                               for ( ; i < length; ) {
+                                       if ( callback.apply( object[ i++ ], args ) === false ) {
+                                               break;
+                                       }
+                               }
+                       }
+
+               // A special, fast, case for the most common use of each
+               } else {
+                       if ( isObj ) {
+                               for ( name in object ) {
+                                       if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+                                               break;
+                                       }
+                               }
+                       } else {
+                               for ( ; i < length; ) {
+                                       if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               return object;
+       },
+
+       // Use native String.trim function wherever possible
+       trim: trim ?
+               function( text ) {
+                       return text == null ?
+                               "" :
+                               trim.call( text );
+               } :
+
+               // Otherwise use our own trimming functionality
+               function( text ) {
+                       return text == null ?
+                               "" :
+                               text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+               },
+
+       // results is for internal usage only
+       makeArray: function( array, results ) {
+               var ret = results || [];
+
+               if ( array != null ) {
+                       // The window, strings (and functions) also have 'length'
+                       // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+                       var type = jQuery.type( array );
+
+                       if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+                               push.call( ret, array );
+                       } else {
+                               jQuery.merge( ret, array );
+                       }
+               }
+
+               return ret;
+       },
+
+       inArray: function( elem, array, i ) {
+               var len;
+
+               if ( array ) {
+                       if ( indexOf ) {
+                               return indexOf.call( array, elem, i );
+                       }
+
+                       len = array.length;
+                       i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+                       for ( ; i < len; i++ ) {
+                               // Skip accessing in sparse arrays
+                               if ( i in array && array[ i ] === elem ) {
+                                       return i;
+                               }
+                       }
+               }
+
+               return -1;
+       },
+
+       merge: function( first, second ) {
+               var i = first.length,
+                       j = 0;
+
+               if ( typeof second.length === "number" ) {
+                       for ( var l = second.length; j < l; j++ ) {
+                               first[ i++ ] = second[ j ];
+                       }
+
+               } else {
+                       while ( second[j] !== undefined ) {
+                               first[ i++ ] = second[ j++ ];
+                       }
+               }
+
+               first.length = i;
+
+               return first;
+       },
+
+       grep: function( elems, callback, inv ) {
+               var ret = [], retVal;
+               inv = !!inv;
+
+               // Go through the array, only saving the items
+               // that pass the validator function
+               for ( var i = 0, length = elems.length; i < length; i++ ) {
+                       retVal = !!callback( elems[ i ], i );
+                       if ( inv !== retVal ) {
+                               ret.push( elems[ i ] );
+                       }
+               }
+
+               return ret;
+       },
+
+       // arg is for internal usage only
+       map: function( elems, callback, arg ) {
+               var value, key, ret = [],
+                       i = 0,
+                       length = elems.length,
+                       // jquery objects are treated as arrays
+                       isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+               // Go through the array, translating each of the items to their
+               if ( isArray ) {
+                       for ( ; i < length; i++ ) {
+                               value = callback( elems[ i ], i, arg );
+
+                               if ( value != null ) {
+                                       ret[ ret.length ] = value;
+                               }
+                       }
+
+               // Go through every key on the object,
+               } else {
+                       for ( key in elems ) {
+                               value = callback( elems[ key ], key, arg );
+
+                               if ( value != null ) {
+                                       ret[ ret.length ] = value;
+                               }
+                       }
+               }
+
+               // Flatten any nested arrays
+               return ret.concat.apply( [], ret );
+       },
+
+       // A global GUID counter for objects
+       guid: 1,
+
+       // Bind a function to a context, optionally partially applying any
+       // arguments.
+       proxy: function( fn, context ) {
+               if ( typeof context === "string" ) {
+                       var tmp = fn[ context ];
+                       context = fn;
+                       fn = tmp;
+               }
+
+               // Quick check to determine if target is callable, in the spec
+               // this throws a TypeError, but we will just return undefined.
+               if ( !jQuery.isFunction( fn ) ) {
+                       return undefined;
+               }
+
+               // Simulated bind
+               var args = slice.call( arguments, 2 ),
+                       proxy = function() {
+                               return fn.apply( context, args.concat( slice.call( arguments ) ) );
+                       };
+
+               // Set the guid of unique handler to the same of original handler, so it can be removed
+               proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+               return proxy;
+       },
+
+       // Mutifunctional method to get and set values to a collection
+       // The value/s can optionally be executed if it's a function
+       access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+               var exec,
+                       bulk = key == null,
+                       i = 0,
+                       length = elems.length;
+
+               // Sets many values
+               if ( key && typeof key === "object" ) {
+                       for ( i in key ) {
+                               jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+                       }
+                       chainable = 1;
+
+               // Sets one value
+               } else if ( value !== undefined ) {
+                       // Optionally, function values get executed if exec is true
+                       exec = pass === undefined && jQuery.isFunction( value );
+
+                       if ( bulk ) {
+                               // Bulk operations only iterate when executing function values
+                               if ( exec ) {
+                                       exec = fn;
+                                       fn = function( elem, key, value ) {
+                                               return exec.call( jQuery( elem ), value );
+                                       };
+
+                               // Otherwise they run against the entire set
+                               } else {
+                                       fn.call( elems, value );
+                                       fn = null;
+                               }
+                       }
+
+                       if ( fn ) {
+                               for (; i < length; i++ ) {
+                                       fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+                               }
+                       }
+
+                       chainable = 1;
+               }
+
+               return chainable ?
+                       elems :
+
+                       // Gets
+                       bulk ?
+                               fn.call( elems ) :
+                               length ? fn( elems[0], key ) : emptyGet;
+       },
+
+       now: function() {
+               return ( new Date() ).getTime();
+       },
+
+       // Use of jQuery.browser is frowned upon.
+       // More details: http://docs.jquery.com/Utilities/jQuery.browser
+       uaMatch: function( ua ) {
+               ua = ua.toLowerCase();
+
+               var match = rwebkit.exec( ua ) ||
+                       ropera.exec( ua ) ||
+                       rmsie.exec( ua ) ||
+                       ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+                       [];
+
+               return { browser: match[1] || "", version: match[2] || "0" };
+       },
+
+       sub: function() {
+               function jQuerySub( selector, context ) {
+                       return new jQuerySub.fn.init( selector, context );
+               }
+               jQuery.extend( true, jQuerySub, this );
+               jQuerySub.superclass = this;
+               jQuerySub.fn = jQuerySub.prototype = this();
+               jQuerySub.fn.constructor = jQuerySub;
+               jQuerySub.sub = this.sub;
+               jQuerySub.fn.init = function init( selector, context ) {
+                       if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+                               context = jQuerySub( context );
+                       }
+
+                       return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+               };
+               jQuerySub.fn.init.prototype = jQuerySub.fn;
+               var rootjQuerySub = jQuerySub(document);
+               return jQuerySub;
+       },
+
+       browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+       class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+       jQuery.browser[ browserMatch.browser ] = true;
+       jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+       jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+       trimLeft = /^[\s\xA0]+/;
+       trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+       DOMContentLoaded = function() {
+               document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+               jQuery.ready();
+       };
+
+} else if ( document.attachEvent ) {
+       DOMContentLoaded = function() {
+               // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+               if ( document.readyState === "complete" ) {
+                       document.detachEvent( "onreadystatechange", DOMContentLoaded );
+                       jQuery.ready();
+               }
+       };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+       if ( jQuery.isReady ) {
+               return;
+       }
+
+       try {
+               // If IE is used, use the trick by Diego Perini
+               // http://javascript.nwbox.com/IEContentLoaded/
+               document.documentElement.doScroll("left");
+       } catch(e) {
+               setTimeout( doScrollCheck, 1 );
+               return;
+       }
+
+       // and execute any waiting functions
+       jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+       var object = flagsCache[ flags ] = {},
+               i, length;
+       flags = flags.split( /\s+/ );
+       for ( i = 0, length = flags.length; i < length; i++ ) {
+               object[ flags[i] ] = true;
+       }
+       return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *     flags:  an optional list of space-separated flags that will change how
+ *                     the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ *     once:                   will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *     memory:                 will keep track of previous values and will call any callback added
+ *                                     after the list has been fired right away with the latest "memorized"
+ *                                     values (like a Deferred)
+ *
+ *     unique:                 will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *     stopOnFalse:    interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+       // Convert flags from String-formatted to Object-formatted
+       // (we check in cache first)
+       flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+       var // Actual callback list
+               list = [],
+               // Stack of fire calls for repeatable lists
+               stack = [],
+               // Last fire value (for non-forgettable lists)
+               memory,
+               // Flag to know if list was already fired
+               fired,
+               // Flag to know if list is currently firing
+               firing,
+               // First callback to fire (used internally by add and fireWith)
+               firingStart,
+               // End of the loop when firing
+               firingLength,
+               // Index of currently firing callback (modified by remove if needed)
+               firingIndex,
+               // Add one or several callbacks to the list
+               add = function( args ) {
+                       var i,
+                               length,
+                               elem,
+                               type,
+                               actual;
+                       for ( i = 0, length = args.length; i < length; i++ ) {
+                               elem = args[ i ];
+                               type = jQuery.type( elem );
+                               if ( type === "array" ) {
+                                       // Inspect recursively
+                                       add( elem );
+                               } else if ( type === "function" ) {
+                                       // Add if not in unique mode and callback is not in
+                                       if ( !flags.unique || !self.has( elem ) ) {
+                                               list.push( elem );
+                                       }
+                               }
+                       }
+               },
+               // Fire callbacks
+               fire = function( context, args ) {
+                       args = args || [];
+                       memory = !flags.memory || [ context, args ];
+                       fired = true;
+                       firing = true;
+                       firingIndex = firingStart || 0;
+                       firingStart = 0;
+                       firingLength = list.length;
+                       for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+                               if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+                                       memory = true; // Mark as halted
+                                       break;
+                               }
+                       }
+                       firing = false;
+                       if ( list ) {
+                               if ( !flags.once ) {
+                                       if ( stack && stack.length ) {
+                                               memory = stack.shift();
+                                               self.fireWith( memory[ 0 ], memory[ 1 ] );
+                                       }
+                               } else if ( memory === true ) {
+                                       self.disable();
+                               } else {
+                                       list = [];
+                               }
+                       }
+               },
+               // Actual Callbacks object
+               self = {
+                       // Add a callback or a collection of callbacks to the list
+                       add: function() {
+                               if ( list ) {
+                                       var length = list.length;
+                                       add( arguments );
+                                       // Do we need to add the callbacks to the
+                                       // current firing batch?
+                                       if ( firing ) {
+                                               firingLength = list.length;
+                                       // With memory, if we're not firing then
+                                       // we should call right away, unless previous
+                                       // firing was halted (stopOnFalse)
+                                       } else if ( memory && memory !== true ) {
+                                               firingStart = length;
+                                               fire( memory[ 0 ], memory[ 1 ] );
+                                       }
+                               }
+                               return this;
+                       },
+                       // Remove a callback from the list
+                       remove: function() {
+                               if ( list ) {
+                                       var args = arguments,
+                                               argIndex = 0,
+                                               argLength = args.length;
+                                       for ( ; argIndex < argLength ; argIndex++ ) {
+                                               for ( var i = 0; i < list.length; i++ ) {
+                                                       if ( args[ argIndex ] === list[ i ] ) {
+                                                               // Handle firingIndex and firingLength
+                                                               if ( firing ) {
+                                                                       if ( i <= firingLength ) {
+                                                                               firingLength--;
+                                                                               if ( i <= firingIndex ) {
+                                                                                       firingIndex--;
+                                                                               }
+                                                                       }
+                                                               }
+                                                               // Remove the element
+                                                               list.splice( i--, 1 );
+                                                               // If we have some unicity property then
+                                                               // we only need to do this once
+                                                               if ( flags.unique ) {
+                                                                       break;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                               return this;
+                       },
+                       // Control if a given callback is in the list
+                       has: function( fn ) {
+                               if ( list ) {
+                                       var i = 0,
+                                               length = list.length;
+                                       for ( ; i < length; i++ ) {
+                                               if ( fn === list[ i ] ) {
+                                                       return true;
+                                               }
+                                       }
+                               }
+                               return false;
+                       },
+                       // Remove all callbacks from the list
+                       empty: function() {
+                               list = [];
+                               return this;
+                       },
+                       // Have the list do nothing anymore
+                       disable: function() {
+                               list = stack = memory = undefined;
+                               return this;
+                       },
+                       // Is it disabled?
+                       disabled: function() {
+                               return !list;
+                       },
+                       // Lock the list in its current state
+                       lock: function() {
+                               stack = undefined;
+                               if ( !memory || memory === true ) {
+                                       self.disable();
+                               }
+                               return this;
+                       },
+                       // Is it locked?
+                       locked: function() {
+                               return !stack;
+                       },
+                       // Call all callbacks with the given context and arguments
+                       fireWith: function( context, args ) {
+                               if ( stack ) {
+                                       if ( firing ) {
+                                               if ( !flags.once ) {
+                                                       stack.push( [ context, args ] );
+                                               }
+                                       } else if ( !( flags.once && memory ) ) {
+                                               fire( context, args );
+                                       }
+                               }
+                               return this;
+                       },
+                       // Call all the callbacks with the given arguments
+                       fire: function() {
+                               self.fireWith( this, arguments );
+                               return this;
+                       },
+                       // To know if the callbacks have already been called at least once
+                       fired: function() {
+                               return !!fired;
+                       }
+               };
+
+       return self;
+};
+
+
+
+
+var // Static reference to slice
+       sliceDeferred = [].slice;
+
+jQuery.extend({
+
+       Deferred: function( func ) {
+               var doneList = jQuery.Callbacks( "once memory" ),
+                       failList = jQuery.Callbacks( "once memory" ),
+                       progressList = jQuery.Callbacks( "memory" ),
+                       state = "pending",
+                       lists = {
+                               resolve: doneList,
+                               reject: failList,
+                               notify: progressList
+                       },
+                       promise = {
+                               done: doneList.add,
+                               fail: failList.add,
+                               progress: progressList.add,
+
+                               state: function() {
+                                       return state;
+                               },
+
+                               // Deprecated
+                               isResolved: doneList.fired,
+                               isRejected: failList.fired,
+
+                               then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+                                       deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+                                       return this;
+                               },
+                               always: function() {
+                                       deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+                                       return this;
+                               },
+                               pipe: function( fnDone, fnFail, fnProgress ) {
+                                       return jQuery.Deferred(function( newDefer ) {
+                                               jQuery.each( {
+                                                       done: [ fnDone, "resolve" ],
+                                                       fail: [ fnFail, "reject" ],
+                                                       progress: [ fnProgress, "notify" ]
+                                               }, function( handler, data ) {
+                                                       var fn = data[ 0 ],
+                                                               action = data[ 1 ],
+                                                               returned;
+                                                       if ( jQuery.isFunction( fn ) ) {
+                                                               deferred[ handler ](function() {
+                                                                       returned = fn.apply( this, arguments );
+                                                                       if ( returned && jQuery.isFunction( returned.promise ) ) {
+                                                                               returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+                                                                       } else {
+                                                                               newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+                                                                       }
+                                                               });
+                                                       } else {
+                                                               deferred[ handler ]( newDefer[ action ] );
+                                                       }
+                                               });
+                                       }).promise();
+                               },
+                               // Get a promise for this deferred
+                               // If obj is provided, the promise aspect is added to the object
+                               promise: function( obj ) {
+                                       if ( obj == null ) {
+                                               obj = promise;
+                                       } else {
+                                               for ( var key in promise ) {
+                                                       obj[ key ] = promise[ key ];
+                                               }
+                                       }
+                                       return obj;
+                               }
+                       },
+                       deferred = promise.promise({}),
+                       key;
+
+               for ( key in lists ) {
+                       deferred[ key ] = lists[ key ].fire;
+                       deferred[ key + "With" ] = lists[ key ].fireWith;
+               }
+
+               // Handle state
+               deferred.done( function() {
+                       state = "resolved";
+               }, failList.disable, progressList.lock ).fail( function() {
+                       state = "rejected";
+               }, doneList.disable, progressList.lock );
+
+               // Call given func if any
+               if ( func ) {
+                       func.call( deferred, deferred );
+               }
+
+               // All done!
+               return deferred;
+       },
+
+       // Deferred helper
+       when: function( firstParam ) {
+               var args = sliceDeferred.call( arguments, 0 ),
+                       i = 0,
+                       length = args.length,
+                       pValues = new Array( length ),
+                       count = length,
+                       pCount = length,
+                       deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+                               firstParam :
+                               jQuery.Deferred(),
+                       promise = deferred.promise();
+               function resolveFunc( i ) {
+                       return function( value ) {
+                               args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+                               if ( !( --count ) ) {
+                                       deferred.resolveWith( deferred, args );
+                               }
+                       };
+               }
+               function progressFunc( i ) {
+                       return function( value ) {
+                               pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+                               deferred.notifyWith( promise, pValues );
+                       };
+               }
+               if ( length > 1 ) {
+                       for ( ; i < length; i++ ) {
+                               if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+                                       args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+                               } else {
+                                       --count;
+                               }
+                       }
+                       if ( !count ) {
+                               deferred.resolveWith( deferred, args );
+                       }
+               } else if ( deferred !== firstParam ) {
+                       deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+               }
+               return promise;
+       }
+});
+
+
+
+
+jQuery.support = (function() {
+
+       var support,
+               all,
+               a,
+               select,
+               opt,
+               input,
+               fragment,
+               tds,
+               events,
+               eventName,
+               i,
+               isSupported,
+               div = document.createElement( "div" ),
+               documentElement = document.documentElement;
+
+       // Preliminary tests
+       div.setAttribute("className", "t");
+       div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+       all = div.getElementsByTagName( "*" );
+       a = div.getElementsByTagName( "a" )[ 0 ];
+
+       // Can't get basic test support
+       if ( !all || !all.length || !a ) {
+               return {};
+       }
+
+       // First batch of supports tests
+       select = document.createElement( "select" );
+       opt = select.appendChild( document.createElement("option") );
+       input = div.getElementsByTagName( "input" )[ 0 ];
+
+       support = {
+               // IE strips leading whitespace when .innerHTML is used
+               leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+               // Make sure that tbody elements aren't automatically inserted
+               // IE will insert them into empty tables
+               tbody: !div.getElementsByTagName("tbody").length,
+
+               // Make sure that link elements get serialized correctly by innerHTML
+               // This requires a wrapper element in IE
+               htmlSerialize: !!div.getElementsByTagName("link").length,
+
+               // Get the style information from getAttribute
+               // (IE uses .cssText instead)
+               style: /top/.test( a.getAttribute("style") ),
+
+               // Make sure that URLs aren't manipulated
+               // (IE normalizes it by default)
+               hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+               // Make sure that element opacity exists
+               // (IE uses filter instead)
+               // Use a regex to work around a WebKit issue. See #5145
+               opacity: /^0.55/.test( a.style.opacity ),
+
+               // Verify style float existence
+               // (IE uses styleFloat instead of cssFloat)
+               cssFloat: !!a.style.cssFloat,
+
+               // Make sure that if no value is specified for a checkbox
+               // that it defaults to "on".
+               // (WebKit defaults to "" instead)
+               checkOn: ( input.value === "on" ),
+
+               // Make sure that a selected-by-default option has a working selected property.
+               // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+               optSelected: opt.selected,
+
+               // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+               getSetAttribute: div.className !== "t",
+
+               // Tests for enctype support on a form(#6743)
+               enctype: !!document.createElement("form").enctype,
+
+               // Makes sure cloning an html5 element does not cause problems
+               // Where outerHTML is undefined, this still works
+               html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+               // Will be defined later
+               submitBubbles: true,
+               changeBubbles: true,
+               focusinBubbles: false,
+               deleteExpando: true,
+               noCloneEvent: true,
+               inlineBlockNeedsLayout: false,
+               shrinkWrapBlocks: false,
+               reliableMarginRight: true,
+               pixelMargin: true
+       };
+
+       // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead
+       jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat");
+
+       // Make sure checked status is properly cloned
+       input.checked = true;
+       support.noCloneChecked = input.cloneNode( true ).checked;
+
+       // Make sure that the options inside disabled selects aren't marked as disabled
+       // (WebKit marks them as disabled)
+       select.disabled = true;
+       support.optDisabled = !opt.disabled;
+
+       // Test to see if it's possible to delete an expando from an element
+       // Fails in Internet Explorer
+       try {
+               delete div.test;
+       } catch( e ) {
+               support.deleteExpando = false;
+       }
+
+       if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+               div.attachEvent( "onclick", function() {
+                       // Cloning a node shouldn't copy over any
+                       // bound event handlers (IE does this)
+                       support.noCloneEvent = false;
+               });
+               div.cloneNode( true ).fireEvent( "onclick" );
+       }
+
+       // Check if a radio maintains its value
+       // after being appended to the DOM
+       input = document.createElement("input");
+       input.value = "t";
+       input.setAttribute("type", "radio");
+       support.radioValue = input.value === "t";
+
+       input.setAttribute("checked", "checked");
+
+       // #11217 - WebKit loses check when the name is after the checked attribute
+       input.setAttribute( "name", "t" );
+
+       div.appendChild( input );
+       fragment = document.createDocumentFragment();
+       fragment.appendChild( div.lastChild );
+
+       // WebKit doesn't clone checked state correctly in fragments
+       support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+       // Check if a disconnected checkbox will retain its checked
+       // value of true after appended to the DOM (IE6/7)
+       support.appendChecked = input.checked;
+
+       fragment.removeChild( input );
+       fragment.appendChild( div );
+
+       // Technique from Juriy Zaytsev
+       // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+       // We only care about the case where non-standard event systems
+       // are used, namely in IE. Short-circuiting here helps us to
+       // avoid an eval call (in setAttribute) which can cause CSP
+       // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+       if ( div.attachEvent ) {
+               for ( i in {
+                       submit: 1,
+                       change: 1,
+                       focusin: 1
+               }) {
+                       eventName = "on" + i;
+                       isSupported = ( eventName in div );
+                       if ( !isSupported ) {
+                               div.setAttribute( eventName, "return;" );
+                               isSupported = ( typeof div[ eventName ] === "function" );
+                       }
+                       support[ i + "Bubbles" ] = isSupported;
+               }
+       }
+
+       fragment.removeChild( div );
+
+       // Null elements to avoid leaks in IE
+       fragment = select = opt = div = input = null;
+
+       // Run tests that need a body at doc ready
+       jQuery(function() {
+               var container, outer, inner, table, td, offsetSupport,
+                       marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
+                       paddingMarginBorderVisibility, paddingMarginBorder,
+                       body = document.getElementsByTagName("body")[0];
+
+               if ( !body ) {
+                       // Return for frameset docs that don't have a body
+                       return;
+               }
+
+               conMarginTop = 1;
+               paddingMarginBorder = "padding:0;margin:0;border:";
+               positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
+               paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
+               style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
+               html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
+                       "<table " + style + "' cellpadding='0' cellspacing='0'>" +
+                       "<tr><td></td></tr></table>";
+
+               container = document.createElement("div");
+               container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+               body.insertBefore( container, body.firstChild );
+
+               // Construct the test element
+               div = document.createElement("div");
+               container.appendChild( div );
+
+               // Check if table cells still have offsetWidth/Height when they are set
+               // to display:none and there are still other visible table cells in a
+               // table row; if so, offsetWidth/Height are not reliable for use when
+               // determining if an element has been hidden directly using
+               // display:none (it is still safe to use offsets if a parent element is
+               // hidden; don safety goggles and see bug #4512 for more information).
+               // (only IE 8 fails this test)
+               div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>";
+               tds = div.getElementsByTagName( "td" );
+               isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+               tds[ 0 ].style.display = "";
+               tds[ 1 ].style.display = "none";
+
+               // Check if empty table cells still have offsetWidth/Height
+               // (IE <= 8 fail this test)
+               support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+               // Check if div with explicit width and no margin-right incorrectly
+               // gets computed margin-right based on width of container. For more
+               // info see bug #3333
+               // Fails in WebKit before Feb 2011 nightlies
+               // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+               if ( window.getComputedStyle ) {
+                       div.innerHTML = "";
+                       marginDiv = document.createElement( "div" );
+                       marginDiv.style.width = "0";
+                       marginDiv.style.marginRight = "0";
+                       div.style.width = "2px";
+                       div.appendChild( marginDiv );
+                       support.reliableMarginRight =
+                               ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+               }
+
+               if ( typeof div.style.zoom !== "undefined" ) {
+                       // Check if natively block-level elements act like inline-block
+                       // elements when setting their display to 'inline' and giving
+                       // them layout
+                       // (IE < 8 does this)
+                       div.innerHTML = "";
+                       div.style.width = div.style.padding = "1px";
+                       div.style.border = 0;
+                       div.style.overflow = "hidden";
+                       div.style.display = "inline";
+                       div.style.zoom = 1;
+                       support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+                       // Check if elements with layout shrink-wrap their children
+                       // (IE 6 does this)
+                       div.style.display = "block";
+                       div.style.overflow = "visible";
+                       div.innerHTML = "<div style='width:5px;'></div>";
+                       support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+               }
+
+               div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
+               div.innerHTML = html;
+
+               outer = div.firstChild;
+               inner = outer.firstChild;
+               td = outer.nextSibling.firstChild.firstChild;
+
+               offsetSupport = {
+                       doesNotAddBorder: ( inner.offsetTop !== 5 ),
+                       doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+               };
+
+               inner.style.position = "fixed";
+               inner.style.top = "20px";
+
+               // safari subtracts parent border width here which is 5px
+               offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+               inner.style.position = inner.style.top = "";
+
+               outer.style.overflow = "hidden";
+               outer.style.position = "relative";
+
+               offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+               offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+               if ( window.getComputedStyle ) {
+                       div.style.marginTop = "1%";
+                       support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
+               }
+
+               if ( typeof container.style.zoom !== "undefined" ) {
+                       container.style.zoom = 1;
+               }
+
+               body.removeChild( container );
+               marginDiv = div = container = null;
+
+               jQuery.extend( support, offsetSupport );
+       });
+
+       return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+       rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+       cache: {},
+
+       // Please use with caution
+       uuid: 0,
+
+       // Unique for each copy of jQuery on the page
+       // Non-digits removed to match rinlinejQuery
+       expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+       // The following elements throw uncatchable exceptions if you
+       // attempt to add expando properties to them.
+       noData: {
+               "embed": true,
+               // Ban all objects except for Flash (which handle expandos)
+               "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+               "applet": true
+       },
+
+       hasData: function( elem ) {
+               elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+               return !!elem && !isEmptyDataObject( elem );
+       },
+
+       data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+               if ( !jQuery.acceptData( elem ) ) {
+                       return;
+               }
+
+               var privateCache, thisCache, ret,
+                       internalKey = jQuery.expando,
+                       getByName = typeof name === "string",
+
+                       // We have to handle DOM nodes and JS objects differently because IE6-7
+                       // can't GC object references properly across the DOM-JS boundary
+                       isNode = elem.nodeType,
+
+                       // Only DOM nodes need the global jQuery cache; JS object data is
+                       // attached directly to the object so GC can occur automatically
+                       cache = isNode ? jQuery.cache : elem,
+
+                       // Only defining an ID for JS objects if its cache already exists allows
+                       // the code to shortcut on the same path as a DOM node with no cache
+                       id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+                       isEvents = name === "events";
+
+               // Avoid doing any more work than we need to when trying to get data on an
+               // object that has no data at all
+               if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+                       return;
+               }
+
+               if ( !id ) {
+                       // Only DOM nodes need a new unique ID for each element since their data
+                       // ends up in the global cache
+                       if ( isNode ) {
+                               elem[ internalKey ] = id = ++jQuery.uuid;
+                       } else {
+                               id = internalKey;
+                       }
+               }
+
+               if ( !cache[ id ] ) {
+                       cache[ id ] = {};
+
+                       // Avoids exposing jQuery metadata on plain JS objects when the object
+                       // is serialized using JSON.stringify
+                       if ( !isNode ) {
+                               cache[ id ].toJSON = jQuery.noop;
+                       }
+               }
+
+               // An object can be passed to jQuery.data instead of a key/value pair; this gets
+               // shallow copied over onto the existing cache
+               if ( typeof name === "object" || typeof name === "function" ) {
+                       if ( pvt ) {
+                               cache[ id ] = jQuery.extend( cache[ id ], name );
+                       } else {
+                               cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+                       }
+               }
+
+               privateCache = thisCache = cache[ id ];
+
+               // jQuery data() is stored in a separate object inside the object's internal data
+               // cache in order to avoid key collisions between internal data and user-defined
+               // data.
+               if ( !pvt ) {
+                       if ( !thisCache.data ) {
+                               thisCache.data = {};
+                       }
+
+                       thisCache = thisCache.data;
+               }
+
+               if ( data !== undefined ) {
+                       thisCache[ jQuery.camelCase( name ) ] = data;
+               }
+
+               // Users should not attempt to inspect the internal events object using jQuery.data,
+               // it is undocumented and subject to change. But does anyone listen? No.
+               if ( isEvents && !thisCache[ name ] ) {
+                       return privateCache.events;
+               }
+
+               // Check for both converted-to-camel and non-converted data property names
+               // If a data property was specified
+               if ( getByName ) {
+
+                       // First Try to find as-is property data
+                       ret = thisCache[ name ];
+
+                       // Test for null|undefined property data
+                       if ( ret == null ) {
+
+                               // Try to find the camelCased property
+                               ret = thisCache[ jQuery.camelCase( name ) ];
+                       }
+               } else {
+                       ret = thisCache;
+               }
+
+               return ret;
+       },
+
+       removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+               if ( !jQuery.acceptData( elem ) ) {
+                       return;
+               }
+
+               var thisCache, i, l,
+
+                       // Reference to internal data cache key
+                       internalKey = jQuery.expando,
+
+                       isNode = elem.nodeType,
+
+                       // See jQuery.data for more information
+                       cache = isNode ? jQuery.cache : elem,
+
+                       // See jQuery.data for more information
+                       id = isNode ? elem[ internalKey ] : internalKey;
+
+               // If there is already no cache entry for this object, there is no
+               // purpose in continuing
+               if ( !cache[ id ] ) {
+                       return;
+               }
+
+               if ( name ) {
+
+                       thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+                       if ( thisCache ) {
+
+                               // Support array or space separated string names for data keys
+                               if ( !jQuery.isArray( name ) ) {
+
+                                       // try the string as a key before any manipulation
+                                       if ( name in thisCache ) {
+                                               name = [ name ];
+                                       } else {
+
+                                               // split the camel cased version by spaces unless a key with the spaces exists
+                                               name = jQuery.camelCase( name );
+                                               if ( name in thisCache ) {
+                                                       name = [ name ];
+                                               } else {
+                                                       name = name.split( " " );
+                                               }
+                                       }
+                               }
+
+                               for ( i = 0, l = name.length; i < l; i++ ) {
+                                       delete thisCache[ name[i] ];
+                               }
+
+                               // If there is no data left in the cache, we want to continue
+                               // and let the cache object itself get destroyed
+                               if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+                                       return;
+                               }
+                       }
+               }
+
+               // See jQuery.data for more information
+               if ( !pvt ) {
+                       delete cache[ id ].data;
+
+                       // Don't destroy the parent cache unless the internal data object
+                       // had been the only thing left in it
+                       if ( !isEmptyDataObject(cache[ id ]) ) {
+                               return;
+                       }
+               }
+
+               // Browsers that fail expando deletion also refuse to delete expandos on
+               // the window, but it will allow it on all other JS objects; other browsers
+               // don't care
+               // Ensure that `cache` is not a window object #10080
+               if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+                       delete cache[ id ];
+               } else {
+                       cache[ id ] = null;
+               }
+
+               // We destroyed the cache and need to eliminate the expando on the node to avoid
+               // false lookups in the cache for entries that no longer exist
+               if ( isNode ) {
+                       // IE does not allow us to delete expando properties from nodes,
+                       // nor does it have a removeAttribute function on Document nodes;
+                       // we must handle all of these cases
+                       if ( jQuery.support.deleteExpando ) {
+                               delete elem[ internalKey ];
+                       } else if ( elem.removeAttribute ) {
+                               elem.removeAttribute( internalKey );
+                       } else {
+                               elem[ internalKey ] = null;
+                       }
+               }
+       },
+
+       // For internal use only.
+       _data: function( elem, name, data ) {
+               return jQuery.data( elem, name, data, true );
+       },
+
+       // A method for determining if a DOM node can handle the data expando
+       acceptData: function( elem ) {
+               if ( elem.nodeName ) {
+                       var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+                       if ( match ) {
+                               return !(match === true || elem.getAttribute("classid") !== match);
+                       }
+               }
+
+               return true;
+       }
+});
+
+jQuery.fn.extend({
+       data: function( key, value ) {
+               var parts, part, attr, name, l,
+                       elem = this[0],
+                       i = 0,
+                       data = null;
+
+               // Gets all values
+               if ( key === undefined ) {
+                       if ( this.length ) {
+                               data = jQuery.data( elem );
+
+                               if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+                                       attr = elem.attributes;
+                                       for ( l = attr.length; i < l; i++ ) {
+                                               name = attr[i].name;
+
+                                               if ( name.indexOf( "data-" ) === 0 ) {
+                                                       name = jQuery.camelCase( name.substring(5) );
+
+                                                       dataAttr( elem, name, data[ name ] );
+                                               }
+                                       }
+                                       jQuery._data( elem, "parsedAttrs", true );
+                               }
+                       }
+
+                       return data;
+               }
+
+               // Sets multiple values
+               if ( typeof key === "object" ) {
+                       return this.each(function() {
+                               jQuery.data( this, key );
+                       });
+               }
+
+               parts = key.split( ".", 2 );
+               parts[1] = parts[1] ? "." + parts[1] : "";
+               part = parts[1] + "!";
+
+               return jQuery.access( this, function( value ) {
+
+                       if ( value === undefined ) {
+                               data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+                               // Try to fetch any internally stored data first
+                               if ( data === undefined && elem ) {
+                                       data = jQuery.data( elem, key );
+                                       data = dataAttr( elem, key, data );
+                               }
+
+                               return data === undefined && parts[1] ?
+                                       this.data( parts[0] ) :
+                                       data;
+                       }
+
+                       parts[1] = value;
+                       this.each(function() {
+                               var self = jQuery( this );
+
+                               self.triggerHandler( "setData" + part, parts );
+                               jQuery.data( this, key, value );
+                               self.triggerHandler( "changeData" + part, parts );
+                       });
+               }, null, value, arguments.length > 1, null, false );
+       },
+
+       removeData: function( key ) {
+               return this.each(function() {
+                       jQuery.removeData( this, key );
+               });
+       }
+});
+
+function dataAttr( elem, key, data ) {
+       // If nothing was found internally, try to fetch any
+       // data from the HTML5 data-* attribute
+       if ( data === undefined && elem.nodeType === 1 ) {
+
+               var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+               data = elem.getAttribute( name );
+
+               if ( typeof data === "string" ) {
+                       try {
+                               data = data === "true" ? true :
+                               data === "false" ? false :
+                               data === "null" ? null :
+                               jQuery.isNumeric( data ) ? +data :
+                                       rbrace.test( data ) ? jQuery.parseJSON( data ) :
+                                       data;
+                       } catch( e ) {}
+
+                       // Make sure we set the data so it isn't changed later
+                       jQuery.data( elem, key, data );
+
+               } else {
+                       data = undefined;
+               }
+       }
+
+       return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+       for ( var name in obj ) {
+
+               // if the public data object is empty, the private is still empty
+               if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+                       continue;
+               }
+               if ( name !== "toJSON" ) {
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+       var deferDataKey = type + "defer",
+               queueDataKey = type + "queue",
+               markDataKey = type + "mark",
+               defer = jQuery._data( elem, deferDataKey );
+       if ( defer &&
+               ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+               ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+               // Give room for hard-coded callbacks to fire first
+               // and eventually mark/queue something else on the element
+               setTimeout( function() {
+                       if ( !jQuery._data( elem, queueDataKey ) &&
+                               !jQuery._data( elem, markDataKey ) ) {
+                               jQuery.removeData( elem, deferDataKey, true );
+                               defer.fire();
+                       }
+               }, 0 );
+       }
+}
+
+jQuery.extend({
+
+       _mark: function( elem, type ) {
+               if ( elem ) {
+                       type = ( type || "fx" ) + "mark";
+                       jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+               }
+       },
+
+       _unmark: function( force, elem, type ) {
+               if ( force !== true ) {
+                       type = elem;
+                       elem = force;
+                       force = false;
+               }
+               if ( elem ) {
+                       type = type || "fx";
+                       var key = type + "mark",
+                               count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+                       if ( count ) {
+                               jQuery._data( elem, key, count );
+                       } else {
+                               jQuery.removeData( elem, key, true );
+                               handleQueueMarkDefer( elem, type, "mark" );
+                       }
+               }
+       },
+
+       queue: function( elem, type, data ) {
+               var q;
+               if ( elem ) {
+                       type = ( type || "fx" ) + "queue";
+                       q = jQuery._data( elem, type );
+
+                       // Speed up dequeue by getting out quickly if this is just a lookup
+                       if ( data ) {
+                               if ( !q || jQuery.isArray(data) ) {
+                                       q = jQuery._data( elem, type, jQuery.makeArray(data) );
+                               } else {
+                                       q.push( data );
+                               }
+                       }
+                       return q || [];
+               }
+       },
+
+       dequeue: function( elem, type ) {
+               type = type || "fx";
+
+               var queue = jQuery.queue( elem, type ),
+                       fn = queue.shift(),
+                       hooks = {};
+
+               // If the fx queue is dequeued, always remove the progress sentinel
+               if ( fn === "inprogress" ) {
+                       fn = queue.shift();
+               }
+
+               if ( fn ) {
+                       // Add a progress sentinel to prevent the fx queue from being
+                       // automatically dequeued
+                       if ( type === "fx" ) {
+                               queue.unshift( "inprogress" );
+                       }
+
+                       jQuery._data( elem, type + ".run", hooks );
+                       fn.call( elem, function() {
+                               jQuery.dequeue( elem, type );
+                       }, hooks );
+               }
+
+               if ( !queue.length ) {
+                       jQuery.removeData( elem, type + "queue " + type + ".run", true );
+                       handleQueueMarkDefer( elem, type, "queue" );
+               }
+       }
+});
+
+jQuery.fn.extend({
+       queue: function( type, data ) {
+               var setter = 2;
+
+               if ( typeof type !== "string" ) {
+                       data = type;
+                       type = "fx";
+                       setter--;
+               }
+
+               if ( arguments.length < setter ) {
+                       return jQuery.queue( this[0], type );
+               }
+
+               return data === undefined ?
+                       this :
+                       this.each(function() {
+                               var queue = jQuery.queue( this, type, data );
+
+                               if ( type === "fx" && queue[0] !== "inprogress" ) {
+                                       jQuery.dequeue( this, type );
+                               }
+                       });
+       },
+       dequeue: function( type ) {
+               return this.each(function() {
+                       jQuery.dequeue( this, type );
+               });
+       },
+       // Based off of the plugin by Clint Helfers, with permission.
+       // http://blindsignals.com/index.php/2009/07/jquery-delay/
+       delay: function( time, type ) {
+               time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+               type = type || "fx";
+
+               return this.queue( type, function( next, hooks ) {
+                       var timeout = setTimeout( next, time );
+                       hooks.stop = function() {
+                               clearTimeout( timeout );
+                       };
+               });
+       },
+       clearQueue: function( type ) {
+               return this.queue( type || "fx", [] );
+       },
+       // Get a promise resolved when queues of a certain type
+       // are emptied (fx is the type by default)
+       promise: function( type, object ) {
+               if ( typeof type !== "string" ) {
+                       object = type;
+                       type = undefined;
+               }
+               type = type || "fx";
+               var defer = jQuery.Deferred(),
+                       elements = this,
+                       i = elements.length,
+                       count = 1,
+                       deferDataKey = type + "defer",
+                       queueDataKey = type + "queue",
+                       markDataKey = type + "mark",
+                       tmp;
+               function resolve() {
+                       if ( !( --count ) ) {
+                               defer.resolveWith( elements, [ elements ] );
+                       }
+               }
+               while( i-- ) {
+                       if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+                                       ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+                                               jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+                                       jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+                               count++;
+                               tmp.add( resolve );
+                       }
+               }
+               resolve();
+               return defer.promise( object );
+       }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+       rspace = /\s+/,
+       rreturn = /\r/g,
+       rtype = /^(?:button|input)$/i,
+       rfocusable = /^(?:button|input|object|select|textarea)$/i,
+       rclickable = /^a(?:rea)?$/i,
+       rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+       getSetAttribute = jQuery.support.getSetAttribute,
+       nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+       attr: function( name, value ) {
+               return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+       },
+
+       removeAttr: function( name ) {
+               return this.each(function() {
+                       jQuery.removeAttr( this, name );
+               });
+       },
+
+       prop: function( name, value ) {
+               return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+       },
+
+       removeProp: function( name ) {
+               name = jQuery.propFix[ name ] || name;
+               return this.each(function() {
+                       // try/catch handles cases where IE balks (such as removing a property on window)
+                       try {
+                               this[ name ] = undefined;
+                               delete this[ name ];
+                       } catch( e ) {}
+               });
+       },
+
+       addClass: function( value ) {
+               var classNames, i, l, elem,
+                       setClass, c, cl;
+
+               if ( jQuery.isFunction( value ) ) {
+                       return this.each(function( j ) {
+                               jQuery( this ).addClass( value.call(this, j, this.className) );
+                       });
+               }
+
+               if ( value && typeof value === "string" ) {
+                       classNames = value.split( rspace );
+
+                       for ( i = 0, l = this.length; i < l; i++ ) {
+                               elem = this[ i ];
+
+                               if ( elem.nodeType === 1 ) {
+                                       if ( !elem.className && classNames.length === 1 ) {
+                                               elem.className = value;
+
+                                       } else {
+                                               setClass = " " + elem.className + " ";
+
+                                               for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+                                                       if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+                                                               setClass += classNames[ c ] + " ";
+                                                       }
+                                               }
+                                               elem.className = jQuery.trim( setClass );
+                                       }
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       removeClass: function( value ) {
+               var classNames, i, l, elem, className, c, cl;
+
+               if ( jQuery.isFunction( value ) ) {
+                       return this.each(function( j ) {
+                               jQuery( this ).removeClass( value.call(this, j, this.className) );
+                       });
+               }
+
+               if ( (value && typeof value === "string") || value === undefined ) {
+                       classNames = ( value || "" ).split( rspace );
+
+                       for ( i = 0, l = this.length; i < l; i++ ) {
+                               elem = this[ i ];
+
+                               if ( elem.nodeType === 1 && elem.className ) {
+                                       if ( value ) {
+                                               className = (" " + elem.className + " ").replace( rclass, " " );
+                                               for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+                                                       className = className.replace(" " + classNames[ c ] + " ", " ");
+                                               }
+                                               elem.className = jQuery.trim( className );
+
+                                       } else {
+                                               elem.className = "";
+                                       }
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       toggleClass: function( value, stateVal ) {
+               var type = typeof value,
+                       isBool = typeof stateVal === "boolean";
+
+               if ( jQuery.isFunction( value ) ) {
+                       return this.each(function( i ) {
+                               jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+                       });
+               }
+
+               return this.each(function() {
+                       if ( type === "string" ) {
+                               // toggle individual class names
+                               var className,
+                                       i = 0,
+                                       self = jQuery( this ),
+                                       state = stateVal,
+                                       classNames = value.split( rspace );
+
+                               while ( (className = classNames[ i++ ]) ) {
+                                       // check each className given, space seperated list
+                                       state = isBool ? state : !self.hasClass( className );
+                                       self[ state ? "addClass" : "removeClass" ]( className );
+                               }
+
+                       } else if ( type === "undefined" || type === "boolean" ) {
+                               if ( this.className ) {
+                                       // store className if set
+                                       jQuery._data( this, "__className__", this.className );
+                               }
+
+                               // toggle whole className
+                               this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+                       }
+               });
+       },
+
+       hasClass: function( selector ) {
+               var className = " " + selector + " ",
+                       i = 0,
+                       l = this.length;
+               for ( ; i < l; i++ ) {
+                       if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       },
+
+       val: function( value ) {
+               var hooks, ret, isFunction,
+                       elem = this[0];
+
+               if ( !arguments.length ) {
+                       if ( elem ) {
+                               hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+                               if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+                                       return ret;
+                               }
+
+                               ret = elem.value;
+
+                               return typeof ret === "string" ?
+                                       // handle most common string cases
+                                       ret.replace(rreturn, "") :
+                                       // handle cases where value is null/undef or number
+                                       ret == null ? "" : ret;
+                       }
+
+                       return;
+               }
+
+               isFunction = jQuery.isFunction( value );
+
+               return this.each(function( i ) {
+                       var self = jQuery(this), val;
+
+                       if ( this.nodeType !== 1 ) {
+                               return;
+                       }
+
+                       if ( isFunction ) {
+                               val = value.call( this, i, self.val() );
+                       } else {
+                               val = value;
+                       }
+
+                       // Treat null/undefined as ""; convert numbers to string
+                       if ( val == null ) {
+                               val = "";
+                       } else if ( typeof val === "number" ) {
+                               val += "";
+                       } else if ( jQuery.isArray( val ) ) {
+                               val = jQuery.map(val, function ( value ) {
+                                       return value == null ? "" : value + "";
+                               });
+                       }
+
+                       hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+                       // If set returns undefined, fall back to normal setting
+                       if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+                               this.value = val;
+                       }
+               });
+       }
+});
+
+jQuery.extend({
+       valHooks: {
+               option: {
+                       get: function( elem ) {
+                               // attributes.value is undefined in Blackberry 4.7 but
+                               // uses .value. See #6932
+                               var val = elem.attributes.value;
+                               return !val || val.specified ? elem.value : elem.text;
+                       }
+               },
+               select: {
+                       get: function( elem ) {
+                               var value, i, max, option,
+                                       index = elem.selectedIndex,
+                                       values = [],
+                                       options = elem.options,
+                                       one = elem.type === "select-one";
+
+                               // Nothing was selected
+                               if ( index < 0 ) {
+                                       return null;
+                               }
+
+                               // Loop through all the selected options
+                               i = one ? index : 0;
+                               max = one ? index + 1 : options.length;
+                               for ( ; i < max; i++ ) {
+                                       option = options[ i ];
+
+                                       // Don't return options that are disabled or in a disabled optgroup
+                                       if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+                                                       (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+                                               // Get the specific value for the option
+                                               value = jQuery( option ).val();
+
+                                               // We don't need an array for one selects
+                                               if ( one ) {
+                                                       return value;
+                                               }
+
+                                               // Multi-Selects return an array
+                                               values.push( value );
+                                       }
+                               }
+
+                               // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+                               if ( one && !values.length && options.length ) {
+                                       return jQuery( options[ index ] ).val();
+                               }
+
+                               return values;
+                       },
+
+                       set: function( elem, value ) {
+                               var values = jQuery.makeArray( value );
+
+                               jQuery(elem).find("option").each(function() {
+                                       this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+                               });
+
+                               if ( !values.length ) {
+                                       elem.selectedIndex = -1;
+                               }
+                               return values;
+                       }
+               }
+       },
+
+       attrFn: {
+               val: true,
+               css: true,
+               html: true,
+               text: true,
+               data: true,
+               width: true,
+               height: true,
+               offset: true
+       },
+
+       attr: function( elem, name, value, pass ) {
+               var ret, hooks, notxml,
+                       nType = elem.nodeType;
+
+               // don't get/set attributes on text, comment and attribute nodes
+               if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+                       return;
+               }
+
+               if ( pass && name in jQuery.attrFn ) {
+                       return jQuery( elem )[ name ]( value );
+               }
+
+               // Fallback to prop when attributes are not supported
+               if ( typeof elem.getAttribute === "undefined" ) {
+                       return jQuery.prop( elem, name, value );
+               }
+
+               notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+               // All attributes are lowercase
+               // Grab necessary hook if one is defined
+               if ( notxml ) {
+                       name = name.toLowerCase();
+                       hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+               }
+
+               if ( value !== undefined ) {
+
+                       if ( value === null ) {
+                               jQuery.removeAttr( elem, name );
+                               return;
+
+                       } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+                               return ret;
+
+                       } else {
+                               elem.setAttribute( name, "" + value );
+                               return value;
+                       }
+
+               } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+                       return ret;
+
+               } else {
+
+                       ret = elem.getAttribute( name );
+
+                       // Non-existent attributes return null, we normalize to undefined
+                       return ret === null ?
+                               undefined :
+                               ret;
+               }
+       },
+
+       removeAttr: function( elem, value ) {
+               var propName, attrNames, name, l, isBool,
+                       i = 0;
+
+               if ( value && elem.nodeType === 1 ) {
+                       attrNames = value.toLowerCase().split( rspace );
+                       l = attrNames.length;
+
+                       for ( ; i < l; i++ ) {
+                               name = attrNames[ i ];
+
+                               if ( name ) {
+                                       propName = jQuery.propFix[ name ] || name;
+                                       isBool = rboolean.test( name );
+
+                                       // See #9699 for explanation of this approach (setting first, then removal)
+                                       // Do not do this for boolean attributes (see #10870)
+                                       if ( !isBool ) {
+                                               jQuery.attr( elem, name, "" );
+                                       }
+                                       elem.removeAttribute( getSetAttribute ? name : propName );
+
+                                       // Set corresponding property to false for boolean attributes
+                                       if ( isBool && propName in elem ) {
+                                               elem[ propName ] = false;
+                                       }
+                               }
+                       }
+               }
+       },
+
+       attrHooks: {
+               type: {
+                       set: function( elem, value ) {
+                               // We can't allow the type property to be changed (since it causes problems in IE)
+                               if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+                                       jQuery.error( "type property can't be changed" );
+                               } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+                                       // Setting the type on a radio button after the value resets the value in IE6-9
+                                       // Reset value to it's default in case type is set after value
+                                       // This is for element creation
+                                       var val = elem.value;
+                                       elem.setAttribute( "type", value );
+                                       if ( val ) {
+                                               elem.value = val;
+                                       }
+                                       return value;
+                               }
+                       }
+               },
+               // Use the value property for back compat
+               // Use the nodeHook for button elements in IE6/7 (#1954)
+               value: {
+                       get: function( elem, name ) {
+                               if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+                                       return nodeHook.get( elem, name );
+                               }
+                               return name in elem ?
+                                       elem.value :
+                                       null;
+                       },
+                       set: function( elem, value, name ) {
+                               if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+                                       return nodeHook.set( elem, value, name );
+                               }
+                               // Does not return so that setAttribute is also used
+                               elem.value = value;
+                       }
+               }
+       },
+
+       propFix: {
+               tabindex: "tabIndex",
+               readonly: "readOnly",
+               "for": "htmlFor",
+               "class": "className",
+               maxlength: "maxLength",
+               cellspacing: "cellSpacing",
+               cellpadding: "cellPadding",
+               rowspan: "rowSpan",
+               colspan: "colSpan",
+               usemap: "useMap",
+               frameborder: "frameBorder",
+               contenteditable: "contentEditable"
+       },
+
+       prop: function( elem, name, value ) {
+               var ret, hooks, notxml,
+                       nType = elem.nodeType;
+
+               // don't get/set properties on text, comment and attribute nodes
+               if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+                       return;
+               }
+
+               notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+               if ( notxml ) {
+                       // Fix name and attach hooks
+                       name = jQuery.propFix[ name ] || name;
+                       hooks = jQuery.propHooks[ name ];
+               }
+
+               if ( value !== undefined ) {
+                       if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+                               return ret;
+
+                       } else {
+                               return ( elem[ name ] = value );
+                       }
+
+               } else {
+                       if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+                               return ret;
+
+                       } else {
+                               return elem[ name ];
+                       }
+               }
+       },
+
+       propHooks: {
+               tabIndex: {
+                       get: function( elem ) {
+                               // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+                               // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+                               var attributeNode = elem.getAttributeNode("tabindex");
+
+                               return attributeNode && attributeNode.specified ?
+                                       parseInt( attributeNode.value, 10 ) :
+                                       rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+                                               0 :
+                                               undefined;
+                       }
+               }
+       }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+       get: function( elem, name ) {
+               // Align boolean attributes with corresponding properties
+               // Fall back to attribute presence where some booleans are not supported
+               var attrNode,
+                       property = jQuery.prop( elem, name );
+               return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+                       name.toLowerCase() :
+                       undefined;
+       },
+       set: function( elem, value, name ) {
+               var propName;
+               if ( value === false ) {
+                       // Remove boolean attributes when set to false
+                       jQuery.removeAttr( elem, name );
+               } else {
+                       // value is true since we know at this point it's type boolean and not false
+                       // Set boolean attributes to the same name and set the DOM property
+                       propName = jQuery.propFix[ name ] || name;
+                       if ( propName in elem ) {
+                               // Only set the IDL specifically if it already exists on the element
+                               elem[ propName ] = true;
+                       }
+
+                       elem.setAttribute( name, name.toLowerCase() );
+               }
+               return name;
+       }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+       fixSpecified = {
+               name: true,
+               id: true,
+               coords: true
+       };
+
+       // Use this for any attribute in IE6/7
+       // This fixes almost every IE6/7 issue
+       nodeHook = jQuery.valHooks.button = {
+               get: function( elem, name ) {
+                       var ret;
+                       ret = elem.getAttributeNode( name );
+                       return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+                               ret.nodeValue :
+                               undefined;
+               },
+               set: function( elem, value, name ) {
+                       // Set the existing or create a new attribute node
+                       var ret = elem.getAttributeNode( name );
+                       if ( !ret ) {
+                               ret = document.createAttribute( name );
+                               elem.setAttributeNode( ret );
+                       }
+                       return ( ret.nodeValue = value + "" );
+               }
+       };
+
+       // Apply the nodeHook to tabindex
+       jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+       // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+       // This is for removals
+       jQuery.each([ "width", "height" ], function( i, name ) {
+               jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+                       set: function( elem, value ) {
+                               if ( value === "" ) {
+                                       elem.setAttribute( name, "auto" );
+                                       return value;
+                               }
+                       }
+               });
+       });
+
+       // Set contenteditable to false on removals(#10429)
+       // Setting to empty string throws an error as an invalid value
+       jQuery.attrHooks.contenteditable = {
+               get: nodeHook.get,
+               set: function( elem, value, name ) {
+                       if ( value === "" ) {
+                               value = "false";
+                       }
+                       nodeHook.set( elem, value, name );
+               }
+       };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+       jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+               jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+                       get: function( elem ) {
+                               var ret = elem.getAttribute( name, 2 );
+                               return ret === null ? undefined : ret;
+                       }
+               });
+       });
+}
+
+if ( !jQuery.support.style ) {
+       jQuery.attrHooks.style = {
+               get: function( elem ) {
+                       // Return undefined in the case of empty string
+                       // Normalize to lowercase since IE uppercases css property names
+                       return elem.style.cssText.toLowerCase() || undefined;
+               },
+               set: function( elem, value ) {
+                       return ( elem.style.cssText = "" + value );
+               }
+       };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+       jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+               get: function( elem ) {
+                       var parent = elem.parentNode;
+
+                       if ( parent ) {
+                               parent.selectedIndex;
+
+                               // Make sure that it also works with optgroups, see #5701
+                               if ( parent.parentNode ) {
+                                       parent.parentNode.selectedIndex;
+                               }
+                       }
+                       return null;
+               }
+       });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+       jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+       jQuery.each([ "radio", "checkbox" ], function() {
+               jQuery.valHooks[ this ] = {
+                       get: function( elem ) {
+                               // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+                               return elem.getAttribute("value") === null ? "on" : elem.value;
+                       }
+               };
+       });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+       jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+               set: function( elem, value ) {
+                       if ( jQuery.isArray( value ) ) {
+                               return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+                       }
+               }
+       });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+       rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+       rhoverHack = /(?:^|\s)hover(\.\S+)?\b/,
+       rkeyEvent = /^key/,
+       rmouseEvent = /^(?:mouse|contextmenu)|click/,
+       rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+       rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+       quickParse = function( selector ) {
+               var quick = rquickIs.exec( selector );
+               if ( quick ) {
+                       //   0  1    2   3
+                       // [ _, tag, id, class ]
+                       quick[1] = ( quick[1] || "" ).toLowerCase();
+                       quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+               }
+               return quick;
+       },
+       quickIs = function( elem, m ) {
+               var attrs = elem.attributes || {};
+               return (
+                       (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+                       (!m[2] || (attrs.id || {}).value === m[2]) &&
+                       (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+               );
+       },
+       hoverHack = function( events ) {
+               return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+       };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+       add: function( elem, types, handler, data, selector ) {
+
+               var elemData, eventHandle, events,
+                       t, tns, type, namespaces, handleObj,
+                       handleObjIn, quick, handlers, special;
+
+               // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+               if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+                       return;
+               }
+
+               // Caller can pass in an object of custom data in lieu of the handler
+               if ( handler.handler ) {
+                       handleObjIn = handler;
+                       handler = handleObjIn.handler;
+                       selector = handleObjIn.selector;
+               }
+
+               // Make sure that the handler has a unique ID, used to find/remove it later
+               if ( !handler.guid ) {
+                       handler.guid = jQuery.guid++;
+               }
+
+               // Init the element's event structure and main handler, if this is the first
+               events = elemData.events;
+               if ( !events ) {
+                       elemData.events = events = {};
+               }
+               eventHandle = elemData.handle;
+               if ( !eventHandle ) {
+                       elemData.handle = eventHandle = function( e ) {
+                               // Discard the second event of a jQuery.event.trigger() and
+                               // when an event is called after a page has unloaded
+                               return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+                                       jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+                                       undefined;
+                       };
+                       // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+                       eventHandle.elem = elem;
+               }
+
+               // Handle multiple events separated by a space
+               // jQuery(...).bind("mouseover mouseout", fn);
+               types = jQuery.trim( hoverHack(types) ).split( " " );
+               for ( t = 0; t < types.length; t++ ) {
+
+                       tns = rtypenamespace.exec( types[t] ) || [];
+                       type = tns[1];
+                       namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+                       // If event changes its type, use the special event handlers for the changed type
+                       special = jQuery.event.special[ type ] || {};
+
+                       // If selector defined, determine special event api type, otherwise given type
+                       type = ( selector ? special.delegateType : special.bindType ) || type;
+
+                       // Update special based on newly reset type
+                       special = jQuery.event.special[ type ] || {};
+
+                       // handleObj is passed to all event handlers
+                       handleObj = jQuery.extend({
+                               type: type,
+                               origType: tns[1],
+                               data: data,
+                               handler: handler,
+                               guid: handler.guid,
+                               selector: selector,
+                               quick: selector && quickParse( selector ),
+                               namespace: namespaces.join(".")
+                       }, handleObjIn );
+
+                       // Init the event handler queue if we're the first
+                       handlers = events[ type ];
+                       if ( !handlers ) {
+                               handlers = events[ type ] = [];
+                               handlers.delegateCount = 0;
+
+                               // Only use addEventListener/attachEvent if the special events handler returns false
+                               if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+                                       // Bind the global event handler to the element
+                                       if ( elem.addEventListener ) {
+                                               elem.addEventListener( type, eventHandle, false );
+
+                                       } else if ( elem.attachEvent ) {
+                                               elem.attachEvent( "on" + type, eventHandle );
+                                       }
+                               }
+                       }
+
+                       if ( special.add ) {
+                               special.add.call( elem, handleObj );
+
+                               if ( !handleObj.handler.guid ) {
+                                       handleObj.handler.guid = handler.guid;
+                               }
+                       }
+
+                       // Add to the element's handler list, delegates in front
+                       if ( selector ) {
+                               handlers.splice( handlers.delegateCount++, 0, handleObj );
+                       } else {
+                               handlers.push( handleObj );
+                       }
+
+                       // Keep track of which events have ever been used, for event optimization
+                       jQuery.event.global[ type ] = true;
+               }
+
+               // Nullify elem to prevent memory leaks in IE
+               elem = null;
+       },
+
+       global: {},
+
+       // Detach an event or set of events from an element
+       remove: function( elem, types, handler, selector, mappedTypes ) {
+
+               var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+                       t, tns, type, origType, namespaces, origCount,
+                       j, events, special, handle, eventType, handleObj;
+
+               if ( !elemData || !(events = elemData.events) ) {
+                       return;
+               }
+
+               // Once for each type.namespace in types; type may be omitted
+               types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+               for ( t = 0; t < types.length; t++ ) {
+                       tns = rtypenamespace.exec( types[t] ) || [];
+                       type = origType = tns[1];
+                       namespaces = tns[2];
+
+                       // Unbind all events (on this namespace, if provided) for the element
+                       if ( !type ) {
+                               for ( type in events ) {
+                                       jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+                               }
+                               continue;
+                       }
+
+                       special = jQuery.event.special[ type ] || {};
+                       type = ( selector? special.delegateType : special.bindType ) || type;
+                       eventType = events[ type ] || [];
+                       origCount = eventType.length;
+                       namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+                       // Remove matching events
+                       for ( j = 0; j < eventType.length; j++ ) {
+                               handleObj = eventType[ j ];
+
+                               if ( ( mappedTypes || origType === handleObj.origType ) &&
+                                        ( !handler || handler.guid === handleObj.guid ) &&
+                                        ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+                                        ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+                                       eventType.splice( j--, 1 );
+
+                                       if ( handleObj.selector ) {
+                                               eventType.delegateCount--;
+                                       }
+                                       if ( special.remove ) {
+                                               special.remove.call( elem, handleObj );
+                                       }
+                               }
+                       }
+
+                       // Remove generic event handler if we removed something and no more handlers exist
+                       // (avoids potential for endless recursion during removal of special event handlers)
+                       if ( eventType.length === 0 && origCount !== eventType.length ) {
+                               if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+                                       jQuery.removeEvent( elem, type, elemData.handle );
+                               }
+
+                               delete events[ type ];
+                       }
+               }
+
+               // Remove the expando if it's no longer used
+               if ( jQuery.isEmptyObject( events ) ) {
+                       handle = elemData.handle;
+                       if ( handle ) {
+                               handle.elem = null;
+                       }
+
+                       // removeData also checks for emptiness and clears the expando if empty
+                       // so use it instead of delete
+                       jQuery.removeData( elem, [ "events", "handle" ], true );
+               }
+       },
+
+       // Events that are safe to short-circuit if no handlers are attached.
+       // Native DOM events should not be added, they may have inline handlers.
+       customEvent: {
+               "getData": true,
+               "setData": true,
+               "changeData": true
+       },
+
+       trigger: function( event, data, elem, onlyHandlers ) {
+               // Don't do events on text and comment nodes
+               if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+                       return;
+               }
+
+               // Event object or event type
+               var type = event.type || event,
+                       namespaces = [],
+                       cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+               // focus/blur morphs to focusin/out; ensure we're not firing them right now
+               if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+                       return;
+               }
+
+               if ( type.indexOf( "!" ) >= 0 ) {
+                       // Exclusive events trigger only for the exact event (no namespaces)
+                       type = type.slice(0, -1);
+                       exclusive = true;
+               }
+
+               if ( type.indexOf( "." ) >= 0 ) {
+                       // Namespaced trigger; create a regexp to match event type in handle()
+                       namespaces = type.split(".");
+                       type = namespaces.shift();
+                       namespaces.sort();
+               }
+
+               if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+                       // No jQuery handlers for this event type, and it can't have inline handlers
+                       return;
+               }
+
+               // Caller can pass in an Event, Object, or just an event type string
+               event = typeof event === "object" ?
+                       // jQuery.Event object
+                       event[ jQuery.expando ] ? event :
+                       // Object literal
+                       new jQuery.Event( type, event ) :
+                       // Just the event type (string)
+                       new jQuery.Event( type );
+
+               event.type = type;
+               event.isTrigger = true;
+               event.exclusive = exclusive;
+               event.namespace = namespaces.join( "." );
+               event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+               ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+               // Handle a global trigger
+               if ( !elem ) {
+
+                       // TODO: Stop taunting the data cache; remove global events and always attach to document
+                       cache = jQuery.cache;
+                       for ( i in cache ) {
+                               if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+                                       jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+                               }
+                       }
+                       return;
+               }
+
+               // Clean up the event in case it is being reused
+               event.result = undefined;
+               if ( !event.target ) {
+                       event.target = elem;
+               }
+
+               // Clone any incoming data and prepend the event, creating the handler arg list
+               data = data != null ? jQuery.makeArray( data ) : [];
+               data.unshift( event );
+
+               // Allow special events to draw outside the lines
+               special = jQuery.event.special[ type ] || {};
+               if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+                       return;
+               }
+
+               // Determine event propagation path in advance, per W3C events spec (#9951)
+               // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+               eventPath = [[ elem, special.bindType || type ]];
+               if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+                       bubbleType = special.delegateType || type;
+                       cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+                       old = null;
+                       for ( ; cur; cur = cur.parentNode ) {
+                               eventPath.push([ cur, bubbleType ]);
+                               old = cur;
+                       }
+
+                       // Only add window if we got to document (e.g., not plain obj or detached DOM)
+                       if ( old && old === elem.ownerDocument ) {
+                               eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+                       }
+               }
+
+               // Fire handlers on the event path
+               for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+                       cur = eventPath[i][0];
+                       event.type = eventPath[i][1];
+
+                       handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+                       if ( handle ) {
+                               handle.apply( cur, data );
+                       }
+                       // Note that this is a bare JS function and not a jQuery handler
+                       handle = ontype && cur[ ontype ];
+                       if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+                               event.preventDefault();
+                       }
+               }
+               event.type = type;
+
+               // If nobody prevented the default action, do it now
+               if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+                       if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+                               !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+                               // Call a native DOM method on the target with the same name name as the event.
+                               // Can't use an .isFunction() check here because IE6/7 fails that test.
+                               // Don't do default actions on window, that's where global variables be (#6170)
+                               // IE<9 dies on focus/blur to hidden element (#1486)
+                               if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+                                       // Don't re-trigger an onFOO event when we call its FOO() method
+                                       old = elem[ ontype ];
+
+                                       if ( old ) {
+                                               elem[ ontype ] = null;
+                                       }
+
+                                       // Prevent re-triggering of the same event, since we already bubbled it above
+                                       jQuery.event.triggered = type;
+                                       elem[ type ]();
+                                       jQuery.event.triggered = undefined;
+
+                                       if ( old ) {
+                                               elem[ ontype ] = old;
+                                       }
+                               }
+                       }
+               }
+
+               return event.result;
+       },
+
+       dispatch: function( event ) {
+
+               // Make a writable jQuery.Event from the native event object
+               event = jQuery.event.fix( event || window.event );
+
+               var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+                       delegateCount = handlers.delegateCount,
+                       args = [].slice.call( arguments, 0 ),
+                       run_all = !event.exclusive && !event.namespace,
+                       special = jQuery.event.special[ event.type ] || {},
+                       handlerQueue = [],
+                       i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+               // Use the fix-ed jQuery.Event rather than the (read-only) native event
+               args[0] = event;
+               event.delegateTarget = this;
+
+               // Call the preDispatch hook for the mapped type, and let it bail if desired
+               if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+                       return;
+               }
+
+               // Determine handlers that should run if there are delegated events
+               // Avoid non-left-click bubbling in Firefox (#3861)
+               if ( delegateCount && !(event.button && event.type === "click") ) {
+
+                       // Pregenerate a single jQuery object for reuse with .is()
+                       jqcur = jQuery(this);
+                       jqcur.context = this.ownerDocument || this;
+
+                       for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+                               // Don't process events on disabled elements (#6911, #8165)
+                               if ( cur.disabled !== true ) {
+                                       selMatch = {};
+                                       matches = [];
+                                       jqcur[0] = cur;
+                                       for ( i = 0; i < delegateCount; i++ ) {
+                                               handleObj = handlers[ i ];
+                                               sel = handleObj.selector;
+
+                                               if ( selMatch[ sel ] === undefined ) {
+                                                       selMatch[ sel ] = (
+                                                               handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+                                                       );
+                                               }
+                                               if ( selMatch[ sel ] ) {
+                                                       matches.push( handleObj );
+                                               }
+                                       }
+                                       if ( matches.length ) {
+                                               handlerQueue.push({ elem: cur, matches: matches });
+                                       }
+                               }
+                       }
+               }
+
+               // Add the remaining (directly-bound) handlers
+               if ( handlers.length > delegateCount ) {
+                       handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+               }
+
+               // Run delegates first; they may want to stop propagation beneath us
+               for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+                       matched = handlerQueue[ i ];
+                       event.currentTarget = matched.elem;
+
+                       for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+                               handleObj = matched.matches[ j ];
+
+                               // Triggered event must either 1) be non-exclusive and have no namespace, or
+                               // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+                               if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+                                       event.data = handleObj.data;
+                                       event.handleObj = handleObj;
+
+                                       ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+                                                       .apply( matched.elem, args );
+
+                                       if ( ret !== undefined ) {
+                                               event.result = ret;
+                                               if ( ret === false ) {
+                                                       event.preventDefault();
+                                                       event.stopPropagation();
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               // Call the postDispatch hook for the mapped type
+               if ( special.postDispatch ) {
+                       special.postDispatch.call( this, event );
+               }
+
+               return event.result;
+       },
+
+       // Includes some event props shared by KeyEvent and MouseEvent
+       // *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+       props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+       fixHooks: {},
+
+       keyHooks: {
+               props: "char charCode key keyCode".split(" "),
+               filter: function( event, original ) {
+
+                       // Add which for key events
+                       if ( event.which == null ) {
+                               event.which = original.charCode != null ? original.charCode : original.keyCode;
+                       }
+
+                       return event;
+               }
+       },
+
+       mouseHooks: {
+               props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+               filter: function( event, original ) {
+                       var eventDoc, doc, body,
+                               button = original.button,
+                               fromElement = original.fromElement;
+
+                       // Calculate pageX/Y if missing and clientX/Y available
+                       if ( event.pageX == null && original.clientX != null ) {
+                               eventDoc = event.target.ownerDocument || document;
+                               doc = eventDoc.documentElement;
+                               body = eventDoc.body;
+
+                               event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+                               event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
+                       }
+
+                       // Add relatedTarget, if necessary
+                       if ( !event.relatedTarget && fromElement ) {
+                               event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+                       }
+
+                       // Add which for click: 1 === left; 2 === middle; 3 === right
+                       // Note: button is not normalized, so don't use it
+                       if ( !event.which && button !== undefined ) {
+                               event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+                       }
+
+                       return event;
+               }
+       },
+
+       fix: function( event ) {
+               if ( event[ jQuery.expando ] ) {
+                       return event;
+               }
+
+               // Create a writable copy of the event object and normalize some properties
+               var i, prop,
+                       originalEvent = event,
+                       fixHook = jQuery.event.fixHooks[ event.type ] || {},
+                       copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+               event = jQuery.Event( originalEvent );
+
+               for ( i = copy.length; i; ) {
+                       prop = copy[ --i ];
+                       event[ prop ] = originalEvent[ prop ];
+               }
+
+               // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+               if ( !event.target ) {
+                       event.target = originalEvent.srcElement || document;
+               }
+
+               // Target should not be a text node (#504, Safari)
+               if ( event.target.nodeType === 3 ) {
+                       event.target = event.target.parentNode;
+               }
+
+               // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+               if ( event.metaKey === undefined ) {
+                       event.metaKey = event.ctrlKey;
+               }
+
+               return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+       },
+
+       special: {
+               ready: {
+                       // Make sure the ready event is setup
+                       setup: jQuery.bindReady
+               },
+
+               load: {
+                       // Prevent triggered image.load events from bubbling to window.load
+                       noBubble: true
+               },
+
+               focus: {
+                       delegateType: "focusin"
+               },
+               blur: {
+                       delegateType: "focusout"
+               },
+
+               beforeunload: {
+                       setup: function( data, namespaces, eventHandle ) {
+                               // We only want to do this special case on windows
+                               if ( jQuery.isWindow( this ) ) {
+                                       this.onbeforeunload = eventHandle;
+                               }
+                       },
+
+                       teardown: function( namespaces, eventHandle ) {
+                               if ( this.onbeforeunload === eventHandle ) {
+                                       this.onbeforeunload = null;
+                               }
+                       }
+               }
+       },
+
+       simulate: function( type, elem, event, bubble ) {
+               // Piggyback on a donor event to simulate a different one.
+               // Fake originalEvent to avoid donor's stopPropagation, but if the
+               // simulated event prevents default then we do the same on the donor.
+               var e = jQuery.extend(
+                       new jQuery.Event(),
+                       event,
+                       { type: type,
+                               isSimulated: true,
+                               originalEvent: {}
+                       }
+               );
+               if ( bubble ) {
+                       jQuery.event.trigger( e, null, elem );
+               } else {
+                       jQuery.event.dispatch.call( elem, e );
+               }
+               if ( e.isDefaultPrevented() ) {
+                       event.preventDefault();
+               }
+       }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+       function( elem, type, handle ) {
+               if ( elem.removeEventListener ) {
+                       elem.removeEventListener( type, handle, false );
+               }
+       } :
+       function( elem, type, handle ) {
+               if ( elem.detachEvent ) {
+                       elem.detachEvent( "on" + type, handle );
+               }
+       };
+
+jQuery.Event = function( src, props ) {
+       // Allow instantiation without the 'new' keyword
+       if ( !(this instanceof jQuery.Event) ) {
+               return new jQuery.Event( src, props );
+       }
+
+       // Event object
+       if ( src && src.type ) {
+               this.originalEvent = src;
+               this.type = src.type;
+
+               // Events bubbling up the document may have been marked as prevented
+               // by a handler lower down the tree; reflect the correct value.
+               this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+                       src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+       // Event type
+       } else {
+               this.type = src;
+       }
+
+       // Put explicitly provided properties onto the event object
+       if ( props ) {
+               jQuery.extend( this, props );
+       }
+
+       // Create a timestamp if incoming event doesn't have one
+       this.timeStamp = src && src.timeStamp || jQuery.now();
+
+       // Mark it as fixed
+       this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+       return false;
+}
+function returnTrue() {
+       return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+       preventDefault: function() {
+               this.isDefaultPrevented = returnTrue;
+
+               var e = this.originalEvent;
+               if ( !e ) {
+                       return;
+               }
+
+               // if preventDefault exists run it on the original event
+               if ( e.preventDefault ) {
+                       e.preventDefault();
+
+               // otherwise set the returnValue property of the original event to false (IE)
+               } else {
+                       e.returnValue = false;
+               }
+       },
+       stopPropagation: function() {
+               this.isPropagationStopped = returnTrue;
+
+               var e = this.originalEvent;
+               if ( !e ) {
+                       return;
+               }
+               // if stopPropagation exists run it on the original event
+               if ( e.stopPropagation ) {
+                       e.stopPropagation();
+               }
+               // otherwise set the cancelBubble property of the original event to true (IE)
+               e.cancelBubble = true;
+       },
+       stopImmediatePropagation: function() {
+               this.isImmediatePropagationStopped = returnTrue;
+               this.stopPropagation();
+       },
+       isDefaultPrevented: returnFalse,
+       isPropagationStopped: returnFalse,
+       isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+       mouseenter: "mouseover",
+       mouseleave: "mouseout"
+}, function( orig, fix ) {
+       jQuery.event.special[ orig ] = {
+               delegateType: fix,
+               bindType: fix,
+
+               handle: function( event ) {
+                       var target = this,
+                               related = event.relatedTarget,
+                               handleObj = event.handleObj,
+                               selector = handleObj.selector,
+                               ret;
+
+                       // For mousenter/leave call the handler if related is outside the target.
+                       // NB: No relatedTarget if the mouse left/entered the browser window
+                       if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+                               event.type = handleObj.origType;
+                               ret = handleObj.handler.apply( this, arguments );
+                               event.type = fix;
+                       }
+                       return ret;
+               }
+       };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+       jQuery.event.special.submit = {
+               setup: function() {
+                       // Only need this for delegated form submit events
+                       if ( jQuery.nodeName( this, "form" ) ) {
+                               return false;
+                       }
+
+                       // Lazy-add a submit handler when a descendant form may potentially be submitted
+                       jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+                               // Node name check avoids a VML-related crash in IE (#9807)
+                               var elem = e.target,
+                                       form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+                               if ( form && !form._submit_attached ) {
+                                       jQuery.event.add( form, "submit._submit", function( event ) {
+                                               event._submit_bubble = true;
+                                       });
+                                       form._submit_attached = true;
+                               }
+                       });
+                       // return undefined since we don't need an event listener
+               },
+               
+               postDispatch: function( event ) {
+                       // If form was submitted by the user, bubble the event up the tree
+                       if ( event._submit_bubble ) {
+                               delete event._submit_bubble;
+                               if ( this.parentNode && !event.isTrigger ) {
+                                       jQuery.event.simulate( "submit", this.parentNode, event, true );
+                               }
+                       }
+               },
+
+               teardown: function() {
+                       // Only need this for delegated form submit events
+                       if ( jQuery.nodeName( this, "form" ) ) {
+                               return false;
+                       }
+
+                       // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+                       jQuery.event.remove( this, "._submit" );
+               }
+       };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+       jQuery.event.special.change = {
+
+               setup: function() {
+
+                       if ( rformElems.test( this.nodeName ) ) {
+                               // IE doesn't fire change on a check/radio until blur; trigger it on click
+                               // after a propertychange. Eat the blur-change in special.change.handle.
+                               // This still fires onchange a second time for check/radio after blur.
+                               if ( this.type === "checkbox" || this.type === "radio" ) {
+                                       jQuery.event.add( this, "propertychange._change", function( event ) {
+                                               if ( event.originalEvent.propertyName === "checked" ) {
+                                                       this._just_changed = true;
+                                               }
+                                       });
+                                       jQuery.event.add( this, "click._change", function( event ) {
+                                               if ( this._just_changed && !event.isTrigger ) {
+                                                       this._just_changed = false;
+                                                       jQuery.event.simulate( "change", this, event, true );
+                                               }
+                                       });
+                               }
+                               return false;
+                       }
+                       // Delegated event; lazy-add a change handler on descendant inputs
+                       jQuery.event.add( this, "beforeactivate._change", function( e ) {
+                               var elem = e.target;
+
+                               if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+                                       jQuery.event.add( elem, "change._change", function( event ) {
+                                               if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+                                                       jQuery.event.simulate( "change", this.parentNode, event, true );
+                                               }
+                                       });
+                                       elem._change_attached = true;
+                               }
+                       });
+               },
+
+               handle: function( event ) {
+                       var elem = event.target;
+
+                       // Swallow native change events from checkbox/radio, we already triggered them above
+                       if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+                               return event.handleObj.handler.apply( this, arguments );
+                       }
+               },
+
+               teardown: function() {
+                       jQuery.event.remove( this, "._change" );
+
+                       return rformElems.test( this.nodeName );
+               }
+       };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+       jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+               // Attach a single capturing handler while someone wants focusin/focusout
+               var attaches = 0,
+                       handler = function( event ) {
+                               jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+                       };
+
+               jQuery.event.special[ fix ] = {
+                       setup: function() {
+                               if ( attaches++ === 0 ) {
+                                       document.addEventListener( orig, handler, true );
+                               }
+                       },
+                       teardown: function() {
+                               if ( --attaches === 0 ) {
+                                       document.removeEventListener( orig, handler, true );
+                               }
+                       }
+               };
+       });
+}
+
+jQuery.fn.extend({
+
+       on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+               var origFn, type;
+
+               // Types can be a map of types/handlers
+               if ( typeof types === "object" ) {
+                       // ( types-Object, selector, data )
+                       if ( typeof selector !== "string" ) { // && selector != null
+                               // ( types-Object, data )
+                               data = data || selector;
+                               selector = undefined;
+                       }
+                       for ( type in types ) {
+                               this.on( type, selector, data, types[ type ], one );
+                       }
+                       return this;
+               }
+
+               if ( data == null && fn == null ) {
+                       // ( types, fn )
+                       fn = selector;
+                       data = selector = undefined;
+               } else if ( fn == null ) {
+                       if ( typeof selector === "string" ) {
+                               // ( types, selector, fn )
+                               fn = data;
+                               data = undefined;
+                       } else {
+                               // ( types, data, fn )
+                               fn = data;
+                               data = selector;
+                               selector = undefined;
+                       }
+               }
+               if ( fn === false ) {
+                       fn = returnFalse;
+               } else if ( !fn ) {
+                       return this;
+               }
+
+               if ( one === 1 ) {
+                       origFn = fn;
+                       fn = function( event ) {
+                               // Can use an empty set, since event contains the info
+                               jQuery().off( event );
+                               return origFn.apply( this, arguments );
+                       };
+                       // Use same guid so caller can remove using origFn
+                       fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+               }
+               return this.each( function() {
+                       jQuery.event.add( this, types, fn, data, selector );
+               });
+       },
+       one: function( types, selector, data, fn ) {
+               return this.on( types, selector, data, fn, 1 );
+       },
+       off: function( types, selector, fn ) {
+               if ( types && types.preventDefault && types.handleObj ) {
+                       // ( event )  dispatched jQuery.Event
+                       var handleObj = types.handleObj;
+                       jQuery( types.delegateTarget ).off(
+                               handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+                               handleObj.selector,
+                               handleObj.handler
+                       );
+                       return this;
+               }
+               if ( typeof types === "object" ) {
+                       // ( types-object [, selector] )
+                       for ( var type in types ) {
+                               this.off( type, selector, types[ type ] );
+                       }
+                       return this;
+               }
+               if ( selector === false || typeof selector === "function" ) {
+                       // ( types [, fn] )
+                       fn = selector;
+                       selector = undefined;
+               }
+               if ( fn === false ) {
+                       fn = returnFalse;
+               }
+               return this.each(function() {
+                       jQuery.event.remove( this, types, fn, selector );
+               });
+       },
+
+       bind: function( types, data, fn ) {
+               return this.on( types, null, data, fn );
+       },
+       unbind: function( types, fn ) {
+               return this.off( types, null, fn );
+       },
+
+       live: function( types, data, fn ) {
+               jQuery( this.context ).on( types, this.selector, data, fn );
+               return this;
+       },
+       die: function( types, fn ) {
+               jQuery( this.context ).off( types, this.selector || "**", fn );
+               return this;
+       },
+
+       delegate: function( selector, types, data, fn ) {
+               return this.on( types, selector, data, fn );
+       },
+       undelegate: function( selector, types, fn ) {
+               // ( namespace ) or ( selector, types [, fn] )
+               return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+       },
+
+       trigger: function( type, data ) {
+               return this.each(function() {
+                       jQuery.event.trigger( type, data, this );
+               });
+       },
+       triggerHandler: function( type, data ) {
+               if ( this[0] ) {
+                       return jQuery.event.trigger( type, data, this[0], true );
+               }
+       },
+
+       toggle: function( fn ) {
+               // Save reference to arguments for access in closure
+               var args = arguments,
+                       guid = fn.guid || jQuery.guid++,
+                       i = 0,
+                       toggler = function( event ) {
+                               // Figure out which function to execute
+                               var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+                               jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+                               // Make sure that clicks stop
+                               event.preventDefault();
+
+                               // and execute the function
+                               return args[ lastToggle ].apply( this, arguments ) || false;
+                       };
+
+               // link all the functions, so any of them can unbind this click handler
+               toggler.guid = guid;
+               while ( i < args.length ) {
+                       args[ i++ ].guid = guid;
+               }
+
+               return this.click( toggler );
+       },
+
+       hover: function( fnOver, fnOut ) {
+               return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+       }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+       "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+       "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+       // Handle event binding
+       jQuery.fn[ name ] = function( data, fn ) {
+               if ( fn == null ) {
+                       fn = data;
+                       data = null;
+               }
+
+               return arguments.length > 0 ?
+                       this.on( name, null, data, fn ) :
+                       this.trigger( name );
+       };
+
+       if ( jQuery.attrFn ) {
+               jQuery.attrFn[ name ] = true;
+       }
+
+       if ( rkeyEvent.test( name ) ) {
+               jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+       }
+
+       if ( rmouseEvent.test( name ) ) {
+               jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+       }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+       expando = "sizcache" + (Math.random() + '').replace('.', ''),
+       done = 0,
+       toString = Object.prototype.toString,
+       hasDuplicate = false,
+       baseHasDuplicate = true,
+       rBackslash = /\\/g,
+       rReturn = /\r\n/g,
+       rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+       baseHasDuplicate = false;
+       return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+       results = results || [];
+       context = context || document;
+
+       var origContext = context;
+
+       if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+               return [];
+       }
+
+       if ( !selector || typeof selector !== "string" ) {
+               return results;
+       }
+
+       var m, set, checkSet, extra, ret, cur, pop, i,
+               prune = true,
+               contextXML = Sizzle.isXML( context ),
+               parts = [],
+               soFar = selector;
+
+       // Reset the position of the chunker regexp (start from head)
+       do {
+               chunker.exec( "" );
+               m = chunker.exec( soFar );
+
+               if ( m ) {
+                       soFar = m[3];
+
+                       parts.push( m[1] );
+
+                       if ( m[2] ) {
+                               extra = m[3];
+                               break;
+                       }
+               }
+       } while ( m );
+
+       if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+               if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+                       set = posProcess( parts[0] + parts[1], context, seed );
+
+               } else {
+                       set = Expr.relative[ parts[0] ] ?
+                               [ context ] :
+                               Sizzle( parts.shift(), context );
+
+                       while ( parts.length ) {
+                               selector = parts.shift();
+
+                               if ( Expr.relative[ selector ] ) {
+                                       selector += parts.shift();
+                               }
+
+                               set = posProcess( selector, set, seed );
+                       }
+               }
+
+       } else {
+               // Take a shortcut and set the context if the root selector is an ID
+               // (but not if it'll be faster if the inner selector is an ID)
+               if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+                               Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+                       ret = Sizzle.find( parts.shift(), context, contextXML );
+                       context = ret.expr ?
+                               Sizzle.filter( ret.expr, ret.set )[0] :
+                               ret.set[0];
+               }
+
+               if ( context ) {
+                       ret = seed ?
+                               { expr: parts.pop(), set: makeArray(seed) } :
+                               Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+                       set = ret.expr ?
+                               Sizzle.filter( ret.expr, ret.set ) :
+                               ret.set;
+
+                       if ( parts.length > 0 ) {
+                               checkSet = makeArray( set );
+
+                       } else {
+                               prune = false;
+                       }
+
+                       while ( parts.length ) {
+                               cur = parts.pop();
+                               pop = cur;
+
+                               if ( !Expr.relative[ cur ] ) {
+                                       cur = "";
+                               } else {
+                                       pop = parts.pop();
+                               }
+
+                               if ( pop == null ) {
+                                       pop = context;
+                               }
+
+                               Expr.relative[ cur ]( checkSet, pop, contextXML );
+                       }
+
+               } else {
+                       checkSet = parts = [];
+               }
+       }
+
+       if ( !checkSet ) {
+               checkSet = set;
+       }
+
+       if ( !checkSet ) {
+               Sizzle.error( cur || selector );
+       }
+
+       if ( toString.call(checkSet) === "[object Array]" ) {
+               if ( !prune ) {
+                       results.push.apply( results, checkSet );
+
+               } else if ( context && context.nodeType === 1 ) {
+                       for ( i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+                                       results.push( set[i] );
+                               }
+                       }
+
+               } else {
+                       for ( i = 0; checkSet[i] != null; i++ ) {
+                               if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+                                       results.push( set[i] );
+                               }
+                       }
+               }
+
+       } else {
+               makeArray( checkSet, results );
+       }
+
+       if ( extra ) {
+               Sizzle( extra, origContext, results, seed );
+               Sizzle.uniqueSort( results );
+       }
+
+       return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+       if ( sortOrder ) {
+               hasDuplicate = baseHasDuplicate;
+               results.sort( sortOrder );
+
+               if ( hasDuplicate ) {
+                       for ( var i = 1; i < results.length; i++ ) {
+                               if ( results[i] === results[ i - 1 ] ) {
+                                       results.splice( i--, 1 );
+                               }
+                       }
+               }
+       }
+
+       return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+       return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+       return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+       var set, i, len, match, type, left;
+
+       if ( !expr ) {
+               return [];
+       }
+
+       for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+               type = Expr.order[i];
+
+               if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+                       left = match[1];
+                       match.splice( 1, 1 );
+
+                       if ( left.substr( left.length - 1 ) !== "\\" ) {
+                               match[1] = (match[1] || "").replace( rBackslash, "" );
+                               set = Expr.find[ type ]( match, context, isXML );
+
+                               if ( set != null ) {
+                                       expr = expr.replace( Expr.match[ type ], "" );
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if ( !set ) {
+               set = typeof context.getElementsByTagName !== "undefined" ?
+                       context.getElementsByTagName( "*" ) :
+                       [];
+       }
+
+       return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+       var match, anyFound,
+               type, found, item, filter, left,
+               i, pass,
+               old = expr,
+               result = [],
+               curLoop = set,
+               isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+       while ( expr && set.length ) {
+               for ( type in Expr.filter ) {
+                       if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+                               filter = Expr.filter[ type ];
+                               left = match[1];
+
+                               anyFound = false;
+
+                               match.splice(1,1);
+
+                               if ( left.substr( left.length - 1 ) === "\\" ) {
+                                       continue;
+                               }
+
+                               if ( curLoop === result ) {
+                                       result = [];
+                               }
+
+                               if ( Expr.preFilter[ type ] ) {
+                                       match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+                                       if ( !match ) {
+                                               anyFound = found = true;
+
+                                       } else if ( match === true ) {
+                                               continue;
+                                       }
+                               }
+
+                               if ( match ) {
+                                       for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+                                               if ( item ) {
+                                                       found = filter( item, match, i, curLoop );
+                                                       pass = not ^ found;
+
+                                                       if ( inplace && found != null ) {
+                                                               if ( pass ) {
+                                                                       anyFound = true;
+
+                                                               } else {
+                                                                       curLoop[i] = false;
+                                                               }
+
+                                                       } else if ( pass ) {
+                                                               result.push( item );
+                                                               anyFound = true;
+                                                       }
+                                               }
+                                       }
+                               }
+
+                               if ( found !== undefined ) {
+                                       if ( !inplace ) {
+                                               curLoop = result;
+                                       }
+
+                                       expr = expr.replace( Expr.match[ type ], "" );
+
+                                       if ( !anyFound ) {
+                                               return [];
+                                       }
+
+                                       break;
+                               }
+                       }
+               }
+
+               // Improper expression
+               if ( expr === old ) {
+                       if ( anyFound == null ) {
+                               Sizzle.error( expr );
+
+                       } else {
+                               break;
+                       }
+               }
+
+               old = expr;
+       }
+
+       return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+       throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+    var i, node,
+               nodeType = elem.nodeType,
+               ret = "";
+
+       if ( nodeType ) {
+               if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+                       // Use textContent || innerText for elements
+                       if ( typeof elem.textContent === 'string' ) {
+                               return elem.textContent;
+                       } else if ( typeof elem.innerText === 'string' ) {
+                               // Replace IE's carriage returns
+                               return elem.innerText.replace( rReturn, '' );
+                       } else {
+                               // Traverse it's children
+                               for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+                                       ret += getText( elem );
+                               }
+                       }
+               } else if ( nodeType === 3 || nodeType === 4 ) {
+                       return elem.nodeValue;
+               }
+       } else {
+
+               // If no nodeType, this is expected to be an array
+               for ( i = 0; (node = elem[i]); i++ ) {
+                       // Do not traverse comment nodes
+                       if ( node.nodeType !== 8 ) {
+                               ret += getText( node );
+                       }
+               }
+       }
+       return ret;
+};
+
+var Expr = Sizzle.selectors = {
+       order: [ "ID", "NAME", "TAG" ],
+
+       match: {
+               ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+               CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+               NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+               ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+               TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+               CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+               POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+               PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+       },
+
+       leftMatch: {},
+
+       attrMap: {
+               "class": "className",
+               "for": "htmlFor"
+       },
+
+       attrHandle: {
+               href: function( elem ) {
+                       return elem.getAttribute( "href" );
+               },
+               type: function( elem ) {
+                       return elem.getAttribute( "type" );
+               }
+       },
+
+       relative: {
+               "+": function(checkSet, part){
+                       var isPartStr = typeof part === "string",
+                               isTag = isPartStr && !rNonWord.test( part ),
+                               isPartStrNotTag = isPartStr && !isTag;
+
+                       if ( isTag ) {
+                               part = part.toLowerCase();
+                       }
+
+                       for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+                               if ( (elem = checkSet[i]) ) {
+                                       while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+                                       checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+                                               elem || false :
+                                               elem === part;
+                               }
+                       }
+
+                       if ( isPartStrNotTag ) {
+                               Sizzle.filter( part, checkSet, true );
+                       }
+               },
+
+               ">": function( checkSet, part ) {
+                       var elem,
+                               isPartStr = typeof part === "string",
+                               i = 0,
+                               l = checkSet.length;
+
+                       if ( isPartStr && !rNonWord.test( part ) ) {
+                               part = part.toLowerCase();
+
+                               for ( ; i < l; i++ ) {
+                                       elem = checkSet[i];
+
+                                       if ( elem ) {
+                                               var parent = elem.parentNode;
+                                               checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+                                       }
+                               }
+
+                       } else {
+                               for ( ; i < l; i++ ) {
+                                       elem = checkSet[i];
+
+                                       if ( elem ) {
+                                               checkSet[i] = isPartStr ?
+                                                       elem.parentNode :
+                                                       elem.parentNode === part;
+                                       }
+                               }
+
+                               if ( isPartStr ) {
+                                       Sizzle.filter( part, checkSet, true );
+                               }
+                       }
+               },
+
+               "": function(checkSet, part, isXML){
+                       var nodeCheck,
+                               doneName = done++,
+                               checkFn = dirCheck;
+
+                       if ( typeof part === "string" && !rNonWord.test( part ) ) {
+                               part = part.toLowerCase();
+                               nodeCheck = part;
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+               },
+
+               "~": function( checkSet, part, isXML ) {
+                       var nodeCheck,
+                               doneName = done++,
+                               checkFn = dirCheck;
+
+                       if ( typeof part === "string" && !rNonWord.test( part ) ) {
+                               part = part.toLowerCase();
+                               nodeCheck = part;
+                               checkFn = dirNodeCheck;
+                       }
+
+                       checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+               }
+       },
+
+       find: {
+               ID: function( match, context, isXML ) {
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+                               // Check parentNode to catch when Blackberry 4.6 returns
+                               // nodes that are no longer in the document #6963
+                               return m && m.parentNode ? [m] : [];
+                       }
+               },
+
+               NAME: function( match, context ) {
+                       if ( typeof context.getElementsByName !== "undefined" ) {
+                               var ret = [],
+                                       results = context.getElementsByName( match[1] );
+
+                               for ( var i = 0, l = results.length; i < l; i++ ) {
+                                       if ( results[i].getAttribute("name") === match[1] ) {
+                                               ret.push( results[i] );
+                                       }
+                               }
+
+                               return ret.length === 0 ? null : ret;
+                       }
+               },
+
+               TAG: function( match, context ) {
+                       if ( typeof context.getElementsByTagName !== "undefined" ) {
+                               return context.getElementsByTagName( match[1] );
+                       }
+               }
+       },
+       preFilter: {
+               CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+                       match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+                       if ( isXML ) {
+                               return match;
+                       }
+
+                       for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+                               if ( elem ) {
+                                       if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+                                               if ( !inplace ) {
+                                                       result.push( elem );
+                                               }
+
+                                       } else if ( inplace ) {
+                                               curLoop[i] = false;
+                                       }
+                               }
+                       }
+
+                       return false;
+               },
+
+               ID: function( match ) {
+                       return match[1].replace( rBackslash, "" );
+               },
+
+               TAG: function( match, curLoop ) {
+                       return match[1].replace( rBackslash, "" ).toLowerCase();
+               },
+
+               CHILD: function( match ) {
+                       if ( match[1] === "nth" ) {
+                               if ( !match[2] ) {
+                                       Sizzle.error( match[0] );
+                               }
+
+                               match[2] = match[2].replace(/^\+|\s*/g, '');
+
+                               // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+                               var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+                                       match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+                                       !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+                               // calculate the numbers (first)n+(last) including if they are negative
+                               match[2] = (test[1] + (test[2] || 1)) - 0;
+                               match[3] = test[3] - 0;
+                       }
+                       else if ( match[2] ) {
+                               Sizzle.error( match[0] );
+                       }
+
+                       // TODO: Move to normal caching system
+                       match[0] = done++;
+
+                       return match;
+               },
+
+               ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+                       var name = match[1] = match[1].replace( rBackslash, "" );
+
+                       if ( !isXML && Expr.attrMap[name] ) {
+                               match[1] = Expr.attrMap[name];
+                       }
+
+                       // Handle if an un-quoted value was used
+                       match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+                       if ( match[2] === "~=" ) {
+                               match[4] = " " + match[4] + " ";
+                       }
+
+                       return match;
+               },
+
+               PSEUDO: function( match, curLoop, inplace, result, not ) {
+                       if ( match[1] === "not" ) {
+                               // If we're dealing with a complex expression, or a simple one
+                               if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+                                       match[3] = Sizzle(match[3], null, null, curLoop);
+
+                               } else {
+                                       var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+                                       if ( !inplace ) {
+                                               result.push.apply( result, ret );
+                                       }
+
+                                       return false;
+                               }
+
+                       } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+                               return true;
+                       }
+
+                       return match;
+               },
+
+               POS: function( match ) {
+                       match.unshift( true );
+
+                       return match;
+               }
+       },
+
+       filters: {
+               enabled: function( elem ) {
+                       return elem.disabled === false && elem.type !== "hidden";
+               },
+
+               disabled: function( elem ) {
+                       return elem.disabled === true;
+               },
+
+               checked: function( elem ) {
+                       return elem.checked === true;
+               },
+
+               selected: function( elem ) {
+                       // Accessing this property makes selected-by-default
+                       // options in Safari work properly
+                       if ( elem.parentNode ) {
+                               elem.parentNode.selectedIndex;
+                       }
+
+                       return elem.selected === true;
+               },
+
+               parent: function( elem ) {
+                       return !!elem.firstChild;
+               },
+
+               empty: function( elem ) {
+                       return !elem.firstChild;
+               },
+
+               has: function( elem, i, match ) {
+                       return !!Sizzle( match[3], elem ).length;
+               },
+
+               header: function( elem ) {
+                       return (/h\d/i).test( elem.nodeName );
+               },
+
+               text: function( elem ) {
+                       var attr = elem.getAttribute( "type" ), type = elem.type;
+                       // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+                       // use getAttribute instead to test this case
+                       return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+               },
+
+               radio: function( elem ) {
+                       return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+               },
+
+               checkbox: function( elem ) {
+                       return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+               },
+
+               file: function( elem ) {
+                       return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+               },
+
+               password: function( elem ) {
+                       return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+               },
+
+               submit: function( elem ) {
+                       var name = elem.nodeName.toLowerCase();
+                       return (name === "input" || name === "button") && "submit" === elem.type;
+               },
+
+               image: function( elem ) {
+                       return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+               },
+
+               reset: function( elem ) {
+                       var name = elem.nodeName.toLowerCase();
+                       return (name === "input" || name === "button") && "reset" === elem.type;
+               },
+
+               button: function( elem ) {
+                       var name = elem.nodeName.toLowerCase();
+                       return name === "input" && "button" === elem.type || name === "button";
+               },
+
+               input: function( elem ) {
+                       return (/input|select|textarea|button/i).test( elem.nodeName );
+               },
+
+               focus: function( elem ) {
+                       return elem === elem.ownerDocument.activeElement;
+               }
+       },
+       setFilters: {
+               first: function( elem, i ) {
+                       return i === 0;
+               },
+
+               last: function( elem, i, match, array ) {
+                       return i === array.length - 1;
+               },
+
+               even: function( elem, i ) {
+                       return i % 2 === 0;
+               },
+
+               odd: function( elem, i ) {
+                       return i % 2 === 1;
+               },
+
+               lt: function( elem, i, match ) {
+                       return i < match[3] - 0;
+               },
+
+               gt: function( elem, i, match ) {
+                       return i > match[3] - 0;
+               },
+
+               nth: function( elem, i, match ) {
+                       return match[3] - 0 === i;
+               },
+
+               eq: function( elem, i, match ) {
+                       return match[3] - 0 === i;
+               }
+       },
+       filter: {
+               PSEUDO: function( elem, match, i, array ) {
+                       var name = match[1],
+                               filter = Expr.filters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+
+                       } else if ( name === "contains" ) {
+                               return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+                       } else if ( name === "not" ) {
+                               var not = match[3];
+
+                               for ( var j = 0, l = not.length; j < l; j++ ) {
+                                       if ( not[j] === elem ) {
+                                               return false;
+                                       }
+                               }
+
+                               return true;
+
+                       } else {
+                               Sizzle.error( name );
+                       }
+               },
+
+               CHILD: function( elem, match ) {
+                       var first, last,
+                               doneName, parent, cache,
+                               count, diff,
+                               type = match[1],
+                               node = elem;
+
+                       switch ( type ) {
+                               case "only":
+                               case "first":
+                                       while ( (node = node.previousSibling) ) {
+                                               if ( node.nodeType === 1 ) {
+                                                       return false;
+                                               }
+                                       }
+
+                                       if ( type === "first" ) {
+                                               return true;
+                                       }
+
+                                       node = elem;
+
+                                       /* falls through */
+                               case "last":
+                                       while ( (node = node.nextSibling) ) {
+                                               if ( node.nodeType === 1 ) {
+                                                       return false;
+                                               }
+                                       }
+
+                                       return true;
+
+                               case "nth":
+                                       first = match[2];
+                                       last = match[3];
+
+                                       if ( first === 1 && last === 0 ) {
+                                               return true;
+                                       }
+
+                                       doneName = match[0];
+                                       parent = elem.parentNode;
+
+                                       if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+                                               count = 0;
+
+                                               for ( node = parent.firstChild; node; node = node.nextSibling ) {
+                                                       if ( node.nodeType === 1 ) {
+                                                               node.nodeIndex = ++count;
+                                                       }
+                                               }
+
+                                               parent[ expando ] = doneName;
+                                       }
+
+                                       diff = elem.nodeIndex - last;
+
+                                       if ( first === 0 ) {
+                                               return diff === 0;
+
+                                       } else {
+                                               return ( diff % first === 0 && diff / first >= 0 );
+                                       }
+                       }
+               },
+
+               ID: function( elem, match ) {
+                       return elem.nodeType === 1 && elem.getAttribute("id") === match;
+               },
+
+               TAG: function( elem, match ) {
+                       return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+               },
+
+               CLASS: function( elem, match ) {
+                       return (" " + (elem.className || elem.getAttribute("class")) + " ")
+                               .indexOf( match ) > -1;
+               },
+
+               ATTR: function( elem, match ) {
+                       var name = match[1],
+                               result = Sizzle.attr ?
+                                       Sizzle.attr( elem, name ) :
+                                       Expr.attrHandle[ name ] ?
+                                       Expr.attrHandle[ name ]( elem ) :
+                                       elem[ name ] != null ?
+                                               elem[ name ] :
+                                               elem.getAttribute( name ),
+                               value = result + "",
+                               type = match[2],
+                               check = match[4];
+
+                       return result == null ?
+                               type === "!=" :
+                               !type && Sizzle.attr ?
+                               result != null :
+                               type === "=" ?
+                               value === check :
+                               type === "*=" ?
+                               value.indexOf(check) >= 0 :
+                               type === "~=" ?
+                               (" " + value + " ").indexOf(check) >= 0 :
+                               !check ?
+                               value && result !== false :
+                               type === "!=" ?
+                               value !== check :
+                               type === "^=" ?
+                               value.indexOf(check) === 0 :
+                               type === "$=" ?
+                               value.substr(value.length - check.length) === check :
+                               type === "|=" ?
+                               value === check || value.substr(0, check.length + 1) === check + "-" :
+                               false;
+               },
+
+               POS: function( elem, match, i, array ) {
+                       var name = match[2],
+                               filter = Expr.setFilters[ name ];
+
+                       if ( filter ) {
+                               return filter( elem, i, match, array );
+                       }
+               }
+       }
+};
+
+var origPOS = Expr.match.POS,
+       fescape = function(all, num){
+               return "\\" + (num - 0 + 1);
+       };
+
+for ( var type in Expr.match ) {
+       Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+       Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+// Expose origPOS
+// "global" as in regardless of relation to brackets/parens
+Expr.match.globalPOS = origPOS;
+
+var makeArray = function( array, results ) {
+       array = Array.prototype.slice.call( array, 0 );
+
+       if ( results ) {
+               results.push.apply( results, array );
+               return results;
+       }
+
+       return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+       Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+       makeArray = function( array, results ) {
+               var i = 0,
+                       ret = results || [];
+
+               if ( toString.call(array) === "[object Array]" ) {
+                       Array.prototype.push.apply( ret, array );
+
+               } else {
+                       if ( typeof array.length === "number" ) {
+                               for ( var l = array.length; i < l; i++ ) {
+                                       ret.push( array[i] );
+                               }
+
+                       } else {
+                               for ( ; array[i]; i++ ) {
+                                       ret.push( array[i] );
+                               }
+                       }
+               }
+
+               return ret;
+       };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+       sortOrder = function( a, b ) {
+               if ( a === b ) {
+                       hasDuplicate = true;
+                       return 0;
+               }
+
+               if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+                       return a.compareDocumentPosition ? -1 : 1;
+               }
+
+               return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+       };
+
+} else {
+       sortOrder = function( a, b ) {
+               // The nodes are identical, we can exit early
+               if ( a === b ) {
+                       hasDuplicate = true;
+                       return 0;
+
+               // Fallback to using sourceIndex (in IE) if it's available on both nodes
+               } else if ( a.sourceIndex && b.sourceIndex ) {
+                       return a.sourceIndex - b.sourceIndex;
+               }
+
+               var al, bl,
+                       ap = [],
+                       bp = [],
+                       aup = a.parentNode,
+                       bup = b.parentNode,
+                       cur = aup;
+
+               // If the nodes are siblings (or identical) we can do a quick check
+               if ( aup === bup ) {
+                       return siblingCheck( a, b );
+
+               // If no parents were found then the nodes are disconnected
+               } else if ( !aup ) {
+                       return -1;
+
+               } else if ( !bup ) {
+                       return 1;
+               }
+
+               // Otherwise they're somewhere else in the tree so we need
+               // to build up a full list of the parentNodes for comparison
+               while ( cur ) {
+                       ap.unshift( cur );
+                       cur = cur.parentNode;
+               }
+
+               cur = bup;
+
+               while ( cur ) {
+                       bp.unshift( cur );
+                       cur = cur.parentNode;
+               }
+
+               al = ap.length;
+               bl = bp.length;
+
+               // Start walking down the tree looking for a discrepancy
+               for ( var i = 0; i < al && i < bl; i++ ) {
+                       if ( ap[i] !== bp[i] ) {
+                               return siblingCheck( ap[i], bp[i] );
+                       }
+               }
+
+               // We ended someplace up the tree so do a sibling check
+               return i === al ?
+                       siblingCheck( a, bp[i], -1 ) :
+                       siblingCheck( ap[i], b, 1 );
+       };
+
+       siblingCheck = function( a, b, ret ) {
+               if ( a === b ) {
+                       return ret;
+               }
+
+               var cur = a.nextSibling;
+
+               while ( cur ) {
+                       if ( cur === b ) {
+                               return -1;
+                       }
+
+                       cur = cur.nextSibling;
+               }
+
+               return 1;
+       };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+       // We're going to inject a fake input element with a specified name
+       var form = document.createElement("div"),
+               id = "script" + (new Date()).getTime(),
+               root = document.documentElement;
+
+       form.innerHTML = "<a name='" + id + "'/>";
+
+       // Inject it into the root element, check its status, and remove it quickly
+       root.insertBefore( form, root.firstChild );
+
+       // The workaround has to do additional checks after a getElementById
+       // Which slows things down for other browsers (hence the branching)
+       if ( document.getElementById( id ) ) {
+               Expr.find.ID = function( match, context, isXML ) {
+                       if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                               var m = context.getElementById(match[1]);
+
+                               return m ?
+                                       m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+                                               [m] :
+                                               undefined :
+                                       [];
+                       }
+               };
+
+               Expr.filter.ID = function( elem, match ) {
+                       var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+                       return elem.nodeType === 1 && node && node.nodeValue === match;
+               };
+       }
+
+       root.removeChild( form );
+
+       // release memory in IE
+       root = form = null;
+})();
+
+(function(){
+       // Check to see if the browser returns only elements
+       // when doing getElementsByTagName("*")
+
+       // Create a fake element
+       var div = document.createElement("div");
+       div.appendChild( document.createComment("") );
+
+       // Make sure no comments are found
+       if ( div.getElementsByTagName("*").length > 0 ) {
+               Expr.find.TAG = function( match, context ) {
+                       var results = context.getElementsByTagName( match[1] );
+
+                       // Filter out possible comments
+                       if ( match[1] === "*" ) {
+                               var tmp = [];
+
+                               for ( var i = 0; results[i]; i++ ) {
+                                       if ( results[i].nodeType === 1 ) {
+                                               tmp.push( results[i] );
+                                       }
+                               }
+
+                               results = tmp;
+                       }
+
+                       return results;
+               };
+       }
+
+       // Check to see if an attribute returns normalized href attributes
+       div.innerHTML = "<a href='#'></a>";
+
+       if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+                       div.firstChild.getAttribute("href") !== "#" ) {
+
+               Expr.attrHandle.href = function( elem ) {
+                       return elem.getAttribute( "href", 2 );
+               };
+       }
+
+       // release memory in IE
+       div = null;
+})();
+
+if ( document.querySelectorAll ) {
+       (function(){
+               var oldSizzle = Sizzle,
+                       div = document.createElement("div"),
+                       id = "__sizzle__";
+
+               div.innerHTML = "<p class='TEST'></p>";
+
+               // Safari can't handle uppercase or unicode characters when
+               // in quirks mode.
+               if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+                       return;
+               }
+
+               Sizzle = function( query, context, extra, seed ) {
+                       context = context || document;
+
+                       // Only use querySelectorAll on non-XML documents
+                       // (ID selectors don't work in non-HTML documents)
+                       if ( !seed && !Sizzle.isXML(context) ) {
+                               // See if we find a selector to speed up
+                               var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+                               if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+                                       // Speed-up: Sizzle("TAG")
+                                       if ( match[1] ) {
+                                               return makeArray( context.getElementsByTagName( query ), extra );
+
+                                       // Speed-up: Sizzle(".CLASS")
+                                       } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+                                               return makeArray( context.getElementsByClassName( match[2] ), extra );
+                                       }
+                               }
+
+                               if ( context.nodeType === 9 ) {
+                                       // Speed-up: Sizzle("body")
+                                       // The body element only exists once, optimize finding it
+                                       if ( query === "body" && context.body ) {
+                                               return makeArray( [ context.body ], extra );
+
+                                       // Speed-up: Sizzle("#ID")
+                                       } else if ( match && match[3] ) {
+                                               var elem = context.getElementById( match[3] );
+
+                                               // Check parentNode to catch when Blackberry 4.6 returns
+                                               // nodes that are no longer in the document #6963
+                                               if ( elem && elem.parentNode ) {
+                                                       // Handle the case where IE and Opera return items
+                                                       // by name instead of ID
+                                                       if ( elem.id === match[3] ) {
+                                                               return makeArray( [ elem ], extra );
+                                                       }
+
+                                               } else {
+                                                       return makeArray( [], extra );
+                                               }
+                                       }
+
+                                       try {
+                                               return makeArray( context.querySelectorAll(query), extra );
+                                       } catch(qsaError) {}
+
+                               // qSA works strangely on Element-rooted queries
+                               // We can work around this by specifying an extra ID on the root
+                               // and working up from there (Thanks to Andrew Dupont for the technique)
+                               // IE 8 doesn't work on object elements
+                               } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+                                       var oldContext = context,
+                                               old = context.getAttribute( "id" ),
+                                               nid = old || id,
+                                               hasParent = context.parentNode,
+                                               relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+                                       if ( !old ) {
+                                               context.setAttribute( "id", nid );
+                                       } else {
+                                               nid = nid.replace( /'/g, "\\$&" );
+                                       }
+                                       if ( relativeHierarchySelector && hasParent ) {
+                                               context = context.parentNode;
+                                       }
+
+                                       try {
+                                               if ( !relativeHierarchySelector || hasParent ) {
+                                                       return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+                                               }
+
+                                       } catch(pseudoError) {
+                                       } finally {
+                                               if ( !old ) {
+                                                       oldContext.removeAttribute( "id" );
+                                               }
+                                       }
+                               }
+                       }
+
+                       return oldSizzle(query, context, extra, seed);
+               };
+
+               for ( var prop in oldSizzle ) {
+                       Sizzle[ prop ] = oldSizzle[ prop ];
+               }
+
+               // release memory in IE
+               div = null;
+       })();
+}
+
+(function(){
+       var html = document.documentElement,
+               matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+       if ( matches ) {
+               // Check to see if it's possible to do matchesSelector
+               // on a disconnected node (IE 9 fails this)
+               var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+                       pseudoWorks = false;
+
+               try {
+                       // This should fail with an exception
+                       // Gecko does not error, returns false instead
+                       matches.call( document.documentElement, "[test!='']:sizzle" );
+
+               } catch( pseudoError ) {
+                       pseudoWorks = true;
+               }
+
+               Sizzle.matchesSelector = function( node, expr ) {
+                       // Make sure that attribute selectors are quoted
+                       expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+                       if ( !Sizzle.isXML( node ) ) {
+                               try {
+                                       if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+                                               var ret = matches.call( node, expr );
+
+                                               // IE 9's matchesSelector returns false on disconnected nodes
+                                               if ( ret || !disconnectedMatch ||
+                                                               // As well, disconnected nodes are said to be in a document
+                                                               // fragment in IE 9, so check for that
+                                                               node.document && node.document.nodeType !== 11 ) {
+                                                       return ret;
+                                               }
+                                       }
+                               } catch(e) {}
+                       }
+
+                       return Sizzle(expr, null, null, [node]).length > 0;
+               };
+       }
+})();
+
+(function(){
+       var div = document.createElement("div");
+
+       div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+       // Opera can't find a second classname (in 9.6)
+       // Also, make sure that getElementsByClassName actually exists
+       if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+               return;
+       }
+
+       // Safari caches class attributes, doesn't catch changes (in 3.2)
+       div.lastChild.className = "e";
+
+       if ( div.getElementsByClassName("e").length === 1 ) {
+               return;
+       }
+
+       Expr.order.splice(1, 0, "CLASS");
+       Expr.find.CLASS = function( match, context, isXML ) {
+               if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+                       return context.getElementsByClassName(match[1]);
+               }
+       };
+
+       // release memory in IE
+       div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+
+               if ( elem ) {
+                       var match = false;
+
+                       elem = elem[dir];
+
+                       while ( elem ) {
+                               if ( elem[ expando ] === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 && !isXML ){
+                                       elem[ expando ] = doneName;
+                                       elem.sizset = i;
+                               }
+
+                               if ( elem.nodeName.toLowerCase() === cur ) {
+                                       match = elem;
+                                       break;
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+       for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+               var elem = checkSet[i];
+
+               if ( elem ) {
+                       var match = false;
+
+                       elem = elem[dir];
+
+                       while ( elem ) {
+                               if ( elem[ expando ] === doneName ) {
+                                       match = checkSet[elem.sizset];
+                                       break;
+                               }
+
+                               if ( elem.nodeType === 1 ) {
+                                       if ( !isXML ) {
+                                               elem[ expando ] = doneName;
+                                               elem.sizset = i;
+                                       }
+
+                                       if ( typeof cur !== "string" ) {
+                                               if ( elem === cur ) {
+                                                       match = true;
+                                                       break;
+                                               }
+
+                                       } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+                                               match = elem;
+                                               break;
+                                       }
+                               }
+
+                               elem = elem[dir];
+                       }
+
+                       checkSet[i] = match;
+               }
+       }
+}
+
+if ( document.documentElement.contains ) {
+       Sizzle.contains = function( a, b ) {
+               return a !== b && (a.contains ? a.contains(b) : true);
+       };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+       Sizzle.contains = function( a, b ) {
+               return !!(a.compareDocumentPosition(b) & 16);
+       };
+
+} else {
+       Sizzle.contains = function() {
+               return false;
+       };
+}
+
+Sizzle.isXML = function( elem ) {
+       // documentElement is verified for cases where it doesn't yet exist
+       // (such as loading iframes in IE - #4833)
+       var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+       return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+       var match,
+               tmpSet = [],
+               later = "",
+               root = context.nodeType ? [context] : context;
+
+       // Position selectors must be done after the filter
+       // And so must :not(positional) so we move all PSEUDOs to the end
+       while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+               later += match[0];
+               selector = selector.replace( Expr.match.PSEUDO, "" );
+       }
+
+       selector = Expr.relative[selector] ? selector + "*" : selector;
+
+       for ( var i = 0, l = root.length; i < l; i++ ) {
+               Sizzle( selector, root[i], tmpSet, seed );
+       }
+
+       return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+       rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+       // Note: This RegExp should be improved, or likely pulled from Sizzle
+       rmultiselector = /,/,
+       isSimple = /^.[^:#\[\.,]*$/,
+       slice = Array.prototype.slice,
+       POS = jQuery.expr.match.globalPOS,
+       // methods guaranteed to produce a unique set when starting from a unique set
+       guaranteedUnique = {
+               children: true,
+               contents: true,
+               next: true,
+               prev: true
+       };
+
+jQuery.fn.extend({
+       find: function( selector ) {
+               var self = this,
+                       i, l;
+
+               if ( typeof selector !== "string" ) {
+                       return jQuery( selector ).filter(function() {
+                               for ( i = 0, l = self.length; i < l; i++ ) {
+                                       if ( jQuery.contains( self[ i ], this ) ) {
+                                               return true;
+                                       }
+                               }
+                       });
+               }
+
+               var ret = this.pushStack( "", "find", selector ),
+                       length, n, r;
+
+               for ( i = 0, l = this.length; i < l; i++ ) {
+                       length = ret.length;
+                       jQuery.find( selector, this[i], ret );
+
+                       if ( i > 0 ) {
+                               // Make sure that the results are unique
+                               for ( n = length; n < ret.length; n++ ) {
+                                       for ( r = 0; r < length; r++ ) {
+                                               if ( ret[r] === ret[n] ) {
+                                                       ret.splice(n--, 1);
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+               }
+
+               return ret;
+       },
+
+       has: function( target ) {
+               var targets = jQuery( target );
+               return this.filter(function() {
+                       for ( var i = 0, l = targets.length; i < l; i++ ) {
+                               if ( jQuery.contains( this, targets[i] ) ) {
+                                       return true;
+                               }
+                       }
+               });
+       },
+
+       not: function( selector ) {
+               return this.pushStack( winnow(this, selector, false), "not", selector);
+       },
+
+       filter: function( selector ) {
+               return this.pushStack( winnow(this, selector, true), "filter", selector );
+       },
+
+       is: function( selector ) {
+               return !!selector && (
+                       typeof selector === "string" ?
+                               // If this is a positional selector, check membership in the returned set
+                               // so $("p:first").is("p:last") won't return true for a doc with two "p".
+                               POS.test( selector ) ?
+                                       jQuery( selector, this.context ).index( this[0] ) >= 0 :
+                                       jQuery.filter( selector, this ).length > 0 :
+                               this.filter( selector ).length > 0 );
+       },
+
+       closest: function( selectors, context ) {
+               var ret = [], i, l, cur = this[0];
+
+               // Array (deprecated as of jQuery 1.7)
+               if ( jQuery.isArray( selectors ) ) {
+                       var level = 1;
+
+                       while ( cur && cur.ownerDocument && cur !== context ) {
+                               for ( i = 0; i < selectors.length; i++ ) {
+
+                                       if ( jQuery( cur ).is( selectors[ i ] ) ) {
+                                               ret.push({ selector: selectors[ i ], elem: cur, level: level });
+                                       }
+                               }
+
+                               cur = cur.parentNode;
+                               level++;
+                       }
+
+                       return ret;
+               }
+
+               // String
+               var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+                               jQuery( selectors, context || this.context ) :
+                               0;
+
+               for ( i = 0, l = this.length; i < l; i++ ) {
+                       cur = this[i];
+
+                       while ( cur ) {
+                               if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+                                       ret.push( cur );
+                                       break;
+
+                               } else {
+                                       cur = cur.parentNode;
+                                       if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+               return this.pushStack( ret, "closest", selectors );
+       },
+
+       // Determine the position of an element within
+       // the matched set of elements
+       index: function( elem ) {
+
+               // No argument, return index in parent
+               if ( !elem ) {
+                       return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+               }
+
+               // index in selector
+               if ( typeof elem === "string" ) {
+                       return jQuery.inArray( this[0], jQuery( elem ) );
+               }
+
+               // Locate the position of the desired element
+               return jQuery.inArray(
+                       // If it receives a jQuery object, the first element is used
+                       elem.jquery ? elem[0] : elem, this );
+       },
+
+       add: function( selector, context ) {
+               var set = typeof selector === "string" ?
+                               jQuery( selector, context ) :
+                               jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+                       all = jQuery.merge( this.get(), set );
+
+               return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+                       all :
+                       jQuery.unique( all ) );
+       },
+
+       andSelf: function() {
+               return this.add( this.prevObject );
+       }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+       return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+       parent: function( elem ) {
+               var parent = elem.parentNode;
+               return parent && parent.nodeType !== 11 ? parent : null;
+       },
+       parents: function( elem ) {
+               return jQuery.dir( elem, "parentNode" );
+       },
+       parentsUntil: function( elem, i, until ) {
+               return jQuery.dir( elem, "parentNode", until );
+       },
+       next: function( elem ) {
+               return jQuery.nth( elem, 2, "nextSibling" );
+       },
+       prev: function( elem ) {
+               return jQuery.nth( elem, 2, "previousSibling" );
+       },
+       nextAll: function( elem ) {
+               return jQuery.dir( elem, "nextSibling" );
+       },
+       prevAll: function( elem ) {
+               return jQuery.dir( elem, "previousSibling" );
+       },
+       nextUntil: function( elem, i, until ) {
+               return jQuery.dir( elem, "nextSibling", until );
+       },
+       prevUntil: function( elem, i, until ) {
+               return jQuery.dir( elem, "previousSibling", until );
+       },
+       siblings: function( elem ) {
+               return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+       },
+       children: function( elem ) {
+               return jQuery.sibling( elem.firstChild );
+       },
+       contents: function( elem ) {
+               return jQuery.nodeName( elem, "iframe" ) ?
+                       elem.contentDocument || elem.contentWindow.document :
+                       jQuery.makeArray( elem.childNodes );
+       }
+}, function( name, fn ) {
+       jQuery.fn[ name ] = function( until, selector ) {
+               var ret = jQuery.map( this, fn, until );
+
+               if ( !runtil.test( name ) ) {
+                       selector = until;
+               }
+
+               if ( selector && typeof selector === "string" ) {
+                       ret = jQuery.filter( selector, ret );
+               }
+
+               ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+               if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+                       ret = ret.reverse();
+               }
+
+               return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+       };
+});
+
+jQuery.extend({
+       filter: function( expr, elems, not ) {
+               if ( not ) {
+                       expr = ":not(" + expr + ")";
+               }
+
+               return elems.length === 1 ?
+                       jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+                       jQuery.find.matches(expr, elems);
+       },
+
+       dir: function( elem, dir, until ) {
+               var matched = [],
+                       cur = elem[ dir ];
+
+               while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+                       if ( cur.nodeType === 1 ) {
+                               matched.push( cur );
+                       }
+                       cur = cur[dir];
+               }
+               return matched;
+       },
+
+       nth: function( cur, result, dir, elem ) {
+               result = result || 1;
+               var num = 0;
+
+               for ( ; cur; cur = cur[dir] ) {
+                       if ( cur.nodeType === 1 && ++num === result ) {
+                               break;
+                       }
+               }
+
+               return cur;
+       },
+
+       sibling: function( n, elem ) {
+               var r = [];
+
+               for ( ; n; n = n.nextSibling ) {
+                       if ( n.nodeType === 1 && n !== elem ) {
+                               r.push( n );
+                       }
+               }
+
+               return r;
+       }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+       // Can't pass null or undefined to indexOf in Firefox 4
+       // Set to 0 to skip string check
+       qualifier = qualifier || 0;
+
+       if ( jQuery.isFunction( qualifier ) ) {
+               return jQuery.grep(elements, function( elem, i ) {
+                       var retVal = !!qualifier.call( elem, i, elem );
+                       return retVal === keep;
+               });
+
+       } else if ( qualifier.nodeType ) {
+               return jQuery.grep(elements, function( elem, i ) {
+                       return ( elem === qualifier ) === keep;
+               });
+
+       } else if ( typeof qualifier === "string" ) {
+               var filtered = jQuery.grep(elements, function( elem ) {
+                       return elem.nodeType === 1;
+               });
+
+               if ( isSimple.test( qualifier ) ) {
+                       return jQuery.filter(qualifier, filtered, !keep);
+               } else {
+                       qualifier = jQuery.filter( qualifier, filtered );
+               }
+       }
+
+       return jQuery.grep(elements, function( elem, i ) {
+               return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+       });
+}
+
+
+
+
+function createSafeFragment( document ) {
+       var list = nodeNames.split( "|" ),
+       safeFrag = document.createDocumentFragment();
+
+       if ( safeFrag.createElement ) {
+               while ( list.length ) {
+                       safeFrag.createElement(
+                               list.pop()
+                       );
+               }
+       }
+       return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+               "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+       rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+       rleadingWhitespace = /^\s+/,
+       rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+       rtagName = /<([\w:]+)/,
+       rtbody = /<tbody/i,
+       rhtml = /<|&#?\w+;/,
+       rnoInnerhtml = /<(?:script|style)/i,
+       rnocache = /<(?:script|object|embed|option|style)/i,
+       rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+       // checked="checked" or checked
+       rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+       rscriptType = /\/(java|ecma)script/i,
+       rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+       wrapMap = {
+               option: [ 1, "<select multiple='multiple'>", "</select>" ],
+               legend: [ 1, "<fieldset>", "</fieldset>" ],
+               thead: [ 1, "<table>", "</table>" ],
+               tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+               td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+               col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+               area: [ 1, "<map>", "</map>" ],
+               _default: [ 0, "", "" ]
+       },
+       safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+       wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+       text: function( value ) {
+               return jQuery.access( this, function( value ) {
+                       return value === undefined ?
+                               jQuery.text( this ) :
+                               this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+               }, null, value, arguments.length );
+       },
+
+       wrapAll: function( html ) {
+               if ( jQuery.isFunction( html ) ) {
+                       return this.each(function(i) {
+                               jQuery(this).wrapAll( html.call(this, i) );
+                       });
+               }
+
+               if ( this[0] ) {
+                       // The elements to wrap the target around
+                       var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+                       if ( this[0].parentNode ) {
+                               wrap.insertBefore( this[0] );
+                       }
+
+                       wrap.map(function() {
+                               var elem = this;
+
+                               while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+                                       elem = elem.firstChild;
+                               }
+
+                               return elem;
+                       }).append( this );
+               }
+
+               return this;
+       },
+
+       wrapInner: function( html ) {
+               if ( jQuery.isFunction( html ) ) {
+                       return this.each(function(i) {
+                               jQuery(this).wrapInner( html.call(this, i) );
+                       });
+               }
+
+               return this.each(function() {
+                       var self = jQuery( this ),
+                               contents = self.contents();
+
+                       if ( contents.length ) {
+                               contents.wrapAll( html );
+
+                       } else {
+                               self.append( html );
+                       }
+               });
+       },
+
+       wrap: function( html ) {
+               var isFunction = jQuery.isFunction( html );
+
+               return this.each(function(i) {
+                       jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+               });
+       },
+
+       unwrap: function() {
+               return this.parent().each(function() {
+                       if ( !jQuery.nodeName( this, "body" ) ) {
+                               jQuery( this ).replaceWith( this.childNodes );
+                       }
+               }).end();
+       },
+
+       append: function() {
+               return this.domManip(arguments, true, function( elem ) {
+                       if ( this.nodeType === 1 ) {
+                               this.appendChild( elem );
+                       }
+               });
+       },
+
+       prepend: function() {
+               return this.domManip(arguments, true, function( elem ) {
+                       if ( this.nodeType === 1 ) {
+                               this.insertBefore( elem, this.firstChild );
+                       }
+               });
+       },
+
+       before: function() {
+               if ( this[0] && this[0].parentNode ) {
+                       return this.domManip(arguments, false, function( elem ) {
+                               this.parentNode.insertBefore( elem, this );
+                       });
+               } else if ( arguments.length ) {
+                       var set = jQuery.clean( arguments );
+                       set.push.apply( set, this.toArray() );
+                       return this.pushStack( set, "before", arguments );
+               }
+       },
+
+       after: function() {
+               if ( this[0] && this[0].parentNode ) {
+                       return this.domManip(arguments, false, function( elem ) {
+                               this.parentNode.insertBefore( elem, this.nextSibling );
+                       });
+               } else if ( arguments.length ) {
+                       var set = this.pushStack( this, "after", arguments );
+                       set.push.apply( set, jQuery.clean(arguments) );
+                       return set;
+               }
+       },
+
+       // keepData is for internal use only--do not document
+       remove: function( selector, keepData ) {
+               for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+                       if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+                               if ( !keepData && elem.nodeType === 1 ) {
+                                       jQuery.cleanData( elem.getElementsByTagName("*") );
+                                       jQuery.cleanData( [ elem ] );
+                               }
+
+                               if ( elem.parentNode ) {
+                                       elem.parentNode.removeChild( elem );
+                               }
+                       }
+               }
+
+               return this;
+       },
+
+       empty: function() {
+               for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+                       // Remove element nodes and prevent memory leaks
+                       if ( elem.nodeType === 1 ) {
+                               jQuery.cleanData( elem.getElementsByTagName("*") );
+                       }
+
+                       // Remove any remaining nodes
+                       while ( elem.firstChild ) {
+                               elem.removeChild( elem.firstChild );
+                       }
+               }
+
+               return this;
+       },
+
+       clone: function( dataAndEvents, deepDataAndEvents ) {
+               dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+               deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+               return this.map( function () {
+                       return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+               });
+       },
+
+       html: function( value ) {
+               return jQuery.access( this, function( value ) {
+                       var elem = this[0] || {},
+                               i = 0,
+                               l = this.length;
+
+                       if ( value === undefined ) {
+                               return elem.nodeType === 1 ?
+                                       elem.innerHTML.replace( rinlinejQuery, "" ) :
+                                       null;
+                       }
+
+
+                       if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+                               ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+                               !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+                               value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+                               try {
+                                       for (; i < l; i++ ) {
+                                               // Remove element nodes and prevent memory leaks
+                                               elem = this[i] || {};
+                                               if ( elem.nodeType === 1 ) {
+                                                       jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+                                                       elem.innerHTML = value;
+                                               }
+                                       }
+
+                                       elem = 0;
+
+                               // If using innerHTML throws an exception, use the fallback method
+                               } catch(e) {}
+                       }
+
+                       if ( elem ) {
+                               this.empty().append( value );
+                       }
+               }, null, value, arguments.length );
+       },
+
+       replaceWith: function( value ) {
+               if ( this[0] && this[0].parentNode ) {
+                       // Make sure that the elements are removed from the DOM before they are inserted
+                       // this can help fix replacing a parent with child elements
+                       if ( jQuery.isFunction( value ) ) {
+                               return this.each(function(i) {
+                                       var self = jQuery(this), old = self.html();
+                                       self.replaceWith( value.call( this, i, old ) );
+                               });
+                       }
+
+                       if ( typeof value !== "string" ) {
+                               value = jQuery( value ).detach();
+                       }
+
+                       return this.each(function() {
+                               var next = this.nextSibling,
+                                       parent = this.parentNode;
+
+                               jQuery( this ).remove();
+
+                               if ( next ) {
+                                       jQuery(next).before( value );
+                               } else {
+                                       jQuery(parent).append( value );
+                               }
+                       });
+               } else {
+                       return this.length ?
+                               this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+                               this;
+               }
+       },
+
+       detach: function( selector ) {
+               return this.remove( selector, true );
+       },
+
+       domManip: function( args, table, callback ) {
+               var results, first, fragment, parent,
+                       value = args[0],
+                       scripts = [];
+
+               // We can't cloneNode fragments that contain checked, in WebKit
+               if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+                       return this.each(function() {
+                               jQuery(this).domManip( args, table, callback, true );
+                       });
+               }
+
+               if ( jQuery.isFunction(value) ) {
+                       return this.each(function(i) {
+                               var self = jQuery(this);
+                               args[0] = value.call(this, i, table ? self.html() : undefined);
+                               self.domManip( args, table, callback );
+                       });
+               }
+
+               if ( this[0] ) {
+                       parent = value && value.parentNode;
+
+                       // If we're in a fragment, just use that instead of building a new one
+                       if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+                               results = { fragment: parent };
+
+                       } else {
+                               results = jQuery.buildFragment( args, this, scripts );
+                       }
+
+                       fragment = results.fragment;
+
+                       if ( fragment.childNodes.length === 1 ) {
+                               first = fragment = fragment.firstChild;
+                       } else {
+                               first = fragment.firstChild;
+                       }
+
+                       if ( first ) {
+                               table = table && jQuery.nodeName( first, "tr" );
+
+                               for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+                                       callback.call(
+                                               table ?
+                                                       root(this[i], first) :
+                                                       this[i],
+                                               // Make sure that we do not leak memory by inadvertently discarding
+                                               // the original fragment (which might have attached data) instead of
+                                               // using it; in addition, use the original fragment object for the last
+                                               // item instead of first because it can end up being emptied incorrectly
+                                               // in certain situations (Bug #8070).
+                                               // Fragments from the fragment cache must always be cloned and never used
+                                               // in place.
+                                               results.cacheable || ( l > 1 && i < lastIndex ) ?
+                                                       jQuery.clone( fragment, true, true ) :
+                                                       fragment
+                                       );
+                               }
+                       }
+
+                       if ( scripts.length ) {
+                               jQuery.each( scripts, function( i, elem ) {
+                                       if ( elem.src ) {
+                                               jQuery.ajax({
+                                                       type: "GET",
+                                                       global: false,
+                                                       url: elem.src,
+                                                       async: false,
+                                                       dataType: "script"
+                                               });
+                                       } else {
+                                               jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+                                       }
+
+                                       if ( elem.parentNode ) {
+                                               elem.parentNode.removeChild( elem );
+                                       }
+                               });
+                       }
+               }
+
+               return this;
+       }
+});
+
+function root( elem, cur ) {
+       return jQuery.nodeName(elem, "table") ?
+               (elem.getElementsByTagName("tbody")[0] ||
+               elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+               elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+       if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+               return;
+       }
+
+       var type, i, l,
+               oldData = jQuery._data( src ),
+               curData = jQuery._data( dest, oldData ),
+               events = oldData.events;
+
+       if ( events ) {
+               delete curData.handle;
+               curData.events = {};
+
+               for ( type in events ) {
+                       for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+                               jQuery.event.add( dest, type, events[ type ][ i ] );
+                       }
+               }
+       }
+
+       // make the cloned public data object a copy from the original
+       if ( curData.data ) {
+               curData.data = jQuery.extend( {}, curData.data );
+       }
+}
+
+function cloneFixAttributes( src, dest ) {
+       var nodeName;
+
+       // We do not need to do anything for non-Elements
+       if ( dest.nodeType !== 1 ) {
+               return;
+       }
+
+       // clearAttributes removes the attributes, which we don't want,
+       // but also removes the attachEvent events, which we *do* want
+       if ( dest.clearAttributes ) {
+               dest.clearAttributes();
+       }
+
+       // mergeAttributes, in contrast, only merges back on the
+       // original attributes, not the events
+       if ( dest.mergeAttributes ) {
+               dest.mergeAttributes( src );
+       }
+
+       nodeName = dest.nodeName.toLowerCase();
+
+       // IE6-8 fail to clone children inside object elements that use
+       // the proprietary classid attribute value (rather than the type
+       // attribute) to identify the type of content to display
+       if ( nodeName === "object" ) {
+               dest.outerHTML = src.outerHTML;
+
+       } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+               // IE6-8 fails to persist the checked state of a cloned checkbox
+               // or radio button. Worse, IE6-7 fail to give the cloned element
+               // a checked appearance if the defaultChecked value isn't also set
+               if ( src.checked ) {
+                       dest.defaultChecked = dest.checked = src.checked;
+               }
+
+               // IE6-7 get confused and end up setting the value of a cloned
+               // checkbox/radio button to an empty string instead of "on"
+               if ( dest.value !== src.value ) {
+                       dest.value = src.value;
+               }
+
+       // IE6-8 fails to return the selected option to the default selected
+       // state when cloning options
+       } else if ( nodeName === "option" ) {
+               dest.selected = src.defaultSelected;
+
+       // IE6-8 fails to set the defaultValue to the correct value when
+       // cloning other types of input fields
+       } else if ( nodeName === "input" || nodeName === "textarea" ) {
+               dest.defaultValue = src.defaultValue;
+
+       // IE blanks contents when cloning scripts
+       } else if ( nodeName === "script" && dest.text !== src.text ) {
+               dest.text = src.text;
+       }
+
+       // Event data gets referenced instead of copied if the expando
+       // gets copied too
+       dest.removeAttribute( jQuery.expando );
+
+       // Clear flags for bubbling special change/submit events, they must
+       // be reattached when the newly cloned events are first activated
+       dest.removeAttribute( "_submit_attached" );
+       dest.removeAttribute( "_change_attached" );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+       var fragment, cacheable, cacheresults, doc,
+       first = args[ 0 ];
+
+       // nodes may contain either an explicit document object,
+       // a jQuery collection or context object.
+       // If nodes[0] contains a valid object to assign to doc
+       if ( nodes && nodes[0] ) {
+               doc = nodes[0].ownerDocument || nodes[0];
+       }
+
+       // Ensure that an attr object doesn't incorrectly stand in as a document object
+       // Chrome and Firefox seem to allow this to occur and will throw exception
+       // Fixes #8950
+       if ( !doc.createDocumentFragment ) {
+               doc = document;
+       }
+
+       // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+       // Cloning options loses the selected state, so don't cache them
+       // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+       // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+       // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+       if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+               first.charAt(0) === "<" && !rnocache.test( first ) &&
+               (jQuery.support.checkClone || !rchecked.test( first )) &&
+               (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+               cacheable = true;
+
+               cacheresults = jQuery.fragments[ first ];
+               if ( cacheresults && cacheresults !== 1 ) {
+                       fragment = cacheresults;
+               }
+       }
+
+       if ( !fragment ) {
+               fragment = doc.createDocumentFragment();
+               jQuery.clean( args, doc, fragment, scripts );
+       }
+
+       if ( cacheable ) {
+               jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+       }
+
+       return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+       appendTo: "append",
+       prependTo: "prepend",
+       insertBefore: "before",
+       insertAfter: "after",
+       replaceAll: "replaceWith"
+}, function( name, original ) {
+       jQuery.fn[ name ] = function( selector ) {
+               var ret = [],
+                       insert = jQuery( selector ),
+                       parent = this.length === 1 && this[0].parentNode;
+
+               if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+                       insert[ original ]( this[0] );
+                       return this;
+
+               } else {
+                       for ( var i = 0, l = insert.length; i < l; i++ ) {
+                               var elems = ( i > 0 ? this.clone(true) : this ).get();
+                               jQuery( insert[i] )[ original ]( elems );
+                               ret = ret.concat( elems );
+                       }
+
+                       return this.pushStack( ret, name, insert.selector );
+               }
+       };
+});
+
+function getAll( elem ) {
+       if ( typeof elem.getElementsByTagName !== "undefined" ) {
+               return elem.getElementsByTagName( "*" );
+
+       } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+               return elem.querySelectorAll( "*" );
+
+       } else {
+               return [];
+       }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+       if ( elem.type === "checkbox" || elem.type === "radio" ) {
+               elem.defaultChecked = elem.checked;
+       }
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+       var nodeName = ( elem.nodeName || "" ).toLowerCase();
+       if ( nodeName === "input" ) {
+               fixDefaultChecked( elem );
+       // Skip scripts, get other children
+       } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+               jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+       }
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+       var div = document.createElement( "div" );
+       safeFragment.appendChild( div );
+
+       div.innerHTML = elem.outerHTML;
+       return div.firstChild;
+}
+
+jQuery.extend({
+       clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+               var srcElements,
+                       destElements,
+                       i,
+                       // IE<=8 does not properly clone detached, unknown element nodes
+                       clone = jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ?
+                               elem.cloneNode( true ) :
+                               shimCloneNode( elem );
+
+               if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+                               (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+                       // IE copies events bound via attachEvent when using cloneNode.
+                       // Calling detachEvent on the clone will also remove the events
+                       // from the original. In order to get around this, we use some
+                       // proprietary methods to clear the events. Thanks to MooTools
+                       // guys for this hotness.
+
+                       cloneFixAttributes( elem, clone );
+
+                       // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+                       srcElements = getAll( elem );
+                       destElements = getAll( clone );
+
+                       // Weird iteration because IE will replace the length property
+                       // with an element if you are cloning the body and one of the
+                       // elements on the page has a name or id of "length"
+                       for ( i = 0; srcElements[i]; ++i ) {
+                               // Ensure that the destination node is not null; Fixes #9587
+                               if ( destElements[i] ) {
+                                       cloneFixAttributes( srcElements[i], destElements[i] );
+                               }
+                       }
+               }
+
+               // Copy the events from the original to the clone
+               if ( dataAndEvents ) {
+                       cloneCopyEvent( elem, clone );
+
+                       if ( deepDataAndEvents ) {
+                               srcElements = getAll( elem );
+                               destElements = getAll( clone );
+
+                               for ( i = 0; srcElements[i]; ++i ) {
+                                       cloneCopyEvent( srcElements[i], destElements[i] );
+                               }
+                       }
+               }
+
+               srcElements = destElements = null;
+
+               // Return the cloned set
+               return clone;
+       },
+
+       clean: function( elems, context, fragment, scripts ) {
+               var checkScriptType, script, j,
+                               ret = [];
+
+               context = context || document;
+
+               // !context.createElement fails in IE with an error but returns typeof 'object'
+               if ( typeof context.createElement === "undefined" ) {
+                       context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+               }
+
+               for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+                       if ( typeof elem === "number" ) {
+                               elem += "";
+                       }
+
+                       if ( !elem ) {
+                               continue;
+                       }
+
+                       // Convert html string into DOM nodes
+                       if ( typeof elem === "string" ) {
+                               if ( !rhtml.test( elem ) ) {
+                                       elem = context.createTextNode( elem );
+                               } else {
+                                       // Fix "XHTML"-style tags in all browsers
+                                       elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+                                       // Trim whitespace, otherwise indexOf won't work as expected
+                                       var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+                                               wrap = wrapMap[ tag ] || wrapMap._default,
+                                               depth = wrap[0],
+                                               div = context.createElement("div"),
+                                               safeChildNodes = safeFragment.childNodes,
+                                               remove;
+
+                                       // Append wrapper element to unknown element safe doc fragment
+                                       if ( context === document ) {
+                                               // Use the fragment we've already created for this document
+                                               safeFragment.appendChild( div );
+                                       } else {
+                                               // Use a fragment created with the owner document
+                                               createSafeFragment( context ).appendChild( div );
+                                       }
+
+                                       // Go to html and back, then peel off extra wrappers
+                                       div.innerHTML = wrap[1] + elem + wrap[2];
+
+                                       // Move to the right depth
+                                       while ( depth-- ) {
+                                               div = div.lastChild;
+                                       }
+
+                                       // Remove IE's autoinserted <tbody> from table fragments
+                                       if ( !jQuery.support.tbody ) {
+
+                                               // String was a <table>, *may* have spurious <tbody>
+                                               var hasBody = rtbody.test(elem),
+                                                       tbody = tag === "table" && !hasBody ?
+                                                               div.firstChild && div.firstChild.childNodes :
+
+                                                               // String was a bare <thead> or <tfoot>
+                                                               wrap[1] === "<table>" && !hasBody ?
+                                                                       div.childNodes :
+                                                                       [];
+
+                                               for ( j = tbody.length - 1; j >= 0 ; --j ) {
+                                                       if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+                                                               tbody[ j ].parentNode.removeChild( tbody[ j ] );
+                                                       }
+                                               }
+                                       }
+
+                                       // IE completely kills leading whitespace when innerHTML is used
+                                       if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+                                               div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+                                       }
+
+                                       elem = div.childNodes;
+
+                                       // Clear elements from DocumentFragment (safeFragment or otherwise)
+                                       // to avoid hoarding elements. Fixes #11356
+                                       if ( div ) {
+                                               div.parentNode.removeChild( div );
+
+                                               // Guard against -1 index exceptions in FF3.6
+                                               if ( safeChildNodes.length > 0 ) {
+                                                       remove = safeChildNodes[ safeChildNodes.length - 1 ];
+
+                                                       if ( remove && remove.parentNode ) {
+                                                               remove.parentNode.removeChild( remove );
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+
+                       // Resets defaultChecked for any radios and checkboxes
+                       // about to be appended to the DOM in IE 6/7 (#8060)
+                       var len;
+                       if ( !jQuery.support.appendChecked ) {
+                               if ( elem[0] && typeof (len = elem.length) === "number" ) {
+                                       for ( j = 0; j < len; j++ ) {
+                                               findInputs( elem[j] );
+                                       }
+                               } else {
+                                       findInputs( elem );
+                               }
+                       }
+
+                       if ( elem.nodeType ) {
+                               ret.push( elem );
+                       } else {
+                               ret = jQuery.merge( ret, elem );
+                       }
+               }
+
+               if ( fragment ) {
+                       checkScriptType = function( elem ) {
+                               return !elem.type || rscriptType.test( elem.type );
+                       };
+                       for ( i = 0; ret[i]; i++ ) {
+                               script = ret[i];
+                               if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) {
+                                       scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script );
+
+                               } else {
+                                       if ( script.nodeType === 1 ) {
+                                               var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType );
+
+                                               ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+                                       }
+                                       fragment.appendChild( script );
+                               }
+                       }
+               }
+
+               return ret;
+       },
+
+       cleanData: function( elems ) {
+               var data, id,
+                       cache = jQuery.cache,
+                       special = jQuery.event.special,
+                       deleteExpando = jQuery.support.deleteExpando;
+
+               for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+                       if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+                               continue;
+                       }
+
+                       id = elem[ jQuery.expando ];
+
+                       if ( id ) {
+                               data = cache[ id ];
+
+                               if ( data && data.events ) {
+                                       for ( var type in data.events ) {
+                                               if ( special[ type ] ) {
+                                                       jQuery.event.remove( elem, type );
+
+                                               // This is a shortcut to avoid jQuery.event.remove's overhead
+                                               } else {
+                                                       jQuery.removeEvent( elem, type, data.handle );
+                                               }
+                                       }
+
+                                       // Null the DOM reference to avoid IE6/7/8 leak (#7054)
+                                       if ( data.handle ) {
+                                               data.handle.elem = null;
+                                       }
+                               }
+
+                               if ( deleteExpando ) {
+                                       delete elem[ jQuery.expando ];
+
+                               } else if ( elem.removeAttribute ) {
+                                       elem.removeAttribute( jQuery.expando );
+                               }
+
+                               delete cache[ id ];
+                       }
+               }
+       }
+});
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+       ropacity = /opacity=([^)]*)/,
+       // fixed for IE9, see #8346
+       rupper = /([A-Z]|^ms)/g,
+       rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
+       rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
+       rrelNum = /^([\-+])=([\-+.\de]+)/,
+       rmargin = /^margin/,
+
+       cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+
+       // order is important!
+       cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+
+       curCSS,
+
+       getComputedStyle,
+       currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+       return jQuery.access( this, function( elem, name, value ) {
+               return value !== undefined ?
+                       jQuery.style( elem, name, value ) :
+                       jQuery.css( elem, name );
+       }, name, value, arguments.length > 1 );
+};
+
+jQuery.extend({
+       // Add in style property hooks for overriding the default
+       // behavior of getting and setting a style property
+       cssHooks: {
+               opacity: {
+                       get: function( elem, computed ) {
+                               if ( computed ) {
+                                       // We should always get a number back from opacity
+                                       var ret = curCSS( elem, "opacity" );
+                                       return ret === "" ? "1" : ret;
+
+                               } else {
+                                       return elem.style.opacity;
+                               }
+                       }
+               }
+       },
+
+       // Exclude the following css properties to add px
+       cssNumber: {
+               "fillOpacity": true,
+               "fontWeight": true,
+               "lineHeight": true,
+               "opacity": true,
+               "orphans": true,
+               "widows": true,
+               "zIndex": true,
+               "zoom": true
+       },
+
+       // Add in properties whose names you wish to fix before
+       // setting or getting the value
+       cssProps: {
+               // normalize float css property
+               "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+       },
+
+       // Get and set the style property on a DOM Node
+       style: function( elem, name, value, extra ) {
+               // Don't set styles on text and comment nodes
+               if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+                       return;
+               }
+
+               // Make sure that we're working with the right name
+               var ret, type, origName = jQuery.camelCase( name ),
+                       style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+               name = jQuery.cssProps[ origName ] || origName;
+
+               // Check if we're setting a value
+               if ( value !== undefined ) {
+                       type = typeof value;
+
+                       // convert relative number strings (+= or -=) to relative numbers. #7345
+                       if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+                               value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+                               // Fixes bug #9237
+                               type = "number";
+                       }
+
+                       // Make sure that NaN and null values aren't set. See: #7116
+                       if ( value == null || type === "number" && isNaN( value ) ) {
+                               return;
+                       }
+
+                       // If a number was passed in, add 'px' to the (except for certain CSS properties)
+                       if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+                               value += "px";
+                       }
+
+                       // If a hook was provided, use that value, otherwise just set the specified value
+                       if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+                               // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+                               // Fixes bug #5509
+                               try {
+                                       style[ name ] = value;
+                               } catch(e) {}
+                       }
+
+               } else {
+                       // If a hook was provided get the non-computed value from there
+                       if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+                               return ret;
+                       }
+
+                       // Otherwise just get the value from the style object
+                       return style[ name ];
+               }
+       },
+
+       css: function( elem, name, extra ) {
+               var ret, hooks;
+
+               // Make sure that we're working with the right name
+               name = jQuery.camelCase( name );
+               hooks = jQuery.cssHooks[ name ];
+               name = jQuery.cssProps[ name ] || name;
+
+               // cssFloat needs a special treatment
+               if ( name === "cssFloat" ) {
+                       name = "float";
+               }
+
+               // If a hook was provided get the computed value from there
+               if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+                       return ret;
+
+               // Otherwise, if a way to get the computed value exists, use that
+               } else if ( curCSS ) {
+                       return curCSS( elem, name );
+               }
+       },
+
+       // A method for quickly swapping in/out CSS properties to get correct calculations
+       swap: function( elem, options, callback ) {
+               var old = {},
+                       ret, name;
+
+               // Remember the old values, and insert the new ones
+               for ( name in options ) {
+                       old[ name ] = elem.style[ name ];
+                       elem.style[ name ] = options[ name ];
+               }
+
+               ret = callback.call( elem );
+
+               // Revert the old values
+               for ( name in options ) {
+                       elem.style[ name ] = old[ name ];
+               }
+
+               return ret;
+       }
+});
+
+// DEPRECATED in 1.3, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+       getComputedStyle = function( elem, name ) {
+               var ret, defaultView, computedStyle, width,
+                       style = elem.style;
+
+               name = name.replace( rupper, "-$1" ).toLowerCase();
+
+               if ( (defaultView = elem.ownerDocument.defaultView) &&
+                               (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+
+                       ret = computedStyle.getPropertyValue( name );
+                       if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+                               ret = jQuery.style( elem, name );
+                       }
+               }
+
+               // A tribute to the "awesome hack by Dean Edwards"
+               // WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
+               // which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+               if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
+                       width = style.width;
+                       style.width = ret;
+                       ret = computedStyle.width;
+                       style.width = width;
+               }
+
+               return ret;
+       };
+}
+
+if ( document.documentElement.currentStyle ) {
+       currentStyle = function( elem, name ) {
+               var left, rsLeft, uncomputed,
+                       ret = elem.currentStyle && elem.currentStyle[ name ],
+                       style = elem.style;
+
+               // Avoid setting ret to empty string here
+               // so we don't default to auto
+               if ( ret == null && style && (uncomputed = style[ name ]) ) {
+                       ret = uncomputed;
+               }
+
+               // From the awesome hack by Dean Edwards
+               // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+               // If we're not dealing with a regular pixel number
+               // but a number that has a weird ending, we need to convert it to pixels
+               if ( rnumnonpx.test( ret ) ) {
+
+                       // Remember the original values
+                       left = style.left;
+                       rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+                       // Put in the new values to get a computed value out
+                       if ( rsLeft ) {
+                               elem.runtimeStyle.left = elem.currentStyle.left;
+                       }
+                       style.left = name === "fontSize" ? "1em" : ret;
+                       ret = style.pixelLeft + "px";
+
+                       // Revert the changed values
+                       style.left = left;
+                       if ( rsLeft ) {
+                               elem.runtimeStyle.left = rsLeft;
+                       }
+               }
+
+               return ret === "" ? "auto" : ret;
+       };
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWidthOrHeight( elem, name, extra ) {
+
+       // Start with offset property
+       var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+               i = name === "width" ? 1 : 0,
+               len = 4;
+
+       if ( val > 0 ) {
+               if ( extra !== "border" ) {
+                       for ( ; i < len; i += 2 ) {
+                               if ( !extra ) {
+                                       val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+                               }
+                               if ( extra === "margin" ) {
+                                       val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
+                               } else {
+                                       val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+                               }
+                       }
+               }
+
+               return val + "px";
+       }
+
+       // Fall back to computed then uncomputed css if necessary
+       val = curCSS( elem, name );
+       if ( val < 0 || val == null ) {
+               val = elem.style[ name ];
+       }
+
+       // Computed unit is not pixels. Stop here and return.
+       if ( rnumnonpx.test(val) ) {
+               return val;
+       }
+
+       // Normalize "", auto, and prepare for extra
+       val = parseFloat( val ) || 0;
+
+       // Add padding, border, margin
+       if ( extra ) {
+               for ( ; i < len; i += 2 ) {
+                       val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+                       if ( extra !== "padding" ) {
+                               val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+                       }
+                       if ( extra === "margin" ) {
+                               val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
+                       }
+               }
+       }
+
+       return val + "px";
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+       jQuery.cssHooks[ name ] = {
+               get: function( elem, computed, extra ) {
+                       if ( computed ) {
+                               if ( elem.offsetWidth !== 0 ) {
+                                       return getWidthOrHeight( elem, name, extra );
+                               } else {
+                                       return jQuery.swap( elem, cssShow, function() {
+                                               return getWidthOrHeight( elem, name, extra );
+                                       });
+                               }
+                       }
+               },
+
+               set: function( elem, value ) {
+                       return rnum.test( value ) ?
+                               value + "px" :
+                               value;
+               }
+       };
+});
+
+if ( !jQuery.support.opacity ) {
+       jQuery.cssHooks.opacity = {
+               get: function( elem, computed ) {
+                       // IE uses filters for opacity
+                       return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+                               ( parseFloat( RegExp.$1 ) / 100 ) + "" :
+                               computed ? "1" : "";
+               },
+
+               set: function( elem, value ) {
+                       var style = elem.style,
+                               currentStyle = elem.currentStyle,
+                               opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+                               filter = currentStyle && currentStyle.filter || style.filter || "";
+
+                       // IE has trouble with opacity if it does not have layout
+                       // Force it by setting the zoom level
+                       style.zoom = 1;
+
+                       // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+                       if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+                               // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+                               // if "filter:" is present at all, clearType is disabled, we want to avoid this
+                               // style.removeAttribute is IE Only, but so apparently is this code path...
+                               style.removeAttribute( "filter" );
+
+                               // if there there is no filter style applied in a css rule, we are done
+                               if ( currentStyle && !currentStyle.filter ) {
+                                       return;
+                               }
+                       }
+
+                       // otherwise, set new filter values
+                       style.filter = ralpha.test( filter ) ?
+                               filter.replace( ralpha, opacity ) :
+                               filter + " " + opacity;
+               }
+       };
+}
+
+jQuery(function() {
+       // This hook cannot be added until DOM ready because the support test
+       // for it is not run until after DOM ready
+       if ( !jQuery.support.reliableMarginRight ) {
+               jQuery.cssHooks.marginRight = {
+                       get: function( elem, computed ) {
+                               // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+                               // Work around by temporarily setting element display to inline-block
+                               return jQuery.swap( elem, { "display": "inline-block" }, function() {
+                                       if ( computed ) {
+                                               return curCSS( elem, "margin-right" );
+                                       } else {
+                                               return elem.style.marginRight;
+                                       }
+                               });
+                       }
+               };
+       }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+       jQuery.expr.filters.hidden = function( elem ) {
+               var width = elem.offsetWidth,
+                       height = elem.offsetHeight;
+
+               return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+       };
+
+       jQuery.expr.filters.visible = function( elem ) {
+               return !jQuery.expr.filters.hidden( elem );
+       };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+       margin: "",
+       padding: "",
+       border: "Width"
+}, function( prefix, suffix ) {
+
+       jQuery.cssHooks[ prefix + suffix ] = {
+               expand: function( value ) {
+                       var i,
+
+                               // assumes a single number if not a string
+                               parts = typeof value === "string" ? value.split(" ") : [ value ],
+                               expanded = {};
+
+                       for ( i = 0; i < 4; i++ ) {
+                               expanded[ prefix + cssExpand[ i ] + suffix ] =
+                                       parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+                       }
+
+                       return expanded;
+               }
+       };
+});
+
+
+
+
+var r20 = /%20/g,
+       rbracket = /\[\]$/,
+       rCRLF = /\r?\n/g,
+       rhash = /#.*$/,
+       rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+       rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+       // #7653, #8125, #8152: local protocol detection
+       rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+       rnoContent = /^(?:GET|HEAD)$/,
+       rprotocol = /^\/\//,
+       rquery = /\?/,
+       rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+       rselectTextarea = /^(?:select|textarea)/i,
+       rspacesAjax = /\s+/,
+       rts = /([?&])_=[^&]*/,
+       rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+       // Keep a copy of the old load method
+       _load = jQuery.fn.load,
+
+       /* Prefilters
+        * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+        * 2) These are called:
+        *    - BEFORE asking for a transport
+        *    - AFTER param serialization (s.data is a string if s.processData is true)
+        * 3) key is the dataType
+        * 4) the catchall symbol "*" can be used
+        * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+        */
+       prefilters = {},
+
+       /* Transports bindings
+        * 1) key is the dataType
+        * 2) the catchall symbol "*" can be used
+        * 3) selection will start with transport dataType and THEN go to "*" if needed
+        */
+       transports = {},
+
+       // Document location
+       ajaxLocation,
+
+       // Document location segments
+       ajaxLocParts,
+
+       // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+       allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+       ajaxLocation = location.href;
+} catch( e ) {
+       // Use the href attribute of an A element
+       // since IE will modify it given document.location
+       ajaxLocation = document.createElement( "a" );
+       ajaxLocation.href = "";
+       ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+       // dataTypeExpression is optional and defaults to "*"
+       return function( dataTypeExpression, func ) {
+
+               if ( typeof dataTypeExpression !== "string" ) {
+                       func = dataTypeExpression;
+                       dataTypeExpression = "*";
+               }
+
+               if ( jQuery.isFunction( func ) ) {
+                       var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+                               i = 0,
+                               length = dataTypes.length,
+                               dataType,
+                               list,
+                               placeBefore;
+
+                       // For each dataType in the dataTypeExpression
+                       for ( ; i < length; i++ ) {
+                               dataType = dataTypes[ i ];
+                               // We control if we're asked to add before
+                               // any existing element
+                               placeBefore = /^\+/.test( dataType );
+                               if ( placeBefore ) {
+                                       dataType = dataType.substr( 1 ) || "*";
+                               }
+                               list = structure[ dataType ] = structure[ dataType ] || [];
+                               // then we add to the structure accordingly
+                               list[ placeBefore ? "unshift" : "push" ]( func );
+                       }
+               }
+       };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+               dataType /* internal */, inspected /* internal */ ) {
+
+       dataType = dataType || options.dataTypes[ 0 ];
+       inspected = inspected || {};
+
+       inspected[ dataType ] = true;
+
+       var list = structure[ dataType ],
+               i = 0,
+               length = list ? list.length : 0,
+               executeOnly = ( structure === prefilters ),
+               selection;
+
+       for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+               selection = list[ i ]( options, originalOptions, jqXHR );
+               // If we got redirected to another dataType
+               // we try there if executing only and not done already
+               if ( typeof selection === "string" ) {
+                       if ( !executeOnly || inspected[ selection ] ) {
+                               selection = undefined;
+                       } else {
+                               options.dataTypes.unshift( selection );
+                               selection = inspectPrefiltersOrTransports(
+                                               structure, options, originalOptions, jqXHR, selection, inspected );
+                       }
+               }
+       }
+       // If we're only executing or nothing was selected
+       // we try the catchall dataType if not done already
+       if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+               selection = inspectPrefiltersOrTransports(
+                               structure, options, originalOptions, jqXHR, "*", inspected );
+       }
+       // unnecessary when only executing (prefilters)
+       // but it'll be ignored by the caller in that case
+       return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+       var key, deep,
+               flatOptions = jQuery.ajaxSettings.flatOptions || {};
+       for ( key in src ) {
+               if ( src[ key ] !== undefined ) {
+                       ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+               }
+       }
+       if ( deep ) {
+               jQuery.extend( true, target, deep );
+       }
+}
+
+jQuery.fn.extend({
+       load: function( url, params, callback ) {
+               if ( typeof url !== "string" && _load ) {
+                       return _load.apply( this, arguments );
+
+               // Don't do a request if no elements are being requested
+               } else if ( !this.length ) {
+                       return this;
+               }
+
+               var off = url.indexOf( " " );
+               if ( off >= 0 ) {
+                       var selector = url.slice( off, url.length );
+                       url = url.slice( 0, off );
+               }
+
+               // Default to a GET request
+               var type = "GET";
+
+               // If the second parameter was provided
+               if ( params ) {
+                       // If it's a function
+                       if ( jQuery.isFunction( params ) ) {
+                               // We assume that it's the callback
+                               callback = params;
+                               params = undefined;
+
+                       // Otherwise, build a param string
+                       } else if ( typeof params === "object" ) {
+                               params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+                               type = "POST";
+                       }
+               }
+
+               var self = this;
+
+               // Request the remote document
+               jQuery.ajax({
+                       url: url,
+                       type: type,
+                       dataType: "html",
+                       data: params,
+                       // Complete callback (responseText is used internally)
+                       complete: function( jqXHR, status, responseText ) {
+                               // Store the response as specified by the jqXHR object
+                               responseText = jqXHR.responseText;
+                               // If successful, inject the HTML into all the matched elements
+                               if ( jqXHR.isResolved() ) {
+                                       // #4825: Get the actual response in case
+                                       // a dataFilter is present in ajaxSettings
+                                       jqXHR.done(function( r ) {
+                                               responseText = r;
+                                       });
+                                       // See if a selector was specified
+                                       self.html( selector ?
+                                               // Create a dummy div to hold the results
+                                               jQuery("<div>")
+                                                       // inject the contents of the document in, removing the scripts
+                                                       // to avoid any 'Permission Denied' errors in IE
+                                                       .append(responseText.replace(rscript, ""))
+
+                                                       // Locate the specified elements
+                                                       .find(selector) :
+
+                                               // If not, just inject the full result
+                                               responseText );
+                               }
+
+                               if ( callback ) {
+                                       self.each( callback, [ responseText, status, jqXHR ] );
+                               }
+                       }
+               });
+
+               return this;
+       },
+
+       serialize: function() {
+               return jQuery.param( this.serializeArray() );
+       },
+
+       serializeArray: function() {
+               return this.map(function(){
+                       return this.elements ? jQuery.makeArray( this.elements ) : this;
+               })
+               .filter(function(){
+                       return this.name && !this.disabled &&
+                               ( this.checked || rselectTextarea.test( this.nodeName ) ||
+                                       rinput.test( this.type ) );
+               })
+               .map(function( i, elem ){
+                       var val = jQuery( this ).val();
+
+                       return val == null ?
+                               null :
+                               jQuery.isArray( val ) ?
+                                       jQuery.map( val, function( val, i ){
+                                               return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+                                       }) :
+                                       { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+               }).get();
+       }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+       jQuery.fn[ o ] = function( f ){
+               return this.on( o, f );
+       };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+       jQuery[ method ] = function( url, data, callback, type ) {
+               // shift arguments if data argument was omitted
+               if ( jQuery.isFunction( data ) ) {
+                       type = type || callback;
+                       callback = data;
+                       data = undefined;
+               }
+
+               return jQuery.ajax({
+                       type: method,
+                       url: url,
+                       data: data,
+                       success: callback,
+                       dataType: type
+               });
+       };
+});
+
+jQuery.extend({
+
+       getScript: function( url, callback ) {
+               return jQuery.get( url, undefined, callback, "script" );
+       },
+
+       getJSON: function( url, data, callback ) {
+               return jQuery.get( url, data, callback, "json" );
+       },
+
+       // Creates a full fledged settings object into target
+       // with both ajaxSettings and settings fields.
+       // If target is omitted, writes into ajaxSettings.
+       ajaxSetup: function( target, settings ) {
+               if ( settings ) {
+                       // Building a settings object
+                       ajaxExtend( target, jQuery.ajaxSettings );
+               } else {
+                       // Extending ajaxSettings
+                       settings = target;
+                       target = jQuery.ajaxSettings;
+               }
+               ajaxExtend( target, settings );
+               return target;
+       },
+
+       ajaxSettings: {
+               url: ajaxLocation,
+               isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+               global: true,
+               type: "GET",
+               contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+               processData: true,
+               async: true,
+               /*
+               timeout: 0,
+               data: null,
+               dataType: null,
+               username: null,
+               password: null,
+               cache: null,
+               traditional: false,
+               headers: {},
+               */
+
+               accepts: {
+                       xml: "application/xml, text/xml",
+                       html: "text/html",
+                       text: "text/plain",
+                       json: "application/json, text/javascript",
+                       "*": allTypes
+               },
+
+               contents: {
+                       xml: /xml/,
+                       html: /html/,
+                       json: /json/
+               },
+
+               responseFields: {
+                       xml: "responseXML",
+                       text: "responseText"
+               },
+
+               // List of data converters
+               // 1) key format is "source_type destination_type" (a single space in-between)
+               // 2) the catchall symbol "*" can be used for source_type
+               converters: {
+
+                       // Convert anything to text
+                       "* text": window.String,
+
+                       // Text to html (true = no transformation)
+                       "text html": true,
+
+                       // Evaluate text as a json expression
+                       "text json": jQuery.parseJSON,
+
+                       // Parse text as xml
+                       "text xml": jQuery.parseXML
+               },
+
+               // For options that shouldn't be deep extended:
+               // you can add your own custom options here if
+               // and when you create one that shouldn't be
+               // deep extended (see ajaxExtend)
+               flatOptions: {
+                       context: true,
+                       url: true
+               }
+       },
+
+       ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+       ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+       // Main method
+       ajax: function( url, options ) {
+
+               // If url is an object, simulate pre-1.5 signature
+               if ( typeof url === "object" ) {
+                       options = url;
+                       url = undefined;
+               }
+
+               // Force options to be an object
+               options = options || {};
+
+               var // Create the final options object
+                       s = jQuery.ajaxSetup( {}, options ),
+                       // Callbacks context
+                       callbackContext = s.context || s,
+                       // Context for global events
+                       // It's the callbackContext if one was provided in the options
+                       // and if it's a DOM node or a jQuery collection
+                       globalEventContext = callbackContext !== s &&
+                               ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+                                               jQuery( callbackContext ) : jQuery.event,
+                       // Deferreds
+                       deferred = jQuery.Deferred(),
+                       completeDeferred = jQuery.Callbacks( "once memory" ),
+                       // Status-dependent callbacks
+                       statusCode = s.statusCode || {},
+                       // ifModified key
+                       ifModifiedKey,
+                       // Headers (they are sent all at once)
+                       requestHeaders = {},
+                       requestHeadersNames = {},
+                       // Response headers
+                       responseHeadersString,
+                       responseHeaders,
+                       // transport
+                       transport,
+                       // timeout handle
+                       timeoutTimer,
+                       // Cross-domain detection vars
+                       parts,
+                       // The jqXHR state
+                       state = 0,
+                       // To know if global events are to be dispatched
+                       fireGlobals,
+                       // Loop variable
+                       i,
+                       // Fake xhr
+                       jqXHR = {
+
+                               readyState: 0,
+
+                               // Caches the header
+                               setRequestHeader: function( name, value ) {
+                                       if ( !state ) {
+                                               var lname = name.toLowerCase();
+                                               name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+                                               requestHeaders[ name ] = value;
+                                       }
+                                       return this;
+                               },
+
+                               // Raw string
+                               getAllResponseHeaders: function() {
+                                       return state === 2 ? responseHeadersString : null;
+                               },
+
+                               // Builds headers hashtable if needed
+                               getResponseHeader: function( key ) {
+                                       var match;
+                                       if ( state === 2 ) {
+                                               if ( !responseHeaders ) {
+                                                       responseHeaders = {};
+                                                       while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+                                                               responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+                                                       }
+                                               }
+                                               match = responseHeaders[ key.toLowerCase() ];
+                                       }
+                                       return match === undefined ? null : match;
+                               },
+
+                               // Overrides response content-type header
+                               overrideMimeType: function( type ) {
+                                       if ( !state ) {
+                                               s.mimeType = type;
+                                       }
+                                       return this;
+                               },
+
+                               // Cancel the request
+                               abort: function( statusText ) {
+                                       statusText = statusText || "abort";
+                                       if ( transport ) {
+                                               transport.abort( statusText );
+                                       }
+                                       done( 0, statusText );
+                                       return this;
+                               }
+                       };
+
+               // Callback for when everything is done
+               // It is defined here because jslint complains if it is declared
+               // at the end of the function (which would be more logical and readable)
+               function done( status, nativeStatusText, responses, headers ) {
+
+                       // Called once
+                       if ( state === 2 ) {
+                               return;
+                       }
+
+                       // State is "done" now
+                       state = 2;
+
+                       // Clear timeout if it exists
+                       if ( timeoutTimer ) {
+                               clearTimeout( timeoutTimer );
+                       }
+
+                       // Dereference transport for early garbage collection
+                       // (no matter how long the jqXHR object will be used)
+                       transport = undefined;
+
+                       // Cache response headers
+                       responseHeadersString = headers || "";
+
+                       // Set readyState
+                       jqXHR.readyState = status > 0 ? 4 : 0;
+
+                       var isSuccess,
+                               success,
+                               error,
+                               statusText = nativeStatusText,
+                               response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+                               lastModified,
+                               etag;
+
+                       // If successful, handle type chaining
+                       if ( status >= 200 && status < 300 || status === 304 ) {
+
+                               // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+                               if ( s.ifModified ) {
+
+                                       if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+                                               jQuery.lastModified[ ifModifiedKey ] = lastModified;
+                                       }
+                                       if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+                                               jQuery.etag[ ifModifiedKey ] = etag;
+                                       }
+                               }
+
+                               // If not modified
+                               if ( status === 304 ) {
+
+                                       statusText = "notmodified";
+                                       isSuccess = true;
+
+                               // If we have data
+                               } else {
+
+                                       try {
+                                               success = ajaxConvert( s, response );
+                                               statusText = "success";
+                                               isSuccess = true;
+                                       } catch(e) {
+                                               // We have a parsererror
+                                               statusText = "parsererror";
+                                               error = e;
+                                       }
+                               }
+                       } else {
+                               // We extract error from statusText
+                               // then normalize statusText and status for non-aborts
+                               error = statusText;
+                               if ( !statusText || status ) {
+                                       statusText = "error";
+                                       if ( status < 0 ) {
+                                               status = 0;
+                                       }
+                               }
+                       }
+
+                       // Set data for the fake xhr object
+                       jqXHR.status = status;
+                       jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+                       // Success/Error
+                       if ( isSuccess ) {
+                               deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+                       } else {
+                               deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+                       }
+
+                       // Status-dependent callbacks
+                       jqXHR.statusCode( statusCode );
+                       statusCode = undefined;
+
+                       if ( fireGlobals ) {
+                               globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+                                               [ jqXHR, s, isSuccess ? success : error ] );
+                       }
+
+                       // Complete
+                       completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+                       if ( fireGlobals ) {
+                               globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+                               // Handle the global AJAX counter
+                               if ( !( --jQuery.active ) ) {
+                                       jQuery.event.trigger( "ajaxStop" );
+                               }
+                       }
+               }
+
+               // Attach deferreds
+               deferred.promise( jqXHR );
+               jqXHR.success = jqXHR.done;
+               jqXHR.error = jqXHR.fail;
+               jqXHR.complete = completeDeferred.add;
+
+               // Status-dependent callbacks
+               jqXHR.statusCode = function( map ) {
+                       if ( map ) {
+                               var tmp;
+                               if ( state < 2 ) {
+                                       for ( tmp in map ) {
+                                               statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+                                       }
+                               } else {
+                                       tmp = map[ jqXHR.status ];
+                                       jqXHR.then( tmp, tmp );
+                               }
+                       }
+                       return this;
+               };
+
+               // Remove hash character (#7531: and string promotion)
+               // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+               // We also use the url parameter if available
+               s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+               // Extract dataTypes list
+               s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+               // Determine if a cross-domain request is in order
+               if ( s.crossDomain == null ) {
+                       parts = rurl.exec( s.url.toLowerCase() );
+                       s.crossDomain = !!( parts &&
+                               ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+                                       ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+                                               ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+                       );
+               }
+
+               // Convert data if not already a string
+               if ( s.data && s.processData && typeof s.data !== "string" ) {
+                       s.data = jQuery.param( s.data, s.traditional );
+               }
+
+               // Apply prefilters
+               inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+               // If request was aborted inside a prefilter, stop there
+               if ( state === 2 ) {
+                       return false;
+               }
+
+               // We can fire global events as of now if asked to
+               fireGlobals = s.global;
+
+               // Uppercase the type
+               s.type = s.type.toUpperCase();
+
+               // Determine if request has content
+               s.hasContent = !rnoContent.test( s.type );
+
+               // Watch for a new set of requests
+               if ( fireGlobals && jQuery.active++ === 0 ) {
+                       jQuery.event.trigger( "ajaxStart" );
+               }
+
+               // More options handling for requests with no content
+               if ( !s.hasContent ) {
+
+                       // If data is available, append data to url
+                       if ( s.data ) {
+                               s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+                               // #9682: remove data so that it's not used in an eventual retry
+                               delete s.data;
+                       }
+
+                       // Get ifModifiedKey before adding the anti-cache parameter
+                       ifModifiedKey = s.url;
+
+                       // Add anti-cache in url if needed
+                       if ( s.cache === false ) {
+
+                               var ts = jQuery.now(),
+                                       // try replacing _= if it is there
+                                       ret = s.url.replace( rts, "$1_=" + ts );
+
+                               // if nothing was replaced, add timestamp to the end
+                               s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+                       }
+               }
+
+               // Set the correct header, if data is being sent
+               if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+                       jqXHR.setRequestHeader( "Content-Type", s.contentType );
+               }
+
+               // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+               if ( s.ifModified ) {
+                       ifModifiedKey = ifModifiedKey || s.url;
+                       if ( jQuery.lastModified[ ifModifiedKey ] ) {
+                               jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+                       }
+                       if ( jQuery.etag[ ifModifiedKey ] ) {
+                               jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+                       }
+               }
+
+               // Set the Accepts header for the server, depending on the dataType
+               jqXHR.setRequestHeader(
+                       "Accept",
+                       s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+                               s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+                               s.accepts[ "*" ]
+               );
+
+               // Check for headers option
+               for ( i in s.headers ) {
+                       jqXHR.setRequestHeader( i, s.headers[ i ] );
+               }
+
+               // Allow custom headers/mimetypes and early abort
+               if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+                               // Abort if not done already
+                               jqXHR.abort();
+                               return false;
+
+               }
+
+               // Install callbacks on deferreds
+               for ( i in { success: 1, error: 1, complete: 1 } ) {
+                       jqXHR[ i ]( s[ i ] );
+               }
+
+               // Get transport
+               transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+               // If no transport, we auto-abort
+               if ( !transport ) {
+                       done( -1, "No Transport" );
+               } else {
+                       jqXHR.readyState = 1;
+                       // Send global event
+                       if ( fireGlobals ) {
+                               globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+                       }
+                       // Timeout
+                       if ( s.async && s.timeout > 0 ) {
+                               timeoutTimer = setTimeout( function(){
+                                       jqXHR.abort( "timeout" );
+                               }, s.timeout );
+                       }
+
+                       try {
+                               state = 1;
+                               transport.send( requestHeaders, done );
+                       } catch (e) {
+                               // Propagate exception as error if not done
+                               if ( state < 2 ) {
+                                       done( -1, e );
+                               // Simply rethrow otherwise
+                               } else {
+                                       throw e;
+                               }
+                       }
+               }
+
+               return jqXHR;
+       },
+
+       // Serialize an array of form elements or a set of
+       // key/values into a query string
+       param: function( a, traditional ) {
+               var s = [],
+                       add = function( key, value ) {
+                               // If value is a function, invoke it and return its value
+                               value = jQuery.isFunction( value ) ? value() : value;
+                               s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+                       };
+
+               // Set traditional to true for jQuery <= 1.3.2 behavior.
+               if ( traditional === undefined ) {
+                       traditional = jQuery.ajaxSettings.traditional;
+               }
+
+               // If an array was passed in, assume that it is an array of form elements.
+               if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+                       // Serialize the form elements
+                       jQuery.each( a, function() {
+                               add( this.name, this.value );
+                       });
+
+               } else {
+                       // If traditional, encode the "old" way (the way 1.3.2 or older
+                       // did it), otherwise encode params recursively.
+                       for ( var prefix in a ) {
+                               buildParams( prefix, a[ prefix ], traditional, add );
+                       }
+               }
+
+               // Return the resulting serialization
+               return s.join( "&" ).replace( r20, "+" );
+       }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+       if ( jQuery.isArray( obj ) ) {
+               // Serialize array item.
+               jQuery.each( obj, function( i, v ) {
+                       if ( traditional || rbracket.test( prefix ) ) {
+                               // Treat each array item as a scalar.
+                               add( prefix, v );
+
+                       } else {
+                               // If array item is non-scalar (array or object), encode its
+                               // numeric index to resolve deserialization ambiguity issues.
+                               // Note that rack (as of 1.0.0) can't currently deserialize
+                               // nested arrays properly, and attempting to do so may cause
+                               // a server error. Possible fixes are to modify rack's
+                               // deserialization algorithm or to provide an option or flag
+                               // to force array serialization to be shallow.
+                               buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+                       }
+               });
+
+       } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+               // Serialize object item.
+               for ( var name in obj ) {
+                       buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+               }
+
+       } else {
+               // Serialize scalar item.
+               add( prefix, obj );
+       }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+       // Counter for holding the number of active queries
+       active: 0,
+
+       // Last-Modified header cache for next request
+       lastModified: {},
+       etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+       var contents = s.contents,
+               dataTypes = s.dataTypes,
+               responseFields = s.responseFields,
+               ct,
+               type,
+               finalDataType,
+               firstDataType;
+
+       // Fill responseXXX fields
+       for ( type in responseFields ) {
+               if ( type in responses ) {
+                       jqXHR[ responseFields[type] ] = responses[ type ];
+               }
+       }
+
+       // Remove auto dataType and get content-type in the process
+       while( dataTypes[ 0 ] === "*" ) {
+               dataTypes.shift();
+               if ( ct === undefined ) {
+                       ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+               }
+       }
+
+       // Check if we're dealing with a known content-type
+       if ( ct ) {
+               for ( type in contents ) {
+                       if ( contents[ type ] && contents[ type ].test( ct ) ) {
+                               dataTypes.unshift( type );
+                               break;
+                       }
+               }
+       }
+
+       // Check to see if we have a response for the expected dataType
+       if ( dataTypes[ 0 ] in responses ) {
+               finalDataType = dataTypes[ 0 ];
+       } else {
+               // Try convertible dataTypes
+               for ( type in responses ) {
+                       if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+                               finalDataType = type;
+                               break;
+                       }
+                       if ( !firstDataType ) {
+                               firstDataType = type;
+                       }
+               }
+               // Or just use first one
+               finalDataType = finalDataType || firstDataType;
+       }
+
+       // If we found a dataType
+       // We add the dataType to the list if needed
+       // and return the corresponding response
+       if ( finalDataType ) {
+               if ( finalDataType !== dataTypes[ 0 ] ) {
+                       dataTypes.unshift( finalDataType );
+               }
+               return responses[ finalDataType ];
+       }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+       // Apply the dataFilter if provided
+       if ( s.dataFilter ) {
+               response = s.dataFilter( response, s.dataType );
+       }
+
+       var dataTypes = s.dataTypes,
+               converters = {},
+               i,
+               key,
+               length = dataTypes.length,
+               tmp,
+               // Current and previous dataTypes
+               current = dataTypes[ 0 ],
+               prev,
+               // Conversion expression
+               conversion,
+               // Conversion function
+               conv,
+               // Conversion functions (transitive conversion)
+               conv1,
+               conv2;
+
+       // For each dataType in the chain
+       for ( i = 1; i < length; i++ ) {
+
+               // Create converters map
+               // with lowercased keys
+               if ( i === 1 ) {
+                       for ( key in s.converters ) {
+                               if ( typeof key === "string" ) {
+                                       converters[ key.toLowerCase() ] = s.converters[ key ];
+                               }
+                       }
+               }
+
+               // Get the dataTypes
+               prev = current;
+               current = dataTypes[ i ];
+
+               // If current is auto dataType, update it to prev
+               if ( current === "*" ) {
+                       current = prev;
+               // If no auto and dataTypes are actually different
+               } else if ( prev !== "*" && prev !== current ) {
+
+                       // Get the converter
+                       conversion = prev + " " + current;
+                       conv = converters[ conversion ] || converters[ "* " + current ];
+
+                       // If there is no direct converter, search transitively
+                       if ( !conv ) {
+                               conv2 = undefined;
+                               for ( conv1 in converters ) {
+                                       tmp = conv1.split( " " );
+                                       if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+                                               conv2 = converters[ tmp[1] + " " + current ];
+                                               if ( conv2 ) {
+                                                       conv1 = converters[ conv1 ];
+                                                       if ( conv1 === true ) {
+                                                               conv = conv2;
+                                                       } else if ( conv2 === true ) {
+                                                               conv = conv1;
+                                                       }
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+                       // If we found no converter, dispatch an error
+                       if ( !( conv || conv2 ) ) {
+                               jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+                       }
+                       // If found converter is not an equivalence
+                       if ( conv !== true ) {
+                               // Convert with 1 or 2 converters accordingly
+                               response = conv ? conv( response ) : conv2( conv1(response) );
+                       }
+               }
+       }
+       return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+       jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+       jsonp: "callback",
+       jsonpCallback: function() {
+               return jQuery.expando + "_" + ( jsc++ );
+       }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+       var inspectData = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType );
+
+       if ( s.dataTypes[ 0 ] === "jsonp" ||
+               s.jsonp !== false && ( jsre.test( s.url ) ||
+                               inspectData && jsre.test( s.data ) ) ) {
+
+               var responseContainer,
+                       jsonpCallback = s.jsonpCallback =
+                               jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+                       previous = window[ jsonpCallback ],
+                       url = s.url,
+                       data = s.data,
+                       replace = "$1" + jsonpCallback + "$2";
+
+               if ( s.jsonp !== false ) {
+                       url = url.replace( jsre, replace );
+                       if ( s.url === url ) {
+                               if ( inspectData ) {
+                                       data = data.replace( jsre, replace );
+                               }
+                               if ( s.data === data ) {
+                                       // Add callback manually
+                                       url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+                               }
+                       }
+               }
+
+               s.url = url;
+               s.data = data;
+
+               // Install callback
+               window[ jsonpCallback ] = function( response ) {
+                       responseContainer = [ response ];
+               };
+
+               // Clean-up function
+               jqXHR.always(function() {
+                       // Set callback back to previous value
+                       window[ jsonpCallback ] = previous;
+                       // Call if it was a function and we have a response
+                       if ( responseContainer && jQuery.isFunction( previous ) ) {
+                               window[ jsonpCallback ]( responseContainer[ 0 ] );
+                       }
+               });
+
+               // Use data converter to retrieve json after script execution
+               s.converters["script json"] = function() {
+                       if ( !responseContainer ) {
+                               jQuery.error( jsonpCallback + " was not called" );
+                       }
+                       return responseContainer[ 0 ];
+               };
+
+               // force json dataType
+               s.dataTypes[ 0 ] = "json";
+
+               // Delegate to script
+               return "script";
+       }
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+       accepts: {
+               script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+       },
+       contents: {
+               script: /javascript|ecmascript/
+       },
+       converters: {
+               "text script": function( text ) {
+                       jQuery.globalEval( text );
+                       return text;
+               }
+       }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+       if ( s.cache === undefined ) {
+               s.cache = false;
+       }
+       if ( s.crossDomain ) {
+               s.type = "GET";
+               s.global = false;
+       }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+       // This transport only deals with cross domain requests
+       if ( s.crossDomain ) {
+
+               var script,
+                       head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+               return {
+
+                       send: function( _, callback ) {
+
+                               script = document.createElement( "script" );
+
+                               script.async = "async";
+
+                               if ( s.scriptCharset ) {
+                                       script.charset = s.scriptCharset;
+                               }
+
+                               script.src = s.url;
+
+                               // Attach handlers for all browsers
+                               script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+                                       if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+                                               // Handle memory leak in IE
+                                               script.onload = script.onreadystatechange = null;
+
+                                               // Remove the script
+                                               if ( head && script.parentNode ) {
+                                                       head.removeChild( script );
+                                               }
+
+                                               // Dereference the script
+                                               script = undefined;
+
+                                               // Callback if not abort
+                                               if ( !isAbort ) {
+                                                       callback( 200, "success" );
+                                               }
+                                       }
+                               };
+                               // Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+                               // This arises when a base node is used (#2709 and #4378).
+                               head.insertBefore( script, head.firstChild );
+                       },
+
+                       abort: function() {
+                               if ( script ) {
+                                       script.onload( 0, 1 );
+                               }
+                       }
+               };
+       }
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+       xhrOnUnloadAbort = window.ActiveXObject ? function() {
+               // Abort all pending requests
+               for ( var key in xhrCallbacks ) {
+                       xhrCallbacks[ key ]( 0, 1 );
+               }
+       } : false,
+       xhrId = 0,
+       xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+       try {
+               return new window.XMLHttpRequest();
+       } catch( e ) {}
+}
+
+function createActiveXHR() {
+       try {
+               return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+       } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+       /* Microsoft failed to properly
+        * implement the XMLHttpRequest in IE7 (can't request local files),
+        * so we use the ActiveXObject when it is available
+        * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+        * we need a fallback.
+        */
+       function() {
+               return !this.isLocal && createStandardXHR() || createActiveXHR();
+       } :
+       // For all other browsers, use the standard XMLHttpRequest object
+       createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+       jQuery.extend( jQuery.support, {
+               ajax: !!xhr,
+               cors: !!xhr && ( "withCredentials" in xhr )
+       });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+       jQuery.ajaxTransport(function( s ) {
+               // Cross domain only allowed if supported through XMLHttpRequest
+               if ( !s.crossDomain || jQuery.support.cors ) {
+
+                       var callback;
+
+                       return {
+                               send: function( headers, complete ) {
+
+                                       // Get a new xhr
+                                       var xhr = s.xhr(),
+                                               handle,
+                                               i;
+
+                                       // Open the socket
+                                       // Passing null username, generates a login popup on Opera (#2865)
+                                       if ( s.username ) {
+                                               xhr.open( s.type, s.url, s.async, s.username, s.password );
+                                       } else {
+                                               xhr.open( s.type, s.url, s.async );
+                                       }
+
+                                       // Apply custom fields if provided
+                                       if ( s.xhrFields ) {
+                                               for ( i in s.xhrFields ) {
+                                                       xhr[ i ] = s.xhrFields[ i ];
+                                               }
+                                       }
+
+                                       // Override mime type if needed
+                                       if ( s.mimeType && xhr.overrideMimeType ) {
+                                               xhr.overrideMimeType( s.mimeType );
+                                       }
+
+                                       // X-Requested-With header
+                                       // For cross-domain requests, seeing as conditions for a preflight are
+                                       // akin to a jigsaw puzzle, we simply never set it to be sure.
+                                       // (it can always be set on a per-request basis or even using ajaxSetup)
+                                       // For same-domain requests, won't change header if already provided.
+                                       if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+                                               headers[ "X-Requested-With" ] = "XMLHttpRequest";
+                                       }
+
+                                       // Need an extra try/catch for cross domain requests in Firefox 3
+                                       try {
+                                               for ( i in headers ) {
+                                                       xhr.setRequestHeader( i, headers[ i ] );
+                                               }
+                                       } catch( _ ) {}
+
+                                       // Do send the request
+                                       // This may raise an exception which is actually
+                                       // handled in jQuery.ajax (so no try/catch here)
+                                       xhr.send( ( s.hasContent && s.data ) || null );
+
+                                       // Listener
+                                       callback = function( _, isAbort ) {
+
+                                               var status,
+                                                       statusText,
+                                                       responseHeaders,
+                                                       responses,
+                                                       xml;
+
+                                               // Firefox throws exceptions when accessing properties
+                                               // of an xhr when a network error occured
+                                               // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+                                               try {
+
+                                                       // Was never called and is aborted or complete
+                                                       if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+                                                               // Only called once
+                                                               callback = undefined;
+
+                                                               // Do not keep as active anymore
+                                                               if ( handle ) {
+                                                                       xhr.onreadystatechange = jQuery.noop;
+                                                                       if ( xhrOnUnloadAbort ) {
+                                                                               delete xhrCallbacks[ handle ];
+                                                                       }
+                                                               }
+
+                                                               // If it's an abort
+                                                               if ( isAbort ) {
+                                                                       // Abort it manually if needed
+                                                                       if ( xhr.readyState !== 4 ) {
+                                                                               xhr.abort();
+                                                                       }
+                                                               } else {
+                                                                       status = xhr.status;
+                                                                       responseHeaders = xhr.getAllResponseHeaders();
+                                                                       responses = {};
+                                                                       xml = xhr.responseXML;
+
+                                                                       // Construct response list
+                                                                       if ( xml && xml.documentElement /* #4958 */ ) {
+                                                                               responses.xml = xml;
+                                                                       }
+
+                                                                       // When requesting binary data, IE6-9 will throw an exception
+                                                                       // on any attempt to access responseText (#11426)
+                                                                       try {
+                                                                               responses.text = xhr.responseText;
+                                                                       } catch( _ ) {
+                                                                       }
+
+                                                                       // Firefox throws an exception when accessing
+                                                                       // statusText for faulty cross-domain requests
+                                                                       try {
+                                                                               statusText = xhr.statusText;
+                                                                       } catch( e ) {
+                                                                               // We normalize with Webkit giving an empty statusText
+                                                                               statusText = "";
+                                                                       }
+
+                                                                       // Filter status for non standard behaviors
+
+                                                                       // If the request is local and we have data: assume a success
+                                                                       // (success with no data won't get notified, that's the best we
+                                                                       // can do given current implementations)
+                                                                       if ( !status && s.isLocal && !s.crossDomain ) {
+                                                                               status = responses.text ? 200 : 404;
+                                                                       // IE - #1450: sometimes returns 1223 when it should be 204
+                                                                       } else if ( status === 1223 ) {
+                                                                               status = 204;
+                                                                       }
+                                                               }
+                                                       }
+                                               } catch( firefoxAccessException ) {
+                                                       if ( !isAbort ) {
+                                                               complete( -1, firefoxAccessException );
+                                                       }
+                                               }
+
+                                               // Call complete if needed
+                                               if ( responses ) {
+                                                       complete( status, statusText, responses, responseHeaders );
+                                               }
+                                       };
+
+                                       // if we're in sync mode or it's in cache
+                                       // and has been retrieved directly (IE6 & IE7)
+                                       // we need to manually fire the callback
+                                       if ( !s.async || xhr.readyState === 4 ) {
+                                               callback();
+                                       } else {
+                                               handle = ++xhrId;
+                                               if ( xhrOnUnloadAbort ) {
+                                                       // Create the active xhrs callbacks list if needed
+                                                       // and attach the unload handler
+                                                       if ( !xhrCallbacks ) {
+                                                               xhrCallbacks = {};
+                                                               jQuery( window ).unload( xhrOnUnloadAbort );
+                                                       }
+                                                       // Add to list of active xhrs callbacks
+                                                       xhrCallbacks[ handle ] = callback;
+                                               }
+                                               xhr.onreadystatechange = callback;
+                                       }
+                               },
+
+                               abort: function() {
+                                       if ( callback ) {
+                                               callback(0,1);
+                                       }
+                               }
+                       };
+               }
+       });
+}
+
+
+
+
+var elemdisplay = {},
+       iframe, iframeDoc,
+       rfxtypes = /^(?:toggle|show|hide)$/,
+       rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+       timerId,
+       fxAttrs = [
+               // height animations
+               [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+               // width animations
+               [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+               // opacity animations
+               [ "opacity" ]
+       ],
+       fxNow;
+
+jQuery.fn.extend({
+       show: function( speed, easing, callback ) {
+               var elem, display;
+
+               if ( speed || speed === 0 ) {
+                       return this.animate( genFx("show", 3), speed, easing, callback );
+
+               } else {
+                       for ( var i = 0, j = this.length; i < j; i++ ) {
+                               elem = this[ i ];
+
+                               if ( elem.style ) {
+                                       display = elem.style.display;
+
+                                       // Reset the inline display of this element to learn if it is
+                                       // being hidden by cascaded rules or not
+                                       if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+                                               display = elem.style.display = "";
+                                       }
+
+                                       // Set elements which have been overridden with display: none
+                                       // in a stylesheet to whatever the default browser style is
+                                       // for such an element
+                                       if ( (display === "" && jQuery.css(elem, "display") === "none") ||
+                                               !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+                                               jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+                                       }
+                               }
+                       }
+
+                       // Set the display of most of the elements in a second loop
+                       // to avoid the constant reflow
+                       for ( i = 0; i < j; i++ ) {
+                               elem = this[ i ];
+
+                               if ( elem.style ) {
+                                       display = elem.style.display;
+
+                                       if ( display === "" || display === "none" ) {
+                                               elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+                                       }
+                               }
+                       }
+
+                       return this;
+               }
+       },
+
+       hide: function( speed, easing, callback ) {
+               if ( speed || speed === 0 ) {
+                       return this.animate( genFx("hide", 3), speed, easing, callback);
+
+               } else {
+                       var elem, display,
+                               i = 0,
+                               j = this.length;
+
+                       for ( ; i < j; i++ ) {
+                               elem = this[i];
+                               if ( elem.style ) {
+                                       display = jQuery.css( elem, "display" );
+
+                                       if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+                                               jQuery._data( elem, "olddisplay", display );
+                                       }
+                               }
+                       }
+
+                       // Set the display of the elements in a second loop
+                       // to avoid the constant reflow
+                       for ( i = 0; i < j; i++ ) {
+                               if ( this[i].style ) {
+                                       this[i].style.display = "none";
+                               }
+                       }
+
+                       return this;
+               }
+       },
+
+       // Save the old toggle function
+       _toggle: jQuery.fn.toggle,
+
+       toggle: function( fn, fn2, callback ) {
+               var bool = typeof fn === "boolean";
+
+               if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+                       this._toggle.apply( this, arguments );
+
+               } else if ( fn == null || bool ) {
+                       this.each(function() {
+                               var state = bool ? fn : jQuery(this).is(":hidden");
+                               jQuery(this)[ state ? "show" : "hide" ]();
+                       });
+
+               } else {
+                       this.animate(genFx("toggle", 3), fn, fn2, callback);
+               }
+
+               return this;
+       },
+
+       fadeTo: function( speed, to, easing, callback ) {
+               return this.filter(":hidden").css("opacity", 0).show().end()
+                                       .animate({opacity: to}, speed, easing, callback);
+       },
+
+       animate: function( prop, speed, easing, callback ) {
+               var optall = jQuery.speed( speed, easing, callback );
+
+               if ( jQuery.isEmptyObject( prop ) ) {
+                       return this.each( optall.complete, [ false ] );
+               }
+
+               // Do not change referenced properties as per-property easing will be lost
+               prop = jQuery.extend( {}, prop );
+
+               function doAnimation() {
+                       // XXX 'this' does not always have a nodeName when running the
+                       // test suite
+
+                       if ( optall.queue === false ) {
+                               jQuery._mark( this );
+                       }
+
+                       var opt = jQuery.extend( {}, optall ),
+                               isElement = this.nodeType === 1,
+                               hidden = isElement && jQuery(this).is(":hidden"),
+                               name, val, p, e, hooks, replace,
+                               parts, start, end, unit,
+                               method;
+
+                       // will store per property easing and be used to determine when an animation is complete
+                       opt.animatedProperties = {};
+
+                       // first pass over propertys to expand / normalize
+                       for ( p in prop ) {
+                               name = jQuery.camelCase( p );
+                               if ( p !== name ) {
+                                       prop[ name ] = prop[ p ];
+                                       delete prop[ p ];
+                               }
+
+                               if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
+                                       replace = hooks.expand( prop[ name ] );
+                                       delete prop[ name ];
+
+                                       // not quite $.extend, this wont overwrite keys already present.
+                                       // also - reusing 'p' from above because we have the correct "name"
+                                       for ( p in replace ) {
+                                               if ( ! ( p in prop ) ) {
+                                                       prop[ p ] = replace[ p ];
+                                               }
+                                       }
+                               }
+                       }
+
+                       for ( name in prop ) {
+                               val = prop[ name ];
+                               // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+                               if ( jQuery.isArray( val ) ) {
+                                       opt.animatedProperties[ name ] = val[ 1 ];
+                                       val = prop[ name ] = val[ 0 ];
+                               } else {
+                                       opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+                               }
+
+                               if ( val === "hide" && hidden || val === "show" && !hidden ) {
+                                       return opt.complete.call( this );
+                               }
+
+                               if ( isElement && ( name === "height" || name === "width" ) ) {
+                                       // Make sure that nothing sneaks out
+                                       // Record all 3 overflow attributes because IE does not
+                                       // change the overflow attribute when overflowX and
+                                       // overflowY are set to the same value
+                                       opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+                                       // Set display property to inline-block for height/width
+                                       // animations on inline elements that are having width/height animated
+                                       if ( jQuery.css( this, "display" ) === "inline" &&
+                                                       jQuery.css( this, "float" ) === "none" ) {
+
+                                               // inline-level elements accept inline-block;
+                                               // block-level elements need to be inline with layout
+                                               if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+                                                       this.style.display = "inline-block";
+
+                                               } else {
+                                                       this.style.zoom = 1;
+                                               }
+                                       }
+                               }
+                       }
+
+                       if ( opt.overflow != null ) {
+                               this.style.overflow = "hidden";
+                       }
+
+                       for ( p in prop ) {
+                               e = new jQuery.fx( this, opt, p );
+                               val = prop[ p ];
+
+                               if ( rfxtypes.test( val ) ) {
+
+                                       // Tracks whether to show or hide based on private
+                                       // data attached to the element
+                                       method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+                                       if ( method ) {
+                                               jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+                                               e[ method ]();
+                                       } else {
+                                               e[ val ]();
+                                       }
+
+                               } else {
+                                       parts = rfxnum.exec( val );
+                                       start = e.cur();
+
+                                       if ( parts ) {
+                                               end = parseFloat( parts[2] );
+                                               unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+                                               // We need to compute starting value
+                                               if ( unit !== "px" ) {
+                                                       jQuery.style( this, p, (end || 1) + unit);
+                                                       start = ( (end || 1) / e.cur() ) * start;
+                                                       jQuery.style( this, p, start + unit);
+                                               }
+
+                                               // If a +=/-= token was provided, we're doing a relative animation
+                                               if ( parts[1] ) {
+                                                       end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+                                               }
+
+                                               e.custom( start, end, unit );
+
+                                       } else {
+                                               e.custom( start, val, "" );
+                                       }
+                               }
+                       }
+
+                       // For JS strict compliance
+                       return true;
+               }
+
+               return optall.queue === false ?
+                       this.each( doAnimation ) :
+                       this.queue( optall.queue, doAnimation );
+       },
+
+       stop: function( type, clearQueue, gotoEnd ) {
+               if ( typeof type !== "string" ) {
+                       gotoEnd = clearQueue;
+                       clearQueue = type;
+                       type = undefined;
+               }
+               if ( clearQueue && type !== false ) {
+                       this.queue( type || "fx", [] );
+               }
+
+               return this.each(function() {
+                       var index,
+                               hadTimers = false,
+                               timers = jQuery.timers,
+                               data = jQuery._data( this );
+
+                       // clear marker counters if we know they won't be
+                       if ( !gotoEnd ) {
+                               jQuery._unmark( true, this );
+                       }
+
+                       function stopQueue( elem, data, index ) {
+                               var hooks = data[ index ];
+                               jQuery.removeData( elem, index, true );
+                               hooks.stop( gotoEnd );
+                       }
+
+                       if ( type == null ) {
+                               for ( index in data ) {
+                                       if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+                                               stopQueue( this, data, index );
+                                       }
+                               }
+                       } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+                               stopQueue( this, data, index );
+                       }
+
+                       for ( index = timers.length; index--; ) {
+                               if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+                                       if ( gotoEnd ) {
+
+                                               // force the next step to be the last
+                                               timers[ index ]( true );
+                                       } else {
+                                               timers[ index ].saveState();
+                                       }
+                                       hadTimers = true;
+                                       timers.splice( index, 1 );
+                               }
+                       }
+
+                       // start the next in the queue if the last step wasn't forced
+                       // timers currently will call their complete callbacks, which will dequeue
+                       // but only if they were gotoEnd
+                       if ( !( gotoEnd && hadTimers ) ) {
+                               jQuery.dequeue( this, type );
+                       }
+               });
+       }
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+       setTimeout( clearFxNow, 0 );
+       return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+       fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+       var obj = {};
+
+       jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+               obj[ this ] = type;
+       });
+
+       return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+       slideDown: genFx( "show", 1 ),
+       slideUp: genFx( "hide", 1 ),
+       slideToggle: genFx( "toggle", 1 ),
+       fadeIn: { opacity: "show" },
+       fadeOut: { opacity: "hide" },
+       fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+       jQuery.fn[ name ] = function( speed, easing, callback ) {
+               return this.animate( props, speed, easing, callback );
+       };
+});
+
+jQuery.extend({
+       speed: function( speed, easing, fn ) {
+               var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+                       complete: fn || !fn && easing ||
+                               jQuery.isFunction( speed ) && speed,
+                       duration: speed,
+                       easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+               };
+
+               opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+                       opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+               // normalize opt.queue - true/undefined/null -> "fx"
+               if ( opt.queue == null || opt.queue === true ) {
+                       opt.queue = "fx";
+               }
+
+               // Queueing
+               opt.old = opt.complete;
+
+               opt.complete = function( noUnmark ) {
+                       if ( jQuery.isFunction( opt.old ) ) {
+                               opt.old.call( this );
+                       }
+
+                       if ( opt.queue ) {
+                               jQuery.dequeue( this, opt.queue );
+                       } else if ( noUnmark !== false ) {
+                               jQuery._unmark( this );
+                       }
+               };
+
+               return opt;
+       },
+
+       easing: {
+               linear: function( p ) {
+                       return p;
+               },
+               swing: function( p ) {
+                       return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
+               }
+       },
+
+       timers: [],
+
+       fx: function( elem, options, prop ) {
+               this.options = options;
+               this.elem = elem;
+               this.prop = prop;
+
+               options.orig = options.orig || {};
+       }
+
+});
+
+jQuery.fx.prototype = {
+       // Simple function for setting a style value
+       update: function() {
+               if ( this.options.step ) {
+                       this.options.step.call( this.elem, this.now, this );
+               }
+
+               ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+       },
+
+       // Get the current size
+       cur: function() {
+               if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+                       return this.elem[ this.prop ];
+               }
+
+               var parsed,
+                       r = jQuery.css( this.elem, this.prop );
+               // Empty strings, null, undefined and "auto" are converted to 0,
+               // complex values such as "rotate(1rad)" are returned as is,
+               // simple values such as "10px" are parsed to Float.
+               return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+       },
+
+       // Start an animation from one number to another
+       custom: function( from, to, unit ) {
+               var self = this,
+                       fx = jQuery.fx;
+
+               this.startTime = fxNow || createFxNow();
+               this.end = to;
+               this.now = this.start = from;
+               this.pos = this.state = 0;
+               this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+               function t( gotoEnd ) {
+                       return self.step( gotoEnd );
+               }
+
+               t.queue = this.options.queue;
+               t.elem = this.elem;
+               t.saveState = function() {
+                       if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+                               if ( self.options.hide ) {
+                                       jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+                               } else if ( self.options.show ) {
+                                       jQuery._data( self.elem, "fxshow" + self.prop, self.end );
+                               }
+                       }
+               };
+
+               if ( t() && jQuery.timers.push(t) && !timerId ) {
+                       timerId = setInterval( fx.tick, fx.interval );
+               }
+       },
+
+       // Simple 'show' function
+       show: function() {
+               var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+               // Remember where we started, so that we can go back to it later
+               this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+               this.options.show = true;
+
+               // Begin the animation
+               // Make sure that we start at a small width/height to avoid any flash of content
+               if ( dataShow !== undefined ) {
+                       // This show is picking up where a previous hide or show left off
+                       this.custom( this.cur(), dataShow );
+               } else {
+                       this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+               }
+
+               // Start by showing the element
+               jQuery( this.elem ).show();
+       },
+
+       // Simple 'hide' function
+       hide: function() {
+               // Remember where we started, so that we can go back to it later
+               this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+               this.options.hide = true;
+
+               // Begin the animation
+               this.custom( this.cur(), 0 );
+       },
+
+       // Each step of an animation
+       step: function( gotoEnd ) {
+               var p, n, complete,
+                       t = fxNow || createFxNow(),
+                       done = true,
+                       elem = this.elem,
+                       options = this.options;
+
+               if ( gotoEnd || t >= options.duration + this.startTime ) {
+                       this.now = this.end;
+                       this.pos = this.state = 1;
+                       this.update();
+
+                       options.animatedProperties[ this.prop ] = true;
+
+                       for ( p in options.animatedProperties ) {
+                               if ( options.animatedProperties[ p ] !== true ) {
+                                       done = false;
+                               }
+                       }
+
+                       if ( done ) {
+                               // Reset the overflow
+                               if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+                                       jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+                                               elem.style[ "overflow" + value ] = options.overflow[ index ];
+                                       });
+                               }
+
+                               // Hide the element if the "hide" operation was done
+                               if ( options.hide ) {
+                                       jQuery( elem ).hide();
+                               }
+
+                               // Reset the properties, if the item has been hidden or shown
+                               if ( options.hide || options.show ) {
+                                       for ( p in options.animatedProperties ) {
+                                               jQuery.style( elem, p, options.orig[ p ] );
+                                               jQuery.removeData( elem, "fxshow" + p, true );
+                                               // Toggle data is no longer needed
+                                               jQuery.removeData( elem, "toggle" + p, true );
+                                       }
+                               }
+
+                               // Execute the complete function
+                               // in the event that the complete function throws an exception
+                               // we must ensure it won't be called twice. #5684
+
+                               complete = options.complete;
+                               if ( complete ) {
+
+                                       options.complete = false;
+                                       complete.call( elem );
+                               }
+                       }
+
+                       return false;
+
+               } else {
+                       // classical easing cannot be used with an Infinity duration
+                       if ( options.duration == Infinity ) {
+                               this.now = t;
+                       } else {
+                               n = t - this.startTime;
+                               this.state = n / options.duration;
+
+                               // Perform the easing function, defaults to swing
+                               this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+                               this.now = this.start + ( (this.end - this.start) * this.pos );
+                       }
+                       // Perform the next step of the animation
+                       this.update();
+               }
+
+               return true;
+       }
+};
+
+jQuery.extend( jQuery.fx, {
+       tick: function() {
+               var timer,
+                       timers = jQuery.timers,
+                       i = 0;
+
+               for ( ; i < timers.length; i++ ) {
+                       timer = timers[ i ];
+                       // Checks the timer has not already been removed
+                       if ( !timer() && timers[ i ] === timer ) {
+                               timers.splice( i--, 1 );
+                       }
+               }
+
+               if ( !timers.length ) {
+                       jQuery.fx.stop();
+               }
+       },
+
+       interval: 13,
+
+       stop: function() {
+               clearInterval( timerId );
+               timerId = null;
+       },
+
+       speeds: {
+               slow: 600,
+               fast: 200,
+               // Default speed
+               _default: 400
+       },
+
+       step: {
+               opacity: function( fx ) {
+                       jQuery.style( fx.elem, "opacity", fx.now );
+               },
+
+               _default: function( fx ) {
+                       if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+                               fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+                       } else {
+                               fx.elem[ fx.prop ] = fx.now;
+                       }
+               }
+       }
+});
+
+// Ensure props that can't be negative don't go there on undershoot easing
+jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
+       // exclude marginTop, marginLeft, marginBottom and marginRight from this list
+       if ( prop.indexOf( "margin" ) ) {
+               jQuery.fx.step[ prop ] = function( fx ) {
+                       jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+               };
+       }
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+       jQuery.expr.filters.animated = function( elem ) {
+               return jQuery.grep(jQuery.timers, function( fn ) {
+                       return elem === fn.elem;
+               }).length;
+       };
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+       if ( !elemdisplay[ nodeName ] ) {
+
+               var body = document.body,
+                       elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+                       display = elem.css( "display" );
+               elem.remove();
+
+               // If the simple way fails,
+               // get element's real default display by attaching it to a temp iframe
+               if ( display === "none" || display === "" ) {
+                       // No iframe to use yet, so create it
+                       if ( !iframe ) {
+                               iframe = document.createElement( "iframe" );
+                               iframe.frameBorder = iframe.width = iframe.height = 0;
+                       }
+
+                       body.appendChild( iframe );
+
+                       // Create a cacheable copy of the iframe document on first call.
+                       // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+                       // document to it; WebKit & Firefox won't allow reusing the iframe document.
+                       if ( !iframeDoc || !iframe.createElement ) {
+                               iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+                               iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
+                               iframeDoc.close();
+                       }
+
+                       elem = iframeDoc.createElement( nodeName );
+
+                       iframeDoc.body.appendChild( elem );
+
+                       display = jQuery.css( elem, "display" );
+                       body.removeChild( iframe );
+               }
+
+               // Store the correct default display
+               elemdisplay[ nodeName ] = display;
+       }
+
+       return elemdisplay[ nodeName ];
+}
+
+
+
+
+var getOffset,
+       rtable = /^t(?:able|d|h)$/i,
+       rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+       getOffset = function( elem, doc, docElem, box ) {
+               try {
+                       box = elem.getBoundingClientRect();
+               } catch(e) {}
+
+               // Make sure we're not dealing with a disconnected DOM node
+               if ( !box || !jQuery.contains( docElem, elem ) ) {
+                       return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+               }
+
+               var body = doc.body,
+                       win = getWindow( doc ),
+                       clientTop  = docElem.clientTop  || body.clientTop  || 0,
+                       clientLeft = docElem.clientLeft || body.clientLeft || 0,
+                       scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
+                       scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+                       top  = box.top  + scrollTop  - clientTop,
+                       left = box.left + scrollLeft - clientLeft;
+
+               return { top: top, left: left };
+       };
+
+} else {
+       getOffset = function( elem, doc, docElem ) {
+               var computedStyle,
+                       offsetParent = elem.offsetParent,
+                       prevOffsetParent = elem,
+                       body = doc.body,
+                       defaultView = doc.defaultView,
+                       prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+                       top = elem.offsetTop,
+                       left = elem.offsetLeft;
+
+               while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+                       if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+                               break;
+                       }
+
+                       computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+                       top  -= elem.scrollTop;
+                       left -= elem.scrollLeft;
+
+                       if ( elem === offsetParent ) {
+                               top  += elem.offsetTop;
+                               left += elem.offsetLeft;
+
+                               if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+                                       top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+                                       left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+                               }
+
+                               prevOffsetParent = offsetParent;
+                               offsetParent = elem.offsetParent;
+                       }
+
+                       if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+                               top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+                               left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+                       }
+
+                       prevComputedStyle = computedStyle;
+               }
+
+               if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+                       top  += body.offsetTop;
+                       left += body.offsetLeft;
+               }
+
+               if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+                       top  += Math.max( docElem.scrollTop, body.scrollTop );
+                       left += Math.max( docElem.scrollLeft, body.scrollLeft );
+               }
+
+               return { top: top, left: left };
+       };
+}
+
+jQuery.fn.offset = function( options ) {
+       if ( arguments.length ) {
+               return options === undefined ?
+                       this :
+                       this.each(function( i ) {
+                               jQuery.offset.setOffset( this, options, i );
+                       });
+       }
+
+       var elem = this[0],
+               doc = elem && elem.ownerDocument;
+
+       if ( !doc ) {
+               return null;
+       }
+
+       if ( elem === doc.body ) {
+               return jQuery.offset.bodyOffset( elem );
+       }
+
+       return getOffset( elem, doc, doc.documentElement );
+};
+
+jQuery.offset = {
+
+       bodyOffset: function( body ) {
+               var top = body.offsetTop,
+                       left = body.offsetLeft;
+
+               if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+                       top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+                       left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+               }
+
+               return { top: top, left: left };
+       },
+
+       setOffset: function( elem, options, i ) {
+               var position = jQuery.css( elem, "position" );
+
+               // set position first, in-case top/left are set even on static elem
+               if ( position === "static" ) {
+                       elem.style.position = "relative";
+               }
+
+               var curElem = jQuery( elem ),
+                       curOffset = curElem.offset(),
+                       curCSSTop = jQuery.css( elem, "top" ),
+                       curCSSLeft = jQuery.css( elem, "left" ),
+                       calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+                       props = {}, curPosition = {}, curTop, curLeft;
+
+               // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+               if ( calculatePosition ) {
+                       curPosition = curElem.position();
+                       curTop = curPosition.top;
+                       curLeft = curPosition.left;
+               } else {
+                       curTop = parseFloat( curCSSTop ) || 0;
+                       curLeft = parseFloat( curCSSLeft ) || 0;
+               }
+
+               if ( jQuery.isFunction( options ) ) {
+                       options = options.call( elem, i, curOffset );
+               }
+
+               if ( options.top != null ) {
+                       props.top = ( options.top - curOffset.top ) + curTop;
+               }
+               if ( options.left != null ) {
+                       props.left = ( options.left - curOffset.left ) + curLeft;
+               }
+
+               if ( "using" in options ) {
+                       options.using.call( elem, props );
+               } else {
+                       curElem.css( props );
+               }
+       }
+};
+
+
+jQuery.fn.extend({
+
+       position: function() {
+               if ( !this[0] ) {
+                       return null;
+               }
+
+               var elem = this[0],
+
+               // Get *real* offsetParent
+               offsetParent = this.offsetParent(),
+
+               // Get correct offsets
+               offset       = this.offset(),
+               parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+               // Subtract element margins
+               // note: when an element has margin: auto the offsetLeft and marginLeft
+               // are the same in Safari causing offset.left to incorrectly be 0
+               offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+               offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+               // Add offsetParent borders
+               parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+               parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+               // Subtract the two offsets
+               return {
+                       top:  offset.top  - parentOffset.top,
+                       left: offset.left - parentOffset.left
+               };
+       },
+
+       offsetParent: function() {
+               return this.map(function() {
+                       var offsetParent = this.offsetParent || document.body;
+                       while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+                               offsetParent = offsetParent.offsetParent;
+                       }
+                       return offsetParent;
+               });
+       }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+       var top = /Y/.test( prop );
+
+       jQuery.fn[ method ] = function( val ) {
+               return jQuery.access( this, function( elem, method, val ) {
+                       var win = getWindow( elem );
+
+                       if ( val === undefined ) {
+                               return win ? (prop in win) ? win[ prop ] :
+                                       jQuery.support.boxModel && win.document.documentElement[ method ] ||
+                                               win.document.body[ method ] :
+                                       elem[ method ];
+                       }
+
+                       if ( win ) {
+                               win.scrollTo(
+                                       !top ? val : jQuery( win ).scrollLeft(),
+                                        top ? val : jQuery( win ).scrollTop()
+                               );
+
+                       } else {
+                               elem[ method ] = val;
+                       }
+               }, method, val, arguments.length, null );
+       };
+});
+
+function getWindow( elem ) {
+       return jQuery.isWindow( elem ) ?
+               elem :
+               elem.nodeType === 9 ?
+                       elem.defaultView || elem.parentWindow :
+                       false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+       var clientProp = "client" + name,
+               scrollProp = "scroll" + name,
+               offsetProp = "offset" + name;
+
+       // innerHeight and innerWidth
+       jQuery.fn[ "inner" + name ] = function() {
+               var elem = this[0];
+               return elem ?
+                       elem.style ?
+                       parseFloat( jQuery.css( elem, type, "padding" ) ) :
+                       this[ type ]() :
+                       null;
+       };
+
+       // outerHeight and outerWidth
+       jQuery.fn[ "outer" + name ] = function( margin ) {
+               var elem = this[0];
+               return elem ?
+                       elem.style ?
+                       parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+                       this[ type ]() :
+                       null;
+       };
+
+       jQuery.fn[ type ] = function( value ) {
+               return jQuery.access( this, function( elem, type, value ) {
+                       var doc, docElemProp, orig, ret;
+
+                       if ( jQuery.isWindow( elem ) ) {
+                               // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+                               doc = elem.document;
+                               docElemProp = doc.documentElement[ clientProp ];
+                               return jQuery.support.boxModel && docElemProp ||
+                                       doc.body && doc.body[ clientProp ] || docElemProp;
+                       }
+
+                       // Get document width or height
+                       if ( elem.nodeType === 9 ) {
+                               // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+                               doc = elem.documentElement;
+
+                               // when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height]
+                               // so we can't use max, as it'll choose the incorrect offset[Width/Height]
+                               // instead we use the correct client[Width/Height]
+                               // support:IE6
+                               if ( doc[ clientProp ] >= doc[ scrollProp ] ) {
+                                       return doc[ clientProp ];
+                               }
+
+                               return Math.max(
+                                       elem.body[ scrollProp ], doc[ scrollProp ],
+                                       elem.body[ offsetProp ], doc[ offsetProp ]
+                               );
+                       }
+
+                       // Get width or height on the element
+                       if ( value === undefined ) {
+                               orig = jQuery.css( elem, type );
+                               ret = parseFloat( orig );
+                               return jQuery.isNumeric( ret ) ? ret : orig;
+                       }
+
+                       // Set the width or height on the element
+                       jQuery( elem ).css( type, value );
+               }, type, value, arguments.length, null );
+       };
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+       define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );
diff --git a/tools/infra-dashboard/js/jquery.min.js b/tools/infra-dashboard/js/jquery.min.js
new file mode 100644 (file)
index 0000000..fad9ab1
--- /dev/null
@@ -0,0 +1,5 @@
+/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\f]' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function qa(){}qa.prototype=d.filters=d.pseudos,d.setFilters=new qa,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function ra(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){
+return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ia={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qa[0].contentDocument,b.write(),b.close(),c=sa(a,b),qa.detach()),ra[a]=c),c}var ua=/^margin/,va=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wa=function(b){return b.ownerDocument.defaultView.opener?b.ownerDocument.defaultView.getComputedStyle(b,null):a.getComputedStyle(b,null)};function xa(a,b,c){var d,e,f,g,h=a.style;return c=c||wa(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),va.test(g)&&ua.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function ya(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),f.removeChild(c),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var za=/^(none|table(?!-c[ea]).+)/,Aa=new RegExp("^("+Q+")(.*)$","i"),Ba=new RegExp("^([+-])=("+Q+")","i"),Ca={position:"absolute",visibility:"hidden",display:"block"},Da={letterSpacing:"0",fontWeight:"400"},Ea=["Webkit","O","Moz","ms"];function Fa(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Ea.length;while(e--)if(b=Ea[e]+c,b in a)return b;return d}function Ga(a,b,c){var d=Aa.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Ha(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ia(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wa(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xa(a,b,f),(0>e||null==e)&&(e=a.style[b]),va.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Ha(a,b,c||(g?"border":"content"),d,f)+"px"}function Ja(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",ta(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xa(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fa(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Ba.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fa(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xa(a,b,d)),"normal"===e&&b in Da&&(e=Da[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?za.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Ca,function(){return Ia(a,b,d)}):Ia(a,b,d):void 0},set:function(a,c,d){var e=d&&wa(a);return Ga(a,c,d?Ha(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=ya(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xa,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ua.test(a)||(n.cssHooks[a+b].set=Ga)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wa(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Ja(this,!0)},hide:function(){return Ja(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Ka(a,b,c,d,e){return new Ka.prototype.init(a,b,c,d,e)}n.Tween=Ka,Ka.prototype={constructor:Ka,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Ka.propHooks[this.prop];return a&&a.get?a.get(this):Ka.propHooks._default.get(this)},run:function(a){var b,c=Ka.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ka.propHooks._default.set(this),this}},Ka.prototype.init.prototype=Ka.prototype,Ka.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Ka.propHooks.scrollTop=Ka.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Ka.prototype.init,n.fx.step={};var La,Ma,Na=/^(?:toggle|show|hide)$/,Oa=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pa=/queueHooks$/,Qa=[Va],Ra={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Oa.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Oa.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sa(){return setTimeout(function(){La=void 0}),La=n.now()}function Ta(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ua(a,b,c){for(var d,e=(Ra[b]||[]).concat(Ra["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Va(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||ta(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Na.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?ta(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ua(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wa(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xa(a,b,c){var d,e,f=0,g=Qa.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=La||Sa(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:La||Sa(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wa(k,j.opts.specialEasing);g>f;f++)if(d=Qa[f].call(j,a,k,j.opts))return d;return n.map(k,Ua,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xa,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Ra[c]=Ra[c]||[],Ra[c].unshift(b)},prefilter:function(a,b){b?Qa.unshift(a):Qa.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xa(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pa.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Ta(b,!0),a,d,e)}}),n.each({slideDown:Ta("show"),slideUp:Ta("hide"),slideToggle:Ta("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(La=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),La=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Ma||(Ma=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Ma),Ma=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Ya,Za,$a=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Za:Ya)),
+void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Za={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$a[b]||n.find.attr;$a[b]=function(a,b,d){var e,f;return d||(f=$a[b],$a[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$a[b]=f),e}});var _a=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_a.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ab=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ab," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ab," ").indexOf(b)>=0)return!0;return!1}});var bb=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cb=n.now(),db=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var eb=/#.*$/,fb=/([?&])_=[^&]*/,gb=/^(.*?):[ \t]*([^\r\n]*)$/gm,hb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,ib=/^(?:GET|HEAD)$/,jb=/^\/\//,kb=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,lb={},mb={},nb="*/".concat("*"),ob=a.location.href,pb=kb.exec(ob.toLowerCase())||[];function qb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function rb(a,b,c,d){var e={},f=a===mb;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function sb(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function tb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function ub(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:ob,type:"GET",isLocal:hb.test(pb[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":nb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?sb(sb(a,n.ajaxSettings),b):sb(n.ajaxSettings,a)},ajaxPrefilter:qb(lb),ajaxTransport:qb(mb),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=gb.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||ob)+"").replace(eb,"").replace(jb,pb[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=kb.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===pb[1]&&h[2]===pb[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(pb[3]||("http:"===pb[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),rb(lb,k,b,v),2===t)return v;i=n.event&&k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!ib.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(db.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=fb.test(d)?d.replace(fb,"$1_="+cb++):d+(db.test(d)?"&":"?")+"_="+cb++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+nb+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=rb(mb,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=tb(k,v,f)),u=ub(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var vb=/%20/g,wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&").replace(vb,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Bb=0,Cb={},Db={0:200,1223:204},Eb=n.ajaxSettings.xhr();a.attachEvent&&a.attachEvent("onunload",function(){for(var a in Cb)Cb[a]()}),k.cors=!!Eb&&"withCredentials"in Eb,k.ajax=Eb=!!Eb,n.ajaxTransport(function(a){var b;return k.cors||Eb&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Bb;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Cb[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Db[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Cb[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Fb=[],Gb=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Fb.pop()||n.expando+"_"+cb++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Gb.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Gb.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Gb,"$1"+e):b.jsonp!==!1&&(b.url+=(db.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Fb.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Hb=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Hb)return Hb.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Ib=a.document.documentElement;function Jb(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Jb(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Ib;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Ib})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Jb(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=ya(k.pixelPosition,function(a,c){return c?(c=xa(a,b),va.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Kb=a.jQuery,Lb=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Lb),b&&a.jQuery===n&&(a.jQuery=Kb),n},typeof b===U&&(a.jQuery=a.$=n),n});
+//# sourceMappingURL=jquery.min.map
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/modernizr.js b/tools/infra-dashboard/js/modernizr.js
new file mode 100644 (file)
index 0000000..c3a5741
--- /dev/null
@@ -0,0 +1,4 @@
+/* Modernizr 2.8.3 (Custom Build) | MIT & BSD
+ * Build: http://modernizr.com/download/#-svg-shiv-mq-cssclasses-teststyles-load
+ */
+;window.Modernizr=function(a,b,c){function x(a){j.cssText=a}function y(a,b){return x(prefixes.join(a+";")+(b||""))}function z(a,b){return typeof a===b}function A(a,b){return!!~(""+a).indexOf(b)}function B(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:z(f,"function")?f.bind(d||b):f}return!1}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m={svg:"http://www.w3.org/2000/svg"},n={},o={},p={},q=[],r=q.slice,s,t=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},u=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return t("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},v={}.hasOwnProperty,w;!z(v,"undefined")&&!z(v.call,"undefined")?w=function(a,b){return v.call(a,b)}:w=function(a,b){return b in a&&z(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=r.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(r.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(r.call(arguments)))};return e}),n.svg=function(){return!!b.createElementNS&&!!b.createElementNS(m.svg,"svg").createSVGRect};for(var C in n)w(n,C)&&(s=C.toLowerCase(),e[s]=n[C](),q.push((e[s]?"":"no-")+s));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)w(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},x(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function q(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?o(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function r(a){a||(a=b);var c=n(a);return s.shivCSS&&!g&&!c.hasCSS&&(c.hasCSS=!!l(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||q(a,c),a}var c="3.7.0",d=a.html5||{},e=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,f=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g,h="_html5shiv",i=0,j={},k;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e.mq=u,e.testStyles=t,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+q.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/moment.min.js b/tools/infra-dashboard/js/moment.min.js
new file mode 100644 (file)
index 0000000..d301ddb
--- /dev/null
@@ -0,0 +1,7 @@
+//! moment.js
+//! version : 2.13.0
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return fd.apply(null,arguments)}function b(a){fd=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c<a.length;++c)d.push(b(a[c],c));return d}function f(a,b){return Object.prototype.hasOwnProperty.call(a,b)}function g(a,b){for(var c in b)f(b,c)&&(a[c]=b[c]);return f(b,"toString")&&(a.toString=b.toString),f(b,"valueOf")&&(a.valueOf=b.valueOf),a}function h(a,b,c,d){return Ja(a,b,c,d,!0).utc()}function i(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null}}function j(a){return null==a._pf&&(a._pf=i()),a._pf}function k(a){if(null==a._isValid){var b=j(a),c=gd.call(b.parsedDateParts,function(a){return null!=a});a._isValid=!isNaN(a._d.getTime())&&b.overflow<0&&!b.empty&&!b.invalidMonth&&!b.invalidWeekday&&!b.nullInput&&!b.invalidFormat&&!b.userInvalidated&&(!b.meridiem||b.meridiem&&c),a._strict&&(a._isValid=a._isValid&&0===b.charsLeftOver&&0===b.unusedTokens.length&&void 0===b.bigHour)}return a._isValid}function l(a){var b=h(NaN);return null!=a?g(j(b),a):j(b).userInvalidated=!0,b}function m(a){return void 0===a}function n(a,b){var c,d,e;if(m(b._isAMomentObject)||(a._isAMomentObject=b._isAMomentObject),m(b._i)||(a._i=b._i),m(b._f)||(a._f=b._f),m(b._l)||(a._l=b._l),m(b._strict)||(a._strict=b._strict),m(b._tzm)||(a._tzm=b._tzm),m(b._isUTC)||(a._isUTC=b._isUTC),m(b._offset)||(a._offset=b._offset),m(b._pf)||(a._pf=j(b)),m(b._locale)||(a._locale=b._locale),hd.length>0)for(c in hd)d=hd[c],e=b[d],m(e)||(a[d]=e);return a}function o(b){n(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),id===!1&&(id=!0,a.updateOffset(this),id=!1)}function p(a){return a instanceof o||null!=a&&null!=a._isAMomentObject}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=q(b)),c}function s(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&r(a[d])!==r(b[d]))&&g++;return g+f}function t(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function u(b,c){var d=!0;return g(function(){return null!=a.deprecationHandler&&a.deprecationHandler(null,b),d&&(t(b+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),d=!1),c.apply(this,arguments)},c)}function v(b,c){null!=a.deprecationHandler&&a.deprecationHandler(b,c),jd[b]||(t(c),jd[b]=!0)}function w(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function x(a){return"[object Object]"===Object.prototype.toString.call(a)}function y(a){var b,c;for(c in a)b=a[c],w(b)?this[c]=b:this["_"+c]=b;this._config=a,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function z(a,b){var c,d=g({},a);for(c in b)f(b,c)&&(x(a[c])&&x(b[c])?(d[c]={},g(d[c],a[c]),g(d[c],b[c])):null!=b[c]?d[c]=b[c]:delete d[c]);return d}function A(a){null!=a&&this.set(a)}function B(a){return a?a.toLowerCase().replace("_","-"):a}function C(a){for(var b,c,d,e,f=0;f<a.length;){for(e=B(a[f]).split("-"),b=e.length,c=B(a[f+1]),c=c?c.split("-"):null;b>0;){if(d=D(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&s(e,c,!0)>=b-1)break;b--}f++}return null}function D(a){var b=null;if(!nd[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=ld._abbr,require("./locale/"+a),E(b)}catch(c){}return nd[a]}function E(a,b){var c;return a&&(c=m(b)?H(a):F(a,b),c&&(ld=c)),ld._abbr}function F(a,b){return null!==b?(b.abbr=a,null!=nd[a]?(v("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),b=z(nd[a]._config,b)):null!=b.parentLocale&&(null!=nd[b.parentLocale]?b=z(nd[b.parentLocale]._config,b):v("parentLocaleUndefined","specified parentLocale is not defined yet")),nd[a]=new A(b),E(a),nd[a]):(delete nd[a],null)}function G(a,b){if(null!=b){var c;null!=nd[a]&&(b=z(nd[a]._config,b)),c=new A(b),c.parentLocale=nd[a],nd[a]=c,E(a)}else null!=nd[a]&&(null!=nd[a].parentLocale?nd[a]=nd[a].parentLocale:null!=nd[a]&&delete nd[a]);return nd[a]}function H(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return ld;if(!c(a)){if(b=D(a))return b;a=[a]}return C(a)}function I(){return kd(nd)}function J(a,b){var c=a.toLowerCase();od[c]=od[c+"s"]=od[b]=a}function K(a){return"string"==typeof a?od[a]||od[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)f(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(b,c){return function(d){return null!=d?(O(this,b,d),a.updateOffset(this,c),this):N(this,b)}}function N(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function O(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function P(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=K(a),w(this[a]))return this[a](b);return this}function Q(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function R(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(sd[a]=e),b&&(sd[b[0]]=function(){return Q(e.apply(this,arguments),b[1],b[2])}),c&&(sd[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function S(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function T(a){var b,c,d=a.match(pd);for(b=0,c=d.length;c>b;b++)sd[d[b]]?d[b]=sd[d[b]]:d[b]=S(d[b]);return function(b){var e,f="";for(e=0;c>e;e++)f+=d[e]instanceof Function?d[e].call(b,a):d[e];return f}}function U(a,b){return a.isValid()?(b=V(b,a.localeData()),rd[b]=rd[b]||T(b),rd[b](a)):a.localeData().invalidDate()}function V(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(qd.lastIndex=0;d>=0&&qd.test(a);)a=a.replace(qd,c),qd.lastIndex=0,d-=1;return a}function W(a,b,c){Kd[a]=w(b)?b:function(a,d){return a&&c?c:b}}function X(a,b){return f(Kd,a)?Kd[a](b._strict,b._locale):new RegExp(Y(a))}function Y(a){return Z(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function Z(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function $(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=r(a)}),c=0;c<a.length;c++)Ld[a[c]]=d}function _(a,b){$(a,function(a,c,d,e){d._w=d._w||{},b(a,d._w,d,e)})}function aa(a,b,c){null!=b&&f(Ld,a)&&Ld[a](b,c._a,c,a)}function ba(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function ca(a,b){return c(this._months)?this._months[a.month()]:this._months[Vd.test(b)?"format":"standalone"][a.month()]}function da(a,b){return c(this._monthsShort)?this._monthsShort[a.month()]:this._monthsShort[Vd.test(b)?"format":"standalone"][a.month()]}function ea(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],d=0;12>d;++d)f=h([2e3,d]),this._shortMonthsParse[d]=this.monthsShort(f,"").toLocaleLowerCase(),this._longMonthsParse[d]=this.months(f,"").toLocaleLowerCase();return c?"MMM"===b?(e=md.call(this._shortMonthsParse,g),-1!==e?e:null):(e=md.call(this._longMonthsParse,g),-1!==e?e:null):"MMM"===b?(e=md.call(this._shortMonthsParse,g),-1!==e?e:(e=md.call(this._longMonthsParse,g),-1!==e?e:null)):(e=md.call(this._longMonthsParse,g),-1!==e?e:(e=md.call(this._shortMonthsParse,g),-1!==e?e:null))}function fa(a,b,c){var d,e,f;if(this._monthsParseExact)return ea.call(this,a,b,c);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),d=0;12>d;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function ga(a,b){var c;if(!a.isValid())return a;if("string"==typeof b)if(/^\d+$/.test(b))b=r(b);else if(b=a.localeData().monthsParse(b),"number"!=typeof b)return a;return c=Math.min(a.date(),ba(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a}function ha(b){return null!=b?(ga(this,b),a.updateOffset(this,!0),this):N(this,"Month")}function ia(){return ba(this.year(),this.month())}function ja(a){return this._monthsParseExact?(f(this,"_monthsRegex")||la.call(this),a?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&a?this._monthsShortStrictRegex:this._monthsShortRegex}function ka(a){return this._monthsParseExact?(f(this,"_monthsRegex")||la.call(this),a?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&a?this._monthsStrictRegex:this._monthsRegex}function la(){function a(a,b){return b.length-a.length}var b,c,d=[],e=[],f=[];for(b=0;12>b;b++)c=h([2e3,b]),d.push(this.monthsShort(c,"")),e.push(this.months(c,"")),f.push(this.months(c,"")),f.push(this.monthsShort(c,""));for(d.sort(a),e.sort(a),f.sort(a),b=0;12>b;b++)d[b]=Z(d[b]),e[b]=Z(e[b]),f[b]=Z(f[b]);this._monthsRegex=new RegExp("^("+f.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+e.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+d.join("|")+")","i")}function ma(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[Nd]<0||c[Nd]>11?Nd:c[Od]<1||c[Od]>ba(c[Md],c[Nd])?Od:c[Pd]<0||c[Pd]>24||24===c[Pd]&&(0!==c[Qd]||0!==c[Rd]||0!==c[Sd])?Pd:c[Qd]<0||c[Qd]>59?Qd:c[Rd]<0||c[Rd]>59?Rd:c[Sd]<0||c[Sd]>999?Sd:-1,j(a)._overflowDayOfYear&&(Md>b||b>Od)&&(b=Od),j(a)._overflowWeeks&&-1===b&&(b=Td),j(a)._overflowWeekday&&-1===b&&(b=Ud),j(a).overflow=b),a}function na(a){var b,c,d,e,f,g,h=a._i,i=$d.exec(h)||_d.exec(h);if(i){for(j(a).iso=!0,b=0,c=be.length;c>b;b++)if(be[b][1].exec(i[1])){e=be[b][0],d=be[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=ce.length;c>b;b++)if(ce[b][1].exec(i[3])){f=(i[2]||" ")+ce[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!ae.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),Ca(a)}else a._isValid=!1}function oa(b){var c=de.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(na(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function pa(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 100>a&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function qa(a){var b=new Date(Date.UTC.apply(null,arguments));return 100>a&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function ra(a){return sa(a)?366:365}function sa(a){return a%4===0&&a%100!==0||a%400===0}function ta(){return sa(this.year())}function ua(a,b,c){var d=7+b-c,e=(7+qa(a,0,d).getUTCDay()-b)%7;return-e+d-1}function va(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ua(a,d,e),j=1+7*(b-1)+h+i;return 0>=j?(f=a-1,g=ra(f)+j):j>ra(a)?(f=a+1,g=j-ra(a)):(f=a,g=j),{year:f,dayOfYear:g}}function wa(a,b,c){var d,e,f=ua(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return 1>g?(e=a.year()-1,d=g+xa(e,b,c)):g>xa(a.year(),b,c)?(d=g-xa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function xa(a,b,c){var d=ua(a,b,c),e=ua(a+1,b,c);return(ra(a)-d+e)/7}function ya(a,b,c){return null!=a?a:null!=b?b:c}function za(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function Aa(a){var b,c,d,e,f=[];if(!a._d){for(d=za(a),a._w&&null==a._a[Od]&&null==a._a[Nd]&&Ba(a),a._dayOfYear&&(e=ya(a._a[Md],d[Md]),a._dayOfYear>ra(e)&&(j(a)._overflowDayOfYear=!0),c=qa(e,0,a._dayOfYear),a._a[Nd]=c.getUTCMonth(),a._a[Od]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[Pd]&&0===a._a[Qd]&&0===a._a[Rd]&&0===a._a[Sd]&&(a._nextDay=!0,a._a[Pd]=0),a._d=(a._useUTC?qa:pa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Pd]=24)}}function Ba(a){var b,c,d,e,f,g,h,i;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=ya(b.GG,a._a[Md],wa(Ka(),1,4).year),d=ya(b.W,1),e=ya(b.E,1),(1>e||e>7)&&(i=!0)):(f=a._locale._week.dow,g=a._locale._week.doy,c=ya(b.gg,a._a[Md],wa(Ka(),f,g).year),d=ya(b.w,1),null!=b.d?(e=b.d,(0>e||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f),1>d||d>xa(c,f,g)?j(a)._overflowWeeks=!0:null!=i?j(a)._overflowWeekday=!0:(h=va(c,d,e,f,g),a._a[Md]=h.year,a._dayOfYear=h.dayOfYear)}function Ca(b){if(b._f===a.ISO_8601)return void na(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=V(b._f,b._locale).match(pd)||[],c=0;c<e.length;c++)f=e[c],d=(h.match(X(f,b))||[])[0],d&&(g=h.substr(0,h.indexOf(d)),g.length>0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),sd[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),aa(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[Pd]<=12&&b._a[Pd]>0&&(j(b).bigHour=void 0),j(b).parsedDateParts=b._a.slice(0),j(b).meridiem=b._meridiem,b._a[Pd]=Da(b._locale,b._a[Pd],b._meridiem),Aa(b),ma(b)}function Da(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function Ea(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;e<a._f.length;e++)f=0,b=n({},a),null!=a._useUTC&&(b._useUTC=a._useUTC),b._f=a._f[e],Ca(b),k(b)&&(f+=j(b).charsLeftOver,f+=10*j(b).unusedTokens.length,j(b).score=f,(null==d||d>f)&&(d=f,c=b));g(a,c||b)}function Fa(a){if(!a._d){var b=L(a._i);a._a=e([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),Aa(a)}}function Ga(a){var b=new o(ma(Ha(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Ha(a){var b=a._i,e=a._f;return a._locale=a._locale||H(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),p(b)?new o(ma(b)):(c(e)?Ea(a):e?Ca(a):d(b)?a._d=b:Ia(a),k(a)||(a._d=null),a))}function Ia(b){var f=b._i;void 0===f?b._d=new Date(a.now()):d(f)?b._d=new Date(f.valueOf()):"string"==typeof f?oa(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),Aa(b)):"object"==typeof f?Fa(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ja(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,Ga(f)}function Ka(a,b,c,d){return Ja(a,b,c,d,!1)}function La(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Ka();for(d=b[0],e=1;e<b.length;++e)(!b[e].isValid()||b[e][a](d))&&(d=b[e]);return d}function Ma(){var a=[].slice.call(arguments,0);return La("isBefore",a)}function Na(){var a=[].slice.call(arguments,0);return La("isAfter",a)}function Oa(a){var b=L(a),c=b.year||0,d=b.quarter||0,e=b.month||0,f=b.week||0,g=b.day||0,h=b.hour||0,i=b.minute||0,j=b.second||0,k=b.millisecond||0;this._milliseconds=+k+1e3*j+6e4*i+1e3*h*60*60,this._days=+g+7*f,this._months=+e+3*d+12*c,this._data={},this._locale=H(),this._bubble()}function Pa(a){return a instanceof Oa}function Qa(a,b){R(a,0,0,function(){var a=this.utcOffset(),c="+";return 0>a&&(a=-a,c="-"),c+Q(~~(a/60),2)+b+Q(~~a%60,2)})}function Ra(a,b){var c=(b||"").match(a)||[],d=c[c.length-1]||[],e=(d+"").match(ie)||["-",0,0],f=+(60*e[1])+r(e[2]);return"+"===e[0]?f:-f}function Sa(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(p(b)||d(b)?b.valueOf():Ka(b).valueOf())-e.valueOf(),e._d.setTime(e._d.valueOf()+f),a.updateOffset(e,!1),e):Ka(b).local()}function Ta(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ua(b,c){var d,e=this._offset||0;return this.isValid()?null!=b?("string"==typeof b?b=Ra(Hd,b):Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Ta(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?jb(this,db(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Ta(this):null!=b?this:NaN}function Va(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Wa(a){return this.utcOffset(0,a)}function Xa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Ta(this),"m")),this}function Ya(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Ra(Gd,this._i)),this}function Za(a){return this.isValid()?(a=a?Ka(a).utcOffset():0,(this.utcOffset()-a)%60===0):!1}function $a(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function _a(){if(!m(this._isDSTShifted))return this._isDSTShifted;var a={};if(n(a,this),a=Ha(a),a._a){var b=a._isUTC?h(a._a):Ka(a._a);this._isDSTShifted=this.isValid()&&s(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function ab(){return this.isValid()?!this._isUTC:!1}function bb(){return this.isValid()?this._isUTC:!1}function cb(){return this.isValid()?this._isUTC&&0===this._offset:!1}function db(a,b){var c,d,e,g=a,h=null;return Pa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=je.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:r(h[Od])*c,h:r(h[Pd])*c,m:r(h[Qd])*c,s:r(h[Rd])*c,ms:r(h[Sd])*c}):(h=ke.exec(a))?(c="-"===h[1]?-1:1,g={y:eb(h[2],c),M:eb(h[3],c),w:eb(h[4],c),d:eb(h[5],c),h:eb(h[6],c),m:eb(h[7],c),s:eb(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=gb(Ka(g.from),Ka(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Oa(g),Pa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function eb(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function fb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function gb(a,b){var c;return a.isValid()&&b.isValid()?(b=Sa(b,a),a.isBefore(b)?c=fb(a,b):(c=fb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function hb(a){return 0>a?-1*Math.round(-1*a):Math.round(a)}function ib(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(v(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=db(c,d),jb(this,e,a),this}}function jb(b,c,d,e){var f=c._milliseconds,g=hb(c._days),h=hb(c._months);b.isValid()&&(e=null==e?!0:e,f&&b._d.setTime(b._d.valueOf()+f*d),g&&O(b,"Date",N(b,"Date")+g*d),h&&ga(b,N(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function kb(a,b){var c=a||Ka(),d=Sa(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse",g=b&&(w(b[f])?b[f]():b[f]);return this.format(g||this.localeData().calendar(f,this,Ka(c)))}function lb(){return new o(this)}function mb(a,b){var c=p(a)?a:Ka(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?this.valueOf()>c.valueOf():c.valueOf()<this.clone().startOf(b).valueOf()):!1}function nb(a,b){var c=p(a)?a:Ka(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?this.valueOf()<c.valueOf():this.clone().endOf(b).valueOf()<c.valueOf()):!1}function ob(a,b,c,d){return d=d||"()",("("===d[0]?this.isAfter(a,c):!this.isBefore(a,c))&&(")"===d[1]?this.isBefore(b,c):!this.isAfter(b,c))}function pb(a,b){var c,d=p(a)?a:Ka(a);return this.isValid()&&d.isValid()?(b=K(b||"millisecond"),"millisecond"===b?this.valueOf()===d.valueOf():(c=d.valueOf(),this.clone().startOf(b).valueOf()<=c&&c<=this.clone().endOf(b).valueOf())):!1}function qb(a,b){return this.isSame(a,b)||this.isAfter(a,b)}function rb(a,b){return this.isSame(a,b)||this.isBefore(a,b)}function sb(a,b,c){var d,e,f,g;return this.isValid()?(d=Sa(a,this),d.isValid()?(e=6e4*(d.utcOffset()-this.utcOffset()),b=K(b),"year"===b||"month"===b||"quarter"===b?(g=tb(this,d),"quarter"===b?g/=3:"year"===b&&(g/=12)):(f=this-d,g="second"===b?f/1e3:"minute"===b?f/6e4:"hour"===b?f/36e5:"day"===b?(f-e)/864e5:"week"===b?(f-e)/6048e5:f),c?g:q(g)):NaN):NaN}function tb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)||0}function ub(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function vb(){var a=this.clone().utc();return 0<a.year()&&a.year()<=9999?w(Date.prototype.toISOString)?this.toDate().toISOString():U(a,"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]"):U(a,"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]")}function wb(b){b||(b=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var c=U(this,b);return this.localeData().postformat(c)}function xb(a,b){return this.isValid()&&(p(a)&&a.isValid()||Ka(a).isValid())?db({to:this,from:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function yb(a){return this.from(Ka(),a)}function zb(a,b){return this.isValid()&&(p(a)&&a.isValid()||Ka(a).isValid())?db({from:this,to:a}).locale(this.locale()).humanize(!b):this.localeData().invalidDate()}function Ab(a){return this.to(Ka(),a)}function Bb(a){var b;return void 0===a?this._locale._abbr:(b=H(a),null!=b&&(this._locale=b),this)}function Cb(){return this._locale}function Db(a){switch(a=K(a)){case"year":this.month(0);case"quarter":case"month":this.date(1);case"week":case"isoWeek":case"day":case"date":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a&&this.weekday(0),"isoWeek"===a&&this.isoWeekday(1),"quarter"===a&&this.month(3*Math.floor(this.month()/3)),this}function Eb(a){return a=K(a),void 0===a||"millisecond"===a?this:("date"===a&&(a="day"),this.startOf(a).add(1,"isoWeek"===a?"week":a).subtract(1,"ms"))}function Fb(){return this._d.valueOf()-6e4*(this._offset||0)}function Gb(){return Math.floor(this.valueOf()/1e3)}function Hb(){return this._offset?new Date(this.valueOf()):this._d}function Ib(){var a=this;return[a.year(),a.month(),a.date(),a.hour(),a.minute(),a.second(),a.millisecond()]}function Jb(){var a=this;return{years:a.year(),months:a.month(),date:a.date(),hours:a.hours(),minutes:a.minutes(),seconds:a.seconds(),milliseconds:a.milliseconds()}}function Kb(){return this.isValid()?this.toISOString():null}function Lb(){return k(this)}function Mb(){return g({},j(this))}function Nb(){return j(this).overflow}function Ob(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Pb(a,b){R(0,[a,a.length],0,b)}function Qb(a){return Ub.call(this,a,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)}function Rb(a){return Ub.call(this,a,this.isoWeek(),this.isoWeekday(),1,4)}function Sb(){return xa(this.year(),1,4)}function Tb(){var a=this.localeData()._week;return xa(this.year(),a.dow,a.doy)}function Ub(a,b,c,d,e){var f;return null==a?wa(this,d,e).year:(f=xa(a,d,e),b>f&&(b=f),Vb.call(this,a,b,c,d,e))}function Vb(a,b,c,d,e){var f=va(a,b,c,d,e),g=qa(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Wb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Xb(a){return wa(a,this._week.dow,this._week.doy).week}function Yb(){return this._week.dow}function Zb(){return this._week.doy}function $b(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function _b(a){var b=wa(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function ac(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function bc(a,b){return c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]}function cc(a){return this._weekdaysShort[a.day()]}function dc(a){return this._weekdaysMin[a.day()]}function ec(a,b,c){var d,e,f,g=a.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],d=0;7>d;++d)f=h([2e3,1]).day(d),this._minWeekdaysParse[d]=this.weekdaysMin(f,"").toLocaleLowerCase(),this._shortWeekdaysParse[d]=this.weekdaysShort(f,"").toLocaleLowerCase(),this._weekdaysParse[d]=this.weekdays(f,"").toLocaleLowerCase();return c?"dddd"===b?(e=md.call(this._weekdaysParse,g),-1!==e?e:null):"ddd"===b?(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:null):(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null):"dddd"===b?(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null))):"ddd"===b?(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._minWeekdaysParse,g),-1!==e?e:null))):(e=md.call(this._minWeekdaysParse,g),-1!==e?e:(e=md.call(this._weekdaysParse,g),-1!==e?e:(e=md.call(this._shortWeekdaysParse,g),-1!==e?e:null)))}function fc(a,b,c){var d,e,f;if(this._weekdaysParseExact)return ec.call(this,a,b,c);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;7>d;d++){if(e=h([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function gc(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=ac(a,this.localeData()),this.add(a-b,"d")):b}function hc(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function ic(a){return this.isValid()?null==a?this.day()||7:this.day(this.day()%7?a:a-7):null!=a?this:NaN}function jc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysStrictRegex:this._weekdaysRegex):this._weekdaysStrictRegex&&a?this._weekdaysStrictRegex:this._weekdaysRegex}function kc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):this._weekdaysShortStrictRegex&&a?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}function lc(a){return this._weekdaysParseExact?(f(this,"_weekdaysRegex")||mc.call(this),a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):this._weekdaysMinStrictRegex&&a?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}function mc(){function a(a,b){return b.length-a.length}var b,c,d,e,f,g=[],i=[],j=[],k=[];for(b=0;7>b;b++)c=h([2e3,1]).day(b),d=this.weekdaysMin(c,""),e=this.weekdaysShort(c,""),f=this.weekdays(c,""),g.push(d),i.push(e),j.push(f),k.push(d),k.push(e),k.push(f);for(g.sort(a),i.sort(a),j.sort(a),k.sort(a),b=0;7>b;b++)i[b]=Z(i[b]),j[b]=Z(j[b]),k[b]=Z(k[b]);this._weekdaysRegex=new RegExp("^("+k.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+j.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+g.join("|")+")","i")}function nc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function oc(){return this.hours()%12||12}function pc(){return this.hours()||24}function qc(a,b){R(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function rc(a,b){return b._meridiemParse}function sc(a){return"p"===(a+"").toLowerCase().charAt(0)}function tc(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function uc(a,b){b[Sd]=r(1e3*("0."+a))}function vc(){return this._isUTC?"UTC":""}function wc(){return this._isUTC?"Coordinated Universal Time":""}function xc(a){return Ka(1e3*a)}function yc(){return Ka.apply(null,arguments).parseZone()}function zc(a,b,c){var d=this._calendar[a];return w(d)?d.call(b,c):d}function Ac(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function Bc(){return this._invalidDate}function Cc(a){return this._ordinal.replace("%d",a)}function Dc(a){return a}function Ec(a,b,c,d){var e=this._relativeTime[c];return w(e)?e(a,b,c,d):e.replace(/%d/i,a)}function Fc(a,b){var c=this._relativeTime[a>0?"future":"past"];return w(c)?c(b):c.replace(/%s/i,b)}function Gc(a,b,c,d){var e=H(),f=h().set(d,b);return e[c](f,a)}function Hc(a,b,c){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return Gc(a,b,c,"month");var d,e=[];for(d=0;12>d;d++)e[d]=Gc(a,d,c,"month");return e}function Ic(a,b,c,d){"boolean"==typeof a?("number"==typeof b&&(c=b,b=void 0),b=b||""):(b=a,c=b,a=!1,"number"==typeof b&&(c=b,b=void 0),b=b||"");var e=H(),f=a?e._week.dow:0;if(null!=c)return Gc(b,(c+f)%7,d,"day");var g,h=[];for(g=0;7>g;g++)h[g]=Gc(b,(g+f)%7,d,"day");return h}function Jc(a,b){return Hc(a,b,"months")}function Kc(a,b){return Hc(a,b,"monthsShort")}function Lc(a,b,c){return Ic(a,b,c,"weekdays")}function Mc(a,b,c){return Ic(a,b,c,"weekdaysShort")}function Nc(a,b,c){return Ic(a,b,c,"weekdaysMin")}function Oc(){var a=this._data;return this._milliseconds=Le(this._milliseconds),this._days=Le(this._days),this._months=Le(this._months),a.milliseconds=Le(a.milliseconds),a.seconds=Le(a.seconds),a.minutes=Le(a.minutes),a.hours=Le(a.hours),a.months=Le(a.months),a.years=Le(a.years),this}function Pc(a,b,c,d){var e=db(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function Qc(a,b){return Pc(this,a,b,1)}function Rc(a,b){return Pc(this,a,b,-1)}function Sc(a){return 0>a?Math.floor(a):Math.ceil(a)}function Tc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*Sc(Vc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=q(f/1e3),i.seconds=a%60,b=q(a/60),i.minutes=b%60,c=q(b/60),i.hours=c%24,g+=q(c/24),e=q(Uc(g)),h+=e,g-=Sc(Vc(e)),d=q(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function Uc(a){return 4800*a/146097}function Vc(a){return 146097*a/4800}function Wc(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+Uc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(Vc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function Xc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*r(this._months/12)}function Yc(a){return function(){return this.as(a)}}function Zc(a){
+return a=K(a),this[a+"s"]()}function $c(a){return function(){return this._data[a]}}function _c(){return q(this.days()/7)}function ad(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function bd(a,b,c){var d=db(a).abs(),e=_e(d.as("s")),f=_e(d.as("m")),g=_e(d.as("h")),h=_e(d.as("d")),i=_e(d.as("M")),j=_e(d.as("y")),k=e<af.s&&["s",e]||1>=f&&["m"]||f<af.m&&["mm",f]||1>=g&&["h"]||g<af.h&&["hh",g]||1>=h&&["d"]||h<af.d&&["dd",h]||1>=i&&["M"]||i<af.M&&["MM",i]||1>=j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,ad.apply(null,k)}function cd(a,b){return void 0===af[a]?!1:void 0===b?af[a]:(af[a]=b,!0)}function dd(a){var b=this.localeData(),c=bd(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function ed(){var a,b,c,d=bf(this._milliseconds)/1e3,e=bf(this._days),f=bf(this._months);a=q(d/60),b=q(a/60),d%=60,a%=60,c=q(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var fd,gd;gd=Array.prototype.some?Array.prototype.some:function(a){for(var b=Object(this),c=b.length>>>0,d=0;c>d;d++)if(d in b&&a.call(this,b[d],d,b))return!0;return!1};var hd=a.momentProperties=[],id=!1,jd={};a.suppressDeprecationWarnings=!1,a.deprecationHandler=null;var kd;kd=Object.keys?Object.keys:function(a){var b,c=[];for(b in a)f(a,b)&&c.push(b);return c};var ld,md,nd={},od={},pd=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,qd=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,rd={},sd={},td=/\d/,ud=/\d\d/,vd=/\d{3}/,wd=/\d{4}/,xd=/[+-]?\d{6}/,yd=/\d\d?/,zd=/\d\d\d\d?/,Ad=/\d\d\d\d\d\d?/,Bd=/\d{1,3}/,Cd=/\d{1,4}/,Dd=/[+-]?\d{1,6}/,Ed=/\d+/,Fd=/[+-]?\d+/,Gd=/Z|[+-]\d\d:?\d\d/gi,Hd=/Z|[+-]\d\d(?::?\d\d)?/gi,Id=/[+-]?\d+(\.\d{1,3})?/,Jd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Kd={},Ld={},Md=0,Nd=1,Od=2,Pd=3,Qd=4,Rd=5,Sd=6,Td=7,Ud=8;md=Array.prototype.indexOf?Array.prototype.indexOf:function(a){var b;for(b=0;b<this.length;++b)if(this[b]===a)return b;return-1},R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),R("MMMM",0,0,function(a){return this.localeData().months(this,a)}),J("month","M"),W("M",yd),W("MM",yd,ud),W("MMM",function(a,b){return b.monthsShortRegex(a)}),W("MMMM",function(a,b){return b.monthsRegex(a)}),$(["M","MM"],function(a,b){b[Nd]=r(a)-1}),$(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[Nd]=e:j(c).invalidMonth=a});var Vd=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Wd="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Xd="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Yd=Jd,Zd=Jd,$d=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,_d=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,ae=/Z|[+-]\d\d(?::?\d\d)?/,be=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],ce=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],de=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=u("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),R("Y",0,0,function(){var a=this.year();return 9999>=a?""+a:"+"+a}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),J("year","y"),W("Y",Fd),W("YY",yd,ud),W("YYYY",Cd,wd),W("YYYYY",Dd,xd),W("YYYYYY",Dd,xd),$(["YYYYY","YYYYYY"],Md),$("YYYY",function(b,c){c[Md]=2===b.length?a.parseTwoDigitYear(b):r(b)}),$("YY",function(b,c){c[Md]=a.parseTwoDigitYear(b)}),$("Y",function(a,b){b[Md]=parseInt(a,10)}),a.parseTwoDigitYear=function(a){return r(a)+(r(a)>68?1900:2e3)};var ee=M("FullYear",!0);a.ISO_8601=function(){};var fe=u("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Ka.apply(null,arguments);return this.isValid()&&a.isValid()?this>a?this:a:l()}),ge=u("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Ka.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:l()}),he=function(){return Date.now?Date.now():+new Date};Qa("Z",":"),Qa("ZZ",""),W("Z",Hd),W("ZZ",Hd),$(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Ra(Hd,a)});var ie=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var je=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,ke=/^(-)?P(?:(-?[0-9,.]*)Y)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)W)?(?:(-?[0-9,.]*)D)?(?:T(?:(-?[0-9,.]*)H)?(?:(-?[0-9,.]*)M)?(?:(-?[0-9,.]*)S)?)?$/;db.fn=Oa.prototype;var le=ib(1,"add"),me=ib(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ne=u("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Pb("gggg","weekYear"),Pb("ggggg","weekYear"),Pb("GGGG","isoWeekYear"),Pb("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),W("G",Fd),W("g",Fd),W("GG",yd,ud),W("gg",yd,ud),W("GGGG",Cd,wd),W("gggg",Cd,wd),W("GGGGG",Dd,xd),W("ggggg",Dd,xd),_(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=r(a)}),_(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),R("Q",0,"Qo","quarter"),J("quarter","Q"),W("Q",td),$("Q",function(a,b){b[Nd]=3*(r(a)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),W("w",yd),W("ww",yd,ud),W("W",yd),W("WW",yd,ud),_(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=r(a)});var oe={dow:0,doy:6};R("D",["DD",2],"Do","date"),J("date","D"),W("D",yd),W("DD",yd,ud),W("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),$(["D","DD"],Od),$("Do",function(a,b){b[Od]=r(a.match(yd)[0],10)});var pe=M("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),R("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),R("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),W("d",yd),W("e",yd),W("E",yd),W("dd",function(a,b){return b.weekdaysMinRegex(a)}),W("ddd",function(a,b){return b.weekdaysShortRegex(a)}),W("dddd",function(a,b){return b.weekdaysRegex(a)}),_(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:j(c).invalidWeekday=a}),_(["d","e","E"],function(a,b,c,d){b[d]=r(a)});var qe="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),re="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),se="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),te=Jd,ue=Jd,ve=Jd;R("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),W("DDD",Bd),W("DDDD",vd),$(["DDD","DDDD"],function(a,b,c){c._dayOfYear=r(a)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,oc),R("k",["kk",2],0,pc),R("hmm",0,0,function(){return""+oc.apply(this)+Q(this.minutes(),2)}),R("hmmss",0,0,function(){return""+oc.apply(this)+Q(this.minutes(),2)+Q(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+Q(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+Q(this.minutes(),2)+Q(this.seconds(),2)}),qc("a",!0),qc("A",!1),J("hour","h"),W("a",rc),W("A",rc),W("H",yd),W("h",yd),W("HH",yd,ud),W("hh",yd,ud),W("hmm",zd),W("hmmss",Ad),W("Hmm",zd),W("Hmmss",Ad),$(["H","HH"],Pd),$(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),$(["h","hh"],function(a,b,c){b[Pd]=r(a),j(c).bigHour=!0}),$("hmm",function(a,b,c){var d=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d)),j(c).bigHour=!0}),$("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d,2)),b[Rd]=r(a.substr(e)),j(c).bigHour=!0}),$("Hmm",function(a,b,c){var d=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d))}),$("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Pd]=r(a.substr(0,d)),b[Qd]=r(a.substr(d,2)),b[Rd]=r(a.substr(e))});var we=/[ap]\.?m?\.?/i,xe=M("Hours",!0);R("m",["mm",2],0,"minute"),J("minute","m"),W("m",yd),W("mm",yd,ud),$(["m","mm"],Qd);var ye=M("Minutes",!1);R("s",["ss",2],0,"second"),J("second","s"),W("s",yd),W("ss",yd,ud),$(["s","ss"],Rd);var ze=M("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),W("S",Bd,td),W("SS",Bd,ud),W("SSS",Bd,vd);var Ae;for(Ae="SSSS";Ae.length<=9;Ae+="S")W(Ae,Ed);for(Ae="S";Ae.length<=9;Ae+="S")$(Ae,uc);var Be=M("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var Ce=o.prototype;Ce.add=le,Ce.calendar=kb,Ce.clone=lb,Ce.diff=sb,Ce.endOf=Eb,Ce.format=wb,Ce.from=xb,Ce.fromNow=yb,Ce.to=zb,Ce.toNow=Ab,Ce.get=P,Ce.invalidAt=Nb,Ce.isAfter=mb,Ce.isBefore=nb,Ce.isBetween=ob,Ce.isSame=pb,Ce.isSameOrAfter=qb,Ce.isSameOrBefore=rb,Ce.isValid=Lb,Ce.lang=ne,Ce.locale=Bb,Ce.localeData=Cb,Ce.max=ge,Ce.min=fe,Ce.parsingFlags=Mb,Ce.set=P,Ce.startOf=Db,Ce.subtract=me,Ce.toArray=Ib,Ce.toObject=Jb,Ce.toDate=Hb,Ce.toISOString=vb,Ce.toJSON=Kb,Ce.toString=ub,Ce.unix=Gb,Ce.valueOf=Fb,Ce.creationData=Ob,Ce.year=ee,Ce.isLeapYear=ta,Ce.weekYear=Qb,Ce.isoWeekYear=Rb,Ce.quarter=Ce.quarters=Wb,Ce.month=ha,Ce.daysInMonth=ia,Ce.week=Ce.weeks=$b,Ce.isoWeek=Ce.isoWeeks=_b,Ce.weeksInYear=Tb,Ce.isoWeeksInYear=Sb,Ce.date=pe,Ce.day=Ce.days=gc,Ce.weekday=hc,Ce.isoWeekday=ic,Ce.dayOfYear=nc,Ce.hour=Ce.hours=xe,Ce.minute=Ce.minutes=ye,Ce.second=Ce.seconds=ze,Ce.millisecond=Ce.milliseconds=Be,Ce.utcOffset=Ua,Ce.utc=Wa,Ce.local=Xa,Ce.parseZone=Ya,Ce.hasAlignedHourOffset=Za,Ce.isDST=$a,Ce.isDSTShifted=_a,Ce.isLocal=ab,Ce.isUtcOffset=bb,Ce.isUtc=cb,Ce.isUTC=cb,Ce.zoneAbbr=vc,Ce.zoneName=wc,Ce.dates=u("dates accessor is deprecated. Use date instead.",pe),Ce.months=u("months accessor is deprecated. Use month instead",ha),Ce.years=u("years accessor is deprecated. Use year instead",ee),Ce.zone=u("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Va);var De=Ce,Ee={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},Fe={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},Ge="Invalid date",He="%d",Ie=/\d{1,2}/,Je={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},Ke=A.prototype;Ke._calendar=Ee,Ke.calendar=zc,Ke._longDateFormat=Fe,Ke.longDateFormat=Ac,Ke._invalidDate=Ge,Ke.invalidDate=Bc,Ke._ordinal=He,Ke.ordinal=Cc,Ke._ordinalParse=Ie,Ke.preparse=Dc,Ke.postformat=Dc,Ke._relativeTime=Je,Ke.relativeTime=Ec,Ke.pastFuture=Fc,Ke.set=y,Ke.months=ca,Ke._months=Wd,Ke.monthsShort=da,Ke._monthsShort=Xd,Ke.monthsParse=fa,Ke._monthsRegex=Zd,Ke.monthsRegex=ka,Ke._monthsShortRegex=Yd,Ke.monthsShortRegex=ja,Ke.week=Xb,Ke._week=oe,Ke.firstDayOfYear=Zb,Ke.firstDayOfWeek=Yb,Ke.weekdays=bc,Ke._weekdays=qe,Ke.weekdaysMin=dc,Ke._weekdaysMin=se,Ke.weekdaysShort=cc,Ke._weekdaysShort=re,Ke.weekdaysParse=fc,Ke._weekdaysRegex=te,Ke.weekdaysRegex=jc,Ke._weekdaysShortRegex=ue,Ke.weekdaysShortRegex=kc,Ke._weekdaysMinRegex=ve,Ke.weekdaysMinRegex=lc,Ke.isPM=sc,Ke._meridiemParse=we,Ke.meridiem=tc,E("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===r(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=u("moment.lang is deprecated. Use moment.locale instead.",E),a.langData=u("moment.langData is deprecated. Use moment.localeData instead.",H);var Le=Math.abs,Me=Yc("ms"),Ne=Yc("s"),Oe=Yc("m"),Pe=Yc("h"),Qe=Yc("d"),Re=Yc("w"),Se=Yc("M"),Te=Yc("y"),Ue=$c("milliseconds"),Ve=$c("seconds"),We=$c("minutes"),Xe=$c("hours"),Ye=$c("days"),Ze=$c("months"),$e=$c("years"),_e=Math.round,af={s:45,m:45,h:22,d:26,M:11},bf=Math.abs,cf=Oa.prototype;cf.abs=Oc,cf.add=Qc,cf.subtract=Rc,cf.as=Wc,cf.asMilliseconds=Me,cf.asSeconds=Ne,cf.asMinutes=Oe,cf.asHours=Pe,cf.asDays=Qe,cf.asWeeks=Re,cf.asMonths=Se,cf.asYears=Te,cf.valueOf=Xc,cf._bubble=Tc,cf.get=Zc,cf.milliseconds=Ue,cf.seconds=Ve,cf.minutes=We,cf.hours=Xe,cf.days=Ye,cf.weeks=_c,cf.months=Ze,cf.years=$e,cf.humanize=dd,cf.toISOString=ed,cf.toString=ed,cf.toJSON=ed,cf.locale=Bb,cf.localeData=Cb,cf.toIsoString=u("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ed),cf.lang=ne,R("X",0,0,"unix"),R("x",0,0,"valueOf"),W("x",Fd),W("X",Id),$("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),$("x",function(a,b,c){c._d=new Date(r(a))}),a.version="2.13.0",b(Ka),a.fn=De,a.min=Ma,a.max=Na,a.now=he,a.utc=h,a.unix=xc,a.months=Jc,a.isDate=d,a.locale=E,a.invalid=l,a.duration=db,a.isMoment=p,a.weekdays=Lc,a.parseZone=yc,a.localeData=H,a.isDuration=Pa,a.monthsShort=Kc,a.weekdaysMin=Nc,a.defineLocale=F,a.updateLocale=G,a.locales=I,a.weekdaysShort=Mc,a.normalizeUnits=K,a.relativeTimeThreshold=cd,a.prototype=De;var df=a;return df});
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/script.js b/tools/infra-dashboard/js/script.js
new file mode 100644 (file)
index 0000000..07c832a
--- /dev/null
@@ -0,0 +1,27 @@
+jQuery(document).ready(function () {
+       // If svg is not supported
+       if (!Modernizr.svg) {
+         jQuery('img[src$=".svg"]').each(function() {
+             jQuery(this).attr('src', jQuery(this).attr('src').replace('.svg', '.png'));
+         });
+       }
+       // If media queries are not supported
+       if(!Modernizr.mq('only all')) {
+               jQuery('head').append('<link id="no-mq" rel="stylesheet" type="text/css">');
+               jQuery("link#no-mq").attr("href", "/joomla/media/templates/highsoft_bootstrap/css/ie.css");
+       }
+       // Sidebar click animation
+       jQuery('.nav-sidebar > li').click(function () {
+               if (!jQuery(this).hasClass("active")) {
+                       jQuery('.nav-sidebar > li.active > div.active').removeClass('active');
+                       jQuery('.nav-sidebar > li.active > ul').slideUp("slow");
+                       jQuery('.nav-sidebar > li.active').removeClass('active');
+                       jQuery(this).addClass("active");
+                       jQuery('.nav-sidebar > li.active > ul').slideDown("slow");
+                       jQuery('.nav-sidebar > li.active > div').addClass('active');
+               }
+       });
+       jQuery("#sidebar-toggle").click(function (e) {
+               jQuery("#wrap").toggleClass("toggled");
+       });     
+});    
\ No newline at end of file
diff --git a/tools/infra-dashboard/js/test_graph.js b/tools/infra-dashboard/js/test_graph.js
new file mode 100644 (file)
index 0000000..1d1d5e4
--- /dev/null
@@ -0,0 +1,108 @@
+$(function () {
+
+    // Get the CSV and create the chart
+    $.getJSON('https://www.highcharts.com/samples/data/jsonp.php?filename=analytics.csv&callback=?', function (csv) {
+
+        $('#container').highcharts({
+
+            data: {
+                csv: csv
+            },
+
+            title: {
+                text: 'Daily visits at www.highcharts.com'
+            },
+
+            subtitle: {
+                text: 'Source: Google Analytics'
+            },
+
+            xAxis: {
+                tickInterval: 7 * 24 * 3600 * 1000, // one week
+                tickWidth: 0,
+                gridLineWidth: 1,
+                labels: {
+                    align: 'left',
+                    x: 3,
+                    y: -3
+                }
+            },
+
+            yAxis: [{ // left y axis
+                title: {
+                    text: null
+                },
+                labels: {
+                    align: 'left',
+                    x: 3,
+                    y: 16,
+                    format: '{value:.,0f}'
+                },
+                showFirstLabel: false
+            }, { // right y axis
+                linkedTo: 0,
+                gridLineWidth: 0,
+                opposite: true,
+                title: {
+                    text: null
+                },
+                labels: {
+                    align: 'right',
+                    x: -3,
+                    y: 16,
+                    format: '{value:.,0f}'
+                },
+                showFirstLabel: false
+            }],
+
+            legend: {
+                align: 'left',
+                verticalAlign: 'top',
+                y: 20,
+                floating: true,
+                borderWidth: 0
+            },
+
+            tooltip: {
+                shared: true,
+                crosshairs: true
+            },
+
+            plotOptions: {
+                series: {
+                    cursor: 'pointer',
+                    point: {
+                        events: {
+                            click: function (e) {
+                                hs.htmlExpand(null, {
+                                    pageOrigin: {
+                                        x: e.pageX || e.clientX,
+                                        y: e.pageY || e.clientY
+                                    },
+                                    headingText: this.series.name,
+                                    maincontentText: Highcharts.dateFormat('%A, %b %e, %Y', this.x) + ':<br/> ' +
+                                        this.y + ' visits',
+                                    width: 200
+                                });
+                            }
+                        }
+                    },
+                    marker: {
+                        lineWidth: 1
+                    }
+                }
+            },
+
+            series: [{
+                name: 'All visits',
+                lineWidth: 4,
+                marker: {
+                    radius: 4
+                }
+            }, {
+                name: 'New visitors'
+            }]
+        });
+    });
+
+});
diff --git a/tools/infra-dashboard/media/ajax-loader.gif b/tools/infra-dashboard/media/ajax-loader.gif
new file mode 100644 (file)
index 0000000..3c2f7c0
Binary files /dev/null and b/tools/infra-dashboard/media/ajax-loader.gif differ
diff --git a/tools/infra-dashboard/media/collaborative-projects-logo.png b/tools/infra-dashboard/media/collaborative-projects-logo.png
new file mode 100644 (file)
index 0000000..195ad08
Binary files /dev/null and b/tools/infra-dashboard/media/collaborative-projects-logo.png differ
diff --git a/tools/infra-dashboard/media/diagonal-white.png b/tools/infra-dashboard/media/diagonal-white.png
new file mode 100644 (file)
index 0000000..9772997
Binary files /dev/null and b/tools/infra-dashboard/media/diagonal-white.png differ
diff --git a/tools/infra-dashboard/media/favicon_400x400.jpg b/tools/infra-dashboard/media/favicon_400x400.jpg
new file mode 100644 (file)
index 0000000..a45d1bf
Binary files /dev/null and b/tools/infra-dashboard/media/favicon_400x400.jpg differ
diff --git a/tools/infra-dashboard/media/loader.white.gif b/tools/infra-dashboard/media/loader.white.gif
new file mode 100644 (file)
index 0000000..f2a1bc0
Binary files /dev/null and b/tools/infra-dashboard/media/loader.white.gif differ
diff --git a/tools/infra-dashboard/media/opnfvlogo.JPG b/tools/infra-dashboard/media/opnfvlogo.JPG
new file mode 100644 (file)
index 0000000..ef3fe35
Binary files /dev/null and b/tools/infra-dashboard/media/opnfvlogo.JPG differ
diff --git a/tools/infra-dashboard/pages/ci_pods.php b/tools/infra-dashboard/pages/ci_pods.php
new file mode 100644 (file)
index 0000000..5f51530
--- /dev/null
@@ -0,0 +1,91 @@
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('#example').DataTable( {
+            "order": [[ 3, "desc" ],[ 2, "desc" ]]
+        } );
+    } );
+</script>
+
+<?php
+    include '../utils/jenkinsAdapter.php';
+    include '../utils/database.php';
+
+    connectDB();
+    $result = mysql_query("SELECT * FROM resource, pod WHERE resource.resource_id=pod.resource_id;");
+    closeDB();
+
+    echo '<table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%">';
+    echo "<thead>";
+    echo "<tr>";
+    echo "<th>Name</th>";
+    echo "<th>Slave Name</th>";
+    echo "<th>Status</th>";
+    echo "<th>Installer</th>";
+    echo "<th>Scenario</th>";
+    echo "<th>Branch</th>";
+    echo "<th>Job</th>";
+    echo "</tr>";
+    echo "</thead>";
+    echo "<tbody>";
+
+    while ($row = mysql_fetch_array($result)) {
+        $slave = $row{'slavename'};
+        if (! isCiPod($slave)) continue;
+
+        $slave_url = getSlaveUrl($slave);
+        $status = getSlaveStatus($slave);
+
+        $job_name = "";
+        $job_installer = "";
+        $job_branch = "";
+        $job_url = "";
+        $job_scenario = "";
+        $job_type = "";
+
+        if ($status == 'online'){
+            $job_params = getJJob($slave);
+            $job_name = $job_params['name'];
+            $job_installer = $job_params['installer'];
+            $job_branch = $job_params['branch'];
+            $job_url = $job_params['url']."lastBuild/consoleFull";
+            $job_scenario = $job_params['scenario'];
+            $job_type = $job_params['type'];
+        }
+
+        echo "<tr>";
+        echo "<th><a target='_blank' href='".$row{'link'}."'>".$row{'name'}."</a></th>";
+        echo "<th><a target='_blank' href='".$slave_url."'>".$slave."</a></th>";
+        if ($status == "online") $color = "#BEFAAA";
+        else $color = "#FAAAAB";
+        echo "<th style='background-color: ".$color.";'>".$status."</th>";
+        if ($job_type == "0") $class = "blink_me";
+        else $class="";
+        echo "<th class='".$class."'>".$job_installer."</th>";
+        echo "<th class='".$class."'>".$job_scenario."</th>";
+        echo "<th class='".$class."'>".$job_branch."</th>";
+  $green = '#33cc00';
+  $grey = '#646F73';
+  $red = '#FF5555';
+  $orange = '#EDD62B';
+        if ($job_type == "0") { // job running
+            echo "<th><a class='blink_me' style='font-size:12px;color:".$grey.";' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+        }
+        else if ($job_type == "1") {// last job successful
+            echo "<th><a style='font-size:12px;color:".$green.";' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+        }
+        else if ($job_type == "2") {// last job failed
+            echo "<th><a style='font-size:12px;color:".$red."' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+        }
+        else if ($job_type == "3") {// last job is unstable
+            echo "<th><a style='font-size:12px;color:".$orange."' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+  }
+        else {
+            echo "<th><a style='font-size:12px;' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+        }
+        echo "</tr>";
+    }
+    echo '</tbody>';
+    echo '</table>';
+
+
+?>
diff --git a/tools/infra-dashboard/pages/dev_pods.php b/tools/infra-dashboard/pages/dev_pods.php
new file mode 100644 (file)
index 0000000..144504b
--- /dev/null
@@ -0,0 +1,342 @@
+<?php
+    // trick for the calendar object, if we use always the same id name, it doesn't work properly
+    $random = time();
+    $calendar_id = "calendar_".$random;
+?>
+
+
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('#example').DataTable( {
+            "order": [[ 2, "desc" ],[ 5, "desc"]]
+        } );
+        var date = new Date();
+        var d = date.getDate();
+        var m = date.getMonth();
+        var y = date.getFullYear();
+    } );
+
+    $(function() {
+        var dialog, form,
+
+        // From http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#e-mail-state-%28type=email%29
+        emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,
+        email = $( "#email" ),
+        password = $( "#password" ),
+        allFields = $( [] ).add( email ).add( password ),
+        tips = $( ".validateTips" );
+
+        function updateTips( t ) {
+          tips
+            .text( t )
+            .addClass( "ui-state-highlight" );
+          setTimeout(function() {
+            tips.removeClass( "ui-state-highlight", 1500 );
+          }, 500 );
+        }
+
+        function checkLength( o, n, min, max ) {
+          if ( o.val().length > max || o.val().length < min ) {
+            o.addClass( "ui-state-error" );
+            updateTips( "Length of " + n + " must be between " +
+              min + " and " + max + "." );
+            return false;
+          } else {
+            return true;
+          }
+        }
+
+        function checkRegexp( o, regexp, n ) {
+          if ( !( regexp.test( o.val() ) ) ) {
+            o.addClass( "ui-state-error" );
+            updateTips( n );
+            return false;
+          } else {
+            return true;
+          }
+        }
+
+        function bookResource() {
+            var user_id = $('#hd_user_id').val();
+            if (user_id == "") {
+                alert ("Only registered users can book.");
+                return;
+            }
+            var resource_id = $('#resource_id').val();
+            var resource_name = $('#resource_name').val();
+            var purpose = $('#purpose').val();
+            var starttime = $('#starttime').val();
+            var endtime = $('#endtime').val();
+            $.ajax({
+                type: 'POST',
+                url: "utils/book.php",
+                data: {action: 'book', resource_id: resource_id, resource_name: resource_name, user_id: user_id, start: starttime, end: endtime, purpose: purpose, start: starttime, end: endtime},
+                success: function(data){
+                    if (data == "1") alert("Booking not possible (your account is not associated with a role). Please contact the administrator.");
+                    else if (data == "2") alert("You are not allowed to book this resource.");
+                    else {
+                        alert(data)
+                        //location.reload();
+                        var href = document.location.protocol +"//"+ document.location.hostname + document.location.pathname
+                        location.href = href + "?page=devpods"
+                    }
+                },
+                errpr: function(data){
+                    alert("error")
+                }
+
+            });
+
+            dialog.dialog( "close" );
+            return true;
+        }
+
+        dialog = $( "#dialog-form" ).dialog({
+            autoOpen: false,
+            height: 800,
+            width: 900,
+            modal: true,
+            resizable:false,
+            buttons: {
+                "Book": bookResource,
+                Cancel: function() {
+                    dialog.dialog( "close" );
+                    $('#starttime').attr('value', "");
+                    $('#endtime').attr('value', "");
+                    $('#purpose').attr('value', "");
+                    $(".ui-dialog-buttonpane button:contains('Book')").attr("disabled", true)
+                                              .addClass("ui-state-disabled");
+                }
+            },
+            close: function() {
+                form[ 0 ].reset();
+                allFields.removeClass( "ui-state-error" );
+            }
+        });
+
+        form = dialog.find( "form" ).on( "submit", function( event ) {
+            event.preventDefault();
+            bookResource();
+        });
+
+        $(".ui-dialog-buttonpane button:contains('Book')").attr("disabled", true)
+                                              .addClass("ui-state-disabled");
+        dialog_event = $( "#dialog_event" ).dialog({
+            autoOpen: false,
+            height: 400,
+            width: 420,
+            modal: true,
+            resizable:false,
+            buttons: {
+                Close: function() {
+                    dialog_event.dialog( "close" );
+                },
+                "Release": function() {
+                    alert("Not working yet.");
+                }
+            },
+        });
+
+
+        $( ".btn-book" ).button().on( "click", function() {
+            var resource_id = $(this).attr('id');
+            var resource_name = $(this).attr('value');
+            $('#resource_id').attr('value', resource_id);
+            $('#resource_name').attr('value', resource_name);
+            var title = "Book resource: ".concat(resource_name);
+            var calendar_id = '<?=$calendar_id?>';
+            $('#<?=$calendar_id?>').fullCalendar( 'destroy' );
+            $.ajax({
+                type: 'POST',
+                url: "utils/book.php",
+                data: {action: 'getBookedDates', resource_id: resource_id},
+
+                success: function(data){
+                    //if (data != "") {
+                        var calendarOptions = {
+                            height: 660,
+                            header: {
+                                left: 'prev,next today',
+                                center: 'title',
+                                right: 'agendaWeek,month'
+                            },
+                            defaultView: 'agendaWeek',
+                            slotDuration: '00:60:00',
+                            slotLabelFormat:"HH:mm",
+                            firstDay: 1,
+                            nowIndicator: true,
+                            allDaySlot: false,
+                            selectOverlap: false,
+                            selectable: true,
+                            selectHelper: true,
+                            editable: false,
+                            timezone: 'UTC',
+                            eventStartEditable: false,
+                            eventDurationEditable: false,
+                            events: jQuery.parseJSON( data ),
+                            select: function(start, end) {
+                                var view = $('#<?=$calendar_id?>').fullCalendar('getView');
+                                if (view.name == "month") return
+                                var title = prompt('Purpose of this booking:');
+                                var eventData;
+                                var provisional_id = "537818F62BC63518ECE15338FB86C8BE";
+                                if (title) {
+                                    $('#<?=$calendar_id?>').fullCalendar('removeEvents',provisional_id);
+                                    eventData = {
+                                        id: provisional_id,
+                                        title: title,
+                                        start: start,
+                                        end: end
+                                    };
+                                    start=moment(start).format('YYYY-MM-DD HH:mm:ss');
+                                    end=moment(end).format('YYYY-MM-DD HH:mm:ss');
+                                    $('#<?=$calendar_id?>').fullCalendar('renderEvent', eventData, true); // stick? = true
+                                    $('#starttime').attr('value', start);
+                                    $('#endtime').attr('value', end);
+                                    $('#purpose').attr('value', title);
+                                    $(".ui-dialog-buttonpane button:contains('Book')").attr("disabled", false)
+                                                              .removeClass("ui-state-disabled");
+                                }
+                                $('#<?=$calendar_id?>').fullCalendar('unselect');
+
+                            },
+                            eventLimit: true, // allow "more" link when too many events
+                            timeFormat: 'H(:mm)', // uppercase H for 24-hour clock
+                            eventClick: function(event) {
+                                $('#dg-start').text(event.start);
+                                $('#dg-end').text(event.end);
+                                $('#dg-booker-email').text(event.booker_email);
+                                $('#dg-purpose').text(event.title);
+                                dialog_event.dialog( "open" );
+                            }
+                        }
+                        $('#<?=$calendar_id?>').fullCalendar(calendarOptions);
+                        $('#<?=$calendar_id?>').fullCalendar('render');
+                    //}
+                    //else {
+                        // if first time (it has never been booked)
+                    //    $('#<?=$calendar_id?>').fullCalendar('removeEventSource');
+                    //}
+                },
+                error: function(data){
+                    alert("error getting booked dates.");
+                }
+
+            });
+            dialog.dialog( 'option', 'title', title);
+            dialog.dialog( "open" );
+            ///$('#<?=$calendar_id?>').fullCalendar( 'rerenderEvents' )
+        });
+    });
+</script>
+
+<style>
+    .fc-divider {
+        display:none !important;
+    }
+</style>
+
+<?php
+
+    include '../utils/jenkinsAdapter.php';
+    include '../utils/database.php';
+
+    connectDB();
+    //$q = "select r.ID,r.NAME,r.LINK,r.DESCR,r.ACTIVE,b.BOOKEDBY,b.BOOKEDFROM,b.BOOKEDUNTIL,b.PURPOSE from resource r left join booking b on r.ID = b.RES_Id and now() between b.BOOKEDFROM and b.BOOKEDUNTIL  where r.TYPE=2;";
+    $q = "SELECT r.resource_id,r.name as resname,r.slavename, r.link,u.user_id,u.name as username,u.email,b.starttime,b.endtime,b.purpose from resource r LEFT JOIN pod p LEFT JOIN booking b INNER JOIN user u ON u.user_id = b.user_id ON b.resource_id = p.resource_id AND now() > b.starttime AND now() <= b.endtime ON r.resource_id = p.resource_id;";
+    $result = mysql_query($q);
+
+    //SELECT r.name as resname,r.slavename, r.link, r.resource_id,u.user_id,u.name as username,u.email,b.starttime,b.endtime,b.purpose from resource r LEFT JOIN pod p LEFT JOIN booking b INNER JOIN user u ON u.user_id = b.user_id ON b.resource_id = p.resource_id AND now() > b.starttime AND now() <= b.endtime ON r.resource_id = p.resource_id WHERE type_id=2;
+    closeDB();
+
+    echo '<table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%">';
+    echo "<thead>";
+    echo "<tr>";
+    echo "<th>Name</th>";
+    echo "<th>Slave Name</th>";
+    echo "<th>Booked by</th>";
+    echo "<th>Booked until</th>";
+    echo "<th>Purpose</th>";
+    echo "<th>Status</th>";
+    echo "<th></th>";
+    echo "</tr>";
+    echo "</thead>";
+    echo "<tbody>";
+
+    while ($row = mysql_fetch_array($result)) {
+        $slave = $row{'slavename'};
+        if (! isDevPod($slave)) continue;
+
+        $slave_url = getSlaveUrl($slave);
+        $status = getSlaveStatus($slave);
+        echo "<tr>";
+        echo "<th><a target='_blank' href='".$row{'link'}."'>".$row{'resname'}."</a></th>";
+        echo "<th><a target='_blank' href='".$slave_url."'>".$slave."</a></th>";
+        if ($row{'username'} != "")  $booker = $row{'username'};
+        else $booker="-";
+        echo "<th>".$booker."</th>";
+
+        if ($row{'endtime'} != "")  {
+            $until = strtotime($row{'endtime'});
+            echo "<th>".date('d/M/Y', $until)."</th>";
+            //$until = $row{'endtime'};
+        } else {
+            $until="-";
+            echo "<th>".$until."</th>";
+        }
+        if ($row{'purpose'} != "")  $purpose = $row{'purpose'};
+        else $purpose="-";
+        echo "<th>".$purpose."</th>";
+        $active = 'true';
+        if ($status == "online") $color = "#BEFAAA";
+        else $color = "#FAAAAB";
+        echo "<th style='background-color: ".$color.";'>".$status."</th>";
+
+        echo "<th><button id='".$row{'resource_id'}."' value='".$row{'slavename'}."' class='btn-book' type='button'>Book</button></th>";
+        echo "</tr>";
+    }
+    echo '</tbody>';
+    echo '</table>';
+?>
+
+
+<div id="dialog-form" title="Book resource">
+    <form>
+        <div id='<?=$calendar_id?>' style="margin-top:10px"></div>
+        <input type="submit" tabindex="-1" style="position:absolute; top:-100px"/>
+    </form>
+</div>
+
+
+<div id="dialog_event" title="event">
+    <table>
+        <tr>
+            <td style="width:100px">Booked by:</td>
+            <td><p id="dg-booker-email"></p></td>
+        </tr>
+        <tr>
+            <td>Start:</td>
+            <td><p id="dg-start"></p></td>
+        </tr>
+        <tr>
+            <td>End:</td>
+            <td><p id="dg-end"></p></td>
+        </tr>
+        <tr>
+            <td>Purpose:</td>
+            <td><p id="dg-purpose"></p></td>
+        </tr>
+    <table>
+</div>
+
+
+
+<input type="hidden" id="resource_id" name="resource_id" value="10"/>
+<input type="hidden" id="resource_name" name="resource_name"/>
+<input type="hidden" id="starttime" value=""/>
+<input type="hidden" id="endtime" value=""/>
+<input type="hidden" id="purpose" value=""/>
+<input type="hidden" id="event_id" value=""/>
+
+
+
diff --git a/tools/infra-dashboard/pages/slaves.php b/tools/infra-dashboard/pages/slaves.php
new file mode 100644 (file)
index 0000000..4eb2add
--- /dev/null
@@ -0,0 +1,60 @@
+<script type="text/javascript">
+    $(document).ready(function() {
+        $('#example').DataTable( {
+            "order": [[ 2, "desc" ], [ 1, "desc" ]]
+        } );
+    } );
+</script>
+
+<?php
+    include '../utils/jenkinsAdapter.php';
+
+    echo '<table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%">';
+    echo "<thead>";
+    echo "<tr>";
+    echo "<th>Slave name</th>";
+    echo "<th>Status</th>";
+    echo "<th>Current build</th>";
+    echo "</tr>";
+    echo "</thead>";
+    echo "<tbody>";
+
+    foreach ($SLAVES->xpath('computer') as &$value) {
+
+        $slave = $value->displayName;
+        $idle = $value->idle;
+        $slave_url = getSlaveUrl($slave);
+        $status = getSlaveStatus($slave);
+
+        if ($status == "online" and $idle == "true") {
+            $status = "online / idle";
+            $color = "#C8D6C3";
+        }
+        else if ($status == "online") $color = "#BEFAAA";
+        else $color = "#FAAAAB";
+
+        $job_name = "";
+        $job_url = "";
+        $job_scenario = "";
+
+
+        if ($status == 'online') {
+            $job_params = getJJob($slave);
+            $job_name = $job_params['name'];
+            $job_url = $job_params['url'];
+            $job_scenario = $job_params['scenario'];
+        }
+
+        echo "<tr>";
+        echo "<th><a target='_blank' href='".$slave_url."'>".$slave."</a></th>";
+
+        echo "<th style='background-color: ".$color.";'>".$status."</th>";
+        echo "<th><a class='blink_me' style='font-size:12px;color:#33cc00;' target='_blank' href='".$job_url."'>".$job_name."</a></th>";
+
+        echo "</tr>";
+    }
+    echo '</tbody>';
+    echo '</table>';
+
+
+?>
diff --git a/tools/infra-dashboard/populateDB.txt b/tools/infra-dashboard/populateDB.txt
new file mode 100644 (file)
index 0000000..d3f8f5e
--- /dev/null
@@ -0,0 +1,203 @@
+
+CREATE DATABASE opnfv_pharos;
+USE opnfv_pharos;
+SHOW TABLES;
+use opnfv_pharos;
+
+DROP TABLE resource;
+CREATE TABLE resource (
+    resource_id INT UNSIGNED AUTO_INCREMENT,
+    name        VARCHAR(100) NOT NULL,
+    slavename   VARCHAR(50),
+    description VARCHAR(300),
+    link        VARCHAR(100),
+    bookable    BOOLEAN DEFAULT false,
+    active      BOOLEAN DEFAULT true,
+    PRIMARY KEY (resource_id)
+);
+
+DROP TABLE server;
+CREATE TABLE server (
+    server_id   INT UNSIGNED AUTO_INCREMENT,
+    resource_id INT NOT NULL,
+    model       VARCHAR(200),
+    cpu         VARCHAR(200),
+    ram         VARCHAR(200),
+    storage     VARCHAR(200),
+    count       INT DEFAULT 1,
+    PRIMARY KEY (server_id)
+);
+
+DROP TABLE pod;
+CREATE TABLE pod (
+    pod_id      INT UNSIGNED AUTO_INCREMENT,
+    resource_id INT NOT NULL,
+    chassis     VARCHAR(500),
+    PRIMARY KEY (pod_id)
+);
+
+DROP TABLE user;
+CREATE TABLE user(
+    user_id  INT UNSIGNED AUTO_INCREMENT,
+    name     VARCHAR(100) NOT NULL,
+    email    VARCHAR(100) NOT NULL UNIQUE,
+    password VARCHAR(100) NOT NULL,
+    company  VARCHAR(100),
+    creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (user_id)
+);
+
+DROP TABLE role;
+CREATE TABLE role (
+    role_id     INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+    name        VARCHAR(30),
+    description VARCHAR(300)
+);
+
+DROP TABLE user_role;
+CREATE TABLE user_role (
+    user_id     INT NOT NULL,
+    role_id     INT NOT NULL,
+    description VARCHAR(300),
+    PRIMARY KEY (user_id, role_id)
+);
+
+
+DROP TABLE user_resource;
+CREATE TABLE user_resource (
+    user_id     INT NOT NULL,
+    resource_id     INT NOT NULL,
+    PRIMARY KEY (user_id, resource_id)
+);
+
+
+
+DROP TABLE booking;
+CREATE TABLE booking(
+   booking_id    INT  NOT NULL AUTO_INCREMENT PRIMARY KEY,
+   resource_id   INT NOT NULL,
+   user_id       INT NOT NULL,
+   starttime     DATETIME NOT NULL,
+   endtime       DATETIME NOT NULL,
+   creation      TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+   purpose       VARCHAR(300)
+);
+
+/*
+describe resource;
+describe server;
+describe pod;
+describe pod_type;
+describe user;
+describe role;
+describe user_role;
+describe user_resource;
+describe booking;
+*/
+    
+/* POD TYPES */
+INSERT INTO pod_type (name, description) VALUES ("ci_pod", "PODS for CI usage only");
+INSERT INTO pod_type (name, description) VALUES ("dev_pod", "PODS development");
+
+
+/* CI PODS */
+INSERT INTO resource (name, slavename, description, link) VALUES ("Linux Foundation POD 1", "lf-pod1", "Some description", "https://wiki.opnfv.org/display/pharos/Lf+Lab");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='lf-pod1';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Linux Foundation POD 2", "lf-pod2", "Some description", "https://wiki.opnfv.org/display/pharos/Lf+Lab");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='lf-pod2';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Ericsson  POD 2", "ericsson-pod2", "Some description", "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='ericsson-pod2';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 2", "intel-pod2", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod2");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod2';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 5", "intel-pod5", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod5");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod5';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 6", "intel-pod6", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod6");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod6';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Intel POD 8", "intel-pod8", "Some description", "https://wiki.opnfv.org/display/pharos/Intel+Pod8");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod8';
+
+INSERT INTO resource (name, slavename, description, link) VALUES ("Huawei POD 1", "huawei-pod1", "Some description", "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod1';
+
+
+
+
+
+/* SOME DEV PODS */
+
+
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Intel POD 3", "intel-pod3", "Some description", true, "https://wiki.opnfv.org/display/pharos/Intel+Pod3");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod3';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Dell POD 1", "dell-pod1", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Dell+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='dell-pod1';
+
+INSERT INTO resource (name, slavename, description, bookable,  link) VALUES ("Dell POD 2", "dell-pod2", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Dell+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='dell-pod2';
+
+INSERT INTO resource (name, slavename, description, bookable,  link) VALUES ("Orange POD 2", "orange-pod2", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='orange-pod2';
+
+INSERT INTO resource (name, slavename, description, bookable,  link) VALUES ("Arm POD 1", "arm-build1", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='arm-build1';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Ericsson POD 1", "ericsson-pod1", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='ericsson-pod1';
+
+INSERT INTO resource (name, slavename, description, bookable,link) VALUES ("Huawei POD 2", "huawei-pod2", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod2';
+
+INSERT INTO resource (name, slavename, description, bookable,link) VALUES ("Huawei POD 3", "huawei-pod3", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod3';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Huawei POD 4", "huawei-pod4", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Huawei+Hosting");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='huawei-pod4';
+
+INSERT INTO resource (name, slavename, description, bookable, link) VALUES ("Intel POD 9", "intel-pod9", "Some description", true,  "https://wiki.opnfv.org/display/pharos/Intel+Pod9");
+INSERT INTO pod (resource_id) SELECT resource_id FROM resource where slavename='intel-pod9';
+
+
+SELECT * FROM resource;
+SELECT * FROM pod;
+
+
+INSERT INTO role (name, description) VALUES ("admin", "Administrator of the system");
+INSERT INTO role (name, description) VALUES ("lab_owner", "Owner of a lab.");
+INSERT INTO role (name, description) VALUES ("troubleshooter", "A person who can book a pod for troubleshooting.");
+SELECT * FROM role;
+
+INSERT INTO user (name, password, email, company) VALUES ("Jose Lausuch", md5("opnfv"), "jose.lausuch@ericsson.com", "Ericsson");
+INSERT INTO user (name, password, email, company) VALUES ("Daniel Smith", md5("opnfv"), "daniel.smith@ericsson.com", "Ericsson");
+INSERT INTO user (name, password, email, company) VALUES ("Jack Morgan", md5("opnfv"), "jack.morgan@intel.com", "Intel");
+INSERT INTO user (name, password, email, company) VALUES ("Fatih Degirmenci", md5("opnfv"), "fatih.degirmenci@ericsson.com", "Ericsson");
+INSERT INTO user (name, password, email, company) VALUES ("Trevor Cooper", md5("opnfv"), "trevor.cooper@intel.com", "Intel");
+SELECT * FROM user;
+
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="jose.lausuch@ericsson.com" AND role.name="admin";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="daniel.smith@ericsson.com" AND role.name="lab_owner";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="jack.morgan@intel.com" AND role.name="lab_owner";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="jack.morgan@intel.com" AND role.name="troubleshooter";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="fatih.degirmenci@ericsson.com" AND role.name="troubleshooter";
+INSERT INTO user_role (user_id, role_id) SELECT user_id,role_id FROM user,role WHERE email="trevor.cooper@intel.com" AND role.name="troubleshooter";
+SELECT * FROM user_role;
+
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="daniel.smith@ericsson.com" and slavename="ericsson-pod1";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="daniel.smith@ericsson.com" and slavename="ericsson-pod2";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod2";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod3";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod5";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod6";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod8";
+INSERT INTO user_resource (user_id, resource_id) SELECT user_id,resource_id FROM user,resource WHERE email="jack.morgan@intel.com" and slavename="intel-pod9";
+SELECT * FROM user_resource;
+
+
+
+
diff --git a/tools/infra-dashboard/utils/book.php b/tools/infra-dashboard/utils/book.php
new file mode 100644 (file)
index 0000000..6d4c5b2
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+
+    include 'database.php';
+
+    function book() {
+        $resource_id = $_POST['resource_id'];
+        $resource_name = $_POST['resource_name'];
+        $user_id = $_POST['user_id'];
+        $start = $_POST['start'];
+        $end = $_POST['end'];
+        $purpose = $_POST['purpose'];
+
+        $query = "select role.name as rolename from user, role, user_role where user.user_id = ".$user_id." and role.role_id=user_role.role_id and user_role.user_id=user.user_id;";
+        $result = mysql_query($query);
+
+        if(mysql_num_rows($result) == 0) {
+            echo "1"; //return a code instead of a meesage. Display the message later in javascript according to the returned code.
+            //echo "Booking not possible (your account is not associated with a role). Please contact the administrator.";
+            exit;
+        }
+        $is_only_lab_owner = true;
+        while ($row = mysql_fetch_array($result)) {
+            $rolename = $row['rolename'];
+            if ($rolename != "lab_owner") $is_only_lab_owner = false;
+        }
+        if ($is_only_lab_owner) {
+            $query = "select * from user u inner join user_resource r on r.user_id=u.user_id and u.user_id=".$user_id." and r.resource_id=".$resource_id.";";
+            $result = mysql_query($query);
+            if(mysql_num_rows($result) == 0) {
+                echo "2";
+                //echo "You are not allowed to book this resource. ";
+                exit;
+            }
+        }
+        $query = "INSERT INTO booking (resource_id, user_id, starttime, endtime, purpose) VALUES (".$resource_id.",".$user_id.",'".$start."','".$end."', '".$purpose."');";
+        $result = mysql_query($query);
+        if(mysql_insert_id()>0){
+            echo "Booking successful. The resource '".$resource_name."' is booked from ".$start." to ".$end.".";
+        }
+        else{
+            echo "Mysql Error : ".mysql_error().". Query = ".$query;
+        }
+
+    }
+
+    function getBookedDates() {
+        $resource_id = $_POST['resource_id'];
+        $query = "SELECT b.booking_id, b.resource_id,u.name as username,u.email,b.starttime,b.endtime,b.creation,b.purpose FROM booking as b,user as u WHERE b.resource_id=".$resource_id." AND b.user_id=u.user_id;";
+        $result = mysql_query($query);
+
+        $events = array();
+        while ($row = mysql_fetch_array($result)) {
+            $e = array();
+            $e['id'] = $row['booking_id'];
+            $e['booker_name'] = $row['username'];
+            $e['booker_email'] = $row['email'];
+            $e['title'] = $row['purpose'];
+            $e['start'] = $row['starttime'];
+            $e['end'] = $row['endtime'];
+            $e['bookdate'] = $row['creation'];
+
+            // Merge the event array into the return array
+            array_push($events, $e);
+        }
+
+        echo json_encode($events);
+    }
+
+
+
+    $action = $_POST['action'];
+
+    connectDB();
+    if ($action == "book") {
+        book();
+    } elseif ($action == "getBookedDates" ) {
+        getBookedDates();
+    } else {
+        echo "Invalid POST action.";
+    }
+    closeDB();
+?>
diff --git a/tools/infra-dashboard/utils/database.php b/tools/infra-dashboard/utils/database.php
new file mode 100644 (file)
index 0000000..bacf363
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+
+    date_default_timezone_set('UTC');
+    function connectDB() {
+        $username = "root";
+        $password = "opnfv";
+        $hostname = "localhost";
+        $dbhandle = mysql_connect($hostname, $username, $password)
+            or die("Unable to connect to MySQL.");
+        $selected = mysql_select_db("opnfv_pharos",$dbhandle)
+            or die("Could not select opnfv_pharos DB.");
+    }
+
+    function closeDB(){
+        mysql_connect($dbhandle);
+    }
+
+?>
diff --git a/tools/infra-dashboard/utils/jenkinsAdapter.php b/tools/infra-dashboard/utils/jenkinsAdapter.php
new file mode 100644 (file)
index 0000000..c238feb
--- /dev/null
@@ -0,0 +1,204 @@
+<?php
+
+    function getSlaves() {
+        $query="https://build.opnfv.org/ci/computer/api/xml?tree=computer[displayName,offline,idle]";
+        $output =  file_get_contents($query);
+        $xml = simplexml_load_string($output);
+        if ($xml) return $xml;
+        else return "";
+    }
+
+    function getCiSlaves(){
+        $query="https://build.opnfv.org/ci/label/ci-pod/api/xml?xpath=labelAtom/node[nodeName]&wrapper=nodes";
+        $output =  file_get_contents($query);
+        $xml = simplexml_load_string($output);
+        if ($xml) return $xml;
+        else return "";
+    }
+
+    function getAllBuilds(){
+        $query="https://build.opnfv.org/ci/api/xml?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp,result]]";
+        $output =  file_get_contents($query);
+        $xml = simplexml_load_string($output);
+        if ($xml) return $xml;
+        else return "";
+    }
+
+    $SLAVES = getSlaves();
+    $CI_PODS = getCiSlaves();
+    $ALL_BUILDS = getAllBuilds();
+
+    function getActiveBuilds() {
+        global $ALL_BUILDS;
+        //$query="https://build.opnfv.org/ci/api/xml?tree=jobs[displayName,url,lastBuild[fullDisplayName,building,builtOn,timestamp]]&xpath=hudson/job[lastBuild/building=%27true%27]&wrapper=hudson";
+        $xml = $ALL_BUILDS->xpath('job[lastBuild/building="true"]');
+        if ($xml) return $xml;
+        else return "";
+    }
+
+    $ACTIVE_BUILDS = getActiveBuilds();
+
+    function slaveExists($slave) {
+        global $SLAVES;
+        $slave = $SLAVES->xpath('computer[displayName="'.$slave.'"]');
+        if ($slave) return true;
+        else return false;
+    }
+
+    function getSlaveStatus($slave) {
+        global $SLAVES;
+        $status = "unknown";
+        if (!slaveExists($slave)) return $status;
+        $slave = $SLAVES->xpath('computer[displayName="'.$slave.'"]');
+        $offline = $slave[0]->offline;
+
+        if ($offline == "true") $status = "offline";
+        else $status = "online";
+        return $status;
+    }
+
+    function getSlaveUrl($slave) {
+        if (slaveExists($slave)) return "https://build.opnfv.org/ci/computer/".$slave;
+        else return "";
+    }
+
+
+    function isCiPod($slave) {
+        global $CI_PODS;
+        $result = $CI_PODS->xpath('node[nodeName="'.$slave.'"]');
+        if ($result) return true;
+        else return false;
+    }
+
+    function isDevPod($slave) {
+        global $CI_PODS;
+        if (isCiPod($slave)) return false;
+        else if (strpos($slave, 'pod') !== false)  return true;
+        else return false;
+    }
+
+    function parseJobString($str) {
+        $scenario = '';
+        $installer = '';
+        $branch = '';
+        $installers = array("fuel", "joid", "apex", "compass");
+        $branches = array("master","arno", "brahmaputra", "colorado");
+        $arr = split ('[ -]', $str);
+        for($x = 0; $x < count($arr); $x++) {
+            if (strcmp($arr[$x],"os") == 0)  //all the scenarios start with 'os'
+                $scenario = $arr[$x].'-'.$arr[$x+1].'-'.$arr[$x+2].'-'.$arr[$x+3];
+            else if (in_array($arr[$x], $installers))
+                $installer = $arr[$x];
+            else if (in_array($arr[$x], $branches))
+                $branch = $arr[$x];
+        }
+        $arr2 = explode(' ', $str);
+        $jobname = $arr2[0]; //take first word as job name
+
+        return array(
+            "jobname"=>$jobname,
+            "installer"=>$installer,
+            "branch"=>$branch,
+            "scenario"=>$scenario
+        );
+    }
+
+
+    function getJJob($slave) {
+        global $ALL_BUILDS;
+        if (!slaveExists($slave)) return "";
+
+        //$builds = $ALL_BUILDS;
+        //$xml = $ALL_BUILDS->xpath('job[lastBuild/building="true"][lastBuild/builtOn="'.$slave.'"]');
+        $builds = $ALL_BUILDS->xpath('job[lastBuild/builtOn="'.$slave.'"]');
+        if (! $builds) { //the slave does not have jobs in building state
+            //echo "NO JOBS FOUND";
+            return "";
+        }
+        else {
+            //is there any active build?
+            $builds = $ALL_BUILDS->xpath('job[lastBuild/building="true"][lastBuild/builtOn="'.$slave.'"]');
+            if ($builds) { // there are active builds for this slave
+                //print_r($builds);
+
+                $child_job  = simplexml_import_dom($builds[0]);
+                foreach ($builds as &$build) {
+                    $int1 = intval($build->lastBuild->timestamp);
+                    $int2 = intval($child_job->lastBuild->timestamp);
+                    if ($int1 > $int2) {
+                        $child_job  = simplexml_import_dom($build);
+                    }
+                }
+                $url = strval($child_job->url);
+                $fullDisplayName = $child_job->lastBuild->fullDisplayName;
+                //echo $fullDisplayName."<br>";
+
+                $params = parseJobString($fullDisplayName);
+
+                $type = 0; // type=0 means the job is running
+                $job_params = array(
+                    "name"=>$params['jobname'],
+                    "url"=>$url,
+                    "scenario"=>$params['scenario'],
+                    "installer"=>$params['installer'],
+                    "branch"=>$params['branch'],
+                    "type"=>$type
+                );
+                //print_r($job_params);
+                return $job_params;
+
+            }
+            else { // there are NO active builds for this slave, we take the latest build
+                //echo "NO Active builds";
+                $builds = $ALL_BUILDS->xpath('job[lastBuild/building="false"][lastBuild/builtOn="'.$slave.'"]');
+                $last_job  = simplexml_import_dom($builds[0]);
+                //print_r($last_job);
+                foreach ($builds as &$build) {
+                    $int1 = intval($build->lastBuild->timestamp);
+                    $int2 = intval($last_job->lastBuild->timestamp);
+                    if ($int1 > $int2) {
+                        $last_job  = simplexml_import_dom($build);
+                    }
+                }
+                $url = strval($last_job->url);
+                $result = strval($last_job->lastBuild->result);
+                $fullDisplayName = $last_job->lastBuild->fullDisplayName;
+
+                $params = parseJobString($fullDisplayName);
+
+                $type = 3;
+                if ($result == "SUCCESS") $type = 1; // type=1 means it's the last job and it succeded
+                if ($result == "FAILURE") $type = 2; // type=2 means it's the last job and it failed
+                if ($result == "UNSTABLE") $type = 3; // type=3 means it's the last job is unstable
+
+                $job_params = array(
+                    "name"=>$params['jobname'],
+                    "url"=>$url,
+                    "scenario"=>$params['scenario'],
+                    "installer"=>$params['installer'],
+                    "branch"=>$params['branch'],
+                    "type"=>$type
+                );
+
+                return $job_params;
+                //print_r($job_params);
+            }
+
+        }
+    }
+
+    /*
+    $slave = "lf-pod2";
+    $job_params = getJJob($slave);
+
+    $status = getSlaveStatus($slave);
+    echo "Status slave ".$slave.": ".$status."<br>";
+    echo "Job: ".$job_params['name']."<br>";
+    echo "URL: ".$job_params['url']."<br>";
+    echo "Scenario: ".$job_params['scenario']."<br>";
+    echo "Installer: ".$job_params['installer']."<br>";
+    echo "Branch: ".$job_params['branch']."<br>";
+    echo "Type: ".$job_params['type']."<br>";
+    */
+
+?>
diff --git a/tools/infra-dashboard/utils/login.php b/tools/infra-dashboard/utils/login.php
new file mode 100644 (file)
index 0000000..2ac7101
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+    include 'database.php';
+
+
+    function login(){
+        $email = $_POST['email'];
+        $password = $_POST['password'];
+
+        $query = "SELECT * FROM user where EMAIL='".$email."';";
+        $result = mysql_query($query);
+
+        $user = array();
+        if(mysql_num_rows($result) > 0) {
+            $query = "SELECT * FROM user where email='".$email."' and password='".$password."';";
+            $result = mysql_query($query);
+            if(mysql_num_rows($result) > 0) {
+                while($row = mysql_fetch_assoc($result)) {
+                    $user = $row;
+                    $user["result"] = 0;
+
+                    $_SESSION['user_id'] = $user['user_id'];
+                    $_SESSION['user_name'] = $user['name'];
+                    $_SESSION['user_email'] = $user['email'];
+                }
+            } else {
+                $user["result"] = 1; //wrong password
+            }
+        } else {
+            $user["result"] = 2; //user not registered
+        }
+        echo json_encode($user);
+
+    }
+
+
+    $action = $_POST['action'];
+
+    connectDB();
+    session_start();
+
+    if ($action == "login") {
+        login();
+    } else if ($action == "logout") {
+        unset($_SESSION['user_id']);
+        unset($_SESSION['user_name']);
+        unset($_SESSION['user_email']);
+        session_destroy();
+    } else {
+        echo "Invalid POST action.";
+    }
+    closeDB();
+
+?>
+
+