PageSourceSearch

https://yhuanhuan01.github.io/js/app.js?v=0.2.5

js yhuanhuan01.github.io collected 2026-10-03 10:12:49 UTC 69,903 bytes, 2,455 lines download raw bytes

1var CONFIG = {"version":"0.2.5","hostname":"https://yhuanhuan01.github.io","root":"/","statics":"/","favicon":{"normal":"images/favicon.ico","hidden":"images/failure.ico"},"darkmode":true,"auto_scroll":false,"js":{"valine":"gh/amehime/[email protected]/dist/MiniValine.min.js","chart":"npm/[email protected]/dist/frappe-charts.min.iife.min.js","copy_tex":"npm/[email protected]/dist/contrib/copy-tex.min.js","fancybox":"combine/npm/[email protected]/dist/jquery.min.js,npm/@fancyapps/[email protected]/dist/jquery.fancybox.min.js,npm/[email protected]/dist/js/jquery.justifiedGallery.min.js"},"css":{"valine":"css/comment.css","katex":"npm/[email protected]/dist/katex.min.css","mermaid":"css/mermaid.css","fancybox":"combine/npm/@fancyapps/[email protected]/dist/jquery.fancybox.min.css,npm/[email protected]/dist/css/justifiedGallery.min.css"},"loader":{"start":true,"switch":true},"search":null,"valine":{"appId":"flsOZ3PfHqRDqTbt7BCWqEc6-gzGzoHsz","appKey":"8VlaE0vIPnXadBDvKK6ShFyD","placeholder":"学无止境ヽ(○´∀`)ノ♪","avatar":"mp","pageSize":10,"lang":"zh-CN","visitor":true,"NoRecordIP":false,"serverURLs":null,"powerMode":true,"tagMeta":{"visitor":"新朋友","master":"主人","friend":"小伙伴","investor":"金主粑粑"},"tagColor":{"master":"var(--color-orange)","friend":"var(--color-aqua)","investor":"var(--color-pink)"},"tagMember":{"master":null,"friend":null,"investor":null}},"quicklink":{"timeout":3000,"priority":true},"audio":[{"title":"列表1","list":["https://music.163.com/#/my/m/music/playlist?id=2454234066","https://music.163.com/#/my/m/music/playlist?id=8161689066"]},{"title":"列表2","list":["https://music.163.com/#/my/m/music/playlist?id=7746881212"]}],"fireworks":["rgba(255,182,185,.9)","rgba(250,227,217,.9)","rgba(187,222,214,.9)","rgba(138,198,209,.9)"]};const getRndInteger = function (min, max) {
2  return Math.floor(Math.random() * (max - min + 1)) + min;
3}
4
5const getDocHeight = function () {
6  return $('main > .inner').offsetHeight;
7}
8
9const getScript = function(url, callback, condition) {
10  if (condition) {
11    callback();
12  } else {
13    var script = document.createElement('script');
14    script.onload = script.onreadystatechange = function(_, isAbort) {
15      if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) {
16        script.onload = script.onreadystatechange = null;
17        script = undefined;
18        if (!isAbort && callback) setTimeout(callback, 0);
19      }
20    };
21    script.src = url;
22    document.head.appendChild(script);
23  }
24}
25
26const assetUrl = function(asset, type) {
27  var str = CONFIG[asset][type]
28  if(str.indexOf('npm')>-1||str.indexOf('gh')>-1||str.indexOf('combine')>-1)
29    return "//cdn.jsdelivr.net/" + str
30
31  if(str.indexOf('http')>-1)
32    return str
33
34  return statics + str;
35}
36
37const vendorJs = function(type, callback, condition) {
38  if(LOCAL[type]) {
39    getScript(assetUrl("js", type), callback || function(){
40      window[type] = true;
41    }, condition || window[type]);
42  }
43}
44
45const vendorCss = function(type, condition) {
46  if(window['css'+type])
47    return;
48
49  if(LOCAL[type]) {
50
51    document.head.createChild('link', {
52      rel: 'stylesheet',
53      href: assetUrl("css", type)
54    });
55
56    window['css'+type] = true;
57  }
58}
59
60const pjaxScript = function(element) {
61  var code = element.text || element.textContent || element.innerHTML || '';
62  var parent = element.parentNode;
63  parent.removeChild(element);
64  var script = document.createElement('script');
65  if (element.id) {
66    script.id = element.id;
67  }
68  if (element.className) {
69    script.className = element.className;
70  }
71  if (element.type) {
72    script.type = element.type;
73  }
74  if (element.src) {
75    script.src = element.src;
76    // Force synchronous loading of peripheral JS.
77    script.async = false;
78  }
79  if (element.dataset.pjax !== undefined) {
80    script.dataset.pjax = '';
81  }
82  if (code !== '') {
83    script.appendChild(document.createTextNode(code));
84  }
85  parent.appendChild(script);
86}
87
88const pageScroll = function(target, offset, complete) {
89  var opt = {
90    targets: typeof offset == 'number' ? target.parentNode : document.scrollingElement,
91    duration: 500,
92    easing: "easeInOutQuad",
93    scrollTop: offset || (typeof target == 'number' ? target : (target ? target.top() + document.documentElement.scrollTop - siteNavHe
93ight : 0)),
94    complete: function() {
95      complete && complete()
96    }
97  }
98  anime(opt);
99}
100
101const transition = function(target, type, complete) {
102  var animation = {}
103  var display = 'none'
104  switch(type) {
105    case 0:
106      animation = {opacity: [1, 0]}
107    break;
108    case 1:
109      animation = {opacity: [0, 1]}
110      display = 'block'
111    break;
112    case 'bounceUpIn':
113      animation = {
114        begin: function(anim) {
115          target.display('block')
116        },
117        translateY: [
118          { value: -60, duration: 200 },
119          { value: 10, duration: 200 },
120          { value: -5, duration: 200 },
121          { value: 0, duration: 200 }
122        ],
123        opacity: [0, 1]
124      }
125      display = 'block'
126    break;
127    case 'shrinkIn':
128      animation = {
129        begin: function(anim) {
130          target.display('block')
131        },
132        scale: [
133          { value: 1.1, duration: 300 },
134          { value: 1, duration: 200 }
135        ],
136        opacity: 1
137      }
138      display = 'block'
139    break;
140    case 'slideRightIn':
141      animation = {
142        begin: function(anim) {
143          target.display('block')
144        },
145        translateX: [100, 0],
146        opacity: [0, 1]
147      }
148      display = 'block'
149    break;
150    case 'slideRightOut':
151      animation = {
152        translateX: [0, 100],
153        opacity: [1, 0]
154      }
155    break;
156    default:
157      animation = type
158      display = type.display
159    break;
160  }
161  anime(Object.assign({
162    targets: target,
163    duration: 200,
164    easing: 'linear'
165  }, animation)).finished.then(function() {
166      target.display(display)
167      complete && complete()
168    });
169}
170
171const store = {
172  get: function(item) {
173    return localStorage.getItem(item);
174  },
175  set: function(item, str) {
176    localStorage.setItem(item, str);
177    return str;
178  },
179  del: function(item) {
180    localStorage.removeItem(item);
181  }
182}
183const $ = function(selector, element) {
184  element = element || document;
185  if(selector.indexOf('#') === 0) {
186    return element.getElementById(selector.replace('#', ''))
187  }
188  return element.querySelector(selector)
189};
190
191$.all = function(selector, element) {
192  element = element || document;
193  return element.querySelectorAll(selector)
194};
195
196$.each = function(selector, callback, element) {
197  return $.all(selector, element).forEach(callback)
198}
199
200
201Object.assign(HTMLElement.prototype, {
202  createChild: function(tag, obj, positon) {
203    var child = document.createElement(tag);
204    Object.assign(child, obj)
205    switch(positon) {
206      case 'after':
207        this.insertAfter(child)
208        break;
209      case 'replace':
210        this.innerHTML = ""
211      default:
212        this.appendChild(child)
213    }
214    return child
215  },
216  wrap: function (obj) {
217    var box = document.createElement('div');
218    Object.assign(box, obj)
219    this.parentNode.insertBefore(box, this);
220    this.parentNode.removeChild(this);
221    box.appendChild(this);
222  },
223  height: function(h) {
224    if(h) {
225      this.style.height = typeof h == 'number' ? h + 'rem' : h;
226    }
227    return this.getBoundingClientRect().height
228  },
229  width: function(w) {
230    if(w) {
231      this.style.width = typeof w == 'number' ? w + 'rem' : w;
232    }
233    return this.getBoundingClientRect().width
234  },
235  top: function() {
236    return this.getBoundingClientRect().top
237  },
238  left:function() {
239    return this.getBoundingClientRect().left
240  },
241  attr: function(type, value) {
242    if(value === null) {
243      return this.removeAttribute(type)
244    }
245
246    if(value) {
247      this.setAttribute(type, value)
248      return this
249    } else {
250      return this.getAttribute(type)
251    }
252  },
253  insertAfter: function(element) {
254    var parent = this.parentNode;
255    if(parent.lastChild == this){
256        parent.appendChild(element);
257    }else{
258        parent.insertBefore(element, this.nextSibling);
259    }
260  },
261  display: function(d) {
262    if(d == null) {
263      return this.style.display
264    } else {
265      this.style.display = d;
266      return this
267    }
268  },
269  child: function(selector) {
270    return $(selector, this)
271  },
272  find: function(selector) {
273    return $.all(selector, this)
274  },
275  _class: function(type, className, display) {
276    var classNames = className.indexOf(' ') ?  className.split(' ') : [className];
277    var that = this;
278    classNames.forEach(function(name) {
279      if(type == 'toggle') {
280        that.classList.toggle(name, display)
281      } else {
282        that.classList[type](name)
283      }
284    })
285  },
286  addClass: function(className) {
287    this._class('add', className);
288    return this;
289  },
290  removeClass: function(className) {
291    this._class('remove', className);
292    return this;
293  },
294  toggleClass: function(className, display) {
295    this._class('toggle', className, display);
296    return this;
297  },
298  hasClass: function(className) {
299    return this.classList.contains(className)
300  }
301});
302var NOWPLAYING = null
303const isMobile = /mobile/i.test(window.navigator.userAgent);
304const mediaPlayer = function(t, config) {
305  var option = {
306    type: 'audio',
307    mode: 'random',
308    btns: ['play-pause', 'music'],
309    controls: ['mode', 'backward', 'play-pause', 'forward', 'volume'],
310    events: {
311      "play-pause": function(event) {
312          if(source.paused) {
313            t.player.play()
314          } else {
315            t.player.pause()
316          }
317      },
318      "music": function(event) {
319        if(info.el.hasClass('show')) {
320          info.hide()
321        } else {
322          info.el.addClass('show');
323          playlist.scroll().title()
324        }
325      }
326    }
327  }, utils = {
328    random: function(len) {
329      return Math.floor((Math.random()*len))
330    },
331    parse: function(link) {
332      var result = [];
333      [
334        ['music.163.com.*song.*id=(\\d+)', 'netease', 'song'],
335        ['music.163.com.*album.*id=(\\d+)', 'netease', 'album'],
336        ['music.163.com.*artist.*id=(\\d+)', 'netease', 'artist'],
337        ['music.163.com.*playlist.*id=(\\d+)', 'netease', 'playlist'],
338        ['music.163.com.*discover/toplist.*id=(\\d+)', 'netease', 'playlist'],
339        ['y.qq.com.*song/(\\w+).html', 'tencent', 'song'],
340        ['y.qq.com.*album/(\\w+).html', 'tencent', 'album'],
341        ['y.qq.com.*singer/(\\w+).html', 'tencent', 'artist'],
342        ['y.qq.com.*playsquare/(\\w+).html', 'tencent', 'playlist'],
343        ['y.qq.com.*playlist/(\\w+).html', 'tencent', 'playlist'],
344        ['xiami.com.*song/(\\w+)', 'xiami', 'song'],
345        ['xiami.com.*album/(\\w+)', 'xiami', 'album'],
346        ['xiami.com.*artist/(\\w+)', 'xiami', 'artist'],
347        ['xiami.com.*collect/(\\w+)', 'xiami', 'playlist'],
348      ].forEach(function(rule) {
349        var patt = new RegExp(rule[0])
350        var res = patt.exec(link)
351        if (res !== null) {
352          result = [rule[1], rule[2], res[1]]
353        }
354      })
355      return result
356    },
357    fetch: function(source) {
358      var list = []
359
360      return new Promise(function(resolve, reject) {
361        source.forEach(function(raw) {
362          var meta = utils.parse(raw)
363          if(meta[0]) {
364            var skey = JSON.stringify(meta)
365            var playlist = store.get(skey)
366            if(playlist) {
367              list.push.apply(list, JSON.parse(playlist));
368              resolve(list);
369            } else {
370              fetch('https://api.i-meto.com/meting/api?server='+meta[0]+'&type='+meta[1]+'&id='+meta[2]+'&r='+ Math.random())
371                .then(function(response) {
372                  return response.json()
373                }).then(function(json) {
374                  store.set(skey, JSON.stringify(json))
375                  list.push.apply(list, json);
376                  resolve(list);
377                }).catch(function(ex) {})
378            }
379          } else {
380            list.push(raw);
381            resolve(list);
382          }
383        })
384      })
385    },
386    secondToTime: function(second) {
387      var add0 = function(num) {
388        return isNaN(num) ? '00' : (num < 10 ? '0' + num : '' + num)
389      };
390      var hour = Math.floor(second / 3600);
391      var min = Math.floor((second - hour * 3600) / 60);
392      var sec = Math.floor(second - hour * 3600 - min * 60);
393      return (hour > 0 ? [hour, min, sec] : [min, sec]).map(add0).join(':');
394    },
395    nameMap: {
396      dragStart: isMobile ? 'touchstart' : 'mousedown',
397      dragMove: isMobile ? 'touchmove' : 'mousemove',
398      dragEnd: isMobile ? 'touchend' : 'mouseup',
399    }
400  }, source = null;
401
402  t.player = {
403    _id: utils.random(999999),
404    group: true,
405    // 加载播放列表
406    load: function(newList) {
407      var d = ""
408      var that = this
409
410      if(newList && newList.length > 0) {
411        if(this.options.rawList !== newList) {
412          this.options.rawList = newList;
413          playlist.clear()
414          // 获取新列表
415          //this.fetch()
416        }
417      } else {
418        // 没有列表时,隐藏按钮
419        d = "none"
420        this.pause()
421      }
422      for(var el in buttons.el) {
423        buttons.el[el].display(d)
424      }
425      return this
426    },
427    fetch: function () {
428      var that = this;
429      return new Promise(function(resolve, reject) {
430          if(playlist.data.length > 0) {
431            resolve()
432          } else {
433            if(that.options.rawList) {
434              var promises = [];
435
436              that.options.rawList.forEach(function(raw, index) {
437                promises.push(new Promise(function(resolve, reject) {
438                  var group = index
439                  var source
440                  if(!raw.list) {
441                    group = 0
442                    that.group = false
443                    source = [raw]
444                  } else {
445                    that.group = true
446                    source = raw.list
447                  }
448                  utils.fetch(source).then(function(list) {
449                    playlist.add(group, list)
450                    resolve()
451                  })
452                }))
453              })
454
455              Promise.all(promises).then(function() {
456                resolve(true)
457              })
458            }
459          }
460        }).then(function(c) {
461          if(c) {
462            playlist.create()
463            controller.create()
464            that.mode()
465          }
466        })
467    },
468    // 根据模式切换当前曲目index
469    mode: function() {
470      var total = playlist.data.length;
471
472      if(!total || playlist.errnum == total)
473        return;
474
475      var step = controller.step == 'next' ? 1 : -1
476
477      var next = function() {
478        var index = playlist.index + step
479        if(index > total || index < 0) {
480          index = controller.step == 'next' ? 0 : total-1;
481        }
482        playlist.index = index;
483      }
484
485      var random = function() {
486        var p = utils.random(total)
487        if(playlist.index !== p) {
488          playlist.index = p
489        } else {
490          next()
491        }
492      }
493
494      switch (this.options.mode) {
495        case 'random':
496          random()
497          break;
498        case 'order':
499          next()
500          break;
501        case 'loop':
502          if(controller.step)
503            next()
504
505          if(playlist.index == -1)
506            random()
507          break;
508      }
509
510      this.init()
511    },
512    // 直接设置当前曲目index
513    switch: function(index) {
514      if(typeof index == 'number'
515        && index != playlist.index
516        && playlist.current()
517        && !playlist.current().error) {
518        playlist.index = index;
519        this.init()
520      }
521    },
522    // 更新source为当前曲目index
523    init: function() {
524      var item = playlist.current()
525
526      if(!item || item['error']) {
527        this.mode();
528        return;
529      }
530
531      var playing = false;
532      if(!source.paused) {
533        playing = true
534        this.stop()
535      }
536
537      source.attr('src', item.url);
538      source.attr('title', item.name + ' - ' + item.artist);
539      this.volume(store.get('_PlayerVolume') || '0.7')
540      this.muted(store.get('_PlayerMuted'))
541
542      progress.create()
543
544      if(this.options.type == 'audio')
545        preview.create()
546
547      if(playing == true) {
548        this.play()
549      }
550    },
551    play: function() {
552      NOWPLAYING && NOWPLAYING.player.pause()
553
554      if(playlist.current().error) {
555        this.mode();
556        return;
557      }
558      var that = this
559      source.play().then(function() {
560        playlist.scroll()
561      }).catch(function(e) {});
562    },
563    pause: function() {
564      source.pause()
565      document.title = originTitle
566    },
567    stop: function() {
568      source.pause();
569      source.currentTime = 0;
570      document.title = originTitle;
571    },
572    seek: function(time) {
573      time = Math.max(time, 0)
574      time = Math.min(time, source.duration)
575      source.currentTime = time;
576      progress.update(time / source.duration)
577    },
578    muted: function(status) {
579      if(status == 'muted') {
580        source.muted = status
581        store.set('_PlayerMuted', status)
582        controller.update(0)
583      } else {
584        store.del('_PlayerMuted')
585        source.muted = false
586        controller.update(source.volume)
587      }
588    },
589    volume: function(percentage) {
590      if (!isNaN(percentage)) {
591        controller.update(percentage)
592        store.set('_PlayerVolume', percentage)
593        source.volume = percentage
594      }
595    },
596    mini: function() {
597      info.hide()
598    }
599  };
600
601  var info = {
602    el: null,
603    create: function() {
604      if(this.el)
605        return;
606
607      this.el = t.createChild('div', {
608        className: 'player-info',
609        innerHTML: (t.player.options.type == 'audio' ? '<div class="preview"></div>' : '') + '<div class="controller"></div><div class="playlist"></div>'
610      }, 'after');
611
612      preview.el = this.el.child(".preview");
613      playlist.el = this.el.child(".playlist");
614      controller.el = this.el.child(".controller");
615    },
616    hide: function() {
617      var el = this.el
618      el.addClass('hide');
619      window.setTimeout(function() {
620        el.removeClass('show hide')
621      }, 300);
622    }
623  }
624
625  var playlist = {
626    el: null,
627    data: [],
628    index: -1,
629    errnum: 0,
630    add: function(group, list) {
631      var that = this
632      list.forEach(function(item, i) {
633        item.group = group;
634        item.name = item.name || item.title || 'Meida name';
635        item.artist = item.artist || item.author || 'Anonymous';
636        item.cover = item.cover || item.pic;
637        item.type = item.type || 'normal';
638
639        that.data.push(item);
640      });
641    },
642    clear: function() {
643      this.data = []
644      this.el.innerHTML = ""
645
646      if(this.index !== -1) {
647        this.index = -1
648        t.player.fetch()
649      }
650    },
651    create: function() {
652      var el = this.el
653
654      this.data.map(function(item, index) {
655        if(item.el)
656          return
657
658        var id = 'list-' + t.player._id + '-'+item.group
659        var tab = $('#' + id)
660        if(!tab) {
661          tab = el.createChild('div', {
662            id: id,
663            className: t.player.group ?'tab':'',
664            innerHTML: '<ol></ol>',
665          })
666          if(t.player.group) {
667            tab.attr('data-title', t.player.options.rawList[item.group]['title'])
668                .attr('data-id', t.player._id)
669          }
670        }
671
672        item.el = tab.child('ol').createChild('li', {
673          title: item.name + ' - ' + item.artist,
674          innerHTML: '<span class="info"><span>'+item.name+'</span><span>'+item.artist+'</span></span>',
675          onclick: function(event) {
676            var current = event.currentTarget;
677            if(playlist.index === index && progress.el) {
678              if(source.paused) {
679                t.player.play();
680              } else {
681                t.player.seek(source.duration * progress.percent(event, current))
682              }
683              return;
684            }
685            t.player.switch(index);
686            t.player.play();
687          }
688        })
689
690        return item
691      })
692
693      tabFormat()
694    },
695    current: function() {
696      return this.data[this.index]
697    },
698    scroll: function() {
699      var item = this.current()
700      var li = this.el.child('li.active')
701      li && li.removeClass('active')
702      var tab = this.el.child('.tab.active')
703      tab && tab.removeClass('active')
704      li = this.el.find('.nav li')[item.group]
705      li && li.addClass('active')
706      tab = this.el.find('.tab')[item.group]
707      tab && tab.addClass('active')
708
709      pageScroll(item.el, item.el.offsetTop)
710
711      return this
712    },
713    title: function() {
714      if(source.paused)
715        return
716
717      var current = this.current()
718      document.title = 'Now Playing...' + current['name'] + ' - ' + current['artist'] + ' | ' + originTitle;
719    },
720    error: function() {
721      var current = this.current()
722      current.el.removeClass('current').addClass('error')
723      current.error = true
724      this.errnum++
725    }
726  }
727
728  var lyrics = {
729    el: null,
730    data: null,
731    index: 0,
732    create: function(box) {
733      var current = playlist.index
734      var that = this
735      var raw = playlist.current().lrc
736
737      var callback = function(body) {
738        if(current !== playlist.index)
739          return;
740
741        that.data = that.parse(body)
742
743        var lrc = ''
744        that.data.forEach(function(line, index) {
745          lrc += '<p'+(index===0?' class="current"':'')+'>'+line[1]+'</p>';
746        })
747
748        that.el = box.createChild('div', {
749          className: 'inner',
750          innerHTML: lrc
751        }, 'replace')
752
753        that.index = 0;
754      }
755
756      if(raw.startsWith('http'))
757        this.fetch(raw, callback)
758      else
759        callback(raw)
760    },
761    update: function(currentTime) {
762      if(!this.data)
763        return
764
765      if (this.index > this.data.length - 1 || currentTime < this.data[this.index][0] || (!this.data[this.index + 1] || currentTime >= this.data[this.index + 1][0])) {
766        for (var i = 0; i < this.data.length; i++) {
767          if (currentTime >= this.data[i][0] && (!this.data[i + 1] || currentTime < this.data[i + 1][0])) {
768            this.index = i;
769            var y = -(this.index-1);
770            this.el.style.transform = 'translateY('+y+'rem)';
771            this.el.style.webkitTransform = 'translateY('+y+'rem)';
772            this.el.getElementsByClassName('current')[0].removeClass('current');
773            this.el.getElementsByTagName('p')[i].addClass('current');
774          }
775        }
776      }
777    },
778    parse: function(lrc_s) {
779      if (lrc_s) {
780          lrc_s = lrc_s.replace(/([^\]^\n])\[/g, function(match, p1){return p1 + '\n['});
781          const lyric = lrc_s.split('\n');
782          var lrc = [];
783          const lyricLen = lyric.length;
784          for (var i = 0; i < lyricLen; i++) {
785              // match lrc time
786              const lrcTimes = lyric[i].match(/\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g);
787              // match lrc text
788              const lrcText = lyric[i]
789                  .replace(/.*\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/g, '')
790                  .replace(/<(\d{2}):(\d{2})(\.(\d{2,3}))?>/g, '')
791                  .replace(/^\s+|\s+$/g, '');
792
793              if (lrcTimes) {
794                  // handle multiple time tag
795                  const timeLen = lrcTimes.length;
796                  for (var j = 0; j < timeLen; j++) {
797                      const oneTime = /\[(\d{2}):(\d{2})(\.(\d{2,3}))?]/.exec(lrcTimes[j]);
798                      const min2sec = oneTime[1] * 60;
799                      const sec2sec = parseInt(oneTime[2]);
800                      const msec2sec = oneTime[4] ? parseInt(oneTime[4]) / ((oneTime[4] + '').length === 2 ? 100 : 1000) : 0;
801                      const lrcTime = min2sec + sec2sec + msec2sec;
802                      lrc.push([lrcTime, lrcText]);
803                  }
804              }
805          }
806          // sort by time
807          lrc = lrc.filter(function(item){return item[1]});
808          lrc.sort(function(a, b){return a[0] - b[0]});
809          return lrc;
810      } else {
811          return [];
812      }
813    },
814    fetch: function(url, callback) {
815      fetch(url)
816          .then(function(response) {
817            return response.text()
818          }).then(function(body) {
819            callback(body)
820          }).catch(function(ex) {})
821    }
822  }
823
824  var preview = {
825    el: null,
826    create: function () {
827      var current = playlist.current()
828
829      this.el.innerHTML = '<div class="cover"><div class="disc"><img src="'+(current.cover)+'" class="blur" /></div></div>'
830      + '<div class="info"><h4 class="title">
830'+current.name+'</h4><span>'+current.artist+'</span>'
831      + '<div class="lrc"></div></div>'
832
833      this.el.child('.cover').addEventListener('click', t.player.options.events['play-pause'])
834
835      lyrics.create(this.el.child('.lrc'))
836    }
837  }
838
839  var progress = {
840    el: null,
841    bar: null,
842    create: function() {
843      var current = playlist.current().el
844
845      if(current) {
846
847        if(this.el) {
848          this.el.parentNode.removeClass('current')
849            .removeEventListener(utils.nameMap.dragStart, this.drag)
850          this.el.remove()
851        }
852
853        this.el = current.createChild('div', {
854          className: 'progress'
855        })
856
857        this.el.attr('data-dtime', utils.secondToTime(0))
858
859        this.bar = this.el.createChild('div', {
860          className: 'bar',
861        })
862
863        current.addClass('current')
864
865        current.addEventListener(utils.nameMap.dragStart, this.drag);
866
867        playlist.scroll()
868      }
869    },
870    update: function(percent) {
871      this.bar.width(Math.floor(percent * 100) + '%')
872      this.el.attr('data-ptime', utils.secondToTime(percent * source.duration))
873    },
874    seeking: function(type) {
875      if(type)
876        this.el.addClass('seeking')
877      else
878        this.el.removeClass('seeking')
879    },
880    percent: function(e, el) {
881      var percentage = ((e.clientX || e.changedTouches[0].clientX) - el.left()) / el.width();
882      percentage = Math.max(percentage, 0);
883      return Math.min(percentage, 1)
884    },
885    drag: function(e) {
886      e.preventDefault()
887
888      var current = playlist.current().el
889
890      var thumbMove = function(e) {
891        e.preventDefault()
892        var percentage = progress.percent(e, current)
893        progress.update(percentage)
894        lyrics.update(percentage * source.duration);
895      };
896
897      var thumbUp = function(e) {
898        e.preventDefault()
899        current.removeEventListener(utils.nameMap.dragEnd, thumbUp)
900        current.removeEventListener(utils.nameMap.dragMove, thumbMove)
901        var percentage = progress.percent(e, current)
902        progress.update(percentage)
903        t.player.seek(percentage * source.duration)
904        source.disableTimeupdate = false
905        progress.seeking(false)
906      };
907
908      source.disableTimeupdate = true
909      progress.seeking(true)
910      current.addEventListener(utils.nameMap.dragMove, thumbMove)
911      current.addEventListener(utils.nameMap.dragEnd, thumbUp)
912    }
913  }
914
915  var controller = {
916    el: null,
917    btns: {},
918    step: 'next',
919    create: function () {
920      if(!t.player.options.controls)
921        return
922
923      var that = this
924      t.player.options.controls.forEach(function(item) {
925        if(that.btns[item])
926          return;
927
928        var opt = {
929          onclick: function(event){
930            that.events[item] ? that.events[item](event) : t.player.options.events[item](event)
931          }
932        }
933
934        switch(item) {
935          case 'volume':
936            opt.className = ' ' + (source.muted ? 'off' : 'on')
937            opt.innerHTML = '<div class="bar"></div>'
938            opt['on'+utils.nameMap.dragStart] = that.events['volume']
939            opt.onclick = null
940            break;
941          case 'mode':
942            opt.className = ' ' + t.player.options.mode
943            break;
944          default:
945            opt.className = ''
946            break;
947        }
948
949        opt.className = item + opt.className + ' btn'
950
951        that.btns[item] = that.el.createChild('div', opt)
952      })
953
954      that.btns['volume'].bar = that.btns['volume'].child('.bar')
955    },
956    events: {
957      mode: function(e) {
958        switch(t.player.options.mode) {
959          case 'loop':
960            t.player.options.mode = 'random'
961            break;
962          case 'random':
963            t.player.options.mode = 'order'
964            break;
965          default:
966            t.player.options.mode = 'loop'
967        }
968
969        controller.btns['mode'].className = 'mode ' + t.player.options.mode + ' btn'
970        store.set('_PlayerMode', t.player.options.mode)
971      },
972      volume: function(e) {
973        e.preventDefault()
974
975        var current = e.currentTarget
976
977        var drag = false
978
979        var thumbMove = function(e) {
980          e.preventDefault()
981          t.player.volume(controller.percent(e, current))
982          drag = true
983        };
984
985        var thumbUp = function(e) {
986          e.preventDefault()
987          current.removeEventListener(utils.nameMap.dragEnd, thumbUp)
988          current.removeEventListener(utils.nameMap.dragMove, thumbMove)
989          if(drag) {
990            t.player.muted()
991            t.player.volume(controller.percent(e, current))
992          } else {
993            if (source.muted) {
994              t.player.muted()
995              t.player.volume(source.volume)
996            } else {
997              t.player.muted('muted')
998              controller.update(0)
999            }
1000          }
1001        };
1002
1003        current.addEventListener(utils.nameMap.dragMove, thumbMove)
1004        current.addEventListener(utils.nameMap.dragEnd, thumbUp)
1005      },
1006      backward: function(e) {
1007        controller.step = 'prev'
1008        t.player.mode()
1009      },
1010      forward: function(e) {
1011        controller.step = 'next'
1012        t.player.mode()
1013      },
1014    },
1015    update: function(percent) {
1016      controller.btns['volume'].className = 'volume '+ (!source.muted && percent > 0? 'on' :'off') +' btn'
1017      controller.btns['volume'].bar.width(Math.floor(percent * 100) + '%')
1018    },
1019    percent: function(e, el) {
1020      var percentage = ((e.clientX || e.changedTouches[0].clientX) - el.left()) / el.width();
1021      percentage = Math.max(percentage, 0);
1022      return Math.min(percentage, 1);
1023    }
1024  }
1025
1026  var events = {
1027    onerror: function() {
1028      playlist.error()
1029      t.player.mode()
1030    },
1031    ondurationchange: function() {
1032      if (source.duration !== 1) {
1033        progress.el.attr('data-dtime', utils.secondToTime(source.duration))
1034      }
1035    },
1036    onloadedmetadata: function() {
1037      t.player.seek(0)
1038      progress.el.attr('data-dtime', utils.secondToTime(source.duration))
1039    },
1040    onplay: function() {
1041      t.parentNode.addClass('playing')
1042      showtip(this.attr('title'))
1043      NOWPLAYING = t
1044    },
1045    onpause: function() {
1046      t.parentNode.removeClass('playing')
1047      NOWPLAYING = null
1048    },
1049    ontimeupdate: function() {
1050      if(!this.disableTimeupdate) {
1051        progress.update(this.currentTime / this.duration)
1052        lyrics.update(this.currentTime)
1053      }
1054    },
1055    onended: function(argument) {
1056      t.player.mode()
1057      t.player.play()
1058    }
1059  }
1060
1061  var buttons = {
1062    el: {},
1063    create: function() {
1064      if(!t.player.options.btns)
1065        return
1066
1067      var that = this
1068      t.player.options.btns.forEach(function(item) {
1069        if(that.el[item])
1070          return;
1071
1072        that.el[item] = t.createChild('div', {
1073            className: item + ' btn',
1074            onclick: function(event){
1075              t.player.fetch().then(function() {
1076                t.player.options.events[item](event)
1077              })
1078            }
1079          });
1080      });
1081    }
1082  }
1083
1084  var init = function(config) {
1085    if(t.player.created)
1086      return;
1087
1088
1089    t.player.options = Object.assign(option, config);
1090    t.player.options.mode = store.get('_PlayerMode') || t.player.options.mode
1091
1092    // 初始化button、controls以及click事件
1093    buttons.create()
1094
1095    // 初始化audio or video
1096    source = t.createChild(t.player.options.type, events);
1097    // 初始化播放列表、预览、控件按钮等
1098    info.create();
1099
1100    t.parentNode.addClass(t.player.options.type)
1101
1102    t.player.created = true;
1103  }
1104
1105  init(config)
1106
1107  return t;
1108}
1109var statics = CONFIG.statics.indexOf('//') > 0 ? CONFIG.statics : CONFIG.root
1110var scrollAction = { x: 'undefined', y: 'undefined' };
1111var diffY = 0;
1112var originTitle, titleTime;
1113
1114const BODY = document.getElementsByTagName('body')[0];
1115const HTML = document.documentElement;
1116const Container = $('#container');
1117const loadCat = $('#loading');
1118const siteNav = $('#nav');
1119const siteHeader = $('#header');
1120const menuToggle = siteNav.child('.toggle');
1121const quickBtn = $('#quick');
1122const sideBar = $('#sidebar');
1123const siteBrand = $('#brand');
1124var toolBtn = $('#tool'), toolPlayer, backToTop, goToComment, showContents;
1125var siteSearch = $('#search');
1126var siteNavHeight, headerHightInner, headerHight;
1127var oWinHeight = window.innerHeight;
1128var oWinWidth = window.innerWidth;
1129var LOCAL_HASH = 0, LOCAL_URL = window.location.href;
1130var pjax;
1131const lazyload = lozad('img, [data-background-image]', {
1132    loaded: function(el) {
1133        el.addClass('lozaded');
1134    }
1135})
1136
1137const Loader = {
1138  timer: null,
1139  lock: false,
1140  show: function() {
1141    clearTimeout(this.timer);
1142    document.body.removeClass('loaded');
1143    loadCat.attr('style', 'display:block');
1144    Loader.lock = false;
1145  },
1146  hide: function(sec) {
1147    if(!CONFIG.loader.start)
1148      sec = -1
1149    this.timer = setTimeout(this.vanish, sec||3000);
1150  },
1151  vanish: function() {
1152    if(Loader.lock)
1153      return;
1154    if(CONFIG.loader.start)
1155      transition(loadCat, 0)
1156    document.body.addClass('loaded');
1157    Loader.lock = true;
1158  }
1159}
1160
1161const changeTheme = function(type) {
1162  var btn = $('.theme .ic')
1163  if(type == 'dark') {
1164    HTML.attr('data-theme', type);
1165    btn.removeClass('i-sun')
1166    btn.addClass('i-moon')
1167  } else {
1168    HTML.attr('data-theme', null);
1169    btn.removeClass('i-moon');
1170    btn.addClass('i-sun');
1171  }
1172}
1173
1174const changeMetaTheme = function(color) {
1175  if(HTML.attr('data-theme') == 'dark')
1176    color = '#222'
1177
1178  $('meta[name="theme-color"]').attr('content', color);
1179}
1180
1181const themeColorListener = function () {
1182  window.matchMedia('(prefers-color-scheme: dark)').addListener(function(mediaQueryList) {
1183    if(mediaQueryList.matches){
1184      changeTheme('dark');
1185    } else {
1186      changeTheme();
1187    }
1188  });
1189
1190  var t = store.get('theme');
1191  if(t) {
1192    changeTheme(t);
1193  } else {
1194    if(CONFIG.darkmode) {
1195      changeTheme('dark');
1196    }
1197  }
1198
1199  $('.theme').addEventListener('click', function(event) {
1200    var btn = event.currentTarget.child('.ic')
1201
1202    var neko = BODY.createChild('div', {
1203      id: 'neko',
1204      innerHTML: '<div class="planet"><div class="sun"></div><div class="moon"></div></div><div class="body"><div class="face"><section class="eyes left"><span class="pupil"></span></section><section class="eyes right"><span class="pupil"></span></section><span class="nose"></span></div></div>'
1205    });
1206
1207    var hideNeko = function() {
1208        transition(neko, {
1209          delay: 2500,
1210          opacity: 0
1211        }, function() {
1212          BODY.removeChild(neko)
1213        });
1214    }
1215
1216    if(btn.hasClass('i-sun')) {
1217      var c = function() {
1218          neko.addClass('dark');
1219          changeTheme('dark');
1220          store.set('theme', 'dark');
1221          hideNeko();
1222        }
1223    } else {
1224      neko.addClass('dark');
1225      var c = function() {
1226          neko.removeClass('dark');
1227          changeTheme();
1228          store.set('theme', 'light');
1229          hideNeko();
1230        }
1231    }
1232    transition(neko, 1, function() {
1233      setTimeout(c, 210)
1234    })
1235  });
1236}
1237
1238const visibilityListener = function () {
1239  document.addEventListener('visibilitychange', function() {
1240    switch(document.visibilityState) {
1241      case 'hidden':
1242        $('[rel="icon"]').attr('href', statics + CONFIG.favicon.hidden);
1243        document.title = LOCAL.favicon.hide;
1244        if(CONFIG.loader.switch)
1245          Loader.show()
1246        clearTimeout(titleTime);
1247      break;
1248      case 'visible':
1249        $('[rel="icon"]').attr('href', statics + CONFIG.favicon.normal);
1250        document.title = LOCAL.favicon.show;
1251        if(CONFIG.loader.switch)
1252          Loader.hide(1000)
1253        titleTime = setTimeout(function () {
1254          document.title = originTitle;
1255        }, 2000);
1256      break;
1257    }
1258  });
1259}
1260
1261const showtip = function(msg) {
1262  if(!msg)
1263    return
1264
1265  var tipbox = BODY.createChild('div', {
1266    innerHTML: msg,
1267    className: 'tip'
1268  });
1269
1270  setTimeout(function() {
1271    tipbox.addClass('hide')
1272    setTimeout(function() {
1273      BODY.removeChild(tipbox);
1274    }, 300);
1275  }, 3000);
1276}
1277
1278const resizeHandle = function (event) {
1279  siteNavHeight = siteNav.height();
1280  headerHightInner = siteHeader.height();
1281  headerHight = headerHightInner + $('#waves').height();
1282
1283  if(oWinWidth != window.innerWidth)
1284    sideBarToggleHandle(null, 1);
1285
1286  oWinHeight = window.innerHeight;
1287  oWinWidth = window.innerWidth;
1288  sideBar.child('.panels').height(oWinHeight + 'px')
1289}
1290
1291const scrollHandle = function (event) {
1292  var winHeight = window.innerHeight;
1293  var docHeight = getDocHeight();
1294  var contentVisibilityHeight = docHeight > winHeight ? docHeight - winHeight : document.body.scrollHeight - winHeight;
1295  var SHOW = window.pageYOffset > headerHightInner;
1296  var startScroll = window.pageYOffset > 0;
1297
1298  if (SHOW) {
1299    changeMetaTheme('#FFF');
1300  } else {
1301    changeMetaTheme('#222');
1302  }
1303
1304  siteNav.toggleClass('show', SHOW);
1305  toolBtn.toggleClass('affix', startScroll);
1306  siteBrand.toggleClass('affix', startScroll);
1307  sideBar.toggleClass('affix', window.pageYOffset > headerHight && document.body.offsetWidth > 991);
1308
1309  if (typeof scrollAction.y == 'undefined') {
1310    scrollAction.y = window.pageYOffset;
1311    //scrollAction.x = Container.scrollLeft;
1312    //scrollAction.y = Container.scrollTop;
1313  }
1314  //var diffX = scrollAction.x - Container.scrollLeft;
1315  diffY = scrollAction.y - window.pageYOffset;
1316
1317  //if (diffX < 0) {
1318  // Scroll right
1319  //} else if (diffX > 0) {
1320  // Scroll left
1321  //} else
1322  if (diffY < 0) {
1323    // Scroll down
1324    siteNav.removeClass('up')
1325    siteNav.toggleClass('down', SHOW);
1326  } else if (diffY > 0) {
1327    // Scroll up
1328    siteNav.removeClass('down')
1329    siteNav.toggleClass('up', SHOW);
1330  } else {
1331    // First scroll event
1332  }
1333  //scrollAction.x = Container.scrollLeft;
1334  scrollAction.y = window.pageYOffset;
1335
1336  var scrollPercent = Math.round(Math.min(100 * window.pageYOffset / contentVisibilityHeight, 100)) + '%';
1337  backToTop.child('span').innerText = scrollPercent;
1338  $('.percent').width(scrollPercent);
1339}
1340
1341const pagePosition = function() {
1342  if(CONFIG.auto_scroll)
1343    store.set(LOCAL_URL, scrollAction.y)
1344}
1345
1346const positionInit = function(comment) {
1347  var anchor = window.location.hash
1348  var target = null;
1349  if(LOCAL_HASH) {
1350    store.del(LOCAL_URL);
1351    return
1352  }
1353
1354  if(anchor)
1355    target = $(decodeURI(anchor))
1356  else {
1357    target = CONFIG.auto_scroll ? parseInt(store.get(LOCAL_URL)) : 0
1358  }
1359
1360  if(target) {
1361    pageScroll(target);
1362    LOCAL_HASH = 1;
1363  }
1364
1365  if(comment && anchor && !LOCAL_HASH) {
1366    pageScroll(target);
1367    LOCAL_HASH = 1;
1368  }
1369
1370}
1371
1372const clipBoard = function(str, callback) {
1373  var ta = BODY.createChild('textarea', {
1374    style: {
1375      top: window.scrollY + 'px', // Prevent page scrolling
1376      position: 'absolute',
1377      opacity: '0'
1378    },
1379    readOnly: true,
1380    value: str
1381  });
1382
1383  const selection = document.getSelection();
1384  const selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false;
1385  ta.select();
1386  ta.setSelectionRange(0, str.length);
1387  ta.readOnly = false;
1388  var result = document.execCommand('copy');
1389  callback && callback(result);
1390  ta.blur(); // For iOS
1391  if (selected) {
1392    selection.removeAllRanges();
1393    selection.addRange(selected);
1394  }
1395  BODY.removeChild(ta);
1396}
1397
1398const sideBarToggleHandle = function (event, force) {
1399  if(sideBar.hasClass('on')) {
1400    sideBar.removeClass('on');
1401    menuToggle.removeClass('close');
1402    if(force) {
1403      sideBar.style = '';
1404    } else {
1405      transition(sideBar, 'slideRightOut');
1406    }
1407  } else {
1408    if(force) {
1409      sideBar.style = '';
1410    } else {
1411      transition(sideBar, 'slideRightIn', function () {
1412          sideBar.addClass('on');
1413          menuToggle.addClass('close');
1414        });
1415    }
1416  }
1417}
1418
1419const sideBarTab = function () {
1420  var sideBarInner = sideBar.child('.inner');
1421  var panels = sideBar.find('.panel');
1422
1423  if(sideBar.child('.tab')) {
1424    sideBarInner.removeChild(sideBar.child('.tab'));
1425  }
1426
1427  var list = document.createElement('ul'), active = 'active';
1428  list.className = 'tab';
1429
1430  ['contents', 'related', 'overview'].forEach(function (item) {
1431    var element = sideBar.child('.panel.' + item)
1432
1433    if(element.innerHTML.replace(/(^\s*)|(\s*$)/g, "").length < 1) {
1434      if(item == 'contents') {
1435        showContents.display("none")
1436      }
1437      return;
1438    }
1439
1440    if(item == 'contents') {
1441      showContents.display("")
1442    }
1443
1444    var tab = document.createElement('li')
1445    var span = document.createElement('span')
1446    var text = document.createTextNode(element.attr('data-title'));
1447    span.appendChild(text);
1448    tab.appendChild(span);
1449    tab.addClass(item + ' item');
1450
1451    if(active) {
1452      element.addClass(active);
1453      tab.addClass(active);
1454    } else {
1455      element.removeClass('active');
1456    }
1457
1458    tab.addEventListener('click', function (element) {
1459      var target = event.currentTarget;
1460      if (target.hasClass('active'))
1461        return;
1462
1463      sideBar.find('.tab .item').forEach(function (element) {
1464        element.removeClass('active')
1465      });
1466
1467      sideBar.find('.panel').forEach(function (element) {
1468        element.removeClass('active')
1469      });
1470
1471      sideBar.child('.panel.' + target.className.replace(' item', '')).addClass('active');
1472
1473      target.addClass('active');
1474    });
1475
1476    list.appendChild(tab);
1477    active = '';
1478  });
1479
1480  if (list.childNodes.length > 1) {
1481    sideBarInner.insertBefore(list, sideBarInner.childNodes[0]);
1482    sideBar.child('.panels').style.paddingTop = ''
1483  } else {
1484    sideBar.child('.panels').style.paddingTop = '.625rem'
1485  }
1486}
1487
1488const sidebarTOC = function () {
1489  var navItems = $.all('.contents li');
1490
1491  if (navItems.length < 1) {
1492    return;
1493  }
1494
1495  var sections = Array.prototype.slice.call(navItems) || [];
1496  var activeLock = null;
1497
1498  sections = sections.map(function (element, index) {
1499    var link = element.child('a.toc-link');
1500    var anchor = $(decodeURI(link.attr('href')));
1501    if(!anchor)
1502      return
1503    var alink = anchor.child('a.anchor');
1504
1505    var anchorScroll = function (event) {
1506      event.preventDefault();
1507      var target = $(decodeURI(event.currentTarget.attr('href')));
1508
1509      activeLock = index;
1510      pageScroll(target, null, function() {
1511          activateNavByIndex(index)
1512          activeLock = null
1513        })
1514    };
1515
1516    // TOC item animation navigate.
1517    link.addEventListener('click', anchorScroll);
1518    alink && alink.addEventListener('click', function(event) {
1519      anchorScroll(event)
1520      clipBoard(CONFIG.hostname + '/' + LOCAL.path + event.currentTarget.attr('href'))
1521    });
1522    return anchor;
1523  });
1524
1525  var tocElement = sideBar.child('.contents.panel');
1526
1527  var activateNavByIndex = function (index, lock) {
1528    var target = navItems[index]
1529
1530    if (!target)
1531      return;
1532
1533    if (target.hasClass('current')) {
1534      return;
1535    }
1536
1537    $.each('.toc .active', function (element) {
1538      element && element.removeClass('active current');
1539    });
1540
1541    sections.forEach(function (element) {
1542      element && element.removeClass('active');
1543    });
1544
1545    target.addClass('active current');
1546    sections[index] && sections[index].addClass('active');
1547
1548    var parent = target.parentNode;
1549
1550    while (!parent.matches('.contents')) {
1551      if (parent.matches('li')) {
1552        parent.addClass('active');
1553        var t = $(parent.child('a.toc-link').attr('href'))
1554        if(t) {
1555          t.addClass('active');
1556        }
1557      }
1558      parent = parent.parentNode;
1559    }
1560    // Scrolling to center active TOC element if TOC content is taller then viewport.
1561    if(getComputedStyle(sideBar).display != 'none' && tocElement.hasClass('active')) {
1562      pageScroll(tocElement, target.offsetTop- (tocElement.offsetHeight / 4))
1563    }
1564  }
1565
1566  var findIndex = function(entries) {
1567    var index = 0;
1568    var entry = entries[index];
1569
1570    if (entry.boundingClientRect.top > 0) {
1571      index = sections.indexOf(entry.target);
1572      return index === 0 ? 0 : index - 1;
1573    }
1574    for (; index < entries.length; index++) {
1575      if (entries[index].boundingClientRect.top <= 0) {
1576        entry = entries[index];
1577      } else {
1578        return sections.indexOf(entry.target);
1579      }
1580    }
1581    return sections.indexOf(entry.target);
1582  }
1583
1584  var createIntersectionObserver = function() {
1585    if (!window.IntersectionObserver)
1586      return
1587
1588    var observer = new IntersectionObserver(function (entries, observe) {
1589      var index = findIndex(entries) + (diffY < 0? 1 : 0);
1590      if(activeLock === null) {
1591        activateNavByIndex(index);
1592      }
1593    }, {
1594      rootMargin: '0px 0px -100% 0px',
1595      threshold: 0
1596    });
1597
1598    sections.forEach(function (element) {
1599      element && observer.observe(element);
1600    });
1601  }
1602
1603  createIntersectionObserver();
1604}
1605
1606const backToTopHandle = function () {
1607  pageScroll(0);
1608}
1609
1610const goToBottomHandle = function () {
1611  pageScroll(parseInt(Container.height()));
1612}
1613
1614const goToCommentHandle = function () {
1615  pageScroll($('#comments'));
1616}
1617
1618const menuActive = function () {
1619  $.each('.menu .item:not(.title)', function (element) {
1620    var target = element.child('a[href]');
1621    var parentItem = element.parentNode.parentNode;
1622    if (!target) return;
1623    var isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', '');
1624    var isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname);
1625    var active = target.hostname === location.hostname && (isSamePath || isSubPath)
1626    element.toggleClass('active', active);
1627    if(element.parentNode.child('.active') && parentItem.hasClass('dropdown')) {
1628      parentItem.removeClass('active').addClass('expand');
1629    } else {
1630      parentItem.removeClass('expand');
1631    }
1632  });
1633}
1634const cardActive = function() {
1635  if(!$('.index.wrap'))
1636    return
1637
1638  if (!window.IntersectionObserver) {
1639    $.each('.index.wrap article.item, .index.wrap section.item', function(article) {
1640      if( article.hasClass("show") === false){
1641          article.addClass("show");
1642      }
1643    })
1644  } else {
1645    var io = new IntersectionObserver(function(entries) {
1646
1647        entries.forEach(function(article) {
1648          if (article.target.hasClass("show")) {
1649            io.unobserve(article.target)
1650          } else {
1651            if (article.isIntersecting || article.intersectionRatio > 0) {
1652              article.target.addClass("show");
1653              io.unobserve(article.target);
1654            }
1655          }
1656        })
1657    }, {
1658        root: null,
1659        threshold: [0.3]
1660    });
1661
1662    $.each('.index.wrap article.item, .index.wrap section.item', function(article) {
1663      io.observe(article)
1664    })
1665
1666    $('.index.wrap .item:first-child').addClass("show")
1667  }
1668
1669  $.each('.cards .item', function(element, index) {
1670    ['mouseenter', 'touchstart'].forEach(function(item){
1671      element.addEventListener(item, function(event) {
1672        if($('.cards .item.active')) {
1673          $('.cards .item.active').removeClass('active')
1674        }
1675        element.addClass('active')
1676      })
1677    });
1678    ['mouseleave'].forEach(function(item){
1679      element.addEventListener(item, function(event) {
1680        element.removeClass('active')
1681      })
1682    });
1683  });
1684}
1685
1686const registerExtURL = function() {
1687  $.each('span.exturl', function(element) {
1688      var link = document.createElement('a');
1689      // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
1690      link.href = decodeURIComponent(atob(element.dataset.url).split('').map(function(c) {
1691        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
1692      }).join(''));
1693      link.rel = 'noopener external nofollow noreferrer';
1694      link.target = '_blank';
1695      link.className = element.className;
1696      link.title = element.title || element.innerText;
1697      link.innerHTML = element.innerHTML;
1698      if(element.dataset.backgroundImage) {
1699        link.dataset.backgroundImage = element.dataset.backgroundImage;
1700      }
1701      element.parentNode.replaceChild(link, element);
1702    });
1703}
1704
1705const postFancybox = function(p) {
1706  if($(p + ' .md img')) {
1707    vendorCss('fancybox');
1708    vendorJs('fancybox', function() {
1709      var q = jQuery.noConflict();
1710
1711      $.each(p + ' p.gallery', function(element) {
1712        var box = document.createElement('div');
1713        box.className = 'gallery';
1714        box.attr('data-height', element.attr('data-height')||220);
1715
1716        box.innerHTML = element.innerHTML.replace(/<br>/g, "")
1717
1718        element.parentNode.insertBefore(box, element);
1719        element.remove();
1720      });
1721
1722      $.each(p + ' .md img:not(.emoji):not(.vemoji)', function(element) {
1723        var $image = q(element);
1724        var info, captionClass = 'image-info';
1725        if(!$image.is('a img')) {
1726          var imageLink = $image.attr('data-src') || $image.attr('src');
1727          $image.data('safe-src', imageLink)
1728          var $imageWrapLink = $image.wrap('<a class="fancybox" href="'+imageLink+'" itemscope itemtype="http://schema.org/ImageObject" itemprop="url"></a>').parent('a');
1729          if (!$image.is('.gallery img')) {
1730            $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default');
1731          } else {
1732            captionClass = 'jg-caption'
1733          }
1734        }
1735        if(info = element.attr('title')) {
1736          $imageWrapLink.attr('data-caption', info);
1737          var para = document.createElement('span');
1738          var txt = document.createTextNode(info);
1739          para.appendChild(txt);
1740          para.addClass(captionClass);
1741          element.insertAfter(para);
1742        }
1743      });
1744
1745      $.each(p + ' div.gallery', function (el, i) {
1746        q(el).justifiedGallery({rowHeight: q(el).data('height')||120, rel: 'gallery-' + i}).on('jg.complete', function () {
1747          q(this).find('a').each(function(k, ele) {
1748            ele.attr('data-fancybox', 'gallery-' + i);
1749          });
1750        });
1751      });
1752
1753      q.fancybox.defaults.hash = false;
1754      q(p + ' .fancybox').fancybox({
1755        loop   : true,
1756        helpers: {
1757          overlay: {
1758            locked: false
1759          }
1760        }
1761      });
1762    }, window.jQuery);
1763  }
1764}
1765
1766const postBeauty = function () {
1767  loadComments();
1768
1769  if(!$('.md'))
1770    return
1771
1772  postFancybox('.post.block');
1773
1774  $('.post.block').oncopy = function(event) {
1775    showtip(LOCAL.copyright)
1776
1777    if(LOCAL.nocopy) {
1778      event.preventDefault()
1779      return
1780    }
1781
1782    var copyright = $('#copyright')
1783    if(window.getSelection().toString().length > 30 && copyright) {
1784      event.preventDefault();
1785      var author = "# " + copyright.child('.author').innerText
1786      var link = "# " + copyright.child('.link').innerText
1787      var license = "# " + copyright.child('.license').innerText
1788      var htmlData = author + "<br>" + link + "<br>" + license + "<br><br>" + window.getSelection().toString().replace(/\r\n/g, "<br>");;
1789      var textData = author + "\n" + link + "\n" + license + "\n\n" + window.getSelection().toString().replace(/\r\n/g, "\n");
1790      if (event.clipboardData) {
1791          event.clipboardData.setData("text/html", htmlData);
1792          event.clipboardData.setData("text/plain", textData);
1793      } else if (window.clipboardData) {
1794          return window.clipboardData.setData("text", textData);
1795      }
1796    }
1797  }
1798
1799  $.each('li ruby', function(element) {
1800    var parent = element.parentNode;
1801    if(element.parentNode.tagName != 'LI') {
1802      parent = element.parentNode.parentNode;
1803    }
1804    parent.addClass('ruby');
1805  })
1806
1807  $.each('ol[start]', function(element) {
1808    element.style.counterReset = "counter " + parseInt(element.attr('start') - 1)
1809  })
1810
1811  $.each('.md table', function (element) {
1812    element.wrap({
1813      className: 'table-container'
1814    });
1815  });
1816
1817  $.each('.highlight > .table-container', function (element) {
1818    element.className = 'code-container'
1819  });
1820
1821  $.each('figure.highlight', function (element) {
1822
1823    var code_container = element.child('.code-container');
1824    var caption = element.child('figcaption');
1825
1826    element.insertAdjacentHTML('beforeend', '<div class="operation"><span class="breakline-btn"><i class="ic i-align-left"></i></span><span class="copy-btn"><i class="ic i-clipboard"></i></span><span class="fullscreen-btn"><i class="ic i-expand"></i></span></div>');
1827
1828    var copyBtn = element.child('.copy-btn');
1829    if(LOCAL.nocopy) {
1830      copyBtn.remove()
1831    } else {
1832      copyBtn.addEventListener('click', function (event) {
1833        var target = event.currentTarget;
1834        var comma = '', code = '';
1835        code_container.find('pre').forEach(function(line) {
1836          code += comma + line.innerText;
1837          comma = '\n'
1838        })
1839
1840        clipBoard(code, function(result) {
1841          target.child('.ic').className = result ? 'ic i-check' : 'ic i-times';
1842          target.blur();
1843          showtip(LOCAL.copyright);
1844        })
1845      });
1846      copyBtn.addEventListener('mouseleave', function (event) {
1847        setTimeout(function () {
1848          event.target.child('.ic').className = 'ic i-clipboard';
1849        }, 1000);
1850      });
1851    }
1852
1853    var breakBtn = element.child('.breakline-btn');
1854    breakBtn.addEventListener('click', function (event) {
1855      var target = event.currentTarget;
1856      if (element.hasClass('breakline')) {
1857        element.removeClass('breakline');
1858        target.child('.ic').className = 'ic i-align-left';
1859      } else {
1860        element.addClass('breakline');
1861        target.child('.ic').className = 'ic i-align-justify';
1862      }
1863    });
1864
1865    var fullscreenBtn = element.child('.fullscreen-btn');
1866    var removeFullscreen = function() {
1867      element.removeClass('fullscreen');
1868      element.scrollTop = 0;
1869      BODY.removeClass('fullscreen');
1870      fullscreenBtn.child('.ic').className = 'ic i-expand';
1871    }
1872    var fullscreenHandle = function(event) {
1873      var target = event.currentTarget;
1874      if (element.hasClass('fullscreen')) {
1875        removeFullscreen();
1876        hideCode && hideCode();
1877        pageScroll(element)
1878      } else {
1879        element.addClass('fullscreen');
1880        BODY.addClass('fullscreen');
1881        fullscreenBtn.child('.ic').className = 'ic i-compress';
1882        showCode && showCode();
1883      }
1884    }
1885    fullscreenBtn.addEventListener('click', fullscreenHandle);
1886    caption && caption.addEventListener('click', fullscreenHandle);
1887
1888    if(code_container && code_container.find("tr").length > 15) {
1889      
1890      code_container.style.maxHeight = "300px";
1891      code_container.insertAdjacentHTML('beforeend', '<div class="show-btn"><i class="ic i-angle-down"></i></div>');
1892      var showBtn = code_container.child('.show-btn');
1893
1894      var showCode = function() {
1895        code_container.style.maxHeight = ""
1896        showBtn.addClass('open')
1897      }
1898
1899      var hideCode = function() {
1900        code_container.style.maxHeight = "300px"
1901        showBtn.removeClass('open')
1902      }
1903
1904      showBtn.addEventListener('click', function(event) {
1905        if (showBtn.hasClass('open')) {
1906          removeFullscreen()
1907          hideCode()
1908          pageScroll(code_container)
1909        } else {
1910          showCode()
1911        }
1912      });
1913    }
1914  });
1915
1916  $.each('pre.mermaid > svg', function (element) {
1917    element.style.maxWidth = ''
1918  });
1919
1920  $.each('.reward button', function (element) {
1921    element.addEventListener('click', function (event) {
1922      event.preventDefault();
1923      var qr = $('#qr')
1924      if(qr.display() === 'inline-flex') {
1925        transition(qr, 0)
1926      } else {
1927        transition(qr, 1, function() {
1928          qr.display('inline-flex')
1929        }) // slideUpBigIn
1930      }
1931    });
1932  });
1933
1934  //quiz
1935  $.each('.quiz > ul.options li', function (element) {
1936    element.addEventListener('click', function (event) {
1937      if (element.hasClass('correct')) {
1938        element.toggleClass('right')
1939        element.parentNode.parentNode.addClass('show')
1940      } else {
1941        element.toggleClass('wrong')
1942      }
1943    });
1944  });
1945
1946  $.each('.quiz > p', function (element) {
1947    element.addEventListener('click', function (event) {
1948      element.parentNode.toggleClass('show')
1949    });
1950  });
1951
1952  $.each('.quiz > p:first-child', function (element) {
1953    var quiz = element.parentNode;
1954    var type = 'choice'
1955    if(quiz.hasClass('true') || quiz.hasClass('false'))
1956      type = 'true_false'
1957    if(quiz.hasClass('multi'))
1958      type = 'multiple'
1959    if(quiz.hasClass('fill'))
1960      type = 'gap_fill'
1961    if(quiz.hasClass('essay'))
1962      type = 'essay'
1963    element.attr('data-type', LOCAL.quiz[type])
1964  });
1965
1966  $.each('.quiz .mistake', function (element) {
1967    element.attr('data-type', LOCAL.quiz.mistake)
1968  });
1969
1970  $.each('div.tags a', function(element) {
1971    element.className = ['primary', 'success', 'info', 'warning', 'danger'][Math.floor(Math.random() * 5)]
1972  })
1973
1974  $.each('.md div.player', function(element) {
1975    mediaPlayer(element, {
1976      type: element.attr('data-type'),
1977      mode: 'order',
1978      btns: []
1979    }).player.load(JSON.parse(element.attr('data-src'))).fetch()
1980  })
1981}
1982
1983const tabFormat = function() {
1984  // tab
1985  var first_tab
1986  $.each('div.tab', function(element, index) {
1987    if(element.attr('data-ready'))
1988      return
1989
1990    var id = element.attr('data-id');
1991    var title = element.attr('data-title');
1992    var box = $('#' + id);
1993    if(!box) {
1994      box = document.createElement('div');
1995      box.className = 'tabs';
1996      box.id = id;
1997      box.innerHTML = '<div class="show-btn"></div>'
1998
1999      var showBtn = box.child('.show-btn');
2000      showBtn.addEventListener('click', function(event) {
2001        pageScroll(box)
2002      });
2003
2004      element.parentNode.insertBefore(box, element);
2005      first_tab = true;
2006    } else {
2007      first_tab = false;
2008    }
2009
2010    var ul = box.child('.nav ul');
2011    if(!ul) {
2012      ul = box.createChild('div', {
2013        className: 'nav',
2014        innerHTML: '<ul></ul>'
2015      }).child('ul');
2016    }
2017
2018    var li = ul.createChild('li', {
2019      innerHTML: title
2020    });
2021
2022    if(first_tab) {
2023      li.addClass('active');
2024      element.addClass('active');
2025    }
2026
2027    li.addEventListener('click', function(event) {
2028      var target = event.currentTarget;
2029      box.find('.active').forEach(function(el) {
2030        el.removeClass('active');
2031      })
2032      element.addClass('active');
2033      target.addClass('active');
2034    });
2035
2036    box.appendChild(element);
2037    element.attr('data-ready', true)
2038  });
2039}
2040
2041const loadComments = function () {
2042  var element = $('#comments');
2043  if (!element) {
2044    goToComment.display("none")
2045    return;
2046  } else {
2047    goToComment.display("")
2048  }
2049
2050  if (!window.IntersectionObserver) {
2051    vendorCss('valine');
2052  } else {
2053    var io = new IntersectionObserver(function(entries, observer) {
2054      var entry = entries[0];
2055      vendorCss('valine');
2056      if (entry.isIntersecting || entry.intersectionRatio > 0) {
2057        transition($('#comments'), 'bounceUpIn');
2058        observer.disconnect();
2059      }
2060    });
2061
2062    io.observe(element);
2063  }
2064}
2065
2066const algoliaSearch = function(pjax) {
2067  if(CONFIG.search === null)
2068    return
2069
2070  if(!siteSearch) {
2071    siteSearch = BODY.createChild('div', {
2072      id: 'search',
2073      innerHTML: '<div class="inner"><div class="header"><span class="icon"><i class="ic i-search"></i></span><div class="search-input-container"></div><span class="close-btn"><i class="ic i-times-circle"></i></span></div><div class="results"><div class="inner"><div id="search-stats"></div><div id="search-hits"></div><div id="search-pagination"></div></div></div></div>'
2074    });
2075  }
2076
2077  var search = instantsearch({
2078    indexName: CONFIG.search.indexName,
2079    searchClient  : algoliasearch(CONFIG.search.appID, CONFIG.search.apiKey),
2080    searchFunction: function(helper) {
2081      var searchInput = $('.search-input');
2082      if (searchInput.value) {
2083        helper.search();
2084      }
2085    }
2086  });
2087
2088  search.on('render', function() {
2089    pjax.refresh($('#search-hits'));
2090  });
2091
2092  // Registering Widgets
2093  search.addWidgets([
2094    instantsearch.widgets.configure({
2095      hitsPerPage: CONFIG.search.hits.per_page || 10
2096    }),
2097
2098    instantsearch.widgets.searchBox({
2099      container           : '.search-input-container',
2100      placeholder         : LOCAL.search.placeholder,
2101      // Hide default icons of algolia search
2102      showReset           : false,
2103      showSubmit          : false,
2104      showLoadingIndicator: false,
2105      cssClasses          : {
2106        input: 'search-input'
2107      }
2108    }),
2109
2110    instantsearch.widgets.stats({
2111      container: '#search-stats',
2112      templates: {
2113        text: function(data) {
2114          var stats = LOCAL.search.stats
2115            .replace(/\$\{hits}/, data.nbHits)
2116            .replace(/\$\{time}/, data.processingTimeMS);
2117          return stats + '<span class="algolia-powered"></span><hr>';
2118        }
2119      }
2120    }),
2121
2122    instantsearch.widgets.hits({
2123      container: '#search-hits',
2124      templates: {
2125        item: function(data) {
2126          var cats = data.categories ? '<span>'+data.categories.join('<i class="ic i-angle-right"></i>')+'</span>' : '';
2127          return '<a href="' + CONFIG.root + data.path +'">'+cats+data._highlightResult.title.value+'</a>';
2128        },
2129        empty: function(data) {
2130          return '<div id="hits-empty">'+
2131              LOCAL.search.empty.replace(/\$\{query}/, data.query) +
2132            '</div>';
2133        }
2134      },
2135      cssClasses: {
2136        item: 'item'
2137      }
2138    }),
2139
2140    instantsearch.widgets.pagination({
2141      container: '#search-pagination',
2142      scrollTo : false,
2143      showFirst: false,
2144      showLast : false,
2145      templates: {
2146        first   : '<i class="ic i-angle-double-left"></i>',
2147        last    : '<i class="ic i-angle-double-right"></i>',
2148        previous: '<i class="ic i-angle-left"></i>',
2149        next    : '<i class="ic i-angle-right"></i>'
2150      },
2151      cssClasses: {
2152        root        : 'pagination',
2153        item        : 'pagination-item',
2154        link        : 'page-number',
2155        selectedItem: 'current',
2156        disabledItem: 'disabled-item'
2157      }
2158    })
2159  ]);
2160
2161  search.start();
2162
2163  // Handle and trigger popup window
2164  $.each('.search', function(element) {
2165    element.addEventListener('click', function() {
2166      document.body.style.overflow = 'hidden';
2167      transition(siteSearch, 'shrinkIn', function() {
2168          $('.search-input').focus();
2169        }) // transition.shrinkIn
2170    });
2171  });
2172
2173  // Monitor main search box
2174  const onPopupClose = function() {
2175    document.body.style.overflow = '';
2176    transition(siteSearch, 0); // "transition.shrinkOut"
2177  };
2178
2179  siteSearch.addEventListener('click', function(event) {
2180    if (event.target === siteSearch) {
2181      onPopupClose();
2182    }
2183  });
2184  $('.close-btn').addEventListener('click', onPopupClose);
2185  window.addEventListener('pjax:success', onPopupClose);
2186  window.addEventListener('keyup', function(event) {
2187    if (event.key === 'Escape') {
2188      onPopupClose();
2189    }
2190  });
2191}
2192const domInit = function() {
2193  $.each('.overview .menu > .item', function(el) {
2194    siteNav.child('.menu').appendChild(el.cloneNode(true));
2195  })
2196
2197  loadCat.addEventListener('click', Loader.vanish);
2198  menuToggle.addEventListener('click', sideBarToggleHandle);
2199  $('.dimmer').addEventListener('click', sideBarToggleHandle);
2200
2201  quickBtn.child('.down').addEventListener('click', goToBottomHandle);
2202  quickBtn.child('.up').addEventListener('click', backToTopHandle);
2203
2204  if(!toolBtn) {
2205    toolBtn = siteHeader.createChild('div', {
2206      id: 'tool',
2207      innerHTML: '<div class="item player"></div><div class="item contents"><i class="ic i-list-ol"></i></div><div class="item chat"><i class="ic i-comments"></i></div><div class="item back-to-top"><i class="ic i-arrow-up"></i><span>0%</span></div>'
2208    });
2209  }
2210
2211  toolPlayer = toolBtn.child('.player');
2212  backToTop = toolBtn.child('.back-to-top');
2213  goToComment = toolBtn.child('.chat');
2214  showContents = toolBtn.child('.contents');
2215
2216  backToTop.addEventListener('click', backToTopHandle);
2217  goToComment.addEventListener('click', goToCommentHandle);
2218  showContents.addEventListener('click', sideBarToggleHandle);
2219
2220  mediaPlayer(toolPlayer)
2221  $('main').addEventListener('click', function() {
2222    toolPlayer.player.mini()
2223  })
2224}
2225
2226const pjaxReload = function () {
2227  pagePosition()
2228
2229  if(sideBar.hasClass('on')) {
2230    transition(sideBar, function () {
2231        sideBar.removeClass('on');
2232        menuToggle.removeClass('close');
2233      }); // 'transition.slideRightOut'
2234  }
2235
2236  $('#main').innerHTML = ''
2237  $('#main').appendChild(loadCat.lastChild.cloneNode(true));
2238  pageScroll(0);
2239}
2240
2241const siteRefresh = function (reload) {
2242  LOCAL_HASH = 0
2243  LOCAL_URL = window.location.href
2244
2245  vendorCss('katex');
2246  vendorJs('copy_tex');
2247  vendorCss('mermaid');
2248  vendorJs('chart');
2249  vendorJs('valine', function() {
2250    var options = Object.assign({}, CONFIG.valine);
2251    options = Object.assign(options, LOCAL.valine||{});
2252    options.el = '#comments';
2253    options.pathname = LOCAL.path;
2254    options.pjax = pjax;
2255    options.lazyload = lazyload;
2256
2257    new MiniValine(options);
2258
2259    setTimeout(function(){
2260      positionInit(1);
2261      postFancybox('.v');
2262    }, 1000);
2263  }, window.MiniValine);
2264
2265  if(!reload) {
2266    $.each('script[data-pjax]', pjaxScript);
2267  }
2268
2269  originTitle = document.title
2270
2271  resizeHandle()
2272
2273  menuActive()
2274
2275  sideBarTab()
2276  sidebarTOC()
2277
2278  registerExtURL()
2279  postBeauty()
2280  tabFormat()
2281
2282  toolPlayer.player.load(LOCAL.audio || CONFIG.audio || {})
2283
2284  Loader.hide()
2285
2286  setTimeout(function(){
2287    positionInit()
2288  }, 500);
2289
2290  cardActive()
2291
2292  lazyload.observe()
2293}
2294
2295const siteInit = function () {
2296
2297  domInit()
2298
2299  pjax = new Pjax({
2300            selectors: [
2301              'head title',
2302              '.languages',
2303              '.pjax',
2304              'script[data-config]'
2305            ],
2306            analytics: false,
2307            cacheBust: false
2308          })
2309
2310  CONFIG.quicklink.ignores = LOCAL.ignores
2311  quicklink.listen(CONFIG.quicklink)
2312
2313  visibilityListener()
2314  themeColorListener()
2315
2316  algoliaSearch(pjax)
2317
2318  window.addEventListener('scroll', scrollHandle)
2319
2320  window.addEventListener('resize', resizeHandle)
2321
2322  window.addEventListener('pjax:send', pjaxReload)
2323
2324  window.addEventListener('pjax:success', siteRefresh)
2325
2326  window.addEventListener('beforeunload', function() {
2327    pagePosition()
2328  })
2329
2330  siteRefresh(1)
2331}
2332
2333window.addEventListener('DOMContentLoaded', siteInit);
2334
2335console.log('%c Theme.Shoka v' + CONFIG.version + ' %c https://shoka.lostyu.me/ ', 'color: white; background: #e9546b; padding:5px 0;', 'padding:4px;border:1px solid #e9546b;')
2336var canvasEl = document.createElement('canvas');
2337canvasEl.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:9999999';
2338document.body.appendChild(canvasEl);
2339
2340var ctx = canvasEl.getContext('2d');
2341var numberOfParticules = 30;
2342var pointerX = 0;
2343var pointerY = 0;
2344var tap = 'click'; // ('ontouchstart' in window || navigator.msMaxTouchPoints) ? 'touchstart' : 'mousedown'
2345var colors = CONFIG.fireworks;
2346
2347function setCanvasSize() {
2348  canvasEl.width = window.innerWidth * 2;
2349  canvasEl.height = window.innerHeight * 2;
2350  canvasEl.style.width = window.innerWidth + 'px';
2351  canvasEl.style.height = window.innerHeight + 'px';
2352  canvasEl.getContext('2d').scale(2, 2);
2353}
2354
2355function updateCoords(e) {
2356  pointerX = e.clientX || e.touches && e.touches[0].clientX;
2357  pointerY = e.clientY || e.touches && e.touches[0].clientY;
2358}
2359
2360function setParticuleDirection(p) {
2361  var angle = anime.random(0, 360) * Math.PI / 180;
2362  var value = anime.random(50, 180);
2363  var radius = [-1, 1][anime.random(0, 1)] * value;
2364  return {
2365    x: p.x + radius * Math.cos(angle),
2366    y: p.y + radius * Math.sin(angle)
2367  }
2368}
2369
2370function createParticule(x,y) {
2371  var p = {};
2372  p.x = x;
2373  p.y = y;
2374  p.color = colors[anime.random(0, colors.length - 1)];
2375  p.radius = anime.random(16, 32);
2376  p.endPos = setParticuleDirection(p);
2377  p.draw = function() {
2378    ctx.beginPath();
2379    ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true);
2380    ctx.fillStyle = p.color;
2381    ctx.fill();
2382  }
2383  return p;
2384}
2385
2386function createCircle(x,y) {
2387  var p = {};
2388  p.x = x;
2389  p.y = y;
2390  p.color = '#FFF';
2391  p.radius = 0.1;
2392  p.alpha = .5;
2393  p.lineWidth = 6;
2394  p.draw = function() {
2395    ctx.globalAlpha = p.alpha;
2396    ctx.beginPath();
2397    ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI, true);
2398    ctx.lineWidth = p.lineWidth;
2399    ctx.strokeStyle = p.color;
2400    ctx.stroke();
2401    ctx.globalAlpha = 1;
2402  }
2403  return p;
2404}
2405
2406function renderParticule(anim) {
2407  for (var i = 0; i < anim.animatables.length; i++) {
2408    anim.animatables[i].target.draw();
2409  }
2410}
2411
2412function animateParticules(x, y) {
2413  var circle = createCircle(x, y);
2414  var particules = [];
2415  for (var i = 0; i < numberOfParticules; i++) {
2416    particules.push(createParticule(x, y));
2417  }
2418  anime.timeline().add({
2419    targets: particules,
2420    x: function(p) { return p.endPos.x; },
2421    y: function(p) { return p.endPos.y; },
2422    radius: 0.1,
2423    duration: anime.random(1200, 1800),
2424    easing: 'easeOutExpo',
2425    update: renderParticule
2426  }).add({
2427    targets: circle,
2428    radius: anime.random(80, 160),
2429    lineWidth: 0,
2430    alpha: {
2431      value: 0,
2432      easing: 'linear',
2433      duration: anime.random(600, 800),
2434    },
2435    duration: anime.random(1200, 1800),
2436    easing: 'easeOutExpo',
2437    update: renderParticule
2438  }, 0);
2439}
2440
2441var render = anime({
2442  duration: Infinity,
2443  update: function() {
2444    ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
2445  }
2446});
2447
2448document.addEventListener(tap, function(e) {
2449  render.play();
2450  updateCoords(e);
2451  animateParticules(pointerX, pointerY);
2452}, false);
2453
2454setCanvasSize();
2455window.addEventListener('resize', setCanvasSize, false);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.