From 616ae1ea8288aa4e8b89fd281d882ef9b5437a11 Mon Sep 17 00:00:00 2001 From: Timothy J Warren Date: Fri, 5 Oct 2018 21:32:15 -0400 Subject: [PATCH] Ugly Progress Commit * Cache and resize images - not just cache them * Convert to webp on cache * Show webp images if available * Settings Form Generation (doesn't yet save) --- .gitignore | 3 +- app/appConf/routes.php | 2 +- app/bootstrap.php | 10 + app/views/_form.php | 32 ++++ app/views/anime/cover-item.php | 7 +- app/views/anime/details.php | 14 +- app/views/character.php | 6 +- app/views/collection/cover-item.php | 8 +- app/views/manga/cover.php | 6 +- app/views/manga/details.php | 15 +- app/views/settings.php | 68 +++---- public/css/app.min.css | 2 +- public/css/base.css | 26 +++ src/Controller.php | 49 ++--- src/Controller/Index.php | 64 ++++++- src/FormGenerator.php | 115 +++++++++++ src/Helper/Form.php | 41 ++++ src/Model/Settings.php | 286 ++++++++++++++++++++++++++++ 18 files changed, 663 insertions(+), 91 deletions(-) create mode 100644 app/views/_form.php create mode 100644 src/FormGenerator.php create mode 100644 src/Helper/Form.php create mode 100644 src/Model/Settings.php diff --git a/.gitignore b/.gitignore index 9cff69ac..162aeb70 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ public/images/anime/** public/images/avatars/** public/images/manga/** public/images/characters/** -public/images/people/** \ No newline at end of file +public/images/people/** +public/mal_mappings.json \ No newline at end of file diff --git a/app/appConf/routes.php b/app/appConf/routes.php index 067bbce7..9c33f058 100644 --- a/app/appConf/routes.php +++ b/app/appConf/routes.php @@ -200,7 +200,7 @@ return [ 'verb' => 'get', 'tokens' => [ 'type' => '[a-z0-9\-]+', - 'file' => '[a-z0-9\-]+\.[a-z]{3}' + 'file' => '[a-z0-9\-]+\.[a-z]{3,4}' ] ], 'cache_purge' => [ diff --git a/app/bootstrap.php b/app/bootstrap.php index 3facd1c8..ed6b3e63 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -81,6 +81,11 @@ return function ($configArray = []) { $menuHelper->setContainer($container); return $menuHelper; }); + $htmlHelper->set('field', function() use ($container) { + $formHelper = new Helper\Form(); + $formHelper->setContainer($container); + return $formHelper; + }); return $htmlHelper; }); @@ -156,6 +161,11 @@ return function ($configArray = []) { $container->set('manga-collection-model', function($container) { return new Model\MangaCollection($container); }); + $container->set('settings-model', function($container) { + $model = new Model\Settings($container->get('config')); + $model->setContainer($container); + return $model; + }); // Miscellaneous Classes $container->set('auth', function($container) { diff --git a/app/views/_form.php b/app/views/_form.php new file mode 100644 index 00000000..4c2f7091 --- /dev/null +++ b/app/views/_form.php @@ -0,0 +1,32 @@ + + + $field): ?> + + +
+

+ +
+ +
+
+
+ field($fieldname, $field); ?> +
+ + field($fieldname, $field); ?> + + diff --git a/app/views/anime/cover-item.php b/app/views/anime/cover-item.php index 3f8060ae..1a6ffb4b 100644 --- a/app/views/anime/cover-item.php +++ b/app/views/anime/cover-item.php @@ -6,7 +6,12 @@ isAuthenticated()): ?> - " alt=""/> + + " type="image/webp"> + " type="image/jpeg"> + " alt="" /> + +
diff --git a/app/views/anime/details.php b/app/views/anime/details.php index 7e6d4fc6..72aed462 100644 --- a/app/views/anime/details.php +++ b/app/views/anime/details.php @@ -1,7 +1,11 @@
- " alt="" /> + + " type="image/webp"> + " type="image/jpeg"> + " alt="" /> +

@@ -96,9 +100,11 @@ a($link, $char['name']); ?> - img($urlGenerator->assetUrl("images/characters/{$id}.jpg"), [ - 'width' => '225' - ]) ?> + + " type="image/webp"> + " type="image/jpeg"> + " alt="" /> + diff --git a/app/views/character.php b/app/views/character.php index e28b1795..1f0cdb23 100644 --- a/app/views/character.php +++ b/app/views/character.php @@ -2,7 +2,11 @@
- " alt="" /> + + " type="image/webp"> + " type="image/jpeg"> + " alt="" /> +

diff --git a/app/views/collection/cover-item.php b/app/views/collection/cover-item.php index 0244dd83..8a25a2dc 100644 --- a/app/views/collection/cover-item.php +++ b/app/views/collection/cover-item.php @@ -1,6 +1,10 @@
- " - alt=" cover image"/> + + " type="image/webp"> + " type="image/jpeg"> + " alt=" cover image" /> + + - " /> + + " type="image/webp"> + " type="image/jpeg"> + " alt="" /> +
@@ -47,9 +51,14 @@ a($link, $char['name']); ?> - img($urlGenerator->assetUrl('images/characters', "{$id}.jpg"), [ + + " type="image/webp"> + " type="image/jpeg"> + " alt="" /> + + img($urlGenerator->assetUrl('images/characters', "{$id}.jpg"), [ 'width' => '225' - ]) ?> + ]) ?> */ ?> diff --git a/app/views/settings.php b/app/views/settings.php index 15aa5ecd..0269231b 100644 --- a/app/views/settings.php +++ b/app/views/settings.php @@ -1,60 +1,42 @@ get('config_dir')); - if ( ! $auth->isAuthenticated()) { echo '

Not Authorized

'; return; } +$sectionMapping = [ + 'config' => 'General Settings', + 'cache' => 'Caching', + 'database' => 'Collection Database Settings', +]; -function render_settings_form ($data, $file) -{ - ob_start(); - foreach ($data as $key => $value) - { - ?> - - - - -
- $properties): ?> -
- - - -
- -
- - - - -
- +
+ +
+ $fields): ?> +
+ +
+ +
+
+ + +
+ + + + +
- diff --git a/public/css/app.min.css b/public/css/app.min.css index 8204e9a0..b90a8c28 100644 --- a/public/css/app.min.css +++ b/public/css/app.min.css @@ -1 +1 @@ -:root{-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-text-size-adjust:100%;box-sizing:border-box;cursor:default;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;overflow-y:scroll;scroll-behavior:smooth;text-size-adjust:100%}audio:not([controls]){display:none}details{display:block}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{margin:0 auto;padding:0 1.6rem 1.6rem}main,pre,summary{display:block}pre{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;font-size:1.4em;font-size:14px;font-size:1.4rem;margin:1.6rem 0;overflow:auto;padding:1.6rem;word-break:break-all;word-wrap:break-word}progress{display:inline-block}small{color:#777;font-size:75%}big{font-size:125%}template{display:none}textarea{border:.1rem solid #ccc;border-radius:0;display:block;margin-bottom:.8rem;overflow:auto;padding:.8rem;resize:vertical;vertical-align:middle}[hidden]{display:none}[unselectable]{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}*,:after,:before{-webkit-box-sizing:inherit;border-style:solid;border-width:0;box-sizing:inherit}*{font-size:inherit;line-height:inherit;margin:0;padding:0}:after,:before{text-decoration:inherit;vertical-align:inherit}a{-webkit-transition:.25s ease;color:#1271db;text-decoration:none;transition:.25s ease}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{border:.1rem solid #ccc;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit;min-height:1.4em}code,kbd,pre,samp{font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace}table{border-collapse:collapse;border-spacing:0;margin-bottom:1.6rem}::-moz-selection{background-color:#b3d4fc;text-shadow:none}::selection{background-color:#b3d4fc;text-shadow:none}button::-moz-focus-inner{border:0}body{color:#444;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:16px;font-size:1.6rem;font-style:normal;font-weight:400;padding:0}p{margin:0 0 1.6rem}h1,h2,h3,h4,h5,h6{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;margin:2rem 0 1.6rem}h1{border-bottom:.1rem solid rgba(0,0,0,.2);font-size:3.6em;font-size:36px;font-size:3.6rem}h1,h2{font-style:normal;font-weight:500}h2{font-size:3em;font-size:30px;font-size:3rem}h3{font-size:2.4em;font-size:24px;font-size:2.4rem;font-style:normal;font-weight:500;margin:1.6rem 0 .4rem}h4{font-size:1.8em;font-size:18px;font-size:1.8rem}h4,h5{font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}h5{font-size:1.6em;font-size:16px;font-size:1.6rem}h6{color:#777;font-size:1.4em;font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}code,h6{font-size:14px;font-size:1.4rem}code{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;word-break:break-all;word-wrap:break-word}a:focus,a:hover{text-decoration:none}dl{margin-bottom:1.6rem}dd{margin-left:4rem}ol,ul{margin-bottom:.8rem;padding-left:2rem}blockquote{border-left:.2rem solid #1271db;font-style:italic;margin:1.6rem 0;padding-left:1.6rem}blockquote,figcaption{font-family:Georgia,Times,Times New Roman,serif}html{font-size:62.5%}article,aside,details,footer,header,main,section,summary{display:block;height:auto;margin:0 auto;width:100%}footer{clear:both;display:inline-block;float:left;max-width:100%;padding:1rem 0;text-align:center}footer,hr{border-top:.1rem solid rgba(0,0,0,.2)}hr{display:block;margin-bottom:1.6rem;width:100%}img{height:auto;vertical-align:baseline}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select{border:.1rem solid #ccc;border-radius:0;display:inline-block;padding:.8rem;vertical-align:middle}input:not([type]){-webkit-appearance:none;background-clip:padding-box;background-color:#fff;border:.1rem solid #ccc;border-radius:0;color:#444;display:inline-block;padding:.8rem;text-align:left}input[type=color]{padding:.8rem 1.6rem}input:not([type]):focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#b3d4fc}input[type=checkbox],input[type=radio]{vertical-align:middle}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:1px thin solid #444;outline:.1rem thin solid #444}input:not([type])[disabled],input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled],select[disabled],textarea[disabled]{background-color:#efefef;color:#777;cursor:not-allowed}input[readonly],select[readonly],textarea[readonly]{background-color:#efefef;border-color:#ccc;color:#777}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{border-color:#e9322d;color:#b94a48}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#ff4136}select{background-color:#fff;border:.1rem solid #ccc}select[multiple]{height:auto}label{line-height:2}fieldset{border:0;margin:0;padding:.8rem 0}legend{border-bottom:.1rem solid #ccc;color:#444;display:block;margin-bottom:.8rem;padding:.8rem 0;width:100%}button,input[type=submit]{-moz-user-select:none;-ms-user-select:none;-webkit-transition:.25s ease;-webkit-user-drag:none;-webkit-user-select:none;border:.2rem solid #444;border-radius:0;color:#444;cursor:pointer;display:inline-block;margin-bottom:.8rem;margin-right:.4rem;padding:.8rem 1.6rem;text-align:center;text-decoration:none;text-transform:uppercase;transition:.25s ease;user-select:none;vertical-align:baseline}button a,input[type=submit] a{color:#444}button::-moz-focus-inner,input[type=submit]::-moz-focus-inner{padding:0}button:hover,input[type=submit]:hover{background:#444;border-color:#444;color:#fff}button:hover a,input[type=submit]:hover a{color:#fff}button:active,input[type=submit]:active{background:#6a6a6a;border-color:#6a6a6a;color:#fff}button:active a,input[type=submit]:active a{color:#fff}button:disabled,input[type=submit]:disabled{-webkit-box-shadow:none;box-shadow:none;cursor:not-allowed;opacity:.4}nav ul{list-style:none;margin:0;padding:0;text-align:center}nav ul li{display:inline}nav a{-webkit-transition:.25s ease;border-bottom:.2rem solid transparent;color:#444;padding:.8rem 1.6rem;text-decoration:none;transition:.25s ease}nav a:hover,nav li.selected a{border-color:rgba(0,0,0,.2)}nav a:active{border-color:rgba(0,0,0,.56)}caption{padding:.8rem 0}thead th{background:#efefef;color:#444}tr{background:#fff;margin-bottom:.8rem}td,th{border:.1rem solid #ccc;padding:.8rem 1.6rem;text-align:center;vertical-align:inherit}tfoot tr{background:none}tfoot td{color:#efefef;font-size:8px;font-size:.8rem;font-style:italic;padding:1.6rem .4rem}@media screen{[hidden~=screen]{display:inherit}[hidden~=screen]:not(:active):not(:focus):not(:target){clip:rect(0)!important;position:absolute!important}}@media screen and max-width 40rem{article,aside,section{clear:both;display:block;max-width:100%}img{margin-right:1.6rem}}.media[hidden],[hidden=hidden],template{display:none}body{margin:.5em}button{background:hsla(0,0%,100%,.65);margin:0}table{margin:0 auto}td{padding:1rem}thead td,thead th{padding:.5rem}input[type=number]{width:4em}tbody>tr:nth-child(odd){background:#ddd}a:active,a:hover{color:#7d12db}.bracketed{color:#12db18}#main-nav a,.bracketed{text-shadow:1px 1px 1px #000}.bracketed:before{content:"[\00a0"}.bracketed:after{content:"\00a0]"}.bracketed:active,.bracketed:hover{color:#db7d12}.grow-1{-ms-flex-positive:1;-webkit-box-flex:1;flex-grow:1}.flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-no-wrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-end{-ms-flex-align:end;-webkit-box-align:end;align-items:flex-end}.flex-align-space-around{-ms-flex-line-pack:distribute;align-content:space-around}.flex-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.flex-self-center{-ms-flex-item-align:center;align-self:center}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.small-font{font-size:16px;font-size:1.6rem}.justify{text-align:justify}.align_center{text-align:center!important}.align_left{text-align:left!important}.align_right{text-align:right!important}.valign_top{vertical-align:top}.no_border{border:none}.media-wrap{margin:0 auto;position:relative;text-align:center}.danger{background-color:#ff4136;border-color:#924949;color:#fff}.danger:active,.danger:hover{background-color:#924949;border-color:#ff4136;color:#fff}.user-btn{border-color:#12db18;color:#12db18;padding:0 .5rem;text-shadow:1px 1px 1px #000}.user-btn:active,.user-btn:hover{background-color:#db7d12;border-color:#db7d12}.full_width{width:100%}#main-nav{border-bottom:.1rem solid rgba(0,0,0,.2);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:3.6em;font-size:36px;font-size:3.6rem;font-style:normal;font-weight:500;margin:2rem 0 1.6rem}.cssload-loader{-webkit-perspective:780px;border-radius:50%;height:62px;left:calc(50% - 31px);perspective:780px;position:relative;width:62px}.cssload-inner{-webkit-box-sizing:border-box;border-radius:50%;box-sizing:border-box;height:100%;position:absolute;width:100%}.cssload-inner.cssload-one{-webkit-animation:cssload-rotate-one 1.15s linear infinite;animation:cssload-rotate-one 1.15s linear infinite;border-bottom:3px solid #000;left:0;top:0}.cssload-inner.cssload-two{-webkit-animation:cssload-rotate-two 1.15s linear infinite;animation:cssload-rotate-two 1.15s linear infinite;border-right:3px solid #000;right:0;top:0}.cssload-inner.cssload-three{-webkit-animation:cssload-rotate-three 1.15s linear infinite;animation:cssload-rotate-three 1.15s linear infinite;border-top:3px solid #000;bottom:0;right:0}@-webkit-keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}@keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}.sorting,.sorting_asc,.sorting_desc{vertical-align:text-bottom}.sorting:before{content:" ↕\00a0"}.sorting_asc:before{content:" ↑\00a0"}.sorting_desc:before{content:" ↓\00a0"}.form thead th,.form thead tr{background:inherit;border:0}.form tr>td:nth-child(odd){max-width:30%;min-width:25px;text-align:right}.form tr>td:nth-child(2n){text-align:left}.invisible tbody>tr:nth-child(odd){background:inherit}.invisible td,.invisible th,.invisible tr{border:0}.message{margin:.5em auto;padding:.5em;position:relative;width:95%}.message .close{height:1em;line-height:1em;position:absolute;right:.5em;text-align:center;top:.5em;vertical-align:middle;width:1em}.message:hover .close:after{content:"☒"}.message:hover{cursor:pointer}.message .icon{left:.5em;margin-right:1em;top:.5em}.message.error{background:#f3e6e6;border:1px solid #924949}.message.error .icon:after{content:"✘"}.message.success{background:#70dda9;border:1px solid #1f8454}.message.success .icon:after{content:"✔"}.message.info{background:#ffc;border:1px solid #bfbe3a}.message.info .icon:after{content:"⚠"}.character,.media,.small_character{display:inline-block;height:311px;margin:.25em .125em;position:relative;text-align:center;vertical-align:top;width:220px;z-index:0}.character>img,.media>img,.small_character>img{width:100%}.media .edit_buttons>button{margin:.5em auto}.media_metadata>div,.medium_metadata>div,.name,.row{color:#fff;padding:.25em .125em;text-align:right;text-shadow:2px 2px 2px #000;z-index:2}.age_rating,.media_type{text-align:left}.media>.media_metadata{bottom:0;position:absolute;right:0}.media>.medium_metadata{bottom:0;left:0;position:absolute}.media>.name{position:absolute;top:0}.media>.name a{-webkit-transition:none;transition:none}.media .name a:before{content:"";display:block;height:311px;left:0;position:absolute;top:0;width:220px;z-index:-1}.media-list .media:hover .name a:before{background:rgba(0,0,0,.75)}.media>.name span.canonical{font-weight:700}.media>.name small{font-weight:400}.media:hover .name{background:rgba(0,0,0,.75)}.media-list .media>.name a:hover,.media-list .media>.name a:hover small{color:#1271db}.media:hover>.edit_buttons[hidden],.media:hover>button[hidden]{-webkit-transition:.25s ease;display:block;transition:.25s ease}.media:hover{-webkit-transition:.25s ease;transition:.25s ease}.character>.name a,.character>.name a small,.media>.name a,.media>.name a small,.small_character>.name a,.small_character>.name a small{background:none;color:#fff;text-shadow:2px 2px 2px #000}.anime .name,.manga .name{background:#000;background:rgba(0,0,0,.45);padding:.5em .25em;text-align:center;width:100%}.anime .age_rating,.anime .airing_status,.anime .completion,.anime .delete,.anime .edit,.anime .media_type,.anime .user_rating{background:none;text-align:center}.anime .table,.manga .table{bottom:0;left:0;position:absolute;width:100%}.anime .row,.manga .row{-ms-flex-line-pack:distribute;-ms-flex-pack:distribute;align-content:space-around;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around;padding:0 inherit;text-align:center;width:100%}.anime .row>span,.manga .row>span{text-align:left;z-index:2}.anime .row>div,.manga .row>div{-ms-flex-item-align:center;align-self:center;display:flex-item;font-size:.8em;text-align:center;vertical-align:middle;z-index:2}.anime .media>button.plus_one{border-color:hsla(0,0%,100%,.65);left:44px;left:calc(50% - 66.5px);position:absolute;top:138px;top:calc(50% - 21.5px);z-index:50}.anime .media>button.plus_one:hover{background:#888;color:hsla(0,0%,100%,.65)}.anime .media>button.plus_one:active{background:#444}.manga .row{padding:1px}.manga .media{height:310px;margin:.25em}.manga .media>.edit_buttons{left:43.5px;left:calc(50% - 66.5px);position:absolute;top:86px;top:calc(50% - 22.4px);z-index:40}.manga .media>.edit_buttons button{border-color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons:hover button{background:#888;color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons button:active{background:#444}.media.search>.name{background-color:#555;background-color:rgba(0,0,0,.35);background-repeat:no-repeat;background-size:cover;background-size:contain}.media.search>.row{z-index:6}.big-check,.mal-check{display:none}.big-check:checked+label{-webkit-transition:.25s ease;background:rgba(0,0,0,.75);transition:.25s ease}.big-check:checked+label:after{color:#adff2f;content:"✓";font-size:15em;font-size:150px;font-size:15rem;height:100%;left:0;position:absolute;text-align:center;top:147px;width:100%;z-index:5}#series_list article.media{position:relative}#series_list .name,#series_list .name label{display:block;height:100%;left:0;line-height:1.25em;position:absolute;top:0;vertical-align:middle;width:100%}#series_list .name small{color:#fff}.details{font-size:inherit;margin:1.5rem auto 0;padding:1rem}.description{max-width:800px;max-width:80rem}.fixed{max-width:930px;max-width:93rem}.details .cover{display:block;width:284px}.details h2{margin-top:0}.details .flex>div{margin:1rem}.details .media_details{max-width:300px}.details .media_details td{padding:0 1.5rem}.details p{text-align:justify}.details .media_details td:nth-child(odd){text-align:right;white-space:nowrap;width:1%}.details .media_details td:nth-child(2n){text-align:left}.character,.small_character{height:350px;vertical-align:middle;white-space:nowrap;width:225px}.character:hover .name,.small_character:hover .name{background:rgba(0,0,0,.8)}.small_character a{display:inline-block;height:100%;width:100%}.character .name,.small_character .name{bottom:0;left:0;position:absolute;z-index:10}.character img,.small_character img{-webkit-transform:translateY(-50%);position:relative;top:50%;transform:translateY(-50%);width:100%;z-index:5}.min-table{margin-left:0;min-width:0}.small_character{height:250px;width:160px}.user-page .media-wrap{text-align:left}.media a{display:inline-block;height:100%;width:100%}@media screen and (max-width:40em){nav a{line-height:4em;line-height:4rem}.media{margin:2px 0}main{padding:0 .5rem .5rem}}.streaming-logo{height:50px;vertical-align:middle;width:50px}.cover_streaming_link{display:none}.media:hover .cover_streaming_link{display:block}.cover_streaming_link .streaming-logo{-webkit-filter:drop-shadow(0 -1px 4px #fff);filter:drop-shadow(0 -1px 4px #fff);height:20px;width:20px}#loading-shadow{background:rgba(0,0,0,.8);z-index:500}#loading-shadow,#loading-shadow .loading-wrapper{height:100%;left:0;position:fixed;top:0;width:100%}#loading-shadow .loading-wrapper{-ms-flex-align:center;-ms-flex-pack:center;-webkit-box-align:center;-webkit-box-pack:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:center;z-index:501}#loading-shadow .loading-content{color:#fff;position:relative}.loading-content .cssload-inner.cssload-one,.loading-content .cssload-inner.cssload-three,.loading-content .cssload-inner.cssload-two{border-color:#fff}.tabs{-ms-flex-wrap:wrap;-webkit-box-shadow:0 48px 80px -32px rgba(0,0,0,.3);background:#efefef;box-shadow:0 48px 80px -32px rgba(0,0,0,.3);display:-webkit-box;display:-ms-flexbox;display:flex;flex-wrap:wrap;margin-top:1.5em}.tabs label{-webkit-transition:background .1s,color .1s;background:#e5e5e5;border:1px solid #e5e5e5;color:#7f7f7f;cursor:pointer;font-size:18px;font-weight:700;padding:20px 30px;transition:background .1s,color .1s;width:100%}.tabs label:hover{background:#d8d8d8}.tabs label:active{background:#ccc}.tabs [type=radio]:focus+label{-webkit-box-shadow:inset 0 0 0 3px #2aa1c0;box-shadow:inset 0 0 0 3px #2aa1c0;z-index:1}.tabs [type=radio]{opacity:0;position:absolute}.tabs [type=radio]:checked+label{background:#fff;border-bottom:1px solid #fff;color:#000}.tabs [type=radio]:checked+label+.content{background:#fff;border:1px solid #e5e5e5;border-top:0;display:block;padding:20px 30px 30px;width:100%}.tabs .content{display:none}@media (min-width:600px){.tabs label{width:auto}.tabs .content{-ms-flex-order:99;-webkit-box-ordinal-group:100;order:99}} \ No newline at end of file +:root{-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-text-size-adjust:100%;box-sizing:border-box;cursor:default;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;overflow-y:scroll;scroll-behavior:smooth;text-size-adjust:100%}audio:not([controls]){display:none}details{display:block}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{margin:0 auto;padding:0 1.6rem 1.6rem}main,pre,summary{display:block}pre{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;font-size:1.4em;font-size:14px;font-size:1.4rem;margin:1.6rem 0;overflow:auto;padding:1.6rem;word-break:break-all;word-wrap:break-word}progress{display:inline-block}small{color:#777;font-size:75%}big{font-size:125%}template{display:none}textarea{border:.1rem solid #ccc;border-radius:0;display:block;margin-bottom:.8rem;overflow:auto;padding:.8rem;resize:vertical;vertical-align:middle}[hidden]{display:none}[unselectable]{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}*,:after,:before{-webkit-box-sizing:inherit;border-style:solid;border-width:0;box-sizing:inherit}*{font-size:inherit;line-height:inherit;margin:0;padding:0}:after,:before{text-decoration:inherit;vertical-align:inherit}a{-webkit-transition:.25s ease;color:#1271db;text-decoration:none;transition:.25s ease}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{border:.1rem solid #ccc;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit;min-height:1.4em}code,kbd,pre,samp{font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace}table{border-collapse:collapse;border-spacing:0;margin-bottom:1.6rem}::-moz-selection{background-color:#b3d4fc;text-shadow:none}::selection{background-color:#b3d4fc;text-shadow:none}button::-moz-focus-inner{border:0}body{color:#444;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:16px;font-size:1.6rem;font-style:normal;font-weight:400;padding:0}p{margin:0 0 1.6rem}h1,h2,h3,h4,h5,h6{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;margin:2rem 0 1.6rem}h1{border-bottom:.1rem solid rgba(0,0,0,.2);font-size:3.6em;font-size:36px;font-size:3.6rem}h1,h2{font-style:normal;font-weight:500}h2{font-size:3em;font-size:30px;font-size:3rem}h3{font-size:2.4em;font-size:24px;font-size:2.4rem;font-style:normal;font-weight:500;margin:1.6rem 0 .4rem}h4{font-size:1.8em;font-size:18px;font-size:1.8rem}h4,h5{font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}h5{font-size:1.6em;font-size:16px;font-size:1.6rem}h6{color:#777;font-size:1.4em;font-style:normal;font-weight:600;margin:1.6rem 0 .4rem}code,h6{font-size:14px;font-size:1.4rem}code{background:#efefef;color:#444;font-family:Anonymous Pro,Fira Code,Menlo,Monaco,Consolas,Courier New,monospace;word-break:break-all;word-wrap:break-word}a:focus,a:hover{text-decoration:none}dl{margin-bottom:1.6rem}dd{margin-left:4rem}ol,ul{margin-bottom:.8rem;padding-left:2rem}blockquote{border-left:.2rem solid #1271db;font-style:italic;margin:1.6rem 0;padding-left:1.6rem}blockquote,figcaption{font-family:Georgia,Times,Times New Roman,serif}html{font-size:62.5%}article,aside,details,footer,header,main,section,summary{display:block;height:auto;margin:0 auto;width:100%}footer{clear:both;display:inline-block;float:left;max-width:100%;padding:1rem 0;text-align:center}footer,hr{border-top:.1rem solid rgba(0,0,0,.2)}hr{display:block;margin-bottom:1.6rem;width:100%}img{height:auto;vertical-align:baseline}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select{border:.1rem solid #ccc;border-radius:0;display:inline-block;padding:.8rem;vertical-align:middle}input:not([type]){-webkit-appearance:none;background-clip:padding-box;background-color:#fff;border:.1rem solid #ccc;border-radius:0;color:#444;display:inline-block;padding:.8rem;text-align:left}input[type=color]{padding:.8rem 1.6rem}input:not([type]):focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#b3d4fc}input[type=checkbox],input[type=radio]{vertical-align:middle}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:1px thin solid #444;outline:.1rem thin solid #444}input:not([type])[disabled],input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled],select[disabled],textarea[disabled]{background-color:#efefef;color:#777;cursor:not-allowed}input[readonly],select[readonly],textarea[readonly]{background-color:#efefef;border-color:#ccc;color:#777}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{border-color:#e9322d;color:#b94a48}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#ff4136}select{background-color:#fff;border:.1rem solid #ccc}select[multiple]{height:auto}label{line-height:2}fieldset{border:0;margin:0;padding:.8rem 0}legend{border-bottom:.1rem solid #ccc;color:#444;display:block;margin-bottom:.8rem;padding:.8rem 0;width:100%}button,input[type=submit]{-moz-user-select:none;-ms-user-select:none;-webkit-transition:.25s ease;-webkit-user-drag:none;-webkit-user-select:none;border:.2rem solid #444;border-radius:0;color:#444;cursor:pointer;display:inline-block;margin-bottom:.8rem;margin-right:.4rem;padding:.8rem 1.6rem;text-align:center;text-decoration:none;text-transform:uppercase;transition:.25s ease;user-select:none;vertical-align:baseline}button a,input[type=submit] a{color:#444}button::-moz-focus-inner,input[type=submit]::-moz-focus-inner{padding:0}button:hover,input[type=submit]:hover{background:#444;border-color:#444;color:#fff}button:hover a,input[type=submit]:hover a{color:#fff}button:active,input[type=submit]:active{background:#6a6a6a;border-color:#6a6a6a;color:#fff}button:active a,input[type=submit]:active a{color:#fff}button:disabled,input[type=submit]:disabled{-webkit-box-shadow:none;box-shadow:none;cursor:not-allowed;opacity:.4}nav ul{list-style:none;margin:0;padding:0;text-align:center}nav ul li{display:inline}nav a{-webkit-transition:.25s ease;border-bottom:.2rem solid transparent;color:#444;padding:.8rem 1.6rem;text-decoration:none;transition:.25s ease}nav a:hover,nav li.selected a{border-color:rgba(0,0,0,.2)}nav a:active{border-color:rgba(0,0,0,.56)}caption{padding:.8rem 0}thead th{background:#efefef;color:#444}tr{background:#fff;margin-bottom:.8rem}td,th{border:.1rem solid #ccc;padding:.8rem 1.6rem;text-align:center;vertical-align:inherit}tfoot tr{background:none}tfoot td{color:#efefef;font-size:8px;font-size:.8rem;font-style:italic;padding:1.6rem .4rem}@media screen{[hidden~=screen]{display:inherit}[hidden~=screen]:not(:active):not(:focus):not(:target){clip:rect(0)!important;position:absolute!important}}@media screen and max-width 40rem{article,aside,section{clear:both;display:block;max-width:100%}img{margin-right:1.6rem}}.media[hidden],[hidden=hidden],template{display:none}body{margin:.5em}button{background:hsla(0,0%,100%,.65);margin:0}table{margin:0 auto}td{padding:1rem}thead td,thead th{padding:.5rem}input[type=number]{width:4em}tbody>tr:nth-child(odd){background:#ddd}a:active,a:hover{color:#7d12db}.bracketed{color:#12db18}#main-nav a,.bracketed{text-shadow:1px 1px 1px #000}.bracketed:before{content:"[\00a0"}.bracketed:after{content:"\00a0]"}.bracketed:active,.bracketed:hover{color:#db7d12}.grow-1{-ms-flex-positive:1;-webkit-box-flex:1;flex-grow:1}.flex-wrap{-ms-flex-wrap:wrap;flex-wrap:wrap}.flex-no-wrap{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.flex-align-end{-ms-flex-align:end;-webkit-box-align:end;align-items:flex-end}.flex-align-space-around{-ms-flex-line-pack:distribute;align-content:space-around}.flex-justify-space-around{-ms-flex-pack:distribute;justify-content:space-around}.flex-self-center{-ms-flex-item-align:center;align-self:center}.flex{display:-webkit-box;display:-ms-flexbox;display:flex}.small-font{font-size:16px;font-size:1.6rem}.justify{text-align:justify}.align_center{text-align:center!important}.align_left{text-align:left!important}.align_right{text-align:right!important}.valign_top{vertical-align:top}.no_border{border:none}.media-wrap{margin:0 auto;position:relative;text-align:center}.danger{background-color:#ff4136;border-color:#924949;color:#fff}.danger:active,.danger:hover{background-color:#924949;border-color:#ff4136;color:#fff}.user-btn{border-color:#12db18;color:#12db18;padding:0 .5rem;text-shadow:1px 1px 1px #000}.user-btn:active,.user-btn:hover{background-color:#db7d12;border-color:#db7d12}.full_width{width:100%}#main-nav{border-bottom:.1rem solid rgba(0,0,0,.2);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,sans-serif;font-size:3.6em;font-size:36px;font-size:3.6rem;font-style:normal;font-weight:500;margin:2rem 0 1.6rem}.cssload-loader{-webkit-perspective:780px;border-radius:50%;height:62px;left:calc(50% - 31px);perspective:780px;position:relative;width:62px}.cssload-inner{-webkit-box-sizing:border-box;border-radius:50%;box-sizing:border-box;height:100%;position:absolute;width:100%}.cssload-inner.cssload-one{-webkit-animation:cssload-rotate-one 1.15s linear infinite;animation:cssload-rotate-one 1.15s linear infinite;border-bottom:3px solid #000;left:0;top:0}.cssload-inner.cssload-two{-webkit-animation:cssload-rotate-two 1.15s linear infinite;animation:cssload-rotate-two 1.15s linear infinite;border-right:3px solid #000;right:0;top:0}.cssload-inner.cssload-three{-webkit-animation:cssload-rotate-three 1.15s linear infinite;animation:cssload-rotate-three 1.15s linear infinite;border-top:3px solid #000;bottom:0;right:0}@-webkit-keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@keyframes cssload-rotate-one{0%{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(0deg);transform:rotateX(35deg) rotateY(-45deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(-45deg) rotate(1turn);transform:rotateX(35deg) rotateY(-45deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@keyframes cssload-rotate-two{0%{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(0deg);transform:rotateX(50deg) rotateY(10deg) rotate(0deg)}to{-webkit-transform:rotateX(50deg) rotateY(10deg) rotate(1turn);transform:rotateX(50deg) rotateY(10deg) rotate(1turn)}}@-webkit-keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}@keyframes cssload-rotate-three{0%{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(0deg);transform:rotateX(35deg) rotateY(55deg) rotate(0deg)}to{-webkit-transform:rotateX(35deg) rotateY(55deg) rotate(1turn);transform:rotateX(35deg) rotateY(55deg) rotate(1turn)}}.sorting,.sorting_asc,.sorting_desc{vertical-align:text-bottom}.sorting:before{content:" ↕\00a0"}.sorting_asc:before{content:" ↑\00a0"}.sorting_desc:before{content:" ↓\00a0"}.form thead th,.form thead tr{background:inherit;border:0}.form tr>td:nth-child(odd){max-width:30%;min-width:25px;text-align:right}.form tr>td:nth-child(2n){text-align:left}.invisible tbody>tr:nth-child(odd){background:inherit}.invisible td,.invisible th,.invisible tr{border:0}.message{margin:.5em auto;padding:.5em;position:relative;width:95%}.message .close{height:1em;line-height:1em;position:absolute;right:.5em;text-align:center;top:.5em;vertical-align:middle;width:1em}.message:hover .close:after{content:"☒"}.message:hover{cursor:pointer}.message .icon{left:.5em;margin-right:1em;top:.5em}.message.error{background:#f3e6e6;border:1px solid #924949}.message.error .icon:after{content:"✘"}.message.success{background:#70dda9;border:1px solid #1f8454}.message.success .icon:after{content:"✔"}.message.info{background:#ffc;border:1px solid #bfbe3a}.message.info .icon:after{content:"⚠"}.character,.media,.small_character{display:inline-block;height:311px;margin:.25em .125em;position:relative;text-align:center;vertical-align:top;width:220px;z-index:0}.details picture.cover,picture.cover{display:inline;display:initial;width:100%}.character>img,.media>img,.small_character>img{width:100%}.media .edit_buttons>button{margin:.5em auto}.media_metadata>div,.medium_metadata>div,.name,.row{color:#fff;padding:.25em .125em;text-align:right;text-shadow:2px 2px 2px #000;z-index:2}.age_rating,.media_type{text-align:left}.media>.media_metadata{bottom:0;position:absolute;right:0}.media>.medium_metadata{bottom:0;left:0;position:absolute}.media>.name{position:absolute;top:0}.media>.name a{-webkit-transition:none;transition:none}.media .name a:before{content:"";display:block;height:311px;left:0;position:absolute;top:0;width:220px;z-index:-1}.media-list .media:hover .name a:before{background:rgba(0,0,0,.75)}.media>.name span.canonical{font-weight:700}.media>.name small{font-weight:400}.media:hover .name{background:rgba(0,0,0,.75)}.media-list .media>.name a:hover,.media-list .media>.name a:hover small{color:#1271db}.media:hover>.edit_buttons[hidden],.media:hover>button[hidden]{-webkit-transition:.25s ease;display:block;transition:.25s ease}.media:hover{-webkit-transition:.25s ease;transition:.25s ease}.character>.name a,.character>.name a small,.media>.name a,.media>.name a small,.small_character>.name a,.small_character>.name a small{background:none;color:#fff;text-shadow:2px 2px 2px #000}.anime .name,.manga .name{background:#000;background:rgba(0,0,0,.45);padding:.5em .25em;text-align:center;width:100%}.anime .age_rating,.anime .airing_status,.anime .completion,.anime .delete,.anime .edit,.anime .media_type,.anime .user_rating{background:none;text-align:center}.anime .table,.manga .table{bottom:0;left:0;position:absolute;width:100%}.anime .row,.manga .row{-ms-flex-line-pack:distribute;-ms-flex-pack:distribute;align-content:space-around;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around;padding:0 inherit;text-align:center;width:100%}.anime .row>span,.manga .row>span{text-align:left;z-index:2}.anime .row>div,.manga .row>div{-ms-flex-item-align:center;align-self:center;display:flex-item;font-size:.8em;text-align:center;vertical-align:middle;z-index:2}.anime .media>button.plus_one{border-color:hsla(0,0%,100%,.65);left:44px;left:calc(50% - 66.5px);position:absolute;top:138px;top:calc(50% - 21.5px);z-index:50}.anime .media>button.plus_one:hover{background:#888;color:hsla(0,0%,100%,.65)}.anime .media>button.plus_one:active{background:#444}.manga .row{padding:1px}.manga .media{height:310px;margin:.25em}.manga .media>.edit_buttons{left:43.5px;left:calc(50% - 66.5px);position:absolute;top:86px;top:calc(50% - 22.4px);z-index:40}.manga .media>.edit_buttons button{border-color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons:hover button{background:#888;color:hsla(0,0%,100%,.65)}.manga .media>.edit_buttons button:active{background:#444}.media.search>.name{background-color:#555;background-color:rgba(0,0,0,.35);background-repeat:no-repeat;background-size:cover;background-size:contain}.media.search>.row{z-index:6}.big-check,.mal-check{display:none}.big-check:checked+label{-webkit-transition:.25s ease;background:rgba(0,0,0,.75);transition:.25s ease}.big-check:checked+label:after{color:#adff2f;content:"✓";font-size:15em;font-size:150px;font-size:15rem;height:100%;left:0;position:absolute;text-align:center;top:147px;width:100%;z-index:5}#series_list article.media{position:relative}#series_list .name,#series_list .name label{display:block;height:100%;left:0;line-height:1.25em;position:absolute;top:0;vertical-align:middle;width:100%}#series_list .name small{color:#fff}.details{font-size:inherit;margin:1.5rem auto 0;padding:1rem}.description{max-width:800px;max-width:80rem}.fixed{max-width:930px;max-width:93rem}.details .cover{display:block;width:284px}.details h2{margin-top:0}.details .flex>div{margin:1rem}.details .media_details{max-width:300px}.details .media_details td{padding:0 1.5rem}.details p{text-align:justify}.details .media_details td:nth-child(odd){text-align:right;white-space:nowrap;width:1%}.details .media_details td:nth-child(2n){text-align:left}.character,.small_character{height:350px;vertical-align:middle;white-space:nowrap;width:225px}.character:hover .name,.small_character:hover .name{background:rgba(0,0,0,.8)}.small_character a{display:inline-block;height:100%;width:100%}.character .name,.small_character .name{bottom:0;left:0;position:absolute;z-index:10}.character img,.small_character img{-webkit-transform:translateY(-50%);position:relative;top:50%;transform:translateY(-50%);width:100%;z-index:5}.min-table{margin-left:0;min-width:0}.small_character{height:250px;width:160px}.user-page .media-wrap{text-align:left}.media a{display:inline-block;height:100%;width:100%}@media screen and (max-width:40em){nav a{line-height:4em;line-height:4rem}.media{margin:2px 0}main{padding:0 .5rem .5rem}}.streaming-logo{height:50px;vertical-align:middle;width:50px}.cover_streaming_link{display:none}.media:hover .cover_streaming_link{display:block}.cover_streaming_link .streaming-logo{-webkit-filter:drop-shadow(0 -1px 4px #fff);filter:drop-shadow(0 -1px 4px #fff);height:20px;width:20px}#loading-shadow{background:rgba(0,0,0,.8);z-index:500}#loading-shadow,#loading-shadow .loading-wrapper{height:100%;left:0;position:fixed;top:0;width:100%}#loading-shadow .loading-wrapper{-ms-flex-align:center;-ms-flex-pack:center;-webkit-box-align:center;-webkit-box-pack:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:center;z-index:501}#loading-shadow .loading-content{color:#fff;position:relative}.loading-content .cssload-inner.cssload-one,.loading-content .cssload-inner.cssload-three,.loading-content .cssload-inner.cssload-two{border-color:#fff}.tabs{-ms-flex-wrap:wrap;-webkit-box-shadow:0 48px 80px -32px rgba(0,0,0,.3);background:#efefef;box-shadow:0 48px 80px -32px rgba(0,0,0,.3);display:-webkit-box;display:-ms-flexbox;display:flex;flex-wrap:wrap;margin-top:1.5em}.tabs label{-webkit-transition:background .1s,color .1s;background:#e5e5e5;border:1px solid #e5e5e5;color:#7f7f7f;cursor:pointer;font-size:18px;font-weight:700;padding:20px 30px;transition:background .1s,color .1s;width:100%}.tabs label:hover{background:#d8d8d8}.tabs label:active{background:#ccc}.tabs [type=radio]:focus+label{-webkit-box-shadow:inset 0 0 0 3px #2aa1c0;box-shadow:inset 0 0 0 3px #2aa1c0;z-index:1}.tabs [type=radio]{opacity:0;position:absolute}.tabs [type=radio]:checked+label{background:#fff;border-bottom:1px solid #fff;color:#000}.tabs [type=radio]:checked+label+.content{background:#fff;border:1px solid #e5e5e5;border-top:0;display:block;padding:20px 30px 30px;width:100%}.tabs .content{display:none}@media (min-width:600px){.tabs label{width:auto}.tabs .content{-ms-flex-order:99;-webkit-box-ordinal-group:100;order:99}}fieldset.box{display:inline-block;margin:2em;vertical-align:top;width:40%}fieldset.box section{margin:0 auto;text-align:center}fieldset.box article{margin:auto;text-align:left} \ No newline at end of file diff --git a/public/css/base.css b/public/css/base.css index 63f1be7c..a319ef7b 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -384,6 +384,12 @@ a:hover, a:active { z-index: 0; } +.details picture.cover, +picture.cover { + display: initial; + width: 100%; +} + .media > img, .character > img, .small_character > img { @@ -915,3 +921,23 @@ CSS Tabs } } +/* ---------------------------------------------------------------------------- +Settings page forms +-----------------------------------------------------------------------------*/ +fieldset.box { + display: inline-block; + vertical-align:top; + width:40%; + margin: 2em; +} + +fieldset.box section { + margin: 0 auto; + text-align:center; +} + +fieldset.box article { + margin: auto; + text-align:left; +} + diff --git a/src/Controller.php b/src/Controller.php index da2efd69..995a6ba5 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -86,11 +86,11 @@ class Controller { ]; /** - * Constructor + * Controller constructor. * - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException * @param ContainerInterface $container + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException */ public function __construct(ContainerInterface $container) { @@ -140,10 +140,9 @@ class Controller { /** * Set the current url in the session as the target of a future redirect * - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException - * @param string|null $url - * @return void + * @param string|NULL $url + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException */ public function setSessionRedirect(string $url = NULL): void { @@ -180,8 +179,8 @@ class Controller { * Redirect to the url previously set in the session * * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return void */ public function sessionRedirect() @@ -205,8 +204,8 @@ class Controller { * @param string $template * @param array $data * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return string */ protected function loadPartial($view, string $template, array $data = []) @@ -239,8 +238,8 @@ class Controller { * @param string $template * @param array $data * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return void */ protected function renderFullPage($view, string $template, array $data) @@ -269,8 +268,8 @@ class Controller { * @param string $title * @param string $message * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return void */ public function notFound( @@ -292,8 +291,8 @@ class Controller { * @param string $message * @param string $long_message * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return void */ public function errorPage(int $httpCode, string $title, string $message, string $long_message = ''): void @@ -313,7 +312,7 @@ class Controller { */ public function redirectToDefaultRoute(): void { - $defaultType = $this->config->get(['routes', 'route_config', 'default_list']) ?? 'anime'; + $defaultType = $this->config->get('default_list') ?? 'anime'; $this->redirect($this->urlGenerator->defaultUrl($defaultType), 303); } @@ -360,8 +359,8 @@ class Controller { * @param string $type * @param string $message * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return string */ protected function showMessage($view, string $type, string $message): string @@ -380,8 +379,8 @@ class Controller { * @param HtmlView|null $view * @param int $code * @throws InvalidArgumentException - * @throws \Aviat\Ion\Di\ContainerException - * @throws \Aviat\Ion\Di\NotFoundException + * @throws \Aviat\Ion\Di\Exception\ContainerException + * @throws \Aviat\Ion\Di\Exception\NotFoundException * @return void */ protected function outputHTML(string $template, array $data = [], $view = NULL, int $code = 200) @@ -393,6 +392,7 @@ class Controller { $view->setStatusCode($code); $this->renderFullPage($view, $template, $data); + exit(); } /** @@ -407,8 +407,9 @@ class Controller { { (new JsonView($this->container)) ->setStatusCode($code) - ->setOutput($data) - ->send(); + ->setOutput($data); + // ->send(); + exit(); } /** diff --git a/src/Controller/Index.php b/src/Controller/Index.php index b30fa09e..d509402a 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -20,12 +20,21 @@ use function Amp\Promise\wait; use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\API\{HummingbirdClient, JsonAPI}; +use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\View\HtmlView; /** * Controller for handling routes that don't fit elsewhere */ final class Index extends BaseController { + private $settingsModel; + + public function __construct(ContainerInterface $container) + { + parent::__construct($container); + + $this->settingsModel = $container->get('settings-model'); + } /** * Purges the API cache @@ -91,6 +100,7 @@ final class Index extends BaseController { */ public function anilistCallback() { + dump($_GET); $this->outputHTML('blank', [ 'title' => 'Oauth!' ]); @@ -169,8 +179,10 @@ final class Index extends BaseController { public function settings() { $auth = $this->container->get('auth'); + $form = $this->settingsModel->getSettingsForm(); $this->outputHTML('settings', [ 'auth' => $auth, + 'form' => $form, 'config' => $this->config, 'title' => $this->config->get('whose_list') . "'s Settings", ]); @@ -191,6 +203,7 @@ final class Index extends BaseController { * * @param string $type The category of image * @param string $file The filename to look for + * @param bool $display Whether to output the image to the server * @throws \Aviat\Ion\Di\ContainerException * @throws \Aviat\Ion\Di\NotFoundException * @throws \InvalidArgumentException @@ -199,26 +212,30 @@ final class Index extends BaseController { * @throws \Throwable * @return void */ - public function images(string $type, string $file): void + public function images(string $type, string $file, $display = TRUE): void { $kitsuUrl = 'https://media.kitsu.io/'; - [$id, $ext] = explode('.', basename($file)); + $fileName = str_replace('-original', '', $file); + [$id, $ext] = explode('.', basename($fileName)); switch ($type) { case 'anime': - $kitsuUrl .= "anime/poster_images/{$id}/small.{$ext}"; + $kitsuUrl .= "anime/poster_images/{$id}/small.jpg"; + $width = 220; break; case 'avatars': - $kitsuUrl .= "users/avatars/{$id}/original.{$ext}"; + $kitsuUrl .= "users/avatars/{$id}/original.jpg"; break; case 'manga': - $kitsuUrl .= "manga/poster_images/{$id}/small.{$ext}"; + $kitsuUrl .= "manga/poster_images/{$id}/small.jpg"; + $width = 220; break; case 'characters': - $kitsuUrl .= "characters/images/{$id}/original.{$ext}"; + $kitsuUrl .= "characters/images/{$id}/original.jpg"; + $width = 225; break; default: @@ -231,9 +248,38 @@ final class Index extends BaseController { $data = wait($response->getBody()); $baseSavePath = $this->config->get('img_cache_path'); - file_put_contents("{$baseSavePath}/{$type}/{$id}.{$ext}", $data); - header('Content-type: ' . $response->getHeader('content-type')[0]); - echo $data; + $filePrefix = "{$baseSavePath}/{$type}/{$id}"; + + [$origWidth, $origHeight] = getimagesizefromstring($data); + $gdImg = imagecreatefromstring($data); + $resizedImg = imagescale($gdImg, $width ?? $origWidth); + + // save the webp versions + imagewebp($gdImg, "{$filePrefix}-original.webp"); + imagewebp($resizedImg, "{$filePrefix}.webp"); + + // save the scaled jpeg file + imagejpeg($resizedImg, "{$filePrefix}.jpg"); + + imagedestroy($gdImg); + imagedestroy($resizedImg); + + // And the original + file_put_contents("{$filePrefix}-original.jpg", $data); + + if ($display) + { + $contentType = ($ext === 'webp') + ? "image/webp" + : $response->getHeader('content-type')[0]; + + $outputFile = (strpos($file, '-original') !== FALSE) + ? "{$filePrefix}-original.{$ext}" + : "{$filePrefix}.{$ext}"; + + header("Content-Type: {$contentType}"); + echo file_get_contents($outputFile); + } } /** diff --git a/src/FormGenerator.php b/src/FormGenerator.php new file mode 100644 index 00000000..1a642347 --- /dev/null +++ b/src/FormGenerator.php @@ -0,0 +1,115 @@ + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient; + +use Aviat\Ion\ +{ + ArrayWrapper, StringWrapper +}; +use Aviat\Ion\Di\ContainerInterface; + +/** + * Helper object to manage form generation, especially for config editing + */ +final class FormGenerator { + use ArrayWrapper; + use StringWrapper; + + /** + * Injection Container + * @var ContainerInterface $container + */ + protected $container; + + /** + * Html generation helper + * + * @var \Aura\Html\HelperLocator + */ + protected $helper; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + $this->helper = $container->get('html-helper'); + } + + /** + * Generate the html structure of the form + * + * @param string $name + * @param array $form + * @return string + */ + public function generate(string $name, array $form) + { + $type = $form['type']; + + if ($form['display'] === FALSE) + { + return $this->helper->input([ + 'type' => 'hidden', + 'name' => $name, + 'value' => $form['value'], + ]); + } + + $params = [ + 'name' => $name, + 'value' => $form['value'], + 'attribs' => [ + 'id' => $name, + ], + ]; + + switch($type) + { + case 'boolean': + /* $params['type'] = 'checkbox'; + $params['attribs']['label'] = $form['description']; + $params['attribs']['value'] = TRUE; + $params['attribs']['value_unchecked'] = '0'; */ + + $params['type'] = 'radio'; + $params['options'] = [ + '1' => 'Yes', + '0' => 'No', + ]; + unset($params['attribs']['id']); + break; + + case 'string': + $params['type'] = 'text'; + break; + + case 'select': + $params['type'] = 'select'; + $params['options'] = array_flip($form['options']); + break; + } + + foreach (['readonly', 'disabled'] as $key) + { + if ($form[$key] !== FALSE) + { + $params['attribs'][$key] = $form[$key]; + } + } + + return $this->helper->input($params); + } +} \ No newline at end of file diff --git a/src/Helper/Form.php b/src/Helper/Form.php new file mode 100644 index 00000000..244030ab --- /dev/null +++ b/src/Helper/Form.php @@ -0,0 +1,41 @@ + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Helper; + +use Aviat\AnimeClient\FormGenerator; +use Aviat\Ion\Di\ContainerAware; + +/** + * MenuGenerator helper wrapper + */ +final class Form { + + use ContainerAware; + + /** + * Create the html for the selected menu + * + * @param string $name + * @param array $form + * @return string + */ + public function __invoke(string $name, array $form) + { + return (new FormGenerator($this->container))->generate($name, $form); + } +} +// End of Menu.php \ No newline at end of file diff --git a/src/Model/Settings.php b/src/Model/Settings.php new file mode 100644 index 00000000..fcb8fab4 --- /dev/null +++ b/src/Model/Settings.php @@ -0,0 +1,286 @@ + + * @copyright 2015 - 2018 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 4.1 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Model; + +use Aviat\AnimeClient\Types\{Config, UndefinedPropertyException}; + +use Aviat\Ion\ConfigInterface; +use Aviat\Ion\Di\ContainerAware; +use Aviat\Ion\StringWrapper; + +/** + * Model for handling settings control panel + */ +final class Settings { + use ContainerAware; + use StringWrapper; + + private $config; + + /** + * Map the config values to types and form fields + */ + private const SETTINGS_MAP = [ + 'anilist' => [ + + ], + 'config' => [ + 'kitsu_username' => [ + 'type' => 'string', + 'title' => 'Kitsu Username', + 'default' => '', + 'readonly' => TRUE, + 'description' => 'Username of the account to pull list data from.', + ], + 'whose_list' => [ + 'type' => 'string', + 'title' => 'Whose List', + 'default' => 'Somebody', + 'readonly' => TRUE, + 'description' => 'Name of the owner of the list data.', + ], + 'show_anime_collection' => [ + 'type' => 'boolean', + 'title' => 'Show Anime Collection', + 'default' => FALSE, + 'description' => 'Should the anime collection be shown?', + ], + 'show_manga_collection' => [ + 'type' => 'boolean', + 'title' => 'Show Manga Collection', + 'default' => FALSE, + 'description' => 'Should the manga collection be shown?', + ], + 'asset_path' => [ + 'type' => 'string', + 'display' => FALSE, + 'description' => 'Path to public directory, where images/css/javascript are located', + ], + 'default_list' => [ + 'type' => 'select', + 'title' => 'Default List', + 'description' => 'Which list to show by default.', + 'options' => [ + 'Anime' => 'anime', + 'Manga' => 'manga', + ], + ], + 'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all + 'type' => 'select', + 'title' => 'Default Anime List Section', + 'description' => 'Which part of the anime list to show by default.', + 'options' => [ + 'Watching' => 'watching', + 'Plan to Watch' => 'plan_to_watch', + 'On Hold' => 'on_hold', + 'Dropped' => 'dropped', + 'Completed' => 'completed', + 'All' => 'all', + ] + ], + 'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all + 'type' => 'select', + 'title' => 'Default Manga List Section', + 'description' => 'Which part of the manga list to show by default.', + 'options' => [ + 'Reading' => 'reading', + 'Plan to Read' => 'plan_to_read', + 'On Hold' => 'on_hold', + 'Dropped' => 'dropped', + 'Completed' => 'completed', + 'All' => 'all', + ] + ] + ], + 'cache' => [ + 'driver' => [ + 'type' => 'select', + 'title' => 'Cache Type', + 'description' => 'The Cache backend', + 'options' => [ + 'APCu' => 'apcu', + 'Memcached' => 'memcached', + 'Redis' => 'redis', + 'No Cache' => 'null' + ], + ], + 'connection' => [ + 'type' => 'subfield', + 'title' => 'Connection', + 'fields' => [ + 'host' => [ + 'type' => 'string', + 'title' => 'Cache Host', + 'description' => 'Host of the cache backend to connect to', + ], + 'port' => [ + 'type' => 'string', + 'title' => 'Cache Port', + 'description' => 'Port of the cache backend to connect to', + ], + 'password' => [ + 'type' => 'string', + 'title' => 'Cache Password', + 'description' => 'Password to connect to cache backend', + ], + 'database' => [ + 'type' => 'string', + 'title' => 'Cache Database', + 'description' => 'Cache database number for Redis', + ], + ], + ], + ], + 'database' => [ + 'type' => [ + 'type' => 'select', + 'title' => 'Database Type', + 'options' => [ + 'MySQL' => 'mysql', + 'PostgreSQL' => 'pgsql', + 'SQLite' => 'sqlite', + ], + 'description' => 'Type of database to connect to', + ], + 'host' => [ + 'type' => 'string', + 'title' => 'Host', + 'description' => 'The host of the database server', + ], + 'user' => [ + 'type' => 'string', + 'title' => 'User', + 'description' => 'Database connection user', + ], + 'pass' => [ + 'type' => 'string', + 'title' => 'Password', + 'description' => 'Database connection password' + ], + 'port' => [ + 'type' => 'string', + 'title' => 'Port', + 'description' => 'Database connection port' + ], + 'database' => [ + 'type' => 'string', + 'title' => 'Database Name', + 'description' => 'Name of the database/schema to connect to', + ], + 'file' => [ + 'type' => 'string', + 'title' => 'Database File', + 'description' => 'Path to the database file, if required by the current database type.' + ], + ], + ]; + + public function __construct(ConfigInterface $config) + { + $this->config = $config; + } + + public function getSettings() + { + $settings = [ + 'config' => [], + ]; + + foreach(static::SETTINGS_MAP as $file => $values) + { + if ($file === 'config') + { + $keys = array_keys($values); + foreach($keys as $key) + { + $settings['config'][$key] = $this->config->get($key); + } + } + else + { + $settings[$file] = $this->config->get($file); + } + } + + return $settings; + } + + public function getSettingsForm() + { + $output = []; + + $settings = $this->getSettings(); + + foreach($settings as $file => $values) + { + foreach(static::SETTINGS_MAP[$file] as $key => $value) + { + if ($value['type'] === 'subfield') + { + foreach($value['fields'] as $k => $field) + { + $value['fields'][$k]['value'] = $values[$key][$k] ?? ''; + $value['fields'][$k]['display'] = TRUE; + $value['fields'][$k]['readonly'] = FALSE; + $value['fields'][$k]['disabled'] = FALSE; + } + } + + if (is_scalar($values[$key])) + { + $value['value'] = $values[$key]; + } + + foreach (['readonly', 'disabled'] as $flag) + { + if ( ! array_key_exists($flag, $value)) + { + $value[$flag] = FALSE; + } + } + + if ( ! array_key_exists('display', $value)) + { + $value['display'] = TRUE; + } + + $output[$file][$key] = $value; + } + } + + return $output; + } + + public function validateSettings(array $settings): bool + { + try + { + new Config($settings); + } + catch (UndefinedPropertyException $e) + { + return FALSE; + } + + return TRUE; + } + + public function saveSettingsFile() + { + + } +} \ No newline at end of file