From ccc103c9864b2c6083cb868e0399f9351dcd5cb9 Mon Sep 17 00:00:00 2001 From: carlos Date: Wed, 5 Mar 2014 18:16:50 +0100 Subject: [PATCH] =?UTF-8?q?Versi=C3=B3n=202.0=20beta=201:=20-=20Nueva=20in?= =?UTF-8?q?terfaz=20unificada=20para=20todas=20las=20plataformas.=20-=20El?= =?UTF-8?q?iminados=20los=20elementos=20multimedia.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base/fs_controller.php | 66 +- config_sample.php | 18 +- controller/comments.php | 28 +- controller/discover_stories.php | 4 +- controller/edit_story.php | 197 +- controller/explore_feed.php | 65 +- controller/feed_list.php | 12 +- controller/feedback.php | 71 + controller/help.php | 59 - controller/home.php | 5 +- controller/last_editions.php | 15 +- controller/new_story.php | 107 + controller/not_found.php | 7 +- controller/popular_stories.php | 7 +- controller/search.php | 12 +- controller/show_edition.php | 114 +- controller/show_story.php | 73 +- controller/stats.php | 275 - controller/suscriptions.php | 18 +- cron.php | 42 +- cron_multicore.php | 45 +- cron_multicore.sh | 3 +- export.php | 2 +- import.php | 24 +- model/comment.php | 27 +- model/feed.php | 135 +- model/feed_story.php | 15 +- model/media_item.php | 736 --- model/my_image.php | 175 - model/story.php | 392 +- model/story_edition.php | 111 +- model/story_media.php | 160 - model/story_visit.php | 27 +- model/suscription.php | 8 +- model/visitor.php | 157 +- sitemap.php | 2 +- view/comments.html | 31 + view/css/bootstrap-theme.css | 347 + view/css/bootstrap-theme.css.map | 1 + view/css/bootstrap-theme.min.css | 7 + view/css/bootstrap.css | 5785 +++++++++++++++++ view/css/bootstrap.css.map | 1 + view/css/bootstrap.min.css | 7 + view/css/custom.css | 81 + view/desktop/comments.html | 26 - view/desktop/desktop.css | 715 -- view/desktop/discover.html | 55 - view/desktop/edit_story.html | 87 - view/desktop/explore_feed.html | 124 - view/desktop/feed_list.html | 78 - view/desktop/footer.html | 41 - view/desktop/header.html | 89 - view/desktop/help.html | 91 - view/desktop/home.html | 52 - view/desktop/last_editions.html | 67 - view/desktop/popular.html | 52 - view/desktop/search.html | 54 - view/desktop/show_edition.html | 107 - view/desktop/show_edition_fp.html | 141 - view/desktop/show_story.html | 133 - view/desktop/show_story_fp.html | 187 - view/desktop/stats.html | 121 - view/desktop/suscriptions.html | 66 - view/discover_stories.html | 41 + view/edit_story.html | 148 + view/explore_feed.html | 97 + view/feed_list.html | 58 + view/feedback.html | 58 + view/fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20335 bytes view/fonts/glyphicons-halflings-regular.svg | 229 + view/fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 41280 bytes view/fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23320 bytes view/footer.html | 24 + view/header.html | 131 + view/home.html | 70 + view/img/close.png | Bin 1749 -> 0 bytes view/img/eso_es.png | Bin 4821 -> 0 bytes view/img/me_gusta.png | Bin 12534 -> 0 bytes view/img/mother_of_god.png | Bin 9453 -> 0 bytes view/img/search.png | Bin 775 -> 0 bytes view/img/troll.png | Bin 11023 -> 0 bytes view/img/truquitos.png | Bin 351415 -> 0 bytes view/img/video.png | Bin 1322 -> 0 bytes view/img/wooow.png | Bin 22042 -> 0 bytes view/js/basic.js | 124 - view/{mobile => }/js/bootstrap.js | 723 +- view/js/bootstrap.min.js | 6 + view/js/jquery.min.js | 4 + view/js/masonry.pkgd.min.js | 9 - view/last_editions.html | 26 + view/mobile/comments.html | 48 - view/mobile/css/bootstrap.css | 4675 ------------- view/mobile/css/bootstrap.min.css | 9 - view/mobile/discover.html | 37 - view/mobile/edit_story.html | 68 - view/mobile/explore_feed.html | 40 - view/mobile/feed_list.html | 53 - view/mobile/footer.html | 17 - view/mobile/header.html | 111 - view/mobile/help.html | 66 - view/mobile/home.html | 37 - .../mobile/img/glyphicons-halflings-white.png | Bin 8777 -> 0 bytes view/mobile/img/glyphicons-halflings.png | Bin 12799 -> 0 bytes view/mobile/js/bootstrap.min.js | 6 - view/mobile/last_editions.html | 34 - view/mobile/popular.html | 37 - view/mobile/search.html | 26 - view/mobile/show_edition_fp.html | 58 - view/mobile/show_story_fp.html | 79 - view/mobile/stats.html | 33 - view/mobile/suscriptions.html | 46 - view/new_story.html | 41 + view/popular_stories.html | 41 + view/redir.html | 11 - view/search.html | 52 + view/show_edition.html | 43 + view/show_story.html | 157 + view/suscriptions.html | 106 + 118 files changed, 8762 insertions(+), 10677 deletions(-) create mode 100644 controller/feedback.php delete mode 100644 controller/help.php create mode 100644 controller/new_story.php delete mode 100644 controller/stats.php delete mode 100644 model/media_item.php delete mode 100644 model/my_image.php delete mode 100644 model/story_media.php create mode 100644 view/comments.html create mode 100644 view/css/bootstrap-theme.css create mode 100644 view/css/bootstrap-theme.css.map create mode 100644 view/css/bootstrap-theme.min.css create mode 100644 view/css/bootstrap.css create mode 100644 view/css/bootstrap.css.map create mode 100644 view/css/bootstrap.min.css create mode 100644 view/css/custom.css delete mode 100644 view/desktop/comments.html delete mode 100644 view/desktop/desktop.css delete mode 100644 view/desktop/discover.html delete mode 100644 view/desktop/edit_story.html delete mode 100644 view/desktop/explore_feed.html delete mode 100644 view/desktop/feed_list.html delete mode 100644 view/desktop/footer.html delete mode 100644 view/desktop/header.html delete mode 100644 view/desktop/help.html delete mode 100644 view/desktop/home.html delete mode 100644 view/desktop/last_editions.html delete mode 100644 view/desktop/popular.html delete mode 100644 view/desktop/search.html delete mode 100644 view/desktop/show_edition.html delete mode 100644 view/desktop/show_edition_fp.html delete mode 100644 view/desktop/show_story.html delete mode 100644 view/desktop/show_story_fp.html delete mode 100644 view/desktop/stats.html delete mode 100644 view/desktop/suscriptions.html create mode 100644 view/discover_stories.html create mode 100644 view/edit_story.html create mode 100644 view/explore_feed.html create mode 100644 view/feed_list.html create mode 100644 view/feedback.html create mode 100644 view/fonts/glyphicons-halflings-regular.eot create mode 100644 view/fonts/glyphicons-halflings-regular.svg create mode 100644 view/fonts/glyphicons-halflings-regular.ttf create mode 100644 view/fonts/glyphicons-halflings-regular.woff create mode 100644 view/footer.html create mode 100644 view/header.html create mode 100644 view/home.html delete mode 100644 view/img/close.png delete mode 100644 view/img/eso_es.png delete mode 100644 view/img/me_gusta.png delete mode 100644 view/img/mother_of_god.png delete mode 100644 view/img/search.png delete mode 100644 view/img/troll.png delete mode 100644 view/img/truquitos.png delete mode 100644 view/img/video.png delete mode 100644 view/img/wooow.png delete mode 100644 view/js/basic.js rename view/{mobile => }/js/bootstrap.js (71%) create mode 100644 view/js/bootstrap.min.js create mode 100644 view/js/jquery.min.js delete mode 100644 view/js/masonry.pkgd.min.js create mode 100644 view/last_editions.html delete mode 100644 view/mobile/comments.html delete mode 100644 view/mobile/css/bootstrap.css delete mode 100644 view/mobile/css/bootstrap.min.css delete mode 100644 view/mobile/discover.html delete mode 100644 view/mobile/edit_story.html delete mode 100644 view/mobile/explore_feed.html delete mode 100644 view/mobile/feed_list.html delete mode 100644 view/mobile/footer.html delete mode 100644 view/mobile/header.html delete mode 100644 view/mobile/help.html delete mode 100644 view/mobile/home.html delete mode 100644 view/mobile/img/glyphicons-halflings-white.png delete mode 100644 view/mobile/img/glyphicons-halflings.png delete mode 100644 view/mobile/js/bootstrap.min.js delete mode 100644 view/mobile/last_editions.html delete mode 100644 view/mobile/popular.html delete mode 100644 view/mobile/search.html delete mode 100644 view/mobile/show_edition_fp.html delete mode 100644 view/mobile/show_story_fp.html delete mode 100644 view/mobile/stats.html delete mode 100644 view/mobile/suscriptions.html create mode 100644 view/new_story.html create mode 100644 view/popular_stories.html delete mode 100644 view/redir.html create mode 100644 view/search.html create mode 100644 view/show_edition.html create mode 100644 view/show_story.html create mode 100644 view/suscriptions.html diff --git a/base/fs_controller.php b/base/fs_controller.php index 8311d50..d59f5e8 100644 --- a/base/fs_controller.php +++ b/base/fs_controller.php @@ -1,7 +1,7 @@ uptime = $tiempo[1] + $tiempo[0]; $this->page = $name; - $this->page_title = $ptitle; $this->title = $title; $this->errors = array(); $this->messages = array(); @@ -54,10 +47,7 @@ public function __construct($name, $ptitle, $title, $template) { $visitor = $this->visitor->get($_COOKIE['key']); if($visitor) - { $this->visitor = $visitor; - $this->visitor->login(); - } else $this->new_error_msg('No se encuentra el usuario.'); } @@ -67,15 +57,15 @@ public function __construct($name, $ptitle, $title, $template) $this->new_message(FS_NAME.' usa cookies propias y de terceros para mejorar tu experiencia de navegación y realizar tareas de análisis. Al continuar con tu navegación entendemos que das tu consentimiento - a nuestra política de cookies.'); + a nuestra política de cookies.'); } + $this->visitor->login(); if( $this->visitor->save() ) - { setcookie('key', $this->visitor->get_id(), time()+FS_MAX_AGE, FS_PATH); - } - $this->set_template($template); + $this->template = $name; + $this->noindex = TRUE; } public function __destruct() @@ -85,7 +75,7 @@ public function __destruct() public function version() { - return '1.4'; + return '2.0b1'; } public function php_version() @@ -98,14 +88,6 @@ public function mongo_version() return $this->mongo->version(); } - protected function set_template($tpl='main') - { - if( !$this->visitor->mobile() ) - $this->template = 'mobile/'.$tpl; - else - $this->template = 'desktop/'.$tpl; - } - public function new_error_msg($msg) { if( $msg ) @@ -151,31 +133,35 @@ public function duration() public function url() { - if(FS_MOD_REWRITE) - return FS_PATH.'/'.$this->page; - else - return FS_PATH.'/index.php?page='.$this->page; + return FS_PATH.'/'.$this->page; } public function domain() { if( mb_substr($_SERVER["SERVER_NAME"], 0, 4) == 'www.') - return 'http://'.$_SERVER["SERVER_NAME"].FS_PATH; + return 'http://'.$_SERVER["SERVER_NAME"]; else - return 'http://www.'.$_SERVER["SERVER_NAME"].FS_PATH; + return 'http://www.'.$_SERVER["SERVER_NAME"]; } - public function page_url($name='') + /// devulve TRUE si el número aleatorio es igual a 0 + public function random($num=9) { - if(FS_MOD_REWRITE) - return FS_PATH.'/'.$name; - else - return FS_PATH.'/index.php?page='.$name; + return mt_rand(0, $num) == 0; } - public function random($num) + public function split_stories(&$stories, $cols=3, $col=1) { - return mt_rand(0, $num) == 0; + $cut = max( array(1, ceil( count($stories)/$cols) ) ); + $list = array(); + + foreach($stories as $i => $value) + { + if($i >= $cut*($col-1) AND $i < $cut*$col) + $list[] = $value; + } + + return $list; } } diff --git a/config_sample.php b/config_sample.php index 0e20432..759f4be 100644 --- a/config_sample.php +++ b/config_sample.php @@ -23,16 +23,11 @@ define('FS_MONGO_HOST', 'localhost'); define('FS_MONGO_DBNAME', 'ponme_un_nombre'); -/// Número de historias máximo para cada feed y para la portada. +/// Número de artículos en portada, búsquedas, etc... define('FS_MAX_STORIES', 50); /* * Caducidad de los elementos, en segundos. - * Se eliminarán: - * - los usuarios que no hayan vuelto en un máximo de FS_MAX_AGE segundos. - * - las historias con una edad superior a FS_MAX_AGE segundos. - * - las ediciones con una edad superior a FS_MAX_AGE segundos. - * - los elementos multimedia con una edad superior a FS_MAX_AGE segundos. */ define('FS_MAX_AGE', 5184000); @@ -43,14 +38,6 @@ */ define('FS_TIMEOUT', 10); -/* - * Cuando se comprueba una fuente, se extraen las historias, y para - * cada una se buscan imáganes asociadas. Pues esta constante - * define el número máximo de imáganes descargadas de una sóla vez. - * Cuanto mayor el número, más tarda. - */ -define('FS_MAX_DOWNLOADS', 10); - /* * Contraseña maestra, necesaria para tareas como eliminar fuentes. */ @@ -62,7 +49,4 @@ */ define('FS_DEBUG', FALSE); -/// ¿Tienes activado el mod_rewrite de apache? -define('FS_MOD_REWRITE', FALSE); - ?> \ No newline at end of file diff --git a/controller/comments.php b/controller/comments.php index 2c85410..49ccf09 100644 --- a/controller/comments.php +++ b/controller/comments.php @@ -1,7 +1,7 @@ comment = new comment(); - $this->txt_comment = '¡Escribe algo!'; + $comment = new comment(); + $this->comments = $comment->all(); - if( isset($_POST['comment']) ) - { - if($this->visitor->human() AND $_POST['human'] == '' ) - { - $comment2 = new comment(); - $comment2->nick = $this->visitor->nick; - $comment2->text = $_POST['comment']; - $comment2->save(); - } - else - { - $this->new_error_msg('Tienes que borrar el número para demostrar que eres humano.'); - $this->txt_comment = $_POST['comment']; - } - } + if( count($this->comments) == 0 ) + $this->new_message('Aun no hay comentarios :-('); } public function get_description() diff --git a/controller/discover_stories.php b/controller/discover_stories.php index 0e25b5b..b4c0af4 100644 --- a/controller/discover_stories.php +++ b/controller/discover_stories.php @@ -1,7 +1,7 @@ stories = array(); diff --git a/controller/edit_story.php b/controller/edit_story.php index 409ae1a..a315239 100644 --- a/controller/edit_story.php +++ b/controller/edit_story.php @@ -1,7 +1,7 @@ story_edition = new story_edition(); $this->story_visit = new story_visit(); - $this->masterkey = ''; - if( isset($_COOKIE['masterkey']) ) - { - $this->masterkey = $_COOKIE['masterkey']; - } - else if( isset($_POST['masterkey']) ) - { - if($_POST['masterkey'] == FS_MASTER_KEY AND FS_MASTER_KEY != '') - { - $this->masterkey = $_POST['masterkey']; - setcookie('masterkey', $this->masterkey, time()+86400, FS_PATH); - } - } - if( isset($_GET['id']) ) { $story = new story(); @@ -58,133 +42,127 @@ public function __construct() else $this->story = FALSE; - if($this->story) + + if($this->story AND isset($_POST['delete']) AND $this->visitor->admin) + { + $this->story->delete(); + $this->story = FALSE; + $this->new_message('Artículo eliminado correctamente.'); + } + else if($this->story) { - if( $this->masterkey != '' OR $this->visitor->human() ) + if( $this->visitor->human() ) { + $new_edition = FALSE; + $fake_edition = TRUE; + $se0 = $this->story_edition->get_by_params($this->story->get_id(), $this->visitor->get_id()); if( $se0 ) $this->story_edition = $se0; else { + $this->story_edition->nick = $this->visitor->nick; $this->story_edition->description = $this->story->description; + $this->story_edition->points = $this->visitor->points; $this->story_edition->story_id = $this->story->get_id(); - $this->story_edition->media_id = $this->story->media_id; $this->story_edition->title = $this->story->title; $this->story_edition->visitor_id = $this->visitor->get_id(); + $new_edition = TRUE; } if( isset($_POST['title']) AND isset($_POST['description']) AND isset($_POST['human']) ) { + if($_POST['title'] != $this->story_edition->title AND $_POST['description'] != $this->story_edition->description) + $fake_edition = FALSE; + $this->story_edition->title = $_POST['title']; $this->story_edition->description = $_POST['description']; - if( !isset($_POST['media_id']) ) - $this->story_edition->media_id = NULL; - else if($_POST['media_id'] == 'none' OR isset($_POST['noimages']) ) - $this->story_edition->media_id = NULL; - else - $this->story_edition->media_id = $_POST['media_id']; - /// otra comprobación más para evitar el spam if( strstr($_POST['description'], 'masterkey == '' AND $_POST['human'] != '' ) + else if( $_POST['human'] != '' AND !$this->visitor->admin ) $this->new_error_msg('Tienes que borrar el número para demostrar que eres humano, y si no eres - humano no puedes editar historias. Y si, ya sé que esto es nazismo puro, + humano no puedes editar artículos. Y si, ya sé que esto es nazismo puro, pero es una forma sencilla de atajar el SPAM.'); else { $this->story_edition->save(); - $this->new_message('Historia editada correctamente. Hac clic aquí para verla. Recuerda que - aparecerá en la sección ediciones.'); + $this->new_message('Artículo editado correctamente.'); - if($this->masterkey != '') + if($this->visitor->admin) { + $this->story->edition_id = $this->story_edition->get_id(); $this->story->title = $this->story_edition->title; $this->story->description = $this->story_edition->description; - $this->story->native_lang = TRUE; - if( isset($_POST['noimages']) ) + $nkeywords = mb_strtolower( trim($_POST['keywords']), 'utf8' ); + if($nkeywords != $this->story->keywords) { - $this->story->noimages = TRUE; - $this->story->media_id = NULL; - - $sm = new story_media(); - foreach($sm->all4story($this->story->get_id()) as $sm0) + $this->story->keywords = $nkeywords; + if($this->story->keywords != '') { - $sm0->delete(); - $mi = $sm0->media_item(); - if($mi) - $mi->delete(); - } - } - else - $this->story->noimages = FALSE; - - $this->story->keywords = strtolower($_POST['keywords']); - - if( substr($_POST['related'], 0, 7) == 'http://' ) - { - $aux = explode('/', $_POST['related']); - $related = $this->story->get($aux[ count($aux)-1 ]); - if($related) - $this->story->related_id = $related->get_id(); - } - else if( isset($_POST['norelated']) ) - { - $this->story->related_id = NULL; - } - else if( !isset($this->story->related_id) AND $this->story->keywords != '' ) - { - $aux = explode(',', $this->story->keywords); - $keyword = trim($aux[0]); - $relateds = $this->story->search($keyword); - - for($i = 0; $i < count($relateds); $i++) - { - if( !isset($this->story->related_id) AND $relateds[$i]->date < $this->story->date AND $relateds[$i]->native_lang ) - $this->story->related_id = $relateds[$i]->get_id(); - - if( $relateds[$i]->get_id() != $this->story->get_id() ) + /// añadimos las keyword a todas las noticias de la búsqueda + $kwlist = explode(',', $this->story->keywords); + $relateds = $this->story->search($kwlist[0]); + for($i = 0; $i < count($relateds); $i++) { - $relateds[$i]->add_keyword($keyword); - - for($j = 0; $j < count($relateds); $j++) + if( $relateds[$i]->get_id() != $this->story->get_id() ) { - if( !isset($relateds[$i]->related_id) AND $relateds[$j]->date < $relateds[$i]->date AND $relateds[$j]->native_lang ) + foreach($kwlist as $kw) { - $relateds[$i]->related_id = $relateds[$j]->get_id(); - break; + if( preg_match('/\b'.$kw.'\b/iu', $relateds[$i]->title) ) + $relateds[$i]->add_keyword($kw); } + + $relateds[$i]->save(); } - - $relateds[$i]->save(); } } } + $this->story->native_lang = isset($_POST['native_lang']); + $this->story->parody = isset($_POST['parody']); + + if( isset($_POST['featured']) ) + { + $this->story->featured = TRUE; + $this->story->penalize = FALSE; + $this->story->published = time(); + } + else if( isset($_POST['penalize']) ) + { + $this->story->penalize = TRUE; + $this->story->featured = FALSE; + } + + if($new_edition) + $this->story->num_editions++; + $this->story->save(); } - else if($_POST['masterkey']) - $this->new_error_msg('Contraseña incorrecta.'); - - $this->select_best_image4story(); - - /// ¿La noticia está en otro idioma? - if( !$this->story->native_lang ) + else if( !$this->story->native_lang AND !$fake_edition ) /// ¿La noticia está en otro idioma? { + $this->story->edition_id = $this->story_edition->get_id(); $this->story->title = $this->story_edition->title; $this->story->description = $this->story_edition->description; $this->story->native_lang = TRUE; + + if($new_edition) + $this->story->num_editions++; + $this->story->save(); } + else + $this->set_best_edition(); /// actualizamos al visitante - $this->visitor->human = TRUE; - $this->visitor->need_save = TRUE; - $this->visitor->save(); + if($new_edition) + { + $this->visitor->num_editions++; + $this->visitor->need_save = TRUE; + $this->visitor->save(); + } } } @@ -205,7 +183,7 @@ public function __construct() } } else - $this->new_error_msg('Historia no encontrada.'); + $this->new_error_msg('Artículo no encontrado.'); } public function url() @@ -224,20 +202,27 @@ public function get_description() return parent::get_description(); } - private function select_best_image4story() + private function set_best_edition() { - if( !$this->story->noimages ) + $edition = NULL; + $num_editions = 0; + + foreach($this->story->editions() as $edi) { - /// Elegimos la foto de la edición más votada de esta historia - $maxvotes = 0; - foreach($this->story->editions() as $edi) - { - if($edi->votes > $maxvotes) - { - $maxvotes = $edi->votes; - $this->story->media_id = $edi->media_id; - } - } + if( is_null($edition) ) + $edition = $edi; + else if($edi->points > $edition->points) + $edition = $edi; + + $num_editions++; + } + + if( isset($edition) ) + { + $this->story->edition_id = $edition->get_id(); + $this->story->title = $edition->title; + $this->story->description = $edition->description; + $this->story->num_editions = $num_editions; $this->story->save(); } } diff --git a/controller/explore_feed.php b/controller/explore_feed.php index 24e6d18..5239613 100644 --- a/controller/explore_feed.php +++ b/controller/explore_feed.php @@ -1,7 +1,7 @@ stories = array(); - if( isset($_GET['id']) ) + if( isset($_POST['modify']) AND $this->visitor->admin ) { $this->feed = $feed->get($_GET['id']); - - if( isset($_GET['mkey']) ) - { - if($_GET['mkey'] == FS_MASTER_KEY AND FS_MASTER_KEY != '') - { - if( isset($_GET['delete']) ) - { - $this->feed->delete(); - $this->new_message('Fuente eliminada correctamente.'); - $this->feed = FALSE; - } - else if( isset($_GET['native_lang']) ) - { - $this->feed->native_lang = ($_GET['native_lang'] == 'TRUE'); - $this->feed->save(); - $this->new_message("Fuente modificada correctamente."); - } - } - else - $this->new_error_msg('Clave incorrecta.'); - } + $this->feed->native_lang = isset($_POST['native_lang']); + $this->feed->parody = isset($_POST['parody']); + $this->feed->penalize = isset($_POST['penalize']); + $this->feed->save(); + $this->new_message('Fuente modificada correctamente.'); } + else if( isset($_GET['id']) ) + $this->feed = $feed->get($_GET['id']); else $this->feed = FALSE; - if($this->feed) + + if($this->feed AND isset($_POST['delete']) AND $this->visitor->admin) + { + $this->feed->delete(); + $this->feed = FALSE; + $this->new_message('Fuente eliminada correctamente.'); + } + else if($this->feed) { $this->title = $this->feed->name.' ‹ '.FS_NAME; $this->stories = $this->feed->stories(); @@ -73,13 +66,13 @@ public function __construct() $suscription->feed_id = $this->feed->get_id(); if( $suscription->exists() ) { - $this->suscribe_url = 'index.php?page=suscriptions&unsuscribe='.$suscription->get_id(); + $this->suscribe_url = FS_PATH.'/index.php?page=suscriptions&unsuscribe='.$suscription->get_id(); $this->suscribe_text = 'Anular suscripción'; $this->unsuscribe = TRUE; } else { - $this->suscribe_url = 'index.php?page=suscriptions&suscribe='.$this->feed->get_id(); + $this->suscribe_url = FS_PATH.'/index.php?page=suscriptions&suscribe='.$this->feed->get_id(); $this->suscribe_text = 'Suscribirse'; $this->unsuscribe = FALSE; } @@ -103,24 +96,6 @@ public function get_description() else return parent::get_description(); } - - public function twitter_url() - { - if($this->feed) - return 'https://twitter.com/share?url='.urlencode( $this->domain().'/'.$this->feed->url() ). - '&text='.urlencode($this->feed->name); - else - return 'https://twitter.com/share'; - } - - public function facebook_url() - { - if($this->feed) - return 'http://www.facebook.com/sharer.php?s=100&p[title]='.urlencode($this->feed->name). - '&p[url]='.urlencode( $this->domain().'/'.$this->feed->url() ); - else - return 'http://www.facebook.com/sharer.php'; - } } ?> \ No newline at end of file diff --git a/controller/feed_list.php b/controller/feed_list.php index 1015c03..481a654 100644 --- a/controller/feed_list.php +++ b/controller/feed_list.php @@ -1,7 +1,7 @@ noindex = FALSE; $this->feed = new feed(); if( isset($_POST['feed_url']) AND $this->visitor->human() ) @@ -51,9 +52,11 @@ public function __construct() $feed0->save(); } else if( $_POST['human'] != '' ) + { $this->new_error_msg('No has borrado el número para demostrar que eres humano, y si no eres humano no puedes añadir fuentes. Y si, ya sé que esto es nazismo puro, pero es una forma sencilla de atajar el SPAM.'); + } else { $this->feed->url = $_POST['feed_url']; @@ -76,11 +79,6 @@ public function __construct() /// actualizamos el número de suscriptores $this->feed->suscriptors++; $this->feed->save(); - - /// actualizamos al visitante - $this->visitor->human = TRUE; - $this->visitor->need_save = TRUE; - $this->visitor->save(); } } } diff --git a/controller/feedback.php b/controller/feedback.php new file mode 100644 index 0000000..a853cdb --- /dev/null +++ b/controller/feedback.php @@ -0,0 +1,71 @@ +. + */ + +require_once 'model/comment.php'; + +class feedback extends fs_controller +{ + public $comments; + public $txt_comment; + public $email; + + public function __construct() + { + parent::__construct('feedback', 'Feedback ‹ '.FS_NAME); + + $comment = new comment(); + $this->comments = $comment->all4thread(); + + if( isset($_POST['comment']) ) + $this->txt_comment = $_POST['comment']; + else + $this->txt_comment = ''; + + if( isset($_POST['email']) ) + $this->email = $_POST['email']; + else + $this->email = ''; + + if( isset($_POST['human']) ) + { + if($_POST['human'] == '') + { + if($this->email == '') + $comment->nick = $this->visitor->nick; + else + $comment->nick = $this->email; + $comment->text = $this->txt_comment; + $comment->save(); + + $this->new_message('Mensaje enviado correctamente.'); + $this->txt_comment = ''; + $this->email = ''; + } + else + $this->new_error_msg('Tienes que borrar el número para demostrar que eres humano.'); + } + } + + public function get_description() + { + return 'Feedback de '.FS_NAME.'.'; + } +} + +?> \ No newline at end of file diff --git a/controller/help.php b/controller/help.php deleted file mode 100644 index 1cc6fb1..0000000 --- a/controller/help.php +++ /dev/null @@ -1,59 +0,0 @@ -. - */ - -require_once 'model/feed.php'; - -class help extends fs_controller -{ - public $feed; - - public function __construct() - { - parent::__construct('help', 'Ayuda', 'Ayuda ‹ '.FS_NAME, 'help'); - - $this->feed = new feed(); - } - - public function get_description() - { - return 'Ayuda de '.FS_NAME.'. Qué es, cómo funciona qué hay de nuevo, truqitos, etc...'; - } - - public function show_max_age() - { - $time = FS_MAX_AGE; - - if($time <= 60) - return $time.' segundos'; - else if(60 < $time && $time <= 3600) - return round($time/60,0).' minutos'; - else if(3600 < $time && $time <= 86400) - return round($time/3600,0).' horas'; - else if(86400 < $time && $time <= 604800) - return round($time/86400,0).' dias'; - else if(604800 < $time && $time <= 2592000) - return round($time/604800,0).' semanas'; - else if(2592000 < $time && $time <= 29030400) - return round($time/2592000,0).' meses'; - else if($time > 29030400) - return 'mucho tiempo'; - } -} - -?> \ No newline at end of file diff --git a/controller/home.php b/controller/home.php index 6c66b33..af117c5 100644 --- a/controller/home.php +++ b/controller/home.php @@ -1,7 +1,7 @@ noindex = FALSE; $this->stories = $this->visitor->last_stories(); } } diff --git a/controller/last_editions.php b/controller/last_editions.php index 5f823f6..ed8841b 100644 --- a/controller/last_editions.php +++ b/controller/last_editions.php @@ -1,7 +1,7 @@ show_info = FALSE; - setcookie('editions_info', 'FALSE', time()+315360000, FS_PATH); - } - else - $this->show_info = !isset($_COOKIE['editions_info']); + parent::__construct('last_editions', 'Ediciones ‹ '.FS_NAME); $se = new story_edition(); $this->editions = $se->last_editions(); @@ -42,7 +33,7 @@ public function __construct() public function get_description() { - return 'Últimas modificaciones realizadas por los usuarios. Historias corregidas o mejoradas.'; + return 'Últimas modificaciones realizadas por los usuarios. Artículos corregidos o mejorados.'; } } diff --git a/controller/new_story.php b/controller/new_story.php new file mode 100644 index 0000000..1b50ce7 --- /dev/null +++ b/controller/new_story.php @@ -0,0 +1,107 @@ +. + */ + +require_once 'model/story.php'; +require_once 'model/story_edition.php'; + +class new_story extends fs_controller +{ + public $s_title; + public $s_description; + public $s_link; + + public function __construct() + { + parent::__construct('new_story', 'Escribir ‹ '.FS_NAME); + + if( isset($_POST['title']) ) + $this->s_title = $_POST['title']; + + if( isset($_POST['description']) ) + $this->s_description = $_POST['description']; + + if( isset($_POST['link']) ) + $this->s_link = $_POST['link']; + + if( isset($_POST['human']) AND $this->visitor->human() ) + { + if($_POST['human'] == '' OR $this->visitor->admin) + { + if( mb_strlen($_POST['title']) > 5 AND mb_strlen($_POST['description']) > 50 ) + { + $story = new story(); + $story->title = $_POST['title']; + $story->description = $_POST['description']; + + if($_POST['link'] == '') + { + $this->save_story_and_more($story); + } + else + { + $story2 = $story->get_by_link($_POST['link']); + if($story2) + { + $this->new_error_msg('Ya han enviado este enlace, puedes ver el artículo aquí.'); + } + else + { + $story->link = $_POST['link']; + $this->save_story_and_more($story); + } + } + } + else + $this->new_error_msg('Tienes que escribir más...'); + } + else + $this->new_error_msg('Tienes que borrar el número para demostrar que eres humano.'); + } + } + + private function save_story_and_more(&$story) + { + $story->save(); + + /// guardamos una edicion para saber el usuario y la ip + $se = new story_edition(); + $se->story_id = $story->get_id(); + $se->visitor_id = $this->visitor->get_id(); + $se->nick = $this->visitor->nick; + $se->title = $story->title; + $se->description = $story->description; + $se->points = $this->visitor->points; + $se->save(); + + /// enlazamos el artículo con la edición + $story->num_editions = 1; + $story->edition_id = $se->get_id(); + $story->save(); + + /// ahora actualizamos al usuario + $this->visitor->num_stories++; + $this->visitor->num_editions++; + $this->visitor->need_save = TRUE; + $this->visitor->save(); + + header( 'Location: '.$story->url() ); + } +} + +?> \ No newline at end of file diff --git a/controller/not_found.php b/controller/not_found.php index 4299b88..0e21da2 100644 --- a/controller/not_found.php +++ b/controller/not_found.php @@ -1,7 +1,7 @@ template = 'home'; $this->new_error_msg('¡Página no encontrada! Usa el buscador.'); $story = new story(); @@ -35,7 +36,7 @@ public function __construct() public function get_description() { - return 'Historia no encontrada en '.FS_NAME.'. ¡Usa el buscador! A Ver si tienes más suerte.'; + return 'Artículo no encontrado en '.FS_NAME.'. ¡Usa el buscador! A Ver si tienes más suerte.'; } } diff --git a/controller/popular_stories.php b/controller/popular_stories.php index 4f2d509..c8bc91f 100644 --- a/controller/popular_stories.php +++ b/controller/popular_stories.php @@ -1,7 +1,7 @@ noindex = FALSE; $story = new story(); - $this->stories = $story->popular_stories(); + $this->stories = $story->published_stories(); } public function get_description() diff --git a/controller/search.php b/controller/search.php index 38d753c..fd0369f 100644 --- a/controller/search.php +++ b/controller/search.php @@ -1,7 +1,7 @@ query = ''; $this->stories = array(); @@ -40,15 +40,17 @@ public function __construct() $this->stories = $story->search($this->query); if( count($this->stories) == 0 ) - $this->new_message('Sin resultados!'); + $this->new_message('Sin resultados'); } + else + $this->new_error_msg('Demasiado corto'); } } public function get_description() { - return 'El buscador de '.FS_NAME.'. Si no encuentras una historia aquí es porque - ya no es popular XD.'; + return 'El buscador de '.FS_NAME.'. Si no encuentras un artículo aquí es porque + es irrelevante XD.'; } } diff --git a/controller/show_edition.php b/controller/show_edition.php index 13ca43b..0e5141f 100644 --- a/controller/show_edition.php +++ b/controller/show_edition.php @@ -1,7 +1,7 @@ set_template('show_edition_fp'); + parent::__construct('show_edition', 'Edición...'); $se = new story_edition(); - + $this->edition = FALSE; if( isset($_GET['id']) ) { - $se2 = $se->get($_GET['id']); - if($se2) + $this->edition = $se->get($_GET['id']); + if($this->edition) { - /// si la historia ya no existe, borramos la edición - if($se2->story) - $this->edition = $se2; - else - $se2->delete(); + $this->story = $this->edition->story(); + if(!$this->story) + { + $this->edition->delete(); + $this->edition = FALSE; + } } } - else - $this->edition = FALSE; - if($this->edition) + if($this->edition AND isset($_POST['delete']) AND $this->visitor->admin) + { + $this->edition->delete(); + $this->edition = FALSE; + $this->new_message('Edición eliminada correctamente.'); + } + else if($this->edition) { $this->title = $this->edition->title . ' (edición)'; - if( !$this->edition->story->readed() AND $this->visitor->human() AND isset($_SERVER['REMOTE_ADDR']) ) - { - $this->edition->story->read(); - - if( isset($_GET['vote']) ) - { - $story_visit = new story_visit(); - $sv0 = $story_visit->get_by_params($this->edition->story_id, $_SERVER['REMOTE_ADDR']); - if( $sv0 ) - { - if( is_null($sv0->edition_id) ) - { - $sv0->edition_id = $this->edition->get_id(); - $sv0->save(); - $this->edition->votes++; - $this->edition->save(); - } - } - else - { - $story_visit->visitor_id = $this->visitor->get_id(); - $story_visit->story_id = $this->edition->story_id; - $story_visit->edition_id = $this->edition->get_id(); - $story_visit->save(); - $this->edition->story->clics++; - $this->edition->story->save(); - $this->edition->votes++; - $this->edition->save(); - } - } - } + if( !$this->story->readed() AND $this->visitor->human() AND isset($_SERVER['REMOTE_ADDR']) ) + $this->story->read(); } else $this->new_error_msg('Edición no encontrada. Usa el buscador.'); - - if( isset($_POST['popup']) OR $this->visitor->mobile() ) - $this->editions = array(); - else - { - $this->editions = $se->last_editions(5); - - if($this->edition) - { - /// excluimos la edición actual - foreach($this->editions as $i => $value) - { - if( $value->get_id() == $this->edition->get_id() ) - unset($this->editions[$i]); - } - } - } } public function url() @@ -122,32 +78,6 @@ public function get_description() else return parent::get_description(); } - - public function twitter_url() - { - if($this->edition) - return 'https://twitter.com/share?url='.urlencode( $this->domain().'/'.$this->edition->url(FALSE) ). - '&text='.urlencode($this->edition->title); - else - return 'https://twitter.com/share'; - } - - public function facebook_url() - { - if($this->edition) - return 'http://www.facebook.com/sharer.php?s=100&p[title]='.urlencode($this->edition->title). - '&p[url]='.urlencode( $this->domain().'/'.$this->edition->url(FALSE) ); - else - return 'http://www.facebook.com/sharer.php'; - } - - public function plusone_url() - { - if($this->edition) - return 'https://plus.google.com/share?url='.urlencode( $this->domain().'/'.$this->edition->url(FALSE) ); - else - return 'https://plus.google.com/share'; - } } ?> \ No newline at end of file diff --git a/controller/show_story.php b/controller/show_story.php index fbfbf4f..9cd3045 100644 --- a/controller/show_story.php +++ b/controller/show_story.php @@ -1,7 +1,7 @@ template = 'redir'; - else if( !isset($_POST['popup']) ) - $this->set_template('show_story_fp'); + parent::__construct('show_story', 'Artículo...'); $story = new story(); @@ -49,6 +43,9 @@ public function __construct() $this->title = $this->story->title; $this->comments = $this->comments(); + if($this->story->published) + $this->noindex = FALSE; + if( !$this->story->readed() AND $this->visitor->human() AND isset($_SERVER['REMOTE_ADDR']) ) { $this->story->read(); @@ -64,9 +61,14 @@ public function __construct() $this->story->save(); } } + + if(count($this->get_errors()) + count($this->get_errors()) == 0 AND mt_rand(0, 3) == 0) + { + $this->new_message('Si tienes más información o hay algún error en el artículo, no lo dudes, haz clic en la pestaña editar.'); + } } else - $this->new_error_msg('Historia no encontrada. Usa el buscador.'); + $this->new_error_msg('Artículo no encontrado. Usa el buscador.'); } public function url() @@ -88,7 +90,7 @@ public function full_url() public function get_description() { if($this->story) - return $this->story->description; + return $this->story->description(); else return parent::get_description(); } @@ -105,12 +107,14 @@ public function twitter_url() { if($this->story) { - if(mt_rand(0, 1) == 0) - return 'https://twitter.com/share?url='.urlencode($this->full_url()). - '&text='.urlencode($this->story->title); - else - return 'https://twitter.com/share?url='.urlencode($this->story->link). + $url = 'https://twitter.com/share?url='.urlencode( $this->full_url() ). + '&text='.urlencode($this->story->title); + if( isset($this->story->link) AND mt_rand(0, 1) == 0 ) + { + $url = 'https://twitter.com/share?url='.urlencode($this->story->link). '&text='.urlencode($this->story->title); + } + return $url; } else return 'https://twitter.com/share'; @@ -120,12 +124,14 @@ public function facebook_url() { if($this->story) { - if(mt_rand(0, 1) == 0) - return 'http://www.facebook.com/sharer.php?s=100&p[title]='.urlencode($this->story->title). - '&p[url]='.urlencode($this->full_url()); - else - return 'http://www.facebook.com/sharer.php?s=100&p[title]='.urlencode($this->story->title). + $url = 'http://www.facebook.com/sharer.php?s=100&p[title]='.urlencode($this->story->title). + '&p[url]='.urlencode( $this->full_url() ); + if( isset($this->story->link) AND mt_rand(0, 1) == 0 ) + { + $url = 'http://www.facebook.com/sharer.php?s=100&p[title]='.urlencode($this->story->title). '&p[url]='.urlencode($this->story->link); + } + return $url; } else return 'http://www.facebook.com/sharer.php'; @@ -135,10 +141,12 @@ public function plusone_url() { if($this->story) { - if(mt_rand(0, 1) == 0) - return 'https://plus.google.com/share?url='.urlencode($this->full_url()); - else - return 'https://plus.google.com/share?url='.urlencode($this->story->link); + $url = 'https://plus.google.com/share?url='.urlencode( $this->full_url() ); + if( isset($this->story->link) AND mt_rand(0, 1) == 0 ) + { + $url = 'https://plus.google.com/share?url='.urlencode($this->story->link); + } + return $url; } else return 'https://plus.google.com/share'; @@ -148,21 +156,21 @@ private function comments() { $comment = new comment(); $this->txt_comment = ''; - $all_comments = $comment->all4thread( $this->story->get_id() ); + $all_comments = $this->story->comments(); if( isset($_POST['comment']) ) { - if($this->visitor->human() AND $_POST['human'] == '' ) + if($this->visitor->human() AND ($_POST['human'] == '' OR $this->visitor->admin) ) { $comment = new comment(); $comment->thread = $this->story->get_id(); $comment->nick = $this->visitor->nick; $comment->text = $_POST['comment']; $comment->save(); - array_unshift($all_comments, $comment); + $all_comments[] = $comment; /// actualizamos al visitante - $this->visitor->human = TRUE; + $this->visitor->num_comments++; $this->visitor->need_save = TRUE; $this->visitor->save(); } @@ -173,7 +181,7 @@ private function comments() } } - return array_reverse($all_comments); + return $all_comments; } public function related_stories() @@ -194,6 +202,13 @@ public function related_stories() break; } + if( count($stories) == 0 ) + { + $story = $this->story->pre_related_story(); + if($story) + $stories[] = $story; + } + return $stories; } } diff --git a/controller/stats.php b/controller/stats.php deleted file mode 100644 index dc34bbe..0000000 --- a/controller/stats.php +++ /dev/null @@ -1,275 +0,0 @@ -. - */ - -require_once 'model/feed.php'; -require_once 'model/feed_story.php'; -require_once 'model/media_item.php'; -require_once 'model/story.php'; -require_once 'model/story_edition.php'; -require_once 'model/story_media.php'; -require_once 'model/story_visit.php'; -require_once 'model/suscription.php'; -require_once 'model/visitor.php'; - -class GooglePageRank { - - var $_GOOGLE_MAGIC = 0xE6359A60; - var $_url = ''; - var $_checksum = ''; - - function GooglePageRank($url) - { - $this->_url = $url; - } - - function _strToNum($Str, $Check, $Magic) - { - $Int32Unit = 4294967296; - - $length = strlen($Str); - for ($i = 0; $i < $length; $i++) { - $Check *= $Magic; - - if ($Check >= $Int32Unit) { - $Check = ($Check - $Int32Unit * (int) ($Check / $Int32Unit)); - $Check = ($Check < -2147483647) ? ($Check + $Int32Unit) : $Check; - } - $Check += ord($Str{$i}); - } - return $Check; - } - - function _hashURL($String) - { - $Check1 = $this->_strToNum($String, 0x1505, 0x21); - $Check2 = $this->_strToNum($String, 0, 0x1003F); - - $Check1 >>= 2; - $Check1 = (($Check1 >> 4) & 0x3FFFFC0 ) | ($Check1 & 0x3F); - $Check1 = (($Check1 >> 4) & 0x3FFC00 ) | ($Check1 & 0x3FF); - $Check1 = (($Check1 >> 4) & 0x3C000 ) | ($Check1 & 0x3FFF); - - $T1 = (((($Check1 & 0x3C0) << 4) | ($Check1 & 0x3C)) <<2 ) | ($Check2 & 0xF0F ); - $T2 = (((($Check1 & 0xFFFFC000) << 4) | ($Check1 & 0x3C00)) << 0xA) | ($Check2 & 0xF0F0000 ); - - return ($T1 | $T2); - } - - function checksum() - { - if($this->_checksum != '') return $this->_checksum; - - $Hashnum = $this->_hashURL($this->_url); - - $CheckByte = 0; - $Flag = 0; - - $HashStr = sprintf('%u', $Hashnum) ; - $length = strlen($HashStr); - - for ($i = $length - 1; $i >= 0; $i --) { - $Re = $HashStr{$i}; - if (1 == ($Flag % 2)) { - $Re += $Re; - $Re = (int)($Re / 10) + ($Re % 10); - } - $CheckByte += $Re; - $Flag ++; - } - - $CheckByte %= 10; - if (0 !== $CheckByte) { - $CheckByte = 10 - $CheckByte; - if (1 === ($Flag%2) ) { - if (1 === ($CheckByte % 2)) { - $CheckByte += 9; - } - $CheckByte >>= 1; - } - } - - $this->_checksum = '7'.$CheckByte.$HashStr; - return $this->_checksum; - } - - function pageRankUrl($dcchosen) - { - return $dcchosen . 'tbr?client=navclient-auto&features=Rank:&q=info:'.$this->_url.'&ch='.$this->checksum(); - } - - function getPageRank($dcchosen) - { - $fh = @fopen($this->pageRankUrl($dcchosen), "r"); - if($fh) - { - $contenido = ''; - while (!feof($fh)) { - $contenido .= fread($fh, 8192); - } - fclose($fh); - ltrim($contenido); - rtrim($contenido); - $contenido=str_replace("Rank_1:1:","",$contenido); - $contenido=str_replace("Rank_1:2:","",$contenido); - //$contenido=intval($contenido); - $contenido=intval($contenido); - - if(is_numeric($contenido)) - return $contenido; - else - return -2; - } - return -1; - } - -} - -class stats extends fs_controller -{ - public $feed; - public $feed_story; - public $media_item; - public $story; - public $story_edition; - public $story_media; - public $story_visit; - public $suscription; - - public $showing; - - public function __construct() - { - parent::__construct('stats', 'Estadísticas', 'Estadísticas ‹ '.FS_NAME, 'stats'); - - $this->feed = new feed(); - $this->feed_story = new feed_story(); - $this->media_item = new media_item(); - $this->story = new story(); - $this->story_edition = new story_edition(); - $this->story_media = new story_media(); - $this->story_visit = new story_visit(); - $this->suscription = new suscription(); - - $this->showing = 'visits'; - if( isset($_GET['showing']) ) - $this->showing = $_GET['showing']; - } - - public function get_description() - { - return 'Estadísticas de '.FS_NAME.'. Número de fuentes, de historias, visitas, usuarios - y un largo etcétera.'; - } - - public function tmp_size($path='tmp', $show_units=TRUE) - { - $total_size = 0; - $files = scandir($path); - - foreach($files as $t) - { - if(is_dir(rtrim($path, '/') . '/' . $t)) - { - if($t<>"." && $t<>"..") - { - $size = $this->tmp_size( rtrim($path, '/').'/'.$t, FALSE ); - $total_size += $size; - } - } - else - { - $size = filesize( rtrim($path, '/').'/'.$t ); - $total_size += $size; - } - } - - if($show_units) - { - $mod = 1024; - $units = explode(' ','B KB MB GB TB PB'); - - for($i = 0; $total_size > $mod; $i++) - $total_size /= $mod; - - return round($total_size, 2) . ' ' . $units[$i]; - } - else - return $total_size; - } - - public function analyze_visits() - { - if( isset($_SERVER['REMOTE_ADDR']) ) - $ip = $_SERVER['REMOTE_ADDR']; - else - $ip = 'unknown'; - - $visits = $this->story_visit->last(FS_MAX_STORIES * 4, $ip); - $aux = array(); - - foreach($visits as $i => $value) - { - if( array_key_exists($value->story_id, $aux) ) - { - $aux[$value->story_id]['visits']++; - $aux[$value->story_id]['date'] = $value->date; - } - else - { - $aux[$value->story_id] = array( - 'visits' => 1, - 'date' => $value->date - ); - } - } - - arsort($aux); - - $stlist = array(); - $n = 0; - foreach($aux as $i => $value) - { - if($n < FS_MAX_STORIES AND $value['visits'] > 1) - { - $stlist[] = array( - 'story' => $this->story->get($i), - 'visits' => $value['visits'], - 'date' => $value['date'], - 'spc' => intval( (time()-$value['date'])/$value['visits'] ) - ); - } - else - break; - - $n++; - } - - return $stlist; - } - - public function pagerank() - { - $dc = "http://toolbarqueries.google.com/"; - $gpr =& new GooglePageRank( trim( $this->domain() ) ); - $pagerank = $gpr->getPageRank($dc); - return $pagerank; - } -} - -?> \ No newline at end of file diff --git a/controller/suscriptions.php b/controller/suscriptions.php index f5570da..5f33347 100644 --- a/controller/suscriptions.php +++ b/controller/suscriptions.php @@ -1,7 +1,7 @@ visitor->human() ) + if( isset($_POST['admin_ps']) ) + { + if($_POST['admin_ps'] == FS_MASTER_KEY AND FS_MASTER_KEY != '') + { + $this->visitor->admin = TRUE; + $this->visitor->need_save = TRUE; + $this->visitor->save(); + $this->new_message('Ahora eres Dios. Alabado seas tú.'); + } + else + $this->new_error_msg('Contraseña incorrecta.'); + } + else if( isset($_GET['suscribe']) AND $this->visitor->human() ) { $suscription->visitor_id = $this->visitor->get_id(); $suscription->feed_id = $_GET['suscribe']; diff --git a/cron.php b/cron.php index dc89b88..4a70b54 100644 --- a/cron.php +++ b/cron.php @@ -1,7 +1,7 @@ install_indexes(); $feed->install_indexes(); $feed_story->install_indexes(); - $media_item->install_indexes(); $story->install_indexes(); $story_edition->install_indexes(); - $story_media->install_indexes(); $story_visit->install_indexes(); $suscription->install_indexes(); $visitor->install_indexes(); - /// si se pasa el parámetro full_stories procesamos todas las historias - if( count($_SERVER["argv"]) == 2 ) - $full = ($_SERVER['argv'][1] == 'redownload'); - else - $full = FALSE; - echo "\nComprobamos los modelos... "; - if($full) - { - $story->full_redownload(); - } - else - { - echo "\nComprobamos los modelos... "; - $comment->cron_job(); - $feed->cron_job(); - $feed_story->cron_job(); - $media_item->cron_job(); - $story->cron_job(); - $story_edition->cron_job(); - $story_media->cron_job(); - $story_visit->cron_job(); - $suscription->cron_job(); - $visitor->cron_job(); - } + $comment->cron_job(); + $feed->cron_job(); + $feed_story->cron_job(); + $story->cron_job(); + $story_edition->cron_job(); + $story_visit->cron_job(); + $suscription->cron_job(); + $visitor->cron_job(); $mongo->close(); diff --git a/cron_multicore.php b/cron_multicore.php index c5cc138..d59bc41 100644 --- a/cron_multicore.php +++ b/cron_multicore.php @@ -1,7 +1,7 @@ get( $_SERVER["argv"][1] ); - if($feed0) - $feed0->mini_cron_job(); + if($_SERVER["argv"][1] == 'END') + { + echo "\nComprobamos los modelos... "; + $comment->cron_job(); + $feed_story->cron_job(); + $story->cron_job(); + $story_edition->cron_job(); + $story_visit->cron_job(); + $suscription->cron_job(); + $visitor->cron_job(); + echo "\n"; + } else - echo "¡Feed ".$_SERVER["argv"][1]." no encontrado!"; + { + $feed0 = $feed->get( $_SERVER["argv"][1] ); + if($feed0) + $feed0->mini_cron_job(); + else + echo "¡Feed ".$_SERVER["argv"][1]." no encontrado!"; + } } else { @@ -67,27 +76,15 @@ $comment->install_indexes(); $feed->install_indexes(); $feed_story->install_indexes(); - $media_item->install_indexes(); $story->install_indexes(); $story_edition->install_indexes(); - $story_media->install_indexes(); $story_visit->install_indexes(); $suscription->install_indexes(); $visitor->install_indexes(); - echo "\nComprobamos los modelos... "; - $comment->cron_job(); - $feed_story->cron_job(); - $media_item->cron_job(); - $story->cron_job(); - $story_edition->cron_job(); - $story_media->cron_job(); - $story_visit->cron_job(); - $suscription->cron_job(); - $visitor->cron_job(); - echo "\n"; + /// metemos los IDs de los feeds para asignárlos a cada hilo $fp = fopen('tmp/feeds.txt', 'wb'); foreach($feed->all() as $f) fwrite ($fp, $f->get_id()."\n"); diff --git a/cron_multicore.sh b/cron_multicore.sh index 699aded..f920b3b 100644 --- a/cron_multicore.sh +++ b/cron_multicore.sh @@ -5,7 +5,8 @@ php5 cron_multicore.php echo "Paralelizando..." cat tmp/feeds.txt | parallel --gnu 'php5 cron_multicore.php {}' -echo "FIN" + +php5 cron_multicore.php END finish_time=$(date +%s) echo "Tiempo de ejecución: $((finish_time - start_time)) s" \ No newline at end of file diff --git a/export.php b/export.php index fcef78b..71554de 100644 --- a/export.php +++ b/export.php @@ -1,7 +1,7 @@ item as $item) { - $f0 = $feed->get_by_url( base64_decode( (string)$item->feed ) ); - if( !$f0 ) + if( !in_array(base64_decode( (string)$item->feed ), $feeds) ) { - $f0 = new feed(); - $f0->url = base64_decode( (string)$item->feed ); + $feeds[] = base64_decode( (string)$item->feed ); - if( $f0->reddit() ) - $f0->native_lang = FALSE; - - $f0->save(); + $f0 = $feed->get_by_url( base64_decode( (string)$item->feed ) ); + if( !$f0 ) + { + $f0 = new feed(); + $f0->url = base64_decode( (string)$item->feed ); + + if( $f0->reddit() ) + $f0->native_lang = FALSE; + + $f0->save(); + } } if( (string)$item->user != '-' ) diff --git a/model/comment.php b/model/comment.php index 55db933..f9ac9dd 100644 --- a/model/comment.php +++ b/model/comment.php @@ -1,7 +1,7 @@ date = $c['date']; $this->text = $c['text']; $this->nick = $c['nick']; + $this->ip = $c['ip']; } else { @@ -45,13 +47,18 @@ public function __construct($c = FALSE) $this->date = time(); $this->text = ''; $this->nick = 'anónimo'; + + if( isset($_SERVER['REMOTE_ADDR']) ) + $this->ip = $_SERVER['REMOTE_ADDR']; + else + $this->ip = 'unknown'; } } public function install_indexes() { $this->collection->ensureIndex( array('date' => -1) ); - $this->collection->ensureIndex( array('thread' => 1, 'date' => -1) ); + $this->collection->ensureIndex( array('thread' => 1, 'date' => 1) ); } public function timesince() @@ -68,10 +75,10 @@ public function url() if($story2) return $story2->url(); else - return $story->url(); + return FS_PATH.'/index.php?page=comments'; } else - return 'index.php?page=comments'; + return FS_PATH.'/index.php?page=comments'; } public function get($id) @@ -108,7 +115,8 @@ public function save() 'thread' => $this->thread, 'date' => $this->date, 'text' => $this->text, - 'nick' => $this->nick + 'nick' => $this->nick, + 'ip' => $this->ip ); if( $this->exists() ) @@ -146,7 +154,7 @@ public function all4thread($thread = NULL) $find = array('thread' => $this->var2str($thread)); $comlist = array(); - foreach($this->collection->find($find)->sort(array('date'=>-1))->limit(FS_MAX_STORIES) as $c) + foreach($this->collection->find($find)->sort(array('date'=>1)) as $c) $comlist[] = new comment($c); return $comlist; @@ -154,12 +162,7 @@ public function all4thread($thread = NULL) public function cron_job() { - if( mt_rand(0, 2) == 0 ) - { - echo "\nEliminamos comentarios antiguos..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE - $this->collection->remove( array('date' => array('$lt'=>time()-FS_MAX_AGE)) ); - } + } } diff --git a/model/feed.php b/model/feed.php index 462a684..010837f 100644 --- a/model/feed.php +++ b/model/feed.php @@ -1,7 +1,7 @@ last_update = $f['last_update']; $this->suscriptors = $f['suscriptors']; $this->strikes = $f['strikes']; - - if( isset($f['num_stories']) ) - $this->num_stories = $f['num_stories']; - else - $this->num_stories = 0; - - if( isset($f['native_lang']) ) - $this->native_lang = $f['native_lang']; - else - $this->native_lang = TRUE; + $this->num_stories = $f['num_stories']; + $this->native_lang = $f['native_lang']; + $this->parody = $f['parody']; + $this->penalize = $f['penalize']; } else { $this->id = NULL; $this->url = NULL; $this->name = $this->random_string(15); - $this->description = 'Sin descriptión'; + $this->description = 'Sin descripción.'; $this->last_check_date = 0; $this->last_update = 0; $this->suscriptors = 0; $this->strikes = 0; $this->num_stories = 0; $this->native_lang = TRUE; + $this->parody = FALSE; + $this->penalize = FALSE; } } @@ -84,13 +80,9 @@ public function install_indexes() public function url($w3c = TRUE) { if( is_null($this->id) ) - return 'index.php'; - else if(FS_MOD_REWRITE) - return 'explore_feed/'.$this->id; - else if($w3c) - return 'index.php?page=explore_feed&id='.$this->id; + return FS_PATH.'/index.php'; else - return 'index.php?page=explore_feed&id='.$this->id; + return FS_PATH.'/explore_feed/'.$this->id; } public function show_url($size=60) @@ -183,58 +175,25 @@ public function read() if($xml) { /// intentamos leer las noticias - $i = 0; if( $xml->channel->item ) { foreach($xml->channel->item as $item) - { - if($i < FS_MAX_STORIES) - { - $this->new_story($item); - $i++; - } - else - break; - } + $this->new_story($item); } else if( $xml->item ) { foreach($xml->item as $item) - { - if($i < FS_MAX_STORIES) - { - $this->new_story($item); - $i++; - } - else - break; - } + $this->new_story($item); } else if( $xml->feed->entry ) { foreach($xml->feed->entry as $item) - { - if($i < FS_MAX_STORIES) - { - $this->new_story($item); - $i++; - } - else - break; - } + $this->new_story($item); } else if( $xml->entry ) { foreach($xml->entry as $item) - { - if($i < FS_MAX_STORIES) - { - $this->new_story($item); - $i++; - } - else - break; - } + $this->new_story($item); } else { @@ -421,41 +380,6 @@ private function new_story(&$item) $description = preg_replace("/<\s*style.+?<\s*\/\s*style.*?>/si", '', html_entity_decode($description, ENT_QUOTES, 'UTF-8') ); $story->description = $this->remove_bad_utf8( strip_tags($description) ); - /// si la descripción de la noticia es demasiado corta, incluimos información adicional. - if( mb_strlen($story->description) < 250 ) - { - $dado = mt_rand(0, 4); - switch ($dado) - { - case 0: - $story->description .= ' Historia original de "'.$this->name.'" y publicada '.$story->timesince(). - ' ¿Y tú qué opinas?'; - break; - - case 1: - $story->description .= ' Escrito '.$story->timesince().' desde la fuente "'.$this->name. - '" ¿Tienes más información? Escribe un comentario ;-)'; - break; - - case 2: - $story->description .= ' ¿Y tú qué opinas? Deja un comentario ¡Que es gratis!'; - break; - - case 3: - $story->description .= ' No sé tú como lo ves ... ¿Por qué no dejas un comentario? ¡Es gratis!'; - break; - - default: - $story->description .= ' Historia indexada el '.Date('d/m/Y').' desde "'.$this->name.'".'; - if( !$this->native_lang ) - { - $story->description .= ' Esta historia no está e español, - pero puedes traducirla pulsando el botón editar.'; - } - break; - } - } - /// ¿story ya existe? $story2 = $story->get_by_link($story->link); if($story2) @@ -483,30 +407,29 @@ private function new_story(&$item) $story2->description = $story->description; } + /// ¿La noticia está penalizada pero la fuente no? + if($story2->penalize AND !$this->penalize) + $story2->penalize = FALSE; + /// actualizamos la noticia if($meneos > $story2->meneos) $story2->meneos = $meneos; $story2->random_count( !$this->meneame() ); + $story2->num_feeds++; $story2->save(); } - - /* - * Si la historia no tiene asociado un elemento multimedia, - * tiramos un dado y buscamos más elementos multimedia. - */ - if( is_null($story2->media_id) AND mt_rand(0, 2) == 0 ) - $story2->add_media_items($item); } else if( $story->date > time() - FS_MAX_AGE ) /// no guardamos noticias antiguas { $story->meneos = $meneos; $story->random_count( !$this->meneame() ); $story->native_lang = $this->native_lang; + $story->parody = $this->parody; + $story->penalize = $this->penalize; + $story->num_feeds = 1; $story->save(); /// hay que guardar para tener un ID $feed_story->story_id = $story->get_id(); $feed_story->save(); - - $story->add_media_items($item, FALSE); /// ya se encarga de guardar } } @@ -584,7 +507,9 @@ public function save() 'suscriptors' => $this->suscriptors, 'strikes' => $this->strikes, 'num_stories' => $this->num_stories, - 'native_lang' => $this->native_lang + 'native_lang' => $this->native_lang, + 'parody' => $this->parody, + 'penalize' => $this->penalize ); if( $this->exists() ) @@ -611,12 +536,10 @@ public function delete() $this->collection->remove( array('_id' => $this->id) ); $suscription = new suscription(); - foreach($suscription->all4feed($this->id) as $sus) - $sus->delete(); + $suscription->delete4feed($this->id); $feed_story = new feed_story(); - foreach($feed_story->all4feed($this->id) as $fs) - $fs->delete(); + $feed_story->delete4feed($this->id); } public function all() diff --git a/model/feed_story.php b/model/feed_story.php index 18b80d2..b0666c5 100644 --- a/model/feed_story.php +++ b/model/feed_story.php @@ -1,7 +1,7 @@ collection->remove( array('_id' => $this->id) ); } + public function delete4feed($fid) + { + $this->add2history(__CLASS__.'::'.__FUNCTION__); + $this->collection->remove( array('feed_id' => $this->var2str($fid)) ); + } + public function all() { $this->add2history(__CLASS__.'::'.__FUNCTION__); @@ -272,12 +278,7 @@ public function count4feed($fid) public function cron_job() { - if( mt_rand(0, 2) == 0 ) - { - echo "\nEliminamos feed_stories antiguos..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE - $this->collection->remove( array('date' => array('$lt'=>time()-FS_MAX_AGE)) ); - } + } } diff --git a/model/media_item.php b/model/media_item.php deleted file mode 100644 index 5de8f95..0000000 --- a/model/media_item.php +++ /dev/null @@ -1,736 +0,0 @@ -. - */ - -require_once 'base/fs_model.php'; -require_once 'model/my_image.php'; -require_once 'model/story_media.php'; - -class media_item extends fs_model -{ - public $url; - public $type; - public $filename; - public $width; - public $original_width; - public $height; - public $original_height; - public $thumbnail_url; - public $date; - - public $description; /// para sacar la descripción de la noticia cuando se escanea el link - - public function __construct($m = FALSE) - { - parent::__construct('media_items'); - if($m) - { - $this->id = $m['_id']; - $this->url = $m['url']; - $this->type = $m['type']; - $this->filename = $m['filename']; - $this->width = $m['width']; - $this->original_width = $m['original_width']; - $this->height = $m['height']; - $this->original_height = $m['original_height']; - $this->thumbnail_url = $m['thumbnail_url']; - $this->date = $m['date']; - } - else - { - $this->id = NULL; - $this->url = NULL; - $this->type = NULL; - $this->filename = NULL; - $this->width = 0; - $this->original_width = 0; - $this->height = 0; - $this->original_height = 0; - $this->thumbnail_url = NULL; - $this->date = time(); - } - - $this->description = ''; - } - - public function install_indexes() - { - $this->collection->ensureIndex('url'); - } - - public function ratio() - { - if($this->width > 0 AND $this->height > 0) - return($this->width / $this->height); - else - return 0; - } - - public function show_image() - { - if($this->type == 'imgur') - { - return ''.$this->date.''; - } - else if($this->type == 'image') - { - if( file_exists('tmp/images/'.$this->filename) ) - { - return ''.$this->filename.
-                 ''; - } - else - return ''; - } - else if($this->type == 'youtube') - return ''.$this->filename.
-              ''; - else if($this->type == 'vimeo') - return ''.$this->filename.
-              ''; - else if($this->type == 'video') - return ''.$this->date.''; - else - return ''; - } - - private function mobile() - { - $user_agent = 'unknown'; - if( isset($_SERVER['HTTP_USER_AGENT']) ) - $user_agent = $_SERVER['HTTP_USER_AGENT']; - - return (strstr(strtolower($user_agent), 'mobile') || strstr(strtolower($user_agent), 'android')); - } - - public function show($url=FALSE) - { - if($this->type == 'imgur') - { - $aux = ''.$this->date.''; - - if($url) - return ''.$aux.''; - else - return $aux; - } - else if($this->type == 'image') - { - if( !file_exists('tmp/images/'.$this->filename) ) - return ''; - else - { - $aux = ''.$this->filename.
-                    ''; - - if($url) - return ''.$aux.''; - else - return $aux; - } - } - else if($this->type == 'youtube') - { - if( $this->mobile() ) - { - return ''; - } - else - { - return ''; - } - } - else if($this->type == 'vimeo') - { - if( $this->mobile() ) - { - return ''; - } - else - { - return ''; - } - } - else if($this->type == 'video') - { - if( $this->mobile() ) - return ''; - else - return ''; - } - else - return ''; - } - - public function min_height() - { - if($this->type == 'youtube') - { - if( $this->mobile() ) - return 169; - else - return 360; - } - else if($this->type == 'vimeo') - { - if( $this->mobile() ) - return 169; - else - return 281; - } - else if($this->type == 'video') - { - if( $this->mobile() ) - return 170; - else - return 281; - } - else - return $this->height; - } - - public function is_video() - { - return in_array($this->type, array('youtube', 'vimeo', 'video')); - } - - public function find_media($item, $link, $search_link=TRUE) - { - $mlist = array(); - - if( !$this->find_media_aux($link, $mlist) ) - { - if($item) - { - $text = ''; - if( $item->description ) - $text .= (string)$item->description; - if( $item->content ) - $text .= (string)$item->content; - else if( $item->summary ) - $text .= (string)$item->summary; - else - { - /// intentamos leer el espacio de nombres atom - foreach($item->children('atom', TRUE) as $element) - { - if($element->getName() == 'summary') - { - $text .= (string)$element; - break; - } - } - foreach($item->children('content', TRUE) as $element) - { - if($element->getName() == 'encoded') - { - $text .= (string)$element; - break; - } - } - } - - $urls = $this->find_urls($text); - } - else - $urls = array(); - - /// buscamos más imágenes en el link, después descartamos - if($search_link) - { - $html = $this->curl_download($link); - foreach($this->find_urls($html) as $url) - { - if( !in_array($url, $urls) ) - $urls[] = $url; - } - - /// sacamos la descripción del html por si la noticia la necesita - $descs = array(); - if(preg_match_all('#description = $this->remove_bad_utf8($descs[1][0]); - } - } - - foreach($urls as $url) - $this->find_media_aux($url, $mlist); - } - - return $mlist; - } - - private function find_media_aux($link, &$mlist) - { - if( mb_substr($link, 0, 19) == 'http://i.imgur.com/' ) - { - $mi = new media_item(); - $mi->url = $link; - $mi->type = 'imgur'; - $mlist[] = $mi; - return TRUE; - } - else if( $this->is_valid_image_url($link) ) - { - $mi = new media_item(); - $mi->url = $link; - $mi->type = 'image'; - $mlist[] = $mi; - return TRUE; - } - else if($this->is_valid_video($link) ) - { - $mi = new media_item(); - $mi->url = $link; - $mi->type = 'video'; - $mlist[] = $mi; - return TRUE; - } - else if( mb_substr($link, 0, 29) == 'http://www.youtube.com/embed/' ) - { - $mi = new media_item(); - $mi->type = 'youtube'; - $parts = explode('/', $link); - $mi->filename = $this->clean_youtube_id($parts[4]); - $mi->url = 'http://www.youtube.com/embed/'.$mi->filename; - $mi->original_width = $mi->width = 225; - $mi->original_height = $mi->height = 127; - $mi->thumbnail_url = 'http://img.youtube.com/vi/'.$mi->filename.'/0.jpg'; - $mlist[] = $mi; - return TRUE; - } - else if( mb_substr($link, 0, 23) == 'http://www.youtube.com/' OR mb_substr($link, 0, 24) == 'https://www.youtube.com/' ) - { - $my_array_of_vars = array(); - parse_str( parse_url($link, PHP_URL_QUERY), $my_array_of_vars); - if( isset($my_array_of_vars['v']) ) - { - $mi = new media_item(); - $mi->type = 'youtube'; - $mi->filename = $this->clean_youtube_id($my_array_of_vars['v']); - $mi->url = 'http://www.youtube.com/embed/'.$mi->filename; - $mi->original_width = $mi->width = 225; - $mi->original_height = $mi->height = 127; - $mi->thumbnail_url = 'http://img.youtube.com/vi/'.$mi->filename.'/0.jpg'; - $mlist[] = $mi; - } - return TRUE; - } - else if( mb_substr($link, 0, 16) == 'http://youtu.be/' ) - { - $mi = new media_item(); - $mi->type = 'youtube'; - $parts = explode('/', $link); - $mi->filename = $this->clean_youtube_id($parts[3]); - $mi->url = 'http://www.youtube.com/embed/'.$mi->filename; - $mi->original_width = $mi->width = 225; - $mi->original_height = $mi->height = 127; - $mi->thumbnail_url = 'http://img.youtube.com/vi/'.$mi->filename.'/0.jpg'; - $mlist[] = $mi; - return TRUE; - } - else if( mb_substr($link, 0, 17) == 'http://vimeo.com/' ) - { - $mi = new media_item(); - $mi->type = 'vimeo'; - $parts = explode('/', $link); - $mi->filename = $this->clean_youtube_id($parts[3]); - if( is_numeric($mi->filename) ) - { - $mi->url = 'http://vimeo.com/'.$mi->filename; - $mi->original_width = $mi->width = 225; - $mi->original_height = $mi->height = 127; - try - { - $hash = unserialize( $this->curl_download('http://vimeo.com/api/v2/video/'.$mi->filename.'.php', FALSE) ); - $mi->thumbnail_url = $hash[0]['thumbnail_medium']; - $mlist[] = $mi; - } - catch(Exception $e) - { - $this->new_error('Imposible obtener los datos del vídeo de vimeo: '.$link."\n".$e); - } - } - return TRUE; - } - else if( mb_substr($link, 0, 17) == 'http://imgur.com/' ) - { - $status = FALSE; - $html = $this->curl_download($link); - $links = array(); - if( preg_match_all('#url = 'http:'.$links[1][0]; - else - $mi->url = $links[1][0]; - - $mi->type = 'imgur'; - $mlist[] = $mi; - $status = TRUE; - } - return $status; - } - else - return FALSE; - } - - private function clean_youtube_id($yid) - { - $new_yid = ''; - $yid = trim($yid); - for($i = 0; $i < mb_strlen($yid); $i++) - { - $aux = mb_substr($yid, $i, 1); - if( preg_match("#[a-zA-Z0-9\-_]#", $aux) ) - $new_yid .= $aux; - else - break; - } - return $new_yid; - } - - private function find_urls($text) - { - $text = html_entity_decode($text); - $found = array(); - $urls = array(); - if( preg_match_all("#//[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/))#", $text, $urls) ) - { - foreach($urls as $url) - { - foreach($url as $u) - { - if( !in_array('http:'.$u, $found) ) - $found[] = 'http:'.$u; - } - } - } - return $found; - } - - private function is_valid_image_url($url) - { - $status = TRUE; - $extensions = array('.png', '.jpg', 'jpeg', '.gif', 'webp'); - - if( mb_substr($url, 0, 4) != 'http' ) - $status = FALSE; - else if( mb_strlen($url) > 200 ) - $status = FALSE; - else if( mb_strstr($url, '/favicon.') ) - $status = FALSE; - else if( mb_strstr($url, 'doubleclick.net') ) - $status = FALSE; - else if( mb_substr($url, 0, 10) == 'http://ad.' ) - $status = FALSE; - else if( mb_strstr($url, '/avatar') OR mb_strstr($url, 'banner') ) - $status = FALSE; - else if( mb_substr($url, 0, 47) == 'http://www.meneame.net/backend/vote_com_img.php' ) - $status = FALSE; - else if( mb_substr($url, 0, 26) == 'http://publicidadinternet.' ) - $status = FALSE; - else if( !in_array( mb_strtolower( mb_substr($url, -4) ), $extensions) ) - $status = FALSE; - else if( mb_substr($url, -19) == 'vpreview_center.png' ) - $status = FALSE; - - return $status; - } - - private function is_valid_video($url) - { - $status = TRUE; - $extensions = array('.mp4', 'webm'); - - if( mb_substr($url, 0, 4) != 'http' ) - $status = FALSE; - else if( mb_strlen($url) > 200 ) - $status = FALSE; - else if( !in_array( mb_strtolower( mb_substr($url, -4) ), $extensions) ) - $status = FALSE; - - return $status; - } - - public function download() - { - $status = FALSE; - - if( in_array( $this->type, array('youtube', 'vimeo', 'video') ) ) - { - $status = TRUE; - } - else if($this->type == 'image' OR $this->type == 'imgur') - { - $this->filename = $this->random_string(30); - try - { - if( !file_exists('tmp/images') ) - mkdir('tmp/images'); - - $this->curl_save($this->url, 'tmp/images/'.$this->filename); - - if( file_exists('tmp/images/'.$this->filename) ) - { - $image = new my_image(); - $image->load('tmp/images/'.$this->filename); - $this->original_width = $image->getWidth(); - $this->original_height = $image->getHeight(); - - if($image->getWidth() > 100 AND $image->getHeight() > 80) - { - $image->resizeToWidth(225); - $image->save(); - $this->height = $image->getHeight(); - $this->width = $image->getWidth(); - $status = TRUE; - - /* - * Las imágenes de imgur solo las descargamos para obtener las - * dimensiones. - */ - if( $this->type == 'imgur' ) - unlink('tmp/images/'.$this->filename); - } - else - { - /* - * Si la imágen no nos vale, la borramos, pero nos guardamos los - * datos (la url) para no descargarla de nuevo. - */ - unlink('tmp/images/'.$this->filename); - $this->save(); - } - } - else - $this->new_error('No se encuentra el archivo después de descargar '.$this->url); - } - catch(Exception $e) - { - $this->new_error('Error al descargar '.$this->url.' : '.$e); - } - } - else - $this->new_error('Tipo desconocido.'); - - return $status; - } - - public function redownload() - { - if($this->type == 'image' AND !file_exists('tmp/images/'.$this->filename) ) - { - try - { - if( !file_exists('tmp/images') ) - mkdir('tmp/images'); - - $this->curl_save($this->url, 'tmp/images/'.$this->filename); - - if( file_exists('tmp/images/'.$this->filename) ) - { - $image = new my_image(); - $image->load('tmp/images/'.$this->filename); - $this->original_width = $image->getWidth(); - $this->original_height = $image->getHeight(); - if($image->getWidth() > 100 AND $image->getHeight() > 80) - { - $image->resizeToWidth(225); - $image->save(); - $this->height = $image->getHeight(); - $this->width = $image->getWidth(); - } - else - unlink('tmp/images/'.$this->filename); - } - else - $this->delete(); - } - catch(Exception $e) - { - $this->new_error('Error al descargar '.$this->url.' : '.$e); - $this->delete(); - } - } - - $this->date = time(); - $this->save(); - } - - public function get($id) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - - try - { - $data = $this->collection->findone( array('_id' => new MongoId($id)) ); - if($data) - return new media_item($data); - else - return FALSE; - } - catch(Exception $e) - { - $this->new_error($e); - return FALSE; - } - } - - public function get_by_url($url) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - - try - { - $data = $this->collection->findone( array('url' => $url) ); - if($data) - return new media_item($data); - else - return FALSE; - } - catch(Exception $e) - { - $this->new_error($e); - return FALSE; - } - } - - public function exists() - { - if( is_null($this->id) ) - return FALSE; - else - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $data = $this->collection->findone( array('_id' => new MongoId($this->id)) ); - if($data) - return TRUE; - else - return FALSE; - } - } - - public function save() - { - $data = array( - 'url' => $this->url, - 'type' => $this->type, - 'filename' => $this->filename, - 'width' => $this->width, - 'original_width' => $this->original_width, - 'height' => $this->height, - 'original_height' => $this->original_height, - 'thumbnail_url' => $this->thumbnail_url, - 'date' => $this->date - ); - - if( $this->exists() ) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__.'@update'); - $filter = array('_id' => $this->id); - $this->collection->update($filter, $data); - } - else - { - $this->add2history(__CLASS__.'::'.__FUNCTION__.'@insert'); - $this->collection->insert($data); - $this->id = $data['_id']; - } - } - - public function delete() - { - if( file_exists('tmp/images/'.$this->filename) ) - unlink('tmp/images/'.$this->filename); - - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $this->collection->remove( array('_id' => $this->id) ); - } - - public function all() - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $mlist = array(); - foreach($this->collection->find() as $i) - $mlist[] = new media_item($i); - return $mlist; - } - - public function random($limit=FS_MAX_STORIES) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $mlist = array(); - $offset = mt_rand(0, max( array(0, $this->count()-$limit) ) ); - foreach($this->collection->find()->skip($offset)->limit($limit) as $i) - $mlist[] = new media_item($i); - return $mlist; - } - - public function stats() - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - return array( - array('imgur', number_format( $this->collection->find(array('type'=>'imgur'))->count()) , 0, ',', '.'), - array('image', number_format( $this->collection->find(array('type'=>'image'))->count()) , 0, ',', '.'), - array('youtube', number_format( $this->collection->find(array('type'=>'youtube'))->count()) , 0, ',', '.'), - array('vimeo', number_format( $this->collection->find(array('type'=>'vimeo'))->count()) , 0, ',', '.'), - array('video', number_format( $this->collection->find(array('type'=>'video'))->count()) , 0, ',', '.'), - ); - } - - public function cron_job() - { - if( mt_rand(0, 2) == 0 ) - { - $DIR = 'tmp/images/'; - if( file_exists($DIR) ) - { - echo "\nEliminamos imágenes antiguas... "; - foreach(scandir($DIR) as $file) - { - if( filemtime($DIR.$file) <= time()-FS_MAX_AGE ) - { - unlink($DIR.$file); - echo '-'; - } - } - } - - echo "\nEliminamos media_items antiguos..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE - $this->collection->remove( array('date' => array('$lt'=>time()-FS_MAX_AGE)) ); - } - } -} - -?> \ No newline at end of file diff --git a/model/my_image.php b/model/my_image.php deleted file mode 100644 index fd59b54..0000000 --- a/model/my_image.php +++ /dev/null @@ -1,175 +0,0 @@ -. - */ - -class my_image -{ - public $image; - public $image_type; - public $path; - - function load($filename) - { - $this->path = $filename; - - try - { - $image_info = @getimagesize($filename); - - if($image_info[0] == 0 AND $image_info[1] == 0) - { - $this->image = NULL; - $this->image_type = NULL; - } - else - { - $this->image_type = $image_info[2]; - - if( $this->image_type == IMAGETYPE_JPEG ) - $this->image = @imagecreatefromjpeg($filename); - else if( $this->image_type == IMAGETYPE_GIF ) - $this->image = @imagecreatefromgif($filename); - else if( $this->image_type == IMAGETYPE_PNG ) - $this->image = @imagecreatefrompng($filename); - else - { - $this->image = NULL; - $this->image_type = NULL; - } - } - } - catch(Exception $e) - { - $this->image = NULL; - $this->image_type = NULL; - } - } - - function save($filename=FALSE, $image_type=IMAGETYPE_JPEG, $compression=75, $permissions=null) - { - if( isset($this->image) ) - { - if( !$filename ) - $filename = $this->path; - - if( $image_type == IMAGETYPE_JPEG ) - imagejpeg($this->image,$filename,$compression); - else if( $image_type == IMAGETYPE_GIF ) - imagegif($this->image,$filename); - else if( $image_type == IMAGETYPE_PNG ) - imagepng($this->image,$filename); - - if( $permissions != null) - chmod($filename,$permissions); - } - } - - function output($image_type=IMAGETYPE_JPEG) - { - if( isset($this->image) ) - { - if( $image_type == IMAGETYPE_JPEG ) - imagejpeg($this->image); - else if( $image_type == IMAGETYPE_GIF ) - imagegif($this->image); - else if( $image_type == IMAGETYPE_PNG ) - imagepng($this->image); - } - } - - function getWidth() - { - if( is_null($this->image) OR is_bool($this->image) ) - return 0; - else - return imagesx($this->image); - } - - function getHeight() - { - if( is_null($this->image) OR is_bool($this->image) ) - return 0; - else - return imagesy($this->image); - } - - function resizeToHeight($height) - { - if( isset($this->image) ) - { - $ratio = $height / $this->getHeight(); - $width = $this->getWidth() * $ratio; - $this->resize($width, $height); - } - } - - function resizeToWidth($width) - { - if( isset($this->image) ) - { - $ratio = $width / $this->getWidth(); - $height = $this->getheight() * $ratio; - $this->resize($width, $height); - } - } - - function scale($scale) - { - if( isset($this->image) ) - { - $width = $this->getWidth() * $scale/100; - $height = $this->getheight() * $scale/100; - $this->resize($width, $height); - } - } - - function resize($width, $height) - { - if( isset($this->image) ) - { - $new_image = imagecreatetruecolor($width, $height); - - if($this->image_type == IMAGETYPE_GIF OR $this->image_type == IMAGETYPE_PNG) - { - $current_transparent = imagecolortransparent($this->image); - - if($current_transparent != -1) - { - $transparent_color = @imagecolorsforindex($this->image, $current_transparent); - $current_transparent = imagecolorallocate($new_image, $transparent_color['red'], - $transparent_color['green'], $transparent_color['blue']); - imagefill($new_image, 0, 0, $current_transparent); - imagecolortransparent($new_image, $current_transparent); - } - else if($this->image_type == IMAGETYPE_PNG) - { - imagealphablending($new_image, false); - $color = imagecolorallocatealpha($new_image, 0, 0, 0, 127); - imagefill($new_image, 0, 0, $color); - imagesavealpha($new_image, true); - } - } - - imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, - $this->getWidth(), $this->getHeight()); - $this->image = $new_image; - } - } -} - -?> \ No newline at end of file diff --git a/model/story.php b/model/story.php index fea06d6..0efe656 100644 --- a/model/story.php +++ b/model/story.php @@ -1,7 +1,7 @@ id = $item['_id']; - - if( isset($item['name']) ) - $this->name = $item['name']; - else - $this->name = ''; - + $this->name = $item['name']; $this->date = $item['date']; + $this->published = $item['published']; $this->title = $item['title']; $this->description = $item['description']; $this->link = $item['link']; - $this->media_id = $item['media_id']; $this->clics = $item['clics']; $this->tweets = $item['tweets']; - - if( isset($item['meneos']) ) - $this->meneos = $item['meneos']; - else - $this->meneos = 0; - - if( isset($item['likes']) ) - $this->likes = $item['likes']; - else - $this->likes = 0; - - if( isset($item['plusones']) ) - $this->plusones = $item['plusones']; - else - $this->plusones = 0; - + $this->meneos = $item['meneos']; + $this->likes = $item['likes']; + $this->plusones = $item['plusones']; $this->popularity = $item['popularity']; + $this->native_lang = $item['native_lang']; + $this->parody = $item['parody']; + $this->penalize = $item['penalize']; + $this->featured = $item['featured']; - if( isset($item['native_lang']) ) - $this->native_lang = $item['native_lang']; - else - $this->native_lang = TRUE; - - if( isset($item['noimages']) ) - $this->noimages = $item['noimages']; - else - $this->noimages = FALSE; - - if( isset($item['keywords']) ) - $this->keywords = $item['keywords']; - else - $this->keywords = ''; - - if( isset($item['related_id']) ) - $this->related_id = $item['related_id']; - else - $this->related_id = NULL; + $this->keywords = ''; + foreach( explode(',', $item['keywords']) as $kw ) + $this->add_keyword($kw); - if( is_null($this->media_id) ) - $this->media_item = NULL; - else - { - if( !isset(self::$mi0) ) - self::$mi0 = new media_item(); - - $this->media_item = self::$mi0->get($this->media_id); - } + $this->related_id = $item['related_id']; + $this->edition_id = $item['edition_id']; + $this->num_editions = $item['num_editions']; + $this->num_feeds = $item['num_feeds']; + $this->num_comments = $item['num_comments']; } else { $this->id = NULL; $this->name = ''; $this->date = time(); + $this->published = NULL; $this->title = NULL; $this->description = NULL; $this->link = NULL; - $this->media_id = NULL; $this->clics = 0; $this->tweets = 0; $this->meneos = 0; @@ -133,10 +101,15 @@ public function __construct($item=FALSE) $this->plusones = 0; $this->popularity = 0; $this->native_lang = TRUE; - $this->noimages = FALSE; + $this->parody = FALSE; + $this->penalize = FALSE; + $this->featured = FALSE; $this->keywords = ''; $this->related_id = NULL; - $this->media_item = NULL; + $this->edition_id = NULL; + $this->num_editions = 0; + $this->num_feeds = 0; + $this->num_comments = 0; } } @@ -144,6 +117,7 @@ public function install_indexes() { $this->collection->ensureIndex( array('popularity' => -1) ); $this->collection->ensureIndex( array('date' => -1) ); + $this->collection->ensureIndex( array('published' => -1) ); $this->collection->ensureIndex('link'); $this->collection->ensureIndex('name'); } @@ -151,19 +125,19 @@ public function install_indexes() public function url($w3c = TRUE) { if( is_null($this->id) ) - return 'index.php'; - else if(FS_MOD_REWRITE AND $this->name != '') - return 'show_story/'.$this->name; + return FS_PATH.'/index.php'; + else if($this->name != '') + return FS_PATH.'/show_story/'.$this->name; else if($w3c) - return 'index.php?page=show_story&id='.$this->id; + return FS_PATH.'/index.php?page=show_story&id='.$this->id; else - return 'index.php?page=show_story&id='.$this->id; + return FS_PATH.'/index.php?page=show_story&id='.$this->id; } public function link() { if( is_null($this->id) ) - return 'index.php'; + return $this->url(); else return $this->link; } @@ -171,9 +145,9 @@ public function link() public function edit_url() { if( is_null($this->id) ) - return 'index.php'; + return FS_PATH.'/index.php'; else - return 'index.php?page=edit_story&id='.$this->id; + return FS_PATH.'/index.php?page=edit_story&id='.$this->id; } public function show_date($iso=FALSE) @@ -184,9 +158,12 @@ public function show_date($iso=FALSE) return Date('Y-m-d H:m', $this->date); } - public function timesince() + public function timesince($published=FALSE) { - return $this->time2timesince($this->date); + if($published) + return $this->time2timesince($this->published); + else + return $this->time2timesince($this->date); } public function popularity() @@ -197,29 +174,43 @@ public function popularity() public function feed_links() { $feed_story = new feed_story(); - return $feed_story->all4story( $this->id ); + $feed_links = $feed_story->all4story($this->id); + + if($this->num_feeds != count($feed_links)) + { + $this->num_feeds = count($feed_links); + $this->save(); + } + + return $feed_links; } - public function media_items() + public function editions() { - if($this->noimages) - { - $this->media_items = array(); - } - else if( !isset($this->media_items) ) + $edition = new story_edition(); + $editions = $edition->all4story($this->id); + + if($this->num_editions != count($editions)) { - $this->media_items = array(); - $story_media = new story_media(); - foreach($story_media->all4story($this->id) as $sm) - $this->media_items[] = $sm->media_item(); + $this->num_editions = count($editions); + $this->save(); } - return $this->media_items; + + return $editions; } - public function editions() + public function comments() { - $edition = new story_edition(); - return $edition->all4story( $this->id ); + $comment = new comment(); + $comments = $comment->all4thread($this->id); + + if($this->num_comments != count($comments)) + { + $this->num_comments = count($comments); + $this->save(); + } + + return $comments; } public function related_story() @@ -230,18 +221,30 @@ public function related_story() return FALSE; } + public function pre_related_story() + { + $this->add2history(__CLASS__.'::'.__FUNCTION__); + $data = $this->collection->findone( array('related_id' => $this->var2str($this->id)) ); + if($data) + return new story($data); + else + return FALSE; + } + public function add_keyword($key) { - if($key != '') + $nkey = trim($key); + + if($nkey != '') { if($this->keywords == '') - $this->keywords = $key; - else if( strstr($this->keywords, $key) === FALSE ) - $this->keywords .= ', '.$key; + $this->keywords = $nkey; + else if( strstr($this->keywords, $nkey) === FALSE ) + $this->keywords .= ', '.$nkey; } } - public function description($width=300) + public function description($width=250) { return $this->true_text_break($this->description, $width); } @@ -250,15 +253,17 @@ private function calculate_popularity() { $tclics = $this->clics; - if($this->native_lang) + if($this->native_lang AND !$this->penalize AND mb_strlen($this->description) > 0) { - $points = 1 + count( explode(',', $this->keywords) ); - if($this->media_id) - $points++; + $points = 1 + $this->num_editions + $this->num_feeds + $this->num_comments; + if($this->related_id) $points++; + if( mb_strlen($this->description) > 250 ) $points++; + else if( mb_strlen($this->description) < 150 ) + $points -= 2; if($this->tweets > 1000) $tclics += min( array($this->tweets, 10 + $points*$this->clics) ); @@ -365,9 +370,9 @@ public function plusones_count() public function random_count($meneame = TRUE) { - if($this->native_lang) + if( isset($this->link) AND $this->native_lang AND !$this->penalize ) { - switch( mt_rand(0, 3) ) + switch( mt_rand(0, 4) ) { case 0: $this->tweet_count(); @@ -387,7 +392,10 @@ public function random_count($meneame = TRUE) else $this->plusones_count(); break; - + + case 3: + break; + default: $this->plusones_count(); break; @@ -447,17 +455,17 @@ public function save() { $this->title = $this->true_text_break($this->title, 149, 18); $this->description = $this->true_text_break($this->description, 999, 25); - $this->media_id = $this->var2str($this->media_id); $this->related_id = $this->var2str($this->related_id); + $this->edition_id = $this->var2str($this->edition_id); $this->calculate_popularity(); $data = array( 'name' => $this->name, 'date' => $this->date, + 'published' => $this->published, 'title' => $this->title, 'description' => $this->description, 'link' => $this->link, - 'media_id' => $this->media_id, 'clics' => $this->clics, 'tweets' => $this->tweets, 'meneos' => $this->meneos, @@ -465,9 +473,15 @@ public function save() 'plusones' => $this->plusones, 'popularity' => $this->popularity, 'native_lang' => $this->native_lang, - 'noimages' => $this->noimages, + 'parody' => $this->parody, + 'penalize' => $this->penalize, + 'featured' => $this->featured, 'keywords' => $this->keywords, - 'related_id' => $this->related_id + 'related_id' => $this->related_id, + 'edition_id' => $this->edition_id, + 'num_editions' => $this->num_editions, + 'num_feeds' => $this->num_feeds, + 'num_comments' => $this->num_comments ); if( $this->exists() ) @@ -515,6 +529,15 @@ public function delete() { $this->add2history(__CLASS__.'::'.__FUNCTION__); $this->collection->remove( array('_id' => $this->id) ); + + foreach($this->editions() as $edi) + $edi->delete(); + + foreach($this->feed_links() as $fs) + $fs->delete(); + + foreach($this->comments() as $com) + $com->delete(); } public function all() @@ -556,6 +579,18 @@ public function popular_stories($num = FS_MAX_STORIES) return $stlist; } + public function published_stories() + { + $this->add2history(__CLASS__.'::'.__FUNCTION__); + $stlist = array(); + foreach($this->collection->find()->sort(array('published'=>-1))->limit(FS_MAX_STORIES) as $s) + { + if( isset($s['published']) ) + $stlist[] = new story($s); + } + return $stlist; + } + public function random_stories($limit=FS_MAX_STORIES) { $this->add2history(__CLASS__.'::'.__FUNCTION__); @@ -578,125 +613,28 @@ public function search($query) { $this->add2history(__CLASS__.'::'.__FUNCTION__); $stlist = array(); - $search = array( 'title' => new MongoRegex("/\b".$query."\b/i") ); + $search = array( 'title' => new MongoRegex('/'.$query.'/iu') ); foreach($this->collection->find($search)->sort(array('popularity'=>-1))->limit(FS_MAX_STORIES) as $s) - $stlist[] = new story($s); - return $stlist; - } - - public function add_media_items($item=FALSE, $search_link=TRUE) - { - if(!$this->noimages) { - $num_downloads = 0; - $width = 0; - $height = 0; - $first_forced = FALSE; - $media_item = new media_item(); - foreach($media_item->find_media($item, $this->link, $search_link) as $mi) - { - $story_media = new story_media(); - $story_media->story_id = $this->id; - - if( !$media_item->get_by_url($mi->url) ) - { - if( $mi->download() ) - { - echo 'D'; - $num_downloads++; - - $mi->save(); - $story_media->media_id = $mi->get_id(); - $story_media->save(); - - if($this->link == $mi->url) - { - echo 'S'; - - $this->media_id = $mi->get_id(); - $width = $mi->original_width; - $height = $mi->original_height; - break; - } - else if($num_downloads == 1) - { - echo 'S'; - - $this->media_id = $mi->get_id(); - $width = $mi->original_width; - $height = $mi->original_height; - - if($mi->ratio() < 1 OR $mi->ratio() > 2) - $first_forced = TRUE; - } - else if($num_downloads > FS_MAX_DOWNLOADS) - { - break; - } - else if($first_forced AND $mi->ratio() >= 1 AND $mi->ratio() <= 2) - { - echo 'S'; - - $this->media_id = $mi->get_id(); - $width = $mi->original_width; - $height = $mi->original_height; - } - else if($mi->ratio() >= 1 AND $mi->ratio() <= 2 AND $mi->width > $width AND $mi->height > $height) - { - echo 'S'; - - $this->media_id = $mi->get_id(); - $width = $mi->original_width; - $height = $mi->original_height; - } - } - else - echo 'E'; - } - else - echo 'I'; - } - - /// Si la descripción obtenida es más larga, la usamos - if( mb_strlen($media_item->description) > mb_strlen($this->description) ) - $this->description = $media_item->description; - - echo "F\n"; - $this->save(); + /// parece ser que las expresiones regulares no funciona muy bien en mongodb + if( preg_match('/\b'.$query.'\b/iu', $s['title']) ) + $stlist[] = new story($s); } + return $stlist; } public function cron_job() { - if( mt_rand(0, 2) == 0 ) - { - echo "\nEliminamos historias antiguas..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE y con menos de 100 clics - $this->collection->remove( - array('date' => array('$lt' => time()-FS_MAX_AGE), 'clics' => array('$lt' => 100)) - ); - } - - echo "\nActualizamos las historias populares...\n"; - $i = 0; + echo "\nActualizamos los artículos populares y publicamos..."; + $j = 0; $keywords = array(); $keywords2 = array(); + $publish = 2; /// máximo de artículos publicados cada vez foreach($this->popular_stories(FS_MAX_STORIES * 4) as $ps) { - /// obtenemos las menciones de la historia + /// obtenemos las menciones del artículo $ps->random_count(); - if($i < FS_MAX_STORIES) - { - /// si no hay imagen, buscamos más - if( !$ps->media_item AND mt_rand(0, 2) == 0 ) - $ps->add_media_items(); - else - echo '.'; - } - else - echo '.'; - /// extraemos las keywords if($ps->keywords != '') { @@ -713,8 +651,17 @@ public function cron_job() } } + /// si la noticia alcanza el TOP FS_MAX_STORIES, entonces la publicamos + if($j < FS_MAX_STORIES AND is_null($ps->published) AND $publish > 0 AND $ps->popularity > 0) + { + $ps->published = time(); + $publish--; + } + $ps->save(); - $i++; + $j++; + + echo '.'; } /// necesitamos más keywords @@ -736,7 +683,7 @@ public function cron_job() } } - echo "\nInterconectamos las historias...\n"; + echo "\nInterconectamos los artículos...\n"; foreach( array_merge($keywords, $keywords2) as $keyword ) { if($keyword != '') @@ -762,27 +709,6 @@ public function cron_job() echo '.'; } } - - public function full_redownload() - { - echo "\nEliminamos historias antiguas..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE y con menos de 100 clics - $this->collection->remove( - array('date' => array('$lt' => time()-FS_MAX_AGE), 'clics' => array('$lt' => 100)) - ); - - echo "\nComprobamos TODAS las historias..."; - foreach($this->all() as $s) - { - if($s->media_item) - { - $s->media_item->redownload(); - echo 'D'; - } - else - echo '.'; - } - } } ?> \ No newline at end of file diff --git a/model/story_edition.php b/model/story_edition.php index 562181a..15e3920 100644 --- a/model/story_edition.php +++ b/model/story_edition.php @@ -1,7 +1,7 @@ id = $se['_id']; - $this->story_id = $se['story_id']; - $this->visitor_id = $se['visitor_id']; - $this->ip = $se['ip']; $this->date = $se['date']; - $this->title = $se['title']; $this->description = $se['description']; - $this->media_id = $se['media_id']; - $this->votes = $se['votes']; - - if( is_null($this->story_id) ) - $this->story = NULL; - else - { - if( !isset(self::$st0) ) - self::$st0 = new story(); - - $this->story = self::$st0->get($this->story_id); - } - - if( is_null($this->media_id) ) - $this->media_item = NULL; - else - { - if( !isset(self::$mi0) ) - self::$mi0 = new media_item(); - - $this->media_item = self::$mi0->get($this->media_id); - } + $this->ip = $se['ip']; + $this->nick = $se['nick']; + $this->points = $se['points']; + $this->story_id = $se['story_id']; + $this->title = $se['title']; + $this->visitor_id = $se['visitor_id']; } else { $this->id = NULL; - $this->story_id = NULL; - $this->visitor_id = NULL; + $this->date = time(); + $this->description = ''; if( isset($_SERVER['REMOTE_ADDR']) ) $this->ip = $_SERVER['REMOTE_ADDR']; else $this->ip = 'unknown'; - $this->date = time(); + $this->nick = 'anonymous'; + $this->points = 0; + $this->story_id = NULL; $this->title = ''; - $this->description = ''; - $this->media_id = NULL; - $this->votes = 1; - - $this->story = NULL; - $this->media_item = NULL; + $this->visitor_id = NULL; } } @@ -114,27 +84,19 @@ public function timesince() public function url($sitemap=TRUE) { if( is_null($this->id) ) - return 'index.php'; + return FS_PATH.'/index.php'; else if($sitemap) - return 'index.php?page=show_edition&id='.$this->id; + return FS_PATH.'/index.php?page=show_edition&id='.$this->id; else - return 'index.php?page=show_edition&id='.$this->id; + return FS_PATH.'/index.php?page=show_edition&id='.$this->id; } public function edit_url() { if( is_null($this->id) ) - return 'index.php'; - else - return 'index.php?page=edit_story&id='.$this->id; - } - - public function vote_url() - { - if( is_null($this->id) ) - return 'index.php'; + return FS_PATH.'/index.php'; else - return 'index.php?page=show_edition&id='.$this->id.'&vote=TRUE'; + return FS_PATH.'/index.php?page=edit_story&id='.$this->id; } public function description($width=300) @@ -142,9 +104,10 @@ public function description($width=300) return $this->true_text_break($this->description, $width); } - public function editions() + public function story() { - return $this->all4story( $this->story_id ); + $story = new story(); + return $story->get($this->story_id); } public function get($id) @@ -199,17 +162,16 @@ public function save() $this->visitor_id = $this->var2str($this->visitor_id); $this->title = $this->true_text_break($this->title, 149, 18); $this->description = $this->true_text_break($this->description, 999, 25); - $this->media_id = $this->var2str($this->media_id); $data = array( - 'story_id' => $this->story_id, - 'visitor_id' => $this->visitor_id, - 'ip' => $this->ip, 'date' => $this->date, - 'title' => $this->title, 'description' => $this->description, - 'media_id' => $this->media_id, - 'votes' => $this->votes + 'ip' => $this->ip, + 'nick' => $this->nick, + 'points' => $this->points, + 'story_id' => $this->story_id, + 'title' => $this->title, + 'visitor_id' => $this->visitor_id ); if( $this->exists() ) @@ -270,12 +232,7 @@ public function last_editions($limit = FS_MAX_STORIES) public function cron_job() { - if( mt_rand(0, 2) == 0 ) - { - echo "\nEliminamos ediciones antiguas..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE - $this->collection->remove( array('date' => array('$lt'=>time()-FS_MAX_AGE)) ); - } + } } diff --git a/model/story_media.php b/model/story_media.php deleted file mode 100644 index ccdf749..0000000 --- a/model/story_media.php +++ /dev/null @@ -1,160 +0,0 @@ -. - */ - -require_once 'base/fs_model.php'; -require_once 'model/media_item.php'; - -class story_media extends fs_model -{ - public $story_id; - public $media_id; - public $date; - - public function __construct($i = FALSE) - { - parent::__construct('story_medias'); - if($i) - { - $this->id = $i['_id']; - $this->story_id = $i['story_id']; - $this->media_id = $i['media_id']; - - if( isset($i['date']) ) - $this->date = $i['date']; - else - { - $this->date = time(); - $this->save(); - } - } - else - { - $this->id = NULL; - $this->story_id = NULL; - $this->media_id = NULL; - $this->date = time(); - } - } - - public function install_indexes() - { - $this->collection->ensureIndex('story_id'); - } - - public function media_item() - { - $media_item = new media_item(); - return $media_item->get($this->media_id); - } - - public function get($id) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $data = $this->collection->findone( array('_id' => new MongoId($id)) ); - if($data) - return new story_media($data); - else - return FALSE; - } - - public function exists() - { - if( is_null($this->id) ) - return FALSE; - else - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $data = $this->collection->findone( array('_id' => new MongoId($this->id)) ); - if($data) - return TRUE; - else - return FALSE; - } - } - - public function save() - { - $this->story_id = $this->var2str($this->story_id); - $this->media_id = $this->var2str($this->media_id); - - $data = array( - 'story_id' => $this->story_id, - 'media_id' => $this->media_id, - 'date' => $this->date - ); - - if( $this->exists() ) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__.'@update'); - $filter = array('_id' => $this->id); - $this->collection->update($filter, $data); - } - else - { - $this->add2history(__CLASS__.'::'.__FUNCTION__.'@insert'); - $this->collection->insert($data); - $this->id = $data['_id']; - } - } - - public function delete() - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $this->collection->remove( array('_id' => $this->id) ); - } - - public function all() - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $ilist = array(); - foreach($this->collection->find() as $i) - $ilist[] = new story_media($i); - return $ilist; - } - - public function all4story($sid) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $ilist = array(); - foreach($this->collection->find( array('story_id' => $this->var2str($sid)) ) as $i) - $ilist[] = new story_media($i); - return $ilist; - } - - public function all4media($mid) - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $ilist = array(); - foreach($this->collection->find( array('media_id' => $this->var2str($mid)) ) as $i) - $ilist[] = new story_media($i); - return $ilist; - } - - public function cron_job() - { - if( mt_rand(0, 2) == 0 ) - { - echo "\nEliminamos story_medias antiguas..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE - $this->collection->remove( array('date' => array('$lt'=>time()-FS_MAX_AGE)) ); - } - } -} - -?> \ No newline at end of file diff --git a/model/story_visit.php b/model/story_visit.php index 16fe565..292e189 100644 --- a/model/story_visit.php +++ b/model/story_visit.php @@ -1,7 +1,7 @@ id = $sv['_id']; - - if( isset($sv['visitor_id']) ) - $this->visitor_id = $sv['visitor_id']; - else - $this->visitor_id = NULL; - + $this->visitor_id = $sv['visitor_id']; $this->story_id = $sv['story_id']; $this->edition_id = $sv['edition_id']; $this->ip = $sv['ip']; @@ -90,9 +85,8 @@ public function story() public function url() { $s = $this->story(); - if($s) - return 'index.php?page=show_story&id='.$this->story_id; + return $s->url(); else return '#'; } @@ -100,9 +94,8 @@ public function url() public function edition_url() { $s = $this->story(); - if($s) - return 'index.php?page=show_edition&id='.$this->edition_id; + return $s->edit_url(); else return '#'; } @@ -110,9 +103,8 @@ public function edition_url() public function title() { $s = $this->story(); - if($s) - return $this->story->title; + return $s->title; else return 'Noticia no encontrada.'; } @@ -233,12 +225,9 @@ public function count4visitor($id) public function cron_job() { - if( mt_rand(0, 2) == 0 ) - { - echo "\nEliminamos visitas antiguas..."; - /// eliminamos los registros más antiguos que FS_MAX_AGE - $this->collection->remove( array('date' => array('$lt'=>time()-FS_MAX_AGE)) ); - } + echo "\nEliminamos visitas antiguas..."; + /// eliminamos los registros de más de 7 días + $this->collection->remove( array('date' => array('$lt'=>time()-604800)) ); } } diff --git a/model/suscription.php b/model/suscription.php index 6df4b1b..5c0b9c7 100644 --- a/model/suscription.php +++ b/model/suscription.php @@ -1,7 +1,7 @@ collection->remove( array('visitor_id' => $this->var2str($vid)) ); } + public function delete4feed($fid) + { + $this->add2history(__CLASS__.'::'.__FUNCTION__); + $this->collection->remove( array('feed_id' => $this->var2str($fid)) ); + } + public function all() { $this->add2history(__CLASS__.'::'.__FUNCTION__); diff --git a/model/visitor.php b/model/visitor.php index 5471c17..ebe651d 100644 --- a/model/visitor.php +++ b/model/visitor.php @@ -1,7 +1,7 @@ id = $k['_id']; $this->nick = $k['nick']; + $this->ip = $k['ip']; $this->user_agent = $k['user_agent']; - - if( isset($k['first_login_date']) ) - $this->first_login_date = $k['first_login_date']; - else - $this->first_login_date = $k['last_login_date']; - + $this->first_login_date = $k['first_login_date']; $this->last_login_date = $k['last_login_date']; - - if( isset($k['human']) ) - $this->human = $k['human']; - else - $this->human = FALSE; - - if( isset($k['num_suscriptions']) ) - $this->num_suscriptions = $k['num_suscriptions']; - else - $this->num_suscriptions = 0; - - if( isset($k['age']) ) - $this->age = $k['age']; - else - $this->age = 0; - + $this->admin = $k['admin']; + $this->num_suscriptions = $k['num_suscriptions']; + $this->num_stories = $k['num_stories']; + $this->num_editions = $k['num_editions']; + $this->num_comments = $k['num_comments']; + $this->num_visits = $k['num_visits']; + $this->points = $k['points']; + $this->extra_points = $k['extra_points']; $this->noob = FALSE; - $this->need_save = FALSE; } else { $this->id = NULL; $this->nick = $this->random_string(12); + $this->ip = 'unknown'; + $this->user_agent = 'unknown'; $this->first_login_date = time(); - $this->human = FALSE; + $this->last_login_date = 0; + $this->admin = FALSE; $this->num_suscriptions = 0; - $this->login(); - $this->age = 0; + $this->num_stories = 0; + $this->num_editions = 0; + $this->num_comments = 0; + $this->num_visits = 0; + $this->points = 0; + $this->extra_points = 0; $this->noob = TRUE; - $this->need_save = TRUE; } - if( !isset(self::$sus0) ) - self::$sus0 = new suscription(); + $this->need_save = FALSE; } public function install_indexes() { $this->collection->ensureIndex('last_login_date'); - $this->collection->ensureIndex( array('age' => -1) ); } public function login_date() @@ -106,12 +101,9 @@ public function login_timesince() return $this->time2timesince($this->last_login_date); } - public function age($real = FALSE) + public function age() { - if($real) - $time = $this->age; - else - $time = $this->last_login_date - $this->first_login_date; + $time = $this->last_login_date - $this->first_login_date; if($time <= 60) return $time.' segundos'; @@ -158,6 +150,11 @@ public function human() public function login() { + if( isset($_SERVER['REMOTE_ADDR']) ) + $this->ip = $_SERVER['REMOTE_ADDR']; + else + $this->ip = 'unknown'; + if( isset($_SERVER['HTTP_USER_AGENT']) ) $this->user_agent = $_SERVER['HTTP_USER_AGENT']; else @@ -168,17 +165,25 @@ public function login() $this->last_login_date = time(); $this->need_save = TRUE; } - } - - public function num_visits() - { - $sv = new story_visit(); - return $sv->count4visitor($this->id); + + if( $this->num_editions < (2*$this->num_visits) AND $this->num_stories < (2*$this->num_visits) ) + $this->points = intval( ($this->num_comments+$this->num_editions+$this->num_stories)/3 ) + $this->extra_points; + else + $this->points = 0; } public function last_visits() { $sv = new story_visit(); + + $num_visits = $sv->count4visitor($this->id); + if($this->num_visits != $num_visits) + { + $this->num_visits = $num_visits; + $this->need_save = TRUE; + $this->save(); + } + return $sv->all4visitor($this->id); } @@ -186,9 +191,10 @@ public function suscriptions() { if( !isset($this->suscriptions) ) { - $this->suscriptions = self::$sus0->all4visitor($this->id); + $sus0 = new suscription(); + $this->suscriptions = $sus0->all4visitor($this->id); - if( $this->num_suscriptions != count($this->suscriptions) ) + if($this->num_suscriptions != count($this->suscriptions) ) { $this->num_suscriptions = count($this->suscriptions); $this->need_save = TRUE; @@ -222,7 +228,7 @@ public function last_stories() else { $story = new story(); - return $story->popular_stories(); + return $story->published_stories(); } } @@ -264,17 +270,20 @@ public function save() { if( $this->need_save AND $this->human() ) { - $age = $this->last_login_date - $this->first_login_date; - $data = array( 'nick' => $this->nick, + 'ip' => $this->ip, 'user_agent' => $this->user_agent, 'first_login_date' => $this->first_login_date, 'last_login_date' => $this->last_login_date, - 'human' => $this->human, + 'admin' => $this->admin, 'num_suscriptions' => $this->num_suscriptions, - 'mobile' => $this->mobile(), - 'age' => $this->age + 'num_stories' => $this->num_stories, + 'num_editions' => $this->num_editions, + 'num_comments' => $this->num_comments, + 'num_visits' => $this->num_visits, + 'points' => $this->points, + 'extra_points' => $this->extra_points ); if( $this->exists() ) @@ -302,15 +311,20 @@ public function force_insert($id) $data = array( '_id' => $this->id, 'nick' => $this->nick, + 'ip' => $this->ip, 'user_agent' => $this->user_agent, 'first_login_date' => $this->first_login_date, 'last_login_date' => $this->last_login_date, - 'human' => $this->human, + 'admin' => $this->admin, 'num_suscriptions' => $this->num_suscriptions, - 'mobile' => $this->mobile(), - 'age' => $this->age + 'num_stories' => $this->num_stories, + 'num_editions' => $this->num_editions, + 'num_comments' => $this->num_comments, + 'num_visits' => $this->num_visits, + 'points' => $this->points, + 'extra_points' => $this->extra_points ); - $this->add2history(__CLASS__.'::'.__FUNCTION__.'@insert'); + $this->add2history(__CLASS__.'::'.__FUNCTION__); $this->collection->insert($data); } @@ -320,7 +334,8 @@ public function delete() $this->collection->remove( array('_id' => $this->id) ); /// eliminamos las suscripciones - self::$sus0->delete4visitor($this->id); + $sus0 = new suscription(); + $sus0->delete4visitor($this->id); } public function all() @@ -341,30 +356,6 @@ public function last() return $vlist; } - public function usuals() - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - $vlist = array(); - $sort = array('age' => -1); - foreach($this->collection->find()->sort($sort)->limit(FS_MAX_STORIES) as $v) - { - if($v['age'] >= 300) - $vlist[] = new visitor($v); - } - return $vlist; - } - - public function count_usuals() - { - $this->add2history(__CLASS__.'::'.__FUNCTION__); - return $this->collection->find( array('age' => array('$gt'=>300)) )->count(); - } - - public function show_count_usuals() - { - return number_format($this->count_usuals(), 0, ',', '.'); - } - public function cron_job() { echo "\nEliminamos usuarios inactivos..."; diff --git a/sitemap.php b/sitemap.php index 03e70b6..0dbe228 100644 --- a/sitemap.php +++ b/sitemap.php @@ -29,7 +29,7 @@ $mongo = new fs_mongo(); $story = new story(); -foreach($story->popular_stories(FS_MAX_STORIES * 4) as $s) +foreach($story->published_stories() as $s) { if($s->native_lang) { diff --git a/view/comments.html b/view/comments.html new file mode 100644 index 0000000..3a5b975 --- /dev/null +++ b/view/comments.html @@ -0,0 +1,31 @@ +{include="header"} + +
+
+ + {loop="$cols"} +
+ {loop="$fsc->split_stories($fsc->comments, 3, $value1)"} + {if condition="$value2->thread"} +
+
+ {if condition="$fsc->visitor->nick==$value2->nick"} + dices: + {else} + @{$value2->nick} dice: + {/if} + {$value2->timesince()} +
+

+ {$value2->text} + leer más. +

+
+ {/if} + {/loop} +
+ {/loop} +
+
+ +{include="footer"} \ No newline at end of file diff --git a/view/css/bootstrap-theme.css b/view/css/bootstrap-theme.css new file mode 100644 index 0000000..a406992 --- /dev/null +++ b/view/css/bootstrap-theme.css @@ -0,0 +1,347 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); +} +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn:active, +.btn.active { + background-image: none; +} +.btn-default { + text-shadow: 0 1px 0 #fff; + background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #dbdbdb; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; +} +.btn-default:active, +.btn-default.active { + background-color: #e0e0e0; + border-color: #dbdbdb; +} +.btn-primary { + background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #2b669a; +} +.btn-primary:hover, +.btn-primary:focus { + background-color: #2d6ca2; + background-position: 0 -15px; +} +.btn-primary:active, +.btn-primary.active { + background-color: #2d6ca2; + border-color: #2b669a; +} +.btn-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #3e8f3e; +} +.btn-success:hover, +.btn-success:focus { + background-color: #419641; + background-position: 0 -15px; +} +.btn-success:active, +.btn-success.active { + background-color: #419641; + border-color: #3e8f3e; +} +.btn-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #28a4c9; +} +.btn-info:hover, +.btn-info:focus { + background-color: #2aabd2; + background-position: 0 -15px; +} +.btn-info:active, +.btn-info.active { + background-color: #2aabd2; + border-color: #28a4c9; +} +.btn-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #e38d13; +} +.btn-warning:hover, +.btn-warning:focus { + background-color: #eb9316; + background-position: 0 -15px; +} +.btn-warning:active, +.btn-warning.active { + background-color: #eb9316; + border-color: #e38d13; +} +.btn-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #b92c28; +} +.btn-danger:hover, +.btn-danger:focus { + background-color: #c12e2a; + background-position: 0 -15px; +} +.btn-danger:active, +.btn-danger.active { + background-color: #c12e2a; + border-color: #b92c28; +} +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-color: #e8e8e8; + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-color: #357ebd; + background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); + background-repeat: repeat-x; +} +.navbar-default { + background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); + background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); +} +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, .25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; +} +.navbar-inverse .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%); + background-image: linear-gradient(to bottom, #222 0%, #282828 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); +} +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); +} +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); +} +.alert-success { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + background-repeat: repeat-x; + border-color: #b2dba1; +} +.alert-info { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); + background-repeat: repeat-x; + border-color: #9acfea; +} +.alert-warning { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); + background-repeat: repeat-x; + border-color: #f5e79e; +} +.alert-danger { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); + background-repeat: repeat-x; + border-color: #dca7a7; +} +.progress { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar { + background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); + background-repeat: repeat-x; +} +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #3071a9; + background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); + background-repeat: repeat-x; + border-color: #3278b3; +} +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: 0 1px 2px rgba(0, 0, 0, .05); +} +.panel-default > .panel-heading { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.panel-primary > .panel-heading { + background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); + background-repeat: repeat-x; +} +.panel-success > .panel-heading { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); + background-repeat: repeat-x; +} +.panel-info > .panel-heading { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); + background-repeat: repeat-x; +} +.panel-warning > .panel-heading { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); + background-repeat: repeat-x; +} +.panel-danger > .panel-heading { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); + background-repeat: repeat-x; +} +.well { + background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; + border-color: #dcdcdc; + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); +} +/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/view/css/bootstrap-theme.css.map b/view/css/bootstrap-theme.css.map new file mode 100644 index 0000000..b36fc9a --- /dev/null +++ b/view/css/bootstrap-theme.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/theme.less","less/mixins.less"],"names":[],"mappings":"AAeA;AACA;AACA;AACA;AACA;AACA;EACE,wCAAA;ECoGA,2FAAA;EACQ,mFAAA;;ADhGR,YAAC;AAAD,YAAC;AAAD,YAAC;AAAD,SAAC;AAAD,YAAC;AAAD,WAAC;AACD,YAAC;AAAD,YAAC;AAAD,YAAC;AAAD,SAAC;AAAD,YAAC;AAAD,WAAC;EC8FD,wDAAA;EACQ,gDAAA;;ADnER,IAAC;AACD,IAAC;EACC,sBAAA;;AAKJ;EC4PI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;EAyB2C,yBAAA;EAA2B,kBAAA;;AAvBtE,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAeJ;EC2PI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAgBJ;EC0PI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAiBJ;ECyPI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,SAAC;AACD,SAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,SAAC;AACD,SAAC;EACC,yBAAA;EACA,qBAAA;;AAkBJ;ECwPI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,YAAC;AACD,YAAC;EACC,yBAAA;EACA,qBAAA;;AAmBJ;ECuPI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EAEA,sHAAA;EAoCF,mEAAA;ED7TA,2BAAA;EACA,qBAAA;;AAEA,WAAC;AACD,WAAC;EACC,yBAAA;EACA,4BAAA;;AAGF,WAAC;AACD,WAAC;EACC,yBAAA;EACA,qBAAA;;AA2BJ;AACA;EC6CE,kDAAA;EACQ,0CAAA;;ADpCV,cAAe,KAAK,IAAG;AACvB,cAAe,KAAK,IAAG;ECmOnB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EDpOF,yBAAA;;AAEF,cAAe,UAAU;AACzB,cAAe,UAAU,IAAG;AAC5B,cAAe,UAAU,IAAG;EC6NxB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED9NF,yBAAA;;AAUF;ECiNI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EAoCF,mEAAA;EDrPA,kBAAA;ECaA,2FAAA;EACQ,mFAAA;;ADjBV,eAOE,YAAY,UAAU;EC0MpB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EApMF,wDAAA;EACQ,gDAAA;;ADLV;AACA,WAAY,KAAK;EACf,8CAAA;;AAIF;EC+LI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EAoCF,mEAAA;;ADtOF,eAIE,YAAY,UAAU;EC2LpB,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;EApMF,uDAAA;EACQ,+CAAA;;ADCV,eASE;AATF,eAUE,YAAY,KAAK;EACf,yCAAA;;AAKJ;AACA;AACA;EACE,gBAAA;;AAUF;EACE,6CAAA;EChCA,0FAAA;EACQ,kFAAA;;AD2CV;ECqJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAKF;ECoJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAMF;ECmJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAOF;ECkJI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED5JF,qBAAA;;AAgBF;ECyII,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADlIJ;EC+HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADjIJ;EC8HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADhIJ;EC6HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;AD/HJ;EC4HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;AD9HJ;EC2HI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADtHJ;EACE,kBAAA;EC/EA,kDAAA;EACQ,0CAAA;;ADiFV,gBAAgB;AAChB,gBAAgB,OAAO;AACvB,gBAAgB,OAAO;EACrB,6BAAA;EC4GE,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED7GF,qBAAA;;AAUF;ECjGE,iDAAA;EACQ,yCAAA;;AD0GV,cAAe;ECsFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADxFJ,cAAe;ECqFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADvFJ,cAAe;ECoFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADtFJ,WAAY;ECmFR,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADrFJ,cAAe;ECkFX,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;ADpFJ,aAAc;ECiFV,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;;AD5EJ;ECyEI,kBAAkB,sDAAlB;EACA,kBAAkB,oDAAlB;EACA,2BAAA;EACA,sHAAA;ED1EF,qBAAA;EC1HA,yFAAA;EACQ,iFAAA","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-bg, 5%); @end-color: darken(@navbar-default-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-bg; @end-color: lighten(@navbar-inverse-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n}\n\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","//\n// Mixins\n// --------------------------------------------------\n\n\n// Utilities\n// -------------------------\n\n// Clearfix\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n\n// WebKit-style focus\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n\n// Center-align a block level element\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n\n// Sizing shortcuts\n.size(@width; @height) {\n width: @width;\n height: @height;\n}\n.square(@size) {\n .size(@size; @size);\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n &::-moz-placeholder { color: @color; // Firefox\n opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Text overflow\n// Requires inline-block or block for proper styling\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note\n// that we cannot chain the mixins together in Less, so they are repeated.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n\n\n\n// CSS3 PROPERTIES\n// --------------------------------------------------\n\n// Single side border-radius\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support the\n// standard `box-shadow` property.\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Transitions\n.transition(@transition) {\n -webkit-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n// Transformations\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n transform: rotate(@degrees);\n}\n.scale(@ratio; @ratio-y...) {\n -webkit-transform: scale(@ratio, @ratio-y);\n -ms-transform: scale(@ratio, @ratio-y); // IE9 only\n transform: scale(@ratio, @ratio-y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n transform: translate(@x, @y);\n}\n.skew(@x; @y) {\n -webkit-transform: skew(@x, @y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n transform: skew(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// User select\n// For selecting text on the page\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n\n// Resize anything\n.resizable(@direction) {\n resize: @direction; // Options: horizontal, vertical, both\n overflow: auto; // Safari fix\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Opacity\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n\n\n\n// GRADIENTS\n// --------------------------------------------------\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n\n// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n\n\n\n// Retina images\n//\n// Short retina mixin for setting background-image and -size\n\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// COMPONENT MIXINS\n// --------------------------------------------------\n\n// Horizontal dividers\n// -------------------------\n// Dividers (basically an hr) within dropdowns and nav lists\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n\n// Panels\n// -------------------------\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse .panel-body {\n border-top-color: @border;\n }\n }\n & > .panel-footer {\n + .panel-collapse .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n\n// Alerts\n// -------------------------\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n\n// Tables\n// -------------------------\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n\n// List Groups\n// -------------------------\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading { color: inherit; }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n\n// Button variants\n// -------------------------\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &:active,\n &.active,\n .open .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 8%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n// -------------------------\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n\n// Pagination\n// -------------------------\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n\n// Labels\n// -------------------------\n.label-variant(@color) {\n background-color: @color;\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n\n// Contextual backgrounds\n// -------------------------\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n\n// Typography\n// -------------------------\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n\n// Navbar vertical align\n// -------------------------\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n\n// Progress bars\n// -------------------------\n.progress-bar-variant(@color) {\n background-color: @color;\n .progress-striped & {\n #gradient > .striped();\n }\n}\n\n// Responsive utilities\n// -------------------------\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n\n\n// Grid System\n// -----------\n\n// Centered container element\n.container-fixed() {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n @media (min-width: @screen-xs-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-xs-column-push(@columns) {\n @media (min-width: @screen-xs-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-xs-column-pull(@columns) {\n @media (min-width: @screen-xs-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n\n// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-focus-border` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. ``\n// element gets special love because it's special, and that's a fact!\n\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Variables\n// --------------------------------------------------\n\n\n//== Colors\n//\n//## Gray and brand colors for use across Bootstrap.\n\n@gray-darker: lighten(#000, 13.5%); // #222\n@gray-dark: lighten(#000, 20%); // #333\n@gray: lighten(#000, 33.5%); // #555\n@gray-light: lighten(#000, 60%); // #999\n@gray-lighter: lighten(#000, 93.5%); // #eee\n\n@brand-primary: #428bca;\n@brand-success: #5cb85c;\n@brand-info: #5bc0de;\n@brand-warning: #f0ad4e;\n@brand-danger: #d9534f;\n\n\n//== Scaffolding\n//\n// ## Settings for some of the most global styles.\n\n//** Background color for ``.\n@body-bg: #fff;\n//** Global text color on ``.\n@text-color: @gray-dark;\n\n//** Global textual link color.\n@link-color: @brand-primary;\n//** Link hover color set via `darken()` function.\n@link-hover-color: darken(@link-color, 15%);\n\n\n//== Typography\n//\n//## Font, line-height, and color for body text, headings, and more.\n\n@font-family-sans-serif: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n@font-family-serif: Georgia, \"Times New Roman\", Times, serif;\n//** Default monospace fonts for ``, ``, and `
`.\n@font-family-monospace:   Menlo, Monaco, Consolas, \"Courier New\", monospace;\n@font-family-base:        @font-family-sans-serif;\n\n@font-size-base:          14px;\n@font-size-large:         ceil((@font-size-base * 1.25)); // ~18px\n@font-size-small:         ceil((@font-size-base * 0.85)); // ~12px\n\n@font-size-h1:            floor((@font-size-base * 2.6)); // ~36px\n@font-size-h2:            floor((@font-size-base * 2.15)); // ~30px\n@font-size-h3:            ceil((@font-size-base * 1.7)); // ~24px\n@font-size-h4:            ceil((@font-size-base * 1.25)); // ~18px\n@font-size-h5:            @font-size-base;\n@font-size-h6:            ceil((@font-size-base * 0.85)); // ~12px\n\n//** Unit-less `line-height` for use in components like buttons.\n@line-height-base:        1.428571429; // 20/14\n//** Computed \"line-height\" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.\n@line-height-computed:    floor((@font-size-base * @line-height-base)); // ~20px\n\n//** By default, this inherits from the ``.\n@headings-font-family:    inherit;\n@headings-font-weight:    500;\n@headings-line-height:    1.1;\n@headings-color:          inherit;\n\n\n//-- Iconography\n//\n//## Specify custom locations of the include Glyphicons icon font. Useful for those including Bootstrap via Bower.\n\n@icon-font-path:          \"../fonts/\";\n@icon-font-name:          \"glyphicons-halflings-regular\";\n@icon-font-svg-id:        \"glyphicons_halflingsregular\";\n\n//== Components\n//\n//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).\n\n@padding-base-vertical:     6px;\n@padding-base-horizontal:   12px;\n\n@padding-large-vertical:    10px;\n@padding-large-horizontal:  16px;\n\n@padding-small-vertical:    5px;\n@padding-small-horizontal:  10px;\n\n@padding-xs-vertical:       1px;\n@padding-xs-horizontal:     5px;\n\n@line-height-large:         1.33;\n@line-height-small:         1.5;\n\n@border-radius-base:        4px;\n@border-radius-large:       6px;\n@border-radius-small:       3px;\n\n//** Global color for active items (e.g., navs or dropdowns).\n@component-active-color:    #fff;\n//** Global background color for active items (e.g., navs or dropdowns).\n@component-active-bg:       @brand-primary;\n\n//** Width of the `border` for generating carets that indicator dropdowns.\n@caret-width-base:          4px;\n//** Carets increase slightly in size for larger components.\n@caret-width-large:         5px;\n\n\n//== Tables\n//\n//## Customizes the `.table` component with basic values, each used across all table variations.\n\n//** Padding for ``s and ``s.\n@table-cell-padding:            8px;\n//** Padding for cells in `.table-condensed`.\n@table-condensed-cell-padding:  5px;\n\n//** Default background color used for all tables.\n@table-bg:                      transparent;\n//** Background color used for `.table-striped`.\n@table-bg-accent:               #f9f9f9;\n//** Background color used for `.table-hover`.\n@table-bg-hover:                #f5f5f5;\n@table-bg-active:               @table-bg-hover;\n\n//** Border color for table and cell borders.\n@table-border-color:            #ddd;\n\n\n//== Buttons\n//\n//## For each of Bootstrap's buttons, define text, background and border color.\n\n@btn-font-weight:                normal;\n\n@btn-default-color:              #333;\n@btn-default-bg:                 #fff;\n@btn-default-border:             #ccc;\n\n@btn-primary-color:              #fff;\n@btn-primary-bg:                 @brand-primary;\n@btn-primary-border:             darken(@btn-primary-bg, 5%);\n\n@btn-success-color:              #fff;\n@btn-success-bg:                 @brand-success;\n@btn-success-border:             darken(@btn-success-bg, 5%);\n\n@btn-info-color:                 #fff;\n@btn-info-bg:                    @brand-info;\n@btn-info-border:                darken(@btn-info-bg, 5%);\n\n@btn-warning-color:              #fff;\n@btn-warning-bg:                 @brand-warning;\n@btn-warning-border:             darken(@btn-warning-bg, 5%);\n\n@btn-danger-color:               #fff;\n@btn-danger-bg:                  @brand-danger;\n@btn-danger-border:              darken(@btn-danger-bg, 5%);\n\n@btn-link-disabled-color:        @gray-light;\n\n\n//== Forms\n//\n//##\n\n//** `` background color\n@input-bg:                       #fff;\n//** `` background color\n@input-bg-disabled:              @gray-lighter;\n\n//** Text color for ``s\n@input-color:                    @gray;\n//** `` border color\n@input-border:                   #ccc;\n//** `` border radius\n@input-border-radius:            @border-radius-base;\n//** Border color for inputs on focus\n@input-border-focus:             #66afe9;\n\n//** Placeholder text color\n@input-color-placeholder:        @gray-light;\n\n//** Default `.form-control` height\n@input-height-base:              (@line-height-computed + (@padding-base-vertical * 2) + 2);\n//** Large `.form-control` height\n@input-height-large:             (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);\n//** Small `.form-control` height\n@input-height-small:             (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);\n\n@legend-color:                   @gray-dark;\n@legend-border-color:            #e5e5e5;\n\n//** Background color for textual input addons\n@input-group-addon-bg:           @gray-lighter;\n//** Border color for textual input addons\n@input-group-addon-border-color: @input-border;\n\n\n//== Dropdowns\n//\n//## Dropdown menu container and contents.\n\n//** Background for the dropdown menu.\n@dropdown-bg:                    #fff;\n//** Dropdown menu `border-color`.\n@dropdown-border:                rgba(0,0,0,.15);\n//** Dropdown menu `border-color` **for IE8**.\n@dropdown-fallback-border:       #ccc;\n//** Divider color for between dropdown items.\n@dropdown-divider-bg:            #e5e5e5;\n\n//** Dropdown link text color.\n@dropdown-link-color:            @gray-dark;\n//** Hover color for dropdown links.\n@dropdown-link-hover-color:      darken(@gray-dark, 5%);\n//** Hover background for dropdown links.\n@dropdown-link-hover-bg:         #f5f5f5;\n\n//** Active dropdown menu item text color.\n@dropdown-link-active-color:     @component-active-color;\n//** Active dropdown menu item background color.\n@dropdown-link-active-bg:        @component-active-bg;\n\n//** Disabled dropdown menu item background color.\n@dropdown-link-disabled-color:   @gray-light;\n\n//** Text color for headers within dropdown menus.\n@dropdown-header-color:          @gray-light;\n\n// Note: Deprecated @dropdown-caret-color as of v3.1.0\n@dropdown-caret-color:           #000;\n\n\n//-- Z-index master list\n//\n// Warning: Avoid customizing these values. They're used for a bird's eye view\n// of components dependent on the z-axis and are designed to all work together.\n//\n// Note: These variables are not generated into the Customizer.\n\n@zindex-navbar:            1000;\n@zindex-dropdown:          1000;\n@zindex-popover:           1010;\n@zindex-tooltip:           1030;\n@zindex-navbar-fixed:      1030;\n@zindex-modal-background:  1040;\n@zindex-modal:             1050;\n\n\n//== Media queries breakpoints\n//\n//## Define the breakpoints at which your layout will change, adapting to different screen sizes.\n\n// Extra small screen / phone\n// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1\n@screen-xs:                  480px;\n@screen-xs-min:              @screen-xs;\n@screen-phone:               @screen-xs-min;\n\n// Small screen / tablet\n// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1\n@screen-sm:                  768px;\n@screen-sm-min:              @screen-sm;\n@screen-tablet:              @screen-sm-min;\n\n// Medium screen / desktop\n// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1\n@screen-md:                  992px;\n@screen-md-min:              @screen-md;\n@screen-desktop:             @screen-md-min;\n\n// Large screen / wide desktop\n// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1\n@screen-lg:                  1200px;\n@screen-lg-min:              @screen-lg;\n@screen-lg-desktop:          @screen-lg-min;\n\n// So media queries don't overlap when required, provide a maximum\n@screen-xs-max:              (@screen-sm-min - 1);\n@screen-sm-max:              (@screen-md-min - 1);\n@screen-md-max:              (@screen-lg-min - 1);\n\n\n//== Grid system\n//\n//## Define your custom responsive grid.\n\n//** Number of columns in the grid.\n@grid-columns:              12;\n//** Padding between columns. Gets divided in half for the left and right.\n@grid-gutter-width:         30px;\n// Navbar collapse\n//** Point at which the navbar becomes uncollapsed.\n@grid-float-breakpoint:     @screen-sm-min;\n//** Point at which the navbar begins collapsing.\n@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);\n\n\n//== Container sizes\n//\n//## Define the maximum width of `.container` for different screen sizes.\n\n// Small screen / tablet\n@container-tablet:             ((720px + @grid-gutter-width));\n//** For `@screen-sm-min` and up.\n@container-sm:                 @container-tablet;\n\n// Medium screen / desktop\n@container-desktop:            ((940px + @grid-gutter-width));\n//** For `@screen-md-min` and up.\n@container-md:                 @container-desktop;\n\n// Large screen / wide desktop\n@container-large-desktop:      ((1140px + @grid-gutter-width));\n//** For `@screen-lg-min` and up.\n@container-lg:                 @container-large-desktop;\n\n\n//== Navbar\n//\n//##\n\n// Basics of a navbar\n@navbar-height:                    50px;\n@navbar-margin-bottom:             @line-height-computed;\n@navbar-border-radius:             @border-radius-base;\n@navbar-padding-horizontal:        floor((@grid-gutter-width / 2));\n@navbar-padding-vertical:          ((@navbar-height - @line-height-computed) / 2);\n@navbar-collapse-max-height:       340px;\n\n@navbar-default-color:             #777;\n@navbar-default-bg:                #f8f8f8;\n@navbar-default-border:            darken(@navbar-default-bg, 6.5%);\n\n// Navbar links\n@navbar-default-link-color:                #777;\n@navbar-default-link-hover-color:          #333;\n@navbar-default-link-hover-bg:             transparent;\n@navbar-default-link-active-color:         #555;\n@navbar-default-link-active-bg:            darken(@navbar-default-bg, 6.5%);\n@navbar-default-link-disabled-color:       #ccc;\n@navbar-default-link-disabled-bg:          transparent;\n\n// Navbar brand label\n@navbar-default-brand-color:               @navbar-default-link-color;\n@navbar-default-brand-hover-color:         darken(@navbar-default-brand-color, 10%);\n@navbar-default-brand-hover-bg:            transparent;\n\n// Navbar toggle\n@navbar-default-toggle-hover-bg:           #ddd;\n@navbar-default-toggle-icon-bar-bg:        #888;\n@navbar-default-toggle-border-color:       #ddd;\n\n\n// Inverted navbar\n// Reset inverted navbar basics\n@navbar-inverse-color:                      @gray-light;\n@navbar-inverse-bg:                         #222;\n@navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);\n\n// Inverted navbar links\n@navbar-inverse-link-color:                 @gray-light;\n@navbar-inverse-link-hover-color:           #fff;\n@navbar-inverse-link-hover-bg:              transparent;\n@navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;\n@navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);\n@navbar-inverse-link-disabled-color:        #444;\n@navbar-inverse-link-disabled-bg:           transparent;\n\n// Inverted navbar brand label\n@navbar-inverse-brand-color:                @navbar-inverse-link-color;\n@navbar-inverse-brand-hover-color:          #fff;\n@navbar-inverse-brand-hover-bg:             transparent;\n\n// Inverted navbar toggle\n@navbar-inverse-toggle-hover-bg:            #333;\n@navbar-inverse-toggle-icon-bar-bg:         #fff;\n@navbar-inverse-toggle-border-color:        #333;\n\n\n//== Navs\n//\n//##\n\n//=== Shared nav styles\n@nav-link-padding:                          10px 15px;\n@nav-link-hover-bg:                         @gray-lighter;\n\n@nav-disabled-link-color:                   @gray-light;\n@nav-disabled-link-hover-color:             @gray-light;\n\n@nav-open-link-hover-color:                 #fff;\n\n//== Tabs\n@nav-tabs-border-color:                     #ddd;\n\n@nav-tabs-link-hover-border-color:          @gray-lighter;\n\n@nav-tabs-active-link-hover-bg:             @body-bg;\n@nav-tabs-active-link-hover-color:          @gray;\n@nav-tabs-active-link-hover-border-color:   #ddd;\n\n@nav-tabs-justified-link-border-color:            #ddd;\n@nav-tabs-justified-active-link-border-color:     @body-bg;\n\n//== Pills\n@nav-pills-border-radius:                   @border-radius-base;\n@nav-pills-active-link-hover-bg:            @component-active-bg;\n@nav-pills-active-link-hover-color:         @component-active-color;\n\n\n//== Pagination\n//\n//##\n\n@pagination-color:                     @link-color;\n@pagination-bg:                        #fff;\n@pagination-border:                    #ddd;\n\n@pagination-hover-color:               @link-hover-color;\n@pagination-hover-bg:                  @gray-lighter;\n@pagination-hover-border:              #ddd;\n\n@pagination-active-color:              #fff;\n@pagination-active-bg:                 @brand-primary;\n@pagination-active-border:             @brand-primary;\n\n@pagination-disabled-color:            @gray-light;\n@pagination-disabled-bg:               #fff;\n@pagination-disabled-border:           #ddd;\n\n\n//== Pager\n//\n//##\n\n@pager-bg:                             @pagination-bg;\n@pager-border:                         @pagination-border;\n@pager-border-radius:                  15px;\n\n@pager-hover-bg:                       @pagination-hover-bg;\n\n@pager-active-bg:                      @pagination-active-bg;\n@pager-active-color:                   @pagination-active-color;\n\n@pager-disabled-color:                 @pagination-disabled-color;\n\n\n//== Jumbotron\n//\n//##\n\n@jumbotron-padding:              30px;\n@jumbotron-color:                inherit;\n@jumbotron-bg:                   @gray-lighter;\n@jumbotron-heading-color:        inherit;\n@jumbotron-font-size:            ceil((@font-size-base * 1.5));\n\n\n//== Form states and alerts\n//\n//## Define colors for form feedback states and, by default, alerts.\n\n@state-success-text:             #3c763d;\n@state-success-bg:               #dff0d8;\n@state-success-border:           darken(spin(@state-success-bg, -10), 5%);\n\n@state-info-text:                #31708f;\n@state-info-bg:                  #d9edf7;\n@state-info-border:              darken(spin(@state-info-bg, -10), 7%);\n\n@state-warning-text:             #8a6d3b;\n@state-warning-bg:               #fcf8e3;\n@state-warning-border:           darken(spin(@state-warning-bg, -10), 5%);\n\n@state-danger-text:              #a94442;\n@state-danger-bg:                #f2dede;\n@state-danger-border:            darken(spin(@state-danger-bg, -10), 5%);\n\n\n//== Tooltips\n//\n//##\n\n//** Tooltip max width\n@tooltip-max-width:           200px;\n//** Tooltip text color\n@tooltip-color:               #fff;\n//** Tooltip background color\n@tooltip-bg:                  #000;\n@tooltip-opacity:             .9;\n\n//** Tooltip arrow width\n@tooltip-arrow-width:         5px;\n//** Tooltip arrow color\n@tooltip-arrow-color:         @tooltip-bg;\n\n\n//== Popovers\n//\n//##\n\n//** Popover body background color\n@popover-bg:                          #fff;\n//** Popover maximum width\n@popover-max-width:                   276px;\n//** Popover border color\n@popover-border-color:                rgba(0,0,0,.2);\n//** Popover fallback border color\n@popover-fallback-border-color:       #ccc;\n\n//** Popover title background color\n@popover-title-bg:                    darken(@popover-bg, 3%);\n\n//** Popover arrow width\n@popover-arrow-width:                 10px;\n//** Popover arrow color\n@popover-arrow-color:                 #fff;\n\n//** Popover outer arrow width\n@popover-arrow-outer-width:           (@popover-arrow-width + 1);\n//** Popover outer arrow color\n@popover-arrow-outer-color:           fadein(@popover-border-color, 5%);\n//** Popover outer arrow fallback color\n@popover-arrow-outer-fallback-color:  darken(@popover-fallback-border-color, 20%);\n\n\n//== Labels\n//\n//##\n\n//** Default label background color\n@label-default-bg:            @gray-light;\n//** Primary label background color\n@label-primary-bg:            @brand-primary;\n//** Success label background color\n@label-success-bg:            @brand-success;\n//** Info label background color\n@label-info-bg:               @brand-info;\n//** Warning label background color\n@label-warning-bg:            @brand-warning;\n//** Danger label background color\n@label-danger-bg:             @brand-danger;\n\n//** Default label text color\n@label-color:                 #fff;\n//** Default text color of a linked label\n@label-link-hover-color:      #fff;\n\n\n//== Modals\n//\n//##\n\n//** Padding applied to the modal body\n@modal-inner-padding:         20px;\n\n//** Padding applied to the modal title\n@modal-title-padding:         15px;\n//** Modal title line-height\n@modal-title-line-height:     @line-height-base;\n\n//** Background color of modal content area\n@modal-content-bg:                             #fff;\n//** Modal content border color\n@modal-content-border-color:                   rgba(0,0,0,.2);\n//** Modal content border color **for IE8**\n@modal-content-fallback-border-color:          #999;\n\n//** Modal backdrop background color\n@modal-backdrop-bg:           #000;\n//** Modal backdrop opacity\n@modal-backdrop-opacity:      .5;\n//** Modal header border color\n@modal-header-border-color:   #e5e5e5;\n//** Modal footer border color\n@modal-footer-border-color:   @modal-header-border-color;\n\n@modal-lg:                    900px;\n@modal-md:                    600px;\n@modal-sm:                    300px;\n\n\n//== Alerts\n//\n//## Define alert colors, border radius, and padding.\n\n@alert-padding:               15px;\n@alert-border-radius:         @border-radius-base;\n@alert-link-font-weight:      bold;\n\n@alert-success-bg:            @state-success-bg;\n@alert-success-text:          @state-success-text;\n@alert-success-border:        @state-success-border;\n\n@alert-info-bg:               @state-info-bg;\n@alert-info-text:             @state-info-text;\n@alert-info-border:           @state-info-border;\n\n@alert-warning-bg:            @state-warning-bg;\n@alert-warning-text:          @state-warning-text;\n@alert-warning-border:        @state-warning-border;\n\n@alert-danger-bg:             @state-danger-bg;\n@alert-danger-text:           @state-danger-text;\n@alert-danger-border:         @state-danger-border;\n\n\n//== Progress bars\n//\n//##\n\n//** Background color of the whole progress component\n@progress-bg:                 #f5f5f5;\n//** Progress bar text color\n@progress-bar-color:          #fff;\n\n//** Default progress bar color\n@progress-bar-bg:             @brand-primary;\n//** Success progress bar color\n@progress-bar-success-bg:     @brand-success;\n//** Warning progress bar color\n@progress-bar-warning-bg:     @brand-warning;\n//** Danger progress bar color\n@progress-bar-danger-bg:      @brand-danger;\n//** Info progress bar color\n@progress-bar-info-bg:        @brand-info;\n\n\n//== List group\n//\n//##\n\n//** Background color on `.list-group-item`\n@list-group-bg:                 #fff;\n//** `.list-group-item` border color\n@list-group-border:             #ddd;\n//** List group border radius\n@list-group-border-radius:      @border-radius-base;\n\n//** Background color of single list elements on hover\n@list-group-hover-bg:           #f5f5f5;\n//** Text color of active list elements\n@list-group-active-color:       @component-active-color;\n//** Background color of active list elements\n@list-group-active-bg:          @component-active-bg;\n//** Border color of active list elements\n@list-group-active-border:      @list-group-active-bg;\n@list-group-active-text-color:  lighten(@list-group-active-bg, 40%);\n\n@list-group-link-color:         #555;\n@list-group-link-heading-color: #333;\n\n\n//== Panels\n//\n//##\n\n@panel-bg:                    #fff;\n@panel-body-padding:          15px;\n@panel-border-radius:         @border-radius-base;\n\n//** Border color for elements within panels\n@panel-inner-border:          #ddd;\n@panel-footer-bg:             #f5f5f5;\n\n@panel-default-text:          @gray-dark;\n@panel-default-border:        #ddd;\n@panel-default-heading-bg:    #f5f5f5;\n\n@panel-primary-text:          #fff;\n@panel-primary-border:        @brand-primary;\n@panel-primary-heading-bg:    @brand-primary;\n\n@panel-success-text:          @state-success-text;\n@panel-success-border:        @state-success-border;\n@panel-success-heading-bg:    @state-success-bg;\n\n@panel-info-text:             @state-info-text;\n@panel-info-border:           @state-info-border;\n@panel-info-heading-bg:       @state-info-bg;\n\n@panel-warning-text:          @state-warning-text;\n@panel-warning-border:        @state-warning-border;\n@panel-warning-heading-bg:    @state-warning-bg;\n\n@panel-danger-text:           @state-danger-text;\n@panel-danger-border:         @state-danger-border;\n@panel-danger-heading-bg:     @state-danger-bg;\n\n\n//== Thumbnails\n//\n//##\n\n//** Padding around the thumbnail image\n@thumbnail-padding:           4px;\n//** Thumbnail background color\n@thumbnail-bg:                @body-bg;\n//** Thumbnail border color\n@thumbnail-border:            #ddd;\n//** Thumbnail border radius\n@thumbnail-border-radius:     @border-radius-base;\n\n//** Custom text color for thumbnail captions\n@thumbnail-caption-color:     @text-color;\n//** Padding around the thumbnail caption\n@thumbnail-caption-padding:   9px;\n\n\n//== Wells\n//\n//##\n\n@well-bg:                     #f5f5f5;\n@well-border:                 darken(@well-bg, 7%);\n\n\n//== Badges\n//\n//##\n\n@badge-color:                 #fff;\n//** Linked badge text color on hover\n@badge-link-hover-color:      #fff;\n@badge-bg:                    @gray-light;\n\n//** Badge text color in active nav link\n@badge-active-color:          @link-color;\n//** Badge background color in active nav link\n@badge-active-bg:             #fff;\n\n@badge-font-weight:           bold;\n@badge-line-height:           1;\n@badge-border-radius:         10px;\n\n\n//== Breadcrumbs\n//\n//##\n\n@breadcrumb-padding-vertical:   8px;\n@breadcrumb-padding-horizontal: 15px;\n//** Breadcrumb background color\n@breadcrumb-bg:                 #f5f5f5;\n//** Breadcrumb text color\n@breadcrumb-color:              #ccc;\n//** Text color of current page in the breadcrumb\n@breadcrumb-active-color:       @gray-light;\n//** Textual separator for between breadcrumb elements\n@breadcrumb-separator:          \"/\";\n\n\n//== Carousel\n//\n//##\n\n@carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6);\n\n@carousel-control-color:                      #fff;\n@carousel-control-width:                      15%;\n@carousel-control-opacity:                    .5;\n@carousel-control-font-size:                  20px;\n\n@carousel-indicator-active-bg:                #fff;\n@carousel-indicator-border-color:             #fff;\n\n@carousel-caption-color:                      #fff;\n\n\n//== Close\n//\n//##\n\n@close-font-weight:           bold;\n@close-color:                 #000;\n@close-text-shadow:           0 1px 0 #fff;\n\n\n//== Code\n//\n//##\n\n@code-color:                  #c7254e;\n@code-bg:                     #f9f2f4;\n\n@kbd-color:                   #fff;\n@kbd-bg:                      #333;\n\n@pre-bg:                      #f5f5f5;\n@pre-color:                   @gray-dark;\n@pre-border-color:            #ccc;\n@pre-scrollable-max-height:   340px;\n\n\n//== Type\n//\n//##\n\n//** Text muted color\n@text-muted:                  @gray-light;\n//** Abbreviations and acronyms border color\n@abbr-border-color:           @gray-light;\n//** Headings small color\n@headings-small-color:        @gray-light;\n//** Blockquote small color\n@blockquote-small-color:      @gray-light;\n//** Blockquote font size\n@blockquote-font-size:        (@font-size-base * 1.25);\n//** Blockquote border color\n@blockquote-border-color:     @gray-lighter;\n//** Page header border color\n@page-header-border-color:    @gray-lighter;\n\n\n//== Miscellaneous\n//\n//##\n\n//** Horizontal line color.\n@hr-border:                   @gray-lighter;\n\n//** Horizontal offset for forms and lists.\n@component-offset-horizontal: 180px;\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n  display: block;\n  padding: @thumbnail-padding;\n  margin-bottom: @line-height-computed;\n  line-height: @line-height-base;\n  background-color: @thumbnail-bg;\n  border: 1px solid @thumbnail-border;\n  border-radius: @thumbnail-border-radius;\n  .transition(all .2s ease-in-out);\n\n  > img,\n  a > img {\n    &:extend(.img-responsive);\n    margin-left: auto;\n    margin-right: auto;\n  }\n\n  // Add a hover state for linked versions only\n  a&:hover,\n  a&:focus,\n  a&.active {\n    border-color: @link-color;\n  }\n\n  // Image captions\n  .caption {\n    padding: @thumbnail-caption-padding;\n    color: @thumbnail-caption-color;\n  }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n  position: relative;\n}\n\n.carousel-inner {\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n\n  > .item {\n    display: none;\n    position: relative;\n    .transition(.6s ease-in-out left);\n\n    // Account for jankitude on images\n    > img,\n    > a > img {\n      &:extend(.img-responsive);\n      line-height: 1;\n    }\n  }\n\n  > .active,\n  > .next,\n  > .prev { display: block; }\n\n  > .active {\n    left: 0;\n  }\n\n  > .next,\n  > .prev {\n    position: absolute;\n    top: 0;\n    width: 100%;\n  }\n\n  > .next {\n    left: 100%;\n  }\n  > .prev {\n    left: -100%;\n  }\n  > .next.left,\n  > .prev.right {\n    left: 0;\n  }\n\n  > .active.left {\n    left: -100%;\n  }\n  > .active.right {\n    left: 100%;\n  }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n  position: absolute;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  width: @carousel-control-width;\n  .opacity(@carousel-control-opacity);\n  font-size: @carousel-control-font-size;\n  color: @carousel-control-color;\n  text-align: center;\n  text-shadow: @carousel-text-shadow;\n  // We can't have this transition here because WebKit cancels the carousel\n  // animation if you trip this while in the middle of another animation.\n\n  // Set gradients for backgrounds\n  &.left {\n    #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n  }\n  &.right {\n    left: auto;\n    right: 0;\n    #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n  }\n\n  // Hover/focus state\n  &:hover,\n  &:focus {\n    outline: none;\n    color: @carousel-control-color;\n    text-decoration: none;\n    .opacity(.9);\n  }\n\n  // Toggles\n  .icon-prev,\n  .icon-next,\n  .glyphicon-chevron-left,\n  .glyphicon-chevron-right {\n    position: absolute;\n    top: 50%;\n    z-index: 5;\n    display: inline-block;\n  }\n  .icon-prev,\n  .glyphicon-chevron-left {\n    left: 50%;\n  }\n  .icon-next,\n  .glyphicon-chevron-right {\n    right: 50%;\n  }\n  .icon-prev,\n  .icon-next {\n    width:  20px;\n    height: 20px;\n    margin-top: -10px;\n    margin-left: -10px;\n    font-family: serif;\n  }\n\n  .icon-prev {\n    &:before {\n      content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n    }\n  }\n  .icon-next {\n    &:before {\n      content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n    }\n  }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n  position: absolute;\n  bottom: 10px;\n  left: 50%;\n  z-index: 15;\n  width: 60%;\n  margin-left: -30%;\n  padding-left: 0;\n  list-style: none;\n  text-align: center;\n\n  li {\n    display: inline-block;\n    width:  10px;\n    height: 10px;\n    margin: 1px;\n    text-indent: -999px;\n    border: 1px solid @carousel-indicator-border-color;\n    border-radius: 10px;\n    cursor: pointer;\n\n    // IE8-9 hack for event handling\n    //\n    // Internet Explorer 8-9 does not support clicks on elements without a set\n    // `background-color`. We cannot use `filter` since that's not viewed as a\n    // background color by the browser. Thus, a hack is needed.\n    //\n    // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n    // set alpha transparency for the best results possible.\n    background-color: #000 \\9; // IE8\n    background-color: rgba(0,0,0,0); // IE9\n  }\n  .active {\n    margin: 0;\n    width:  12px;\n    height: 12px;\n    background-color: @carousel-indicator-active-bg;\n  }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n  position: absolute;\n  left: 15%;\n  right: 15%;\n  bottom: 20px;\n  z-index: 10;\n  padding-top: 20px;\n  padding-bottom: 20px;\n  color: @carousel-caption-color;\n  text-align: center;\n  text-shadow: @carousel-text-shadow;\n  & .btn {\n    text-shadow: none; // No shadow for button elements in carousel-caption\n  }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n  // Scale up the controls a smidge\n  .carousel-control {\n    .glyphicon-chevron-left,\n    .glyphicon-chevron-right,\n    .icon-prev,\n    .icon-next {\n      width: 30px;\n      height: 30px;\n      margin-top: -15px;\n      margin-left: -15px;\n      font-size: 30px;\n    }\n  }\n\n  // Show and left align the captions\n  .carousel-caption {\n    left: 20%;\n    right: 20%;\n    padding-bottom: 30px;\n  }\n\n  // Move up the indicators\n  .carousel-indicators {\n    bottom: 20px;\n  }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n  font-family: @headings-font-family;\n  font-weight: @headings-font-weight;\n  line-height: @headings-line-height;\n  color: @headings-color;\n\n  small,\n  .small {\n    font-weight: normal;\n    line-height: 1;\n    color: @headings-small-color;\n  }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n  margin-top: @line-height-computed;\n  margin-bottom: (@line-height-computed / 2);\n\n  small,\n  .small {\n    font-size: 65%;\n  }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n  margin-top: (@line-height-computed / 2);\n  margin-bottom: (@line-height-computed / 2);\n\n  small,\n  .small {\n    font-size: 75%;\n  }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n  margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n  margin-bottom: @line-height-computed;\n  font-size: floor((@font-size-base * 1.15));\n  font-weight: 200;\n  line-height: 1.4;\n\n  @media (min-width: @screen-sm-min) {\n    font-size: (@font-size-base * 1.5);\n  }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: 14px base font * 85% = about 12px\nsmall,\n.small  { font-size: 85%; }\n\n// Undo browser default styling\ncite    { font-style: normal; }\n\n// Alignment\n.text-left           { text-align: left; }\n.text-right          { text-align: right; }\n.text-center         { text-align: center; }\n.text-justify        { text-align: justify; }\n\n// Contextual colors\n.text-muted {\n  color: @text-muted;\n}\n.text-primary {\n  .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n  .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n  .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n  .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n  .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n  // Given the contrast here, this is the only class to have its color inverted\n  // automatically.\n  color: #fff;\n  .bg-variant(@brand-primary);\n}\n.bg-success {\n  .bg-variant(@state-success-bg);\n}\n.bg-info {\n  .bg-variant(@state-info-bg);\n}\n.bg-warning {\n  .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n  .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n  padding-bottom: ((@line-height-computed / 2) - 1);\n  margin: (@line-height-computed * 2) 0 @line-height-computed;\n  border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// --------------------------------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n  margin-top: 0;\n  margin-bottom: (@line-height-computed / 2);\n  ul,\n  ol {\n    margin-bottom: 0;\n  }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n  padding-left: 0;\n  list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n  .list-unstyled();\n  margin-left: -5px;\n\n  > li {\n    display: inline-block;\n    padding-left: 5px;\n    padding-right: 5px;\n  }\n}\n\n// Description Lists\ndl {\n  margin-top: 0; // Remove browser default\n  margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n  line-height: @line-height-base;\n}\ndt {\n  font-weight: bold;\n}\ndd {\n  margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n@media (min-width: @grid-float-breakpoint) {\n  .dl-horizontal {\n    dt {\n      float: left;\n      width: (@component-offset-horizontal - 20);\n      clear: left;\n      text-align: right;\n      .text-overflow();\n    }\n    dd {\n      margin-left: @component-offset-horizontal;\n      &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n    }\n  }\n}\n\n// MISC\n// ----\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n  cursor: help;\n  border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n  font-size: 90%;\n  text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n  padding: (@line-height-computed / 2) @line-height-computed;\n  margin: 0 0 @line-height-computed;\n  font-size: @blockquote-font-size;\n  border-left: 5px solid @blockquote-border-color;\n\n  p,\n  ul,\n  ol {\n    &:last-child {\n      margin-bottom: 0;\n    }\n  }\n\n  // Note: Deprecated small and .small as of v3.1.0\n  // Context: https://github.com/twbs/bootstrap/issues/11660\n  footer,\n  small,\n  .small {\n    display: block;\n    font-size: 80%; // back to default font-size\n    line-height: @line-height-base;\n    color: @blockquote-small-color;\n\n    &:before {\n      content: '\\2014 \\00A0'; // em dash, nbsp\n    }\n  }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n  padding-right: 15px;\n  padding-left: 0;\n  border-right: 5px solid @blockquote-border-color;\n  border-left: 0;\n  text-align: right;\n\n  // Account for citation\n  footer,\n  small,\n  .small {\n    &:before { content: ''; }\n    &:after {\n      content: '\\00A0 \\2014'; // nbsp, em dash\n    }\n  }\n}\n\n// Quotes\nblockquote:before,\nblockquote:after {\n  content: \"\";\n}\n\n// Addresses\naddress {\n  margin-bottom: @line-height-computed;\n  font-style: normal;\n  line-height: @line-height-base;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n  font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: @code-color;\n  background-color: @code-bg;\n  white-space: nowrap;\n  border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n  padding: 2px 4px;\n  font-size: 90%;\n  color: @kbd-color;\n  background-color: @kbd-bg;\n  border-radius: @border-radius-small;\n  box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n}\n\n// Blocks of code\npre {\n  display: block;\n  padding: ((@line-height-computed - 1) / 2);\n  margin: 0 0 (@line-height-computed / 2);\n  font-size: (@font-size-base - 1); // 14px to 13px\n  line-height: @line-height-base;\n  word-break: break-all;\n  word-wrap: break-word;\n  color: @pre-color;\n  background-color: @pre-bg;\n  border: 1px solid @pre-border-color;\n  border-radius: @border-radius-base;\n\n  // Account for some code outputs that place code tags in pre tags\n  code {\n    padding: 0;\n    font-size: inherit;\n    color: inherit;\n    white-space: pre-wrap;\n    background-color: transparent;\n    border-radius: 0;\n  }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n  max-height: @pre-scrollable-max-height;\n  overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n  .container-fixed();\n\n  @media (min-width: @screen-sm-min) {\n    width: @container-sm;\n  }\n  @media (min-width: @screen-md-min) {\n    width: @container-md;\n  }\n  @media (min-width: @screen-lg-min) {\n    width: @container-lg;\n  }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n  .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n  .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n  .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n  .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n  .make-grid(lg);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n  max-width: 100%;\n  background-color: @table-bg;\n}\nth {\n  text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n  width: 100%;\n  margin-bottom: @line-height-computed;\n  // Cells\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        padding: @table-cell-padding;\n        line-height: @line-height-base;\n        vertical-align: top;\n        border-top: 1px solid @table-border-color;\n      }\n    }\n  }\n  // Bottom align for column headings\n  > thead > tr > th {\n    vertical-align: bottom;\n    border-bottom: 2px solid @table-border-color;\n  }\n  // Remove top border from thead by default\n  > caption + thead,\n  > colgroup + thead,\n  > thead:first-child {\n    > tr:first-child {\n      > th,\n      > td {\n        border-top: 0;\n      }\n    }\n  }\n  // Account for multiple tbody instances\n  > tbody + tbody {\n    border-top: 2px solid @table-border-color;\n  }\n\n  // Nesting\n  .table {\n    background-color: @body-bg;\n  }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        padding: @table-condensed-cell-padding;\n      }\n    }\n  }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n  border: 1px solid @table-border-color;\n  > thead,\n  > tbody,\n  > tfoot {\n    > tr {\n      > th,\n      > td {\n        border: 1px solid @table-border-color;\n      }\n    }\n  }\n  > thead > tr {\n    > th,\n    > td {\n      border-bottom-width: 2px;\n    }\n  }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n  > tbody > tr:nth-child(odd) {\n    > td,\n    > th {\n      background-color: @table-bg-accent;\n    }\n  }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n  > tbody > tr:hover {\n    > td,\n    > th {\n      background-color: @table-bg-hover;\n    }\n  }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n  position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)\n  float: none;\n  display: table-column;\n}\ntable {\n  td,\n  th {\n    &[class*=\"col-\"] {\n      position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)\n      float: none;\n      display: table-cell;\n    }\n  }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n@media (max-width: @screen-xs-max) {\n  .table-responsive {\n    width: 100%;\n    margin-bottom: (@line-height-computed * 0.75);\n    overflow-y: hidden;\n    overflow-x: scroll;\n    -ms-overflow-style: -ms-autohiding-scrollbar;\n    border: 1px solid @table-border-color;\n    -webkit-overflow-scrolling: touch;\n\n    // Tighten up spacing\n    > .table {\n      margin-bottom: 0;\n\n      // Ensure the content doesn't wrap\n      > thead,\n      > tbody,\n      > tfoot {\n        > tr {\n          > th,\n          > td {\n            white-space: nowrap;\n          }\n        }\n      }\n    }\n\n    // Special overrides for the bordered tables\n    > .table-bordered {\n      border: 0;\n\n      // Nuke the appropriate borders so that the parent can handle them\n      > thead,\n      > tbody,\n      > tfoot {\n        > tr {\n          > th:first-child,\n          > td:first-child {\n            border-left: 0;\n          }\n          > th:last-child,\n          > td:last-child {\n            border-right: 0;\n          }\n        }\n      }\n\n      // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n      // chances are there will be only one `tr` in a `thead` and that would\n      // remove the border altogether.\n      > tbody,\n      > tfoot {\n        > tr:last-child {\n          > th,\n          > td {\n            border-bottom: 0;\n          }\n        }\n      }\n\n    }\n  }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n  padding: 0;\n  margin: 0;\n  border: 0;\n  // Chrome and Firefox set a `min-width: -webkit-min-content;` on fieldsets,\n  // so we reset that to ensure it behaves more like a standard block element.\n  // See https://github.com/twbs/bootstrap/issues/12359.\n  min-width: 0;\n}\n\nlegend {\n  display: block;\n  width: 100%;\n  padding: 0;\n  margin-bottom: @line-height-computed;\n  font-size: (@font-size-base * 1.5);\n  line-height: inherit;\n  color: @legend-color;\n  border: 0;\n  border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n  display: inline-block;\n  margin-bottom: 5px;\n  font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n  .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n  margin: 4px 0 0;\n  margin-top: 1px \\9; /* IE8-9 */\n  line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n  display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n  display: block;\n  width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n  height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n  .tab-focus();\n}\n\n// Adjust output element\noutput {\n  display: block;\n  padding-top: (@padding-base-vertical + 1);\n  font-size: @font-size-base;\n  line-height: @line-height-base;\n  color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n  display: block;\n  width: 100%;\n  height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n  padding: @padding-base-vertical @padding-base-horizontal;\n  font-size: @font-size-base;\n  line-height: @line-height-base;\n  color: @input-color;\n  background-color: @input-bg;\n  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n  border: 1px solid @input-border;\n  border-radius: @input-border-radius;\n  .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n  .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n  // Customize the `:focus` state to imitate native WebKit styles.\n  .form-control-focus();\n\n  // Placeholder\n  .placeholder();\n\n  // Disabled and read-only inputs\n  //\n  // HTML5 says that controls under a fieldset > legend:first-child won't be\n  // disabled if the fieldset is disabled. Due to implementation difficulty, we\n  // don't honor that edge case; we style them as disabled anyway.\n  &[disabled],\n  &[readonly],\n  fieldset[disabled] & {\n    cursor: not-allowed;\n    background-color: @input-bg-disabled;\n    opacity: 1; // iOS fix for unreadable disabled content\n  }\n\n  // Reset height for `textarea`s\n  textarea& {\n    height: auto;\n  }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n  -webkit-appearance: none;\n}\n\n\n// Special styles for iOS date input\n//\n// In Mobile Safari, date inputs require a pixel line-height that matches the\n// given height of the input.\n\ninput[type=\"date\"] {\n  line-height: @input-height-base;\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n  margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n  display: block;\n  min-height: @line-height-computed; // clear the floating input if there is no label text\n  margin-top: 10px;\n  margin-bottom: 10px;\n  padding-left: 20px;\n  label {\n    display: inline;\n    font-weight: normal;\n    cursor: pointer;\n  }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n  float: left;\n  margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n  margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n  display: inline-block;\n  padding-left: 20px;\n  margin-bottom: 0;\n  vertical-align: middle;\n  font-weight: normal;\n  cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n  margin-top: 0;\n  margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"],\n.radio,\n.radio-inline,\n.checkbox,\n.checkbox-inline {\n  &[disabled],\n  fieldset[disabled] & {\n    cursor: not-allowed;\n  }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n\n.input-sm {\n  .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n.input-lg {\n  .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n  // Enable absolute positioning\n  position: relative;\n\n  // Ensure icons don't overlap text\n  .form-control {\n    padding-right: (@input-height-base * 1.25);\n  }\n\n  // Feedback icon (requires .glyphicon classes)\n  .form-control-feedback {\n    position: absolute;\n    top: (@line-height-computed + 5); // Height of the `label` and its margin\n    right: 0;\n    display: block;\n    width: @input-height-base;\n    height: @input-height-base;\n    line-height: @input-height-base;\n    text-align: center;\n  }\n}\n\n// Feedback states\n.has-success {\n  .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n  .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n  .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n  margin-bottom: 0; // Remove default margin from `p`\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n  display: block; // account for any element using help-block\n  margin-top: 5px;\n  margin-bottom: 10px;\n  color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n  // Kick in the inline\n  @media (min-width: @screen-sm-min) {\n    // Inline-block all the things for \"inline\"\n    .form-group {\n      display: inline-block;\n      margin-bottom: 0;\n      vertical-align: middle;\n    }\n\n    // In navbar-form, allow folks to *not* use `.form-group`\n    .form-control {\n      display: inline-block;\n      width: auto; // Prevent labels from stacking above inputs in `.form-group`\n      vertical-align: middle;\n    }\n    // Input groups need that 100% width though\n    .input-group > .form-control {\n      width: 100%;\n    }\n\n    .control-label {\n      margin-bottom: 0;\n      vertical-align: middle;\n    }\n\n    // Remove default margin on radios/checkboxes that were used for stacking, and\n    // then undo the floating of radios and checkboxes to match (which also avoids\n    // a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969).\n    .radio,\n    .checkbox {\n      display: inline-block;\n      margin-top: 0;\n      margin-bottom: 0;\n      padding-left: 0;\n      vertical-align: middle;\n    }\n    .radio input[type=\"radio\"],\n    .checkbox input[type=\"checkbox\"] {\n      float: none;\n      margin-left: 0;\n    }\n\n    // Validation states\n    //\n    // Reposition the icon because it's now within a grid column and columns have\n    // `position: relative;` on them. Also accounts for the grid gutter padding.\n    .has-feedback .form-control-feedback {\n      top: 0;\n    }\n  }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n  // Consistent vertical alignment of labels, radios, and checkboxes\n  .control-label,\n  .radio,\n  .checkbox,\n  .radio-inline,\n  .checkbox-inline {\n    margin-top: 0;\n    margin-bottom: 0;\n    padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n  }\n  // Account for padding we're adding to ensure the alignment and of help text\n  // and other content below items\n  .radio,\n  .checkbox {\n    min-height: (@line-height-computed + (@padding-base-vertical + 1));\n  }\n\n  // Make form groups behave like rows\n  .form-group {\n    .make-row();\n  }\n\n  .form-control-static {\n    padding-top: (@padding-base-vertical + 1);\n  }\n\n  // Only right align form labels here when the columns stop stacking\n  @media (min-width: @screen-sm-min) {\n    .control-label {\n      text-align: right;\n    }\n  }\n\n  // Validation states\n  //\n  // Reposition the icon because it's now within a grid column and columns have\n  // `position: relative;` on them. Also accounts for the grid gutter padding.\n  .has-feedback .form-control-feedback {\n    top: 0;\n    right: (@grid-gutter-width / 2);\n  }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n  display: inline-block;\n  margin-bottom: 0; // For input.btn\n  font-weight: @btn-font-weight;\n  text-align: center;\n  vertical-align: middle;\n  cursor: pointer;\n  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n  border: 1px solid transparent;\n  white-space: nowrap;\n  .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);\n  .user-select(none);\n\n  &,\n  &:active,\n  &.active {\n    &:focus {\n      .tab-focus();\n    }\n  }\n\n  &:hover,\n  &:focus {\n    color: @btn-default-color;\n    text-decoration: none;\n  }\n\n  &:active,\n  &.active {\n    outline: 0;\n    background-image: none;\n    .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n  }\n\n  &.disabled,\n  &[disabled],\n  fieldset[disabled] & {\n    cursor: not-allowed;\n    pointer-events: none; // Future-proof disabling of clicks\n    .opacity(.65);\n    .box-shadow(none);\n  }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n  .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n  .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n  .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n  .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n  .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n  .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n  color: @link-color;\n  font-weight: normal;\n  cursor: pointer;\n  border-radius: 0;\n\n  &,\n  &:active,\n  &[disabled],\n  fieldset[disabled] & {\n    background-color: transparent;\n    .box-shadow(none);\n  }\n  &,\n  &:hover,\n  &:focus,\n  &:active {\n    border-color: transparent;\n  }\n  &:hover,\n  &:focus {\n    color: @link-hover-color;\n    text-decoration: underline;\n    background-color: transparent;\n  }\n  &[disabled],\n  fieldset[disabled] & {\n    &:hover,\n    &:focus {\n      color: @btn-link-disabled-color;\n      text-decoration: none;\n    }\n  }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n  // line-height: ensure even-numbered height of button next to large input\n  .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm {\n  // line-height: ensure proper height of button next to small input\n  .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n  .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n  display: block;\n  width: 100%;\n  padding-left: 0;\n  padding-right: 0;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n  margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n  &.btn-block {\n    width: 100%;\n  }\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n  position: relative;\n  display: inline-block;\n  vertical-align: middle; // match .btn alignment given font-size hack above\n  > .btn {\n    position: relative;\n    float: left;\n    // Bring the \"active\" button to the front\n    &:hover,\n    &:focus,\n    &:active,\n    &.active {\n      z-index: 2;\n    }\n    &:focus {\n      // Remove focus outline when dropdown JS adds it after closing the menu\n      outline: none;\n    }\n  }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n  .btn + .btn,\n  .btn + .btn-group,\n  .btn-group + .btn,\n  .btn-group + .btn-group {\n    margin-left: -1px;\n  }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n  margin-left: -5px; // Offset the first child's margin\n  &:extend(.clearfix all);\n\n  .btn-group,\n  .input-group {\n    float: left;\n  }\n  > .btn,\n  > .btn-group,\n  > .input-group {\n    margin-left: 5px;\n  }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n  border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n  margin-left: 0;\n  &:not(:last-child):not(.dropdown-toggle) {\n    .border-right-radius(0);\n  }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n  .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n  float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group > .btn-group:first-child {\n  > .btn:last-child,\n  > .dropdown-toggle {\n    .border-right-radius(0);\n  }\n}\n.btn-group > .btn-group:last-child > .btn:first-child {\n  .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n  outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n  padding-left: 8px;\n  padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n  padding-left: 12px;\n  padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n  .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n  // Show no shadow for `.btn-link` since it has no other button styles.\n  &.btn-link {\n    .box-shadow(none);\n  }\n}\n\n\n// Reposition the caret\n.btn .caret {\n  margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n  border-width: @caret-width-large @caret-width-large 0;\n  border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n  border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n  > .btn,\n  > .btn-group,\n  > .btn-group > .btn {\n    display: block;\n    float: none;\n    width: 100%;\n    max-width: 100%;\n  }\n\n  // Clear floats so dropdown menus can be properly placed\n  > .btn-group {\n    &:extend(.clearfix all);\n    > .btn {\n      float: none;\n    }\n  }\n\n  > .btn + .btn,\n  > .btn + .btn-group,\n  > .btn-group + .btn,\n  > .btn-group + .btn-group {\n    margin-top: -1px;\n    margin-left: 0;\n  }\n}\n\n.btn-group-vertical > .btn {\n  &:not(:first-child):not(:last-child) {\n    border-radius: 0;\n  }\n  &:first-child:not(:last-child) {\n    border-top-right-radius: @border-radius-base;\n    .border-bottom-radius(0);\n  }\n  &:last-child:not(:first-child) {\n    border-bottom-left-radius: @border-radius-base;\n    .border-top-radius(0);\n  }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n  border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n  > .btn:last-child,\n  > .dropdown-toggle {\n    .border-bottom-radius(0);\n  }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n  .border-top-radius(0);\n}\n\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n  display: table;\n  width: 100%;\n  table-layout: fixed;\n  border-collapse: separate;\n  > .btn,\n  > .btn-group {\n    float: none;\n    display: table-cell;\n    width: 1%;\n  }\n  > .btn-group .btn {\n    width: 100%;\n  }\n}\n\n\n// Checkbox and radio options\n[data-toggle=\"buttons\"] > .btn > input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn > input[type=\"checkbox\"] {\n  display: none;\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552.\n\n.fade {\n  opacity: 0;\n  .transition(opacity .15s linear);\n  &.in {\n    opacity: 1;\n  }\n}\n\n.collapse {\n  display: none;\n  &.in {\n    display: block;\n  }\n}\n.collapsing {\n  position: relative;\n  height: 0;\n  overflow: hidden;\n  .transition(height .35s ease);\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n//  Star\n\n// Import the fonts\n@font-face {\n  font-family: 'Glyphicons Halflings';\n  src: ~\"url('@{icon-font-path}@{icon-font-name}.eot')\";\n  src: ~\"url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype')\",\n       ~\"url('@{icon-font-path}@{icon-font-name}.woff') format('woff')\",\n       ~\"url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype')\",\n       ~\"url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg')\";\n}\n\n// Catchall baseclass\n.glyphicon {\n  position: relative;\n  top: 1px;\n  display: inline-block;\n  font-family: 'Glyphicons Halflings';\n  font-style: normal;\n  font-weight: normal;\n  line-height: 1;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk               { &:before { content: \"\\2a\"; } }\n.glyphicon-plus                   { &:before { content: \"\\2b\"; } }\n.glyphicon-euro                   { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus                  { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud                  { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope               { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil                 { &:before { content: \"\\270f\"; } }\n.glyphicon-glass                  { &:before { content: \"\\e001\"; } }\n.glyphicon-music                  { &:before { content: \"\\e002\"; } }\n.glyphicon-search                 { &:before { content: \"\\e003\"; } }\n.glyphicon-heart                  { &:before { content: \"\\e005\"; } }\n.glyphicon-star                   { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty             { &:before { content: \"\\e007\"; } }\n.glyphicon-user                   { &:before { content: \"\\e008\"; } }\n.glyphicon-film                   { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large               { &:before { content: \"\\e010\"; } }\n.glyphicon-th                     { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list                { &:before { content: \"\\e012\"; } }\n.glyphicon-ok                     { &:before { content: \"\\e013\"; } }\n.glyphicon-remove                 { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in                { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out               { &:before { content: \"\\e016\"; } }\n.glyphicon-off                    { &:before { content: \"\\e017\"; } }\n.glyphicon-signal                 { &:before { content: \"\\e018\"; } }\n.glyphicon-cog                    { &:before { content: \"\\e019\"; } }\n.glyphicon-trash                  { &:before { content: \"\\e020\"; } }\n.glyphicon-home                   { &:before { content: \"\\e021\"; } }\n.glyphicon-file                   { &:before { content: \"\\e022\"; } }\n.glyphicon-time                   { &:before { content: \"\\e023\"; } }\n.glyphicon-road                   { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt           { &:before { content: \"\\e025\"; } }\n.glyphicon-download               { &:before { content: \"\\e026\"; } }\n.glyphicon-upload                 { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox                  { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle            { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat                 { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh                { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt               { &:before { content: \"\\e032\"; } }\n.glyphicon-lock                   { &:before { content: \"\\e033\"; } }\n.glyphicon-flag                   { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones             { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off             { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down            { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up              { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode                 { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode                { &:before { content: \"\\e040\"; } }\n.glyphicon-tag                    { &:before { content: \"\\e041\"; } }\n.glyphicon-tags                   { &:before { content: \"\\e042\"; } }\n.glyphicon-book                   { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark               { &:before { content: \"\\e044\"; } }\n.glyphicon-print                  { &:before { content: \"\\e045\"; } }\n.glyphicon-camera                 { &:before { content: \"\\e046\"; } }\n.glyphicon-font                   { &:before { content: \"\\e047\"; } }\n.glyphicon-bold                   { &:before { content: \"\\e048\"; } }\n.glyphicon-italic                 { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height            { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width             { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left             { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center           { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right            { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify          { &:before { content: \"\\e055\"; } }\n.glyphicon-list                   { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left            { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right           { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video         { &:before { content: \"\\e059\"; } }\n.glyphicon-picture                { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker             { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust                 { &:before { content: \"\\e063\"; } }\n.glyphicon-tint                   { &:before { content: \"\\e064\"; } }\n.glyphicon-edit                   { &:before { content: \"\\e065\"; } }\n.glyphicon-share                  { &:before { content: \"\\e066\"; } }\n.glyphicon-check                  { &:before { content: \"\\e067\"; } }\n.glyphicon-move                   { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward          { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward          { &:before { content: \"\\e070\"; } }\n.glyphicon-backward               { &:before { content: \"\\e071\"; } }\n.glyphicon-play                   { &:before { content: \"\\e072\"; } }\n.glyphicon-pause                  { &:before { content: \"\\e073\"; } }\n.glyphicon-stop                   { &:before { content: \"\\e074\"; } }\n.glyphicon-forward                { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward           { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward           { &:before { content: \"\\e077\"; } }\n.glyphicon-eject                  { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left           { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right          { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign              { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign             { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign            { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign                { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign          { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign              { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot             { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle          { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle              { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle             { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left             { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right            { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up               { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down             { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt              { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full            { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small           { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign       { &:before { content: \"\\e101\"; } }\n.glyphicon-gift                   { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf                   { &:before { content: \"\\e103\"; } }\n.glyphicon-fire                   { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open               { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close              { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign           { &:before { content: \"\\e107\"; } }\n.glyphicon-plane                  { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar               { &:before { content: \"\\e109\"; } }\n.glyphicon-random                 { &:before { content: \"\\e110\"; } }\n.glyphicon-comment                { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet                 { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up             { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down           { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet                { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart          { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close           { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open            { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical        { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal      { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd                    { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn               { &:before { content: \"\\e122\"; } }\n.glyphicon-bell                   { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate            { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up              { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down            { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right             { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left              { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up                { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down              { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right     { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left      { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up        { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down      { &:before { content: \"\\e134\"; } }\n.glyphicon-globe                  { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench                 { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks                  { &:before { content: \"\\e137\"; } }\n.glyphicon-filter                 { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase              { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen             { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard              { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip              { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty            { &:before { content: \"\\e143\"; } }\n.glyphicon-link                   { &:before { content: \"\\e144\"; } }\n.glyphicon-phone                  { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin                { &:before { content: \"\\e146\"; } }\n.glyphicon-usd                    { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp                    { &:before { content: \"\\e149\"; } }\n.glyphicon-sort                   { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet       { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt   { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order          { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt      { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes     { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked              { &:before { content: \"\\e157\"; } }\n.glyphicon-expand                 { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down          { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up            { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in                 { &:before { content: \"\\e161\"; } }\n.glyphicon-flash                  { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out                { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window             { &:before { content: \"\\e164\"; } }\n.glyphicon-record                 { &:before { content: \"\\e165\"; } }\n.glyphicon-save                   { &:before { content: \"\\e166\"; } }\n.glyphicon-open                   { &:before { content: \"\\e167\"; } }\n.glyphicon-saved                  { &:before { content: \"\\e168\"; } }\n.glyphicon-import                 { &:before { content: \"\\e169\"; } }\n.glyphicon-export                 { &:before { content: \"\\e170\"; } }\n.glyphicon-send                   { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk            { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved           { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove          { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save            { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open            { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card            { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer               { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery                { &:before { content: \"\\e179\"; } }\n.glyphicon-header                 { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed             { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone               { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt              { &:before { content: \"\\e183\"; } }\n.glyphicon-tower                  { &:before { content: \"\\e184\"; } }\n.glyphicon-stats                  { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video               { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video               { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles              { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo           { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby            { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1              { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1              { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1              { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark         { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark      { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download         { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload           { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer           { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous         { &:before { content: \"\\e200\"; } }\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 2px;\n  vertical-align: middle;\n  border-top:   @caret-width-base solid;\n  border-right: @caret-width-base solid transparent;\n  border-left:  @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropdown {\n  position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n  outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  z-index: @zindex-dropdown;\n  display: none; // none by default, but block on \"open\" of the menu\n  float: left;\n  min-width: 160px;\n  padding: 5px 0;\n  margin: 2px 0 0; // override default ul\n  list-style: none;\n  font-size: @font-size-base;\n  background-color: @dropdown-bg;\n  border: 1px solid @dropdown-fallback-border; // IE8 fallback\n  border: 1px solid @dropdown-border;\n  border-radius: @border-radius-base;\n  .box-shadow(0 6px 12px rgba(0,0,0,.175));\n  background-clip: padding-box;\n\n  // Aligns the dropdown menu to right\n  //\n  // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n  &.pull-right {\n    right: 0;\n    left: auto;\n  }\n\n  // Dividers (basically an hr) within the dropdown\n  .divider {\n    .nav-divider(@dropdown-divider-bg);\n  }\n\n  // Links within the dropdown menu\n  > li > a {\n    display: block;\n    padding: 3px 20px;\n    clear: both;\n    font-weight: normal;\n    line-height: @line-height-base;\n    color: @dropdown-link-color;\n    white-space: nowrap; // prevent links from randomly breaking onto new lines\n  }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n  &:hover,\n  &:focus {\n    text-decoration: none;\n    color: @dropdown-link-hover-color;\n    background-color: @dropdown-link-hover-bg;\n  }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n  &,\n  &:hover,\n  &:focus {\n    color: @dropdown-link-active-color;\n    text-decoration: none;\n    outline: 0;\n    background-color: @dropdown-link-active-bg;\n  }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n  &,\n  &:hover,\n  &:focus {\n    color: @dropdown-link-disabled-color;\n  }\n}\n// Nuke hover/focus effects\n.dropdown-menu > .disabled > a {\n  &:hover,\n  &:focus {\n    text-decoration: none;\n    background-color: transparent;\n    background-image: none; // Remove CSS gradient\n    .reset-filter();\n    cursor: not-allowed;\n  }\n}\n\n// Open state for the dropdown\n.open {\n  // Show the menu\n  > .dropdown-menu {\n    display: block;\n  }\n\n  // Remove the outline when :focus is triggered\n  > a {\n    outline: 0;\n  }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n  left: auto; // Reset the default from `.dropdown-menu`\n  right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n  left: 0;\n  right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n  display: block;\n  padding: 3px 20px;\n  font-size: @font-size-small;\n  line-height: @line-height-base;\n  color: @dropdown-header-color;\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n  position: fixed;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  top: 0;\n  z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n  right: 0;\n  left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n  // Reverse the caret\n  .caret {\n    border-top: 0;\n    border-bottom: @caret-width-base solid;\n    content: \"\";\n  }\n  // Different positioning for bottom up menu\n  .dropdown-menu {\n    top: auto;\n    bottom: 100%;\n    margin-bottom: 1px;\n  }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n  .navbar-right {\n    .dropdown-menu {\n      .dropdown-menu-right();\n    }\n    // Necessary for overrides of the default right aligned menu.\n    // Will remove come v4 in all likelihood.\n    .dropdown-menu-left {\n      .dropdown-menu-left();\n    }\n  }\n}\n\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n  position: relative; // For dropdowns\n  display: table;\n  border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n  // Undo padding and float of grid classes\n  &[class*=\"col-\"] {\n    float: none;\n    padding-left: 0;\n    padding-right: 0;\n  }\n\n  .form-control {\n    // Ensure that the input is always above the *appended* addon button for\n    // proper border colors.\n    position: relative;\n    z-index: 2;\n\n    // IE9 fubars the placeholder attribute in text inputs and the arrows on\n    // select elements in input groups. To fix it, we float the input. Details:\n    // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n    float: left;\n\n    width: 100%;\n    margin-bottom: 0;\n  }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn { .input-lg(); }\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn { .input-sm(); }\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n  display: table-cell;\n\n  &:not(:first-child):not(:last-child) {\n    border-radius: 0;\n  }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n  width: 1%;\n  white-space: nowrap;\n  vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n  padding: @padding-base-vertical @padding-base-horizontal;\n  font-size: @font-size-base;\n  font-weight: normal;\n  line-height: 1;\n  color: @input-color;\n  text-align: center;\n  background-color: @input-group-addon-bg;\n  border: 1px solid @input-group-addon-border-color;\n  border-radius: @border-radius-base;\n\n  // Sizing\n  &.input-sm {\n    padding: @padding-small-vertical @padding-small-horizontal;\n    font-size: @font-size-small;\n    border-radius: @border-radius-small;\n  }\n  &.input-lg {\n    padding: @padding-large-vertical @padding-large-horizontal;\n    font-size: @font-size-large;\n    border-radius: @border-radius-large;\n  }\n\n  // Nuke default margins from checkboxes and radios to vertically center within.\n  input[type=\"radio\"],\n  input[type=\"checkbox\"] {\n    margin-top: 0;\n  }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n  .border-right-radius(0);\n}\n.input-group-addon:first-child {\n  border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n  .border-left-radius(0);\n}\n.input-group-addon:last-child {\n  border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n  position: relative;\n  // Jankily prevent input button groups from wrapping with `white-space` and\n  // `font-size` in combination with `inline-block` on buttons.\n  font-size: 0;\n  white-space: nowrap;\n\n  // Negative margin for spacing, position for bringing hovered/focused/actived\n  // element above the siblings.\n  > .btn {\n    position: relative;\n    + .btn {\n      margin-left: -1px;\n    }\n    // Bring the \"active\" button to the front\n    &:hover,\n    &:focus,\n    &:active {\n      z-index: 2;\n    }\n  }\n\n  // Negative margin to only have a 1px border between the two\n  &:first-child {\n    > .btn,\n    > .btn-group {\n      margin-right: -1px;\n    }\n  }\n  &:last-child {\n    > .btn,\n    > .btn-group {\n      margin-left: -1px;\n    }\n  }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n  margin-bottom: 0;\n  padding-left: 0; // Override default ul/ol\n  list-style: none;\n  &:extend(.clearfix all);\n\n  > li {\n    position: relative;\n    display: block;\n\n    > a {\n      position: relative;\n      display: block;\n      padding: @nav-link-padding;\n      &:hover,\n      &:focus {\n        text-decoration: none;\n        background-color: @nav-link-hover-bg;\n      }\n    }\n\n    // Disabled state sets text to gray and nukes hover/tab effects\n    &.disabled > a {\n      color: @nav-disabled-link-color;\n\n      &:hover,\n      &:focus {\n        color: @nav-disabled-link-hover-color;\n        text-decoration: none;\n        background-color: transparent;\n        cursor: not-allowed;\n      }\n    }\n  }\n\n  // Open dropdowns\n  .open > a {\n    &,\n    &:hover,\n    &:focus {\n      background-color: @nav-link-hover-bg;\n      border-color: @link-color;\n    }\n  }\n\n  // Nav dividers (deprecated with v3.0.1)\n  //\n  // This should have been removed in v3 with the dropping of `.nav-list`, but\n  // we missed it. We don't currently support this anywhere, but in the interest\n  // of maintaining backward compatibility in case you use it, it's deprecated.\n  .nav-divider {\n    .nav-divider();\n  }\n\n  // Prevent IE8 from misplacing imgs\n  //\n  // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n  > li > a > img {\n    max-width: none;\n  }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n  border-bottom: 1px solid @nav-tabs-border-color;\n  > li {\n    float: left;\n    // Make the list-items overlay the bottom border\n    margin-bottom: -1px;\n\n    // Actual tabs (as links)\n    > a {\n      margin-right: 2px;\n      line-height: @line-height-base;\n      border: 1px solid transparent;\n      border-radius: @border-radius-base @border-radius-base 0 0;\n      &:hover {\n        border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n      }\n    }\n\n    // Active state, and its :hover to override normal :hover\n    &.active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @nav-tabs-active-link-hover-color;\n        background-color: @nav-tabs-active-link-hover-bg;\n        border: 1px solid @nav-tabs-active-link-hover-border-color;\n        border-bottom-color: transparent;\n        cursor: default;\n      }\n    }\n  }\n  // pulling this in mainly for less shorthand\n  &.nav-justified {\n    .nav-justified();\n    .nav-tabs-justified();\n  }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n  > li {\n    float: left;\n\n    // Links rendered as pills\n    > a {\n      border-radius: @nav-pills-border-radius;\n    }\n    + li {\n      margin-left: 2px;\n    }\n\n    // Active state\n    &.active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @nav-pills-active-link-hover-color;\n        background-color: @nav-pills-active-link-hover-bg;\n      }\n    }\n  }\n}\n\n\n// Stacked pills\n.nav-stacked {\n  > li {\n    float: none;\n    + li {\n      margin-top: 2px;\n      margin-left: 0; // no need for this gap between nav items\n    }\n  }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n  width: 100%;\n\n  > li {\n    float: none;\n     > a {\n      text-align: center;\n      margin-bottom: 5px;\n    }\n  }\n\n  > .dropdown .dropdown-menu {\n    top: auto;\n    left: auto;\n  }\n\n  @media (min-width: @screen-sm-min) {\n    > li {\n      display: table-cell;\n      width: 1%;\n      > a {\n        margin-bottom: 0;\n      }\n    }\n  }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n  border-bottom: 0;\n\n  > li > a {\n    // Override margin from .nav-tabs\n    margin-right: 0;\n    border-radius: @border-radius-base;\n  }\n\n  > .active > a,\n  > .active > a:hover,\n  > .active > a:focus {\n    border: 1px solid @nav-tabs-justified-link-border-color;\n  }\n\n  @media (min-width: @screen-sm-min) {\n    > li > a {\n      border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n      border-radius: @border-radius-base @border-radius-base 0 0;\n    }\n    > .active > a,\n    > .active > a:hover,\n    > .active > a:focus {\n      border-bottom-color: @nav-tabs-justified-active-link-border-color;\n    }\n  }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n  > .tab-pane {\n    display: none;\n  }\n  > .active {\n    display: block;\n  }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n  // make dropdown border overlap tab border\n  margin-top: -1px;\n  // Remove the top rounded corners here since there is a hard edge above the menu\n  .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n  position: relative;\n  min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n  margin-bottom: @navbar-margin-bottom;\n  border: 1px solid transparent;\n\n  // Prevent floats from breaking the navbar\n  &:extend(.clearfix all);\n\n  @media (min-width: @grid-float-breakpoint) {\n    border-radius: @navbar-border-radius;\n  }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n  &:extend(.clearfix all);\n\n  @media (min-width: @grid-float-breakpoint) {\n    float: left;\n  }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n  max-height: @navbar-collapse-max-height;\n  overflow-x: visible;\n  padding-right: @navbar-padding-horizontal;\n  padding-left:  @navbar-padding-horizontal;\n  border-top: 1px solid transparent;\n  box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n  &:extend(.clearfix all);\n  -webkit-overflow-scrolling: touch;\n\n  &.in {\n    overflow-y: auto;\n  }\n\n  @media (min-width: @grid-float-breakpoint) {\n    width: auto;\n    border-top: 0;\n    box-shadow: none;\n\n    &.collapse {\n      display: block !important;\n      height: auto !important;\n      padding-bottom: 0; // Override default setting\n      overflow: visible !important;\n    }\n\n    &.in {\n      overflow-y: visible;\n    }\n\n    // Undo the collapse side padding for navbars with containers to ensure\n    // alignment of right-aligned contents.\n    .navbar-fixed-top &,\n    .navbar-static-top &,\n    .navbar-fixed-bottom & {\n      padding-left: 0;\n      padding-right: 0;\n    }\n  }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n  > .navbar-header,\n  > .navbar-collapse {\n    margin-right: -@navbar-padding-horizontal;\n    margin-left:  -@navbar-padding-horizontal;\n\n    @media (min-width: @grid-float-breakpoint) {\n      margin-right: 0;\n      margin-left:  0;\n    }\n  }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n  z-index: @zindex-navbar;\n  border-width: 0 0 1px;\n\n  @media (min-width: @grid-float-breakpoint) {\n    border-radius: 0;\n  }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n  position: fixed;\n  right: 0;\n  left: 0;\n  z-index: @zindex-navbar-fixed;\n\n  // Undo the rounded corners\n  @media (min-width: @grid-float-breakpoint) {\n    border-radius: 0;\n  }\n}\n.navbar-fixed-top {\n  top: 0;\n  border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n  bottom: 0;\n  margin-bottom: 0; // override .navbar defaults\n  border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n  float: left;\n  padding: @navbar-padding-vertical @navbar-padding-horizontal;\n  font-size: @font-size-large;\n  line-height: @line-height-computed;\n  height: @navbar-height;\n\n  &:hover,\n  &:focus {\n    text-decoration: none;\n  }\n\n  @media (min-width: @grid-float-breakpoint) {\n    .navbar > .container &,\n    .navbar > .container-fluid & {\n      margin-left: -@navbar-padding-horizontal;\n    }\n  }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n  position: relative;\n  float: right;\n  margin-right: @navbar-padding-horizontal;\n  padding: 9px 10px;\n  .navbar-vertical-align(34px);\n  background-color: transparent;\n  background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n  border: 1px solid transparent;\n  border-radius: @border-radius-base;\n\n  // We remove the `outline` here, but later compensate by attaching `:hover`\n  // styles to `:focus`.\n  &:focus {\n    outline: none;\n  }\n\n  // Bars\n  .icon-bar {\n    display: block;\n    width: 22px;\n    height: 2px;\n    border-radius: 1px;\n  }\n  .icon-bar + .icon-bar {\n    margin-top: 4px;\n  }\n\n  @media (min-width: @grid-float-breakpoint) {\n    display: none;\n  }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n  margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n  > li > a {\n    padding-top:    10px;\n    padding-bottom: 10px;\n    line-height: @line-height-computed;\n  }\n\n  @media (max-width: @grid-float-breakpoint-max) {\n    // Dropdowns get custom display when collapsed\n    .open .dropdown-menu {\n      position: static;\n      float: none;\n      width: auto;\n      margin-top: 0;\n      background-color: transparent;\n      border: 0;\n      box-shadow: none;\n      > li > a,\n      .dropdown-header {\n        padding: 5px 15px 5px 25px;\n      }\n      > li > a {\n        line-height: @line-height-computed;\n        &:hover,\n        &:focus {\n          background-image: none;\n        }\n      }\n    }\n  }\n\n  // Uncollapse the nav\n  @media (min-width: @grid-float-breakpoint) {\n    float: left;\n    margin: 0;\n\n    > li {\n      float: left;\n      > a {\n        padding-top:    @navbar-padding-vertical;\n        padding-bottom: @navbar-padding-vertical;\n      }\n    }\n\n    &.navbar-right:last-child {\n      margin-right: -@navbar-padding-horizontal;\n    }\n  }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n\n@media (min-width: @grid-float-breakpoint) {\n  .navbar-left  { .pull-left(); }\n  .navbar-right { .pull-right(); }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n  margin-left: -@navbar-padding-horizontal;\n  margin-right: -@navbar-padding-horizontal;\n  padding: 10px @navbar-padding-horizontal;\n  border-top: 1px solid transparent;\n  border-bottom: 1px solid transparent;\n  @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n  .box-shadow(@shadow);\n\n  // Mixin behavior for optimum display\n  .form-inline();\n\n  .form-group {\n    @media (max-width: @grid-float-breakpoint-max) {\n      margin-bottom: 5px;\n    }\n  }\n\n  // Vertically center in expanded, horizontal navbar\n  .navbar-vertical-align(@input-height-base);\n\n  // Undo 100% width for pull classes\n  @media (min-width: @grid-float-breakpoint) {\n    width: auto;\n    border: 0;\n    margin-left: 0;\n    margin-right: 0;\n    padding-top: 0;\n    padding-bottom: 0;\n    .box-shadow(none);\n\n    // Outdent the form if last child to line up with content down the page\n    &.navbar-right:last-child {\n      margin-right: -@navbar-padding-horizontal;\n    }\n  }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n  margin-top: 0;\n  .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n  .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n  .navbar-vertical-align(@input-height-base);\n\n  &.btn-sm {\n    .navbar-vertical-align(@input-height-small);\n  }\n  &.btn-xs {\n    .navbar-vertical-align(22);\n  }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n  .navbar-vertical-align(@line-height-computed);\n\n  @media (min-width: @grid-float-breakpoint) {\n    float: left;\n    margin-left: @navbar-padding-horizontal;\n    margin-right: @navbar-padding-horizontal;\n\n    // Outdent the form if last child to line up with content down the page\n    &.navbar-right:last-child {\n      margin-right: 0;\n    }\n  }\n}\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n  background-color: @navbar-default-bg;\n  border-color: @navbar-default-border;\n\n  .navbar-brand {\n    color: @navbar-default-brand-color;\n    &:hover,\n    &:focus {\n      color: @navbar-default-brand-hover-color;\n      background-color: @navbar-default-brand-hover-bg;\n    }\n  }\n\n  .navbar-text {\n    color: @navbar-default-color;\n  }\n\n  .navbar-nav {\n    > li > a {\n      color: @navbar-default-link-color;\n\n      &:hover,\n      &:focus {\n        color: @navbar-default-link-hover-color;\n        background-color: @navbar-default-link-hover-bg;\n      }\n    }\n    > .active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-default-link-active-color;\n        background-color: @navbar-default-link-active-bg;\n      }\n    }\n    > .disabled > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-default-link-disabled-color;\n        background-color: @navbar-default-link-disabled-bg;\n      }\n    }\n  }\n\n  .navbar-toggle {\n    border-color: @navbar-default-toggle-border-color;\n    &:hover,\n    &:focus {\n      background-color: @navbar-default-toggle-hover-bg;\n    }\n    .icon-bar {\n      background-color: @navbar-default-toggle-icon-bar-bg;\n    }\n  }\n\n  .navbar-collapse,\n  .navbar-form {\n    border-color: @navbar-default-border;\n  }\n\n  // Dropdown menu items\n  .navbar-nav {\n    // Remove background color from open dropdown\n    > .open > a {\n      &,\n      &:hover,\n      &:focus {\n        background-color: @navbar-default-link-active-bg;\n        color: @navbar-default-link-active-color;\n      }\n    }\n\n    @media (max-width: @grid-float-breakpoint-max) {\n      // Dropdowns get custom display when collapsed\n      .open .dropdown-menu {\n        > li > a {\n          color: @navbar-default-link-color;\n          &:hover,\n          &:focus {\n            color: @navbar-default-link-hover-color;\n            background-color: @navbar-default-link-hover-bg;\n          }\n        }\n        > .active > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-default-link-active-color;\n            background-color: @navbar-default-link-active-bg;\n          }\n        }\n        > .disabled > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-default-link-disabled-color;\n            background-color: @navbar-default-link-disabled-bg;\n          }\n        }\n      }\n    }\n  }\n\n\n  // Links in navbars\n  //\n  // Add a class to ensure links outside the navbar nav are colored correctly.\n\n  .navbar-link {\n    color: @navbar-default-link-color;\n    &:hover {\n      color: @navbar-default-link-hover-color;\n    }\n  }\n\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n  background-color: @navbar-inverse-bg;\n  border-color: @navbar-inverse-border;\n\n  .navbar-brand {\n    color: @navbar-inverse-brand-color;\n    &:hover,\n    &:focus {\n      color: @navbar-inverse-brand-hover-color;\n      background-color: @navbar-inverse-brand-hover-bg;\n    }\n  }\n\n  .navbar-text {\n    color: @navbar-inverse-color;\n  }\n\n  .navbar-nav {\n    > li > a {\n      color: @navbar-inverse-link-color;\n\n      &:hover,\n      &:focus {\n        color: @navbar-inverse-link-hover-color;\n        background-color: @navbar-inverse-link-hover-bg;\n      }\n    }\n    > .active > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-inverse-link-active-color;\n        background-color: @navbar-inverse-link-active-bg;\n      }\n    }\n    > .disabled > a {\n      &,\n      &:hover,\n      &:focus {\n        color: @navbar-inverse-link-disabled-color;\n        background-color: @navbar-inverse-link-disabled-bg;\n      }\n    }\n  }\n\n  // Darken the responsive nav toggle\n  .navbar-toggle {\n    border-color: @navbar-inverse-toggle-border-color;\n    &:hover,\n    &:focus {\n      background-color: @navbar-inverse-toggle-hover-bg;\n    }\n    .icon-bar {\n      background-color: @navbar-inverse-toggle-icon-bar-bg;\n    }\n  }\n\n  .navbar-collapse,\n  .navbar-form {\n    border-color: darken(@navbar-inverse-bg, 7%);\n  }\n\n  // Dropdowns\n  .navbar-nav {\n    > .open > a {\n      &,\n      &:hover,\n      &:focus {\n        background-color: @navbar-inverse-link-active-bg;\n        color: @navbar-inverse-link-active-color;\n      }\n    }\n\n    @media (max-width: @grid-float-breakpoint-max) {\n      // Dropdowns get custom display\n      .open .dropdown-menu {\n        > .dropdown-header {\n          border-color: @navbar-inverse-border;\n        }\n        .divider {\n          background-color: @navbar-inverse-border;\n        }\n        > li > a {\n          color: @navbar-inverse-link-color;\n          &:hover,\n          &:focus {\n            color: @navbar-inverse-link-hover-color;\n            background-color: @navbar-inverse-link-hover-bg;\n          }\n        }\n        > .active > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-inverse-link-active-color;\n            background-color: @navbar-inverse-link-active-bg;\n          }\n        }\n        > .disabled > a {\n          &,\n          &:hover,\n          &:focus {\n            color: @navbar-inverse-link-disabled-color;\n            background-color: @navbar-inverse-link-disabled-bg;\n          }\n        }\n      }\n    }\n  }\n\n  .navbar-link {\n    color: @navbar-inverse-link-color;\n    &:hover {\n      color: @navbar-inverse-link-hover-color;\n    }\n  }\n\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n  .clearfix();\n}\n.center-block {\n  .center-block();\n}\n.pull-right {\n  float: right !important;\n}\n.pull-left {\n  float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n  display: none !important;\n}\n.show {\n  display: block !important;\n}\n.invisible {\n  visibility: hidden;\n}\n.text-hide {\n  .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n  display: none !important;\n  visibility: hidden !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n  position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n  padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n  margin-bottom: @line-height-computed;\n  list-style: none;\n  background-color: @breadcrumb-bg;\n  border-radius: @border-radius-base;\n\n  > li {\n    display: inline-block;\n\n    + li:before {\n      content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n      padding: 0 5px;\n      color: @breadcrumb-color;\n    }\n  }\n\n  > .active {\n    color: @breadcrumb-active-color;\n  }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n  display: inline-block;\n  padding-left: 0;\n  margin: @line-height-computed 0;\n  border-radius: @border-radius-base;\n\n  > li {\n    display: inline; // Remove list-style and block-level defaults\n    > a,\n    > span {\n      position: relative;\n      float: left; // Collapse white-space\n      padding: @padding-base-vertical @padding-base-horizontal;\n      line-height: @line-height-base;\n      text-decoration: none;\n      color: @pagination-color;\n      background-color: @pagination-bg;\n      border: 1px solid @pagination-border;\n      margin-left: -1px;\n    }\n    &:first-child {\n      > a,\n      > span {\n        margin-left: 0;\n        .border-left-radius(@border-radius-base);\n      }\n    }\n    &:last-child {\n      > a,\n      > span {\n        .border-right-radius(@border-radius-base);\n      }\n    }\n  }\n\n  > li > a,\n  > li > span {\n    &:hover,\n    &:focus {\n      color: @pagination-hover-color;\n      background-color: @pagination-hover-bg;\n      border-color: @pagination-hover-border;\n    }\n  }\n\n  > .active > a,\n  > .active > span {\n    &,\n    &:hover,\n    &:focus {\n      z-index: 2;\n      color: @pagination-active-color;\n      background-color: @pagination-active-bg;\n      border-color: @pagination-active-border;\n      cursor: default;\n    }\n  }\n\n  > .disabled {\n    > span,\n    > span:hover,\n    > span:focus,\n    > a,\n    > a:hover,\n    > a:focus {\n      color: @pagination-disabled-color;\n      background-color: @pagination-disabled-bg;\n      border-color: @pagination-disabled-border;\n      cursor: not-allowed;\n    }\n  }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n  .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n  .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n  padding-left: 0;\n  margin: @line-height-computed 0;\n  list-style: none;\n  text-align: center;\n  &:extend(.clearfix all);\n  li {\n    display: inline;\n    > a,\n    > span {\n      display: inline-block;\n      padding: 5px 14px;\n      background-color: @pager-bg;\n      border: 1px solid @pager-border;\n      border-radius: @pager-border-radius;\n    }\n\n    > a:hover,\n    > a:focus {\n      text-decoration: none;\n      background-color: @pager-hover-bg;\n    }\n  }\n\n  .next {\n    > a,\n    > span {\n      float: right;\n    }\n  }\n\n  .previous {\n    > a,\n    > span {\n      float: left;\n    }\n  }\n\n  .disabled {\n    > a,\n    > a:hover,\n    > a:focus,\n    > span {\n      color: @pager-disabled-color;\n      background-color: @pager-bg;\n      cursor: not-allowed;\n    }\n  }\n\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n  display: inline;\n  padding: .2em .6em .3em;\n  font-size: 75%;\n  font-weight: bold;\n  line-height: 1;\n  color: @label-color;\n  text-align: center;\n  white-space: nowrap;\n  vertical-align: baseline;\n  border-radius: .25em;\n\n  // Add hover effects, but only for links\n  &[href] {\n    &:hover,\n    &:focus {\n      color: @label-link-hover-color;\n      text-decoration: none;\n      cursor: pointer;\n    }\n  }\n\n  // Empty labels collapse automatically (not available in IE8)\n  &:empty {\n    display: none;\n  }\n\n  // Quick fix for labels in buttons\n  .btn & {\n    position: relative;\n    top: -1px;\n  }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n  .label-variant(@label-default-bg);\n}\n\n.label-primary {\n  .label-variant(@label-primary-bg);\n}\n\n.label-success {\n  .label-variant(@label-success-bg);\n}\n\n.label-info {\n  .label-variant(@label-info-bg);\n}\n\n.label-warning {\n  .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n  .label-variant(@label-danger-bg);\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base classes\n.badge {\n  display: inline-block;\n  min-width: 10px;\n  padding: 3px 7px;\n  font-size: @font-size-small;\n  font-weight: @badge-font-weight;\n  color: @badge-color;\n  line-height: @badge-line-height;\n  vertical-align: baseline;\n  white-space: nowrap;\n  text-align: center;\n  background-color: @badge-bg;\n  border-radius: @badge-border-radius;\n\n  // Empty badges collapse automatically (not available in IE8)\n  &:empty {\n    display: none;\n  }\n\n  // Quick fix for badges in buttons\n  .btn & {\n    position: relative;\n    top: -1px;\n  }\n  .btn-xs & {\n    top: 0;\n    padding: 1px 5px;\n  }\n}\n\n// Hover state, but only for links\na.badge {\n  &:hover,\n  &:focus {\n    color: @badge-link-hover-color;\n    text-decoration: none;\n    cursor: pointer;\n  }\n}\n\n// Account for counters in navs\na.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n  color: @badge-active-color;\n  background-color: @badge-active-bg;\n}\n.nav-pills > li > a > .badge {\n  margin-left: 3px;\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n  padding: @jumbotron-padding;\n  margin-bottom: @jumbotron-padding;\n  color: @jumbotron-color;\n  background-color: @jumbotron-bg;\n\n  h1,\n  .h1 {\n    color: @jumbotron-heading-color;\n  }\n  p {\n    margin-bottom: (@jumbotron-padding / 2);\n    font-size: @jumbotron-font-size;\n    font-weight: 200;\n  }\n\n  .container & {\n    border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n  }\n\n  .container {\n    max-width: 100%;\n  }\n\n  @media screen and (min-width: @screen-sm-min) {\n    padding-top:    (@jumbotron-padding * 1.6);\n    padding-bottom: (@jumbotron-padding * 1.6);\n\n    .container & {\n      padding-left:  (@jumbotron-padding * 2);\n      padding-right: (@jumbotron-padding * 2);\n    }\n\n    h1,\n    .h1 {\n      font-size: (@font-size-base * 4.5);\n    }\n  }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n  padding: @alert-padding;\n  margin-bottom: @line-height-computed;\n  border: 1px solid transparent;\n  border-radius: @alert-border-radius;\n\n  // Headings for larger alerts\n  h4 {\n    margin-top: 0;\n    // Specified for the h4 to prevent conflicts of changing @headings-color\n    color: inherit;\n  }\n  // Provide class for links that match alerts\n  .alert-link {\n    font-weight: @alert-link-font-weight;\n  }\n\n  // Improve alignment and spacing of inner content\n  > p,\n  > ul {\n    margin-bottom: 0;\n  }\n  > p + p {\n    margin-top: 5px;\n  }\n}\n\n// Dismissable alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable {\n padding-right: (@alert-padding + 20);\n\n  // Adjust close link position\n  .close {\n    position: relative;\n    top: -2px;\n    right: -21px;\n    color: inherit;\n  }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n  .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n.alert-info {\n  .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n.alert-warning {\n  .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n.alert-danger {\n  .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n  from  { background-position: 40px 0; }\n  to    { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n  from  { background-position: 40px 0; }\n  to    { background-position: 0 0; }\n}\n\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n  overflow: hidden;\n  height: @line-height-computed;\n  margin-bottom: @line-height-computed;\n  background-color: @progress-bg;\n  border-radius: @border-radius-base;\n  .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n  float: left;\n  width: 0%;\n  height: 100%;\n  font-size: @font-size-small;\n  line-height: @line-height-computed;\n  color: @progress-bar-color;\n  text-align: center;\n  background-color: @progress-bar-bg;\n  .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n  .transition(width .6s ease);\n}\n\n// Striped bars\n.progress-striped .progress-bar {\n  #gradient > .striped();\n  background-size: 40px 40px;\n}\n\n// Call animation for the active one\n.progress.active .progress-bar {\n  .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n  .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n  .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n  .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n  .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Media objects\n// Source: http://stubbornella.org/content/?p=497\n// --------------------------------------------------\n\n\n// Common styles\n// -------------------------\n\n// Clear the floats\n.media,\n.media-body {\n  overflow: hidden;\n  zoom: 1;\n}\n\n// Proper spacing between instances of .media\n.media,\n.media .media {\n  margin-top: 15px;\n}\n.media:first-child {\n  margin-top: 0;\n}\n\n// For images and videos, set to block\n.media-object {\n  display: block;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n  margin: 0 0 5px;\n}\n\n\n// Media image alignment\n// -------------------------\n\n.media {\n  > .pull-left {\n    margin-right: 10px;\n  }\n  > .pull-right {\n    margin-left: 10px;\n  }\n}\n\n\n// Media list variation\n// -------------------------\n\n// Undo default ul/ol styles\n.media-list {\n  padding-left: 0;\n  list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on