Youtube/Holotools/Holostats, Display Hololive live streaming on youtube

You can check schedule of recently hololive stream on youtube

// ==UserScript==
// @name         Youtube/Holotools/Holostats, Display Hololive live streaming on youtube
// @name:ja      Youtube/Holotools/Holostats, ホロライブ配信一覧をYoutubeで表示
// @namespace    http://tampermonkey.net/
// @version      0.6.X
// @description  You can check schedule of recently hololive stream on youtube
// @description:ja ホロライブの直近のスケジュールをYoutubeで確認できます
// @author       You
// @icon         https://holo.poi.cat/youtube-stream/assets/icons/icon-72x72.png
// @match        https://www.youtube.com*
// @match        https://www.youtube.com/*
// @match        https://hololive.jetri.co/*
// @match        https://archive.net/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @noframes
// @unwrap
// ==/UserScript==

(function() {
    'use strict';
    const VERSION = "0.4.3", APPNAME = "DHLSOY";
    const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 mins
    const HOLOSTATS_INTERVAL = 15 * 60 * 1000; // 15 mins
    const FREECHAT_SCHEDULE = 7 * 24 * 60 * 60 * 1000; // 7 days
    const ARCHIVE_ENDHOURS = 36 * 60 * 60 * 1000; // 36 hours
    const ERROR_TIMEOUT = 3000; // 3 secs
    const holotoolsHideChannels = "holotoolsHideChannels";

    const storage = {
        schedule: {
            get updated() { return catchParseError(() => parseInt(localStorage.ht_s_updated), 0); },
            set updated(value) { localStorage.ht_s_updated = new String(value); },
            get cache() { return catchParseError(() => JSON.parse(localStorage.ht_s_cache), null); },
            set cache(value) { localStorage.ht_s_cache = JSON.stringify(value); },
            clear: () => {
                localStorage.ht_s_updated = 0;
                delete localStorage.ht_s_cache;
            }
        },
        archive: {
            get updated() { return catchParseError(() => parseInt(localStorage.ht_a_updated), 0); },
            set updated(value) { localStorage.ht_a_updated = new String(value); },
            get cache() { return catchParseError(() => JSON.parse(localStorage.ht_a_cache), null); },
            set cache(value) { localStorage.ht_a_cache = JSON.stringify(value); },
            clear: () => {
                localStorage.ht_a_updated = 0;
                delete localStorage.ht_a_cache;
            }
        },
        get mute() { return catchParseError(() => JSON.parse(GM_getValue(holotoolsHideChannels) || "[]") || [], []); },
        set mute(value) { GM_setValue(holotoolsHideChannels, value); },
        holostats: {
            favoriteLoaded_: false,
            favorite_: {
                hololive: true,
                sora: true,
                roboco: true,
                miko: true,
                suisei: true,
                fubuki: true,
                matsuri: true,
                haato: true,
                aki: true,
                mel: true,
                choco: true,
                choco_alt: true,
                shion: true,
                aqua: true,
                subaru: true,
                ayame: true,
                pekora: true,
                rushia: true,
                flare: true,
                marine: true,
                noel: true,
                kanata: true,
                coco: true,
                watame: true,
                towa: true,
                himemoriluna: true,
                lamy: true,
                nene: true,
                botan: true,
                polka: true,
                chloe: true,
                iroha: true,
                koyori: true,
                laplus: true,
                lui: true,
                mio: true,
                okayu: true,
                korone: true,
                azki: true,
                risu: true,
                moona: true,
                iofi: true,
                ollie: true,
                melfissa: true,
                reine: true,
                amelia: true,
                calliope: true,
                gura: true,
                inanis: true,
                kiara: true,
                irys: true,
                sana: true,
                ceres: true,
                ouro: true,
                mumei: true,
                hakos: true,
                shien: true,
                oga: true,
                astel: true,
                temma: true,
                roberu: true,
                miyabi: true,
                izuru: true,
                aruran: true,
                rikka: true,
            },
            get favorite() {
                if (this.favoriteLoaded_) {
                    return this.favorite_;
                } else {
                    this.favoriteLoaded_ = true;
                    this.favorite_ = catchParseError(() => Object.assign({}, this.favorite_, JSON.parse(localStorage.ht_stats_favorite)), this.favorite_);
                    return this.favorite_;
                }
            },
            set favorite(value) {
                localStorage.ht_stats_favorite = JSON.stringify(value);
            },
            get updated() { return catchParseError(() => parseInt(localStorage.ht_stats_updated), 0); },
            set updated(value) { localStorage.ht_stats_updated = new String(value); },
            get cache() { return catchParseError(() => JSON.parse(localStorage.ht_stats_cache), null); },
            set cache(value) { localStorage.ht_stats_cache = JSON.stringify(value); },
        }
    }

    // holotools
    if (location.host == "hololive.jetri.co") {
        storage.mute = localStorage.hideChannels;
        return;
    }

    var isRefreshing = false,
        scheduleUpdated = 0,
        archiveUpdated = 0,
        hideChannels = storage.mute;
    // console.log(hideChannels);

    initilize();

    function catchParseError(func, defaultValue) {
        try {
            return func();
        } catch {
            return defaultValue;
        }
    }

    function initilize() {
        if (document.enabledHololiveSchedule) return;
        document.enabledHololiveSchedule = true;

        buildScheduleBase();
    }

    function onScrollContainer(ev) {
        // console.log(ev);

        ev.preventDefault();
        ev.stopPropagation();
        ev.stopImmediatePropagation();

        // console.log("on wheel", ev, document.querySelector("#hololive-schedule").clientWidth, document.documentElement.clientWidth, container.style.left);

        const contents = document.querySelector("#hololive-schedule #contents"),
              lp = parseFloat(contents.style.left) || 0,
              dw = document.documentElement.clientWidth,
              cw = contents.clientWidth;

        // console.log (contents, lp, dw, cw);

        if (ev.deltaY > 0) {
            contents.style.left = Math.max(lp - dw / 5, - cw + dw) + "px";
        } else if (ev.deltaY < 0) {
            contents.style.left = Math.min(lp + dw / 5, 0) + "px";
        } else {
            contents.style.left = - cw + dw;
        }

        return false;
    }

    function buildScheduleBase() {
        const style = document.createElement("style");
        style.id = "hololive-schedule-style";
        style.innerHTML = `
#hololive-schedule {
    --background-color: #ffffff;
    --icon-hover-background-color: #d9d9d9;
    --icon-fill: #212121;
}
[dark=true] #hololive-schedule {
    --background-color: #212121;
    --icon-hover-background-color: #4c4c4c;
    --icon-fill: #ffffff;
}

#hololive-schedule {
    position: fixed;
    bottom: 0;
    left: 0;
    min-width: 100%;
    opacity: 0;
    scrollbar-width: none;
    box-sizing: border-box;
    z-index: 2028; /* ytd-mini-guid-renderer is 2027 */
    transition: opacity .1s linear;
}
#hololive-schedule:hover {
    opacity: 1;
}
#hololive-schedule {
    pointer-events: none;
}
#hololive-schedule.visible {
    pointer-events: unset;
}
#hololive-schedule #contents {
    position: absolute;
    bottom: 0;
    left: 0;
    min-width: 100%;
    max-height: 60px;
    padding: 8px 32px 8px 0;
    white-space: nowrap;
    scrollbar-width: none;
    box-sizing: border-box;
    overflow-y: hidden;
    overflow-x: scroll;
    background-color: var(--background-color);
    transition: left .2s ease, max-height .5s linear;
}
#hololive-schedule:hover #contents {
    max-height: 200px;
    scrollbar-width: none;
}
#hololive-schedule #contents::-webkit-scrollbar {
    display: none;
}
.hololive-stream {
    display: inline-block;
    position: relative;
    border: solid 3px;
    border-raduis: 3px;
    font-size: 10px;
    margin: 0 0 0 8px;
}
.hololive-stream.hide-channel {
    display: none;
}
.hololive-stream .thumbnail {
    vertical-align: middle;
    width: 128px;
    height: 72px;
    background-size: contain;
    background-repeat: 1;
    background-position: center center;
    opacity: 0;
    transition: opacity 0.1s ease;
}
.hololive-stream .thumbnail.loaded {
    opacity: 1;
}
.hololive-stream .title-wrapper {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    overflow: hidden;
    color: #202020;
    background: #fffa;
    z-index: 1;
}
.hololive-stream:hover .title {
    animation: textAnimation 4s linear infinite;
}
@keyframes textAnimation {
    0% { margin-left: 0; }
    100% { margin-left: -350%; }
}
.hololive-stream .date {
    position: absolute;
    padding: 1px 3px;
    color: #202020;
    background: #fffd;
    font-weight: bold;
    z-index: 1;
}
.hololive-stream .photo {
    position: absolute;
    right: -8px;
    top: -8px;
    width: 32px;
    height: 32px;
    border: solid 2px #fff;
    border-radius: 18px;
    z-index: 1;
    background-color: var(--background-color);
    background-size: cover;
    opacity: 0;
    transition: opacity 0.1s ease;
}
.hololive-stream .photo.loaded {
    opacity: 1;
}
.hololive-stream.live,
.hololive-stream.live .photo {
    border-color: crimson;
}
.hololive-stream.upcoming,
.hololive-stream.upcoming .photo {
    border-color: deepskyblue;
}
.hololive-stream.ended,
.hololive-stream.ended .photo {
    border-color: gray;
}
.hololive-stream.dummy {
    pointer-events: none;
}

#hololive-schedule #tools {
    display: block;
    position: absolute;
    right: 0;
    bottom: 0;
    width: 32px;
    height: 97px;
    z-index: 2;
    transition: opacity .2s linear, width .1s linear;
}
#hololive-schedule:hover #tools {
    max-height: 200px;
    opacity: 1;
}
#hololive-schedule #tools:hover {
    width: 60px;
}
#hololive-schedule #tools a {
    display: block;
    position: relative;
    cursor: pointer;
    vertical-align: middle;
    text-align: center;
    height: 100%;
    background: linear-gradient(to right, #fff0 0%, var(--background-color) 30%, var(--background-color) 100%);
    transition: background .1s linear, padding .1s linear;
}
#hololive-schedule #tools a:hover {
    background: linear-gradient(to right, #fff0 0%, var(--icon-hover-background-color) 30%, var(--icon-hover-background-color) 100%);
}
#hololive-schedule #tools a svg {
    display: inlnie-block;
    height: 100%;
}
#hololive-schedule #tools a svg .shape {
    fill: var(--icon-fill);
    transition: fill .1s linear;
}
#hololive-schedule #tools a:hover svg .shape {
}
#hololive-schedule #tools #powered {
    display: block;
    position: absolute;
    width: 140px;
    top: -24px;
    right: 0;
    height: 24px;
    line-height: 24px;
    background-color: var(--background-color);
    color: var(--icon-fill);
    border-radius: 5px 0 0 0;
}

#hololive-schedule #contents .contents.hidden {
    display: none;
    width: 0;
}
#hololive-schedule #thumbnail-preview {
    position: fixed;
    width: 512px;
    height: 288px;
    bottom: 144px;
    background-size: cover;
    background-position: center;
    border-radius: 4px;
    z-index: 2031;
    pointer-events: none;
    box-shadow: #000 0px 0px 12px;
}
#hololive-schedule #thumbnail-preview.hidden {
    opacity: 0;
}
#hololive-schedule #title-preview {
    position: fixed;
    bottom: 100px;
    font-size: 20px;
    padding: 6px 12px;
    border-radius: 4px;
    color: #303030;
    background: #efefef;
    z-index: 2031;
    pointer-events: none;
    transition: opacity 0.1s ease;
}
#hololive-schedule #title-preview.hidden {
    opacity: 0;
}
#archive-home {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 2031;
    background-color: #000a;
    backdrop-filter: blur(3px);
    transition: opacity 0.25s ease;
}
#archive-home::-webkit-scrollbar {
    display: none;
}
#archive-home .content {
    width: 100%;
    height: 100%;
    max-width: calc(100% - 64px);
    max-height: calc(100% - 48px);
    margin: 24px 32px;
    overflow-x: hidden;
    overflow-y: scroll;
    scrollbar-width: none;
}
#archive-home[order-name] .content {
    display: grid;
    grid-auto-flow: row dense;
}
#archive-home .content::-webkit-scrollbar {
    display: none;
}
#archive-home.hidden {
    opacity: 0;
    pointer-events: none;
}
#archive-home .hidden {
    opacity: 0;
}
#archive-home[order-name] .category {
    display: inline-block;
    vertical-align: top;
}
/* thumbnail */
#archive-home .thumbnail {
    display: inline-block;
    position: relative;
    height: 96px;
    width: 168px;
    background-color: #fff3;
    background-size: cover;
    background-position: center;
    border: 2px solid gray;
    border-color: var(--image-color, gray);
    margin-left: 5px;
    margin-top: 4px;
    box-sizing: border-box;
    transition: opacity 0.1s ease;
}
#archive-home .category .header {
    color: #fff;
    font-size: 18px;
    margin: 16px 32px;
    content: attr(--name);
}
#archive-home[order-name] .category .header {
    display: none;
}
#archive-home .thumbnail.hidden {
    display: inline-block;
    opacity: 0;
}
#archive-home .thumbnail.preview {
    opacity: 1;
}
/* blur */
#archive-home .thumbnail::after {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
    content: "";
    background: linear-gradient(#000a, #000a, #0008, #0000);
    z-index: 3;
    opacity: 0;
    backdrop-filter: blur(2px);
    transition: opacity 0.1s ease;
    pointer-events: none;
}
#archive-home .thumbnail:hover::after {
    opacity: 1;
}
/* user icon */
#archive-home .thumbnail::before {
    content: "";
    position: absolute;
    right: -12px;
    top: -14px;
    background-position: center;
    background-size: cover;
    background-image: var(--icon);
    border: solid 2px gray;
    border-color: var(--image-color);
    border-radius: 36px;
    height: 48px;
    width: 48px;
    z-index: 5;
    transition: transform 0.1s ease,
        top 0.1s ease,right 0.1s ease,
        width 0.1s ease, height 0.1s ease,
        border-width 0.1s ease, border-radius 0.1s ease;
    box-sizing: border-box;
    pointer-events: none;
}
#archive-home .thumbnail:hover::before {
    top: 0;
    right: 0;
    width: 100%;
    height: 100%;
    border-width: 0;
    border-radius: 0;
    pointer-events: none;
    z-index: 2;
}
/* title */
#archive-home .thumbnail span.title {
    display: block;
    position: absolute;
    font-size: 13px;
    color: #fff;
    max-height: 6em;
    top: 6px;
    right: 6px;
    bottom: 6px;
    left: 6px;
    z-index: 4;
    opacity: 0;
    overflow: hidden;
    transition: opacity 0.1s ease;
}
#archive-home .thumbnail:hover span.title {
    opacity: 1;
}
#archive-home .thumbnail span.length {
    display: block;
    position: absolute;
    right: 8px;
    bottom: 8px;
    padding: 1px 3px;
    background: #0008;
    font-size: 12px;
    color: #fff;
    transition: opacity 0.1s linear;
}
#archive-home .thumbnail:hover span.length {
    opacity: 0;
}
#archive-home .error {
    color: #fff;
    text-align: center;
    vertical-align: middle;
}
#archive-home .menu {
    position: absolute;
    top: 8px;
    right: 8px;
    color: #fff;
    font-size: 12px;
}
#archive-home .menu a {
    color: #fff;
    margin-left: 16px;
    text-decoration: none;
    cursor: pointer;
}
#archive-home .sort::after {
    display: inline;
    content: attr(data-sort1);
}
#archive-home[order-name] .sort::after {
    content: attr(data-sort2);
}
#archive-home .loading {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 100px;
    z-index: 100000;
}
#archive-home .loading .fragment {
    position: relative;
    height: 8px;
    background: #fffc;
    border-radius: 4px;
    margin-top: 3px;
}
#archive-home .loading:not(.hidden) .fragment {
    animation: archiveLoadingFragment infinite 3s 0s both;
}
#archive-home .loading .fragment:nth-child(0) {
    animation-delay: 0s !important;
}
#archive-home .loading .fragment:nth-child(1) {
    animation-delay: -0.5s !important;
}
#archive-home .loading .fragment:nth-child(2) {
    animation-delay: -1s !important;
}
#archive-home .loading .fragment:nth-child(3) {
    animation-delay: -1.5s !important;
}
#archive-home .loading .fragment:nth-child(4) {
    animation-delay: -2s !important;
}
#archive-home .loading .fragment:nth-child(5) {
    animation-delay: -2.5s !important;
}
@keyframes archiveLoadingFragment {
    0% {
        left: 0;
        width: 0%;
    }
    50% {
        left: 0;
        width: 100%;
    }
    100% {
        left: 100%;
        width: 0%;
    }
}
#archive-home .face-list {
    display: grid;
    grid-template-columns: repeat(40, max-content);
    grid-template-rows: repeat(2, max-content);
}
#archive-home[order-name] .face-list {
    grid-column: span 2;
}
#archive-home .face-list .face {
    display: inline-grid;
    position: relative;
    width: 32px;
    height: 40px;
    background-size: cover;
    background-position: center;
    background-image: var(--icon);
    border: 2px solid gray;
    border-color: var(--image-color);
    border-radius: 32px;
    grid-column: var(--column);
    grid-row: var(--row);
    box-sizing: border-box;
    cursor: pointer;
}
#archive-home .face-list .face.mute {
    filter: grayscale();
    border-color: gray;
    opacity: 0.5;
}
#archive-home .face-list .face::before {
    display: block;
    position: absolute;
    content: var(--name);
    margin-top: 5px;
    padding: 2px 5px;
    top: 100%;
    background: #fff;
    font-size: 14px;
    border-radius: 3px;
    pointer-events: none;
    opacity: 0;
    white-space: nowrap;
    z-index: 20;
    transition: opacity 0.1s linear;
}
#archive-home .face-list .face:hover::before {
    opacity: 1;
}
#archive-home .next {
    border: solid 1px #fff;
    padding: 8px;
    margin: 0 auto 80px auto;
    color: #fff;
    width: 50%;
    max-width: 500px;
    cursor: pointer;
    position: relative;
    text-align: center;
    font-size: 13px;
    background-color: #0000;
    transition: background-color 0.15s ease;
}
#archive-home .next::before {
    content: "";
    height: 16px;
}
#archive-home .next::after {
    content: "";
    height: 100px;
}
#archive-home[order-name] .next {
    grid-column: span 2;
}
#archive-home .next:hover {
    background-color: #fffa;
}
#archive-home .next.hidden {
    pointer-events: none;
    display: block;
}

#archive-home {
    --pos-jp-0: 1;
    --pos-jp-1: 9;
    --pos-jp-2: 14;
    --pos-jp-gamers: 6;
    --pos-jp-3: 20;
    --pos-jp-4: 1;
    --pos-jp-5: 6;
    --pos-jp-6: 10;
    --pos-en-1: 15;
    --pos-en-2: 20;
    --pos-id-1: 1;
    --pos-id-2: 4;
    --pos-stars-1: 7;
    --pos-stars-2: 11;
    --pos-stars-3: 14;

    --row-jp-0: 1;
    --row-jp-1: 1;
    --row-jp-2: 1;
    --row-jp-gamers: 1;
    --row-jp-3: 1;
    --row-jp-4: 2;
    --row-jp-5: 2;
    --row-jp-6: 2;
    --row-en-1: 2;
    --row-en-2: 2;
    --row-id-1: 3;
    --row-id-2: 3;
    --row-stars-1: 3;
    --row-stars-2: 3;
    --row-stars-3: 3;
}
/* JP */
#archive-home [data-name="sora"] {
    --name: "ときのそら";
    --image-color: cornflowerblue;
    --icon: url('https://holo.poi.cat/assets/thumbnail/sora.jpg');
    counter-increment: gen-0;
    --column: calc(var(--pos-jp-0) + 0);
    --row: var(--row-jp-0);
}
#archive-home.en [data-name="sora"] {
    --name: "Tokino Sora";
}
#archive-home [data-name="roboco"] {
    counter-increment: gen-0;
    --name: "ロボ子さん";
    --image-color: red;
    --icon: url('https://holo.poi.cat/assets/thumbnail/roboco.jpg');
    --column: calc(var(--pos-jp-0) + 1);
    --row: var(--row-jp-0);
}
#archive-home.en [data-name="roboco"] {
    --name: "Roboco san";
}
#archive-home [data-name="miko"] {
    --name: "さくらみこ";
    --image-color: pink;
    --icon: url('https://holo.poi.cat/assets/thumbnail/miko.jpg');
    --column: calc(var(--pos-jp-0) + 2);
    --row: var(--row-jp-0);
}
#archive-home.en [data-name="miko"] {
    --name: "Sakura Miko";
}
#archive-home [data-name="suisei"] {
    counter-increment: gen-0;
    --name: "星街すいせい";
    --image-color: navy;
    --icon: url('https://holo.poi.cat/assets/thumbnail/suisei.jpg');
    --column: calc(var(--pos-jp-0) + 3);
    --row: var(--row-jp-0);
}
#archive-home.en [data-name="suisei"] {
    --name: "Hoshimachi Suisei";
}
#archive-home [data-name="azki"] {
    counter-increment: gen-0;
    --name: "Azki";
    --image-color: deeppink;
    --icon: url('https://holo.poi.cat/assets/thumbnail/azki.jpg');
    --column: calc(var(--pos-jp-0) + 4);
    --row: var(--row-jp-0);
}
#archive-home.en [data-name="azki"] {
    --name: "Azuki";
}

#archive-home [data-name="mio"] {
    counter-increment: gen-gamers;
    --name: "大神ミオ";
    --image-color: maroon !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/mio.jpg');
    --column: calc(var(--pos-jp-gamers) + 0);
    --row: var(--row-jp-gamers);
}
#archive-home.en [data-name="mio"] {
    --name: "Ookami Mio";
}
#archive-home [data-name="okayu"] {
    counter-increment: gen-gamers;
    --name: "猫又おかゆ";
    --image-color: violet;
    --icon: url('https://holo.poi.cat/assets/thumbnail/okayu.jpg');
    --column: calc(var(--pos-jp-gamers) + 1);
    --row: var(--row-jp-gamers);
}
#archive-home.en [data-name="okayu"] {
    --name: "Nekomata Okayu";
}
#archive-home [data-name="korone"] {
    counter-increment: gen-gamers;
    --name: "戌神ころね";
    --image-color: sienna !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/korone.jpg');
    --column: calc(var(--pos-jp-gamers) + 2);
    --row: var(--row-jp-gamers);
}
#archive-home.en [data-name="korone"] {
    --name: "Inugami Korone";
}

#archive-home [data-name="fubuki"] {
    counter-increment: gen-1;
    --name: "白上フブキ";
    --image-color: whitesmoke !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/fubuki.jpg');
    --column: calc(var(--pos-jp-1) + 0);
    --row: var(--row-jp-1);
}
#archive-home.en [data-name="fubuki"] {
    --name: "Shirakami Fubuki";
}
#archive-home [data-name="matsuri"] {
    counter-increment: gen-1;
    --name: "夏色まつり";
    --image-color: orange !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/matsuri.jpg');
    --column: calc(var(--pos-jp-1) + 1);
    --row: var(--row-jp-1);
}
#archive-home.en [data-name="matsuri"] {
    --name: "Natsuiro Matsuri";
}
#archive-home [data-name="haato"] {
    counter-increment: gen-1;
    --name: "赤井はあと";
    --image-color: deeppink !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/haato.jpg');
    --column: calc(var(--pos-jp-1) + 2);
    --row: var(--row-jp-1);
}
#archive-home.en [data-name="haato"] {
    --name: "Akai Haato";
}
#archive-home [data-name="aki"] {
    counter-increment: gen-1;
    --name: "アキ・ローゼンタール";
    --image-color: yellow !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/aki.jpg');
    --column: calc(var(--pos-jp-1) + 3);
    --row: var(--row-jp-1);
}
#archive-home.en [data-name="aki"] {
    --name: "Aki Rosenthal";
}
#archive-home [data-name="mel"] {
    counter-increment: gen-1;
    --name: "夜空メル";
    --image-color: gold !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/mel.jpg');
    --column: calc(var(--pos-jp-1) + 4);
    --row: var(--row-jp-1);
}
#archive-home.en [data-name="mel"] {
    --name: "Yozora Mel";
}

#archive-home [data-name="choco"] {
    counter-increment: gen-2;
    --name: "癒月ちょこ";
    --image-color: mediumvioletred !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/choco.jpg');
    --column: calc(var(--pos-jp-2) + 0);
    --row: var(--row-jp-2);
}
#archive-home.en [data-name="choco"] {
    --name: "Yuzuki Choco";
}
#archive-home [data-name="choco_alt"] {
    counter-increment: gen-2;
    --name: "癒月ちょこ サブ";
    --image-color: mediumvioletred !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/choco_alt.jpg');
    --column: calc(var(--pos-jp-2) + 1);
    --row: var(--row-jp-2);
}
#archive-home.en [data-name="choco_alt"] {
    --name: "Yuzuki Choco (alt)";
}
#archive-home [data-name="shion"] {
    counter-increment: gen-2;
    --name: "紫咲シオン";
    --image-color: mediumorchid !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/shion.jpg');
    --column: calc(var(--pos-jp-2) + 2);
    --row: var(--row-jp-2);
}
#archive-home.en [data-name="shion"] {
    --name: "Murasaki Shion";
}
#archive-home [data-name="aqua"] {
    counter-increment: gen-2;
    --name: "湊あくあ";
    --image-color: violet !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/aqua.jpg');
    --column: calc(var(--pos-jp-2) + 3);
    --row: var(--row-jp-2);
}
#archive-home.en [data-name="aqua"] {
    --name: "Minato Aqua";
}
#archive-home [data-name="subaru"] {
    counter-increment: gen-2;
    --name: "大空スバル";
    --image-color: dodgerblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/subaru.jpg');
    --column: calc(var(--pos-jp-2) + 4);
    --row: var(--row-jp-2);
}
#archive-home.en [data-name="subaru"] {
    --name: "Ozora Subaru";
}
#archive-home [data-name="ayame"] {
    counter-increment: gen-2;
    --name: "百鬼あやめ";
    --image-color: darkred !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/ayame.jpg');
    --column: calc(var(--pos-jp-2) + 5);
    --row: var(--row-jp-2);
}
#archive-home.en [data-name="ayame"] {
    --name: "Nakiri Ayame";
}

#archive-home [data-name="pekora"] {
    counter-increment: gen-3;
    --name: "兎田ぺこら";
    --image-color: skyblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/pekora.jpg');
    --column: calc(var(--pos-jp-3) + 0);
    --row: var(--row-jp-3);
}
#archive-home.en [data-name="pekora"] {
    --name: "Usada Pekora";
}
#archive-home [data-name="rushia"] {
    counter-increment: gen-3;
    --name: "潤羽るしあ";
    --image-color: mediumseagreen !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/rushia.jpg');
    --column: calc(var(--pos-jp-3) + 1);
    --row: var(--row-jp-3);
}
#archive-home.en [data-name="rushia"] {
    --name: "Uruha Rushia";
}
#archive-home [data-name="flare"] {
    counter-increment: gen-3;
    --name: "不知火フレア";
    --image-color: tan !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/flare.jpg');
    --column: calc(var(--pos-jp-3) + 2);
    --row: var(--row-jp-3);
}
#archive-home.en [data-name="flare"] {
    --name: "Shiranui Flare";
}
#archive-home [data-name="marine"] {
    counter-increment: gen-3;
    --name: "宝鐘マリン";
    --image-color: mediumvioletred !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/marine.jpg');
    --column: calc(var(--pos-jp-3) + 3);
    --row: var(--row-jp-3);
}
#archive-home.en [data-name="marine"] {
    --name: "Houshou Marine";
}
#archive-home [data-name="noel"] {
    counter-increment: gen-3;
    --name: "白銀ノエル";
    --image-color: silver !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/noel.jpg');
    --column: calc(var(--pos-jp-3) + 4);
    --row: var(--row-jp-3);
}
#archive-home.en [data-name="noel"] {
    --name: "Shirogane Noel";
}
#archive-home [data-name="kanata"] {
    counter-increment: gen-4;
    --name: "天音かなた";
    --image-color: gold !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/kanata.jpg');
    --column: calc(var(--pos-jp-4) + 0);
    --row: var(--row-jp-4);
}
#archive-home.en [data-name="kanata"] {
    --name: "Amane Kanata";
}
#archive-home [data-name="coco"] {
    counter-increment: gen-4;
    --name: "桐生ココ";
    --image-color: darkorange !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/coco.jpg');
    --column: calc(var(--pos-jp-4) + 1);
    --row: var(--row-jp-4);
}
#archive-home.en [data-name="coco"] {
    --name: "Kiryu Coco";
}
#archive-home [data-name="watame"] {
    counter-increment: gen-4;
    --name: "角巻わため";
    --image-color: lemonchiffon !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/watame.jpg');
    --column: calc(var(--pos-jp-4) + 2);
    --row: var(--row-jp-4);
}
#archive-home.en [data-name="watame"] {
    --name: "Tsunomaki Watame";
}
#archive-home [data-name="towa"] {
    counter-increment: gen-4;
    --name: "常闇トワ";
    --image-color: orchid !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/towa.jpg');
    --column: calc(var(--pos-jp-4) + 3);
    --row: var(--row-jp-4);
}
#archive-home.en [data-name="towa"] {
    --name: "Tokoyami Towa";
}
#archive-home [data-name="himemoriluna"] {
    counter-increment: gen-4;
    --name: "姫森ルーナ";
    --image-color: hotpink !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/himemoriluna.jpg');
    --column: calc(var(--pos-jp-4) + 4);
    --row: var(--row-jp-4);
}
#archive-home [data-name="lamy"] {
    counter-increment: gen-5;
    --name: "雪花ラミィ";
    --image-color: lightskyblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/lamy.jpg');
    --column: calc(var(--pos-jp-5) + 0);
    --row: var(--row-jp-5);
}
#archive-home.en [data-name="lamy"] {
    --name: "Yukihana Lamy";
}
#archive-home [data-name="nene"] {
    counter-increment: gen-5;
    --name: "桃鈴ねね";
    --image-color: darksalmon !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/nene.jpg');
    --column: calc(var(--pos-jp-5) + 1);
    --row: var(--row-jp-5);
}
#archive-home.en [data-name="nene"] {
    --name: "Momosuzu Nene";
}
#archive-home [data-name="botan"] {
    counter-increment: gen-5;
    --name: "獅白ぼたん";
    --image-color: dimgray !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/botan.jpg');
    --column: calc(var(--pos-jp-5) + 2);
    --row: var(--row-jp-5);
}
#archive-home.en [data-name="botan"] {
    --name: "Shishiro Botan";
}
#archive-home [data-name="polka"] {
    counter-increment: gen-5;
    --name: "尾丸ポルカ";
    --image-color: red !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/polka.jpg');
    --column: calc(var(--pos-jp-5) + 3);
    --row: var(--row-jp-5);
}
#archive-home.en [data-name="polka"] {
    --name: "Omaru Polka";
}
#archive-home [data-name="chloe"] {
    counter-increment: gen-6;
    --name: "沙花叉クロヱ";
    --image-color: darkred !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/chloe.jpg');
    --column: calc(var(--pos-jp-6) + 0);
    --row: var(--row-jp-6);
}
#archive-home.en [data-name="chloe"] {
    --name: "Sakamata Chloe";
}
#archive-home [data-name="iroha"] {
    counter-increment: gen-6;
    --name: "風真いろは";
    --image-color: paleturquoise !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/iroha.jpg');
    --column: calc(var(--pos-jp-6) + 1);
    --row: var(--row-jp-6);
}
#archive-home.en [data-name="iroha"] {
    --name: "Kazama Iroha";
}
#archive-home [data-name="koyori"] {
    counter-increment: gen-6;
    --name: "博衣こより";
    --image-color: #e3adc1 !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/koyori.jpg');
    --column: calc(var(--pos-jp-6) + 2);
    --row: var(--row-jp-6);
}
#archive-home.en [data-name="koyori"] {
    --name: "Hakui Koyori";
}
#archive-home [data-name="laplus"] {
    counter-increment: gen-6;
    --name: "ラプラス・ダークネス";
    --image-color: darkslateblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/laplus.jpg');
    --column: calc(var(--pos-jp-6) + 3);
    --row: var(--row-jp-6);
}
#archive-home.en [data-name="laplus"] {
    --name: "La+ Darknesss";
}
#archive-home [data-name="lui"] {
    counter-increment: gen-5;
    --name: "鷹嶺ルイ";
    --image-color: plum !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/lui.jpg');
    --column: calc(var(--pos-jp-6) + 4);
    --row: var(--row-jp-6);
}
#archive-home.en [data-name="lui"] {
    --name: "Takane Lui";
}
/* ID */
#archive-home [data-name="risu"] {
    counter-increment: gen-id1;
    --name: "アユンダ・リス";
    --image-color: lightpink !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/risu.jpg');
    --column: calc(var(--pos-id-1) + 0);
    --row: var(--row-id-1);
}
#archive-home.en [data-name="risu"] {
    --name: "Ayunda Risu";
}
#archive-home [data-name="moona"] {
    counter-increment: gen-id1;
    --name: "ムーナ・ホシノヴァ";
    --image-color: mediumorchid !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/moona.jpg');
    --column: calc(var(--pos-id-1) + 1);
    --row: var(--row-id-1);
}
#archive-home.en [data-name="moona"] {
    --name: "Moona Hoshinova";
}
#archive-home [data-name="iofi"] {
    counter-increment: gen-id1;
    --name: "アイラニ・イオフィフティーン";
    --image-color: pink !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/iofi.jpg');
    --column: calc(var(--pos-id-1) + 2);
    --row: var(--row-id-1);
}
#archive-home.en [data-name="iofi"] {
    --name: "Airani Iofifteen";
}
#archive-home [data-name="ollie"] {
    counter-increment: gen-id2;
    --name: "クレイジー・オリー";
    --image-color: crimson !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/ollie.jpg');
    --column: calc(var(--pos-id-2) + 0);
    --row: var(--row-id-2);
}
#archive-home.en [data-name="ollie"] {
    --name: "Kureiji Ollie";
}
#archive-home [data-name="melfissa"] {
    counter-increment: gen-id2;
    --name: "アーニャ・メルフィッサ";
    --image-color: tan !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/melfissa.jpg');
    --column: calc(var(--pos-id-2) + 1);
    --row: var(--row-id-2);
}
#archive-home.en [data-name="melfissa"] {
    --name: "Anya Melfissa";
}
#archive-home [data-name="reine"] {
    counter-increment: gen-id2;
    --name: "パヴォリア・レイネ";
    --border-color: mediumblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/reine.jpg');
    --column: calc(var(--pos-id-2) + 2);
    --row: var(--row-id-2);
}
#archive-home.en [data-name="reine"] {
    --name: "Pavolia Reine";
}
/* EN */
#archive-home [data-name="amelia"] {
    counter-increment: gen-en1;
    --name: "ワトソン・アメリア";
    --image-color: khaki !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/amelia.jpg');
    --column: calc(var(--pos-en-1) + 0);
    --row: var(--row-en-1);
}
#archive-home.en [data-name="amelia"] {
    --name: "Watson Amelia";
}
#archive-home [data-name="calliope"] {
    counter-increment: gen-en1;
    --name: "森カリオペ";
    --image-color: lightpink !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/calliope.jpg');
    --column: calc(var(--pos-en-1) + 1);
    --row: var(--row-en-1);
}
#archive-home.en [data-name="calliope"] {
    --name: "Mori Calliope";
}
#archive-home [data-name="gura"] {
    counter-increment: gen-en1;
    --name: "がうる・ぐら";
    --image-color: royalblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/gura.jpg');
    --column: calc(var(--pos-en-1) + 2);
    --row: var(--row-en-1);
}
#archive-home.en [data-name="gura"] {
    --name: "Graw Gura";
}
#archive-home [data-name="inanis"] {
    counter-increment: gen-en1;
    --name: "一伊那尓栖";
    --image-color: indigo !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/inanis.jpg');
    --column: calc(var(--pos-en-1) + 3);
    --row: var(--row-en-1);
}
#archive-home.en [data-name="inanis"] {
    --name: "Ninomae Ina'nis";
}
#archive-home [data-name="kiara"] {
    counter-increment: gen-en1;
    --name: "小鳥遊キアラ";
    --image-color: orange !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/kiara.jpg');
    --column: calc(var(--pos-en-1) + 4);
    --row: var(--row-en-1);
}
#archive-home.en [data-name="kiara"] {
    --name: "Takanashi Kiara";
}
#archive-home [data-name="irys"] {
    counter-increment: gen-vsinger;
    --name: "IRyS";
    --image-color: darkred !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/irys.jpg');
    --column: calc(var(--pos-en-2) + 0);
    --row: var(--row-en-2);
}
#archive-home.en [data-name="irys"] {
    --name: "IRyS";
}
#archive-home [data-name="sana"] {
    counter-increment: gen-engikai;
    --name: "九十九 佐命";
    --image-color: deeppink !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/sana.jpg');
    --column: calc(var(--pos-en-2) + 1);
    --row: var(--row-en-2);
}
#archive-home.en [data-name="sana"] {
    --name: "Tsukumo Sana";
}
#archive-home [data-name="ceres"] {
    counter-increment: gen-engikai;
    --name: "セレス・ファウナ";
    --image-color: palegreen !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/ceres.jpg');
    --column: calc(var(--pos-en-2) + 2);
    --row: var(--row-en-2);
}
#archive-home.en [data-name="ceres"] {
    --name: "Ceres Fauna";
}
#archive-home [data-name="ouro"] {
    counter-increment: gen-engikai;
    --name: "オーロ・クロニー";
    --image-color: navy !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/ouro.jpg');
    --column: calc(var(--pos-en-2) + 3);
    --row: var(--row-en-2);
}
#archive-home.en [data-name="ouro"] {
    --name: "Ouro Kronii";
}
#archive-home [data-name="mumei"] {
    counter-increment: gen-engikai;
    --name: "七詩ムメイ";
    --image-color: darkslategray !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/mumei.jpg');
    --column: calc(var(--pos-en-2) + 4);
    --row: var(--row-en-2);
}
#archive-home.en [data-name="mumei"] {
    --name: "Nanashi Mumei";
}
#archive-home [data-name="hakos"] {
    counter-increment: gen-engikai;
    --name: "ハコス・ベールズ";
    --image-color: crimson !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/hakos.jpg');
    --column: calc(var(--pos-en-2) + 5);
    --row: var(--row-en-2);
}
#archive-home.en [data-name="hakos"] {
    --name: "Hakos Baelz";
}

/* STARS */
#archive-home [data-name="miyabi"] {
    counter-increment: gen-star2;
    --name: "花咲みやび";
    --image-color: crimson;
    --icon: url('https://holo.poi.cat/assets/thumbnail/miyabi.jpg');
    --column: calc(var(--pos-stars-1) + 0);
    --row: var(--row-stars-1);
}
#archive-home.en [data-name="miyabi"] {
    --name: "Hanasaki Miyabi";
}
#archive-home [data-name="izuru"] {
    counter-increment: gen-star1;
    --name: "奏手イヅル";
    --image-color: mediumblue !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/izuru.jpg');
    --column: calc(var(--pos-stars-1) + 1);
    --row: var(--row-stars-1);
}
#archive-home.en [data-name="izuru"] {
    --name: "Kanade Izru";
}
#archive-home [data-name="aruran"] {
    counter-increment: gen-star1;
    --name: "アルランディス";
    --image-color: forestgreen !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/aruran.jpg');
    --column: calc(var(--pos-stars-1) + 2);
    --row: var(--row-stars-1);
}
#archive-home.en [data-name="aruran"] {
    --name: "Arurandeisu";
}
#archive-home [data-name="rikka"] {
    counter-increment: gen-star1;
    --name: "律可";
    --image-color: mistyrose;
    --icon: url('https://holo.poi.cat/assets/thumbnail/rikka.jpg');
    --column: calc(var(--pos-stars-1) + 3);
    --row: var(--row-stars-1);
}
#archive-home.en [data-name="rikka"] {
    --name: "Rikka";
}

#archive-home [data-name="astel"] {
    counter-increment: gen-star2;
    --name: "アステル レダ";
    --image-color: yellow !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/astel.jpg');
    --column: calc(var(--pos-stars-2) + 0);
    --row: var(--row-stars-2);
}
#archive-home.en [data-name="astel"] {
    --name: "Astel Leda";
}
#archive-home [data-name="temma"] {
    counter-increment: gen-star2;
    --name: "岸堂天真";
    --image-color: khaki;
    --icon: url('https://holo.poi.cat/assets/thumbnail/temma.jpg');
    --column: calc(var(--pos-stars-2) + 1);
    --row: var(--row-stars-2);
}
#archive-home.en [data-name="temma"] {
    --name: "Kishido Temma";
}
#archive-home [data-name="roberu"] {
    counter-increment: gen-star2;
    --name: "夕刻ロベル";
    --image-color: chocolate !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/roberu.jpg');
    --column: calc(var(--pos-stars-2) + 2);
    --row: var(--row-stars-2);
}
#archive-home.en [data-name="roberu"] {
    --name: "Yukoku Roberu";
}

#archive-home [data-name="shien"] {
    counter-increment: gen-star3;
    --name: "影山シエン";
    --image-color: plum !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/shien.jpg');
    --column: calc(var(--pos-stars-3) + 0);
    --row: var(--row-stars-3);
}
#archive-home.en [data-name="shien"] {
    --name: "Kageyama Shien";
}
#archive-home [data-name="oga"] {
    counter-increment: gen-star3;
    --name: "荒咬オウガ";
    --image-color: greenyellow !important;
    --icon: url('https://holo.poi.cat/assets/thumbnail/oga.jpg');
    --column: calc(var(--pos-stars-3) + 1);
    --row: var(--row-stars-3);
}
#archive-home.en [data-name="oga"] {
    --name: "Aragami Oga";
}

/* OFFICIAL */
#archive-home [data-name="hololive"] {
    counter-increment: official;
    --name: "Hololive公式";
    --image-color: deepskyblue;
    --icon: url('https://holo.poi.cat/assets/thumbnail/hololive.jpg');
    --column: calc(var(--pos-stars-3) + 2);
    --row: var(--row-stars-3);
}
#archive-home.en [data-name="hololive"] {
    --name: "Hololive Official";
}
`;
        document.body.appendChild(style);

        const localize = {
            ja: {
                schedule: "スケジュールを見る",
                archive: "終了した放送を見る",
                powered: "参照元: Holotools",
                archive: "参照元: HoloStats",
                sort1: "配信者別に表示",
                sort2: "配信日別に表示",
            },
            en: {
                schedule: "View scheulde",
                archive: "View archive list",
                powered: "Source: Holotools",
                archive: "Source: HoloStats",
                sort1: "Sorted by streamer",
                sort2: "Sorted by date",
            }
        }
        const t = localize[window.navigator.language] || localize.en;

        const container = document.createElement("div");
        container.id = "hololive-schedule";
        container.classList.add("visible");
        container.onmouseenter = onMouseEnter;
        container.innerHTML = `
<div id="tools">
<a id="archive" title="${t.archive}">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="28px" height="28px" viewBox="0 0 512 512">
	<path class="shape" d="M464,56v40h-40V56H88v40H48V56H0v400h48v-40h40v40h336v-40h40v40h48V56H464z M88,354.672H48v-64h40V354.672z
		 M88,221.328H48v-64h40V221.328z M308.094,266.047l-101.734,60.734c-0.75,0.438-1.656,0.469-2.406,0.031
		c-0.734-0.422-1.203-1.219-1.203-2.094V264v-60.719c0-0.859,0.469-1.656,1.203-2.094c0.75-0.406,1.656-0.391,2.406,0.031
		l101.734,60.75c0.719,0.406,1.156,1.203,1.156,2.031C309.25,264.844,308.813,265.625,308.094,266.047z M464,354.672h-40v-64h40
		V354.672z M464,221.328h-40v-64h40V221.328z"></path>
</svg>
</a>
<a id="powered" href="https://hololive.jetri.co/" target="blank_">${t.powered}</a>
</div>
<div id="contents" style="left: 0px;">
    <div id="schedule" class="contents hidden"></div>
    <div id="archive" class="contents hidden"></div>
    <div id="dummy" class="contents"></div>
</div>
<div id="title-preview" class="hidden"></div>
<div id="thumbnail-preview" class="hidden"></div>
`;
        document.body.appendChild(container);

        const home = document.createElement("div");
        home.id = "archive-home";
        home.classList.add("hidden");
        home.classList.add(navigator.language);
        home.innerHTML = `
        <div class="content">
        <div class="face-list"></div>
        <div class="next hidden no-exit">NEXT</div>
        </div>
        <div class="menu">
            <a style="display: none;" class="sort no-exit" target="blank_" data-sort1="${t.sort1}" data-sort2="${t.sort2}"></a>
            <a class="powered" href="https://holo.poi.cat/youtube-stream" target="blank_">${t.archive}</a>
        </div>
        <div class="loading hidden">
            <div class="fragment"></div>
            <div class="fragment"></div>
            <div class="fragment"></div>
            <div class="fragment"></div>
            <div class="fragment"></div>
            <div class="fragment"></div>
        </div>`;

        document.body.appendChild(home);

        var members = storage.holostats.favorite;
        for (var name in members) {
            var isMute = !members[name];
            home.querySelector(".face-list").insertAdjacentHTML("beforeend", `<div class="face no-exit ${isMute ? "mute" : ""}" data-name="${name}"></div>`);
        }
        home.querySelectorAll(".face").forEach((e) => e.addEventListener("click", onToggleMuteChannel));

        home.addEventListener("click", onExitArchiveHome);
        home.querySelector(".next").addEventListener("click", onClickNextArchiveHome);
        home.querySelector(".sort").addEventListener("click", onToggleSortArchiveHome);

        window.addEventListener("wheel", updateVisibility);
        container.addEventListener("wheel", onScrollContainer, true);
        container.querySelector("#tools #archive").addEventListener("click", (ev) => {
            displayArchiveHome(false);
            // refreshArchive(ev.ctrlKey);
        });
        container.addEventListener("mouseenter", (ev) => {

            if (ev.target.classList.contains("hololive-stream")) {
                const title = document.querySelector("#hololive-schedule #title-preview");
                // 左端に合わせておかないと長いタイトルで改行が含まれる場合に
                // clientWidth の値が小さくなって中央寄せに失敗する
                title.style.left = "0px";
                title.innerText = ev.target.querySelector(".title").innerText;
                title.classList.remove("hidden");
                title.style.left = `${document.body.clientWidth / 2 - title.clientWidth / 2}px`;

                const thumbnail = document.querySelector("#hololive-schedule #thumbnail-preview");
                thumbnail.style.backgroundImage = ev.target.querySelector(".thumbnail").style.backgroundImage;
                thumbnail.classList.remove("hidden");
                thumbnail.style.left = `${document.body.clientWidth / 2 - thumbnail.clientWidth / 2}px`;
            }
        }, true);
        container.addEventListener("mouseleave", (ev) => {
            document.querySelector("#hololive-schedule #thumbnail-preview").classList.add("hidden");
            document.querySelector("#hololive-schedule #title-preview").classList.add("hidden");
        });
        drawLoadingDummy();
    }

    function onMouseEnter() {
        if (isRefreshing) return;

        updateVisibility();
        refreshSchedule();
    }

    function updateVisibility() {
        var isFullscreen = document.querySelector("ytd-watch-flexy[fullscreen]");
        var isScrolled = (window.scrollY || window.scrollTop || document.body.scrollY || document.body.scrollTop) > 60;

        document.querySelector("#hololive-schedule").classList.toggle("visible", (!isFullscreen) || (isFullscreen && isScrolled));
    }

    // 枠をつくる
    function createStream(details) {
        // console.log(details);
        var stream = document.createElement("a");
        stream.data = details;
        stream.classList.add("hololive-stream");
        if (hideChannels.find((x) => x == details.channel.yt_channel_id)) {
            stream.classList.add("hide-channel");
        }
        var length = details.live_end - details.live_start;
        stream.href = "/watch/" + details.yt_video_key;
        stream.innerHTML = `
<div class="date">${toLiveDate(details.live_schedule, details.live_start, details.live_end, details.published_at)}</div>
<div class="thumbnail" style="background-image: url('https://i.ytimg.com/vi/${details.yt_video_key}/mqdefault.jpg?${new Date(details.live_start || details.live_schedule || details.published_at).getTime()}')"></div>
<div class="title-wrapper"><div class="title">${details.title}</div></div>
<div class="photo" style="background-image: url('${details.channel.photo.replace("=s800", "=s72")}');" />
`;

        return stream;
    }

    function drawSchedule(streams) {
        // console.log(streams);

        try {
            const contents = document.querySelector("#hololive-schedule #contents #schedule");
            contents.innerHTML = "";
            contents.style.left = "0px";

            var key, stream;

            if (streams) {
                if (streams.live) {
                    streams.live = streams.live
                        .filter(x => x.status != "missing");
                    streams.live
                        .sort((a, b) => (a.live_start || a.live_schedule) < (b.live_start || b.live_schedule) ? 1 : -1);
                    for (key in streams.live) {
                        stream = createStream(streams.live[key]);
                        stream.classList.add("live");
                        contents.appendChild(stream);
                    }
                }

                if (streams.upcoming) {
                    streams.upcoming = streams.upcoming
                        .filter(x => x.status != "missing")
                        .filter(x => new Date(x.live_schedule).getTime() - new Date().getTime() < FREECHAT_SCHEDULE);
                    streams.upcoming
                        .sort((a, b) => a.live_schedule > b.live_schedule ? 1 : -1);
                    for (key in streams.upcoming) {
                        stream = createStream(streams.upcoming[key]);
                        stream.classList.add("upcoming");
                        contents.appendChild(stream);
                    }
                }

                if (streams.ended) {
                    streams.ended = streams.ended
                        .filter(x => x.status != "missing");
                    streams.ended
                        .sort((a, b) => a.live_start < b.live_start ? 1 : -1);
                    for (key in streams.ended) {
                        stream = createStream(streams.ended[key]);
                        stream.classList.add("ended");
                        contents.appendChild(stream);
                    }
                }
            }

            contents.querySelectorAll(".thumbnail").forEach((element, index) => {
                setTimeout(() => element.classList.add("loaded"), (index + 1) * 25);
            });
            contents.querySelectorAll(".photo").forEach((element, index) => {
                setTimeout(() => element.classList.add("loaded"), (index + 1) * 25);
            });
        } finally {
            toggleDisplay("schedule");
        }
    }

    function drawLoadingDummy() {
        const contents = document.querySelector("#hololive-schedule #contents #dummy");
        contents.innerHTML = "";
        contents.style.left = "0px";
        const data = {
            bb_video_id: null,
            channel:
            {
                bb_space_id: null,
                id: 0,
                name: "Loading",
                photo: "https://www.youtube.com/s/desktop/fe7279a7/img/favicon_144.png",
                published_at: "2000-01-01T00:00:00.000Z",
                subscriber_count: 0,
                twitter_link: "",
                video_count: 0,
                view_count: 0,
                yt_channel_id: "",
            },
            id: 0,
            live_end: null,
            live_schedule: "2222-22-22T22:22:22.222Z",
            live_start: null,
            live_viewers: null,
            status: "upcoming",
            thumbnail: null,
            title: "Comming soon ...",
            yt_video_key: "",
        }
        const dummy = createStream(data);
        dummy.classList.add("dummy");

        for (var i = 0; i < 20; i++) {
            contents.appendChild(dummy.cloneNode(true));
        }

        contents.querySelectorAll(".thumbnail").forEach((element, index) => {
            setTimeout(() => element.classList.add("loaded"), 25);
        });
        contents.querySelectorAll(".photo").forEach((element, index) => {
            setTimeout(() => element.classList.add("loaded"), 25);
        });
    }

    function drawArchive(streams) {
        // console.log(streams);

        try {
            const contents = document.querySelector("#hololive-schedule #contents #archive");
            contents.style.left = "0px";
            contents.innerHTML = "";

            if (streams && streams.videos) {
                streams.videos = streams.videos
                    .filter(x => x.status == "past");
                streams.videos
                    .sort((a, b) => (a.live_end || a.published_at) < (b.live_end || b.published_at) ? 1 : -1);
                for (var key in streams.videos) {
                    const stream = createStream(streams.videos[key]);
                    stream.classList.add("ended");
                    contents.appendChild(stream);
                }
            }

            contents.querySelectorAll(".photo").forEach((element, index) => {
                setTimeout(() => element.classList.add("loaded"), (index + 1) * 25);
            });
            contents.querySelectorAll(".thumbnail").forEach((element, index) => {
                setTimeout(() => element.classList.add("loaded"), (index + 1) * 25);
            });
        } finally {
            toggleDisplay("archive");
        }
    }

    function toggleDisplay(target) {
        document.querySelectorAll(`#hololive-schedule .contents`).forEach((element, index) => {
            element.classList.toggle("hidden", element.id != target);
        });
        document.querySelector("#hololive-schedule #contents").style.left = 0;
    }

    function refreshSchedule(force) {
        //console.log(`refreshSchedule(${force})`);

        if (isRefreshing) return;
        isRefreshing = true;

        if (force) {
            storage.schedule.clear();
            scheduleUpdated = 0;
        }

        try {
            const cacheUpdated = storage.schedule.updated;
            const newestUpdated = Math.max(cacheUpdated, scheduleUpdated);

            //console.log(`c:${cacheUpdated}, s:${scheduleUpdated}`);

            // 十分に新しい
            if (new Date().getTime() - newestUpdated < REFRESH_INTERVAL) {
                // キャッシュのが新しい
                if (cacheUpdated > scheduleUpdated) {
                    const cache = storage.schedule.cache;
                    if (cache) {
                        drawSchedule(cache);
                        scheduleUpdated = cacheUpdated;
                        isRefreshing = false;
                        return;
                    }
                } else {
                    isRefreshing = false;
                    toggleDisplay("schedule");
                    return;
                }
            }
        } catch (ex) {
            console.log(ex);
            storage.schedule.clear();
        }

        toggleDisplay("dummy");

        GM_xmlhttpRequest({
            method: "GET",
            url: "https://api.holotools.app/v1/live?max_upcoming_hours=2190&hide_channel_desc=1",
            headers: { "user-agent": `${APPNAME}/${VERSION}` },
            onload: (context) => {
                // console.log(context);
                try {
                    const response = JSON.parse(context.responseText);
                    if (!response.message) {
                        storage.schedule.updated = scheduleUpdated = new Date().getTime();
                        storage.schedule.cache = response;

                        drawSchedule(response);
                    }
                } catch (ex) {
                    console.log(ex, context);
                } finally {
                    isRefreshing = false;
                }
            },
            onerror: setTimeoutToRefresh,
            onabort: setTimeoutToRefresh,
            ontimeout: setTimeoutToRefresh,
        });
    }

    function refreshArchive(force) {
        // console.log("refreshArchive()");
        if (isRefreshing) return;
        isRefreshing = true;

        if (force) {
            storage.archive.clear();
            archiveUpdated = 0;
        }

        try {
            const cacheUpdated = storage.archive.updated;
            const newestUpdated = Math.max(cacheUpdated, archiveUpdated);

            // console.log(`c:${cacheUpdated}, a:${archiveUpdated}`);

            // 十分に新しい
            if (new Date().getTime() - newestUpdated < REFRESH_INTERVAL) {
                // キャッシュのが新しい
                if (cacheUpdated > archiveUpdated) {
                    const cache = storage.archive.cache;
                    if (cache) {
                        drawArchive(cache);
                        archiveUpdated = cacheUpdated;
                        isRefreshing = false;
                        return;
                    }
                } else {
                    isRefreshing = false;
                    toggleDisplay("archive");
                    return;
                }
            }
        } catch (ex) {
            console.log(ex);

            storage.archive.clear();
        }

        const now = new Date().getTime();
        const offset = new Date().getTimezoneOffset() * 60 * 1000;

        const start = new Date();
        start.setTime(now - ARCHIVE_ENDHOURS - offset);
        const end = new Date();
        end.setTime(now - offset);

        // console.log(`${start.toISOString()}${end.toISOString()}`);
        toggleDisplay("dummy");

        GM_xmlhttpRequest({
            method: "GET",
            url: `https://api.holotools.app/v1/videos?start_date=${start.toISOString()}&end_date=${end.toISOString()}&limit=50&sort=live_schedule&order=desc`,
            headers: { "user-agent": `${APPNAME}/${VERSION}` },
            onload: (context) => {
                // console.log(context);
                try {
                    var response = JSON.parse(context.responseText);
                    if (!response.message) {
                        storage.archive.updated = archiveUpdated = new Date().getTime();
                        storage.archive.cache = response;
                        drawArchive(response);
                    }
                } catch (ex) {
                    console.log(ex, context);
                } finally {
                    isRefreshing = false;
                }

            },
            onerror: setTimeoutToRefresh,
            onabort: setTimeoutToRefresh,
            ontimeout: setTimeoutToRefresh,
        });
    }

    function setTimeoutToRefresh() {
        setTimeout(() => {
            isRefreshing = false;
        }, ERROR_TIMEOUT);
    }

    function toLiveDate(schedule, start, end, publish) {
        // console.log(schedule, start, end, publish);

        if (end) {
            const diff = new Date(end).getTime() - new Date(start).getTime();
            const dm = Math.floor(diff / 1000 / 60 % 60);
            const dh = Math.floor(diff / 1000 / 60 / 60);

            var span = "";
            if (dh > 0) {
                span += dh + "h ";
            }
            span += dm + "m";

            return span;
        }

        start = new Date(start || schedule || publish);
        const h = ("0" + start.getHours()).slice(-2);
        const m = ("0" + start.getMinutes()).slice(-2);
        if (start.getDate() != new Date().getDate()) {
            return `${start.getDate()}-${h}:${m}`;
        } else {
            return `${h}:${m}`;
        }
    }

    function onExitArchiveHome(ev) {
        if (ev.target.classList.contains("no-exit")) return;
        this.classList.add("hidden");
    }

    function onToggleMuteChannel() {
        var name = this.getAttribute("data-name");
        var fav = storage.holostats.favorite;
        if (fav[name] != null) {
            fav[name] = !fav[name];
            this.classList.toggle("mute", !fav[name]);
        }
        storage.holostats.favorite = fav;
        storage.holostats.updated = 0; // キャッシュ無効

        displayArchiveHome(true);
    }

    function onClickNextArchiveHome() {
        continueArchiveHome(2);
    }

    function onToggleSortArchiveHome() {
        archiveSortedByName = document.querySelector("#archive-home").toggleAttribute("order-name");
        document.querySelector("#archive-home").toggleAttribute("order-date", !archiveSortedByName);
        document.querySelectorAll("#archive-home .thumbnail, #archive-home .category").forEach((element) => element.remove());
        displayArchiveHome(true, 0);
        document.querySelector("#archive-home .next").classList.remove("hidden");
    }

    var holoStatsTimeout = 0;
    function setHoloStatsTimeout() {
        holoStatsTimeout = clearTimeout(holoStatsTimeout);
        holoStatsTimeout = setTimeout(() => {
            holoStatsTimeout = 0;
        }, 3000);
    }

    function selectArchiveCategory(category) {
        var target = document.querySelector(`#archive-home .category[data-name='${category}']`);
        if (target) return target;

        var div = document.createElement("div");
        div.classList.add("category");
        div.setAttribute("data-name", category);
        div.innerHTML = `<div class="header" data-name="${category}">${category}</div>`;
        document.querySelector("#archive-home .next").before(div);

        return div;
    }

    var archiveSortedByName = false;
    function buildArchiveHome(data) {
        if (!data) return;

        var stream;
        for (var i = 0; i < data.streams.length; i++) {
            stream = data.streams[i];
            if (!stream.startTime) continue;
            if (!storage.holostats.favorite[stream.vtuberId]) continue;

            var startDate = new Date(stream.startTime);
            var length = stream.endTime - stream.startTime;
            var lengthMin = Math.floor(length / 1000 / 60 % 60);
            var lengthHours = Math.floor(length / 1000 / 60 / 60 % 24);
            var date = `${startDate.getMonth() + 1}/${startDate.getDate()}`;


            var category = selectArchiveCategory(archiveSortedByName ? stream.vtuberId : date);
            category.insertAdjacentHTML("beforeend", `<a class="thumbnail ${stream.status}"
                href="/watch?v=${stream.streamId}" data-date="${date}" data-name="${stream.vtuberId}" data-startTime="${stream.startTime}"
                style="background-image: url('https://img.youtube.com/vi/${stream.streamId}/mqdefault.jpg')"><span class="title">${stream.title}</span><span class="length">${lengthHours}:${("00" + lengthMin).slice(-2)}</span></a>`);
            category.style.setProperty("--thumbnail-num", category.querySelectorAll(".thumbnail").length);
        }
    }

    function continueArchiveHome(continueCount, isReload) {
        setHoloStatsTimeout();
        var c = storage.holostats.cache;

        if (isReload) {
            c = null;
            document.querySelectorAll("#archive-home .thumbnail, #archive-home .category").forEach((element) => element.remove());
            document.querySelector("#archive-home .loading").classList.remove("hidden");
        }
        document.querySelector("#archive-home .next").classList.add("hidden");

        loadHoloStats((context) => {
            var response = JSON.parse(context.responseText);
             // console.log(response);

            if (c) {
                c.streams = c.streams.concat(response.streams);
            } else {
                c = response;
            }
            storage.holostats.cache = c;
            buildArchiveHome(response);

            document.querySelector("#archive-home .loading").classList.add("hidden");
            if (continueCount-- > 0) {
                continueArchiveHome(continueCount);
            } else {
                document.querySelector("#archive-home .next").classList.remove("hidden");
            }
        }, setHoloStatsTimeout, c ? c.streams.slice(-1)[0].startTime : 0);
    }

    function displayArchiveHome(force) {
        if (!force && holoStatsTimeout > 0) return false;
        setHoloStatsTimeout();

        document.querySelectorAll("#archive-home .thumbnail, #archive-home .category").forEach((element) => element.remove());
        document.querySelector("#archive-home .next").classList.add("hidden");
        document.querySelector("#archive-home").classList.remove("hidden");

        if ((new Date().getTime() - storage.holostats.updated) < HOLOSTATS_INTERVAL) {
            buildArchiveHome(storage.holostats.cache);
            document.querySelector("#archive-home .next").classList.remove("hidden");
            return true;
        }

        storage.holostats.updated = new Date().getTime();
        storage.holostats.cache = null;

        continueArchiveHome(0, true);
    }

    function loadHoloStats(loaded, errored, endAt) {
        var ids = "";
        var f = storage.holostats.favorite;
        for (var member in f) {
            if (f[member]) {
                ids += member + ",";
            }
        }
        ids = ids.trim(",");

        var url = `https://holoapi.poi.cat/api/v4/youtube_streams?ids=${ids}&status=ended`;
        if (endAt > 0) {
            url += "&endAt=" + endAt;
        }
        // console.log(url);

        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            headers: { "user-agent": `${APPNAME}/${VERSION}` },
            onload: loaded,
            onerror: errored,
            onabort: errored,
            ontimeout: errored,
        });
    }
})();