diff --git a/assets/css/pure-min.css b/assets/css/pure-min.css index f0aa374..7a09310 100644 --- a/assets/css/pure-min.css +++ b/assets/css/pure-min.css @@ -8,4 +8,13 @@ https://github.com/yahoo/pure/blob/master/LICENSE.md normalize.css v^3.0 | MIT License | git.io/normalize Copyright (c) Nicolas Gallagher and Jonathan Neal */ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;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{line-height:normal}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-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}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,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-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;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{line-height:normal}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-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}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap;-ms-align-content:flex-start;-webkit-align-content:flex-start;align-content:flex-start}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;zoom:1;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000\9}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-link,.pure-menu-heading{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;padding:.5em 0}.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar{display:none}.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-disabled,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected .pure-menu-link,.pure-menu-selected .pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} + +/* Pure Responsive Grid */ +/*! +Pure v0.6.0 +Copyright 2014 Yahoo! Inc. All rights reserved. +Licensed under the BSD License. +https://github.com/yahoo/pure/blob/master/LICENSE.md +*/ +@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} \ No newline at end of file diff --git a/assets/css/statbot.css b/assets/css/statbot.css index 329f7d1..621cdd0 100644 --- a/assets/css/statbot.css +++ b/assets/css/statbot.css @@ -211,6 +211,60 @@ aside { background: #1f8dd6; } +.hidden { + display: none; +} + +.bordered { + border: 1px solid #888; + border-radius: 3px; +} + +.levelup-user { + border-radius: 3px; + border: 1px solid #888; + margin: 10px; + overflow: hidden; + height: 75px; + background: #AAA; + padding: 5px; + position: relative; +} + +.levelup-username { + font-size: 2em; + color: #FFF; + background: #777; + border-radius: 3px; + border-top: 1px solid black; + border-left: 1px solid black; + border-right: 1px solid white; + border-bottom: 1px solid white; + padding: 5px; + position: absolute; + top: 5%; + right: 1%; +} +.levelup-level { + font-size: 7em; + margin-right: 5em; + color: #000; + position: absolute; + left: 1%; + top: 35%; + color: #FFF; +} +.levelup-smalllevel { + position: absolute; + bottom: 20%; + right: 1%; + color: #000; +} +.levelup-xp { + position: absolute; + bottom: 1%; + right: 1%; +} /* -- Responsive Styles (Media Queries) ------------------------------------- */ /* diff --git a/assets/js/B.js b/assets/js/B.js new file mode 100644 index 0000000..e0cf012 --- /dev/null +++ b/assets/js/B.js @@ -0,0 +1,276 @@ +function B(els, attrs) { + // Turn 'this' into an array of passed in elements. + function B(els) { + if(typeof els === "string") { + els = this.brb.create(els); + } + for(var i = 0; i < els.length; i++) { + this[i] = els[i]; + } + this.length = els.length; +// return this; + } + + // Map a function to all elements in 'this' + B.prototype.map = function(callback) { + var results = [], i = 0; + for(;i 1 ? m[0] : m; + }; + + // Update css for each element in 'this' + B.prototype.css = function(css_opt_var, css_opt_val) { + if(typeof css_opt_var !== "string") { + for(css_var in css_opt_var) { + this.forEach(function(el){el.style[css_var]=css_opt_var[css_var];}); + } + return this; + } else { + if(typeof css_opt_val !== "undefined") { + return this.forEach(function(el){el.style[css_opt_var]=css_opt_val;}); + } else { + return this.mapOne(function(el){return el.style[css_opt_var];}); + } + } + }; + + // Update the innerText for each element in 'this' + B.prototype.html = function(text) { + if(typeof text !== "undefined") { + return this.forEach(function(el){el.innerHTML=text;}); + } else { + return this.mapOne(function(el){return el.innerHTML;}); + } + }; + + // Check if the first element has the requested class + B.prototype.hasClass = function(cls) { + return this.mapOne(function(el){ + return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1; + }); + } + + // Add a class to each element in 'this' + B.prototype.addClass = function(classes) { + var className = ""; + if(typeof classes !== "string") { + for(var i=0;i-1){ + cs = cs.slice(0,i).concat(cs.slice(++i)); + } + el.className=cs.join(" "); + }); + }; + + // Set an attribute for each element in 'this' + B.prototype.attr = function(attr,val){ + if(typeof val!=="undefined"){ + if(this[0].tagName=="INPUT" && attr.toUpperCase()=="VALUE") { + // If we're setting the 'VALUE' then it's actually .value + return this.forEach(function(el){ + el.value=val; + }); + } else { + // Otherwise use .setAttribute + return this.forEach(function(el){ + el.setAttribute(attr,val); + }); + } + } else { + // And getting the value + if(this[0].tagName=="INPUT" && attr.toUpperCase()=="VALUE") { + return this.mapOne(function(el){ + return el.value; + }); + } else { + return this.mapOne(function(el){ + return el.getAttribute(attr); + }); + } + } + }; + + // Actually set a value on each element (can be done with attr too.) + B.prototype.val = function(new_val) { + if(typeof new_val!=="undefined"){ + return this.forEach(function(el){ + el.value = new_val; + }); + } else { + // Just retrieve the value for the first element + return this.mapOne(function(el) { + return el.value; + }); + } + } + + // Append an element to the DOM after each element in 'this' + B.prototype.append = function(els) { + this.forEach(function(parEl, i) { + els.forEach(function(childEl) { + if(i>0) { + childEl=childEl.cloneNode(true); + } + parEl.appendChild(childEl); + }); + }); + }; + + // Prepend an element to the DOM before each element in 'this' + B.prototype.prepend = function(els) { + return this.forEach(function(parEl, i) { + for(var j = els.length-1; j>-1; j--) { + childEl=(i>0)?els[j].cloneNode(true):els[j]; + parEl.insertBefore(childEl, parEl.firstChild); + } + }); + }; + + // Remove all elements in 'this' from the DOM + B.prototype.remove = function() { + return this.forEach(function(el){ + return el.parentNode.removeChild(el); + }); + }; + + // Find children that match selector + B.prototype.children = function(selector) { + var results = []; + this.forEach(function(el) { + var sub_r = el.querySelectorAll(selector); + for(var i = 0; i < sub_r.length; i++) { + results.push(sub_r[i]); + } + }); + return results; + } + + B.prototype.firstChild = function(selector) { + return this.children(selector)[0]; + } + + // Add an event listener to each element in 'this' + B.prototype.on = (function(){ + // Browser compatibility... + if(document.addEventListener) { + return function(evt,fn) { + return this.forEach(function(el){ + el.addEventListener(evt, fn, false); + }); + }; + } else if(document.attachEvent) { + return function(evt,fn) { + return this.forEach(function(el){ + el.attachEvent("on"+evt,fn); + }); + }; + } else { + return function(evt, fn) { + return this.forEach(function(el){ + el["on"+evt]=fn; + }); + }; + } + }()); + + // Disable event listeners on elements in 'this' + B.prototype.off = (function(){ + // Browser compatibility... + if(document.removeEventListener) { + return function(evt, fn) { + return this.forEach(function(el) { + el.removeEventListener(evt, fn, false); + }); + }; + } else if(document.detachEvent) { + return function(evt, fn) { + return this.forEach(function(el) { + el.detachEvent("on"+evt, fn); + }); + }; + } else { + return function(evt, fn) { + return this.forEach(function(el){ + el["on"+evt]=null; + }); + }; + } + }()); + + // The actual framework object, yay! + var brb = { + // Get an element + get: function(selector) { + var els; + if(typeof selector === "string") { + els = document.querySelectorAll(selector); + } else if(selector.length) { + els = selector; + } else { + els = [selector]; + } + return new B(els); + }, + // Create a new element + create: function(tagName, attrs) { + var el = new B([document.createElement(tagName)]); + // Set attributes on new element + if(attrs) { + if(attrs.className) { + // Classes + el.addClass(attrs.className); + delete attrs.classname; + } + if(attrs.text) { + // Text + el.text(attrs.text); + delete attrs.text; + } + for(var key in attrs) { + // All other Attributes + if(attrs.hasOwnProperty(key)) { + el.attr(key, attrs[key]); + } + } + } + return el; + } + }; + if(els.match) { + var match_tags = els.match(/<([^>\s\/]*)\s?\/?>/); + } + if(match_tags && match_tags.length > 0) { + // It's a 'create tag' command + return brb.create(match_tags[1], attrs); + } else { + // Just search for matches + return brb.get(els); + } +}; +// And there is our minimalist framework. Who needs jquery? diff --git a/assets/js/levelup_main.js b/assets/js/levelup_main.js new file mode 100644 index 0000000..99e120d --- /dev/null +++ b/assets/js/levelup_main.js @@ -0,0 +1,101 @@ +document.addEventListener("DOMContentLoaded", function(event) { + sortByAlpha(); + buildList(); + B('#btnAlphaSort').on('click', function() { + if(sortMode == "ALPHA") { + sortMode = "ALPHA-REV"; + B('#btnAlphaSort>i').removeClass('fa-sort-asc').addClass('fa-sort-desc'); + } else { + sortMode = "ALPHA"; + B('#btnAlphaSort>i').removeClass('fa-sort-desc').addClass('fa-sort-asc'); + } + sortUsers(); + buildList(); + }); + B('#btnXpSort').on('click', function() { + if(sortMode == "XP") { + sortMode = "XP-REV"; + B('#btnXpSort>i').removeClass('fa-sort-desc').addClass('fa-sort-asc'); + } else { + sortMode = "XP"; + B('#btnXpSort>i').removeClass('fa-sort-asc').addClass('fa-sort-desc'); + } + sortUsers(); + buildList(); + }); +}) + +var sortMode = "ALPHA"; + +function clearList() { + B(".levelup-user").remove(); +} + +function sortUsers() { + switch(sortMode) { + case "ALPHA-REV": sortByAlphaRev(); break; + case "XP": sortByXP(); break; + case "XP-REV": sortByXPRev(); break; + default: sortByAlpha(); break; + } +} + +function sortByXP() { + levelUpStats.users.sort(function(a, b){ + if(a.xp > b.xp) { return -1; } + if(a.xp < b.xp) { return 1; } + return 0; + }); +} +function sortByXPRev() { + levelUpStats.users.sort(function(a, b){ + if(a.xp > b.xp) { return 1; } + if(a.xp < b.xp) { return -1; } + return 0; + }); +} +function sortByAlpha() { + levelUpStats.users.sort(function(a, b){ + if(a.name > b.name) { return 1; } + if(a.name < b.name) { return -1; } + return 0; + }); +} +function sortByAlphaRev() { + levelUpStats.users.sort(function(a, b){ + if(a.name > b.name) { return -1; } + if(a.name < b.name) { return 1; } + return 0; + }); +} + +function buildList() { + clearList(); + for(var i = 0; i < levelUpStats.users.length; i++) { + // Figure out the users current level & percentage through it. + var xp = levelUpStats.users[i].xp, + toNext = 100, + tstLvl = 1; + + while(xp - toNext > 0) { + xp -= toNext; + tstLvl++; + toNext = tstLvl*100; + } + var donePct = (xp / toNext)*100, + userLevel = tstLvl, + userName = levelUpStats.users[i].name, + idName = userName.replace('.','_'); + + B("#userXpList").append(B('
').attr('id',"levelup-user-"+idName).addClass('levelup-user pure-u-1 pure-u-md-1-3').attr('data-username',userName)); + var userNameSpan = B("").attr('id','levelup-user-'+idName+'-name').addClass('levelup-username'); + B("#levelup-user-"+idName).append(B("
").attr('id','levelup-user-'+idName+'-level').addClass('levelup-level').html(userLevel)); + B("#levelup-user-"+idName).append(userNameSpan.html(userName)); + B("#levelup-user-"+idName).append(B("
").attr('id','levelup-user-'+idName+'-smalllevel').addClass('levelup-smalllevel').html("Level "+userLevel)); + B("#levelup-user-"+idName).append(B("
").attr('id','levelup-user-'+idName+'-xp').addClass('levelup-xp').html("("+xp+"/"+toNext+")")); + + B('.levelup-user').on('click', function() { + console.log("Load User Profile: "+B(this).attr('data-username')); + }); + } +} diff --git a/assets/js/main_stats.js b/assets/js/main_stats.js index c25e3cb..2d1a77a 100644 --- a/assets/js/main_stats.js +++ b/assets/js/main_stats.js @@ -1,11 +1,52 @@ document.addEventListener("DOMContentLoaded", function(event) { + B("#btnChannelStats").on('click', function(e) { + B(this).addClass('pure-button-disabled'); + B('#btnMessageStats').removeClass('pure-button-disabled'); + B('#btnUserStats').removeClass('pure-button-disabled'); + B('#channelStats').removeClass('hidden'); + B('#userStats').addClass('hidden'); + B('#messageStats').addClass('hidden'); + }); + + B('#btnMessageStats').on('click', function(e) { + B(this).addClass('pure-button-disabled'); + B('#btnChannelStats').removeClass('pure-button-disabled'); + B('#channelStats').addClass('hidden'); + B('#btnUserStats').removeClass('pure-button-disabled'); + B('#userStats').addClass('hidden'); + B('#messageStats').removeClass('hidden'); + }); + + B('#btnUserStats').on('click', function(e) { + B(this).addClass('pure-button-disabled'); + B('#btnChannelStats').removeClass('pure-button-disabled'); + B('#channelStats').addClass('hidden'); + B('#btnMessageStats').removeClass('pure-button-disabled'); + B('#messageStats').addClass('hidden'); + B('#userStats').removeClass('hidden'); + }); + + B('#btnUsrHrlyStats').on('click', function(e) { + B(this).addClass('pure-button-disabled'); + B('#btnUsrDowStats').removeClass('pure-button-disabled'); + B('#userHourlyStats').removeClass('hidden'); + B('#userDowStats').addClass('hidden'); + }); + + B('#btnUsrDowStats').on('click', function(e) { + B(this).addClass('pure-button-disabled'); + B('#btnUsrHrlyStats').removeClass('pure-button-disabled'); + B('#userHourlyStats').addClass('hidden'); + B('#userDowStats').removeClass('hidden'); + }); + var main_channel_names = ['general','random'], main_member_numbers = [], main_message_numbers = [], other_channel_names = [], other_member_numbers = [], other_message_numbers = []; - for(var i = 0; i < stats.channels.length; i++){ + for(var i = 0; i < stats.channels.length; i++) { if(stats.channels[i].name == "general") { main_member_numbers[0] = stats.channels[i].member_count; main_message_numbers[0] = stats.channels[i].message_count; @@ -101,13 +142,212 @@ document.addEventListener("DOMContentLoaded", function(event) { name: 'Messages', color: 'rgba(126,86,134,0.9)', data: other_message_numbers, - pointPadding: 0.3 + pointPadding: 0.0 },{ name: 'Members', color: 'rgba(165,170,217,1)', data: other_member_numbers, - pointPadding: 0.4, + pointPadding: 0.2, yAxis: 1 }] }); + + var msg1 = [{name: 'Messages', data: []}]; + for(var i = 0; i < 24; i++) { + msg1[0].data.push(stats.messages.hours[i]); + } + var msgHourlyChart = new Highcharts.Chart({ + chart: { + renderTo: 'msgHourlyStats' + }, + title: { + text: 'Hourly Stats' + }, + xAxis: { + categories: [ + '00:00', '01:00', '02:00', '03:00', '04:00', '05:00', + '06:00', '07:00', '08:00', '09:00', '10:00', '11:00', + '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', + '18:00', '19:00', '20:00', '21:00', '22:00', '23:00' + ] + }, + yAxis: { + title: { text: '' }, + plotLines: [{ + value: 0, + width: 1, + color: '#808080' + }] + }, + series: msg1 + }); + + var msg2 = [{name: 'Messages', data: []}]; + msg2[0].data.push(stats.messages.dow['Sun']); + msg2[0].data.push(stats.messages.dow['Mon']); + msg2[0].data.push(stats.messages.dow['Tue']); + msg2[0].data.push(stats.messages.dow['Wed']); + msg2[0].data.push(stats.messages.dow['Thu']); + msg2[0].data.push(stats.messages.dow['Fri']); + msg2[0].data.push(stats.messages.dow['Sat']); + + var msgDowChart = new Highcharts.Chart({ + chart: { + renderTo: 'msgDowStats' + }, + title: { + text: 'Day of Week Stats' + }, + xAxis: { + categories: [ + 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' + ] + }, + yAxis: { + title: { text: '' }, + plotLines: [{ + value: 0, + width: 1, + color: '#808080' + }] + }, + series: msg2 + }); + + + var numCharts = 0, + usrHourlyCharts = [], + usrDowCharts = [], + maxMessages = 0; + stats.users.sort(function(a, b){ + if(a.name < b.name) { return -1; } + if(a.name > b.name) { return 1; } + return 0; + }); + + for(var i = 0; i < stats.users.length; i++) { + for(var j = 0; j < 24; j++) { + if(stats.users[i].messages.hours[j] > maxMessages) { + maxMessages = stats.users[i].messages.hours[j]; + } + } + } + while((numCharts*10) < stats.users.length) { + var usrList = [], + usrHourlyActivity = [], + usrDowActivity = []; + for(var i = (numCharts*10); i < 10+(numCharts*10); i++) { + if(i < stats.users.length) { + usrList.push(stats.users[i].name); + for(var j = 0; j < 24; j++) { + usrHourlyActivity.push({ + x: j, + y: (i - (numCharts*10)), + value: stats.users[i].messages.hours[j] + }); + } + dowArr=['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; + for(var dowI = 0; dowI < 7; dowI++) { + usrDowActivity.push({ + x: dowI, + y: (i - (numCharts*10)), + value: stats.users[i].messages.dow[dowArr[dowI]] + }); + } + } + } + + B("#userHourlyStats").append(B("
").attr('id','usrHourlyStats-'+numCharts)); + B("#userDowStats").append(B("
").attr('id','usrDowStats-'+numCharts)); + + usrHourlyCharts.push(new Highcharts.Chart({ + chart: { + type: 'heatmap', + renderTo: 'usrHourlyStats-'+numCharts + }, + title: { + text: 'Hourly Activity by User' + }, + xAxis: { + categories: [ + '00:00','01:00','02:00','03:00','04:00','05:00', + '06:00','07:00','08:00','09:00','10:00','11:00', + '12:00','13:00','14:00','15:00','16:00','17:00', + '18:00','19:00','20:00','21:00','22:00','23:00' + ] + }, + yAxis: { + categories: usrList, + title: null + }, + colorAxis: { + stops: [ + [0, '#3060cf'], + [0.5, '#fffbbc'], + [0.9, '#c4463a'] + ], + min: 0, + max: maxMessages + }, + series: [{ + borderWidth: 1, + data: usrHourlyActivity, + name: "Messages", + tooltip: { + pointFormatter: function() { + if(this.x < 10) { + return this.series.yAxis.categories[this.y] + " - 0"+this.x+":00 ("+this.value+" messages)"; + } + return this.series.yAxis.categories[this.y] + " - "+this.x+":00 ("+this.value+" messages)"; + } + } + }] + })); + + usrDowCharts.push(new Highcharts.Chart({ + chart: { + type: 'heatmap', + renderTo: 'usrDowStats-'+numCharts + }, + title: { + text: 'Day Of Week Activity by User' + }, + xAxis: { + categories: [ + 'Sun','Mon','Tue','Wed','Thu','Fri','Sat' + ] + }, + yAxis: { + categories: usrList, + title: null + }, + colorAxis: { + stops: [ + [0, '#3060cf'], + [0.5, '#fffbbc'], + [0.9, '#c4463a'] + ], + min: 0, + max: maxMessages + }, + series: [{ + borderWidth: 1, + data: usrDowActivity, + name: "Messages", + tooltip: { + pointFormatter: function() { + if(this.x < 10) { + return this.series.yAxis.categories[this.y] + " - 0"+this.x+":00 ("+this.value+" messages)"; + } + return this.series.yAxis.categories[this.y] + " - "+this.x+":00 ("+this.value+" messages)"; + } + } + }] + })); + numCharts++; + } + + B('#userStats').addClass('hidden'); + B('#messageStats').addClass('hidden'); + B('#userDowStats').addClass('hidden'); }); diff --git a/levelup_model.go b/levelup_model.go new file mode 100644 index 0000000..fc0000c --- /dev/null +++ b/levelup_model.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + + "github.com/boltdb/bolt" +) + +func getAllLevelUpChannelXp(user string) map[string]int { + openDatabase() + ret := make(map[string]int) + // First, get a list of all levelup stats + db.Update(func(tx *bolt.Tx) error { + var b, uB, uSB *bolt.Bucket + var err error + + b = tx.Bucket([]byte("users")) + if b == nil { + return fmt.Errorf("Unable to open 'users' bucket") + } + if uB = b.Bucket([]byte(user)); uB != nil { + if uSB = uB.Bucket([]byte("stats")); uSB != nil { + return uSB.ForEach(func(k, v []byte) error { + if strings.HasPrefix(string(k), "levelup-") { + mapKey := strings.Replace(string(k), "levelup-", "", -1) + if mapKey != "xp" { + ret[mapKey], _ = strconv.Atoi(string(v)) + } + } + return nil + }) + } + } + return err + }) + closeDatabase() + return ret +} diff --git a/processor_general.go b/processor_general.go index 84696e2..473d44c 100644 --- a/processor_general.go +++ b/processor_general.go @@ -88,31 +88,54 @@ func (p *generalStatProcessor) GetName() string { func (p *generalStatProcessor) GetStatKeys() []string { return []string{ "bot-message", + //"bot-reaction-*", "channel-message", + //"channel-reaction-*", "message-hour-*", "message-dow-*", "message-dom-*", + //"reaction-*-hour-*", + //"reaction-*-dow-*", + //"reaction-*-dom-*", } } func (p *generalStatProcessor) ProcessMessage(m *Message) { - incrementUserStat(m.User, "message-hour-"+m.Time.Format("15")) - incrementUserStat(m.User, "message-dow-"+m.Time.Format("Mon")) - incrementUserStat(m.User, "message-dom-"+m.Time.Format("02")) + if m.Type == "message" { + incrementUserStat(m.User, "message-hour-"+m.Time.Format("15")) + incrementUserStat(m.User, "message-dow-"+m.Time.Format("Mon")) + incrementUserStat(m.User, "message-dom-"+m.Time.Format("02")) + //} else if m.Type == "reaction_added" { + //incrementUserStat(m.User, "reaction-"+m.Name+"-hour-"+m.Time.Format("15")) + //incrementUserStat(m.User, "reaction-"+m.Name+"-dow-"+m.Time.Format("Mon")) + //incrementUserStat(m.User, "reaction-"+m.Name+"-dom-"+m.Time.Format("02")) + } } func (p *generalStatProcessor) ProcessBotMessage(m *Message) {} func (p *generalStatProcessor) ProcessUserMessage(m *Message) { - incrementUserStat(m.User, "bot-message") + if m.Type == "message" { + incrementUserStat(m.User, "bot-message") + //} else if m.Type == "reaction_added" { + // incrementUserStat(m.User, "bot-reaction-"+m.Name) + } } func (p *generalStatProcessor) ProcessBotUserMessage(m *Message) {} func (p *generalStatProcessor) ProcessChannelMessage(m *Message) { - incrementUserStat(m.User, "channel-message") + if m.Type == "message" { + incrementUserStat(m.User, "channel-message") - incrementChannelStat(m.Channel, "message-hour-"+m.Time.Format("15")) - incrementChannelStat(m.Channel, "message-dow-"+m.Time.Format("Mon")) - incrementChannelStat(m.Channel, "message-dom-"+m.Time.Format("02")) + incrementChannelStat(m.Channel, "message-hour-"+m.Time.Format("15")) + incrementChannelStat(m.Channel, "message-dow-"+m.Time.Format("Mon")) + incrementChannelStat(m.Channel, "message-dom-"+m.Time.Format("02")) + //} else if m.Type == "reaction_added" { + // incrementUserStat(m.User, "channel-reaction-"+m.Name) + + // incrementChannelStat(m.Channel, "reaction-"+m.Name+"-hour-"+m.Time.Format("15")) + // incrementChannelStat(m.Channel, "reaction-"+m.Name+"-dow-"+m.Time.Format("Mon")) + // incrementChannelStat(m.Channel, "reaction-"+m.Name+"-dom-"+m.Time.Format("02")) + } } func (p *generalStatProcessor) ProcessBotChannelMessage(m *Message) {} @@ -149,15 +172,27 @@ func (wm *generalWebModule) GetBottomMenuEntries() []menuItem { func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request) { initRequest(w, req) - type ChannelStat struct { + type ChannelStats struct { Name string MemberCount int MessageCount int } + type MessageStats struct { + Hours map[int]int + Dow map[string]int + } + type UserStats struct { + Name string + Hours map[int]int + Dow map[string]int + Messages int + } type StatData struct { TotalChannelMessages int TotalChannels int - ChannelStats []ChannelStat + ChannelStats []ChannelStats + UserStats []UserStats + MessageStats MessageStats Error string } @@ -167,22 +202,79 @@ func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request openDatabase() chanlst := getChannelList() - var chanstats []ChannelStat + userlst := getUserList() + var chanstats []ChannelStats + var userstats []UserStats + s.MessageStats.Hours = make(map[int]int) + for i := 0; i < 24; i++ { + s.MessageStats.Hours[i] = 0 + } + s.MessageStats.Dow = make(map[string]int) + for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + s.MessageStats.Dow[k] = 0 + } + for _, k := range chanlst { - chanstats = append(chanstats, ChannelStat{ + chanstats = append(chanstats, ChannelStats{ Name: getChannelName(k), MemberCount: getChannelMemberCount(k), MessageCount: getChannelMessageCount(k), }) + for i := 0; i < 24; i++ { + if hrI, err := getChannelStat(k, fmt.Sprintf("message-hour-%0d", i)); err == nil { + s.MessageStats.Hours[i] = s.MessageStats.Hours[i] + hrI + } + } + for _, dow := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + if dowI, err := getChannelStat(k, fmt.Sprintf("message-dow-%s", dow)); err == nil { + s.MessageStats.Dow[dow] = s.MessageStats.Dow[dow] + dowI + } + } } + for _, k := range userlst { + usrName := getUserName(k) + if usrName == "stat_bot" || usrName == "bot" { + continue + } + usrHourStats := make(map[int]int) + usrDowStats := make(map[string]int) + var chanMsg int + var err error + if chanMsg, err = getUserStat(k, "channel-message"); err != nil { + chanMsg = 0 + } + for i := 0; i < 24; i++ { + if hrI, err := getUserStat(k, fmt.Sprintf("message-hour-%0d", i)); err == nil { + usrHourStats[i] = hrI + } else { + usrHourStats[i] = 0 + } + } + for _, dow := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + if dowI, err := getUserStat(k, fmt.Sprintf("message-dow-%s", dow)); err == nil { + usrDowStats[dow] = dowI + } else { + usrDowStats[dow] = 0 + } + } + userstats = append(userstats, UserStats{ + Name: usrName, + Messages: chanMsg, + Hours: usrHourStats, + Dow: usrDowStats, + }) + } + s.ChannelStats = chanstats + s.UserStats = userstats s.TotalChannelMessages = getTotalChannelMsgCount() s.TotalChannels = len(chanlst) + closeDatabase() sc := "var stats = {totalchannelmessages:" sc = fmt.Sprintf("%s%d,", sc, s.TotalChannelMessages) - sc = sc + "\"channels\":[" + sc = sc + "channels:[" for _, k := range s.ChannelStats { sc = fmt.Sprintf("%s{name:\"%s\",member_count:%d,message_count:%d},", sc, @@ -193,7 +285,46 @@ func (wm *generalWebModule) handleStats(w http.ResponseWriter, req *http.Request } // Trim the last , sc = sc[:len(sc)-1] - sc = sc + "]" + sc = sc + "]," + sc = sc + "users:[" + for _, usr := range s.UserStats { + sc = fmt.Sprintf("%s{name:\"%s\",message_count:%d,", + sc, + usr.Name, + usr.Messages, + ) + sc = sc + "messages:{" + sc = sc + "hours:{" + for i, k := range usr.Hours { + sc = sc + fmt.Sprintf("%d:%d,", i, k) + } + sc = sc[:len(sc)-1] + sc = sc + "}," + sc = sc + "dow:{" + for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + sc = sc + fmt.Sprintf("%s:%d,", k, usr.Dow[k]) + } + sc = sc[:len(sc)-1] + sc = sc + "}}" + sc = sc + "}," + } + // Trim the last , + sc = sc[:len(sc)-1] + sc = sc + "]," + // Get all of the message stats + sc = sc + "messages:{" + sc = sc + "hours:{" + for i, k := range s.MessageStats.Hours { + sc = sc + fmt.Sprintf("%d:%d,", i, k) + } + sc = sc[:len(sc)-1] + sc = sc + "}," + sc = sc + "dow:{" + for _, k := range []string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"} { + sc = sc + fmt.Sprintf("%s:%d,", k, s.MessageStats.Dow[k]) + } + sc = sc[:len(sc)-1] + sc = sc + "}}" sc = sc + "};" addToInlineScript(sc) diff --git a/processor_levelup.go b/processor_levelup.go index 69f2f63..ac31321 100644 --- a/processor_levelup.go +++ b/processor_levelup.go @@ -1,6 +1,9 @@ package main -import "net/http" +import ( + "fmt" + "net/http" +) type levelUpStatProcessor struct{} @@ -52,7 +55,7 @@ func (wm *levelUpWebModule) GetName() string { } func (wm *levelUpWebModule) GetRoutes() map[string]func(http.ResponseWriter, *http.Request) { ret := make(map[string]func(http.ResponseWriter, *http.Request)) - //ret["/levelup/"] = wm. + ret["/levelup/"] = wm.handleLevelUpGeneral return ret } func (wm *levelUpWebModule) Register() { @@ -71,6 +74,55 @@ func (wm *levelUpWebModule) GetBottomMenuEntries() []menuItem { } func (wm *levelUpWebModule) handleLevelUpGeneral(w http.ResponseWriter, req *http.Request) { - initRequest(w, req) + type UserLevelUpStats struct { + Name string + Xp int + ChannelStats map[string]int + } + type StatData struct { + UserStats []UserLevelUpStats + } + openDatabase() + userLst := getUserList() + var userStats []UserLevelUpStats + for _, k := range userLst { + usrName := getUserName(k) + if usrName == "stat_bot" || usrName == "bot" { + continue + } + xpVal, _ := getUserStat(k, "levelup-xp") + userStats = append(userStats, UserLevelUpStats{ + Name: usrName, + Xp: xpVal, + ChannelStats: getAllLevelUpChannelXp(k), + }) + } + closeDatabase() + site.TemplateData = StatData{UserStats: userStats} + + initRequest(w, req) + setMenuItemActive("LevelUp!") + + sc := "var levelUpStats = {" + sc = sc + "users:[" + for _, k := range userStats { + sc = sc + "{" + sc = sc + "name:\"" + k.Name + "\"," + sc = fmt.Sprintf("%sxp:%d,", sc, k.Xp) + sc = sc + "channels:[" + if len(k.ChannelStats) > 0 { + for chK, chV := range k.ChannelStats { + sc = fmt.Sprintf("%s{name:\"%s\",xp:%d},", sc, chK, chV) + } + sc = sc[:len(sc)-1] + } + sc = sc + "]}," + } + sc = sc[:len(sc)-1] + sc = sc + "]};" + addToInlineScript(sc) + site.Scripts = append(site.Scripts, "/assets/js/levelup_main.js") + + showPage("levelup-main.html", site, w) } diff --git a/statbot.go b/statbot.go index 9c7a62f..2f2179f 100644 --- a/statbot.go +++ b/statbot.go @@ -87,10 +87,8 @@ func processMessage(slack *Slack, m *Message) { writeToLog(string(mb) + "\n") } */ - // TODO: Handle reaction_added messages - // TODO: Handle reaction_removed messages - if m.Type == "message" { + if m.Type == "message" || m.Type == "reaction_added" { var err error var usr *User diff --git a/statbot_model.go b/statbot_model.go index 82bd9ca..ef2eb05 100644 --- a/statbot_model.go +++ b/statbot_model.go @@ -398,7 +398,6 @@ func addChannelStat(channel string, key string, addVal int) error { openDatabase() v, err := getChannelStat(channel, key) err = saveChannelStat(channel, key, v+addVal) - v, err = getChannelStat(channel, key) closeDatabase() return err } @@ -476,6 +475,9 @@ func getChannelStat(channel string, key string) (int, error) { ret, err = bktGetInt(chSB, key) return err } + fmt.Println("Unable to find channel stats bucket: " + string(channel)) + } else { + fmt.Println("Unable to find channel bucket: " + string(channel)) } return err @@ -828,6 +830,45 @@ func getAllUsersStats() (map[string]int, error) { return ret, err } +func getUserList() []string { + openDatabase() + // First build channel list + var users []string + db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte("users")) + return b.ForEach(func(k, v []byte) error { + users = append(users, string(k)) + return nil + }) + }) + closeDatabase() + return users +} + +func getUserName(uid string) string { + var ret string + openDatabase() + db.View(func(tx *bolt.Tx) error { + var b, uB, uIB *bolt.Bucket + var err error + + b = tx.Bucket([]byte("users")) + if b == nil { + return fmt.Errorf("Error opening 'users' bucket") + } + if uB = b.Bucket([]byte(uid)); uB == nil { + return fmt.Errorf("Error opening user bucket (%s)", uid) + } + if uIB = uB.Bucket([]byte("info")); uIB == nil { + return fmt.Errorf("Error opening user info bucket (%s/info)", uid) + } + ret, err = bktGetString(uIB, "name") + return err + }) + closeDatabase() + return ret +} + func getChannelMessageCount(channel string) int { var ret int openDatabase() diff --git a/statbotweb.go b/statbotweb.go index d285886..3f0d1f4 100644 --- a/statbotweb.go +++ b/statbotweb.go @@ -71,6 +71,7 @@ func statWebMain(slack *Slack) { http.Handle("/assets/", http.StripPrefix("/assets/", assetHandler)) registerWebModule(new(generalWebModule)) + registerWebModule(new(levelUpWebModule)) http.Handle("/", r) go func() { @@ -87,6 +88,9 @@ func initRequest(w http.ResponseWriter, req *http.Request) { site.Scripts = make([]string, 0, 0) site.Scripts = append(site.Scripts, "/assets/js/highcharts.js") + site.Scripts = append(site.Scripts, "/assets/js/B.js") + + site.InlineScript = "" site.Menu = make([]menuItem, 0, 0) site.BottomMenu = make([]menuItem, 0, 0) diff --git a/templates/levelup-main.html b/templates/levelup-main.html new file mode 100644 index 0000000..68df8d7 --- /dev/null +++ b/templates/levelup-main.html @@ -0,0 +1,8 @@ +
+

Level Up!

+
+ + + +
+
diff --git a/templates/stats.html b/templates/stats.html index 7286acf..c3f2477 100644 --- a/templates/stats.html +++ b/templates/stats.html @@ -1,6 +1,24 @@
{{ .TemplateData.Error }}
-
-
-
+
+ + + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+