Questden-BLICK2

Improves readability of questden.org

// ==UserScript==
// @name         Questden-BLICK2
// @namespace    https://phi.pf-control.de/tgchan
// @version      2024-12-29
// @description  Improves readability of questden.org
// @author       dediggefedde
// @match *://questden.org/kusaba/draw/*
// @match *://questden.org/kusaba/meep/*
// @match *://questden.org/kusaba/moo/*
// @match *://questden.org/kusaba/quest/*
// @match *://questden.org/kusaba/questdis/*
// @match *://questden.org/kusaba/tg/*
// @match *://questden.org/kusaba/questarch/*
// @match *://questden.org/kusaba/graveyard/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js
// @grant        GM.xmlHttpRequest
// @grant        GM.setValue
// @grant        GM.getValue
// @license      MIT; http://opensource.org/licenses/MIT
// @sandbox      raw
// ==/UserScript==
// 
/* jshint esnext:true */
/* eslint curly: 0 */
//
//
//
(function () {
  'use strict';
  //
  const imgRes = {
    refreshimg: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAMiSURBVDiNbVJNbFRlFD33e%2B%2BV%2Ba0zbQdbqLYKhhKmoaIzjX%2FRmEY3xGBYqLxEjJCB8LNhBxsMTQwRdGGi4AOjMUwlmDQBN%2FwEIZLUwqtx0dCS1NLGAUY7tB3ozLTz3vu%2B66LzkllwkpvcxT0nJ%2BdcYmb4SGWyzzTo2hEQ3vSkatWEmCfCuOPKEwAu2Jbp1t0eBHCCfIF0JrtF08RAT9cqfV1ns9HWFAGYUShWMDJ2v3xn%2BqEnFe%2B2LfNselc2w4zvAMSImZHKZNcbuhjZ2tcdiseC0InQHA0gHNARDRkIGgIzxUUczw5XCsWKHQ019D4uVw1mNBEz49U9Z0%2F1rGv7NBgwxK3RHKquB0PX5JrVscW%2B9PPh3vWtpAmCEIRfrk2ot15sFwe%2Bvuo4rmzRAYCZP7wzXRCup1gxLzLjM8eVZ8anZ5MT9%2BaPXu9o7tq3dVMoEQvh43e7RC0GAqAEAEipwlXH85IvPPuDlOoRgEHbMvO2ZV7549uPXhqdLOzdfewS7j8sYXqmDKkYAAMACyyvqumpSH9jJPBVZ%2FvKbgBTdWnruiZ2bnsn6XY%2BHUWicQU0Qb4D1mt38V8%2F37yAJ%2BO0J9VrF4cnq1ftqSXfvOOqAAD2W6gCMGoErpvfAOwE4D5BWLct854v4J0%2F%2Br4GEJgZF%2B1%2F1Knzf%2BWkVD22ZRbrWYezY6tK5aWO30fGbwCI6QBABBAR7v5XhiEYJwf%2FJABf1JNTmWyYiO72blx7Jj8zlwKgAagKABBEBAAdiRBWt4Tx5f63qa0lcuyVPT%2FfSmWy6ZrG9hWGrufyswfyheLrmkaubZmuWHZAeFxxcWFoij2P0b4yisM73gh90Jd8ubU5ci29a8A1dPGNVDI%2B92gBTY1B0oQYBAD%2Fkaj%2Fx6Glidzc5KXhyc7Mlk3hRCyI7rUJeq49HiqWXfw7W4LjeBQJGjh3ebTiSXUEAJYfSTFN5OZO3zy5LfmgsPBJ%2F%2Fc35o8P3CwN384jP1tG1ZUQRCjMl%2BS5y6OLUqmMbZljAOC38BOA7bZlci0wA8B7DYa2VzFvkFLFDV2bIcJQ1ZGHbMv82w%2F3fy5VdmoTZczGAAAAAElFTkSuQmCC",
    nextbutbild: "data:image/gif;base64,R0lGODlhEAAQAOMIABIlNRw4UBM7Xi5ZfjhokY6xz6fI4%2Ff4%2Bf%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FyH5BAEKAA8ALAAAAAAQABAAAAQx8IlJa5U2awqQ2RVQIB4oiAdZEOZZtu4Hd3IrsnCu79nw%2BQKgBUAregIZonKJxMAeEQA7",
    wartbild: "data:image/gif;base64,R0lGODlhMgAyAIQSAIdBG4hBG4lBG4pBG4tBG4hCG4xBG4lCG4pCG4tCG4tCHIxCG4lDG4pDG4tDG4xDG4xDHItEG%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FyH%2FC05FVFNDQVBFMi4wAwEAAAAh%2BQQJBQAfACwAAAAAMgAyAEAFk%2BAnjmRpnmiqrmzrigDwzq9M33Cc6ngb%2F7WfbSYE9o7IpHLJHA2bJ170CSUJV0ZmEVnseqXVsHhMLkPBZhR6nC1RyW23ebtbK7u%2BOA6%2F59f9VV9vaYSFhoeIiYqLLHaHg4iQgViEjh%2BSgY6YZ5qVek5pV2pzn06WSXR%2FmaJYpTeAja55rDSwKl5LgqZfYoK4jMBKIQAh%2BQQJBQAfACwAAAAAMgAyAEAFqOAnjmRpnmiqpgEArHBMtnJtf%2B6ty%2B67%2FytXYOi7CXvApHLJJBWbNSR0J5wCe0%2FmkHbFeltCYs5KLptFrcA5uI6N26oqPA5Qz1PYO15KfsN6dkpggUaAYChedUdlikRgfnqRkpM%2FfJKEknWUTptOmJNZlJCTRJ0ilqKhoKOZmqZ5r6xNrnS0U1syW6o%2FgzaPuzx1hzpHuG6Pxl2Aib%2BJtlpYv82fptU3IQAh%2BQQJBQAfACwAAAAAMgAyAEAFrOAnjmRpnmiqpkC7vvAYtEJs20Ee3Hzc5r1gLAfQ7YTIpHLJNM1cTRw0yiseqULiFIvUCYzW70%2FHLSt%2FpCuaOgOaVet36gmQr9pXO4pY08%2FbfndWgSo6LXWEe0RaRYknYlZajpOUN3g7X3qGeXtYkpSHnISfoHGTgJUfoalPoomklHipqlugOX2pPwWzqm6sVoi5q6yXs3iHwY50AqFFhzJfAbtlzqGLkbzZSiEAIfkECQUAHwAsAAAAADIAMgBABbvgJ45kaZ5oqp5BCwCtsJJFMLPtrZvHGry2nXD3%2BwGGyJ0gIGgmn9CodKpz%2Faii4AymxQ5bOS8UzBRjY4GCec0uCV5wuAtYxC6L6uehq5Ov%2BShAeW0%2BYIQ3SzCHM2SAiyZMRo8%2BXI6TJWAwMpecnZ6fkXJ3lmdzlShgm1NGMIpIpIxkVKpVp16usUttsFlAnyRgPb8fo8MirbyLmcbELsw1hsZWzEVX0olHxqPZ0mjMH3uR3x%2BpuuPn6G0hACH5BAkFAB8ALAAAAAAyADIAQAXO4CeOZGmeaKqeAxC8grB%2Bwgvc8%2FkGwpH%2FJJ4KJgMaSz5ULXBsOp%2FQqDS6i8GS0xGi5sp%2BYLMY15vCmrY1cqq46qnf8Li8tOQubfgbM%2Bu6Pl0CCEc8O28BZigBfWxxBSk7XXMfiEFgkiKMJTyUlycxA52hoqOkpaaXeDuphHI3fUQ%2BBwgFijaAWYQvnEOKAFB3e1AHYkcDqmQvP7Q8vobBa8dymSbRc890S46SB7t3pyPVktcjNWnfkwID03C7mDHn4O%2FwNPLz9vf4%2Bfr7oiEAIfkECQUAHwAsAAAAADIAMgBABdjgJ45kaZ5oqp6CELyBcASDitCugKxmTAS8YA8AFBqPpBhyGTwwn6MddEo9JgTE1ktQq4pyA1f1kAuGBV5VAOBMQwNtt3xOr09zMRjMteejqQN6cEs3e1YtLW4HRAA8ZH92i0UoYXYkDTqWR5OaKwNSnSkHoKEspaeoqap1BTFhezBEL3FpM1l5eS0Iu4%2BCVYh%2BkDyvW0x%2BAAW0SJLCQX5dVYY8CMBzN0Q8XMpyL9AnxZprKgMEobMpCNt1iasl7O0jAaTwg%2FAjze3qqvqp86v%2B9gIKHEiQSQgAIfkECQUAHwAsAAAAADIAMgBABdXgJ45kaZ5oqqJDIAwIUqDIICBCoOvH6v%2FA0S5ILOJuxaTqoFM6R4LZc0oFDmzYmwtR7dJygYG3qhOMnTnAmYjjrmm9t%2FzpEtjDtno5NyB4EwMLbkU5doZzRUw5iEFXdow%2FTGGQKwU6EJQqZZkokpwnBy%2BfJDijpqeoQQk4LntpLgVxZwdXfS8vLQUFhoV2V122YkGGO0hOMVV3LqkjrAHMUHayqa3CqQmF0B87g6gNm8yK3acITdABAM%2FnAQvnANOnNw3Qj8wBB%2BOmZqgx%2BZnW2gJSCQEAIfkECQUAHwAsAAAAADIAMgBABd7gJ45kaZ5oqq7syAgDDA9tbacOAht3bzuCgMBHROlgRSIiAAgEktBokYBASK%2BpgiAIsGKhW2fjW5SRazknLVr1ioKfrQDhOJzveMcisR0QCAMIfng%2BMUJzhDcxW3aJLToBAwmOLYtulChAQpgqYZecIwdOn6CiiFCkKgdBQpOgr7CxXwkEBgIHgJKyJQi5cq67A34DQmu7HwcFi0%2FHH0ebzR%2FEjNGLxsesQ9Vbqa9ASM2a2s1y0Uu3zQmL0U7jshHtDrsMq1vYo7tL7WQJ3SPPBRolanOrCgxg0RJGCQEAIfkECQUAHwAsAAAAADIAMgBABeTgJ45kaZ5oqq5s675wiQxIEt8tIggD7puCwMDmShwEvw9COOgln9CodDo4LKZPwoBHwN50gsOK6AUjng%2FHIcAOANq7YPjsrdvv3ofBgI85FgRkfStbTYMrCAU7TocoWwFijSc6A11jd4%2BRkiMKAwFIP5Q1LWtxAXR9CKibrK2ur7AnCQtXsSMLVauwCVq2IgQEuq6VW3yxBAY8tkafwqwHW86bTWy2BIuxCaIp0lSflq%2BPoIh2DnENRWXYMaNQcd0sCOg3CVs840laYDQJ2toMqpaw4cGozoEjQQaq2gHOl0McIQAAOw%3D%3D",
    stylebuts: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQgAAABICAYAAAAK2WsnAAAABmJLR0QAbwBvAG98p6s3AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QMUFjAYLi5uLgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAgAElEQVR42u29d5gcV5X3%2F7lV1dW5J0ijCRqN0ihYVrJkLByRjU2OywoWTDAsmHfJLD%2FDAmZZA2vDsot5CTYLeA1LWMAmGOM1wcYyDhK2ZcsKtrJGmhlN0KSezpXu74%2FqOD2hezRi7Xe7nmcelWrufOr0uVWn7j11%2B3wFE7Zt27apx44dU6LRqGKapqCCzePxyCNHjtiAk%2F2Zcqvxa%2Fz%2FzXwpZUXMiZsQQlbSbq75Ige94447lJt%2BucUa8XhJCA0pKjuPBMKOyTwrw%2FhjL14AjF999dXmDTfc4BQbvH37dvVj31lmDms1fo3%2Fv48PKJuvPmmdCX%2FXjzq0XADK3dBzzFcBWRwshJRS7Nq1S7vma63GmM%2BPpoKqQIV8pARHgm1DQyaN8vQrV3R2dvZv27YttW3bNgkoXV1d2utu8KZGvT5U1Y1KQsx8DimzP9T4Nf7zlw%2BoG9%2FZl54L%2Fu7bW32AXTRSOWO%2BI8F2oD6dZs%2F3WnXAFkK4geiJJ57wfODrC4yRSIi6gCDkE3g04QaJCgKE7YBpS9IGxNOSxngcvetvz7nwwgt7rr76arOurs7zpuuN2HAoRMgv8HlAUwVKhR%2FAkWDZkrQJ8ZRkXjyOfqLGr%2FGfH%2FwVK1Zol1%2FbE59L%2FgPfbg8BZm6GMxf8ZAZiaUnjeIyHblvkBUwhhNQ0TdONkI%2FWekFrvUJjWMGvg6q4J5npBJYjMUwYT0lOjzskNR%2BKoqyLx%2BMpj8cTVRQlIOt9LIwI5oUUQn6BVwNFqfADOJCxXOcMxx1Smg%2Blu8Z%2FzvI9Cbx9t6Ms%2FyDCTqB0fx9n6fue9%2F5ZPH6Al3b9mHuXvI3uyIqK%2BYCvmL%2B5M4xHo6ob2LRg15FYng%2BEgWS22Zz4J5pwGByHhPQBeAFHSmlrsVhMx6fRFFZY2KiwoE4h4BV4VDD7HihNxrReXnYSy4GMKRmNS3QNeqUHKcTqZDLZlU6nzWAwGPKGdJoiCk1hd4SiewSqIovTIEUzIlG0D7YjMExJUAdFURhwPEgpa%2FznKj99EL2pBbVOQPQINDVDRDkr9oueH01%2FA7RfPWf%2B2Tx%2BBL2piXVaN5nIqortB%2FzekCfPrw%2BCR4WMdRN2GlTf1Pb79U%2FiOGDa0Fwn8nwgWDTFCBbzQz7BDQt3uRECeJhLCsBLHi7s5vccPvtfm%2FGqAtuBVMYDoANpAG10dFQVqht1fB73R9dAU0BvuxBr6ImibG3pB3AcN18hHffvvBooqiCRTreNj49HLMvqTyaTmoMX6Ugyphs5DUsWjU7kJGmTonNIiWW7QUg6EgeBkUq1joyMRAzD6Kua3%2F19WPT2s8efC%2FsTQwhjAOltRPG2TM7PDCMzAzieRmxPy3PH%2FpFDGL55KEkJpw%2BBpx6S2WvZNmDo98h0v3t9CwVHa8Ke9zLSh29Btny4Ovtj8ekfkUk5J%2F0bsGJ4h7tJ2AYLEk8Ti1yeDxwz8QHFQeT5adO94dPGuItPl5ttpUHzQSL9SRzp4PV8qYSfzTvkk5PFfEUAhpEfPmRKyJlJ05TxtDuFsW2JKUsvAm10dFSaFiTSDrGkQFMcAl7hDlHQyYyM51HegFN2KTnZEcR4ShJNOMTTMB6NeoUQyokTJ8yFCxcmR%2BJ1SEsSjQs0NTv8qfD1S%2B4clg1JQzKaFqSHhnQhhOju7rYcx6mOr7wFeq2zxz8D%2B0ejDlrP%2F0U1R0Co7k2k%2BlAWvQd8C1yebSNPfB1hDCNRkNLGEV6che8hqTSfFftt08ZGJZGyGDO1afnq4T2oHe9AWBbywG7EoreBYYGVxOn6OiKyCXxbEIEOUIPI0d1Yh7%2BBlTxFWrersl8eOT79qzvNmpP%2Bvaj3PrYrHSyJ7qdzbDcp%2BQzdkVUV9e%2F69esTI3Enz6%2BPZG9ibiKWen3%2BHGYSPIHyfZAEfXBy0M7zgQRgZBuU8DUV8A%2FnuUMlHhma1E9H%2ByxMy72HYymRiyQOgNbX12clDUk0JulXIJVW0D3uyEARgmTP6Two4HXKni5Suv0fS0lG4w7DMcHo4KCVSCSsxx9%2FPD08PGwOxxaTSdhoqshnWAGs3dfkWdrG7015LJfJtWxJ3FFJDA5ayWTSnIlf6XamfGd4B073bW7eyLcE0ifytlfDFye%2BBuNPoix6J%2Br8rTiZfpwDn4KuG9HO%2FTf3IXzsa8jxJxHt1%2BTb2M9%2BEk7eiFz9lTn3j3XoC9lst8SyHdIrbpiUn3zoLWiam0IXXTcUAMducB94gWXg70ALXAFx3B9sYB2ObMA6eT3U21XZbx3rmtZ2LWKfef9KyRt3%2FpSvnv8NXnS6m9auw8xP3cP2VZ0V9e8rX%2FlKazgm8%2Fz5je6zP5pY6z7TY%2BAN50Y8xaMf93cLWvYRTboBIscHxoHcU84s5guAb308j%2Fk4byow31Q4XnSUE%2F1XZnMdknFLIRt8pBBCaidPnrTTFsQSDkhJMi1LkijjPYN5kM%2FZS2bwUZAO3qYteBrXu1lQC5IZSSzlMJ5WiUajdiKRMB999FHz6NGjZlx7FeQGR0Ud4LTfiLXXTWDpsWxnLvw85r4Plh6ThadZHIXY%2BLiTTqetmfj56HzwczixpxFSoL%2FgzkkDxJnwM3tvRm2%2FBq35pVijO3D6%2FjVvezV8R1mL9LXg8V4KMRvHCmDGPZA5hrfD5dnKuUhfC1pRGyPugUwXeoczK%2FunDRDqC7CH7kOZdyX22H0YaTk5f%2FX3EYDxxBvwvuDnkN3Xz8%2Ftb0Nb%2Bw6UIr8U%2FNMKnbchk9XZb4yOTWv7XFw%2Fa04%2FyjNOB33pAHvFcq4aHmZV%2FC7GWt%2BfbzgdH8jE0zLPH0%2FlzrEPgJTaSio5xQdQYTz7u2jMzvOzE5PcNMAq5rsDhaGqRhBjWT%2FZDqSFQvFrTu3JJ5%2B0jTWSREriOJBKZyNRNkCMDozmQcGQB9tZSbrvATj1K4LLdLTwSixbYliQykhStiSdTjtSSufZZ591Dh06JM3LwHQmuTiVZtKZ7Egp99JGbSs%2FVtQJpgKmaTqGYTh%2F%2BMMf7MWLFzMlP7ct%2B0esru9iRx8vYU4WJGbDz6Tj0P0L9PoXo4QuhA0%2FxzBnwY9sRfrHyOy%2FCSe2G6ykex0IgcjxIltxfGOkJ7SRgDCn5idPfA%2BsGE5sL0r4XJzxvYjAYmTyOCKyGWfsYbyrPocdewYn%2BjSke%2FF0fhLHsLEMC9WwsQwTy57ePxnDyNuazhh5f6czaXxq25z6P3%2BdTLXNwfVz0ZGf8psV78Uw4WBgLVY6TX36JAtP7%2BF4w4YZ%2BdkZWp5vWqX8kLcPgIb7FxEKqG6a7MLCyCjXPnc9mW7u0S5eKFXMFwJIFxIbpSmO9ORusgr22wrkgkNuiiHNVWA4DlIKMtnXI9kkKMNjscKcTlmM42lgZOzXblQ78Dsi5yxHZhdamJbElCqWZUkppezp6ZGAjAACOWlOTObDr5z2mJjsb4ATJ05My88%2FCUd3oETOK2GWLSmdBT%2F12Kuzvu8l8%2FjrSrPQF9xdFd%2BK7sE8%2BBkkCmr9C1CbX4F18nvI1NG83VO1IXU0b%2BNkfBk%2FhOMY6G1%2Fg1SD2LFD%2BFZcT%2BqJN%2BJb%2Bj7SBwaxRv6MGlyDmN%2BM1f0f2CM7wNOQ5cmK%2FCNzq28m2XfsDIqiz5n%2FpZQzzx3PgB%2FKDLO5%2Fz629N1bhj7%2F1G85Xr9%2BRr4QQkbeNlzwzxSmBv0Kp4bShP1avs3DdohL1HiWLyddFj2Rj4QvOU7RO4ovFU7ypcLxL03w01SDSs3NJbgZTByZjSCFBolkIfMZSjtIGSgcS3XhM5z8%2B1RHgiNACIGqqqKlpUWoqsoooEg56dNXZA%2BqpQ4oO1bc39Xw85txGu%2BCl5Qxz5QfuuDXOOkBUgf%2FEYx%2B9%2B99iwms%2B1rZ5TAT3zxyEwiV4IbbUfRI7lEBQuTtTk3TRsmeYDK%2BZ%2BHVONE%2FY574Fp72a1ByTKGiIlEVD0JKnOH7QVFR%2FAsRQqAgcbJtbaa2P%2FXYaxFCIoQg%2Ffhr8u3Sj78GpArCgxh7ErVxy5z5X8wwVzrT6%2BdFPb%2Fkl6s%2BwL2d780fu%2Br493nzM1%2Fk%2FIH7%2BcWa62bkd3d3i7q3j%2BT5AnjYDnOJGmMk0UBjcDT%2FN5GAh1BAZQwYSTQgfCLfPsThwlRmwvctivnuyFwpDNJLM%2FTT%2BmkyvlZYc50dzToTV0oWHbBdSu6YonrBzi7tlNk1nwpomqYEg0HlwgsvVL1er7xHcV%2BsTmqcooKQaJgIxYOUElVVssYZiAlPHFUBj8dTMR%2FAHPkzqqbii3TOOOeeDZ9AM97z%2Fp3MwG8x%2Bu9AGifxKNXxNcdBI4UItOH1RQo3gX0aRVXwKODM0EZXp%2BY7p76LFj4fEepATR%2FFtochtgdVyUD0CTC6UUgi7TRS9aFIAxnfDZ75KNZpiO9GtYewnOSk%2FIYX%2FgorupvU4S8QPv%2FOkn2A5LGvYp26Hf%2F88gCRGXoIo%2BvrhM%2F%2FWVX%2Bz10nU22T9UGlfOFYXHbqV3z9%2FFtLOE8ufDlvPPQVWjM9LI4f4lRk5bT8nJl60b2rSpUdsp6Lw2M8YtcTnhemQ9FJZCzCIZV9jYugMYQ6HGOHrEdV1LwN2Y%2BsTjxnsf2Kqhbd%2FErxLyYNFcWfr4gvAbTW1lYRU0AXElURZd%2FD8OqFxQ9ej4NjJvPHvPXL8emFNRFSgq5AIBBQGhsbtU2bNnl8Pp%2B8bx%2F4ppjjWZHFSKMPLfE4WmgN5ugOfF6v%2B7uDHyay%2FtaS6OZVwPH7K%2BYDOLGHCIQX4%2FfMPBqdDX%2F8yb%2FB2%2F4u6ttfhuH3kzrxnbJzzcQPKAqW7kUQxWP3g2OSOvEt%2FJoJmo5XtVA8GrbXB5O0kaoHXbHwavqk%2FIa1XwZrFKFuQ9E04P1uSmO%2Bm0SMLDi%2F4C9jHEWP4FhxFC1UYv%2BwosMU%2FklE78cfWYrfA4nxP%2Bb3AfyrPkLs0D9jHfoYwaUfQQkswjFGMQZ%2Fhxj8NZGlb8OrVed%2FM3udTLnQyDO7%2FvWZ47z28NcQXh9msL60L5UIvQvOY2l0H3%2FddQs%2FXvuPJLyNU%2FIB26sV%2BLtECz7NR6gxxK6RFt66uy2bUYVFre7nufJujWBA5c7zTUKNIeIjcXxagQ%2BUXF3FfCHg877C6qv%2Fc013fv%2BdV%2F45v3%2Feeefl9%2F%2Fzb%2FtL7M%2Fy3QCxadMm9VgafMJdPSmyQSI3rwr6C52gZY5hpocJ%2Br0IRaNp2SvQdFGyLDQhBFoopLS0tHjWrFmjq6oqvIcEfie7dFuUztf0Fe8m1f0fOIPfRzG30LDwamLRu%2FE0vBC98TJUbynfqwi0cLhivgQs2Y0SbsI58jHC535l2mWts%2BL7%2FWjOCcxn342CpLHzXfi8omq%2BuvxqjL6fw%2FFPuhc4CiLSjjSH8ZsH0CLrUJddTabvzpI2SqQd2xjCaxzAG9g8KT%2BoqShi%2FqT2T5xD463L%2Fhuuyn7bOo6n5QoC3tz%2B5Xk%2FCCC47npSPT%2FGOHkjjnQTZppWT2TNJ9AiG6v2v%2BX3TxsggkV9UE3%2F%2FsMT1zI%2FdQqAr%2B58Bf942d2k9HoAPvnQ22g0ByAQ4Lzkk5z32Ov49Nbfk1H9k%2FLdh2yBb2UXODgxh6v3toMX%2FvBig%2FV3SpobvPzhxQZX3e%2B2uXpvgF9uGiHgCRD0ijwf8BV3XTEfAcFgsNCVRUFUUZQp%2FVTsnyK%2B1JqamhS9V4Ii8GqybKFIMNRE%2FaKXYBlR4gO%2FRZoJ6ptWEWm7Et3fnLfScSCDwA84waBobGxUm5ubPZqmKX5VoqoCr5Al3%2B8QApi%2Flrr5pTdtcPOtE3NMOBIyUuBHIqvhA6augBgluOJ9ePRJc1hnxA9ekFvzcO1UObKK%2BPXtr4HWF2HF9oPw4qnbiJMZwIrtQQvOQ9Mh0PFqnNbLsGL7kcKLFtmAkxnEGH8aGZiPX63e%2FgpyfDPab4%2F%2BEd3no77txSgeSHtsd3%2FC2D2w7C2w7C1z4n%2FDX%2FkIohr%2B17b%2BtMQ%2FAsitW%2Fq%2FL76zzD8q4JuCD4hifkbzE2oIEh9N8NstaUL1Qfz4aW%2FRuXvjKCEtiMfr7gfrA%2FhG3SDo8xT4ue9K5O77ifb7s4FTCIGu6zMGCL%2Bn1D9FfKkFAgEZkDaGoqIICwWJUvQ8aVv7gayLJJGmCyY8eyQCgUSiCjDQCEqLjK4n6%2BvrrVAopACEhI0hVALScrOtYrL1%2F7LsewFuM5HPshqKRkjapD2eRDX8hrVfPav8ObVfi6A1XlSYE%2Fpb8fjbShKeqqcOtfHCwrPf34rH18qY8PyP2W8veAnzmq5ACAkC5mWnhme%2Ff6feFCGfC%2F0rivkAibFkPsEaH0sAcPfGRP7%2Fuf1cOyklKjLPzxqSTyhMtH%2Fpa3bkrdyZKNi%2F87Zi%2B%2Fvz149Cqf3FKQqtvr7ebrAy9GkhdMch4tjZbPRk6%2FPlpItxpYSE0BgXCovMFKai9Pj9%2FjFFUUwhhAhlMvToQYRUCDoWubc0w3vePWMnz1v%2FXZevaMSEQruRwlTV3kr4Vdlf4z%2Fv%2BOHVt06%2FyMuUs%2BLf%2BMDlVa3E%2FdTlf5ySD9jF%2FMj%2B%2B2a10jeqqHl%2B7nVB7g6fS%2F8vMlNkV2m6OYiGhgarvv89banW208NebwkEYSw0aWDUvzuuyiOFs9ZM0Iljkpa0ZhvpTl475avNTc3HwyHw6f9fn8KYEvDN5ZGT3%2F4%2BGmPj5gDQWnjkQ6hVd%2BekT9uCBIiyzfTHPrtlq%2FNnz%2F%2FQCX8Su2v8Wv8Yv4HL3mgOr41Ld954N%2Fb6zf9bd%2FYXNj%2FwG3t9RPv9Lnk%2F9Hl56OJBtiLFy%2BO9jxyVWfbpv8%2BMqJ66Vc8OBWXlIKQbdJixzl09%2FqbvV7vY7quP9vR0TGqqqoJ0NbWNqJvf%2F3KlpU%2FOzSieukX1fHDjkmLHePgXetu9vv9j%2Fn9%2FgM1fo3%2FPOFLgCdva%2FVf8PaTqTPhP%2FafHVNmZc8WX9u2bZsDZA4ePDhw%2FMFLl4%2BNjXXatr3ScZw2KWV44jvXSTZnXIjEKej2eDyHgMM%2Bn%2B%2F0%2BvXr04sWLZIA69evN%2B%2B%2F%2F%2F7%2B3Vm%2BaZorHMdZWCk%2FKkTilKLU%2BDX%2B85Kfe2g%2F9p8dHs58m2ql31nha0IIKaV09u%2Ffn%2Bzt7e0NhULReDx%2BTEoZkVJ6p1x%2BVQo0HMcZk1KOhsPh%2BNatW82tW7fmT7R161a2b9%2BeeOaZZwxgzHGco47jVMwXQhhAtMav8Z%2BP%2FOfzJib5fy5DKipwTnGQcIqSJ3Ka89X4Nf7%2FVv7zbtPcLKnMOUa599571e7ubnVsbExNJpMzTmSSyaQMBAIyEonYixYtEtu2bbOmcLwCiO3bt6uHDh1Savwav8avmm9n%2BfJs8EOhkHXttdda2TUUMj%2BCyJW%2B%2F8x%2FtBvepghqwIvQKgueUoIdS2EMjTP8xGtbOjo6xrdt22Zkcxv5qLx%2F%2F37147fOz%2Bjza%2Fwav8Z%2FLvJ7HnpJc0NDw%2FjWrVuNnK6HkFKK7du3q1%2B5Z6Ppa63PVpKqQheDwjLWdN8YR%2B65pLOtra3%2FoosuSv3TP%2F2TBJTDhw9r1323Ke1tqS%2BU06%2Bi5pnE%2FTp5pr%2FGr%2FFr%2FLnmO9mfVN8YT%2F90w3LHcfp6enrSgNTuuOMO5XsPXmZGOuvx6%2BDNl5urPEC4tSDAu6ieziv%2FeKR%2F52tW9vX19e7atctcsGCBdt0365KRznq8GnlhHncYM3N0I8u3bMjU%2BDV%2BjX9W%2BIYFWns961%2Fz2NE9v75geWdnZ%2B%2BRI0cMLRQKaYHmCEGfpM4PIZ9b1bqauv22AykDEhlQ2iP0SXluOp1O%2Bny%2BqKIo3rqFEcJ%2Bid%2FjBiBNqU75xy2tDykTxMIa%2F7nMV%2B0xjj36Oc696ivYRpTDD32Bc6788vPeP82ndrHl0S%2Bz8%2BJPMNh63qz5Pl2gqtn7q5LMZ1a1Lm3Is%2Bcf27U9nganNYJt26sSicQ4MKINDQ1p3rAXvyYJeyWRrDqPqsCBnd8vAa5%2B4Tsmyhq4xS5t0LOSX3ZER0q5KhaLHU8kEmld1%2F2BSCt%2BTeL3yOwIRRTSw9PLMripYUe67aXAjujYtr2yxn9u8mN9O6iLzMOvSYa6dxIJN%2BDT5Fmx%2F%2BCfrp%2F2Blh12RfmzD%2FL%2Bx5GDQToGN7N%2BKKNs7bfp6uoCiQy78RKghaY2v6Q73Y3R6CCkE7F9n9auzsfHe7itQXga%2B8q7BZ1%2BhfueDVIMFWBHvLiOM5K0zSPAGPa6OioqqgKQlpIKXAciZ2t7bB43es58lTh22u2IyeNQLadrQdhS1A0Uq4uQHhgYEDoui5tFAzDco2wBIqQVWt%2FWrbEMCU2GvF4vPX06dOh2fAPbf80K7f%2B81njz4X9RqybZLQLX7iVYMPySbnp8V4SY8fxhlrx1y9%2Fztjf2%2FUEvnAL4ylJ9%2FEn8AabGU9mRWaMJD17%2FoPk2DGktECo%2BCIdtK9%2FL3vv%2B%2F9YecU3q7J%2FbGx8WtvHU3JO%2BteXGsbTe5hkxqTh4IOMr3gbuMVdq%2Bbb2aF%2FPDXqniNVbreRAD0I48nXIZGEfHdhZCq3H086P8eYvial2y%2FxlFtT1jQlUtHIZDJttm2HAUXr6emR1kJIZyRJzRXBKVS1DnF6OJrHjcXllEOUjOlqc6YdGBsb81mWpR49etRKp9MiZWzEsCWaAooyu1fEjuMO5WzV5TuOMyt%2B%2FYbPMzjqnDX%2BmdivSJODD16HmRgAoSBxULUAKy%2B5AV%2Bk3fW5bfLs9usw4oU2iuqn8%2BLP4Qm1nxX7HcsExYNhmEivNi3%2F%2BNG9rHjhKxkcdTh6eA%2BdWz7B4KiDnYnxzPbrmNfxIgKtbyI0bzWaN8xo90PsuOczJKIDNCdkVfZ3n%2Bqf1u55Rf18Jv277ulf8lToAtqij9HeuxMOPMVgy3mzun5UX25Q8iuiyc2FoBADPVy0X1RwN22CnZaV268XAme0xMropH4ajsp8HsJUIJ1OBx3H8QJC6%2Bvrc%2BxWSFsSBYlhiJIk5amiqtaLouXOc7L1KA0LUoYko0AsFpPpdNp59NFHrSVLltip5m2QKdTky0Xnx%2B54WZ5zwbbfTnkspzsgJeB1%2BYZhzMivdDtT%2FlDX%2FRx7%2FGYkJqH6FSTGjuRtr4Z%2F9JEvMNr3DEs2f5jm5a8gOd7Lvt9fy9i9n2TDK%2F8TgMMP%2FzOjfc%2ByeNOH8m32%2Fu49jP72E6x%2FxQ%2Fn3D%2F77%2F8oIHAcBwmc%2B4pbJuU%2F9oOX5AuWDN1TqNV4%2Bp7rQCgEG1YRbDiX4KK%2FASBhAqZEabiE%2BhULOfn7vyOelFXZf3p4ZFrbo3F55v0rJQsf%2Bym%2FfP1PMQYGaR76Dc1P%2FZrDl26c1fWjOu45RhOLAciMgzfirmJIJLLA7H5mHFpaTxBNgp0dDVViP%2F%2FyibxtnyiuUXJt4fi1JX66Ji8AJHUwTTO%2FNko7cuSI07YBpOmWvc8YMi%2F8KRAMjxai0eFnH6PrmXuRjsWic15K69KLioZYbpCwPZBKpWQmk3GOHTvmdHd3y%2BbXg53V9Cr2%2FYorbuPpe9ziIamM%2B%2FvOy%2F%2BdPf%2F99pJjRVNWVA0ymYw0DMPZsWOH3d3dzVT83PbsA39PdPBJBApb3vTHKRNCs%2BXvf%2FiLLN74fppXvJahngc5%2FchnS2yvlO9reiHzAsuItL%2BcVEZiiQiWCJMYOpnneZu20OhfWtLGViKMDvWQMeWs7J9uC7Vewelj9zJ%2F6cs5ffy%2F3dHiJPwNr88G%2BJ9uZcubtgPw5%2BL9n11By%2FqPlvkFQASWsP5191ZtfzKVmtb2ubh%2BOk5upydyDlER4WT9Ws5PJOjYdxepF1xfEkkq5WvZ0nF%2B%2FYRro28%2Bqamq9%2Fvc0QOAZcjK7c9HGleCiyn%2Blx%2BhGK5sAm6GAMuykNnS3FpfX59sdsAxHBzbHWLkRg9CQDRWUPUwnCDhlgs4vu%2FXDD%2FyI0wnxLzWtfk3GbYNUgHbtqXjOHLXrl0OIF%2F9OnCs8mG9x9eCZWVFO7K%2F1%2F3tZcdKLibp8gHZ09Mje3p6puTntpWX%2FitdT36D0b4dkzLPlG8aKbqf%2BQmNi19OQ8ulnP%2BGP055nun4jYuuwkqP8cz2TzE%2B%2BCS2kSCni5HjNbZP3caxnCn5XbtuxTLGGRt8irr564me3k2gbinJ6FHqFmxhpHc752z9MuOD%2BxgfeIJ0rJuVF30ey3SwbBvbcrBtJ5tVn9o%2FlmXnbS3ZN028gYVz6v%2FcdTLVNhfXz5rdP%2BCxF3wI23I4NW8jtmURinYzv3cXAy2bqubbE05TF3DFbBruay%2FoYlx0oijvl5vmOZXbbxUWc5Yu67Rm9JOgtGy%2Fll9NZUkcG2wxddl7f91KVE8TieQdABx48i7Ov%2FLc%2FChCSvcDOI4j5UTRAmeGue9kv3eqmC%2FP0Hbk1MPULTi%2FOmYF%2FJ13bHWHivEeHrvjxSW%2Fe%2BG27VXxo4NP8eyfPgZSoaHtQpo7X0f3nn8nMXYof%2F5K2ky2xYcPYNsZOs55B6oeJjZygNUX38jjv3gFy8%2F%2Fe4zkAKM9jxJpXIcv0MLJp29luOchdF9jIdk0nQ6FI2fct4w0muadU%2F%2FP2d9M0taXHKLz%2BO9YdfQ3Zb9bcfg3DCw4b85sCQVU%2BoYzhP35QvMluhjV3D9fKtaYKdHFKGpToW3aiRMn5HrAsd1SYU4uQGRfSZlFUdqyJGih%2FLHR4ZNYdmF4Iil8u0XTtLzuAIB0ijLDRfy8toEs18VwI075wo6JugbT8XP%2FGokBmpe9svwiP0P%2BC%2F%2F6AdKJPp7903UYSbfQqS%2B8lA0vua1q%2Bw89%2Bo8IVDa%2F%2Bk40bwQEdOf8kf3jfJtX3Ynmy7bJ%2BlFOoYsB0L7mnYz1P8Lx3V%2BlY93%2FQeQ%2BhOJWOFdUtzDh4Il7EYqGL7LI%2Fby5PiqpZVnO33nnFQhcXYw%2F33l5vp27ryKEh%2FGBx2lsu3jO%2FC8qedF%2FBvy1B3%2FGoxd8jMc3fzA%2Fh9j09HfY%2BugNrOj6HQ9fdH3V%2FNnqYryQ%2Fsrtr1IXgwm6GMV8bfHixe51kP2tUlx2ZkInCGT%2BInCDgBeRrceXE1%2BaTHcgJkAtLoNVxHc%2FoATHQJmgiyHtDKpaWvVUEdXxAUZ6H7II4FsAABWzSURBVEHTNCINKymX9uKM%2BYFgC5tf%2FgMGj91N7%2BEfYyS6CtV8KuQLaSOcJMFIO7o3nI%2B4ljGIqiqugI3jFNr4itpk3DYqckp%2B995v0tCyhVDdUtLRQ9jGEOODTyJIEz21g0y8C6wEtpVC0fxIxyA%2B%2BAR6YAFmeoD4wONYmUEcMz4p%2F6I33Ed0cBcHd3yaC17725J9gCNPfJGefd9ifttFZf4f6nmA47u%2BzAte%2B99V%2BX8mXQwl99SaRf8Kx2LtwZ%2Fxq5ffnhckAjjU%2BWoue%2BwmGuMnWTDyLEONq6viz1YXQym6P2eyv1pdjOLPl%2BPrui7OOeccRWttbRWKAjJbEXfiCi%2BvrhWdzMa2Evljjc0r0bKGyexDTlPLdTEecUAoctLln5GGpRjJXhKDjxJuXMdo30N5XYyDD76bDVd9vyS6aRr4J%2BgaTMcHSazvAeoaluBR5RSrgs6Mv%2Bs3r2HRuX%2FHwhWvwucL0PX01%2FGoTnV8TeD1%2BhD2GFbqFI5jcnL3V%2FEIA49XRxUmHo%2BGz%2BfPtunFcax8G033oGDimYK%2F4YqvY6ZHWHzO36BoOpz%2FUQCa%2FupeQLCgvSBoY6WjaL4IlpFE04Ol9gdCU%2FpnrOe31DUsx6NKoj2%2Fy%2B67fjhnyyc4uOMzHHro71h23j%2Fgr1uEkR5h8NhvGDj2S5au%2F1s0RVblf98MuhjF%2FV1N%2F%2BqZcS5%2B4l8RPj9WsKGE4wTqGGrfTOvpPVy6%2B2buu%2FifSQfmVcyfrS6GpsiK7a9WF2PHZ2Jl9jc0NGgXXXSRR9u0aZM6oICiSjQBiiryOn9CQChQOFk6eohU%2FDShgA9F9bBm%2FavxakXOkwJVg9AEXYzHj7jZV4FEKKJkmL9q8%2Fs4ue9WBg99B7P1QtpXX0O0%2B%2Bc0tF7CvIWX51fhua%2BS3GWq4Qm6BtPxEQIn00U4soAjj%2Fwda190y6TDhzPhB%2Fx%2BnNRRnr3%2FzSAkKze%2FNyt0Uh1%2F%2Bfp30H%2F4p3T9%2BcNZtkJdYwdm6jTW%2BD4i8zewfN3b6Tv8U7r%2B%2FJGSNkZyEHN8H6HmSyfn6woBfd6k9k%2Bc4hGK5KJByfBzJvud5BFal1yFT5PYqSO0LrkSn6fA33Dp5%2Bh59vucfOp6HNtdtKPqDZx70fXUNW1GQlX%2BD8ygi%2BHTZte%2Fb77nrdTHewH4wC%2B38h9vuJ%2BMz9UKufruvyKS7IdAgBUjj7Pi7pfw7Tf%2BCUsLVMQPzFIXw6fJiu2vVhfDp8ky%2FyxcuFBftWqVV2tqalJOSwfFo6BJu7BOPNuvdXVNLFv9KtLpMXqO3IWVSdKycA2LV7yMUKQ1f105EncROA7BCboD2lEH1asgbKvkCSCApuZ1NDXfUmLwC176vbLRuZQSqaoIUR0fJD5dQTgjrN78Ebza5LOLM%2BG%2F8JU%2FzR75wJSzl0r4HStfT9vSK4gN7UWoXuoXbMJIDBAdfpJwaD5eDRav%2Bqt8G1QvdU2bMJIDjJ3eRSiyAG0W9jPtzLQy%2B4f7f4%2FX56N92UvxaKBpFouWvXTCqA2Wr3sHy9e9Y078759BF0PXZsf%2F%2BRvuKnvlmTvTndt%2BU%2F5qMztMr4Tvm6Uuhq5Vbn%2B1uhi6Nrl%2FlixZommBQEA6yQyaT4dk0l1crhQeKJsvLSx6aV10YekF5BQpFktQvB7sdAZ9gi6GTGdQfR6cmIUsWsZaaY45n98IunzPBF2DmfhrL7u15EKZa%2F5c2q9pdTS2XlK4MINtLAi2ldiueepoKG4TaKO5ow0l7P8fs7911atpbrsy956MjZffPqW%2F58r%2FlYGfW%2F0Ls9PFwKnc%2Fsve9VTett4iO%2F%2FtrqL%2F3BWd0n5d15ORSMQMh8OKVl9fbxv7xvEsa8a2LJxUBiGqW1AjARHwIT066VOnUSboYqRPx%2FAtXoCtW8hEJt8BTz%2Fw1hnZGy7%2FoZuMDnpRPTrpU4OoE3QNpuJXs5Kyxn%2F%2B8c%2B5%2BPZpuabhzIr%2Fvjsvqsr%2BW%2F760Yr5a%2FofnpV%2FrKDnL%2BR%2F9%2F4NBoNRVVUtraGhwTIOvaNNCfzklLe5HilUZDKDtKyCuNfEV4dF4VXoGkrAi%2BLzkj4VZcePN5bpYixTv7L08Kl%2FOK4vqMeWKk4ijbRsVl34gxn5GUdBCfpQfV7Sp8bY%2BePzynQNpuJXZr9a49f4JfyvvmFndfzA%2F0v%2BKb1%2F87oYj%2Fzmks7FW%2B8%2Fos%2BPoIZDhaoTFYwejFgK89QAj%2Fxw9ZS6GNt%2F%2FLKV886%2F65A%2BP4ISCSNUpeIIZ8ZSJHoHePgHK6fUNajxa%2Fwaf274xfdviS7GvrsvmJUuhhAiQSW6GFl%2BtboDQoiEUomuQY1f49f4c8qv6WLU%2BDV%2BjT8lv6aLUePX%2BDX%2BlPyaLkaNX%2BPX%2BDldDLK6GGJSXYxP%2FVu9oSiiooKXJSvUskvex468Zdq6%2FR%2B7yZ%2FJFeSo8Wv8Gv85xJeSvn1%2FNbUuxhe%2Fs8hUVYGmiep1MZxsyS1bcvKp10xat%2F%2FDn1PTquoWBM19S7DSE0jp1sO0bWr8Gr%2FGn2O%2BW%2BNDYtnw7EOXl%2BhiiJ%2F97Gfqbb%2FaZHm9Cn6fwKsrbpCocPYiHdd405RkDEkmIxk88OaVGzZs6L322mvNBQsWaO%2F%2FjJXUdYFXdwOQqrrLQCsp%2By2l6xjLcvmGUePX%2BDX%2BXPMNU5LOOGQykv1%2Funy5rusFXQzdoxAOKtRFVMJBBa%2Bu5MvOzTQ8yY0cUmmHeMIhlnCQE%2Br2BwKCUEDF7xf4vAqaKqqs2%2B8an0pJ4km7xn8O8zUlQW%2Fvt%2Bjs%2FDi2HePkiW%2BzbPnHnvf%2BaRvbx8XHvstDy6%2Blv27NrPn157bPShdjbH%2FPWfOPaeXuX8E4drkuhscj8PsVImGVurCCT1dQNTh2srSSzrKOV5W%2FRHXcctle3TXKsmWZLobP68XvEwT8WbZa3RzJtrMjGulg2UqZLkCN%2F9zhJ%2BN7iYQa8PsEYyP7CIfq8HnFWbG%2Fq%2Btr09q%2BZMmH5sw%2FK5JPoQYCLMkcYMx77qzt9%2BvMShfD8CsV239D586qdDE%2B8%2BUtgIJpSnRNlOti5L97IQtTBizBwgWX03XqvjzKtqeavxSqWyMp08WwbB3DlIi0xLJsd%2FhTYQTNJVBMyx0GWbYs0wWoht91%2FKssWfqRs8afC%2FvNzCDpdA%2B6bwEB%2F6JJ2Zn0AKlUDx69CZ9%2F0XPG%2Fr6Bp%2FH5mhiPO%2FT270HX5xGLZ%2Bsx2mn6%2B39OOtWDxAJUdG8rLS1v5ODBL9Gx%2BDNV2T%2BjLkbcmZP%2B9Rlj6ANHSVgG8%2BM7iTW8DrJFEarla5nZ6WKk0rJi%2B0lXqYuRcDBMiWm55e%2FLdTGy85tEykFKiccjcN9m%2BEp0MeY32JMMUVznZDIOiaRDKiPLdTFSL8CybLSkQ%2B4tSTWbO5VxnWOak%2BgCVMH3hz%2FAwJB11vhnYr8SMzne9SVscxiEQCJRVR9LOj6E19%2Fijthsi6PH%2FgXLGMq3URQvHe0fQtWbz4r9tuWK3BiGhe2o0%2FJPnDzA4o7LGByyONb1DB2L3sPAkIVlJjl67IvU11%2BA3%2FcSAoGlaJ4QY2NP8PgTN5NKD1LXaFdl%2F0y6GKF6a07697wT%2F80T6lrax56mY%2FQpRPBp%2BuvXzur6ycRmp4uRGLUqtp%2Fx6nQxhkYsVxfDkGQMp1wXw1LcOYgQuLoYqphUF2NBY%2Fkr2NwIwjAdUmlJKuWU6WIkU5tRMm65LUEhNO%2Fd87d5zrr1t015zK0c5pbld5xyXYCp%2BJXfwWfGHx3eQU%2Fv7UgsAr7FpNInC7ZXwe8%2B8Q3GY4doX%2Fh2GudvJZ3p5%2FChzzA69mVWrf4XAE4c%2FybjsUMsbHtbvs2hg9czMvovrFz15Tn3z9EjN%2BYfBI4jWbb805Py9z%2F2LvfiFDA0clP%2B708P3wQoBAJL8fmWo%2Ftejg24xdJtEOcRCM2n59RnicWdquyfSRejJWqfef9KyaIDd%2FGTzd%2FENE%2FTOvQHWg7%2FnmdXnjOr68dKzk4XIxGzK7f%2FE1XqYoxflbffsmS5LkbdcldZS0qHjJHNgrqrJUp0MY6e3ENP%2F0NIx2Fh80U0zduYF9ywLJmNQLJMFyO0%2BL15p4ui2nkdS27kwLNu6bN0xh2GLlr8OQ4euK7kWC5K5zpjoi7AVPzCRf5FYvF9CCnYcN4PpnzKz5Z%2F6Oi3WNj6VuYveDGjo48xPPq1Etsr5eu%2B9dSJdgLhy0hnHCwriGkFSCR68jzdt44IC0vaWHaQROIUhilnZf90m8%2B3hZHRP9FQfxkjow%2B62fRJ%2BKvWuAFxz9NvZcPGHwLw9O7i%2FbezcvU1ZX4BEOpCVq7%2BbtX2z6SLMRfXz9KhnZz0dBJ1gnTpK9iSSLC06%2Ffc2%2FGBUl2MCvladmRQrS5GOiMrt79qXQwnXxNCMokuRmiJxMy%2B8lBNgVAKAaJYFyOT8RPwraW77wFGonezcqmf%2BsiKfN7Cstwk5URdjCs6Jl8cqmlNBQ2MbLD3eFrLjpUNeSfoAkzFzyerlv4DPd0%2FIDr%2B5JTMM%2BFbZppT%2Fb%2BhruFFRCIXsHbdD6c9z1T8uvpLsYxxjhz6CrH4fhw7SV4XI8uL1F2K5Z%2B8jeNMze%2Ft%2BS9sK04svp9waDWx%2BDP4fIvIpE8SDG0gGt1JZ%2BeniMcPEYvtwUj3sWTpR7Fsd17rvoefXBej2D%2BWZedtLd030T0tc%2Br%2FGXUx5uD6WXviFzy67F3YNnSH12JbFmHrFAtG9tNXNM2olD9rXQynCvur1cWwJz4sJ9HFcN8%2BgCWKV2LJEl0Mn3cxggYSSbdS8eHj97N%2BdWdhCCpz87FJdDH%2Bh7fo%2BOOEQuvnnLv7qTcDYBr97Hn6bSW%2F23jef1XFisX2c%2FTIjYCgLrKJxqar6O%2F9Mal0V1VtJttSiaM40qC15Q2oWpBE4hjLl1%2FHnt3vZPHid3P0yBDR6C5CgVXoehN9vT8kOvYEmqd%2BznxlOQaaovN82XyZEVYNPsiagfvLfrd64P4pA8Rsthl1MarYqtbFmGbTTpw4IZdvKYpSE0p6lehi2BKhBvLHxmKnsB1ZMjwpVMct1cXIJlXLh5fZg0JMf6x4GDeZLsBU%2FPznMIZparpi2jaz4Z%2B36b%2FIpAc5cuRGTHMQAK93Eees%2BVLV%2FK7jNyOEwtpzb0XTQwAMnBL5rPpMbYr9N5Hf0rqN8dguentvp63tLYVMvaIiBCiKB4CR0T%2BBouL1txXaCFHWPxP5u5%2B6GiFcSYSnd78l3%2B7p3W8BqSCERiK2l%2Fr6zXPm%2F5l0Mc70%2BtnYdw9%2FWvF37Fx2Tf7YC7p%2BxJUHb2bV6e1sX%2F3hqvmz1cU4T%2Byu3P4qdTEm1i0u18XI5WVEQXZvsk4orPPO6mKoev5YvoCnWl63v9%2Ba%2BuZVFdWV48Is08UAA2XCE0fK6vgA0dEn0DSVcHhZRW8cquX7AwtYt%2F6rDA3ez8DpX2EaPVOuRJ2KL6WDIE3A34LuCxU9dYddXQwFVxdjmjaqOjW%2Ff%2BAHRMLnEQwuIpPuwnFGicf3IRSD%2BPhTGEYvgjS2nUKofpAGieRedM88LHuIZGIPtjOMdJKT8jef%2FyNi0b0cO%2F6vbNj4%2FZJ9gBNdt9Df9yMaG8sDxOjIDrpPfpv1G2%2Bvyv8z6mIos%2B9f4Vhs6LubOzbdXMJ5tu2lXHH0m8zLnKI5cYTT4c6q%2BLPWxVAqt79qXQyl3P4yXQxFcVW9J67AKtbF0DQwrXT%2BWH3dMjzZsubFymwTdTH%2B8KSYMkqHwx2YZj%2Fp1FMEA6sZjz6W1zs4dvSTrFnzldIkEOW6ANPxAZKJnUTCHegeMePTazb8vXveRWvr22hbeCX%2BgJ%2Be7u%2BXnWtmvopX96KIGNI5jSNNek%2Fehq5ZoOloqo3i0fB5fYhJ2kjVg6raKEKblL9mzeexrTE09XUoqga8x311Pc9N2s5vKuhMWkYMTQ9jWUm07EqeSvwTi%2F%2BJSHgxukcQTzyU3wdYseL9HD36bxw%2Fdj1LOt6P19%2BGYYwyMvRHTg%2FdS8fiv8Gjiar8P5MuRnEfVNO%2FXjPOpUdvRfh8WMEGdK2Io9RxumkDbePPcPnJ2%2FjdOR8n5W2smD9bXQxPkQ0z2V%2BtLsaD7zhWZn%2BJLsaxqMCjuZoWatF74Im6GIZ5gnRmxNXFUDRWL7sKn1cpCRCOlGW6GA%2FuFdlhbPndtaLz7fT0%2FpCRoZ9g12%2Bmre2NxGO%2FI1J3Pg0NF%2Bf57hTInc5M1AWYju%2FOw3oJh%2BZzout6Vq26ccoLarb8gN%2BPpJujhz%2Forjhd9tYSuyvlL136JgYH7qLn5A1Z%2FyvURdoxzWGkdQRfeA1Llr6Rwf7yNoYxhG0cxhdaMwVfRfHPr2zi6a3L%2Fhuqyn5pn6S5%2BVJ8XgVpn8jv57Zz11xH36k7OdX7rzjSzW2pWh2rV32UcGRd1f6fURdjQh9Uyn%2FHUx%2BkPt0HwN8%2F%2Fgb%2B%2FeKfkc7mYq559N3UWYMQCLAqtZtVT76FWy69C1P1V8SfrS6G36dUbH%2FVuhhZPxXzS3QxTsRd4Q1dE6jZb3PmXptEQvNZvPAKMkaUvtP3Y1tJmuevoL11KwF%2FU35u4cjCtzon1u13F165UxNFlM59fN5zmTfvptK538aby6J%2BIQFaHR%2FAp6sgxuhc%2Fp6SZb9zxd%2B8%2BVu5GD3lqKESftvCV9DSegnx2LOgeKkLr8PMnCYW30coOA%2BvV7Co%2FRW0tl5CPHYAKXQi2TbR8T0EA%2FPweETV9lcyqprJ%2FtHRP%2BH1eWlr3YrHI9A8DgvbLs%2BPMPOvDZdug6Xb5sT%2FM%2BlieHUxK%2F5PLvvP8tFK9t%2BfXP6jMv8ogKdC%2Fmx1MTweUbH91epieHUxqX%2Fyuhi5%2FALF4qXZbcOawpKK5qZJEkw5IVVc%2BT0EZboYbo6jdGRSyVai4Zs9gRCyTBdgJv7qc754Vvlzab%2BmRmhoKMjgef0t%2BVWU%2BameGqG%2B%2FoJCx%2FtaaPK1uAFa%2Fs%2FYP3%2Fei2hqelE%2BJ7X23Jv%2FYv37fOPPRhejGv5Fr3wgb%2BOJotXoX%2Fp2sfUnyj9Lll%2Bmi%2BF%2B3xyE5Q4zlCq%2FT577J%2FeudqIuhm2DjSzLbzyz%2F%2F0zPMEk5669pSzHMVEXYCq%2BqMz0Gv95yl%2Bx4qvT8g1Tzor%2Fwe0vq%2BgtSe5N%2Fte3%2FrZi%2Fsq998zKP6aUfzH%2Fl%2Bli2IPva2PBLaccR5a90qp0y8F3%2Ff6Skrr6iqKIzsZblj7T%2F97jEy1e1vmNGbn5FWTZT7TrD5Xzq%2F8QNX6ND1%2B%2B8N7quEWrHP%2Bf8w9ZXYwTT76m0zTdb4wZxix%2BTMmDv9h4s23bO4rq6meEEOm2traRwQNvXmnk%2BLP8efCXNX6NX%2BP%2FJfl5XYydO3f2777%2F4mXxeLzTcZyVs6mrryjKQeDIZHX7f%2FGLX%2FTtvu%2BiZfF4vNOyrJWO47QBFfGBhKIoPZqm1fg1fo3%2FF%2BRr2eq19mc%2F%2B9nUj370o1OpVCpqWdax2dTV1zRtyrr9W7duTfb29ub5QNV8RVFq%2FBq%2Fxv8L8mu6GDV%2BjV%2FjT8n%2F%2FwEPV3J1%2Fv5S1QAAAABJRU5ErkJggg%3D%3D"
  };
  //
  function rgb2hsl(r, g, b) {
    r /= 255;
    g /= 255;
    b /= 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    const delta = max - min;
    //
    const lum = (max + min) / 2;
    let hue = 0, sat = 0;
    //
    if (delta !== 0) {
      sat = lum > 0.5 ? delta / (2 - max - min) : delta / (max + min);
      if (max == r)
        hue = ((g - b) / delta + (g < b ? 6 : 0)) * 60;
      else if (max == g)
        hue = ((b - r) / delta + 2) * 60;
      else if (max == b)
        hue = ((r - g) / delta + 4) * 60;
    }
    return [Math.round(hue), Math.round(sat * 100), Math.round(lum * 100)];
  }
  ;
  //
  class Storage {
    //
    constructor(name, defaults) {
      this.name = name;
      this.default = defaults;
      this.data = { ...defaults }; // Initiale Kopie von defaults in data
    }
    reset() {
      this.data = { ...this.default };
      this.save();
    }
    save() {
      return GM.setValue(this.name, this.stringify());
    }
    async load() {
      const storedData = await GM.getValue(this.name, null);
      this.read(storedData);
    }
    read(str) {
      if (str === null) {
        this.data = { ...this.default };
        return;
      }
      try {
        const parsedData = this.parse(str);
        this.data = { ...this.default };
        for (const key of Object.keys(parsedData)) {
          this.data[key] = parsedData[key];
        }
      }
      catch (ex) {
        console.error(`BLICK error: Failed to parse stored settings for ${this.name}:`, ex);
        this.data = { ...this.default };
      }
    }
    set(key, value = this.default[key]) {
      if (key in this.data) {
        this.data[key] = value ?? this.default[key];
        this.save();
      }
      else {
        console.warn(`Blick error: Property ${String(key)} does not exist on Settings`);
      }
    }
    static replacer(key, value) {
      if (value instanceof Map) {
        return { _type: 'Map', value: Array.from(value.entries()) };
      }
      return value;
    }
    static reviver(key, value) {
      if (value && value._type === 'Map') {
        return new Map(value.value);
      }
      return value;
    }
    stringify() {
      return JSON.stringify(this.data, Storage.replacer);
    }
    parse(str) {
      return JSON.parse(str, Storage.reviver);
    }
  }
  class LoginCredents extends Storage {
    constructor() {
      let def = {
        name: "",
        pw: "",
        savelogin: true,
        token: "",
        exp: null
      };
      super("loginCredents", def);
    }
  }
  class Sync {
    constructor() {
      this.access = 0;
      this.serverURL = "https://phi.pf-control.de/tgchan/API.php";
      this.creds = new LoginCredents();
    }
    async login(username, password, savelogin) {
      this.creds.data.savelogin = savelogin;
      if (savelogin) {
        this.creds.data.name = username;
        this.creds.data.pw = password;
      }
      else {
        this.creds.data.name = this.creds.data.pw = "";
      }
      this.creds.save();
      return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({
          method: 'POST',
          url: this.serverURL,
          headers: {
            'Content-Type': 'application/json',
          },
          data: JSON.stringify({
            type: "test",
            username: username,
            password: password
          }),
          onload: (response) => {
            if (response.status === 200) {
              try {
                const result = JSON.parse(response.responseText);
                if (result.token !== undefined) {
                  this.creds.data.token = result.token;
                  this.access = (result.access !== undefined) ? parseInt(result.access) : 0;
                  this.creds.data.name = result.name ?? "undefined";
                  this.creds.data.exp = new Date();
                  this.creds.save();
                  resolve(true);
                }
                else {
                  this.creds.data.token = "";
                  this.creds.data.exp = null;
                  this.creds.save();
                  resolve(false);
                }
              }
              catch (ex) {
                console.log("BLICK login error:", ex, response.responseText);
                reject(`Error: ${response.status}, ${response.response}`);
              }
            }
            else {
              this.creds.data.token = "";
              this.creds.data.exp = null;
              this.creds.save();
              try {
                const result = JSON.parse(response.responseText);
                reject(`Error: ${response.status}, ${result.error}`);
              }
              catch (ex) {
                reject(`Error: ${response.status}, ${response.response}`);
              }
            }
          },
          onerror: () => {
            this.creds.data.token = "";
            this.creds.data.exp = null;
            this.creds.save();
            reject('Request failed');
          }
        });
      });
    }
    async upload(name, storage) {
      if (storage === null)
        return;
      if (!this.creds.data.token) {
        throw new Error('Not authenticated');
      }
      return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({
          method: 'POST',
          url: this.serverURL,
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${this.creds.data.token}`
          },
          data: JSON.stringify({
            type: 'upload',
            token: this.creds.data.token,
            data: storage.data,
            obj: name
          }, Storage.replacer),
          onload: (response) => {
            if (response.status === 200) {
              try {
                let result = JSON.parse(response.responseText);
                if (result.success === undefined) {
                  reject(`Error: ${response.status}, ${result}`);
                }
                else {
                  if (result.access !== undefined)
                    this.access = result.access;
                  resolve();
                }
              }
              catch (ex) {
                console.log("BLICK upload error ", ex);
                reject(`Error: ${response.status}, ${response.response}`);
              }
            }
            else {
              try {
                const result = JSON.parse(response.responseText);
                reject(`Error: ${response.status}, ${result.error}`);
              }
              catch (ex) {
                console.log("BLICK upload error ", ex);
                reject(`Error: ${response.status}, ${response.response}`);
              }
            }
          },
          onerror: () => {
            reject('Request failed');
          }
        });
      });
    }
    async download(name, storage) {
      if (storage === null)
        return;
      if (!this.creds.data.token) {
        throw new Error('Not authenticated');
      }
      return new Promise((resolve, reject) => {
        GM.xmlHttpRequest({
          method: 'POST',
          url: this.serverURL,
          data: JSON.stringify({
            type: 'download',
            token: this.creds.data.token,
            obj: name
          }),
          headers: {
            'Authorization': `Bearer ${this.creds.data.token}`
          },
          onload: (response) => {
            if (response.status === 200) {
              // const serverData: T = JSON.parse(response.responseText);
              storage.read(response.responseText);
              // storage.data = { ...serverData };
              resolve();
            }
            else {
              try {
                const result = JSON.parse(response.responseText);
                reject(`Error: ${response.status}, ${result.error}`);
              }
              catch (ex) {
                reject(`Error: ${response.status}, ${response.response}`);
              }
            }
          },
          onerror: () => {
            reject('Request failed');
          }
        });
      });
    }
  }
  class Settings extends Storage {
    //
    constructor() {
      const def = {
        changeFont: true,
        fontsize: 12,
        fonttype: 1,
        paragraphMargin: true,
        imageHover: true,
        invertCol: false,
        replyForm: true,
        manageWatchlist: true,
        autoUpdWbar: false
      };
      super("settings", def);
      this.fontTypeList = ["unset", "Georgia", "Palatino Linotype", "Book Antiqua", "Tahoma", "Arial", "Helvetica", "Verdana", "Times New Roman"];
    }
    ;
  }
  class EditorConfig extends Storage {
    constructor() {
      let def = {
        colorList: Array(15).fill("#ffffff"),
        iconList: [
          { label: "Favourite", arr: [] },
          { label: "+", arr: [] }
        ],
        saveText: "",
        autoSave: true
      };
      super("editorConfig", def);
    }
  }
  //
  //GUI interaction
  class Sidebar {
    constructor(dom, set, syncr) {
      this.barStyle = `
      #BLICK_bar {width:20px;overflow:hidden auto;position:fixed;transition:width 0.5s,opacity 0.5s, height 0.5s; right:0px;height:30px;background-color:#9ad;z-index:9;opacity:0.5;top:50px;border:2px ridge #ddf;border-top-left-radius:25px;border-bottom-left-radius:25px;}
      #BLICK_bar .BLICK_cont {width:250px;font: 16px/2em georgia, Palatino Linotype, Book Antiqua, Tahoma;padding-left: 20px;visibility:hidden;}
      #BLICK_bar:hover {width:270px;opacity:1;height:min(450px, calc(95vh - 50px));}
      #BLICK_bar:hover .BLICK_cont{visibility:visible;}
      #BLICK_bar .BLICK_button {background-color: #DDDDFF;border: 1px ridge #CCCCCC;border-radius: 10px 10px 10px 10px;cursor: pointer;display: inline-block;font: 30px/17px georgia;height: 20px;margin: 5px;text-align: center;vertical-align: middle;width: 20px;}
      #BLICK_bar .BLICK_button:hover {background-color: #aaf;}
      #BLICK_bar .BLICK_button:active {background-color: #77d;}
      #BLICK_bar input {margin: 5px;}
      #BLICK_bar label #BLICK_bar input[type=checkbox] {cursor:pointer;}
      #BLICK_bar input[type="submit"]:hover {opacity: 0.9;background-color: #32AFFB;}
      #BLICK_bar input[type="submit"] {cursor: pointer;padding: 4px 8px;font-size: 14px;font-family: Arial, sans-serif;border: none;border-radius: 5px;transition: background-color 0.5s;background-color: #2196F3;color: white;font-weight:bold;}
      #BLICK_bar select {cursor:pointer;font-size: 16px;}
      #BLICK_bar .BLICK_Sec {background: #ccf;padding: 0 5px;border-radius: 5px;width: 230px;  margin: 5px 0;}
      #BLICK_bar .BLICK_sec_buts {display:flex;flex-direction: row;justify-content: space-evenly;}
      #BLICK_imgbox {position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);max-width:90vw;max-height:90vh;z-index:777;display:none;}
      #BLICK_imgbox .loading{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}
      #BLICK_imgbox .imgEl{width:100%;height:auto;display:none;}
      #BLICK_sync_updownSec{display:none;flex-direction: row;justify-content: space-evenly;}
      #BLICK_sync_loginHello{color:green;font-weight:bold;white-space:nowrap;overflow: hidden;}
      #BLICK_sync_loginAccess{padding-left:10px;color:green;font-size: 8pt;height: 10px;line-height: 3px;}
      #BLICK_fonttype{width:130px;}
      .BLICK_floatButs{border:none;background:none;cursor:pointer;position:absolute;top:0;width:16px;height:16px;padding:0;}
      .BLICK_floatButs:hover{filter:brightness(120%);}
      #BLICK_set_export{right:0px}
      #BLICK_set_import{right:18px}
      #BLICK_bar #BLICK_fontsize{width: 30px;height: 20px;margin: 0;box-sizing: content-box;padding: 0;vertical-align: middle;text-align: center;border: 1px solid #DDDDF0; z-index: 2;position: relative;}
      #BLICK_bar #BLICK_plus{border-radius: 10px 0px 0px 10px;margin:0;}
      #BLICK_bar #BLICK_minus{border-radius: 0px 10px 10px 0px;margin:0;}
  `;
      this.barHTML = `
      <div class='BLICK_cont' >
        <div class="BLICK_row">
          <button id='BLICK_set_export' class='BLICK_floatButs' title='Export settings to file'>💾</button>
          <button id='BLICK_set_import' class='BLICK_floatButs' title='Import settings from file'>📁</button>
          <input id='BLICK_set_importFile' type="file" id="fileInput" style="display: none;" accept=".json">
          <input type="checkbox" id="BLICK_changeFont" checked="ckecked">
          <label class="BLICK_title">Font size:</label>
          <span class="BLICK_button" id="BLICK_plus">+</span>
          <input type="text" id="BLICK_fontsize" value="0"/>
          <span class="BLICK_button" id="BLICK_minus">-</span>
          <br/>
          <label style='margin-left: 28px;' class="BLICK_title">Font type: </label>
          <select id="BLICK_fonttype">
          </select>
        </div>
        <div class="BLICK_row">
          <span class="BLICK_title">
            <input type="checkbox" id="BLICK_paragraphMargin" alt="Paragraph margin" checked="ckecked">
            <label for="BLICK_paragraphMargin">Paragraph margin</label>
          </span>
        </div>
        <div class="BLICK_row">
          <span class="BLICK_title">
            <input type="checkbox" id="BLICK_imageHover" alt="View image by hover" checked="ckecked">
            <label for="BLICK_imageHover">View image by hover</label>
          </span>
        </div>
        <div class="BLICK_row">
          <span class="BLICK_title">
            <input type="checkbox" id="BLICK_invertCol" alt="Invert colours">
            <label for="BLICK_invertCol">Invert colours</label>
          </span>
        </div>
        <div class="BLICK_row">
          <span class="BLICK_title">
            <input type="checkbox" id="BLICK_replyForm" alt="Change reply form" checked="ckecked">
            <label for="BLICK_replyForm">Change reply form</label>
          </span>
        </div>
        <div class="BLICK_row">
          <span class="BLICK_title">
            <input type="checkbox" id="BLICK_manageWatchlist" alt="Manage watch list" checked="ckecked">
            <label for="BLICK_manageWatchlist">Manage watch list</label>
          </span>
        </div>
        <div class="BLICK_row BLICK_Sec">
          <span class="BLICK_title">Synchronize Webserver:</span>
          <br>
          <div id='BLICK_sync_loginSec'>
            <input type="input" placeholder="name" value="" id="BLICK_sync_user" style="width:30%;" />
            <input style="width:30%;" type="password" placeholder="password" value="" id="BLICK_sync_pass" />
            <label for="BLICK_sync_savelogin" style='width:70%;display: block;cursor: pointer;'><input type="checkbox" id='BLICK_sync_savelogin'/>Store Login Data</label>
            <input type="submit" onclick="return false;" value="Login" id="BLICK_sync_loggin" />
            <a href="https://phi.pf-control.de/tgchan/reg.php" target="_blank">Register Account</a>
          </div>
          <div id='BLICK_sync_loginHello'></div>
          <div id='BLICK_sync_loginAccess'></div>
          <div id='BLICK_sync_updownSec'>
            <input type='submit' onclick='return false;' id='BLICK_sync_upload' value='Upload'/>
            <input type='submit' onclick='return false;' id='BLICK_sync_download' value='Download'/>
          </div>
        </div>
        <div class="BLICK_row BLICK_Sec">
          <span class="BLICK_title">Export Thread to file:</span>
          <br>
          <div class='BLICK_read_readExpButs'>
            <input type="submit" id="BLICK_epub" value="Epub" onclick="return false;">
            <input type="submit" id="BLICK_cbz" value="CBZ" onclick="return false;">
          </div>
        </div>
      </div>
  `;
      this.dom = dom;
      this.syncr = syncr;
      this.setting = set;
      this.addElements();
      this.addEvents();
      this.fexp = new FileExport();
    }
    restart() {
      this.loadSettings();
      this.dom.watchbar?.destroy();
      this.dom.watchbar = null;
      this.dom.repform.removeBar();
      this.dom.initStyles(); //resets watchbar and repform
    }
    //
    loadSettings() {
      let el;
      for (const key in this.setting.data) {
        el = document.querySelector("#BLICK_" + key);
        if (el === null)
          continue;
        if (typeof (this.setting.data[key]) === "boolean")
          el.checked = this.setting.data[key];
        if (typeof (this.setting.data[key]) === "number")
          el.value = String(this.setting.data[key]);
      }
      el = document.querySelector(`#BLICK_fonttype option[value='${this.setting.data.fonttype}']`);
      if (el !== null)
        el.setAttribute("selected", "selected");
      this.syncr.creds.load().then(() => {
        document.getElementById("BLICK_sync_user")?.setAttribute("value", this.syncr.creds.data.name);
        document.getElementById("BLICK_sync_pass")?.setAttribute("value", this.syncr.creds.data.pw);
        const checkbox = document.getElementById("BLICK_sync_savelogin");
        if (checkbox) {
          checkbox.checked = this.syncr.creds.data.savelogin;
        }
      });
    }
    //
    fillFontList() {
      const cont = document.querySelector("#BLICK_fonttype");
      if (cont === null)
        return console.log("BLICK error: language selector not found");
      cont.innerHTML = this.setting.fontTypeList.map((lang, index) => {
        return `<option value='${index}'>${lang}</option>`;
      }).join('');
    }
    //
    addElements() {
      this.tabSet = document.createElement("div");
      this.tabSet.id = "BLICK_bar";
      this.tabSet.innerHTML = this.barHTML;
      document.body.appendChild(this.tabSet);
      this.fillFontList();
      //
      this.tabStyle = document.createElement("style");
      this.tabStyle.id = "BLICK_barStyle";
      this.tabStyle.innerHTML = this.barStyle;
      document.body.appendChild(this.tabStyle);
    }
    //
    addEvents() {
      document.getElementById("BLICK_plus")?.addEventListener("click", () => {
        let num = this.setting.data.fontsize + 1;
        if (num <= 6)
          num = 6;
        this.setting.set("fontsize", num);
        document.getElementById("BLICK_fontsize").value = String(this.setting.data.fontsize);
        this.dom.setFontsize(true);
      });
      document.getElementById("BLICK_minus")?.addEventListener("click", () => {
        let num = this.setting.data.fontsize - 1;
        if (num < 6)
          return;
        this.setting.set("fontsize", num);
        document.getElementById("BLICK_fontsize").value = String(this.setting.data.fontsize);
        this.dom.setFontsize(true);
      });
      document.getElementById("BLICK_fontsize")?.addEventListener("exit", (ev) => {
        let num = parseInt(ev.target?.value ?? this.setting.default.fontsize);
        if (num > 6)
          this.setting.set("fontsize", num);
        else
          ev.target.value = String(this.setting.data.fontsize);
        this.dom.setFontsize(true);
      });
      document.getElementById("BLICK_changeFont")?.addEventListener("change", (ev) => {
        if (!(ev.target instanceof HTMLInputElement))
          return;
        const state = ev.target?.checked;
        this.setting.set("changeFont", state);
        this.dom.setFontsize(true);
      });
      document.getElementById("BLICK_fonttype")?.addEventListener("change", (ev) => {
        if (!(ev.target instanceof HTMLSelectElement))
          return;
        const sel = parseInt(ev.target?.value ?? 0);
        if (sel >= 0 && sel < this.setting.fontTypeList.length)
          this.setting.set("fonttype", sel);
        else
          this.setting.set("fonttype");
        this.dom.setFontsize(true);
      });
      document.getElementById("BLICK_paragraphMargin")?.addEventListener("change", (ev) => {
        if (!(ev.target instanceof HTMLInputElement))
          return;
        this.setting.set("paragraphMargin", ev.target?.checked);
        this.dom.setMargin(true);
      });
      document.getElementById("BLICK_invertCol")?.addEventListener("change", (ev) => {
        if (!(ev.target instanceof HTMLInputElement))
          return;
        this.setting.set("invertCol", ev.target?.checked);
        this.dom.setInvert(true);
      });
      document.getElementById("BLICK_imageHover")?.addEventListener("change", (ev) => {
        if (!(ev.target instanceof HTMLInputElement))
          return;
        this.setting.set("imageHover", ev.target?.checked);
        this.dom.setHoverImg();
      });
      document.getElementById("BLICK_replyForm")?.addEventListener("change", (ev) => {
        if (!(ev.target instanceof HTMLInputElement))
          return;
        this.setting.set("replyForm", ev.target?.checked);
        this.dom.setReplyForm(true);
      });
      document.getElementById("BLICK_manageWatchlist")?.addEventListener("click", (ev) => {
        if (!(ev.target instanceof HTMLInputElement))
          return;
        this.setting.set("manageWatchlist", ev.target.checked);
        this.dom.setWatchbar();
      });
      document.getElementById("BLICK_sync_loggin")?.addEventListener("click", (ev) => {
        let nam = document.getElementById("BLICK_sync_user")?.value;
        let pw = document.getElementById("BLICK_sync_pass")?.value;
        let savelogin = document.getElementById("BLICK_sync_savelogin")?.checked;
        this.syncr.login(nam, pw, savelogin).then((ret) => {
          if (ret === true) { //logged in
            document.getElementById("BLICK_sync_updownSec")?.style.setProperty("display", "flex");
            document.getElementById("BLICK_sync_loginSec")?.style.setProperty("display", "none");
            let loginHello = document.getElementById("BLICK_sync_loginHello");
            if (loginHello)
              loginHello.innerHTML = "Hello, " + this.syncr.creds.data.name + "!";
            //
            let loginAccess = document.getElementById("BLICK_sync_loginAccess");
            if (!loginAccess)
              return;
            if (this.syncr.access > 0) {
              loginAccess.innerHTML = "Last uploaded: " + this.formatDate(this.syncr.access * 1000);
            }
            else {
              loginAccess.innerHTML = "No data uploaded";
            }
          }
          else {
            alert(`Login failed without reason!`);
          }
        }).catch(ex => {
          alert(`Login failed!\n${ex}`);
          console.log("Login failed", ex);
        });
      });
      document.getElementById("BLICK_sync_upload")?.addEventListener("click", (ev) => {
        let loginHello = document.getElementById("BLICK_sync_loginHello");
        let loginAccess = document.getElementById("BLICK_sync_loginAccess");
        if (loginHello)
          loginHello.innerHTML = "Connecting...";
        Promise.all([
          this.syncr.upload("sidebar", this.setting),
          this.syncr.upload("editor", this.dom.repform.editConf),
          this.syncr.upload("watchbar", this.dom.watchbar?.wdata ?? null)
        ]).then(res => {
          if (loginHello)
            loginHello.innerHTML = "Upload successful!";
          if (loginAccess)
            loginAccess.innerHTML = "Last uploaded: " + this.formatDate(this.syncr.access * 1000);
          console.log("Upload successful!");
        }).catch(ex => {
          if (loginHello)
            loginHello.innerHTML = "Error.";
          alert(`Upload failed!\n${ex}`);
          console.log("Upload failed", ex);
        });
      });
      document.getElementById("BLICK_sync_download")?.addEventListener("click", (ev) => {
        let loginHello = document.getElementById("BLICK_sync_loginHello");
        if (loginHello)
          loginHello.innerHTML = "Connecting...";
        Promise.all([
          this.syncr.download("sidebar", this.setting),
          this.syncr.download("editor", this.dom.repform.editConf),
          this.syncr.download("watchbar", this.dom.watchbar?.wdata ?? null),
        ]).then(res => {
          Promise.all([
            this.setting.save(),
            this.dom.repform.editConf.save(),
            this.dom.watchbar?.wdata.save()
          ]).then(() => {
            if (loginHello)
              loginHello.innerHTML = "Download successful!";
            console.log("Download successful!");
            // alert("Download successful!\nRefresh the page to update the settings.");
            this.restart();
            //						alert("Data imported! Reload the page to apply the settings.");
          });
        }).catch(ex => {
          if (loginHello)
            loginHello.innerHTML = "Error.";
          alert(`Download failed!\n${ex}`);
          console.log("Download failed", ex);
        });
      });
      document.getElementById("BLICK_set_export")?.addEventListener("click", () => {
        let expObj = {
          settings: this.setting.stringify(),
          editor: this.dom.repform.editConf.stringify(),
          watchbar: this.dom.watchbar?.wdata.stringify() ?? null
        };
        const jsonString = JSON.stringify(expObj, null, 2);
        const blob = new Blob([jsonString], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'questden_BLICK_settings.json';
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
      });
      document.getElementById("BLICK_set_import")?.addEventListener("click", () => {
        document.getElementById('BLICK_set_importFile')?.click();
      });
      document.getElementById("BLICK_set_importFile")?.addEventListener("change", (event) => {
        if (event.target == null)
          return;
        const file = event.target.files?.item(0) ?? null;
        if (file === null)
          return;
        const reader = new FileReader();
        reader.onload = (e) => {
          try {
            const jsonString = e.target?.result || null;
            if (jsonString === null)
              return;
            const jsonData = JSON.parse(jsonString);
            if (jsonData.settings !== undefined) {
              this.setting.read(jsonData.settings);
              this.setting.save();
            }
            if (jsonData.editor !== undefined) {
              this.dom.repform.editConf.read(jsonData.editor);
              this.dom.repform.editConf.save();
            }
            if (jsonData.watchbar !== undefined) {
              this.dom.watchbar?.wdata.read(jsonData.watchbar);
              this.dom.watchbar?.wdata.save();
            }
            this.restart();
            //						alert("Data imported! Reload the page to apply the settings.");
          }
          catch (error) {
            console.error('Fehler beim Parsen der JSON-Datei:', error);
          }
        };
        reader.readAsText(file); // Lese die Datei als Text
      });
      document.getElementById("BLICK_epub")?.addEventListener("click", () => {
        this.fexp.showExportForm("epub");
      });
      document.getElementById("BLICK_cbz")?.addEventListener("click", () => {
        this.fexp.showExportForm("cbz");
      });
    }
    formatDate(uixTS = Date.now()) {
      const date = new Date(uixTS);
      const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
      const options = {
        year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', second: '2-digit',
        timeZone: userTimezone
      };
      return new Intl.DateTimeFormat('de-DE', options).format(date);
    }
  }
  ;
  //
  //
  class ImageManager {
    constructor(container, conf, clickCallback) {
      this.clickCallback = clickCallback;
      this.container = container;
      this.editConf = conf;
      this.selectedItems = [];
      this.startX = 0;
      this.startY = 0;
      this.selectionBox = null;
      this.allIcons = [];
      this.shownIcons = [];
      this.curActTab = -1;
      this.spliterDraggin = false;
      if (this.editConf.data.iconList === null) {
        this.editConf.set("iconList", [
          { label: "Favourite", arr: [] },
          { label: "+", arr: [] }
        ]);
      }
      this.itemCount = 0;
      this.itemsPerLoad = 40;
      this.initialize();
    }
    //
    initialize() {
      this.insertStyle();
      this.insertHTML();
      this.setupEventListeners();
      this.loadTabs();
      this.loadIcons();
    }
    //
    insertHTML() {
      this.pickerDiv = document.createElement("div");
      this.pickerDiv.innerHTML = `
          <div id="BLICK_iP_tabColumn">
            <input id="BLICK_iP_seachInp" type="text" placeholder="Search...">
            <div id="BLICK_iP_tabs"></div>
          </div>
          <div id="BLICK_iP_splitter"></div>
          <div id="BLICK_iP_ImgColumn">
            <div id="BLICK_iP_loading">loading...</div>
          </div>`;
      this.pickerDiv.id = "BLICK_iconPicker";
      this.container.appendChild(this.pickerDiv);
      //
      this.imgCol = document.getElementById('BLICK_iP_ImgColumn');
      this.loading = document.getElementById('BLICK_iP_loading');
    }
    //
    insertStyle() {
      const sty = document.createElement("style");
      sty.id = 'BLICK_iconPicker_style';
      sty.innerHTML = `
          #BLICK_iconPicker {width: 400px;height: 200px;display: grid;grid-template-columns: 89px auto 1fr;overflow: hidden;background-color: #f8f8f8;user-select: none;flex:1;}
          #BLICK_iP_tabColumn {height:200px;display: grid;  grid-template-rows: auto 1fr;width:100%;padding: 1px;background-color: #e8e8e8;box-sizing: border-box;gap: 5px;}
          #BLICK_iP_splitter{background-color: #888;cursor: col-resize;width: 2px;}
          #BLICK_iP_seachInp {padding: 5px;font-size: 14px;border: 1px solid #ccc;border-radius: 5px;width:100%;box-sizing:border-box;}
          #BLICK_iP_tabs {display: flex;flex-direction: column;gap: 5px;overflow-y:auto}
          #BLICK_iP_tabs div {padding: 3px;background-color: #4CAF50;color: white;border: none;border-radius: 5px;cursor: pointer;}
          #BLICK_iP_tabs div.cur {background-color: #fdc03d;}
          #BLICK_iP_tabs div:hover {filter: brightness(90%);}
          #BLICK_iP_tabs div.dragover {background-color: #a5f0a9;}
          #BLICK_iP_tabs div[data-func] {text-align: center;}
          #BLICK_iP_ImgColumn {display: flex;grid-gap: 4px;padding: 4px;box-sizing: border-box;align-items: start;overflow-y: auto;width: 100%;flex-wrap: wrap;align-content: flex-start;}
          #BLICK_iP_ImgColumn .image-container {position: relative;display: flex;flex-direction: column;align-items: center;justify-content: center;}
          #BLICK_iP_ImgColumn .image-container img {width: 64px;height: 64px;border-radius: 5px;border: 2px solid #ddd;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);object-fit: cover;}
          #imgContent .image-container:hover img {border-color: #4CAF50;}
          .selected {outline: 2px dashed #4CAF50;}`;
      document.head.appendChild(sty);
    }
    //
    async loadIcons() {
      const url = 'https://questden.org/kusaba/icons/thumb/';
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const html = await response.text();
        let match;
        const regex = /<td><a href="(.*?)"/g;
        while ((match = regex.exec(html)) !== null) {
          this.allIcons.push(match[1]);
        }
        this.allIcons.shift();
        this.searchItems();
      }
      catch (error) {
        console.error('There was a problem with the fetch operation:', error);
        this.searchItems();
      }
    }
    stripIconName(name) {
      if (name.length < 3)
        return "";
      return name.substring(0, name.lastIndexOf("s."));
    }
    setupEventListeners() {
      document.getElementById("BLICK_iP_ImgColumn")?.addEventListener('mousedown', this.onMouseDown.bind(this));
      document.getElementById("BLICK_iP_seachInp")?.addEventListener("input", this.searchItems.bind(this));
      document.getElementById("BLICK_iP_seachInp")?.addEventListener("keydown", (ev) => {
        if (ev.key === 'Enter')
          ev.preventDefault();
      });
      //
      document.getElementById('BLICK_iP_tabs')?.addEventListener("click", this.onTabClick.bind(this), true);
      document.getElementById("BLICK_iP_ImgColumn")?.addEventListener('click', (ev) => {
        let el = ev.target.closest(".image-container");
        if (el === null)
          return;
        this.clickCallback(":" + this.stripIconName(el.getAttribute("title") ?? "") + ":");
        //
      }, true);
      //
      const splitter = document.getElementById('BLICK_iP_splitter');
      splitter?.addEventListener('mousedown', (e) => {
        this.spliterDraggin = true;
        document.body.style.cursor = 'col-resize'; // Zeige den Resize-Cursor während des Ziehens
      });
      document.addEventListener('mousemove', (e) => {
        if (!this.spliterDraggin)
          return;
        const containerRect = this.pickerDiv.getBoundingClientRect();
        const newWidth = e.clientX - containerRect.left;
        if (newWidth > 50 && newWidth < containerRect.width - 50) {
          this.pickerDiv.style.gridTemplateColumns = `${newWidth}px auto 1fr`;
        }
      });
      //
      document.addEventListener('mouseup', () => {
        this.spliterDraggin = false;
        document.body.style.cursor = ''; // Cursor zurücksetzen
      });
      //
      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && this.itemCount < this.shownIcons.length) {
          this.loadItems();
        }
      }, {
        root: this.imgCol,
        rootMargin: '0px',
        threshold: 1.0
      });
      if (this.loading !== null)
        observer.observe(this.loading);
    }
    //
    onMouseDown(event) {
      const target = event.target;
      if (target === null)
        return;
      const clst = target.closest('.image-container');
      //
      if (!this.selectedItems.includes(clst)) {
        this.selectedItems.forEach(item => item.classList.remove('selected'));
        this.selectedItems = [];
      }
      if (!clst) {
        this.startX = event.clientX + window.scrollX;
        this.startY = event.clientY + window.scrollY;
        //
        this.selectionBox = document.createElement('div');
        this.selectionBox.style.position = 'absolute';
        this.selectionBox.style.border = '2px dashed #4CAF50';
        this.selectionBox.style.backgroundColor = 'rgba(76, 175, 80, 0.2)';
        document.body.appendChild(this.selectionBox);
        //
        document.addEventListener('mousemove', this.onMouseMove.bind(this));
        document.addEventListener('mouseup', this.onMouseUp.bind(this));
      }
    }
    //
    onMouseMove(event) {
      const currentX = event.clientX + window.scrollX;
      const currentY = event.clientY + window.scrollY;
      if (!this.selectionBox)
        return;
      this.selectionBox.style.left = Math.min(this.startX, currentX) + 'px';
      this.selectionBox.style.top = Math.min(this.startY, currentY) + 'px';
      this.selectionBox.style.width = Math.abs(this.startX - currentX) + 'px';
      this.selectionBox.style.height = Math.abs(this.startY - currentY) + 'px';
      //
      const rect = this.selectionBox.getBoundingClientRect();
      if (rect === null)
        return;
      // Selektionslogik ändern, um nur hinzuzufügen, aber nicht sofort zu entfernen
      document.querySelectorAll('.image-container').forEach((item) => {
        const itemRect = item.getBoundingClientRect();
        //
        if (this.isIntersecting(rect, itemRect)) {
          // Bild nur hinzufügen, wenn es noch nicht selektiert ist
          if (!this.selectedItems.includes(item)) {
            item.classList.add('selected');
            this.selectedItems.push(item);
          }
        }
        else {
          if (this.selectedItems.includes(item)) {
            item.classList.remove('selected');
            this.selectedItems.splice(this.selectedItems.indexOf(item), 1);
          }
        }
      });
    }
    //
    onMouseUp() {
      document.removeEventListener('mousemove', this.onMouseMove.bind(this));
      document.removeEventListener('mouseup', this.onMouseUp.bind(this));
      //
      // Entferne das Auswahlrechteck nach der Selektion
      if (this.selectionBox && this.selectionBox.parentNode) {
        this.selectionBox.parentNode.removeChild(this.selectionBox);
        this.selectionBox = null;
      }
    }
    //
    isIntersecting(rect1, rect2) {
      return (rect1.left < rect2.right &&
        rect1.right > rect2.left &&
        rect1.top < rect2.bottom &&
        rect1.bottom > rect2.top);
    }
    //
    drag(event) {
      if (this.selectedItems.length === 0 && event.currentTarget !== null) {
        this.selectedItems.push(event.currentTarget);
      }
      event.dataTransfer?.setData('text/plain', JSON.stringify(this.selectedItems.map(item => item.title)));
    }
    //
    enableTabDrop() {
      const BLICK_iP_tabs = document.querySelectorAll('#BLICK_iP_tabs div');
      BLICK_iP_tabs.forEach(tab => {
        tab.addEventListener('dragover', (event) => {
          event.preventDefault();
          tab.classList.add('dragover');
        });
        //
        tab.addEventListener('dragleave', () => {
          tab.classList.remove('dragover');
        });
        //
        tab.addEventListener('drop', (event) => {
          event.preventDefault();
          tab.classList.remove('dragover');
          const titles = JSON.parse(event.dataTransfer?.getData('text') ?? "{}");
          const tabId = parseInt(tab.dataset.tabid ?? "-1");
          if (event.target?.dataset.func == "+")
            return;
          if (event.target?.dataset.role == "delete" || tabId === this.curActTab) { //remove entries
            if (confirm("Remove icons from list?")) {
              let ind = 0;
              titles.forEach((title) => {
                ind = this.editConf.data.iconList[tabId].arr.indexOf(title);
                if (ind > -1)
                  this.editConf.data.iconList[tabId].arr.splice(ind, 1);
              });
              this.clearItems();
            }
          }
          else { //add entries
            titles.forEach((title) => {
              if (!this.editConf.data.iconList[tabId].arr.includes(title)) {
                this.editConf.data.iconList[tabId].arr.push(title);
              }
            });
          }
          this.editConf.data.iconList[tabId].arr.sort();
          this.editConf.save();
        });
      });
    }
    //
    searchItems() {
      const search = document.getElementById("BLICK_iP_seachInp")?.value ?? "";
      //
      if (this.curActTab === -1 && search !== "") {
        this.shownIcons = this.allIcons.filter(el => (new RegExp(search.replace(/\s/g, ".*"), "i")).test(el));
      }
      else if (this.curActTab === -1 && search === "") {
        this.shownIcons = this.allIcons;
      }
      else if (this.curActTab !== -1 && search !== "") {
        this.shownIcons = this.editConf.data.iconList[this.curActTab].arr.filter(el => (new RegExp(search.replace(/\s/g, ".*"), "i")).test(el));
      }
      else if (this.curActTab !== -1 && search === "") {
        this.shownIcons = this.editConf.data.iconList[this.curActTab].arr;
      }
      //
      this.clearItems();
    }
    //
    clearItems() {
      if (this.imgCol === null)
        return;
      this.imgCol.innerHTML = "";
      this.imgCol.scrollTop = 0;
      this.itemCount = 0;
      this.loadItems();
    }
    //
    loadTabs() {
      const tabs = document.getElementById('BLICK_iP_tabs');
      if (tabs === null)
        return;
      this.editConf.data.iconList.forEach((item, index) => {
        const tab = document.createElement('div');
        tab.dataset.tabid = String(index);
        if (item.label == "Favourite" || item.label == "+") {
          tab.innerHTML = item.label;
          tab.dataset.func = item.label;
        }
        else {
          tab.innerHTML = item.label + " <span data-role='rename'>🖉</span><span data-role='delete'>🗑️</span>";
        }
        if (index === this.curActTab)
          tab.classList.add("cur");
        tabs.appendChild(tab);
      });
      this.enableTabDrop();
    }
    //
    updateTabs() {
      const tabs = document.getElementById('BLICK_iP_tabs');
      if (tabs === null)
        return;
      tabs.innerHTML = ''; // Leere die BLICK_iP_Tabs und erzeuge sie neu
      this.loadTabs();
    }
    //
    onTabClick(ev) {
      ev.preventDefault();
      ev.stopPropagation();
      let target = ev.target;
      if (target.dataset.func === "+") {
        const newLabel = prompt('New Name:', "");
        if (newLabel === "" || newLabel === null)
          return;
        this.editConf.data.iconList.splice(this.editConf.data.iconList.length - 1, 0, { label: newLabel, arr: [] });
        this.updateTabs();
      }
      else if (target.dataset.role === "rename") {
        const index = parseInt(target.parentElement?.dataset.tabid ?? "0");
        const newLabel = prompt('New Name:', this.editConf.data.iconList[index].label);
        if (newLabel === "" || newLabel === null)
          return;
        this.editConf.data.iconList[index].label = newLabel;
        this.updateTabs();
      }
      else if (target.dataset.role === "delete") {
        if (!confirm("Do you want to delete this icon list?"))
          return;
        const index = parseInt(target.parentElement?.dataset.tabid ?? "0");
        this.editConf.data.iconList.splice(index, 1);
        this.updateTabs();
      }
      else if (target.dataset.tabid !== undefined) {
        const index = parseInt(target.dataset.tabid);
        let stat = this.curActTab == index;
        document.querySelectorAll("#BLICK_iP_tabs>div.cur").forEach(el => el.classList.remove("cur"));
        if (!stat) {
          this.shownIcons = this.editConf.data.iconList[index].arr;
          target.classList.add("cur");
          this.curActTab = index;
        }
        else {
          this.shownIcons = this.allIcons;
          this.curActTab = -1;
        }
        this.searchItems();
      }
      this.editConf.save();
    }
    //
    loadItems() {
      if (this.imgCol === null || this.loading === null)
        return;
      const end = Math.min(this.itemCount + this.itemsPerLoad, this.shownIcons.length);
      for (let i = this.itemCount; i < end; i++) {
        this.imgCol.insertAdjacentHTML("beforeend", `
                <div class="image-container" draggable="true" title="${this.shownIcons[i]}">
                    <img src="https://questden.org/kusaba/icons/thumb/${this.shownIcons[i]}" alt="${this.shownIcons[i]}" />
                </div>`);
      }
      this.itemCount = end;
      this.imgCol.appendChild(this.loading);
      this.loading.style.display = (this.itemCount == this.shownIcons.length) ? "none" : "block";
      //
      document.querySelectorAll('.image-container').forEach(item => {
        item.addEventListener('dragstart', (ev) => {
          this.drag(ev);
        });
      });
    }
    //
    show() {
      this.pickerDiv.style.display = "";
    }
    hide() {
      this.pickerDiv.style.display = "none";
    }
    toggle() {
      if (this.isShown())
        this.show();
      else
        this.hide();
    }
    isShown() {
      return this.pickerDiv.style.display == "none";
    }
  }
  //
  class UndoRedoManager {
    //
    constructor(textarea) {
      this.undoStack = [];
      this.redoStack = [];
      this.textarea = textarea;
      this.applyingState = false;
      this.saveState();
      this.saveStateInterval = 500;
      this.saveStateTimer = 0;
      this.inputListener = this.debSaveState.bind(this);
      this.textarea.addEventListener('input', this.inputListener);
      this.keydownListener = (e) => {
        if (e.ctrlKey && e.key === 'z') {
          e.preventDefault();
          this.undo();
        }
        else if (e.ctrlKey && e.key === 'y') {
          e.preventDefault();
          this.redo();
        }
      };
      document.addEventListener('keydown', this.keydownListener);
    }
    debSaveState() {
      if (this.applyingState)
        return;
      clearTimeout(this.saveStateTimer);
      this.saveStateTimer = setTimeout(() => {
        this.saveState();
      }, this.saveStateInterval);
    }
    //
    saveState() {
      const state = {
        value: this.textarea.value,
        selectionStart: this.textarea.selectionStart,
        selectionEnd: this.textarea.selectionEnd
      };
      this.undoStack.push(state);
      this.redoStack = [];
    }
    //
    undo() {
      if (this.undoStack.length > 1) {
        const currentState = this.undoStack.pop();
        this.redoStack.push(currentState);
        const prevState = this.undoStack[this.undoStack.length - 1];
        this.applyState(prevState);
      }
    }
    //
    redo() {
      if (this.redoStack.length > 0) {
        const nextState = this.redoStack.pop();
        this.undoStack.push(nextState);
        this.applyState(nextState);
      }
    }
    //
    applyState(state) {
      this.applyingState = true;
      this.textarea.value = state.value;
      this.textarea.setSelectionRange(state.selectionStart, state.selectionEnd);
      const event = new Event('input', { bubbles: true });
      this.textarea.dispatchEvent(event);
      this.applyingState = false;
    }
    //
    destroy() {
      this.undoStack = [];
      this.redoStack = [];
      this.textarea.removeEventListener('input', this.inputListener);
      document.removeEventListener('keydown', this.keydownListener);
    }
  }
  //
  class ReplyForm {
    //
    constructor() {
      this.bar = null;
      this.textarea = null;
      this.colPickEl = null;
      this.iconPicker = null;
      this.buttonBar = null;
      this.extendBar = null;
      this.saveTimeStamp = null;
      this.genPrevTimer = 0;
      this.autoGeneratePrev = false;
      this.saveTimer = 0;
      this.undoManager = null;
      //
      this.editConf = new EditorConfig();
      this.editConf.load().then(this.insertStyle);
    }
    ;
    removeBar() {
      this.bar?.remove();
      document.querySelector("#BLICK_previewbut")?.remove();
      document.querySelector("#prevdiv")?.remove();
      this.undoManager?.destroy();
      this.bar = null;
    }
    ;
    insertStyle() {
      const sty = document.createElement("style");
      sty.id = "BLICK_stylebar";
      sty.innerHTML = `
        #BLICK_bigcont{background-color:#6f6f6f;width:100%;padding:3px 0;}
        .BLICK_style{display:inline-block;width:24px;height:24px;overflow:hidden;background-image: url('${imgRes.stylebuts}');background-repeat:no-repeat;}
        .BLICK_style:hover{background-position-y: -24px;}
        .BLICK_style:active{background-position-y: -48px;}
        #BLICK_pallette a {display: inline-block;	height: 20px;width: 20px; margin: 2px;background-color:attr(bgcol);}
        #BLICK_pallette {width: 145px;}
        #BLICK_colorpalette{padding:3px;box-sizing: border-box;display:none;}
        #BLICK_colorpalette.shown{display:flex;gap:10px;}
        #BLICK_cp{display:flex;height:100px;} 
        #BLICK_cp_colorDisplay { height: 40px; user-select: none;cursor:pointer;	}
        #BLICK_cp_bgDisplay { height: 40px; user-select: none;cursor:pointer;	} 
        #BLICK_cp_colTex {height: 20px;box-sizing: border-box;font-family: 'Courier New', Courier, monospace;text-transform: uppercase;border: 1px solid #ccc;border-radius: 4px;background-color: #f9f9f9;color: #333;text-align: center;z-index: 9;font-size: 11px;padding: 2px;width: 60px;letter-spacing: 0.5px;} 
        #BLICK_cp_colPrev { display: flex; flex-direction: column; width:60px;	} 
        #BLICK_cp .slider { writing-mode: vertical-lr; appearance: none; width: 10px; height: 100%; outline: none;	} 
        #BLICK_cp .slider::-webkit-slider-thumb { -webkit-appearance: none; width: 10px; height: 5px; background: #ffffff77; border: 1px solid black; border-radius: 2px; cursor: pointer; opacity: 0.8;	}
        #BLICK_cp .slider:hover::-webkit-slider-thumb { opacity: 1;} 
        #BLICK_cp .slider::-moz-range-thumb { width: 10px; height: 5px; background: #ffffff77; border: 1px solid black; border-radius: 2px; cursor: pointer; opacity: 0.8;	} 
        #BLICK_cp .slider:hover::-moz-range-thumb { opacity: 1;} 
        #BLICK_cp .slider[id="BLICK_cp_hue"] { background: linear-gradient(to bottom, hsl(0, 100%, 50%), hsl(60, 100%, 50%), hsl(120, 100%, 50%), hsl(180, 100%, 50%), hsl(240, 100%, 50%), hsl(300, 100%, 50%), hsl(360, 100%, 50%));	}
        #BLICK_colChoice a.customCol{border:1px dashed black;}
        #BLICK_colChoice a {display: inline-block;	height: 20px;width: 20px; margin: 2px;background-color:attr(bgcol);}
        #BLICK_colChoice {width: 130px;}
        #BLICK_extendBar{display:flex;}
        #BLICK_saveTimeStamp{position: absolute;bottom: 10px;right: 50px; font-size: 12px;color: #3333; pointer-events: none;}
        #BLICK_autoSaveBut{ padding: 0;float: right;margin: 0 10px;}
        #BLICK_autoSaveBut input, #BLICK_autoSaveBut label{ cursor: pointer;}
      `;
      document.head.appendChild(sty);
    }
    insertBar() {
      if (this.bar !== null)
        return;
      this.textarea = document.querySelector("form[name='postform'] textarea[name='message']");
      if (this.textarea === null) {
        this.bar = null;
        return;
      }
      //
      this.undoManager = new UndoRedoManager(this.textarea); //enough to work. ctrl+z manager
      //
      this.bar = document.createElement("div");
      this.bar.id = "BLICK_bigcont";
      this.textarea.parentElement.insertBefore(this.bar, this.textarea);
      this.textarea.parentElement.style.display = "block";
      //
      this.saveTimeStamp = document.createElement("div");
      this.saveTimeStamp.id = "BLICK_saveTimeStamp";
      this.textarea.parentElement.appendChild(this.saveTimeStamp);
      this.textarea.parentElement.style.position = "relative";
      //
      this.buttonBar = document.createElement("div");
      this.buttonBar.id = 'BLICK_buttonBar';
      this.bar.appendChild(this.buttonBar);
      this.extendBar = document.createElement("div");
      this.extendBar.id = 'BLICK_extendBar';
      this.bar.appendChild(this.extendBar);
      //
      let autosaveDiv = document.createElement("div");
      autosaveDiv.id = "BLICK_autoSaveBut";
      let autoSaveCB = document.createElement("input");
      autoSaveCB.type = "checkbox";
      autoSaveCB.id = "BLICK_autoSaveCB";
      autoSaveCB.checked = this.editConf.data.autoSave;
      autoSaveCB.addEventListener("change", (ev) => {
        let state = ev.target?.checked ?? true;
        this.editConf.set("autoSave", state);
        this.debsaveText();
      });
      autosaveDiv.appendChild(autoSaveCB);
      autosaveDiv.insertAdjacentHTML("beforeend", "<label for='BLICK_autoSaveCB'>autosave</label>");
      this.buttonBar.appendChild(autosaveDiv);
      //
      this.addButton("b", 0);
      this.addButton("u", 1);
      this.addButton("i", 2);
      this.addButton("s", 3);
      this.addButton("aa", 4);
      this.addButton("small", 5);
      this.addButton("code", 6);
      this.addButton("spoiler", 7);
      this.addButton("color", 10, () => {
        document.getElementById("BLICK_colorpalette")?.classList.toggle("shown");
        if (this.iconPicker)
          this.iconPicker.hide();
      });
      this.addButton("icon", 9, () => {
        if (this.extendBar === null)
          return;
        if (this.iconPicker === null) {
          this.iconPicker = new ImageManager(this.extendBar, this.editConf, (icon) => {
            if (this.textarea === null)
              return;
            let pos = this.textarea.selectionStart;
            this.textarea.value = this.textarea.value.substring(0, pos) + icon + this.textarea.value.substring(this.textarea.selectionEnd);
            this.textarea.setSelectionRange(pos + icon.length, pos + icon.length);
            this.textarea.focus();
            const event = new Event('input', { bubbles: true });
            this.textarea.dispatchEvent(event);
          });
        }
        else {
          this.iconPicker.toggle();
        }
        document.getElementById("BLICK_colorpalette")?.classList.remove("shown");
      });
      this.insertColorPallette();
      this.addPreviewBut();
      if (this.editConf.data.saveText !== "") {
        this.textarea.value = this.editConf.data.saveText;
      }
    }
    addPreviewBut() {
      const postForm = document.forms.namedItem("postform");
      if (!postForm)
        return;
      //
      let but = document.createElement("input");
      but.id = "BLICK_previewbut";
      but.type = "button";
      but.value = "Preview";
      //
      let subBut = document.querySelector("form[name='postform'] input[type='submit']");
      subBut?.parentElement?.insertBefore(but, subBut);
      subBut?.addEventListener("click", () => {
        this.editConf.set("saveText", "");
      });
      //
      let cbPrev = document.createElement("input");
      cbPrev.id = 'BLICK_cb_preview';
      cbPrev.type = "checkbox";
      cbPrev.style.marginRight = "-3px";
      cbPrev.title = "Automatically update the preview while typing.";
      but.parentElement?.insertBefore(cbPrev, but);
      //
      cbPrev.addEventListener("change", (ev) => {
        this.autoGeneratePrev = ev.target?.checked ?? false;
        this.generatePreview(ev);
      });
      but.addEventListener("click", this.generatePreview);
      document.querySelector("form[name='postform'] textarea[name='message']")?.addEventListener("input", () => {
        this.debGeneratePrev();
        this.debsaveText();
      });
      //
    }
    insertColorPallette() {
      let farbar = ["ff0000", "00ff00", "0000ff", "ffff00", "00ffff", "ff00ff"];
      let cp = document.createElement("div");
      cp.id = "BLICK_colorpalette";
      cp.className = "reply";
      //
      const texinh = { normal: "", brighter: "", darker: "", greys: "" };
      const greystep = 255.0 / (farbar.length - 1);
      let gry;
      for (let i = 0; i < farbar.length; i++) {
        texinh.normal += `<a href='#' bgcol='#${farbar[i]}' style='background-color:#${farbar[i]}');'></a>`;
        texinh.brighter += `<a href='#' bgcol='#${farbar[i].replace(/0/g, `8`)}' style='background-color:#${farbar[i].replace(/0/g, `8`)}');'></a>`;
        texinh.darker += `<a href='#' bgcol='#${farbar[i].replace(/f/g, `8`)}' style='background-color:#${farbar[i].replace(/f/g, `8`)}');'></a>`;
        gry = Math.floor(i * greystep).toString(16).padStart(2, '0');
        texinh.greys += `<a href='#' bgcol='#${gry + gry + gry}' style='background-color:#${gry + gry + gry}');'></a>`;
      }
      cp.innerHTML = `<div id='BLICK_pallette'>${Object.values(texinh).join("\n")}</div>`;
      //
      cp.addEventListener("click", (ev) => {
        if (!(ev.target instanceof HTMLAnchorElement))
          return;
        ev.preventDefault();
        ev.stopPropagation();
        this.styleSelection("color=" + ev.target.getAttribute("bgcol"));
      }, true);
      //
      this.extendBar?.appendChild(cp);
      this.insertColorPicker();
      this.insertColorChoice();
    }
    insertColorPicker() {
      let pick = document.createElement("div");
      pick.id = "BLICK_cp";
      pick.innerHTML = `
          <div id="BLICK_cp_colPrev">
            <div id="BLICK_cp_colorDisplay">Click to insert</div>
            <div id="BLICK_cp_bgDisplay"></div>
            <input type="text" id="BLICK_cp_colTex" />
          </div>
          <input type="range" id="BLICK_cp_hue" class="slider" min="0" max="360" value="0" step="1" orient="vertical">
          <input type="range" id="BLICK_cp_saturation" class="slider" min="0" max="100" value="0" step="10"
            orient="vertical">
          <input type="range" id="BLICK_cp_lightness" class="slider" min="0" max="100" value="50" step="5" orient="vertical">`;
      //
      document.getElementById("BLICK_colorpalette")?.appendChild(pick);
      //
      this.colPickEl = {
        hue: document.getElementById('BLICK_cp_hue'),
        sat: document.getElementById('BLICK_cp_saturation'),
        lum: document.getElementById('BLICK_cp_lightness'),
        bgCol: document.getElementById('BLICK_cp_bgDisplay'),
        prevCol: document.getElementById('BLICK_cp_colorDisplay'),
        prevText: document.getElementById('BLICK_cp_colTex')
      };
      //
      this.colPickEl.hue.addEventListener("input", () => { this.updatePickerColer(true); });
      this.colPickEl.sat.addEventListener("input", () => { this.updatePickerColer(true); });
      this.colPickEl.lum.addEventListener("input", () => { this.updatePickerColer(true); });
      this.colPickEl.prevText.addEventListener('input', () => {
        if (this.colPickEl == null)
          return;
        this.colPickEl.prevCol.style.color = this.colPickEl.prevText.value;
        this.colPickEl.bgCol.style.backgroundColor = this.colPickEl.prevCol.style.color;
        let rgb = this.colPickEl.prevCol.style.color.match(/(\d+)/g) ?? ["0", "0", "0"];
        let hsl = rgb2hsl(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2]));
        this.colPickEl.hue.value = String(hsl[0]);
        this.colPickEl.sat.value = String(100 - hsl[1]);
        this.colPickEl.lum.value = String(100 - hsl[2]);
        this.updatePickerColer(false);
      });
      let insertColFun = () => {
        if (this.colPickEl === null)
          return;
        this.styleSelection("color=" + this.colPickEl.prevText.value);
      };
      this.colPickEl.prevCol.addEventListener("click", insertColFun);
      this.colPickEl.bgCol.addEventListener("click", insertColFun);
      //
      this.updatePickerColer(true);
    }
    updatePickerColer(textupdate = false) {
      if (this.colPickEl == null)
        return;
      const hue = this.colPickEl.hue.value;
      const saturation = 100 - parseInt(this.colPickEl.sat.value);
      const lightness = 100 - parseInt(this.colPickEl.lum.value);
      //
      this.colPickEl.prevCol.style.color = `hsl(${hue}, ${saturation}%, ${lightness}%)`;
      this.colPickEl.bgCol.style.backgroundColor = this.colPickEl.prevCol.style.color;
      if (textupdate) {
        this.colPickEl.prevText.value = "#" + (this.colPickEl.prevCol.style.color.match(/(\d+)/g) ?? ["0", "0", "0"]).map(el => parseInt(el).toString(16).padStart(2, "0")).join("");
      }
      this.colPickEl.sat.style.background = `linear-gradient(to top,hsl(${hue}, 0%, 50%),hsl(${hue}, 100%, 50%))`;
      this.colPickEl.lum.style.background = `linear-gradient(to top,hsl(${hue}, ${saturation}%, 0%),hsl(${hue}, ${saturation}%, 50%),hsl(${hue}, ${saturation}%, 100%))`;
    }
    insertColorChoice() {
      let farbar = this.editConf.data.colorList;
      let cp = document.getElementById("BLICK_colorpalette");
      if (cp === null)
        return;
      // 
      let colCh = document.createElement("div");
      colCh.id = "BLICK_colChoice";
      let texinh = "<div>Rightclick to set</div>";
      for (let i = 0; i < farbar.length; i++) {
        texinh += `<a href='#' class='customCol' customId='${i}' bgcol='${farbar[i]}' style='background-color:${farbar[i]}');'></a>`;
      }
      colCh.innerHTML = texinh;
      cp.appendChild(colCh);
      //
      cp.addEventListener("contextmenu", (ev) => {
        if (!(ev.target instanceof HTMLAnchorElement) || ev.target.className !== "customCol" || this.colPickEl === null)
          return;
        ev.preventDefault();
        ev.stopPropagation();
        let id = parseInt(ev.target.getAttribute("customId") ?? "0");
        this.editConf.data.colorList[id] = this.colPickEl.prevText.value;
        ev.target.setAttribute("bgcol", this.colPickEl.prevText.value);
        ev.target.style.backgroundColor = this.colPickEl.prevText.value;
        this.editConf.save();
      }, true);
    }
    addButton(tag, bgpos, callback = null) {
      const butB = document.createElement("div");
      butB.className = "BLICK_style";
      butB.title = `[${tag}]`;
      butB.addEventListener("click", () => {
        if (callback === null)
          this.styleSelection(tag);
        else
          callback();
      });
      butB.style.backgroundPositionX = -24 * bgpos + "px";
      this.buttonBar?.appendChild(butB);
    }
    //
    styleSelection(tag) {
      if (this.textarea === null)
        return;
      let texStart = this.textarea.selectionStart ?? 0;
      let texEnd = this.textarea.selectionEnd ?? 0;
      let pureTag = tag.indexOf("=") === -1 ? tag : tag.substring(0, tag.indexOf("=")); //strip parameters from closing tag
      //
      if (texStart >= tag.length + 2 && this.textarea.value.substring(texStart - tag.length - 2, texStart) == "[" + tag + "]") {
        //remove tag if selection preceeded by tag
        this.textarea.value = this.textarea.value.substring(0, texStart - tag.length - 2) +
          this.textarea.value.substring(texStart, texEnd) +
          this.textarea.value.substring(texEnd + pureTag.length + 3);
        this.textarea.selectionStart = texStart - tag.length - 2; //selection shifted by start tag length
        this.textarea.selectionEnd = texEnd - tag.length - 2;
      }
      else if (pureTag == "color" && this.textarea.value.substring(texStart - "color=#ffffff]".length, texStart - "=#ffffff]".length) == "color") {
        //color change on color tag if new color is different
        this.textarea.value = this.textarea.value.substring(0, texStart - tag.length - 2) + "[" + tag + "]" +
          this.textarea.value.substring(texStart, texEnd) + "[/" + pureTag + "]" +
          this.textarea.value.substring(texEnd + pureTag.length + 3);
        this.textarea.selectionStart = texStart; //original selection
        this.textarea.selectionEnd = texEnd;
      }
      else {
        //add tag
        this.textarea.value = this.textarea.value.substring(0, texStart) +
          "[" + tag + "]" + this.textarea.value.substring(texStart, texEnd) + "[/" + pureTag + "]" +
          this.textarea.value.substring(texEnd);
        this.textarea.selectionStart = texStart + tag.length + 2; //selection shifted by start tag length
        this.textarea.selectionEnd = texEnd + tag.length + 2;
      }
      this.textarea.focus();
      //
      const event = new Event('input', { bubbles: true });
      this.textarea.dispatchEvent(event);
    }
    debsaveText() {
      if (!this.editConf.data.autoSave) {
        if (this.saveTimeStamp !== null)
          this.saveTimeStamp.innerText = "";
        this.editConf.set("saveText", "");
        return;
      }
      clearTimeout(this.saveTimer);
      this.saveTimer = setTimeout(() => {
        this.saveText(null);
      }, 500);
    }
    saveText(ev) {
      ev?.preventDefault();
      this.editConf.set("saveText", this.textarea?.value ?? "");
      //
      const now = new Date();
      const year = now.getFullYear();
      const month = String(now.getMonth() + 1).padStart(2, '0'); // Monate von 0 bis 11
      const day = String(now.getDate()).padStart(2, '0');
      const hours = String(now.getHours()).padStart(2, '0');
      const minutes = String(now.getMinutes()).padStart(2, '0');
      const seconds = String(now.getSeconds()).padStart(2, '0');
      //
      const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
      if (this.saveTimeStamp !== null)
        this.saveTimeStamp.innerText = `Last saved: ${formattedDate}`;
    }
    debGeneratePrev() {
      if (!this.autoGeneratePrev)
        return;
      clearTimeout(this.genPrevTimer);
      this.genPrevTimer = setTimeout(() => {
        this.generatePreview(null);
      }, 500);
    }
    generatePreview(ev) {
      ev?.preventDefault();
      //
      const postForm = document.forms.namedItem("postform");
      if (postForm === null)
        return;
      let prevDiv = document.getElementById('prevdiv');
      //
      if (document.getElementById('prevdiv') === null) {
        prevDiv = document.createElement('div');
        prevDiv.id = 'prevdiv';
        prevDiv.className = 'reply';
        prevDiv.style.textAlign = 'left';
        prevDiv.style.backgroundColor = '';
        postForm.appendChild(prevDiv);
      }
      //
      const boardValue = postForm.elements.namedItem("board")?.value ?? "";
      const replyThreadValue = postForm.elements.namedItem("replythread")?.value ?? "";
      const messageValue = postForm.elements.namedItem("message")?.value ?? "";
      //
      postpreview('prevdiv', boardValue, replyThreadValue, messageValue); //defined in website.
    }
  }
  ;
  ;
  class WatchData extends Storage {
    constructor() {
      let def = {
        numLinkMode: 1,
        threads: new Map(),
        default: {
          label: "Default",
          author: "none",
          section: "general",
          highImgOnly: true,
          highIDs: [],
          highNames: [],
          ignoreIDs: [],
          ignoreNames: [],
          lastReadId: "",
          currentReadId: "",
          newEntrCnt: 0,
          totalEntrCnt: 0,
        }
      };
      super("watchbar", def);
    }
  }
  ;
  //
  class WatchBar {
    constructor(sets) {
      this.curThread = null;
      this.saveTimer = 0;
      this.saveInterval = 500;
      this.thList = ["draw", "meep", "quest", "questdis", "tg", "questarch"];
      this.sets = sets;
      this.wdata = new WatchData();
      this.wdata.load().then(() => {
        this.insertHTML();
        this.insertStyle();
        this.insertObserver();
        this.insertEntries();
        this.insertSettingHTML();
      });
    }
    destroy() {
      document.getElementById("BLICK_watch_bar")?.remove();
      document.getElementById("BLICK_stylewatchbar")?.remove();
      document.getElementById("BLICK_wbar_form-container")?.remove();
    }
    insertHTML() {
      const wbar = document.createElement("div"); //hover notch&container with overflow-scroll
      wbar.id = "BLICK_watch_bar";
      //
      this.cont = document.createElement("div"); // inner container containing items
      this.cont.id = "BLICK_watch_wrap";
      wbar.appendChild(this.cont);
      //
      const updbut = document.createElement("img");
      updbut.id = "BLICK_watch_update";
      updbut.title = "Check for updates";
      updbut.className = "BLICK_wbar_headButs";
      updbut.src = imgRes.refreshimg;
      wbar.appendChild(updbut);
      updbut.addEventListener("click", () => { this.wdata.load().then(() => { this.updateEntries(); }); });
      //
      const defaultSetBut = document.createElement("button");
      defaultSetBut.id = "BLICK_watch_defaultSet";
      defaultSetBut.title = "Set default settings.";
      defaultSetBut.className = "BLICK_wbar_headButs";
      defaultSetBut.innerText = "🛠️";
      wbar.appendChild(defaultSetBut);
      defaultSetBut.addEventListener("click", () => { this.showSettingDiag("", true); });
      //
      const importBut = document.createElement("button");
      importBut.id = "BLICK_watch_import";
      importBut.title = "Import from questden.org";
      importBut.className = "BLICK_wbar_headButs";
      importBut.innerText = "📥";
      wbar.appendChild(importBut);
      importBut.addEventListener("click", () => { this.fetchFromSite(); });
      //
      const exportBut = document.createElement("button");
      exportBut.id = "BLICK_watch_export";
      exportBut.title = "Export to questden.org";
      exportBut.className = "BLICK_wbar_headButs";
      exportBut.innerText = "📤";
      wbar.appendChild(exportBut);
      exportBut.addEventListener("click", () => { this.copyToSite(); });
      //
      wbar.addEventListener("click", (ev) => {
        if (!(ev.target instanceof HTMLElement))
          return;
        if (ev.target.dataset.role == "remove") {
          let row = ev.target.closest("div.BLICK_wrow");
          if (row === null || row.dataset.id === undefined)
            return;
          if (!confirm(`Remove entry for ${this.wdata.data.threads.get(row.dataset.id)?.label}?`))
            return;
          this.wdata.data.threads.delete(row.dataset.id ?? "0");
          this.wdata.save();
          row.remove();
          ev.preventDefault();
          ev.stopPropagation();
        }
        else if (ev.target.dataset.role == "options") {
          let row = ev.target.closest("div.BLICK_wrow");
          if (row === null || row.dataset.id === undefined)
            return;
          this.showSettingDiag(row.dataset.id);
          ev.preventDefault();
          ev.stopPropagation();
        }
      }, true);
      document.body.appendChild(wbar);
    }
    copyToSite() {
      const fetchPromises = [];
      this.wdata.data.threads.forEach((thread, id) => {
        fetchPromises.push(fetch(`https://questden.org/kusaba/threadwatch.php?do=addthread&board=${thread.section}&threadid=${id}`).then((ret) => {
          if (ret.ok)
            console.log(`added ${thread?.label}, ${thread?.section}/${id} to your watchlist.`);
        }));
      });
      Promise.all(fetchPromises).then(() => {
        getwatchedthreads('0', "quest");
        alert(`${this.wdata.data.threads.size} threads exported.`);
      });
    }
    insertSettingHTML() {
      this.setDiag = document.createElement("div");
      this.setDiag.id = "BLICK_wbar_set_overlay";
      this.setDiag.style.display = "none";
      this.setDiag.innerHTML = `
      <div id="BLICK_wbar_form-container">
        <h2>Settings</h2>
        <p id="BLICK_wbar_formSubtitle">Options for 'Divequest ch4'</p>
        <div id="BLICK_wbar_form-grid">
        <div class="BLICK_wbar_setSect" id='BLICK_wbar_genSec'>
          <span class='BLICK_wbar_secTitl'>General Settings</span>
            <label for="BLICK_wbar_autoUpdate">Automatically update:</label>
            <div class="checkbox-container">
              <input type="checkbox" id="BLICK_wbar_autoUpdate">
              <label for="BLICK_wbar_autoUpdate">On pageload</label>
            </div>
            <label for="BLICK_wbar_linkTo">Number links to:</label>
            <select id="BLICK_wbar_linkTo">
              <option value="new">Last known post</option>
              <option value="current">Last reading position</option>
              <option value="none">No specific post</option>
            </select>
          </div>
          <div class="BLICK_wbar_setSect" id='BLICK_wbar_threadSec'>
            <span class='BLICK_wbar_secTitl'>Default Thread Settings</span>
            <label for="BLICK_wbar_highlightNew">Count New:</label>
            <div class="checkbox-container">
              <input type="checkbox" id="BLICK_wbar_highlightNew">
              <label for="BLICK_wbar_highlightNew">Only Images</label>
            </div>
            <label for="BLICK_wbar_highlightNames">Only Names</label>
            <input type="text" id="BLICK_wbar_highlightNames" title="Names, comma separated">
            <label for="BLICK_wbar_highlightIDs">Only IDs</label>
            <input type="text" id="BLICK_wbar_highlightIDs" title="IDs, comma separated">
            <label for="BLICK_wbar_ignoreNames">Ignore Names</label>
            <input type="text" id="BLICK_wbar_ignoreNames" title="Names, comma separated">
            <label for="BLICK_wbar_ignoreIDs">Ignore IDs</label>
            <input type="text" id="BLICK_wbar_ignoreIDs" title="IDs, comma separated">
          </div>
        </div>
        <div id="BLICK_wbar_button-row">
          <button data-role="save">Save</button>
          <button data-role="cancel">Cancel</button>
          <button data-role="default">Default</button>
          <button data-role="overwrite">Apply to all</button>
        </div>
      </div>`;
      document.body.appendChild(this.setDiag);
      this.setDiag.addEventListener("click", function (ev) {
        if (ev.target == this)
          this.style.display = "none"; //this in function() refers to "this.setDiag"
      });
      document.getElementById("BLICK_wbar_button-row")?.addEventListener("click", (ev) => {
        let target = ev.target;
        if (target.dataset.role == null || this.setDiag.dataset.threadid === undefined)
          return;
        let id = this.setDiag.dataset.threadid;
        switch (target.dataset.role) {
          case "save":
            this.saveSettingDiag(id);
          default:
          case "cancel":
            this.setDiag.style.display = "none";
            return;
          case "default":
            this.showSettingDiag(id, true);
            break;
          case "overwrite":
            if (confirm("Set settings of other entries to this?")) {
              this.wdata.data.threads.forEach((thread, tid) => { this.saveSettingDiag(tid); });
              if (id === "")
                this.saveSettingDiag("");
              this.setDiag.style.display = "none";
            }
            break;
        }
        ev.preventDefault();
        ev.stopPropagation();
      }, true);
    }
    insertStyle() {
      const sty = document.createElement("style");
      sty.id = "BLICK_stylewatchbar";
      sty.innerHTML = `
        #BLICK_watch_bar{width:20px;overflow-y:auto;overflow-x:hidden;position:fixed;transition:width 0.5s,opacity 0.5s, max-height 0.5s, border-radius 1s; right:0px;max-height:30px;height:30px;background-color:#fad;z-index:8;opacity:0.5;top:85px;border:2px ridge #ddf;border-top-left-radius:25px;border-bottom-left-radius:25px;font-size: 16px;color: #d00;}
        #BLICK_watch_bar:hover{overflow-y:auto;overflow-x:hidden;opacity:1;width:400px;height:auto;border-bottom-left-radius:5px;border-top-left-radius:5px;max-height:calc(95vh - 85px)}
        #BLICK_watch_bar>*{visibility:hidden;cursor:default;}
        #BLICK_watch_bar:hover>*{visibility:visible;}
        #BLICK_watch_bar .BLICK_wbar_headButs{width:16px;height:16px;z-index:99;position:absolute;top:0;cursor:pointer;border:none;background:none;}
        #BLICK_watch_bar #BLICK_watch_update{right:0;}
        #BLICK_watch_bar #BLICK_watch_defaultSet{right:25px;}
        #BLICK_watch_bar #BLICK_watch_import{right:50px;}
        #BLICK_watch_bar #BLICK_watch_export{right:75px;}
        #BLICK_watch_bar .BLICK_wbar_headButs:hover{filter:brightness(120%);}
        #BLICK_watch_bar button:hover{filter:brightness(120%);}
        #BLICK_watch_wrap{display: grid; grid-template-columns: auto 100px 45px 50px;}
        #BLICK_watch_bar .BLICK_wrow {display: contents; padding: 0px 5px;}
        #BLICK_watch_bar .BLICK_wrow:nth-child(2n)>div.BLICK_wcell{background-color: #F0D0C6;}
        #BLICK_watch_bar .BLICK_wrow:nth-child(2n+1)>div.BLICK_wcell{background-color: #FBC1AF;}
        #BLICK_watch_bar .BLICK_wrow>div.BLICK_wcell{display: inline-block; vertical-align: top; word-wrap: break-word;line-height:23px;padding:0 5px;}
        #BLICK_watch_bar .BLICK_whead{background-color: #EEAA88;grid-column: 1 / -1;text-align: center;font-weight: bold;padding: 2px;color: black;}
        #BLICK_watch_bar .BLICK_wactions button{width:16px;height:16px;font-size:14px;border:none;background:none;cursor:pointer;display:inline-block;}
        #BLICK_watch_bar a:hover{cursor:pointer;}
        #BLICK_watch_bar div.BLICK_wauthor{color: green;font-weight: bold;}
        #BLICK_watch_bar div.BLICK_wnew a{color: blue;}
        #BLICK_watch_bar div.BLICK_title a{color: red;}
        #BLICK_wbar_set_overlay {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, 0.5);display: flex;justify-content: center;align-items: center;z-index: 1000;}
        #BLICK_wbar_form-container{background-color: #fffff0;padding: 20px;border-radius: 8px;box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);max-width: 400px;width: 90%;font-family: Arial, sans-serif;border: 1px solid #b0a394;}
        #BLICK_wbar_form-container h2 {margin-top: 0;font-size: 1.4em;color: #333;text-align: center;}
        #BLICK_wbar_form-container p {font-size: 0.9em;color: #666;text-align: center;margin-top: 0;margin-bottom: 15px;}
        #BLICK_wbar_form-grid {margin-bottom: 15px;}
        #BLICK_wbar_form-grid label {font-size: 0.9em;color: #555;display: flex;align-items: center;justify-content: flex-end;}
        #BLICK_wbar_form-grid input[type="text"] {padding: 8px;border: 1px solid #ccc;border-radius: 4px;font-size: 0.9em;}
        #BLICK_wbar_form-grid input[type="checkbox"] {cursor:pointer}
        #BLICK_wbar_form-grid .checkbox-container {display: flex;align-items: center;justify-content: center;}
        #BLICK_wbar_form-grid .checkbox-container label {margin-left: 8px;cursor:pointer}
        #BLICK_wbar_button-row {display: flex;gap: 10px;justify-content: space-between;}
        #BLICK_wbar_button-row button {flex: 1;padding: 8px;border: none;border-radius: 4px;font-size: 0.9em;cursor: pointer;transition: background-color 0.2s;color: white;}
        #BLICK_wbar_button-row button:hover {opacity: 0.9;}
        #BLICK_wbar_button-row button[data-role="save"] { background-color: #3ba44b;}
        #BLICK_wbar_button-row button[data-role="cancel"] { background-color: #bb5546;}
        #BLICK_wbar_button-row button[data-role="default"] { background-color: #304f79;}
        #BLICK_wbar_button-row button[data-role="overwrite"] { background-color: #dd9243;}
        .BLICK_wbutton_iswatched{transition:filter 0.5s;filter: hue-rotate(150deg) brightness(150%);}
        .BLICK_wbar_setSect{position: relative;margin-bottom: 15px; display: grid;grid-template-columns: 150px auto;grid-column: span 2;gap: 15px;border: 1px solid #aaa;border-radius: 5px;padding: 15px;padding-top:25px;}
        #BLICK_wbar_linkTo {padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box;}
        .BLICK_wbar_secTitl{position: absolute;left: 5px;right: 0;font-size: smaller;color: #aaa;}
      `;
      document.head.appendChild(sty);
    }
    showSettingDiag(id, def = false) {
      if (this.setDiag === null || document.getElementById("BLICK_wbar_formSubtitle") == null ||
        (id !== "" && !this.wdata.data.threads.has(id)))
        return;
      const thread = def ? this.wdata.data.default : this.wdata.data.threads.get(id);
      //
      if (id === "") {
        document.getElementById("BLICK_wbar_formSubtitle").style.display = "none";
        document.getElementById("BLICK_wbar_genSec").style.display = "";
        document.querySelector("#BLICK_wbar_threadSec .BLICK_wbar_secTitl").innerText = "Default Thread settings";
        document.getElementById("BLICK_wbar_autoUpdate").checked = this.sets.data.autoUpdWbar;
        document.getElementById("BLICK_wbar_linkTo").selectedIndex = this.wdata.data.numLinkMode;
      }
      else {
        document.getElementById("BLICK_wbar_formSubtitle").style.display = "";
        document.getElementById("BLICK_wbar_formSubtitle").innerText = this.wdata.data.threads.get(id)?.label ?? "";
        document.getElementById("BLICK_wbar_genSec").style.display = "none";
        document.querySelector("#BLICK_wbar_threadSec .BLICK_wbar_secTitl").innerText = "Thread settings";
      }
      //
      document.getElementById("BLICK_wbar_highlightNew").checked = thread?.highImgOnly ?? true;
      document.getElementById("BLICK_wbar_highlightNames").value = thread?.highNames.join(",") ?? "";
      document.getElementById("BLICK_wbar_highlightIDs").value = thread?.highIDs.join(",") ?? "";
      document.getElementById("BLICK_wbar_ignoreNames").innerText = thread?.ignoreNames.join(",") ?? "";
      document.getElementById("BLICK_wbar_ignoreIDs").innerText = thread?.ignoreIDs.join(",") ?? "";
      this.setDiag.style.display = "flex";
      this.setDiag.dataset.threadid = id;
    }
    saveSettingDiag(id, close = false) {
      if (this.setDiag === null || document.getElementById("BLICK_wbar_formSubtitle") == null ||
        (id !== "" && !this.wdata.data.threads.has(id)))
        return;
      const thread = (id !== "") ? this.wdata.data.threads.get(id) : this.wdata.data.default;
      //
      let sanit = (str) => { return str.split(",").map(name => name.trim()).filter(name => name !== ""); };
      thread.highImgOnly = document.getElementById("BLICK_wbar_highlightNew").checked ?? true;
      thread.highNames = sanit(document.getElementById("BLICK_wbar_highlightNames").value);
      thread.highIDs = sanit(document.getElementById("BLICK_wbar_highlightIDs").value);
      thread.ignoreNames = sanit(document.getElementById("BLICK_wbar_ignoreNames").value);
      thread.ignoreIDs = sanit(document.getElementById("BLICK_wbar_ignoreIDs").value);
      if (id === "") {
        this.sets.set("autoUpdWbar", document.getElementById("BLICK_wbar_autoUpdate").checked ?? true);
        this.wdata.set("numLinkMode", document.getElementById("BLICK_wbar_linkTo").selectedIndex ?? 0);
      }
      //
      this.debSave();
      if (close)
        this.setDiag.style.display = "none";
    }
    debSave() {
      clearTimeout(this.saveTimer);
      this.saveTimer = setTimeout(() => {
        this.wdata.save();
        this.sets.save();
      }, this.saveInterval);
    }
    scrollObserver(entries, observer) {
      let topElement = document.elementFromPoint(300, 0); //get top element at 300px to left
      if (!topElement || topElement.tagName === "HTML")
        topElement = document.elementFromPoint(300, 20); //if gap, check 20px below
      topElement = topElement?.closest('td.reply') ?? null;
      if (topElement !== null) {
        let postId = topElement.id.match(/\d+/); //id of element at top of viewport
        if (this.curThread != null && postId != null) { //if watched
          this.curThread.currentReadId = postId[0]; //
          this.debSave();
        }
      }
    }
    addThread(id, board) {
      if (this.wdata.data.threads.has(id))
        return;
      const nthread = { ...this.wdata.data.default };
      nthread.section = board;
      this.wdata.data.threads.set(id, nthread);
      this.insertEntries();
      this.updateEntries();
    }
    insertObserver() {
      if (this.sets.data.autoUpdWbar)
        this.updateEntries(); //autoupdate on pageload
      document.querySelectorAll("img.watchthread").forEach((el) => {
        const clickstr = el.parentElement?.getAttribute("onclick");
        const threadData = clickstr?.match(/addtowatchedthreads\('(.+?)','(.+?)'\)/) ?? null;
        if (threadData === null || threadData.length < 3)
          return;
        el.dataset.targetid = threadData[1];
        el.dataset.targetboard = threadData[2];
        if (this.wdata.data.threads.has(threadData[1]))
          el.classList.add("BLICK_wbutton_iswatched");
        //
        el.addEventListener("click", (ev) => {
          let target = ev.target;
          if (target.dataset.targetid === undefined || target.dataset.targetboard === undefined)
            return;
          this.addThread(target.dataset.targetid, target.dataset.targetboard);
          target.classList.add("BLICK_wbutton_iswatched");
        });
      });
      //
      const locMatch = location.href.match(/https:\/\/questden.org\/kusaba\/(\w+)\/res\/(\d+)(\+\d+)?.html/i);
      if (locMatch === null)
        return;
      let id = locMatch[2];
      if (!this.wdata.data.threads.has(id))
        return; //log data only for watched websites.
      this.curThread = this.wdata.data.threads.get(id) ?? null;
      //scroll to last read post
      let currentReadPost = document.getElementById(`reply${this.curThread?.currentReadId}`);
      if (currentReadPost)
        currentReadPost.scrollIntoView(); //executed before Quest Reader, so should not collide.
      //
      const replyElements = document.querySelectorAll('td.reply');
      const lastmatch = replyElements[replyElements.length - 1].id.match(/\d+/);
      if (lastmatch !== null && this.curThread !== null)
        this.curThread.lastReadId = lastmatch[0];
      this.wdata.save();
      //
      const observer = new IntersectionObserver((a, b) => { this.scrollObserver(a, b); }, {
        root: null, //  Viewport
        rootMargin: '0px',
        threshold: 0 // Pixel
      });
      replyElements.forEach(el => observer.observe(el));
    }
    fetchFromSite() {
      const fetchPromises = this.thList.map(el => {
        return fetch(`https://questden.org/kusaba/threadwatch.php?board=${el}`)
          .then(response => response.text())
          .then(htmlText => {
            const parser = new DOMParser();
            const doc = parser.parseFromString(htmlText, 'text/html');
            const entries = doc.querySelectorAll('body span.filetitle');
            entries.forEach(entry => {
              const id = entry.previousElementSibling?.textContent?.trim() ?? null;
              if (id === null)
                return;
              const entr = { ...this.wdata.data.default };
              entr.section = el;
              entr.label = entry.textContent?.trim() ?? "";
              entr.author = entry.nextElementSibling?.textContent?.trim() ?? "";
              const replyEl = entry.nextElementSibling?.nextElementSibling;
              entr.newEntrCnt = parseInt(replyEl?.textContent?.trim() ?? "0");
              //link to newest element.
              //however, lastread should be the element before that
              //also, currentread should be the one to jump to when visiting.
              // if(replyEl?.tagName==="a"){
              // 	const mat=replyEl.getAttribute("href")?.match(/\d+$/)??null;
              // if(mat!==null)entr.lastReadId=parseInt(mat[0]);
              // }
              if (!this.wdata.data.threads.has(id)) {
                this.wdata.data.threads.set(id, entr);
              }
            });
          });
      });
      Promise.all(fetchPromises)
        .then(() => {
          // this.wdata.data.threads.sort((a, b) => a.section.localeCompare(b.section));
          this.wdata.save();
          this.insertEntries();
        })
        .catch(error => {
          console.error("Fehler beim Laden der Daten:", error);
        });
    }
    generateLink(thread, id, curReadInd) {
      let add = "";
      let jump = "";
      let targetInd = this.wdata.data.numLinkMode == 1 ? thread.newEntrCnt : (thread.totalEntrCnt - curReadInd);
      //target last known or last read depending on mode
      if (targetInd < 50 && thread.totalEntrCnt > 50)
        add = "+50";
      else if (targetInd < 100 && thread.totalEntrCnt > 100)
        add = "+100";
      //+50/+100 if possible and target in page.
      if (curReadInd == -1 && this.wdata.data.numLinkMode == 1)
        add = "";
      //when inserting without update, knowing last reading position is within +50/+500 is not possible.
      if (this.wdata.data.numLinkMode == 0 && thread.lastReadId !== "")
        jump = "#" + thread.lastReadId;
      if (this.wdata.data.numLinkMode == 1 && thread.currentReadId !== "")
        jump = "#" + thread.currentReadId;
      return `https://questden.org/kusaba/${thread.section}/res/${id}${add}.html${jump}`;
    }
    updateEntries() {
      let fetchPromise = [];
      document.getElementById("BLICK_watch_update")?.style.setProperty("filter", "hue-rotate(150deg)");
      this.wdata.data.threads.forEach((thread, id) => {
        fetchPromise.push(fetch(`https://questden.org/kusaba/quest/res/${id}.html`)
          .then(response => response.text())
          .then(htmlText => {
            //performance improve idea: count instances of reply update in text for total count. Then cut away text in front of lastReadId before parsing.
            const parser = new DOMParser();
            const doc = parser.parseFromString(htmlText, 'text/html');
            //
            //parse website
            let lastReadElement = null;
            let newCount = 0;
            let curReadInd = -1; //index of current read position
            const replyElements = doc.querySelectorAll(`td.reply`);
            replyElements.forEach((td, ind) => {
              if (thread.currentReadId == td.id)
                curReadInd = ind;
              if (lastReadElement === null && (td.id.endsWith(thread.lastReadId) || thread.lastReadId == ""))
                lastReadElement = td; //filter for only new elements
              if (lastReadElement !== null) { //ignore old
                let postauthName = td.querySelector('span.postername')?.textContent?.toLowerCase() ?? "";
                let postauthID = td.querySelector('span.uid')?.textContent?.match(/\d+/) ?? ["0"];
                if ((!thread.highImgOnly || td.querySelector('img') !== null) && //filter images
                  (thread.highNames.length == 0 || thread.highNames.includes(postauthName)) && //filter poster name
                  (thread.highIDs.length == 0 || thread.highIDs.includes(postauthID[0])) && //filter poster ID
                  (thread.ignoreIDs.length == 0 || !thread.ignoreIDs.includes(postauthID[0])) && //filter ignore ID
                  (thread.ignoreNames.length == 0 || !thread.ignoreNames.includes(postauthName)) //filter ignore name
                )
                  ++newCount;
              }
            });
            //
            //update threads
            thread.newEntrCnt = newCount;
            thread.totalEntrCnt = replyElements.length;
            thread.label = doc.querySelector("div.postwidth span.filetitle")?.innerText.trim() ?? thread.label;
            thread.author = doc.querySelector("div.postwidth span.postername")?.innerText.trim() ?? thread.author;
            this.wdata.save();
            //
            //update list
            let entrNewCnt = document.querySelector(`div.BLICK_wrow[data-id='${id}'] div.BLICK_wnew a`);
            if (entrNewCnt !== null) {
              entrNewCnt.innerHTML = String(newCount);
              entrNewCnt.href = this.generateLink(thread, id, curReadInd);
            }
            let entrLabel = document.querySelector(`div.BLICK_wrow[data-id='${id}'] div.BLICK_wtitle a`);
            if (entrLabel !== null)
              entrLabel.innerText = thread.label;
            let entrAuthor = document.querySelector(`div.BLICK_wrow[data-id='${id}'] div.BLICK_wauthor`);
            if (entrAuthor !== null)
              entrAuthor.innerText = thread.author;
          }));
      });
      Promise.all(fetchPromise).then(() => {
        document.getElementById("BLICK_watch_update")?.style.removeProperty("filter");
      });
    }
    insertEntries() {
      if (this.wdata.data.threads.size == 0) {
        this.cont.replaceChildren("No watched threads yet!");
        return;
      }
      const fragment = document.createDocumentFragment();
      const groupedThreads = new Map(this.thList.map(section => [section, []]));
      this.wdata.data.threads.forEach((thread, id) => { groupedThreads.get(thread.section)?.push(id); });
      groupedThreads.forEach((threads, section) => {
        if (threads.length > 0) {
          let head = document.createElement("div");
          head.className = "BLICK_whead";
          head.innerText = section;
          fragment.appendChild(head);
        }
        threads.forEach(id => {
          const el = this.wdata.data.threads.get(id);
          const row = document.createElement('div');
          row.className = "BLICK_wrow";
          row.dataset.id = id;
          row.innerHTML = `
          <div class="BLICK_wcell BLICK_wtitle"><a target="_blank" href="https://questden.org/kusaba/${el.section}/res/${id}.html">${el.label}</a></div>
          <div class="BLICK_wcell BLICK_wauthor">${el.author}</div>
          <div class="BLICK_wcell BLICK_wnew"><a target="_blank" href="${this.generateLink(el, id, -1)}">${el.newEntrCnt ?? 0}</a></div>
          <div class="BLICK_wcell BLICK_wactions">
              <button data-role='remove' title='Remove watch'>🗑️</button>
              <button data-role='options' title='Adjust settings'>🛠️</button>
          </div>`;
          fragment.appendChild(row);
        });
      });
      this.cont.replaceChildren(fragment);
    }
  }
  //
  //
  let imgQ;
  (function (imgQ) {
    imgQ[imgQ["convert"] = 0] = "convert";
    imgQ[imgQ["fullview"] = 1] = "fullview";
    imgQ[imgQ["thumbnail"] = 2] = "thumbnail";
  })(imgQ || (imgQ = {}));
  let imgF;
  (function (imgF) {
    imgF[imgF["webp"] = 0] = "webp";
    imgF[imgF["jpeg"] = 1] = "jpeg";
    imgF[imgF["unchanged"] = 2] = "unchanged";
  })(imgF || (imgF = {}));
  //
  class FileExport {
    constructor() {
      this.title = "";
      this.author = "";
      this.entryList = [];
      this.insertHTML();
      this.insertStyle();
      this.filter = {
        onlyImg: true, onlyCheck: false, imgQuality: imgQ.convert, include: "", exclude: "", style: 0,
        imgSets: { maxWidth: 800, maxHeight: 800, imgFormat: imgF.webp }, toEpub: true
      };
    }
    ;
    insertHTML() {
      this.form = document.createElement("div");
      this.form.style.display = "none";
      this.form.id = "BLICK_epub_form_overlay";
      this.form.innerHTML = `
        <div id="BLICK_epub_form">
          <h2>Export Thread to EPUB</h2>
          <label for="BLICK_epub_title">Title:</label>
          <input type="text" id="BLICK_epub_title" class="BLICK_epub_input" placeholder="Enter title" required="">
          <label for="BLICK_epub_author">Author:</label>
          <input type="text" id="BLICK_epub_author" class="BLICK_epub_input" placeholder="Enter author" required="">
          <div id='BLICK_epub_checkmarks'>
            <label for="BLICK_epub_includeImages">
              <input type="checkbox" id="BLICK_epub_includeImages">
              Only Include Posts with Images
            </label>
            <label for="BLICK_epub_includeChecked">
              <input type="checkbox" id="BLICK_epub_includeChecked">
              Only Include checked Posts
            </label>
          </div>
          <label for="BLICK_epub_imageQuality">Image Quality</label>
          <select id="BLICK_epub_imageQuality" class="BLICK_epub_select">
            <option value="convert">Convert images</option>
            <option value="fullsize">Use fullsize images</option>
            <option value="thumbnail">Use thumbnails</option>
          </select>
          <label for="BLICK_epub_imageSetting">Image Settings</label>
          <div id='BLICK_epub_imageSetting'>
            <label for="BLICK_epub_imageFormat">Format</label>
            <select id="BLICK_epub_imageFormat" class="BLICK_epub_select" title='Target image format.\nWebp is compresses best and supports animations, but is not yet supported by Kindle.\nJPG compresses photos and gradients well and is widely supported, but removes animations.\nNot converting preserves the format, but gif-animations are replaced by their first frame.'>
              <option value="webp">webp</option>
              <option value="jpg">JPG</option>
              <option value="unchanged">Don't convert</option>
            </select>
            <label for="BLICK_epub_imageMaxW">Max width:</label>
            <input type="text" id="BLICK_epub_imageMaxW" class="BLICK_epub_input" placeholder="Max width" title="Max width in px. 0 to ignore.">
            <label for="BLICK_epub_imageMaxH">Max height:</label>
            <input type="text" id="BLICK_epub_imageMaxH" class="BLICK_epub_input" placeholder="Max height" title="Max height in px. 0 to ignore.">
          </div>
          <label for="BLICK_epub_includeAuthors">Filter by Author:</label>
          <input type="text" id="BLICK_epub_includeAuthors" class="BLICK_epub_input" placeholder="Author ID or name" style="">
          <button data-role="detect" class="BLICK_epub_button" title='Generates a list of Names and IDs of the authors of posts with images'>Detect</button>
          <label for="BLICK_epub_excludeAuthors">Exclude Authors:</label>
          <input type="text" id="BLICK_epub_excludeAuthors" class="BLICK_epub_input" placeholder="Authors to exclude">
          <label for="BLICK_epub_styleSelect">Select Style:</label>
          <select id="BLICK_epub_styleSelect" class="BLICK_epub_select">
            <option>Simple, full-width images</option>
            <option>Simple, floating images with max. width</option>
          </select>
          <div id="BLICK_epub_stat">0 Posts, 0 Images</div>
          <div class="BLICK_epub_buttons">
            <button data-role="export" class="BLICK_epub_button">Export</button>
            <button data-role="cancel" class="BLICK_epub_button">Cancel</button>
            <button data-role="select" class="BLICK_epub_button" title='highlights and checks all posts that will go into the epub file'>Highlight</button>
          </div>
        </div>`;
      document.body.appendChild(this.form);
      this.statDispEl = document.getElementById("BLICK_epub_stat");
      this.form.addEventListener("click", (ev) => {
        if (ev.target === this.form)
          return this.hideEPUBForm();
        if (!(ev.target instanceof HTMLButtonElement))
          return;
        switch (ev.target.dataset.role) {
          case "detect":
            this.detectPosterName();
            break;
          case "select":
            this.transferSettings();
            this.selectEntries();
            break;
          case "export":
            this.transferSettings();
            if (this.filter.toEpub)
              this.exportEpub();
            else
              this.exportCBZ();
            break;
          case "cancel":
            this.hideEPUBForm();
            break;
          default:
            break;
        }
      });
      this.form.addEventListener("change", (ev) => {
        this.transferSettings();
        this.filterEntries(document);
      }, true);
      document.getElementById("BLICK_epub_imageQuality")?.addEventListener("change", ev => {
        let target = ev.target;
        let visibl = target.selectedIndex == imgQ.convert;
        document.getElementById("BLICK_epub_imageSetting")?.style.setProperty("display", visibl ? "" : "none");
        document.querySelector("label[for='BLICK_epub_imageSetting']")?.style.setProperty("display", visibl ? "" : "none");
      });
      document.getElementById("delform")?.addEventListener("click", (ev) => {
        let target = ev.target;
        if (target.matches("input[type='checkbox'][name='post[]']"))
          if (target.checked)
            target.parentElement?.classList.add("BLICK_epub_selected");
          else
            target.parentElement?.classList.remove("BLICK_epub_selected");
      });
    }
    insertStyle() {
      const sty = document.createElement("style");
      sty.id = 'BLICK_epub_style';
      sty.innerHTML = `
          #BLICK_epub_form_overlay{position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;background-color: rgba(0, 0, 0, 0.5);display: flex;justify-content: center;align-items: center;z-index: 1000;}
          #BLICK_epub_form {color: #333; display: grid; grid-template-columns: 130px 3fr 1fr; gap: 15px 5px; border-radius: 8px; box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2); padding: 20px; width: 600px; margin: auto; align-items: center; border: 1px solid #b0a394;  background-color: #fffff0;}
          #BLICK_epub_form h2 {grid-column: span 3; margin: 0; text-align: center; color: #4CAF50;}
          #BLICK_epub_form .BLICK_epub_input,.BLICK_epub_select {padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; grid-column: span 2;}
          #BLICK_epub_form .BLICK_epub_buttons {grid-column: span 3; display: flex; justify-content: space-between; margin-top: 10px;}
          #BLICK_epub_form .BLICK_epub_button {color: white; border: none; border-radius: 4px; padding: 10px 15px; cursor: pointer; transition: background-color 0.3s; flex: 1; margin: 0 5px;}
          #BLICK_epub_form .BLICK_epub_button:hover {filter: brightness(120%);}
          #BLICK_epub_form .BLICK_epub_button[data-role='export']{background-color:#3ba44b}
          #BLICK_epub_form .BLICK_epub_button[data-role='cancel']{background-color:#bb5546}
          #BLICK_epub_form .BLICK_epub_button[data-role='select']{background-color:#dd9243}
          #BLICK_epub_form .BLICK_epub_button[data-role='detect']{background-color:#dd9243;}
          #BLICK_epub_form #BLICK_epub_includeAuthors {grid-column: span 1;}
          #BLICK_epub_form label,#BLICK_epub_form input[type='checkbox']  {cursor: pointer;}
          #BLICK_epub_stat {grid-column: span 3;text-align: center;}
          .BLICK_epub_selected{background-color: #f005;}
          #BLICK_epub_imageSetting {display: flex;grid-column: span 2;align-items: center; justify-content: space-between;}
          #BLICK_epub_imageSetting label{text-align:center;}
          #BLICK_epub_imageFormat {width: 68px;}
          #BLICK_epub_imageMaxW, #BLICK_epub_imageMaxH {width: 60px;}
          #BLICK_epub_checkmarks{grid-column: 2 /span 2;display:flex;justify-content: space-between;}
          `;
      document.head.appendChild(sty);
    }
    showExportForm(mode = "epub") {
      if (this.form == null)
        return;
      this.form.style.display = "flex";
      //
      if (mode == "cbz") {
        this.form.querySelector("h2").innerHTML = "Export Thread to CBZ";
        this.form.querySelector("#BLICK_epub_styleSelect").style.display = "none";
        this.form.querySelector("label[for='BLICK_epub_styleSelect']").style.display = "none";
        this.form.querySelector("label[for='BLICK_epub_includeImages']").style.display = "none";
        this.filter.toEpub = false;
      }
      else if (mode = "epub") {
        this.form.querySelector("h2").innerHTML = "Export Thread to EPUB";
        this.form.querySelector("#BLICK_epub_styleSelect").style.display = "";
        this.form.querySelector("label[for='BLICK_epub_styleSelect']").style.display = "";
        this.form.querySelector("label[for='BLICK_epub_includeImages']").style.display = "";
        this.filter.toEpub = true;
      }
      else {
        alert("questden_blick2 error: unknown export form mode");
        throw new Error("questden_blick2 error: unknown export form mode: " + mode);
      }
      //	
      document.getElementById("BLICK_epub_includeImages").checked = this.filter.onlyImg;
      document.getElementById("BLICK_epub_includeChecked").checked = this.filter.onlyCheck;
      document.getElementById("BLICK_epub_includeAuthors").value = this.filter.include;
      document.getElementById("BLICK_epub_excludeAuthors").value = this.filter.exclude;
      document.getElementById("BLICK_epub_styleSelect").selectedIndex = this.filter.style;
      document.getElementById("BLICK_epub_imageQuality").selectedIndex = this.filter.imgQuality;
      document.getElementById("BLICK_epub_imageFormat").selectedIndex = this.filter.imgSets.imgFormat;
      document.getElementById("BLICK_epub_imageMaxW").value = String(this.filter.imgSets.maxWidth);
      document.getElementById("BLICK_epub_imageMaxH").value = String(this.filter.imgSets.maxHeight);
      //
      this.title = document.querySelector("div.postwidth span.filetitle")?.innerText ?? "Default";
      document.getElementById("BLICK_epub_title").value = this.title;
      this.author = document.querySelector("div.postwidth span.postername")?.innerText ?? "Bob";
      document.getElementById("BLICK_epub_author").value = this.author;
      this.filterEntries(document);
    }
    hideEPUBForm() { this.form.style.display = "none"; }
    transferSettings() {
      this.filter.onlyCheck = document.getElementById("BLICK_epub_includeChecked").checked;
      this.filter.onlyImg = document.getElementById("BLICK_epub_includeImages").checked;
      this.filter.include = document.getElementById("BLICK_epub_includeAuthors").value;
      this.filter.exclude = document.getElementById("BLICK_epub_excludeAuthors").value;
      this.filter.style = document.getElementById("BLICK_epub_styleSelect").selectedIndex;
      this.filter.imgQuality = document.getElementById("BLICK_epub_imageQuality").selectedIndex;
      //
      this.filter.imgSets.imgFormat = document.getElementById("BLICK_epub_imageFormat").selectedIndex;
      this.filter.imgSets.maxWidth = parseInt(document.getElementById("BLICK_epub_imageMaxW").value);
      this.filter.imgSets.maxHeight = parseInt(document.getElementById("BLICK_epub_imageMaxH").value);
      //
      this.title = document.getElementById("BLICK_epub_title").value ?? "Default";
      this.author = document.getElementById("BLICK_epub_author").value ?? "Bob";
    }
    filterEntries(cont) {
      this.entryList = [];
      let imgCnt = 0;
      cont.querySelectorAll("div.postwidth").forEach(el => {
        el.parentElement.classList.remove("BLICK_epub_selected");
        //
        let authName = el.querySelector("span.postername")?.innerText ?? "<noname>";
        let authID = el.querySelector("span.uid")?.innerText.substring(4) ?? "<noid>";
        let hasImg = el.querySelector("img.thumb") !== null;
        let elChecked = el.querySelector("input[type='checkbox'][name='post[]']")?.checked ?? false;
        //
        if (this.filter.onlyCheck === true && !elChecked)
          return;
        if (this.filter.onlyImg === true && hasImg === false)
          return;
        if (this.filter.include !== "" && (!this.filter.include.includes(authName) && !this.filter.include.includes(authID)))
          return;
        if (this.filter.exclude !== "" && (this.filter.exclude.includes(authName) || this.filter.exclude.includes(authID)))
          return;
        //
        if (hasImg)
          ++imgCnt;
        this.entryList.push(el);
      });
      document.getElementById("BLICK_epub_stat").innerHTML = `${this.entryList.length} Posts, ${imgCnt} Images`;
    }
    detectPosterName() {
      if (this.form === null)
        return;
      const authNam = new Set(), authID = new Set();
      document.querySelectorAll("img.thumb").forEach(el => {
        const par = el.closest("div.postwidth");
        if (par === null)
          return;
        authNam.add(par.querySelector("span.postername")?.innerText);
        authID.add(par.querySelector("span.uid")?.innerText.substring(4));
      });
      const resStr = [...authNam, ...authID].join(", ");
      document.getElementById("BLICK_epub_includeAuthors").value = resStr;
    }
    selectEntries() {
      this.filterEntries(document);
      document.querySelectorAll("input[type='checkbox'][name='post[]']").forEach(el => {
        el.checked = false;
        el.parentElement.classList.remove("BLICK_epub_selected");
      });
      this.entryList.forEach(el => {
        let cb = el.querySelector("input[type='checkbox'][name='post[]']");
        cb.checked = true;
        el.parentElement.classList.add("BLICK_epub_selected");
      });
    }
    async fetchImages(imgUrls) {
      let imgcnt = 0;
      const requests = imgUrls.map(async (url) => {
        try {
          const response = await fetch(url);
          ++imgcnt;
          if (!response.ok) {
            throw new Error("Failed to fetch image");
          }
          const blob = await response.blob();
          this.statDispEl.innerHTML = `Downloading Image: ${imgcnt}/${imgUrls.length}`;
          const name = url.split("/").pop() || "unknown";
          //
          let ret = { name, img: blob };
          if (this.filter.imgQuality === imgQ.convert)
            ret = await this.processImage(blob, name, this.filter.imgSets.maxWidth, this.filter.imgSets.maxHeight);
          return ret;
        }
        catch (ex) {
          console.log("error in fetching images!", ex, url);
          throw ex;
        }
      });
      return Promise.all(requests);
    }
    async processImage(blob, name, maxWidth = 800, maxHeight = 800) {
      const img = new Image();
      return new Promise((resolve, reject) => {
        img.onload = () => {
          const canvas = document.createElement("canvas");
          let { width, height } = img;
          if ((maxWidth > 0 && width > maxWidth) || (maxHeight > 0 && height > maxHeight)) {
            const scaleFactor = Math.min(maxWidth / width, maxHeight / height);
            width = width * scaleFactor;
            height = height * scaleFactor;
          }
          //
          canvas.width = width;
          canvas.height = height;
          const ctx = canvas.getContext("2d");
          if (ctx == null)
            return;
          ctx.drawImage(img, 0, 0, width, height);
          //
          const fileType = name.split('.')?.pop()?.toLowerCase() ?? null;
          const formatMap = { webp: 'image/webp', jpeg: 'image/jpeg', jpg: 'image/jpeg', png: 'image/png', gif: 'image/gif' };
          const origFormat = fileType !== null ? formatMap[fileType] : formatMap.jpeg;
          let outputFormat = origFormat;
          if (this.filter.imgSets.imgFormat === imgF.jpeg)
            outputFormat = formatMap.jpeg;
          else if (this.filter.imgSets.imgFormat === imgF.webp)
            outputFormat = formatMap.webp;
          canvas.toBlob((processedBlob) => {
            if (processedBlob !== null)
              resolve({ name, img: processedBlob });
            else
              reject();
          }, outputFormat);
        };
        img.onerror = reject;
        img.src = URL.createObjectURL(blob);
      });
    }
    exportCBZ() {
      this.filter.onlyImg = true;
      this.filterEntries(document);
      const imgs = new Set();
      this.entryList.forEach((el, ind, arr) => {
        this.statDispEl.innerHTML = `Processing Post: ${ind}/${this.entryList.length}%`;
        let cont = el.parentElement;
        const imgEl = cont?.querySelector("img.thumb");
        let imgName = "";
        if (imgEl !== null) {
          let imgUrl = imgEl.src;
          if (imgUrl.includes("spoiler.png"))
            imgUrl = imgEl.closest("a")?.href.replace(".", "s.").replace("src", "thumb") ?? ""; //thumbnail-link from full-link
          if (this.filter.imgQuality !== imgQ.thumbnail) { //all thumbnail. convert uses fullview as base
            imgUrl = imgUrl.replace("s.", ".").replace("thumb", "src"); //full-link from thumbnail-link 
          }
          imgName = imgUrl.split("/").pop() ?? "";
          if (imgName !== "." && imgName !== "")
            imgs.add(imgUrl); //exclude spoiler image without image. link ends then wiht "/."
        }
      });
      //
      const zip = new JSZip();
      //
      this.fetchImages([...imgs]).then((imageBlob) => {
        let imgCnt = 0;
        imageBlob.forEach(blob => {
          ++imgCnt;
          zip.file(`${String(imgCnt).padStart(4, '0')}_${blob.name}`, blob.img, { binary: true });
          this.statDispEl.innerHTML = `Adding Image: ${imgCnt}/${imageBlob.length}`;
        });
      }).then(() => {
        return zip.generateAsync({
          type: "blob",
          compression: "DEFLATE" //, 
          // compressionOptions: { level: 1 }
        }, (metadata) => {
          this.statDispEl.innerHTML = `Compressing: ${metadata.percent.toFixed(2)}%`;
        });
      }).then(blob => {
        const link = document.createElement("a");
        link.href = URL.createObjectURL(blob);
        link.download = `[${this.author}]_${this.title}.cbz`;
        link.click();
        this.statDispEl.innerHTML = `Downloading.`;
      });
    }
    exportEpub() {
      this.filterEntries(document);
      const imgs = new Set();
      const cont = this.entryList.map((el, ind, arr) => {
        this.statDispEl.innerHTML = `Processing Post: ${ind}/${this.entryList.length}%`;
        let cont = el.parentElement;
        //
        const author = cont?.querySelector("span.postername")?.innerText ?? "Bob";
        const title = cont?.querySelector("span.filetitle")?.innerText ?? "";
        const id = cont?.querySelector("input[type='checkbox'][name='post[]']")?.value ?? "";
        const date = cont?.querySelector("span.postername")?.nextSibling?.textContent?.trim() ?? "";
        //
        let msg = cont?.querySelector("blockquote")?.innerHTML ?? ""; //message body
        msg = msg.replace(/src="\/kusaba\/.*?\/thumb\//gi, "src=\"images/") //icons/thumbnails
          .replace(/href="\/kusaba\/.*?\/res\/.*?#/gi, "href=\"#") //references
          .replace(/onmouseover=".*?"/gi, "") //remove onmouseover
          .replace(/onmouseout=".*?"/gi, "") //remove onmouseout
          .replace(/<a .*?>\s*&gt;&gt;(\d+)/gi, "<a data-refid='$1' onmouseenter='hoverPrevEnter(event)' onmouseleave='hoverPrevLeave(event)' href='#$1'>&gt;&gt;$1"). //adjust reflinks
          replace('<div style="display:inline-block; width:400px;"></div><br>', ""); //remove leading spacers
        cont?.querySelector("blockquote")?.querySelectorAll("img").forEach(el => { imgs.add(el.src); }); //download msg images/icons
        //
        const imgEl = cont?.querySelector("img.thumb");
        let imgName = "";
        if (imgEl !== null) {
          let imgUrl = imgEl.src;
          if (imgUrl.includes("spoiler.png"))
            imgUrl = imgEl.closest("a")?.href.replace(".", "s.").replace("src", "thumb") ?? ""; //thumbnail-link from full-link
          if (this.filter.imgQuality !== imgQ.thumbnail) { //all thumbnail. convert uses fullview as base
            imgUrl = imgUrl.replace("s.", ".").replace("thumb", "src"); //full-link from thumbnail-link 
          }
          imgName = imgUrl.split("/").pop() ?? "";
          if (imgName !== "." && imgName !== "")
            imgs.add(imgUrl); //exclude spoiler image without image. link ends then wiht "/."
        }
        //
        let tex = `<div id='${id}' class='post'>`;
        if (imgName !== "")
          tex += `<img class='thumb' src='images/${imgName}' />`;
        tex += `<div class='postHead'>
          <span class='post_author'>${author}</span>
          <span class='post_title'>${title}</span>
          <span class='post_date'>${date}</span>
          <span class='post_id'>#${id}</span>
        </div><div class='postBody'>${msg}</div></div>`;
        return tex;
      }).join("");
      //
      const zip = new JSZip();
      zip.file("mimetype", "application/epub+zip", { compression: "STORE" });
      zip.file("META-INF/container.xml", `
        <?xml version="1.0" encoding="UTF-8"?>
        <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
            <rootfiles>
                <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
            </rootfiles>
        </container>
      `);
      zip.file("OEBPS/content.opf", `
        <?xml version="1.0" encoding="UTF-8"?>
        <package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookId" version="2.0">
            <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
                <dc:title>${this.title}</dc:title>
                <dc:creator>${this.author}</dc:creator>
                <dc:language>en</dc:language>
            </metadata>
            <manifest>
                <item id="content" href="content.html" media-type="application/xhtml+xml"/>
                <item href="style.css" id="css" media-type="text/css" />
            </manifest>
            <spine>
                <itemref idref="content"/>
            </spine>
        </package>
      `);
      switch (this.filter.style) {
        default:
        case 0:
          zip.file("OEBPS/style.css", `
            div.post{margin-bottom: 25px;border-bottom: 1px dashed #ccc;padding: 10px;}
            img.thumb{width:90%;display: block;margin:auto;}
            span.post_title {color: red;font-weight: bold;}
            span.post_author {color: green;font-weight: bold;}
            div.postHead{display:flex;display: flex;justify-content: space-between;margin-bottom:15px;flex-wrap: wrap;}
            #hover-content {background-color: #f9f9f9;padding: 10px;border: 1px solid #ccc;box-shadow: 0px 0px 10px rgba(0,0,0,0.1);z-index: 1000;display: none;}
            span.unkfunc{color: #789922;}
          `);
          break;
        case 1:
          zip.file("OEBPS/style.css", `
          div.post{margin-bottom: 25px;border-bottom: 1px dashed #ccc;padding: 10px;}
          img.thumb{display: block;float:left;width:100%;max-width:300px;margin-right:10px;}
          div.post::after {content: "";display: table;clear: both;}
          span.post_title {color: red;font-weight: bold;}
          span.post_author {color: green;font-weight: bold;}
          div.postHead{display:flex;justify-content: space-between;margin-bottom:15px;min-width:200px;flex-wrap: wrap;}
          #hover-content {background-color: #f9f9f9;padding: 10px;border: 1px solid #ccc;box-shadow: 0px 0px 10px rgba(0,0,0,0.1);z-index: 1000;display: none;}
          span.unkfunc{color: #789922;}
        `);
          break;
      }
      zip.file("OEBPS/content.html", `
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE html>
        <html xmlns="http://www.w3.org/1999/xhtml">
            <head>
              <title>${this.title}</title>
              <link href="style.css" rel="stylesheet" type="text/css" />
              <script>
                function hoverPrevEnter(e){
                  const hoverContent = document.getElementById('hover-content');
                  hoverContent.style.left = e.pageX + 15 + 'px'; // 15px Offset from the cursor
                  hoverContent.style.top = e.pageY + 15 + 'px';									const targetId = e.target.dataset.refid;
                  const targetElement = document.getElementById(targetId);
                  if (targetElement) {
                      hoverContent.innerHTML = targetElement.innerHTML;
                      hoverContent.style.display = 'block';
                  }
                };
                function hoverPrevLeave(e){
                  const hoverContent = document.getElementById('hover-content');
                  hoverContent.style.display = 'none';
                  hoverContent.innerHTML = '';
                }
              </script>
            </head>						
            <body>
              <header>${this.title}</header>
              <div id='questauthor'>By ${this.author}</div>
              ${cont}
            <div id="hover-content" style="position: absolute; display: none;"></div>
            </body>
        </html>
      `);
      this.fetchImages([...imgs]).then((imageBlob) => {
        let imgCnt = 0;
        imageBlob.forEach(blob => {
          ++imgCnt;
          zip.file(`OEBPS/images/${blob.name}`, blob.img, { binary: true }); //
          this.statDispEl.innerHTML = `Adding Image: ${imgCnt}/${imageBlob.length}`;
        });
      }).then(() => {
        return zip.generateAsync({
          type: "blob",
          compression: "DEFLATE" //, 
          // compressionOptions: { level: 1 }
        }, (metadata) => {
          this.statDispEl.innerHTML = `Compressing: ${metadata.percent.toFixed(2)}%`;
        });
      }).then(blob => {
        const link = document.createElement("a");
        link.href = URL.createObjectURL(blob);
        link.download = `[${this.author}]_${this.title}.epub`;
        link.click();
        this.statDispEl.innerHTML = `Downloading.`;
      });
    }
  }
  class DOMManip {
    //
    constructor(set) {
      this.setting = set;
      //
      this.EvImgHoverIn = this.EvImgHoverIn.bind(this);
      this.EvImgHoverOut = this.EvImgHoverOut.bind(this);
      this.repform = new ReplyForm();
      this.watchbar = null;
      //
      this.insertStyleEl();
      this.insertHoverImg();
    }
    ;
    //
    insertStyleEl() {
      this.styEl = document.createElement("style");
      this.styEl.id = 'BLICK_manipStyle';
      document.head.appendChild(this.styEl);
      //
      this.styles = { fontsize: "", paragraphMargin: "", invertCol: "", replyForm: "" };
    }
    insertHoverImg() {
      this.hoverImgCont = document.createElement("div");
      this.hoverImgCont.id = "BLICK_imgbox";
      //
      this.wartImg = document.createElement("img");
      this.wartImg.src = imgRes.wartbild;
      this.wartImg.className = "loading";
      this.hoverImgCont.appendChild(this.wartImg);
      //
      this.fullImg = document.createElement("img");
      this.fullImg.src = imgRes.wartbild;
      this.fullImg.className = "imgEl";
      this.hoverImgCont.appendChild(this.fullImg);
      //
      document.body.appendChild(this.hoverImgCont);
      //
      this.hoverImgCont.addEventListener('mouseenter', this.EvImgHoverIn);
      this.hoverImgCont.addEventListener('mouseleave', this.EvImgHoverOut);
    }
    updateStyle() {
      this.styEl.innerHTML = Object.values(this.styles).join("\n");
    }
    initStyles() {
      this.setFontsize();
      this.setMargin();
      this.setInvert();
      this.setReplyForm();
      this.setWatchbar();
      this.setHoverImg();
      this.updateStyle();
    }
    //
    setFontsize(update = false) {
      if (!this.setting.data.changeFont) {
        this.styles.fontsize = "";
      }
      else {
        const ffam = this.setting.fontTypeList[this.setting.data.fonttype];
        const fsize = this.setting.data.fontsize + "pt";
        const templ = `#delform .bbcode_quote{line-height:1.5em;font-family:${ffam};font-size:${fsize};}
          #delform h2.title {font-family:${ffam};font-weight:bold;font-size: ${fsize};}
          #delform {line-height:1.5em;font-family:${ffam};font-size:${fsize};margin-right: 7%;margin-top: 15px;margin-bottom: 25px;text-align: left;letter-spacing: 1px;color: #003;}`;
        this.styles.fontsize = templ;
      }
      if (update)
        this.updateStyle();
    }
    setMargin(update = false) {
      if (!this.setting.data.paragraphMargin) {
        this.styles.paragraphMargin = "";
      }
      else {
        const pg = window.location.href.search(/\/[\d\+]+\.html/) == -1;
        const inv = !this.setting.data.invertCol;
        //
        const templ = `#delform>br,#delform>hr {display:none;}
          #delform${pg ? ">div" : ""}{box-shadow:0px 0px 5px 3px #${inv ? "000" : "fff"},5px 5px 15px 5px #${!inv ? "000" : "fff"},inset 0px 0px 5px #${!inv ? "000" : "fff"};margin-bottom:30px;	border-radius:5px;	border: 2px groove #000;}
          #delform{width: 100%;}
          .thumb{box-shadow:0px 0px 15px ${inv ? "#fff" : "#000"};	border-radius:10px;}
          .reply{width: 100%;}
          .userdelete{clear:both;}
          #delform>div{padding: 10px;}
          #delform > div::after {content: "";display: inline-block;clear: both;}
          #delform > div > div[id] {clear: both;}
          #delform>span {	margin-top:10px;}
          #delform img.inlineimg {vertical-align: middle!important;	margin: 0 4px;}
      `; //width: 98%;display:table;
        this.styles.paragraphMargin = templ;
      }
      if (update)
        this.updateStyle();
    }
    setInvert(update = false) {
      if (!this.setting.data.invertCol) {
        this.styles.invertCol = "";
      }
      else {
        const pg = window.location.href.search(/\/[\d\+]+\.html/) == -1;
        const templ = `html, body{background-color:#002!important;}
          #delform a {color: #BB5555;font-size: 14px;font-style: italic;text-decoration: underline;}
          #delform, #delform .reply, #delform blockquote{background-color:#001;color: #FFD;}
          #delform span.spoiler{text-shadow:none;}
          #delform .highlight{background-color:#003;}
          ${this.setting.data.paragraphMargin ? ".thumb{	box-shadow:0px 0px 15px #fff;	border-radius:10px;}" : ""}
          "#delform h2.title{color:#fdd}
          ${this.setting.data.paragraphMargin ? ".thumb{	box-shadow:0px 0px 15px #fff;	border-radius:10px;}" : ""}
          ${this.setting.data.paragraphMargin ? ((pg ? "#delform>div{" : "#delform>div{") + "box-shadow:0px 0px 5px 3px #fff,5px 5px 15px 5px #000000,inset 0px 0px 5px #000;}") : ""}
          #delform {color:#ffd}`;
        this.styles.invertCol = templ;
      }
      if (update)
        this.updateStyle();
    }
    setReplyForm(update = false) {
      if (!this.setting.data.replyForm) {
        this.styles.replyForm = "";
        this.repform.removeBar();
        document.querySelector("#BLICK_previewbut")?.remove();
        document.querySelector("#BLICK_cb_preview")?.remove();
      }
      else {
        this.styles.replyForm = `
        #BLICK_previewbut{margin-right:0px!important;}/*workaround questreader*/
        td.postblock + td { display: grid; grid-template-columns: 1fr 30px auto!important; }/*workaround questreader*/
        .postarea table tr td:nth-of-type(2) {display: flex;gap: 5px;align-items: center;}
        .postarea table tr td a.rules{width:auto!important;}
        .postarea table tr td input[type="text"] {flex: 1;}
        .postarea table tr td textarea{width: 100%;  box-sizing: border-box;}
        input[name="postpassword"]{flex:0.5!important;}
        `;
        document.querySelectorAll(".postarea table tr td:nth-of-type(2)").forEach(el => {
          const prevEl = el.querySelector("input[type='text']") ?? null;
          if (prevEl === null)
            return;
          let tex = "";
          el.childNodes.forEach(node => {
            if (node.nodeType == Node.TEXT_NODE) {
              tex += node.textContent?.trim();
              el.removeChild(node);
            }
          });
          if (tex !== "") {
            tex = tex.replace(/\(|\)/g, "");
            prevEl.placeholder = tex;
            prevEl.title = tex;
          }
        });
        let pwbox = document.querySelector(`input[name="postpassword"]`);
        if (pwbox?.nextSibling === null)
          pwbox?.insertAdjacentHTML("afterend", "<span></span>"); //questReader workaround for .nextSibling.remove()
        this.repform.insertBar();
      }
      if (update)
        this.updateStyle();
    }
    setWatchbar() {
      if (this.setting.data.manageWatchlist) {
        if (this.watchbar === null)
          this.watchbar = new WatchBar(this.setting);
      }
      else {
        if (this.watchbar !== null) {
          this.watchbar.destroy();
          this.watchbar = null;
        }
      }
    }
    setHoverImg() {
      if (this.setting.data.imageHover) {
        document.querySelectorAll('img.thumb:not([BLICK_Hover])').forEach(img => {
          img.addEventListener('mouseover', this.EvImgHoverIn);
          img.addEventListener('mouseleave', this.EvImgHoverOut);
        });
      }
      else {
        document.querySelectorAll("img.thumb[BLICK_Hover]").forEach(img => {
          img.removeEventListener("mouseover", this.EvImgHoverIn);
          img.removeEventListener("mouseleave", this.EvImgHoverOut);
          img.removeAttribute("BLICK_Hover");
        });
      }
    }
    EvImgHoverIn(ev) {
      if (!(ev.target instanceof HTMLImageElement))
        return;
      //
      const linkEl = ev.target.parentNode?.parentNode;
      if (linkEl == null)
        return;
      const fullImageUrl = linkEl.getAttribute('href') ?? "";
      //
      this.wartImg.style.display = 'block';
      this.fullImg.style.display = 'none';
      this.hoverImgCont.style.display = 'block';
      //
      this.fullImg.src = fullImageUrl;
      this.fullImg.onload = () => {
        this.wartImg.style.display = 'none';
        this.fullImg.style.display = 'block';
      };
    }
    EvImgHoverOut(ev) {
      if (!this.hoverImgCont.contains(ev.relatedTarget)) {
        this.hoverImgCont.style.display = 'none';
      }
    }
  }
  //
  // start
  //
  function main() {
    //action starts after storage is loaded/sanitized
    delete Array.prototype.toJSON; //remove prototype.js prototype changes to stringify()
    const set = new Settings();
    const syncr = new Sync();
    const dom = new DOMManip(set);
    const sb = new Sidebar(dom, set, syncr);
    set.load().then(() => {
      sb.loadSettings();
      dom.initStyles();
    }).catch(ex => console.log("Blick error: critical initialization error", ex));
  }
  //
  //loading storage
  //
  main();
})();
//# sourceMappingURL=questden_blick2.user.js.map