MOD_Seiga

ニコニコ静画のUIをいじる

// ==UserScript==
// @name        MOD_Seiga
// @namespace   https://github.com/segabito/
// @description ニコニコ静画のUIをいじる
// @include     *://seiga.nicovideo.jp/seiga/*
// @include     *://seiga.nicovideo.jp/tag/*
// @include     *://seiga.nicovideo.jp/illust/*
// @include     *://lohas.nicoseiga.jp/o/*
// @version     0.4.2
// @grant       none
// @require     https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js
// ==/UserScript==

// 本家にprototype.jsが入って衝突するようになった
window.jQuery.noConflict();

(function() {
  var monkey = (function(){
    'use strict';
    var $ = window.jQuery;

    var A4_WIDTH = 210, A4_HEIGHT = 297;


    window.MOD_Seiga = {
      initialize: function() {
        this.initializeUserConfig();
        var path = location.pathname;
        if (path.indexOf('/seiga/') === 0) {
          this.initializeSeigaView();
        } else
        if (path.indexOf('/illust/') === 0 && path.indexOf('ranking') < 0) {
          this.initializeIllustTop();
        } else
        if (path.indexOf('/tag/') === 0) {
          this.initializeTagSearch();
        } else
        if (path.indexOf('/o/') === 0) {
          this.initializeFullView();
        }

      },
      initializeSeigaView: function() {
        if (document.querySelector('#content')) {
          this.initializeBaseLayout();

          this.initializeScrollTop();
          this.initializeDescription();
          this.initializeCommentLink();
          this.initializeThumbnail();
          this.initializePageTopButton();
          this.initializeKnockout();
          this.initializeFullScreenRequest();
          this.initializeSaveScreenshotRequest();

          this.initializeOther();
          this.initializeSettingPanel();

          document.body.classList.add('MOD_Seiga_View');
          this.initializeCss();
        } else {
          // 春画っぽい。説明文の自動リンクだけやる
          this.initializeDescription();
        }

      },
      initializeIllustTop: function() {
        this.initializeThumbnail();
        this.initializeSettingPanel();

        this.initializePageTopButton();
        document.body.classList.add('MOD_Seiga_Top');
        this.initializeCss();
      },
      initializeTagSearch: function() {
        this.initializeThumbnail();
        this.initializeSettingPanel();

        this.initializePageTopButton();
        setTimeout(function() {
          var search = document.querySelector('#bar_search');
          var tagwatchQuery =
            document.querySelector('#ko_tagwatch_min').getAttribute('data-query');
          var val = search.value;
          if (
            val === 'イラストを検索' ||
            val === '春画を検索') {
            search.value = tagwatchQuery;
            search.classList.add('edited');
            search.style.color = '#000';
          }
        }, 1000);
        document.body.classList.add('MOD_Seiga_TagSearch');
        this.initializeCss();
      },
      initializeFullView: function() {
        document.body.classList.add('MOD_Seiga_FullView');
        this.initializeFullscreenImage();
        this.initializeSaveScreenshot();
        this.initializeCss();
      },
      addStyle: function(styles, id) {
        var elm = document.createElement('style');
        elm.type = 'text/css';
        if (id) { elm.id = id; }

        var text = styles.toString();
        text = document.createTextNode(text);
        elm.appendChild(text);
        var head = document.getElementsByTagName('head');
        head = head[0];
        head.appendChild(elm);
        return elm;
      },
      fullScreen: {
        now: function() {
          if (document.fullScreenElement || document.mozFullScreen || document.webkitIsFullScreen) {
            return true;
          }
          return false;
        },
        request: function(target) {
          var elm = typeof target === 'string' ? document.getElementById(target) : target;
          if (!elm) { return; }
          if (elm.requestFullScreen) {
            elm.requestFullScreen();
          } else if (elm.webkitRequestFullScreen) {
            elm.webkitRequestFullScreen();
          } else if (elm.mozRequestFullScreen) {
            elm.mozRequestFullScreen();
          }
        },
        cancel: function() {
          if (document.cancelFullScreen) {
            document.cancelFullScreen();
          } else if (document.webkitCancelFullScreen) {
            document.webkitCancelFullScreen();
          } else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
          }
        }
      },
      initializeCss: function() {
        var __common_css__ = (`
          .MOD_SeigaSettingMenu a {
            font-weight: bolder; color: darkblue !important;
          }
          #MOD_SeigaSettingPanel {
            position: fixed;
            bottom: 2000px; right: 8px;
            z-index: -1;
            width: 500px;
            background: #f0f0f0; border: 1px solid black;
            padding: 8px;
            transition: bottom 0.4s ease-out;
            text-align: left;
          }
          #MOD_SeigaSettingPanel.open {
            display: block;
            bottom: 8px;
            box-shadow: 0 0 8px black;
            z-index: 10000;
          }
          #MOD_SeigaSettingPanel .close {
            position: absolute;
            cursor: pointer;
            right: 8px; top: 8px;
          }
          #MOD_SeigaSettingPanel .panelInner {
            background: #fff;
            border: 1px inset;
            padding: 8px;
            min-height: 300px;
            overflow-y: scroll;
            max-height: 500px;
          }
          #MOD_SeigaSettingPanel .panelInner .item {
            border-bottom: 1px dotted #888;
            margin-bottom: 8px;
            padding-bottom: 8px;
          }
          #MOD_SeigaSettingPanel .panelInner .item:hover {
            background: #eef;
          }
          #MOD_SeigaSettingPanel .windowTitle {
            font-size: 150%;
          }
          #MOD_SeigaSettingPanel .itemTitle {
          }
          #MOD_SeigaSettingPanel label {
            margin-right: 12px;
          }
          #MOD_SeigaSettingPanel small {
            color: #666;
          }
          #MOD_SeigaSettingPanel .expert {
            margin: 32px 0 16px;
            font-size: 150%;
            background: #ccc;
          }

          .comment_info .mod_link, .description .otherSite {
            text-decoration: underline;
            font-weight: bolder;
          }

          /* 画面が狭いときに操作不能になる部分などを直す */
          @media screen and (max-width: 1023px) {
            .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .comment_list {
              position: fixed;
              right: 25px;
              top: 105px; bottom: 105px; overflow-y: auto;
            }
            .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .comment_list .text {
              margin: 0 16px 0 0;
            }
            .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_inner .illust_main .illust_side .illust_comment .res .inner {
              position: fixed;
              bottom: 0; right: 5px;
            }
            .mod_hidePageTopButton.MOD_Seiga .comment_all .comment_all_header .control{
              position: fixed; top: 35px; right: 25px; /* 横幅が狭いと閉じるを押せない問題の対応 */
            }
          }
          @media screen and (max-width: 1183px) {
            .mod_hidePageTopButton #pagetop { display: none !important; }
          }

          @media print {

            body {
              background: #000 !important; /* 背景を黒にしたい場合は「背景画像を印刷」にチェック */
              margin: 0;
              padding: 0;
              overflow: hidden;
              width: 210mm;
                 /* height: calc(297mm - 19mm); */ /* 19mmは印刷マージン */
            }
            body.landscape {
              width: 297mm;
                     /* height: calc(210mm - 19mm); */
            }
            .toggleFullScreen, .control {
              display: none !important;
              opacity: 0 !important;
            }

            .MOD_Seiga_FullView .illust_view_big img {
              top: 0 !important;
              left: 0 !important;
              transform: inherit !important;
              -webkit-transform: inherit !important;
            }

            .MOD_Seiga_FullView:not(.landscape) .illust_view_big img {
              width: auto;
              height: auto;
            }
            .MOD_Seiga_FullView:not(.landscape).fitV .illust_view_big img {
              /*width: auto;
              height: calc(297mm - 19mm); */
            }
            .MOD_Seiga_FullView.landscape .illust_view_big img {
              /*width: 297mm;
              height: auto; */
            }
            .MOD_Seiga_FullView.landscape.fitV .illust_view_big img {
              /*width: auto;
              height: calc(210mm - 19mm); */
              margin-top: 0;
            }
          }

          .saveScreenshotFrame {
            position: fixed;
            top: -200%;
            left: -200%;
            width: 32px;
            left: 32px;
          }

        `).trim();

        var __css__ = (`


        /* マイページや投稿へのリンクがあっても、すぐ上にniconico共通のヘッダーがあるのでいらないと思う。ということで省スペース優先で消す。*/
        #header { background: #fff; }
        #header .sg_global_bar {
          display: none;
        }
        #header_cnt { width: 1004px; }

        /* サムネのホバー調整 */
        .list_item_cutout.middle {
          height: 154px;
          text-align: center;
        }
        .list_item_cutout.middle a {
          height: 100%;
          overflow: visible;
        }
        .list_item_cutout.middle a .illust_info, .list_item_cutout.middle a .illust_info:hover {
          bottom: 0;
        }

        /* サムネのカットなくすやつ。 */
        .list_item_cutout.mod_no_trim  .thum img {
          display: none;
        }


        .list_item_cutout.mod_no_trim  .thum {
          display: block;
          width: 100%;
          height: calc(100% - 40px);
          background-repeat: no-repeat;
          background-position: center center;
          background-size: contain;
          -moz-background-size: contain;
          -webkit-background-size: contain;
          -o-background-size: contain;
          -ms-background-size: contain;
         }

        .list_item.mod_no_trim  .thum img {
          display: none;
        }

        .list_item.mod_no_trim  .thum {
          display: block;
          width: 100%;
          height: 0;
          background-repeat: no-repeat;
          background-position: center center;
          background-size: contain;
          -moz-background-size: contain;
          -webkit-background-size: contain;
          -o-background-size: contain;
          -ms-background-size: contain;
        }
        .MOD_Seiga_View .list_item.mod_no_trim  .thum {
        }
        .list_item.mod_no_trim .thum:hover, .list_item_cutout.mod_no_trim .thum:hover {
          background-size: cover;
        }

        .MOD_Seiga_Top .list_item_cutout.mod_no_trim  .thum {
          height: calc(100% - 50px);
        }
        .MOD_Seiga_Top .list_item_cutout.mod_no_trim a {
          height: 100%;
          width: 100%;
        }
        .MOD_Seiga_Top .list_item_cutout.mod_no_trim.large a .illust_info {
          bottom: 0px !important;
          background-color: rgba(60, 60, 60, 1);
          padding: 10px 40px;
        }
        .MOD_Seiga_Top .list_item_cutout.mod_no_trim.large {
          width: 190px;
          height: 190px;
        }
        .MOD_Seiga_Top .rank_box .item_list.mod_no_trim .more_link a {
          width: 190px;
        }

        /* タグ検索時、カウンタなどが常時見えるように修正 */
        .MOD_Seiga_TagSearch .list_item.large a .illust_count {
          opacity: 1;
        }
        .MOD_Seiga_TagSearch .list_item.large a {
          height: 321px;
        }
        .MOD_Seiga_TagSearch .list_item.large {
          height: 351px;
        }

        /* タイトルと説明文・投稿者アイコンだけコンクリートの地面に置いてあるように感じたので絨毯を敷いた */
        .MOD_Seiga .im_head_bar .inner {
          background-color: #FFFFFF;
          border-color: #E8E8E8;
          border-radius: 5px;
          border-style: solid;
          border-width: 0 0 1px;
          box-shadow: 0 0 15px rgba(0, 0, 0, 0.05);
          display: block;
          margin-top: 20px;
          /*margin-bottom: 20px;*/
          margin-left: auto;
          margin-right: auto;
          padding: 15px;
          position: relative;
          width: 974px;
        }


        /* タグの位置調整 */
        .illust_main .mod_tag-top.illust_sub_info {
          padding-bottom: 25px;
          padding-top: 0;
        }


        .illust_sub_info.mod_tag-description-bottom {
          margin-top: 15px;
        }
        .im_head_bar .illust_tag h2 {
          float: left;
          font-size: 116.7%;
          line-height: 120%;
          margin: 4px 10px 0 -2px;
          overflow: hidden;
        }
        .im_head_bar .illust_sub_info  input#tags {
          margin-bottom: 15px;
          margin-top: 5px;
          padding: 4px 10px;
          width: 280px;
        }
        .im_head_bar .illust_sub_info ul li.btn {
          bottom: 15px;
          position: absolute;
          right: 15px;
        }

        /* タグ右上 */
        .description.mod_tag-description-right {
          float: left;
        }
        .illust_sub_info.mod_tag-description-right {
          width: 300px;
          float: right;
          margin: 0;
        }
        .mod_tag-description-right .tag {
          background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
          border: medium none;
          margin: 0;
        }
        .mod_tag-description-right .tag a {
          padding: 0 5px;
        }
        .mod_tag-description-right .tag li {
        }
        .mod_tag-description-right .tag li a {
          padding: 0 5px 0 0;
          border: 0;
        }
        .im_head_bar .illust_sub_info.mod_tag-description-right ul li.btn.active {
        }
        /* 右下だと被ることがあるので仕方なく */
        .im_head_bar .illust_sub_info.mod_tag-description-right ul li.btn {
          display: inline-block;
          position: relative;
          bottom: auto; right: auto;
        }

        #ichiba_box {
          width: 1004px;
          margin: 0 auto 20px;
          border: 1px solid #ddd;
          border-radius: 8px;
        }

        #content.illust { padding: 0; }

        #related_info .ad_tag { display: none;}

        /* 右カラム広告のせいで無駄に横スクロールが発生しているのを修正 */
        .related_info .sub_info_side {
          overflow-x: hidden;
        }


        .illust_main .illust_side .clip.mod_top {
          padding: 10px 10px 0;
        }


        .MOD_Seiga_FullView #content.illust_big .illust_view_big {
          margin: 0 auto;
        }

        .MOD_Seiga_FullView .control {
          position: absolute;
          right: 0;
          top: 0;
          z-index: 1000;
          opacity: 0;
          transition: opacity 0.5s ease;
        }

        .MOD_Seiga_FullView:hover .control {
          opacity: 1;
        }

        .MOD_Seiga_FullView .illust_view_big img {
          /*transform: scale(1); -webkit-transform: scale(1);
          transition: transform 0.3s ease, -webkit-transform 0.3s ease;*/
        }

        .MOD_Seiga_FullView:not(.mod_noScale) .illust_view_big img {
          position: absolute;
          top: 0;
          left: 0;
                  transform-origin: 0 0 0;
          -webkit-transform-origin: 0 0 0;
        }

        .MOD_Seiga_FullView.mod_contain {
          overflow: hidden;
        }
        .MOD_Seiga_FullView.mod_cover {
        }
        .MOD_Seiga_FullView.mod_contain .illust_view_big img,
        .MOD_Seiga_FullView.mod_cover   .illust_view_big img {
          /*display: none;*/
        }

        .MOD_Seiga_FullView             .illust_view_big {
          background-repeat: no-repeat;
          background-position: center center;
        }
        .MOD_Seiga_FullView.mod_contain .illust_view_big {
          background-size: contain;
        }
        .MOD_Seiga_FullView.mod_cover   .illust_view_big {
          background-size: cover;
        }

        .MOD_Seiga .toggleFullScreen,
        .MOD_Seiga .saveScreenshot {
          display: block;
          width: 200px;
          margin: 8px auto;
          transition: 0.4s opacity, 0.4s box-shadow;
        }
        .MOD_Seiga_FullView .toggleFullScreen {
          position: fixed;
          top: 0;
          left: 0;
          z-index: 1000;
          margin: 0;
          padding: 4px;
          cursor: pointer;
          border: none;
          background: transparent;
          color: #0CA5D2;
          font-weight: bolder;
        }
        .MOD_Seiga .toggleFullScreen:hover,
        .MOD_Seiga .saveScreenshot:hover {
          box-shadow: 2px 2px 2px #333;
        }

        .MOD_Seiga_FullView .toggleFullScreen button,
        .MOD_Seiga_FullView .saveScreenshot   button {
          opacity: 0;
          margin: 0;
          padding: 0;
          cursor: pointer;
          transition: 0.4s opacity, 0.4s box-shadow;
          border: 1px solid #000;
          background: #fff;
          color: #0CA5D2;
          font-weight: bolder;
        }
        .MOD_Seiga_FullView .toggleFullScreen.show button,
        .MOD_Seiga_FullView .toggleFullScreen button:hover {
          opacity: 1;
          box-shadow: 2px 2px 2px #333;
        }
        .MOD_Seiga_FullView:fullscreen .toggleFullScreen {
          display: none;
        }

        #ko_watchlist_info.mod_hide { display: none !important; }

        .saveScreenShotFrame,
        .fullScreenRequestFrame {
          position: fixed;
          width: 100px;
          height: 100px;
          left: -999px;
          top: -999px;
        }

        .MOD_Seiga_FullView  .closeButton {
          opacity: 0;
          transition: 0.4s opacity ease;
        }

        .MOD_Seiga_FullView  .closeButton:hover {
          opacity: 1;
        }

        body.fullScreenFrame {
          background: #000;
        }

        body.MOD_Seiga_FullView img {
          cursor: none;
        }

        body.MOD_Seiga_FullView.mouseMoving img{
          cursor: default;
        }


`).trim();


          this.addStyle(__common_css__);

        if (this.config.get('applyCss')) {
          this.addStyle(__css__);
        }
      },
      initializeUserConfig: function() {
        var prefix = 'MOD_Seiga_';
        var conf = {
          applyCss: true,
          topUserInfo: true,
          tagPosition: 'description-bottom',
          noTrim: true,
          hidePageTopButton: true,
          clipPosition: 'bottom',
          hideBottomUserInfo: false
        };

        this.config = {
          get: function(key) {
            try {
              if (window.localStorage.hasOwnProperty(prefix + key)) {
                return JSON.parse(window.localStorage.getItem(prefix + key));
              }
              return conf[key];
            } catch (e) {
              return conf[key];
            }
          },
          set: function(key, value) {
            window.localStorage.setItem(prefix + key, JSON.stringify(value));
          }
        };
      },
      initializeBaseLayout: function() {
        var description = document.querySelector('#content .description, #content .discription');
        description.classList.add('description');
        document.querySelector('.controll').classList.add('control');

        $('#related_info').after($('#ichiba_box'));

        if (this.config.get('hideBottomUserInfo') === true) {
          document.querySelector('#ko_watchlist_info').classList.add('mod_hide');
        }

      },
      initializeScrollTop: function() {
        var reset = this.resetScrollTop = function() {
          var nofix = document.body.classList.contains('nofix');
          var commonHeaderHeight = nofix ? 0 : 36; //$bar.outerHeight();
          document.documentElement.scrollTop = (Math.max(
            document.documentElement.scrollTop,
            128 /*$('#content .im_head_bar').offset().top*/ - commonHeaderHeight
          ));
        };
        setTimeout(reset, 100);
        reset();
      },
      initializeDescription: function() {
        var description = document.querySelector('#content .description, #content .discription, .illust_user_exp');
        if (!description) { return; }
        var html = description.innerHTML;

        // 説明文中のURLの自動リンク
        var linkmatch = /<a.*?<\/a>/, links = [], n;
        html = html.split('<br />').join(' <br /> ');
        while ((n = linkmatch.exec(html)) !== null) {
          links.push(n);
          html = html.replace(n, ' <!----> ');
        }
        html = html.replace(/(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi, '<a href="$1" target="_blank" class="otherSite">$1</a>');
        for (var i = 0, len = links.length; i < len; i++) {
          html = html.replace(' <!----> ', links[i]);
        }
        html = html.split(' <br /> ').join('<br />');

        description.innerHTML = html;
      },
      initializeCommentLink: function() {
        var videoReg = /((sm|nm|so)\d+)/g;
        var seigaReg = /(im\d+)/g;
        var bookReg  = /((bk|mg)\d+)/g;
        var urlReg   = /(https?:\/\/[\x21-\x3b\x3d-\x7e]+)/gi;

        var autoLink = function(text) {
          text = text
            .replace(videoReg, '<a href="//www.nicovideo.jp/watch/$1" class="video mod_link">$1</a>')
            .replace(seigaReg, '<a href="/seiga/$1" class="illust mod_link">$1</a>')
            .replace(bookReg,  '<a href="/watch/$1" class="book mod_link">$1</a>')
            .replace(urlReg,   '<a href="$1" target="_blank" class="otherSite mod_link">$1</a>');
          return text;
        };
        var commentLink = function(selector) {
          Array.from(document.querySelectorAll(selector)).forEach((elm) => {
            var html = elm.innerHTML, linked = autoLink(html);
            if (html !== linked) {
              elm.innerHTML = linked;
            }
            elm.classList.add('mod_linked');
          });
        };

        setTimeout(function() {
          commentLink('#comment_list .comment_info .text:not(.mod_linked)');
          var updateTimer = null;
          document.body.addEventListener('DOMNodeInserted', function(e) {
            if (e.target.className && e.target.className.indexOf('comment_list_item') >= 0) {
              if (updateTimer) {
                updateTimer = clearTimeout(updateTimer);
              }
              updateTimer = setTimeout(function() {
                updateTimer = clearTimeout(updateTimer);
                commentLink('#comment_list .comment_info .text:not(.mod_linked)');
              }, 100);
            }
          });

        }, 100);
      },
      initializeThumbnail: function() {
        if (this.config.get('noTrim') !== true) { return; }

        var treg = /^(https?:\/\/lohas.nicoseiga.jp\/+thumb\/+.\d+)([a-z\?]*)/;
        var noTrim = function() {
          Array.from(document.querySelectorAll('.list_item_cutout, #main .list_item:not(.mod_no_trim)')).forEach((elm) => {
            var $thum = $(elm).find('.thum');
            var $img  = $thum.find('img');
            var src   = $img.attr('src') || '';
            if ($thum.length * $img.length < 1 || !treg.test(src)) return;
            // TODO: 静画のサムネの種類を調べる
            var url = RegExp.$1 + 'q?';//RegExp.$2 === 't' ? src : RegExp.$1 + 'q?';
            $thum.css({'background-image': 'url("' + url + '")'});
            elm.classList.add('mod_no_trim');
          });
        };
        noTrim();
        document.body.addEventListener('AutoPagerize_DOMNodeInserted', function() {
          setTimeout(function() {
            noTrim();
          }, 500);
        });
      },
      initializePageTopButton: function() {
        document.body.classList.toggle('mod_hidePageTopButton',
          this.config.get('hidePageTopButton'));
      },
      initializeKnockout: function() {
      },
      initializeFullScreenRequest: function() {
        var $body = $('body');
        var $iframe = $('<iframe allowfullscreen="on" class="fullScreenRequestFrame" name="fullScreenRequestFrame"></iframe>');

        $body.append($iframe);

        var $fullScreenButton = $([
          '<button class="toggleFullScreen btn normal" title="フルスクリーン表示に切り換えます(Fキー)">',
            '<span>フルスクリーン</span>',
          '</button>',
          ''].join(''));
        $('.illust_main .illust_wrapper .inner .thum_large:first').append($fullScreenButton);
        var toggleFullScreen = $.proxy(function() {
          if (this.fullScreen.now()) {
            this.fullScreen.cancel();
          } else {
            $iframe[0].contentWindow.location.replace($('#illust_link').attr('href'));
            this.fullScreen.request($iframe[0]);
          }
        }, this);
        $fullScreenButton.on('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          toggleFullScreen();
        });

        var onMessage = function(event) {
          if (event.origin.indexOf('nicoseiga.jp') < 0) return;
          try {
            var data = JSON.parse(event.data);
            if (data.id !== 'MOD_Seiga') { return; }
            if (data.command === 'toggleFullScreen') {
              toggleFullScreen();
            }
          } catch (e) {
            console.log('Exception', e);
            console.trace();
          }
        };

        window.addEventListener('message', onMessage);
        $body.on('keydown.watchItLater', function(e) {
          if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
            return;
          }
          if (e.keyCode === 70) {  // F
            toggleFullScreen();
          }
        });

      },
      initializeOther: function() {
        document.body.classList.add('MOD_Seiga');
      },
      initializeSettingPanel: function() {
        var $menu   = $('<li class="MOD_SeigaSettingMenu"><a href="javascript:;" title="MOD_Seigaの設定変更">MOD_Seiga設定</a></li>');
        var $panel  = $('<div id="MOD_SeigaSettingPanel" />');//.addClass('open');
        var $button = $('<button class="toggleSetting playerBottomButton">設定</botton>');

        $button.on('click', function(e) {
          e.stopPropagation(); e.preventDefault();
          $panel.toggleClass('open');
        });

        var config = this.config;
        $menu.find('a').on('click', function() { $panel.toggleClass('open'); });

        var __tpl__ = (`
          <div class="panelHeader">
          <h1 class="windowTitle">MOD_Seigaの設定</h1>
          <p>設定はリロード後に反映されます</p>
          <button class="close" title="閉じる">×</button>
          </div>
          <div class="panelInner">
            <!--<div class="item" data-setting-name="topUserInfo" data-menu-type="radio">
              <h3 class="itemTitle">投稿者情報を右上に移動 </h3>
              <label><input type="radio" value="true" > する</label>
              <label><input type="radio" value="false"> しない</label>
            </div>-->

            <div class="item" data-setting-name="noTrim" data-menu-type="radio">
              <h3 class="itemTitle">サムネイルの左右カットをやめる </h3>
              <label><input type="radio" value="true" >やめる</label>
              <label><input type="radio" value="false">やめない</label>
            </div>

            <div class="item" data-setting-name="hidePageTopButton" data-menu-type="radio">
              <h3 class="itemTitle">ウィンドウ幅が狭いときはページトップに戻るボタンを隠す</h3>
              <label><input type="radio" value="true" >隠す</label>
              <label><input type="radio" value="false">隠さない</label>
            </div>

            <div class="item" data-setting-name="hideBottomUserInfo" data-menu-type="radio">
              <h3 class="itemTitle">ページ下側の投稿者情報を隠す</h3>
              <small>右上だけでいい場合など</small><br>
              <label><input type="radio" value="true" >隠す</label>
              <label><input type="radio" value="false">隠さない</label>
            </div>

            <div class="item" data-setting-name="clipPosition" data-menu-type="radio">
              <h3 class="itemTitle">クリップ登録メニューの位置(旧verのみ)</h3>
              <label><input type="radio" value="&quot;top&quot;" >上</label>
              <label><input type="radio" value="&quot;bottom&quot;">下</label>
            </div>
            <!--
            <div class="item" data-setting-name="tagPosition" data-menu-type="radio">
              <h3 class="itemTitle">タグの位置(旧verのみ) </h3>
              <label><input type="radio" value="&quot;description-bottom&quot;">説明文の下</label>
              <label><input type="radio" value="&quot;description-right&quot;">説明文の右</label>
              <label><input type="radio" value="&quot;top&quot;">画像の上</label>
              <label><input type="radio" value="&quot;default&quot;">画像の下(標準)</label>
            </div>
            -->

            <div class="expert">
              <h2>上級者向け設定</h2>
            </div>
            <div class="item" data-setting-name="applyCss" data-menu-type="radio">
              <h3 class="itemTitle">MOD_Seiga標準のCSSを使用する</h3>
              <small>他のuserstyleを使用する場合は「しない」を選択してください</small><br>
              <label><input type="radio" value="true" > する</label>
              <label><input type="radio" value="false"> しない</label>
            </div>
          </div>
        `).trim();
        $panel.html(__tpl__);
        $panel.find('.item').on('click', function(e) {
          var $this = $(this);
          var settingName = $this.attr('data-setting-name');
          var value = JSON.parse($this.find('input:checked').val());
          console.log('seting-name', settingName, 'value', value);
          config.set(settingName, value);
        }).each(function(e) {
          var $this = $(this);
          var settingName = $this.attr('data-setting-name');
          var value = config.get(settingName);
          $this.addClass(settingName);
          $this.find('input').attr('name', settingName).val([JSON.stringify(value)]);
        });
        $panel.find('.close').click(function() {
          $panel.removeClass('open');
        });


        $('#siteHeaderRightMenuFix').after($menu);
        $('body').append($panel);
      },
      initializeFullscreenImage: function() {
        var $body = $('body'), $container = $('.illust_view_big'), $img = $container.find('img'), scale = 1;
        var $fullScreenButton = $([
          '<div class="toggleFullScreen show">',
           '<button title="フルスクリーン表示に切り換えます">',
              '<span>フルスクリーン</span>',
            '</button>',
          '</div>',
          ''].join(''));
        var $window = $(window);
        $('.controll').addClass('control');

        var clearCss = function() {
          $body.removeClass('mod_contain mod_cover mod_noScale');
          $container.css({width: '', height: ''});
          $img.css({'transform': '', '-webkit-transform': '', top: '', left: ''});
        };

        // ウィンドウの内枠フィット (画面におさまる範囲で最大化)
        var contain = function() {
          clearCss();
          $body.addClass('mod_contain');
          scale = Math.min(
            $window.innerWidth() / $img.outerWidth(),
            $window.innerHeight() / $img.outerHeight()
          );
          scale = Math.min(scale, 10);
          $img.css({
            'transform':         'scale(' + scale + ')',
            '-webkit-transform': 'scale(' + scale + ')',
            'left':  ($window.innerWidth()  - $img.outerWidth()  * scale) / 2 + 'px',
            'top':   ($window.innerHeight() - $img.outerHeight() * scale) / 2 + 'px'
          });
          $container.width($window.innerWidth());
          $container.height($window.innerHeight());
//          $container.css('background-image', 'url("' + $img.attr('src') + '")');
        };

        // ウィンドウの外枠フィット
        var cover = function() {
          clearCss();
          $body.addClass('mod_cover').css('overflow', 'scroll');
          scale = Math.max(
            $window.innerWidth() / $img.outerWidth(),
            $window.innerHeight() / $img.outerHeight()
          );
          scale = Math.min(scale, 10);
          $img.css({
            'transform':         'scale(' + scale + ')',
            '-webkit-transform': 'scale(' + scale + ')',
          });
          // ウィンドウサイズの計算にスクロールバーの幅を含めるための措置 おもにwindows用
          $body.css('overflow', '');
        };

        // 原寸大表示
        var noScale = function() {
          clearCss();
          $body.addClass('mod_noScale');
          scale = 1;
          $container.css('background-image', '');
        };

        // クリックごとに表示を切り替える処理
        var onClick = function(e) {
          if (e.button > 0) { return; }
          // TODO: クリックした位置が中心になるようにスクロール
          if ($body.hasClass('mod_noScale')) {
            contain();
          } else
          if ($body.hasClass('mod_contain')) {
            cover();
          } else {
            noScale();
          }
        };
        // ウィンドウがリサイズされた時などの再計算用
        var update = function() {
          if ($body.hasClass('mod_contain')) {
            contain();
          } else
          if ($body.hasClass('mod_cover')) {
            cover();
          }
        };

        // モニターいっぱい表示を切り換える
        var toggleFullScreen = $.proxy(function() {
          if (window.name === 'fullScreenRequestFrame') {
            parent.postMessage(JSON.stringify({
              id: 'MOD_Seiga',
              command: 'toggleFullScreen'
            }),
            'http://seiga.nicovideo.jp');
            return;
          }

          if (this.fullScreen.now()) {
            this.fullScreen.cancel(document.documentElement);
          } else {
            this.fullScreen.request(document.documentElement);
          }
        }, this);

        window.setTimeout($.proxy(function() {
          $fullScreenButton.removeClass('show');
        }, this), 2000);

        // /seiga/imXXXXXXからiframeで呼ばれてる時の処理
        if (window.name === 'fullScreenRequestFrame') {
          $('img[src$="btn_close.png"]')
            .addClass('closeButton')
            .off()
            .on('click', function() {
              toggleFullScreen();
            });
          $('body').addClass('fullScreenFrame');
        }

        $fullScreenButton.on('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          toggleFullScreen();
        });
        $body.append($fullScreenButton);


        // マウスを動かさない時はカーソルを消す
        var mousemoveStopTimer = null;
        var showMouseNsec = function() {
          $body.addClass('mouseMoving');
          if (mousemoveStopTimer) {
            window.clearTimeout(mousemoveStopTimer);
            mousemoveStopTimer = null;
          }
          mousemoveStopTimer = window.setTimeout(function() {
            $body.removeClass('mouseMoving');
            mousemoveStopTimer = null;
          }, 1000);
        };
        $body
          .on('mousemove.MOD_Seiga', showMouseNsec)
          .on('mousedown.MOD_Seiga', showMouseNsec);


        contain();
        $img.on('click', onClick);
        $window.on('resize', update);
        var onImageLoad = $.proxy(function() {
          this.initializePrintCss($img.clone());
          contain();
          $img.off('load.MOD_Seiga');
        }, this);

        if ($img[0] && $img[0].complete) {
          onImageLoad();
        } else {
          $img.on('load.MOD_Seiga', onImageLoad);
        }


        // おもにwindows等、縦ホイールしかない環境で横スクロールしやすくする
        // Firefoxでうまくいかない
        var hasWheelDeltaX = false;
        $(document).on('mousewheel', function(e) {
          var delta = 0;

          if ($body.hasClass('mod_contain')) { return; }

          if (document.body.scrollHeight > window.innerHeight) { return; }

          if (hasWheelDeltaX) { return; }

          if (!e.originalEvent) { return; }
          var oe = e.originalEvent;

          if (typeof oe.wheelDelta === 'number') {
            if (typeof oe.wheelDeltaX === 'number' && oe.wheelDeltaX !== 0) {
              hasWheelDeltaX = true;
              return; // ホイールで横スクロールできる環境だとかえって邪魔なのでなにもしない
            }

            delta = e.originalEvent.wheelDelta / Math.abs(e.originalEvent.wheelDelta);
          }
          if (delta === 0) return;

          e.preventDefault();
          e.stopPropagation();

          var scrollLeft = $body.scrollLeft() - (delta * $window.innerWidth() / 10);
          $body.scrollLeft(Math.max(scrollLeft, 0));
        });
      },
      initializeSaveScreenshot: function() {
        $('body').on('keydown', (e) => {
          if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
            return;
          }
          if (e.keyCode === 83) {  // S
            window.MOD_Seiga.saveScreenshot();
          }
        });
      },
      initializeSaveScreenshotRequest: function() {
        const $body = $('body');
        const $iframe = $('<iframe allowfullscreen="on" class="saveScreenshotFrame" name="saveScreenshotFrame"></iframe>');
        $iframe[0].srcdoc = '';
        $body.append($iframe);

        const $saveButton = $([
          '<button class="saveScreenshot btn normal" title="画像を保存(Sキー)">',
            '<span>画像を保存</span>',
          '</button>',
          ''].join(''));
        $('.illust_main .illust_wrapper .inner .thum_large:first').append($saveButton);

        $saveButton.on('click', (e) => {
          e.preventDefault();
          e.stopPropagation();
          $iframe[0].contentWindow.location.replace($('#illust_link').attr('href'));
          //window.open(
          //  $('#illust_link').attr('href'),
          //  'saveScreenshotFrame',
          //  'width=400, height=300, personalbar=0, toolbar=0, scrollbars=1, sizable=1');
        });
        $(window).on('beforeunload', () => {
          $iframe.remove();
          $iframe[0].srcdoc = '';
        });

        $('body').on('keydown', (e) => {
          if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
            return;
          }
          if (e.keyCode === 83) {  // S
            $saveButton.click();
          }
        });
      },
      initializePrintCss: function($img) {
        this.initializePrintCss = function() {};
        var self = this;

        $img.css({
          width: 'auto', height: 'auto',
          transform: '', left: '100%', top: '100%', opacity: 0, position: 'fixed'
        });

        var $body = $('body');
        $body.append($img);

        window.setTimeout(function() {
          var width = $img.outerWidth(), height = $img.outerHeight();
          $img.remove();

          // TODO: 用紙サイズ変更
          var paperMarginV = 0.1; // 19; // 紙送りマージン?
          var imageRatio = height / Math.max(width, 1);
          var paperRatio     = (A4_HEIGHT - paperMarginV) / A4_WIDTH;
          var landscapeRatio = (A4_WIDTH  - paperMarginV) / A4_HEIGHT;
          var isLandscape =
            Math.abs(paperRatio - imageRatio) > Math.abs(landscapeRatio - imageRatio);

          paperRatio = isLandscape ? landscapeRatio : paperRatio;

          var isFitV = paperRatio < imageRatio;
          var paperWidth  = isLandscape ? A4_HEIGHT : A4_WIDTH;
          var paperHeight = (isLandscape ? A4_WIDTH : A4_HEIGHT) - paperMarginV;
          var marginLeft = 0, marginTop = 0, imageWidth = paperWidth, imageHeight = paperHeight;

          if (isFitV) { // タテ合わせ
            imageWidth = paperHeight / imageRatio;
            marginLeft = (paperWidth - imageWidth) / 2;
          } else { // ヨコ合わせ
            imageHeight = paperWidth * imageRatio;
            marginTop = (paperHeight - imageHeight) / 2;
          }
          var pageSize = isLandscape ? 'A4 lanscape' : 'A4';
          var css = [
              '@page { margin: 0; size: ', pageSize, '; }\n\n ',
              '@media print {\n',
                '\t.MOD_Seiga_FullView .illust_view_big img {\n  ',
                  '\t\tmargin-left: ', marginLeft, 'mm; ',
                  '\t\tmargin-top: ', marginTop, 'mm; ',
                  '\t\twidth:',  imageWidth,  'mm !important; ',
                  '\t\theight:', imageHeight, 'mm !important;',
                '\t\n}\n',
              '}',
            ].join('');

          //console.log('paper?', paperWidth, paperHeight, imageWidth, imageHeight);
          //console.log('ratio?', width, height, isLandscape, paperRatio, imageRatio);
          console.log('print css\n', css);

          self._printCss = self.addStyle(css, 'MOD_Seiga_print');
          $body
            .toggleClass('landscape', isLandscape)
            .toggleClass('fitV', isFitV);
        }, 100);
      }
    };

    const toSafeName = function(text) {
      text = text.trim()
        .replace(/</g, '<')
        .replace(/>/g, '>')
        .replace(/\?/g, '?')
        .replace(/:/g, ':')
        .replace(/\|/g, '|')
        .replace(/\//g, '/')
        .replace(/\\/g, '¥')
        .replace(/"/g, '”')
        .replace(/\./g, '.')
        ;
      return text;
    };

    window.MOD_Seiga.saveScreenshot = function() {
      const div = document.querySelector('.illust_view_big');
      const img = div.querySelector('.illust_view_big img');
      window.console.info('img', img, div, document.querySelectorAll('img'));
      const illustId =
        (div.getAttribute('data-watch_url') || '').split('/')[4];
      window.console.info('illustId', illustId);
      const title = toSafeName(document.title);
      const fileName = `${illustId}-${title}`;
      window.console.info('fileName', fileName);

      const link = document.createElement('a');
      link.download = fileName;
      link.href = img.src;
      link.textContent = 'save image';
      document.body.appendChild(link);
      link.click();
       link.remove();
      window.setTimeout(() => {
        if (window.name === 'saveScreenshotFrame') {
          location.replace('/favicon.ico');
        }
      }, 0);
    };

    window.MOD_Seiga.initialize();
    if (window.name === 'saveScreenshotFrame') {
      window.console.info('saveScreenshot', window.name);
      window.MOD_Seiga.saveScreenshot();
    }
  });

  var script = document.createElement("script");
  script.id = "MOD_SeigaLoader";
  script.setAttribute("type", "text/javascript");
  script.setAttribute("charset", "UTF-8");
  script.appendChild(document.createTextNode("(" + monkey + ")()"));
  document.body.appendChild(script);

})();