diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..5636f38
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+/build/*.js
\ No newline at end of file
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..1a76640
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,61 @@
+{
+ "parserOptions": {
+ "ecmaVersion": 9,
+ "sourceType": "module",
+ "ecmaFeatures": {}
+ },
+ "rules": {
+ "constructor-super": 2,
+ "for-direction": 2,
+ "getter-return": 2,
+ "no-case-declarations": 2,
+ "no-class-assign": 2,
+ "no-compare-neg-zero": 2,
+ "no-cond-assign": 2,
+ "no-const-assign": 2,
+ "no-constant-condition": 2,
+ "no-control-regex": 2,
+ "no-debugger": 2,
+ "no-delete-var": 2,
+ "no-dupe-args": 2,
+ "no-dupe-class-members": 2,
+ "no-dupe-keys": 2,
+ "no-duplicate-case": 2,
+ "no-empty-character-class": 2,
+ "no-empty-pattern": 2,
+ "no-ex-assign": 2,
+ "no-extra-boolean-cast": 2,
+ "no-extra-semi": 2,
+ "no-fallthrough": 2,
+ "no-func-assign": 2,
+ "no-global-assign": 2,
+ "no-inner-declarations": 2,
+ "no-invalid-regexp": 2,
+ "no-irregular-whitespace": 2,
+ "no-mixed-spaces-and-tabs": 2,
+ "no-new-symbol": 2,
+ "no-obj-calls": 2,
+ "no-octal": 2,
+ "no-redeclare": 2,
+ "no-regex-spaces": 2,
+ "no-self-assign": 2,
+ "no-sparse-arrays": 2,
+ "no-this-before-super": 2,
+ "no-unexpected-multiline": 2,
+ "no-unreachable": 2,
+ "no-unsafe-finally": 2,
+ "no-unsafe-negation": 2,
+ "no-unused-labels": 2,
+ "no-unused-vars": 2,
+ "require-yield": 2,
+ "use-isnan": 2,
+ "valid-typeof": 2,
+ "no-undefined": 2,
+ "no-undef": 2
+ },
+ "env": {
+ "browser": true,
+ "node": true,
+ "es6": true
+ }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..deefaba
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+/node_modules
+/.vscode
+package-lock.json
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..529887e
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,18 @@
+{
+ "arrowParens": "avoid",
+ "bracketSpacing": true,
+ "htmlWhitespaceSensitivity": "css",
+ "insertPragma": false,
+ "jsxBracketSameLine": true,
+ "jsxSingleQuote": true,
+ "parser": "flow",
+ "printWidth": 80,
+ "proseWrap": "preserve",
+ "quoteProps": "as-needed",
+ "requirePragma": false,
+ "semi": false,
+ "singleQuote": true,
+ "tabWidth": 2,
+ "trailingComma": "none",
+ "useTabs": false
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 6e70d3d..f34072d 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@ Drawception ANBT [ (use to install/update manually, or "save as...")
+[**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js) (use to install/update manually, or "save as...")
[New canvas with recording and playback, standalone version](http://grompe.org.ru/drawit/)
@@ -22,13 +22,13 @@ A userscript to make Drawception.com better: more drawing tools, tablet support,
- Opera 15+: add the script in [Violentmonkey add-on](https://addons.opera.com/en/extensions/details/violent-monkey/?display=en)
- Maxthon: add the script in [Violentmonkey add-on](http://extension.maxthon.com/detail/index.php?view_id=1680)
- Mobile browsers / other / single use:
- - create a bookmark with the following URL:
+ - create a bookmark with the following URL (be careful, the "javascript:" at the beginning may not be copied with the rest in some browsers, don’t forget to check before executing the code):
- `javascript:(()=>(x=new XMLHttpRequest,x.open('GET','https://raw.githubusercontent.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js'),x.onload=()=>eval(x.responseText),x.send()))()`
+ `javascript:(()=>(x=new XMLHttpRequest,x.open('GET','https://raw.githubusercontent.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js'),x.onload=()=>eval(x.responseText),x.send()))()`
and follow it while being on drawception.com site; if that doesn't work, try pasting it in the address bar.
-After installing script management add-on, just click on the [**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js).
+After installing script management add-on, just click on the [**Direct script link**](https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js).
## FEATURES
diff --git a/build/drawception-anbt.user.js b/build/drawception-anbt.user.js
new file mode 100644
index 0000000..9ad8867
--- /dev/null
+++ b/build/drawception-anbt.user.js
@@ -0,0 +1,2312 @@
+// ==UserScript==
+// @name Drawception ANBT
+// @author Grom PE
+// @namespace http://grompe.org.ru/
+// @version 1.156.2019.06
+// @description Enhancement script for Drawception.com - Artists Need Better Tools
+// @downloadURL https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js
+// @match http://drawception.com/*
+// @match https://drawception.com/*
+// @grant none
+// @run-at document-start
+// @license Public domain
+// ==/UserScript==
+;(function() {
+ 'use strict'
+
+ const addDarkCSS = () =>
+ localStorage.setItem(
+ 'gpe_darkCSS',
+ (
+ 'a{color:#77c0ff$}.wrapper{~#444$}#nav-drag{~#353535$}.btn-default{~#7f7f7f$;border-bottom-color:#666$;border-left-color:#666$;border-right-color:#666$;border-top-color:#666$;color:#CCC$}' +
+ '.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{~#757575$;border-bottom-color:#565656$;border-left-color:#565656$;border-right-color:#565656$;border-top-color:#565656$;color:#DDD$}' +
+ '.btn-success{~#2e2e2e$;border-bottom-color:#262626$;border-left-color:#262626$;border-right-color:#262626$;border-top-color:#262626$;color:#CCC$}' +
+ '.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{~#232323$;border-bottom-color:#1c1c1c$;border-left-color:#1c1c1c$;border-right-color:#1c1c1c$;border-top-color:#1c1c1c$;color:#DDD$}' +
+ '.btn-primary{~#213184$;border-bottom-color:#1a1a68$;border-left-color:#1a1a68$;border-right-color:#1a1a68$;border-top-color:#1a1a68$;color:#CCC$}' +
+ '.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{~#191964$;border-bottom-color:#141450$;border-left-color:#141450$;border-right-color:#141450$;border-top-color:#141450$;color:#DDD$}' +
+ '.btn-info{~#2d7787$;border-bottom-color:#236969$;border-left-color:#236969$;border-right-color:#236969$;border-top-color:#236969$;color:#CCC$}' +
+ '.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{~#1c5454$;border-bottom-color:#133939$;border-left-color:#133939$;border-right-color:#133939$;border-top-color:#133939$;color:#DDD$}' +
+ '.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{~#3b3b3b$}.navbar-toggle{~#393939$}.navbar{border-bottom:1px solid #000$}.forum-thread-starter,.breadcrumb,.regForm{~#555$}' +
+ '.form-control{~#666$;border:1px solid #333$;color:#EEE$}code,pre{~#656$;color:#FCC$}body{color:#EEE$}footer{~#333$;border-top:1px solid #000$}' +
+ '.pagination>li:not(.disabled):not(.active),.pagination>li:not(.disabled):not(.active)>a:hover,.pagination>li:not(.disabled):not(.active)>span:hover,.pagination>li:not(.disabled):not(.active)>a:focus,.pagination>li:not(.disabled):not(.active)>span:focus{~#444$}.pagination>li>a,.pagination>li>span{~#555$;border:1px solid #000$}' +
+ '.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{~#666$;border-top:1px solid #444$;border-bottom:1px solid #444$}' +
+ '.drawingForm{~#555$}.well{~#666$;border:1px solid #333$}#timeleft{color:#AAA$}legend{border-bottom:1px solid #000$}.thumbpanel{color:#EEE;~#555$}.thumbpanel img{~#fffdc9$}.panel-number,.modal-content,.profile-user-header{~#555$}' +
+ '#commentForm{~#555$;border:1px solid #000$}.modal-header,.nav-tabs{border-bottom:1px solid #000$}hr,.modal-footer{border-top:1px solid #000$}' +
+ '.store-item{background:#666$;~-moz-linear-gradient(top,#666 0,#333 100%)$;~-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(100%,#333))$;~-webkit-linear-gradient(top,#666 0,#333 100%)$;~-o-linear-gradient(top,#666 0,#333 100%)$;~-ms-linear-gradient(top,#666 0,#333 100%)$;~linear-gradient(to bottom,#666 0,#333 100%)$;border:1px solid #222$}' +
+ '.store-item:hover{border:1px solid #000$}.store-item-title{~#222$;color:#DDD$}.store-title-link{color:#DDD$}.profile-award{~#222$}.profile-award-unlocked{~#888$}.progress-bar{color:#CCC$;~#214565$}.progress{~#333$}' +
+ '.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(0,0,0,0.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(0,0,0,0.15)),color-stop(.75,rgba(0,0,0,0.15)),color-stop(.75,transparent),to(transparent))$;background-image:-webkit-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:-moz-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$}' +
+ '.progress-bar-success{~#363$}.progress-bar-info{~#367$}.progress-bar-warning{~#863$}.progress-bar-danger{~#733$}' +
+ '.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#DDD$;~#555$;border:1px solid #222$}.nav>li>a:hover,.nav>li>a:focus{~#333$;border-bottom-color:#222$;border-left-color:#111$;border-right-color:#111$;border-top-color:#111$}' +
+ '.nav>li.disabled>a,.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#555$}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{~#333$}' +
+ '.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{~#555$}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{border-top:1px solid #333$}.news-alert{~#555$;border:2px solid #444$}' +
+ '.btn-menu{~#2e2e2e$}.btn-menu:hover{~#232323$}.btn-yellow{~#8a874e$}.btn-yellow:hover{~#747034$}' +
+ 'a.label{color:#fff$}.text-muted,a.text-muted{color:#999$}a.wrong-order{color:#F99$}div.comment-holder:target{~#454$}' +
+ '.popover{~#777$}.popover-title{~#666$;border-bottom:1px solid #444$}.popover.top .arrow:after{border-top-color:#777$}.popover.right .arrow:after{border-right-color:#777$}.popover.bottom .arrow:after{border-bottom-color:#777$}.popover.left .arrow:after{border-left-color:#777$}' +
+ '.label-fancy{~#444$;border-color:#333$;color:#FFF$}' +
+ '.avatar,.profile-avatar{~#444$;border:1px solid #777$;}' +
+ '.bg-lifesupport{~#444$}body{~#555$}.snap-content{~#333$}' +
+ 'select,textarea{color:#000$}.help-block{color:#ddd$}.jumbotron{~#444$}' +
+ '.navbar-dropdown{~#444$}a.list-group-item{~#444$;color:#fff$;border:1px solid #222$}a.list-group-item:hover,a.list-group-item:focus{~#222$}' +
+ '.likebutton.btn-success{color:#050$;~#5A5$}.likebutton.btn-success:hover{~#494$}' +
+ ".thumbnail[style*='background-color: rgb(255, 255, 255)']{~#555$}" +
+ '.popup,.v--modal{~#666$;border:1px solid #222$}.btn-reaction{~#666$;border:none$;color:#AAA$}@media(min-width:625px){.create-game-wrapper{~#555$}}' +
+ '.profile-header{~#555$}.profile-nav > li > a{~#333$}.profile-nav>li.active>a,.profile-nav>li>a:hover{~#555$}' +
+ '.gsc-control-cse{~#444$;border-color:#333$}.gsc-above-wrapper-area,.gsc-result{border:none$}.gs-snippet{color:#AAA$}.gs-visibleUrl{color:#8A8$}a.gs-title b,.gs-visibleUrl b{color:#EEE$}.gsc-adBlock{display:none$}.gsc-input{~#444$;border-color:#333$;color:#EEE$}' +
+ '.comment-highlight{border:none$;background:#454$}#header-emotes{~#555$}#header-bar-container{border:none$}.paypal-button-tag-content{color:#EEE$}.numlikes{color:#EEE$}.gsc-input-box{~#444$;border-color:#333$}.gsc-completion-container{~#333$;border-color:#000$}.gsc-completion-selected{~#222$}.gsc-completion-container b{color:#AAA$}.alert-nice{~#4a4a4a$}.store-buy-coins{~#777$}.store-buy-coins:hover{~#666$}.store-buy-coins>h2,.store-buy-coins>h2>small{color:#EEE$}.store-package-selector{~#888$}.store-package-selector>label{color:#EEE$}.label-stat{~#444$;color:#EEE$;border:1px solid #555$}.label-stat.disabled{~#333$}.option{padding:4px 8px$;~#666$;color:#EEE$;border-color:#333$}.option.selected{border-color:#EEE$}.sleek-select{~#666$;border-color:#333$}select{color:#EEE$}.modal-note{color:#EEE$}.vue-dialog-button{~#555$;border:none$}.vue-dialog-button:hover{~#5a5a5a$}.vue-dialog-buttons{border-top:1px solid #222$}.dashboard-item{~#333$}legend{color:#EEE$}.list-group-item{~#444$;color:#EEE$;border:1px solid #222$}.alert-warning{color:#EEE$;~#555$;border-color:#555$}.btn-reaction.active{border:1px solid #EEE$}.bg-shadow-box{~#333$}.btn-gray{~#222$;border:none$}.btn-gray:hover{color:#EEE$;~#1a1a1a$}.btn-bright{~#333$;color:#EEE$}' +
+ '.player-name-new{color:#33b73f$}.gsc-tabsArea>div{overflow:hidden$}.gs-image-popup-box{~#333$;border-color:#222$}.gs-size{color:#6f6f6f$}.gsc-result-info{color:#EEE$}.gsc-refinementsArea{border:none$}.gsc-tabsArea{border-bottom-color:#333$}.gsc-cursor-page{color:#EEE$}.gsc-cursor-current-page{color:#AAA$}.profile-nav>.disabled>a{color:#555$;~#3a3a3a$}.profile-nav>.disabled>a:hover{~#3a3a3a$}.sleek-select:hover{border-color:#EEE$}' +
+ '.btn-menu{border-color:#1e1e1e$;text-shadow:0px 0px 3px #777$}.btn-menu:hover{border-color:#1e1e1e$}.pagination>.active>span{color:#EEE$}#btn-notifications{color:#EEE$}.btn-warning{color:#EEE$}.alert-nice{color:#EEE$}.emotes-popup{~#2e2e2e$}.navbar-toggle{~#2e2e2e$;border-color:#1e1e1e$}.navbar-toggle .icon-bar{~#EEE$}' +
+ '.gamepanel-highlight{box-shadow:0 0 20px #111$;~#222$}' +
+ 'a.anbt_replaypanel:hover{color:#8af$}' +
+ '.anbt_favedpanel{color:#d9534f$}' +
+ ''
+ )
+ .replace(/~/g, 'background-color:')
+ .replace(/\$/g, ' !important')
+ )
+
+ const getLocalStorageItem = (name, empty) => {
+ const item = localStorage.getItem(name)
+ try {
+ return item ? JSON.parse(item) : empty || ''
+ } catch (e) {
+ return item ? item : empty || ''
+ }
+ }
+
+ const setDarkMode = () => {
+ const settings = getLocalStorageItem('gpe_anbtSettings', {})
+ if (settings.anbtDarkMode || typeof settings.anbtDarkMode === 'undefined') {
+ if (getLocalStorageItem('gpe_inDark', 0)) {
+ const css = document.createElement('style')
+ css.id = 'darkgraycss'
+ css.type = 'text/css'
+ css.appendChild(
+ document.createTextNode(getLocalStorageItem('gpe_darkCSS'))
+ )
+ if (document.head) document.head.appendChild(css)
+ else {
+ const darkLoad = setInterval(() => {
+ if (!document.head) return
+ document.head.appendChild(css)
+ clearInterval(darkLoad)
+ }, 100)
+ }
+ }
+ }
+ }
+
+ const options = {
+ enableWacom: 0,
+ fixTabletPluginGoingAWOL: 1,
+ hideCross: 0,
+ enterToCaption: 0,
+ pressureExponent: 0.5,
+ backup: 1,
+ timeoutSound: 0,
+ timeoutSoundBlitz: 0,
+ timeoutSoundVolume: 100,
+ newCanvas: 1,
+ proxyImgur: 0,
+ ajaxRetry: 1,
+ localeTimestamp: 0,
+ autoplay: 1,
+ submitConfirm: 1,
+ smoothening: 1,
+ autoBypassNSFW: 0,
+ colorNumberShortcuts: 1,
+ colorUnderCursorHint: 1,
+ bookmarkOwnCaptions: 0,
+ colorDoublePress: 0,
+ newCanvasCSS: '',
+ forumHiddenUsers: '',
+ maxCommentHeight: 1000,
+ useOldFont: true,
+ useOldFontSize: true,
+ markdownTools: 1,
+ anbtDarkMode: 1
+ }
+
+ const $ = (selector, array = false) => {
+ const elements = selector.startsWith('<')
+ ? [
+ ...new DOMParser().parseFromString(selector, 'text/html').body
+ .children
+ ]
+ : [...document.querySelectorAll(selector)]
+ return elements.length > 1 || array ? elements : elements[0]
+ }
+
+ const betterCreate = () => {
+ if (!options.enterToCaption) {
+ if ($('#prompt'))
+ $('#prompt').addEventListener('keydown', event => {
+ if (event.keyCode === 13) event.preventDefault()
+ })
+ }
+ }
+
+ const addStyle = css => {
+ const parent =
+ document.getElementsByTagName('head')[0] || document.documentElement
+ const style = document.createElement('style')
+ style.type = 'text/css'
+ style.appendChild(document.createTextNode(css))
+ parent.appendChild(style)
+ }
+
+ const globals = {
+ userId: getLocalStorageItem(
+ 'gpe_lastSeenId',
+ $('.player-dropdown a[href^="/player/"]') &&
+ $('.player-dropdown a[href^="/player/"]').href.match(
+ /\/player\/(\d+)\//
+ )[1]
+ ),
+ months: [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'Apr',
+ 'May',
+ 'Jun',
+ 'Jul',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec'
+ ],
+ alphabet: '36QtfkmuFds0UjlvCGIXZ125bEMhz48JSYgipwKn7OVHRBPoy9DLWaceqxANTr',
+ greetings: [
+ 'Oruvaq lbh!',
+ "Ubcr vg'f abg envavat gbqnl.",
+ 'Jurer vf lbhe tbq abj?',
+ 'Lbh fubhyq srry 5% zber cbjreshy abj.',
+ 'Fhqqrayl, abguvat unccrarq!',
+ '^_^',
+ 'Guvf gnxrf fb ybat gb svavfu...',
+ "Jungrire lbh qb, qba'g ernq guvf grkg.",
+ 'Pyvpx urer sbe 1 serr KC',
+ 'Or cngvrag.',
+ "Whfg qba'g fgneg nal qenzn nobhg vg.",
+ '47726S6Q2069732074686520677265617465737421',
+ 'Cynl fzneg.',
+ 'Cynl avpr.',
+ 'Fzvyr!',
+ "Qba'g sbetrg gb rng.",
+ "V xabj jung lbh'ir qbar.",
+ 'Fpvrapr!',
+ 'Gbqnl vf n tbbq qnl.'
+ ]
+ }
+
+ const decimalToBase62 = number => {
+ let result = ''
+ while (number !== 0) {
+ const quotient = number % 62
+ result = globals.alphabet[quotient] + result
+ number = (number - quotient) / 62
+ }
+ return result
+ }
+
+ const scrambleID = number => {
+ if (isNaN(number)) throw new Error('Invalid panel ID')
+ return [...decimalToBase62(parseInt(number, 10) + 3521614606208)]
+ .reverse()
+ .join('')
+ }
+
+ const linkifyDrawingPanels = img => {
+ if (img.parentNode.nodeName !== 'A') {
+ if (
+ img.src.match(/\/images\/panels\//) ||
+ img.src.match(/\/pub\/panels\//)
+ )
+ img.outerHTML = `${img.outerHTML}`
+ if (img.src.match(/\/drawings\//))
+ img.outerHTML = `${img.outerHTML}`
+ if (img.src.match(/\/panel\//))
+ img.outerHTML = `${img.outerHTML}`
+ if (img.src.match(/\/images\/games\//) || img.src.match(/\/pub\/games\//))
+ img.outerHTML = `${img.outerHTML}`
+ if (img.src.match(/\/display-panel.php?/)) {
+ const newsrc = `/panel/drawing/${scrambleID(
+ img.src.match(/x=(\d+)/)[1]
+ )}/`
+ img.setAttribute('src', newsrc)
+ img.outerHTML = `${img.outerHTML}`
+ }
+ }
+ }
+
+ const linkifyNodeText = node => {
+ if (node.textContent.includes('://'))
+ node.innerHTML = node.innerHTML.replace(
+ /([^"]|^)(https?:\/\/(?:(?:(?:[^\s<()]*\([^\s<()]*\))+)|(?:[^\s<()]+)))/g,
+ '$1$2'
+ )
+ }
+
+ const versions = {
+ scriptVersion: '1.156.2019.06',
+ newCanvasVersion: 53,
+ siteVersion: 'f6b35cce',
+ runtimeVersion: '1ba6bf05'
+ }
+
+ const setupNewCanvas = (insandbox, url) => {
+ const canvasHTML = localStorage.getItem('anbt_canvasHTML')
+ const canvasHTMLver = localStorage.getItem('anbt_canvasHTMLver')
+ if (
+ !canvasHTML ||
+ canvasHTMLver < versions.newCanvasVersion ||
+ canvasHTML.length < 10000
+ ) {
+ const request = new XMLHttpRequest()
+ request.open(
+ 'GET',
+ 'https://api.github.com/repos/EnderDragonneau/Drawception-ANBT/contents/build/index.html'
+ )
+ request.setRequestHeader('Accept', 'application/vnd.github.3.raw')
+ request.onload = () => {
+ if (request.responseText.length < 10000) {
+ alert(
+ `Error: instead of new canvas code, got this response from GitHub:\n${request.responseText}`
+ )
+ location.pathname = '/'
+ } else {
+ localStorage.setItem('anbt_canvasHTML', request.responseText)
+ localStorage.setItem('anbt_canvasHTMLver', versions.newCanvasVersion)
+ setupNewCanvas(insandbox, url)
+ }
+ }
+ request.onerror = () => {
+ alert('Error loading the new canvas code. Please try again.')
+ location.pathname = '/'
+ }
+ request.send()
+ return
+ }
+ const inforum = url.match(/forums\//)
+ const friendgameid = url.match(/play\/(.+)\//)
+ const panelid = url.match(/sandbox\/#?([^/]+)/)
+ const incontest =
+ url.match(/contests\/play\//) && document.getElementById('canvas-holder')
+ const vertitle = `ANBT v${versions.scriptVersion}`
+ if (incontest) window.onbeforeunload = () => {}
+ const normalurl =
+ insandbox && !inforum
+ ? `/sandbox/${panelid ? `#${panelid[1]}` : ''}`
+ : incontest
+ ? '/contests/play/'
+ : inforum
+ ? url.match(/\/forums\/?.+/)
+ : `/play/${friendgameid ? `${friendgameid[1]}/` : ''}`
+ try {
+ if (location.pathname + location.hash !== normalurl)
+ history.pushState({}, document.title, normalurl)
+ } catch (e) {}
+ const alarmSoundOgg =
+ 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABnHAAAAAAAAHQUSFoBHgF2b3JiaXMAAAAAAUSsAAAAAAAAYG0AAAAAAADJAU9nZ1MAAAAAAAAAAAAAZxwAAAEAAABq35G0DxD/////////////////NQN2b3JiaXMAAAAAAAAAAAEFdm9yYmlzH0JDVgEAAAEAFGNWKWaZUpJbihlzmDFnGWPUWoolhBRCKKVzVlurKbWaWsq5xZxzzpViUilFmVJQW4oZY1IpBhlTEltpIYQUQgehcxJbaa2l2FpqObacc62VUk4ppBhTiEromFJMKaQYU4pK6Jxz0DnmnFOMSgg1lVpTyTGFlFtLKXROQgephM5SS7F0kEoHJXRQOms5lRJTKZ1jVkJquaUcU8qtpphzjIHQkFUAAAEAwEAQGrIKAFAAABCGoSiKAoSGrAIAMgAABOAojuIokiI5kmM5FhAasgoAAAIAEAAAwHAUSZEUy9EcTdIszdI8U5ZlWZZlWZZlWZZd13VdIDRkFQAAAQBAKAcZxRgQhJSyEggNWQUAIAAAAIIowxADQkNWAQAAAQAIUR4h5qGj3nvvEXIeIeYdg9577yG0XjnqoaTee++99x5777n33nvvkWFeIeehk9577xFiHBnFmXLee+8hpJwx6J2D3nvvvfeec+451957752j3kHpqdTee++Vk14x6Z2jXnvvJdUeQuqlpN5777333nvvvffee++9955777333nvvrefeau+9995777333nvvvffee++9995777333nvvgdCQVQAAEAAAYRg2iHHHpPfae2GYJ4Zp56T3nnvlqGcMegqx9557773X3nvvvffeeyA0ZBUAAAgAACGEEFJIIYUUUkghhhhiyCGHHIIIKqmkoooqqqiiiiqqLKOMMsook4wyyiyjjjrqqMPOQgoppNJKC620VFtvLdUehBBCCCGEEEIIIYQQvvceCA1ZBQCAAAAwxhhjjEEIIYQQQkgppZRiiimmmAJCQ1YBAIAAAAIAAAAsSZM0R3M8x3M8x1M8R3RER3RER5RESbRETfREUTRFVbRF3dRN3dRNXdVN27VVW7ZlXdddXddlXdZlXdd1Xdd1Xdd1Xdd1XbeB0JBVAAAIAABhkEEGGYQQQkghhZRSijHGGHPOOSA0ZBUAAAgAIAAAAEBxFEdxHMmRJMmyLM3yLM8SNVMzNVNzNVdzRVd1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'
+ if (inforum) {
+ if (document.querySelector('.v--modal-overlay'))
+ document.querySelector('.v--modal-overlay').outerHTML = ''
+ const div = document.querySelector('.wrapper').children[1]
+ const iframe = document.createElement('iframe')
+ const modalOverlay = document.createElement('div')
+ modalOverlay.setAttribute('class', 'v--modal-overlay')
+ iframe.id = 'iframe'
+ iframe.setAttribute('class', 'v--modal-background-click')
+ modalOverlay.appendChild(iframe)
+ div.appendChild(modalOverlay)
+ iframe.contentWindow.document.open()
+ iframe.contentWindow.anbtReady = () => {
+ iframe.contentWindow.inforum = inforum
+ iframe.contentWindow.insandbox = insandbox
+ iframe.contentWindow.incontest = incontest
+ iframe.contentWindow.options = options
+ iframe.contentWindow.alarmSoundOgg = alarmSoundOgg
+ iframe.contentWindow.vertitle = vertitle
+ iframe.contentWindow.getLocalStorageItem = getLocalStorageItem
+ iframe.contentWindow.needToGoDeeper()
+ }
+ iframe.contentWindow.document.write(canvasHTML)
+ iframe.contentWindow.document.close()
+ return
+ }
+ document.open()
+ window.anbtReady = () => {
+ if (friendgameid) window.friendgameid = friendgameid[1]
+ if (panelid) window.panelid = panelid[1]
+ window.inforum = inforum
+ window.insandbox = insandbox
+ window.incontest = incontest
+ window.options = options
+ window.alarmSoundOgg = alarmSoundOgg
+ window.vertitle = vertitle
+ window.getLocalStorageItem = getLocalStorageItem
+ window.needToGoDeeper()
+ }
+ document.write(canvasHTML)
+ document.close()
+ }
+
+ const formatTimestamp = date => {
+ if (typeof date === 'number') date = new Date(date)
+ if (options.localeTimestamp) return date.toLocaleString()
+ return `${('0' + date.getDate()).slice(-2)} ${
+ globals.months[date.getMonth()]
+ } ${date.getFullYear()} ${('0' + date.getHours()).slice(-2)}:${(
+ '0' + date.getMinutes()
+ ).slice(-2)}`
+ }
+
+ const betterForums = () => {
+ $('.comment-body *', true).forEach(comment => linkifyNodeText(comment))
+ $('img', true).forEach(img => linkifyDrawingPanels(img))
+ if (document.location.pathname.match(/\/forums\/(\w+|-)\/.+/)) {
+ const hideUserIds = options.forumHiddenUsers
+ ? options.forumHiddenUsers.split(',')
+ : ''
+ if (hideUserIds)
+ addStyle(
+ '.anbt_hideUserPost:not(:target) {opacity: 0.4; margin-bottom: 10px}' +
+ '.anbt_hideUserPost:not(:target) .comment-body, .anbt_hideUserPost:not(:target) .avatar {display: none}' +
+ ''
+ )
+ let lastid = 0
+ $('.comment-avatar', true).forEach(({ parentNode }) => {
+ const commentHolder = parentNode.parentNode.parentNode
+ const anch = commentHolder.id || ''
+ commentHolder.classList.add('comment-holder')
+ const textMuted = commentHolder.querySelector('a.text-muted')
+ const vue = commentHolder.childNodes[0].__vue__
+ if (vue) {
+ textMuted.textContent = `${textMuted.textContent.trim()}, ${formatTimestamp(
+ vue.comment_date * 1000
+ )}`
+ if (vue.edit_date > 0) {
+ const el = textMuted.parentNode.querySelector('span[rel="tooltip"]')
+ const text = `${el.title}, ${formatTimestamp(
+ vue.edit_date * 1000
+ ).replace(/ /g, '\u00A0')}`
+ el.setAttribute('title', text)
+ }
+ }
+ if (anch) {
+ const id = parseInt(anch.substring(1), 10)
+ const text = textMuted.textContent.trim()
+ textMuted.textContent = `${text} #${id}`
+ textMuted.setAttribute('title', 'Link to post')
+ if (id < lastid) textMuted.classList.add('wrong-order')
+ try {
+ const { href } = commentHolder.querySelector('a[href^="/player/"]')
+ if (href) {
+ const userId = href.match(/\d+/)[0]
+ if (hideUserIds.includes(userId))
+ commentHolder.classList.add('anbt_hideUserPost')
+ }
+ } catch (e) {}
+ lastid = id
+ }
+ })
+ if (
+ $('.comment-holder') &&
+ $('.comment-holder').length === 20 &&
+ $('#comment-form .btn-primary')
+ )
+ $('#comment-form .btn-primary').insertAdjacentHTML(
+ 'afterend',
+ '
Note: posting to another page
'
+ )
+ }
+ if (options.proxyImgur)
+ $('img[src*="imgur.com/"]', true).forEach(img =>
+ img.setAttribute(
+ 'src',
+ img.src.replace('imgur.com', 'filmot.com').replace('https', 'http')
+ )
+ )
+ const pagination = $('.pagination', true)
+ if (pagination.length)
+ $('.breadcrumb').insertAdjacentHTML(
+ 'afterend',
+ `${pagination[0].outerHTML}
`
+ )
+ if (document.location.pathname.match(/\/forums\/(\w+)\/$/)) {
+ const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', [])
+ let hidden = 0
+ const tempUnhideLink = $('')
+ $('.forum-thread', true).forEach(thread => {
+ const href = thread
+ .querySelector('a:first-child')
+ .href.match(/\/forums\/\w+\/(\d+)\//)
+ if (!href || !href[1] || href[1] === 11830) return
+ const id = href[1]
+ if (hiddenTopics.includes(id)) {
+ thread.classList.add('anbt_hidden')
+ hidden++
+ }
+ const hideLink = $('')
+ hideLink.addEventListener('click', () => {
+ const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', [])
+ if (hiddenTopics.includes(id)) {
+ if (hiddenTopics.includes(id))
+ hiddenTopics.splice(hiddenTopics.indexOf(id), 1)
+ hiddenTopics.splice(hiddenTopics.indexOf(id), 1)
+ thread.classList.remove('anbt_hidden')
+ hidden--
+ } else {
+ if (!hiddenTopics.includes(id)) hiddenTopics.push(id)
+ hiddenTopics.push(id)
+ thread.classList.add('anbt_hidden')
+ hidden++
+ tempUnhideLink.style.display = ''
+ }
+ tempUnhideLink.textContent = hidden
+ localStorage.setItem(
+ 'gpe_forumHiddenTopics',
+ JSON.stringify(hiddenTopics)
+ )
+ })
+ thread.querySelector('p:nth-child(2)').appendChild(hideLink)
+ })
+ tempUnhideLink.textContent = hidden
+ tempUnhideLink.addEventListener('click', () => {
+ $('#main').classList.toggle('anbt_showt')
+ })
+ if (!hidden) tempUnhideLink.style.display = 'none'
+ if ($('#js-btn-toggle-thread'))
+ $('#js-btn-toggle-thread').parentNode.appendChild(tempUnhideLink)
+ }
+ $('.btn.btn-default', true).forEach(button =>
+ button.addEventListener('click', () => {
+ if (button.textContent === 'Draw')
+ setupNewCanvas(true, document.location.href)
+ })
+ )
+ }
+
+ const betterComments = () => {
+ const comments = [...$('#comments').nextElementSibling.children].slice(1)
+ comments.forEach(x => {
+ x.parentNode.parentNode.classList.add('comment-holder')
+ })
+ const gamePlayers = []
+ const playerdata = {}
+ $('.gamepanel-holder', true).forEach((gamePanel, index) => {
+ const detail = gamePanel.querySelector('.panel-details')
+ const gamepanel = gamePanel.querySelector('.gamepanel')
+ const playerLink = detail.querySelector('.panel-user a')
+ if (!playerLink) return
+ const id = playerLink.href.match(/\/player\/(\d+)\//)[1]
+ playerdata[id] = {
+ panel_number: index + 1,
+ player_anchor: playerLink,
+ panel_id: gamepanel.id,
+ drew: gamepanel.querySelector('img') !== null,
+ comments: 0
+ }
+ gamePlayers.push(id)
+ })
+ const seenComments = getLocalStorageItem('gpe_seenComments', {})
+ const gameid = document.location.href.match(/game\/([^/]+)\//)[1]
+ if (comments) {
+ const hour = Math.floor(Date.now() / (1000 * 60 * 60))
+ for (const tempgame in seenComments)
+ if (seenComments[tempgame].h + 24 * 7 < hour)
+ delete seenComments[tempgame]
+ let maxseenid = 0
+ comments.forEach(holder => {
+ const dateElement = holder.querySelector('a.text-muted')
+ const vue = holder.__vue__
+ if (vue) {
+ const text = dateElement.textContent.trim()
+ dateElement.textContent = `${text}, ${formatTimestamp(
+ vue.comment_date * 1000
+ )}`
+ if (vue.edit_date > 0) {
+ const element = dateElement.parentNode.querySelector(
+ 'span[rel="tooltip"]'
+ )
+ const title = `${element.title}, ${formatTimestamp(
+ vue.edit_date * 1000
+ ).replace(/ /g, '\u00A0')}`
+ element.setAttribute('title', title)
+ }
+ }
+ const ago = dateElement.textContent
+ const commentid = parseInt(holder.id.slice(1), 10)
+ dateElement.setAttribute('title', 'Link to comment')
+ dateElement.textContent = `${dateElement.textContent.trim()} #${commentid}`
+ if (ago.match(/just now|min|hour|a day| [1-7] day/)) {
+ if (!(seenComments[gameid] && seenComments[gameid].id >= commentid)) {
+ holder.classList.add('comment-new')
+ if (maxseenid < commentid) maxseenid = commentid
+ }
+ }
+ const link = holder.querySelector('.text-bold a')
+ ? holder.querySelector('.text-bold a').href.match(/\/player\/(\d+)\//)
+ : ''
+ if (link) {
+ const id = link[1]
+ if (gamePlayers.includes(id)) {
+ const drew = playerdata[id].drew ? 'drew' : 'wrote'
+ dateElement.insertAdjacentHTML(
+ 'beforebegin',
+ `(${drew} #${playerdata[id].panel_number}) `
+ )
+ playerdata[id].comments++
+ }
+ }
+ })
+ if (maxseenid)
+ seenComments[gameid] = {
+ h: hour,
+ id: maxseenid
+ }
+ localStorage.setItem('gpe_seenComments', JSON.stringify(seenComments))
+ }
+ for (const playerId in gamePlayers) {
+ const data = playerdata[playerId]
+ if (data && data.comments) {
+ const cmt2 = `Player left ${data.comments} comment${
+ data.comments > 1 ? 's' : ''
+ }`
+ data.player_anchor.title = cmt2
+ data.player_anchor.insertAdjacentHTML(
+ 'afterend',
+ `${data.comments}`
+ )
+ }
+ }
+ if (options.maxCommentHeight) {
+ const h = options.maxCommentHeight
+ comments.forEach(comment =>
+ comment.addEventListener('click', () => {
+ if (
+ comment.clientHeight > h - 50 &&
+ !$(location.hash).has(comment).length
+ )
+ location.hash = `#${comment.parentNode.parentNode.id}`
+ })
+ )
+ }
+ }
+
+ const waitForComments = () => {
+ const comments = $('#comments')
+ ? [...$('#comments').nextElementSibling.children].slice(1)
+ : ''
+ if (comments.length && !comments[0].classList.contains('spinner'))
+ betterComments()
+ else {
+ if (comments.length === 0) return
+ setTimeout(waitForComments, 1000)
+ }
+ }
+
+ const checkForRecording = (url, success, retrying) => {
+ const request = new XMLHttpRequest()
+ request.open('GET', `${url}?anbt`, true)
+ request.responseType = 'arraybuffer'
+ request.onload = () => {
+ const buffer = request.response
+ const dataView = new window.DataView(buffer)
+ const magic = dataView.getUint32(0)
+ if (magic != 0x89504e47) return request.onerror()
+ for (let i = 8; i < buffer.byteLength; i += 4) {
+ const chunklen = dataView.getUint32(i)
+ i += 4
+ const chunkname = dataView.getUint32(i)
+ i += 4
+ if (chunkname === 0x73764762) return success()
+ else {
+ if (chunkname === 0x49454e44) break
+ i += chunklen
+ }
+ }
+ }
+ request.onerror = () => {
+ console.log(
+ 'checkForRecording fail (likely due to cache without CORS), retrying'
+ )
+ if (!retrying) checkForRecording(`${url}?anbt`, success, true)
+ }
+ request.send()
+ }
+
+ const addReplayButton = drawing => {
+ if (drawing.replayAdded) return
+ drawing.replayAdded = true
+ const { parentNode, src } = drawing
+ checkForRecording(src, () => {
+ const newid = $(`img[src='${src}']`)
+ .parentNode.querySelector('a[href^="/panel/"]')
+ .href.match(/\/panel\/[^/]+\/([^/]+)/)[1]
+ const id = newid.length >= 8 ? newid : scrambleID(parentNode.id.slice(6))
+ const replayButton = $(
+ ``
+ )
+ replayButton.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(true, `/sandbox/#${id}`)
+ })
+ parentNode.insertAdjacentHTML('beforebegin', replayButton.outerHTML)
+ })
+ }
+
+ const reversePanels = () => {
+ const element = $('.gamepanel-holder')[0].parentNode.parentNode
+ ;[...element.childNodes]
+ .reverse()
+ .forEach(child => element.appendChild(child))
+ }
+
+ const betterGame = () => {
+ if (document.title === 'Not Safe For Work (18+) Gate') {
+ if (options.autoBypassNSFW) window.DrawceptionPlay.bypassNsfwGate()
+ return
+ }
+ const drawings = $(
+ 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]'
+ )
+ if ($('#btn-copy-url'))
+ $('#btn-copy-url').insertAdjacentHTML(
+ 'afterend',
+ ' Reverse'
+ )
+ $('.reversePanels').addEventListener('click', reversePanels)
+ const favButton = $(
+ ''
+ )
+ $('.panel-number', true).forEach(panelNumber =>
+ panelNumber.insertAdjacentHTML('afterend', favButton.outerHTML)
+ )
+ $('.gamepanel', true).forEach(({ parentNode }) => {
+ if (parentNode.querySelector('.gamepanel-tools>a:last-child') === null)
+ return
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ const id = parentNode
+ .querySelector('.gamepanel-tools>a:last-child')
+ .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1]
+ if (panels[id])
+ parentNode
+ .querySelector('.anbt_favpanel')
+ .classList.add('anbt_favedpanel')
+ })
+ $('.anbt_favpanel', true).forEach(favPanelButton => {
+ favPanelButton.addEventListener('click', () => {
+ if (favPanelButton.classList.contains('anbt_favedpanel')) return
+ const { parentNode } = favPanelButton
+ const id = parentNode
+ .querySelector('.gamepanel-tools>a:last-child')
+ .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1]
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ const panel = {
+ time: Date.now(),
+ by: parentNode.querySelector('.panel-user a').textContent
+ }
+ panel.userLink = parentNode
+ .querySelector('.panel-user a')
+ .href.match(/\/player\/[^/]+\/[^/]+\//)[0]
+ const img = parentNode.querySelector('.gamepanel img')
+ if (img) {
+ panel.image = img.src
+ panel.caption = img.alt
+ } else {
+ panel.caption = parentNode
+ .querySelector('.gamepanel')
+ .textContent.trim()
+ }
+ panels[id] = panel
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ favPanelButton.classList.add('anbt_favedpanel')
+ })
+ })
+ if (options.newCanvas) {
+ if (drawings)
+ drawings.forEach(drawing =>
+ drawing.addEventListener('load', addReplayButton(drawing))
+ )
+ }
+ setTimeout(waitForComments, 200)
+ }
+
+ const getCookie = name =>
+ document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`))[2] || null
+
+ const setCookie = (name, value, expire) => {
+ if (expire) {
+ const time = new Date()
+ time.setTime(time.getTime() + 24 * expire * 60 * 60 * 1e3)
+ expire = time.toUTCString()
+ }
+ document.cookie = `${name}=${value ? JSON.stringify(value) : ''}; expires=${
+ expire ? expire : 'Thu, 01 Jan 1970 00:00:00 UTC'
+ }; path=/`
+ }
+
+ const getPanelId = url => {
+ const match = url.match(/\/panel\/[^/]+\/(\w+)\//)
+ if (match) return match[1]
+ }
+
+ const base62ToDecimal = number => {
+ number = number.toString()
+ const cachePosition = {}
+ let result = 0
+ let pow = 1
+ for (let i = number.length - 1; i >= 0; i--) {
+ const character = number[i]
+ if (typeof cachePosition[character] === 'undefined') {
+ cachePosition[character] = globals.alphabet.indexOf(character)
+ }
+ result += pow * cachePosition[character]
+ pow *= 62
+ }
+ return result
+ }
+
+ const unscrambleID = string =>
+ base62ToDecimal([...string].reverse().join('')) - 3521614606208
+
+ const betterPanel = () => {
+ const favButton = $(
+ ''
+ )
+ const gamePanel = $(
+ '.panel-caption-display>.flex,.gamepanel-holder>.gamepanel'
+ )
+ if (gamePanel) gamePanel.insertAdjacentHTML('afterend', favButton.outerHTML)
+ const favBtn = $('.btn.btn-info')
+ if (favBtn) {
+ favBtn.addEventListener('click', event => {
+ event.preventDefault()
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ const panel = {
+ time: Date.now(),
+ by: $('.lead a', true)[0].textContent,
+ userLink: $('.lead a', true)[0].href.match(
+ /\/player\/[^/]+\/[^/]+\//
+ )[0]
+ }
+ const id = document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1]
+ const img = $('.gamepanel img')
+ if (img) {
+ panel.image = img.src
+ panel.caption = img.alt
+ } else {
+ panel.caption = $('.gamepanel').textContent.trim()
+ }
+ panels[id] = panel
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ favBtn.setAttribute('disabled', 'disabled')
+ favBtn.querySelector('b').textContent = 'Favorited!'
+ })
+ }
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ if (
+ document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//) &&
+ panels[document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1]]
+ ) {
+ favBtn.setAttribute('disabled', 'disabled')
+ favBtn.querySelector('b').textContent = 'Favorited!'
+ }
+ const panelId = getPanelId(location.pathname)
+ if (options.newCanvas && panelId && unscrambleID(panelId) >= 14924553) {
+ const img = $('.gamepanel img')
+ if (img)
+ checkForRecording(img.src, () => {
+ const replayLink = $(
+ ` Replay `
+ )
+ replayLink.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(true, `/sandbox/#${panelId}`)
+ })
+ $('.gamepanel').insertAdjacentHTML('afterend', replayLink.outerHTML)
+ })
+ }
+ if (
+ $('.btn-primary').length > 1 &&
+ $('.btn-primary')[1].textContent === 'Play again'
+ ) {
+ const ccButton = $(
+ ''
+ )
+ ccButton.addEventListener('click', event => {
+ event.preventDefault()
+ const id = unscrambleID(panelId)
+ const cookie = getCookie('covercreatorids')
+ const ids = cookie ? JSON.parse(cookie) : []
+ if (!ids.includes(id)) {
+ if (ids.length > 98) {
+ window.apprise(
+ 'Max cover creator drawings selected. Please remove some before adding more.'
+ )
+ return
+ } else ids.push(id.toString())
+ } else {
+ ccButton
+ .setAttribute('disabled', 'disabled')
+ .querySelector('b').textContent = 'Already added!'
+ return
+ }
+ setCookie('covercreatorids', JSON.stringify(ids))
+ ccButton
+ .setAttribute('disabled', 'disabled')
+ .querySelector('b').textContent = 'Added!'
+ })
+ $('.gamepanel').insertAdjacentHTML('afterend', ccButton.outerHTML)
+ }
+ }
+
+ const rot13 = number =>
+ [...number.toString()]
+ .map(character => {
+ character = character.charCodeAt(0)
+ if (character >= 97 && character <= 122)
+ character = ((character - 97 + 13) % 26) + 97
+ if (character >= 65 && character <= 90)
+ character = ((character - 65 + 13) % 26) + 65
+ return String.fromCharCode(character)
+ })
+ .join('')
+
+ const simpleHash = number =>
+ number
+ .toString()
+ .split('')
+ .reduce((a, b) => {
+ a = (a << 5) - a + b.charCodeAt(0)
+ return a & a
+ }, 0)
+
+ const randomGreeting = () => {
+ const change_every_half_day = Math.floor(Date.now() / (1000 * 60 * 60 * 12))
+ const rnddata = simpleHash(
+ change_every_half_day + parseInt(globals.userid, 10) + 178889
+ )
+ return rot13(globals.greetings[rnddata % globals.greetings.length])
+ }
+
+ const addReplaySign = drawing => {
+ if (drawing.replayAdded) return
+ drawing.replayAdded = true
+ const panel = drawing.parentNode.parentNode
+ const { src } = drawing
+ checkForRecording(src, () => {
+ const newid = src.match(/(\w+).png$/)[1]
+ const replaySign =
+ newid.length >= 8
+ ? $(
+ ``
+ )
+ : $(
+ ''
+ )
+ panel.appendChild(replaySign)
+ })
+ }
+
+ const fadeOut = (element, duration = 400) => {
+ duration = duration === 'slow' ? 600 : duration
+ element.style.opacity = element.style.opacity
+ ? parseFloat(element.style.opacity) - 0.1
+ : 1
+ if (parseFloat(element.style.opacity) < 0) {
+ element.style.opacity = 0
+ element.style.display = 'none'
+ } else
+ setTimeout(() => {
+ fadeOut(element, duration)
+ }, duration / 10)
+ }
+
+ const viewMyGameBookmarks = () => {
+ const removeButtonHTML =
+ ''
+ const games = getLocalStorageItem('gpe_gameBookmarks', {})
+ const result = []
+ for (let id in games) {
+ const extraClass = games[id].own ? ' anbt_owncaption' : ''
+ if (id.length > 10) {
+ result.push(
+ ``
+ )
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', `/play/${id}`)
+ xhr.onload = () => {
+ const { responseText, status } = xhr
+ if (status === 200) {
+ const m =
+ responseText.match(/Game is not private/) ||
+ (responseText.match(/Problem loading game/) && 'del')
+ if (m) {
+ const gamename =
+ `${
+ games[id].own
+ ? ` with your caption${
+ games[id].caption ? ` ${games[id].caption}` : ''
+ }`
+ : ''
+ }${
+ games[id].time
+ ? ` bookmarked on ${formatTimestamp(games[id].time)}`
+ : ''
+ }` || id
+ const status = m === 'del' ? 'Deleted' : 'Unfinished public'
+ $(`#${id}`).querySelector(
+ 'span'
+ ).textContent = `${status} game${gamename}`
+ return
+ }
+ const title = responseText.match(/(.+)<\/title>/)[1]
+ const [url, gameId] = responseText.match(/\/game\/([^/]+)\/[^/]+\//)
+ delete games[id]
+ games[gameId] = {
+ title,
+ url
+ }
+ $(`#${id}`).id = gameId
+ const spanId = $(`#${gameId}`).querySelector('span')
+ spanId.parentNode.replaceChild(
+ $(`${title}`),
+ spanId
+ )
+ localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
+ } else {
+ $(`#${id}`).querySelector(
+ 'span'
+ ).textContent = `Error while retrieving game: ${responseText}`
+ }
+ }
+ xhr.send()
+ } else if (id.length === 10)
+ result.push(
+ ``
+ )
+ }
+ $('#anbt_userpage').innerHTML = result.length
+ ? result.join('')
+ : "You don't have any bookmarked games."
+ $('#anbt_userpage .anbt_gamedel', true).forEach(gameDelete =>
+ gameDelete.addEventListener('click', event => {
+ event.preventDefault()
+ const { id } = gameDelete.parentNode
+ fadeOut($(`#${id}`))
+ delete games[id]
+ localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
+ })
+ )
+ }
+
+ const viewMyPanelFavorites = () => {
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ let result = ''
+ let needsUpdate = false
+ for (const id in panels) {
+ if (panels[id].image && panels[id].image.match(/^\/pub\/panels\//)) {
+ needsUpdate = true
+ panels[id].image = panels[id].image.replace(
+ '/pub/panels/',
+ 'https://cdn.drawception.com/images/panels/'
+ )
+ }
+ result += ``
+ }
+ if (needsUpdate)
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ result = result
+ ? `${result}`
+ : "You don't have any favorited panels."
+ $('#anbt_userpage').innerHTML = result
+ $('#anbt_userpage .anbt_paneldel', true).forEach(panelDelete =>
+ panelDelete.addEventListener('click', event => {
+ event.preventDefault()
+ const { id } = panelDelete.parentNode.parentNode
+ fadeOut($(`#${CSS.escape(id)}`))
+ delete panels[id]
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ })
+ )
+ }
+
+ const betterPlayer = () => {
+ const pubinfo = $('.profile-header-info .text-muted > span:last-child')
+ if (pubinfo) linkifyNodeText(pubinfo.parentNode)
+ const loc = document.location.href
+ if (loc.match(new RegExp(`/player/${globals.userId}/[^/]+/(?:$|#)`))) {
+ const anbtSection = $('ANBT stuff:
')
+ const panelFavoritesButton = $(
+ 'Panel Favorites'
+ )
+ const gameBookmarks = $(
+ 'Game Bookmarks'
+ )
+ anbtSection.appendChild(panelFavoritesButton)
+ anbtSection.appendChild(gameBookmarks)
+ const profilemain = $('.profile-layout-content').firstChild
+ profilemain.insertAdjacentHTML(
+ 'afterbegin',
+ `${randomGreeting()}
`
+ )
+ profilemain.insertAdjacentHTML('afterbegin', anbtSection.outerHTML)
+ $('.viewFavorites').addEventListener('click', event => {
+ event.preventDefault()
+ viewMyPanelFavorites()
+ })
+ $('.viewBookmarks').addEventListener('click', event => {
+ event.preventDefault()
+ viewMyGameBookmarks()
+ })
+ if (document.location.hash.includes('#anbt_panelfavorites'))
+ viewMyPanelFavorites()
+ if (document.location.hash.includes('#anbt_gamebookmarks'))
+ viewMyGameBookmarks()
+ if (window.date) {
+ const pubinfo = $('.profile-user-header>div.row>div>h1+p')
+ if (pubinfo)
+ [...pubinfo.childNodes][4].nodeValue = ` ${formatTimestamp(
+ window.date
+ )} \xa0`
+ }
+ } else {
+ const drawings = $(
+ 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]',
+ true
+ )
+ if (options.newCanvas)
+ drawings.forEach(drawing =>
+ drawing.addEventListener('load', addReplaySign(drawing))
+ )
+ drawings.forEach(({ src, parentNode }) => {
+ if (src.match(/-1\.png$/))
+ parentNode.parentNode.appendChild(
+ $(
+ '
'
+ )
+ )
+ })
+ }
+ if (loc.match(/player\/\d+\/[^/]+\/(posts)|(comments)\//)) {
+ $('.forum-thread-starter', true).forEach(threadStarter => {
+ const vue = threadStarter.childNodes[0].__vue__
+ if (vue) {
+ const ts = threadStarter.querySelector('a.text-muted').firstChild
+ ts.textContent = `${ts.textContent.trim()}, ${formatTimestamp(
+ vue.comment_date * 1000
+ )}`
+ if (vue.edit_date > 0) {
+ const el = ts.parentNode.parentNode.querySelector(
+ 'span[rel="tooltip"]'
+ )
+ const text = `${el.title}, ${formatTimestamp(
+ vue.edit_date * 1000
+ ).replace(/ /g, '\u00A0')}`
+ el.setAttribute('title', text)
+ }
+ }
+ const postlink = threadStarter.querySelector(
+ '.add-margin-top small.text-muted'
+ )
+ const created = postlink.textContent.match(/^\s*Created/)
+ const commented = postlink.textContent.match(/^\s*Commented/)
+ const prefix = commented
+ ? 'Comment in the game'
+ : created
+ ? 'New thread'
+ : 'Reply in'
+ const n = $(`${prefix}:
`)
+ const thread = postlink.querySelector('a')
+ n.appendChild(thread)
+ threadStarter.insertAdjacentHTML('afterbegin', n.outerHTML)
+ postlink.parentNode.parentNode.removeChild(postlink.parentNode)
+ })
+ }
+ }
+
+ const escapeHTML = value =>
+ value
+ .toString()
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+
+ const addGroup = (name, settings) => {
+ const controlGroup = $('')
+ controlGroup.appendChild($(``))
+ settings.forEach(setting => {
+ const value = options[setting[0]]
+ const [name, type, description] = setting
+ const controls = $('')
+ if (type === 'boolean') {
+ controls.appendChild(
+ $(
+ ``
+ )
+ )
+ } else if (type === 'number') {
+ $(
+ `${description}:`
+ ).forEach(node => controls.appendChild(node))
+ } else if (type === 'longstr') {
+ $(
+ `${description}:`
+ ).forEach(node => controls.appendChild(node))
+ } else {
+ $(
+ `${description}:`
+ ).forEach(node => controls.appendChild(node))
+ }
+ controlGroup.appendChild(controls)
+ })
+ return controlGroup
+ }
+
+ const fadeIn = (element, duration = 400) => {
+ element.style.display = 'inline'
+ duration = duration === 'slow' ? 600 : duration
+ element.style.opacity = element.style.opacity
+ ? parseFloat(element.style.opacity) + 0.1
+ : 0.2
+ if (parseFloat(element.style.opacity) > 1) element.style.opacity = 1
+ else
+ setTimeout(() => {
+ fadeIn(element, duration)
+ }, duration / 10)
+ }
+
+ const loadScriptSettings = () => {
+ const result = getLocalStorageItem('gpe_anbtSettings', null)
+ if (!result) return
+ for (const i in result) window.options[i] = result[i]
+ }
+
+ const updateScriptSettings = ({ currentTarget: theForm }) => {
+ const result = {}
+ theForm.querySelectorAll('input,textarea').forEach(fromField => {
+ if (fromField.type === 'checkbox')
+ result[fromField.name] = fromField.checked ? 1 : 0
+ else if (fromField.getAttribute('data-subtype') === 'number')
+ result[fromField.name] = parseFloat(fromField.value) || 0
+ else result[fromField.name] = fromField.value
+ })
+ localStorage.setItem('gpe_anbtSettings', JSON.stringify(result))
+ loadScriptSettings()
+ fadeIn($('#anbtSettingsOK'), 'slow')
+ setTimeout(() => {
+ fadeOut($('#anbtSettingsOK'), 'slow')
+ }, 800)
+ }
+
+ const betterSettings = () => {
+ const theForm = $(
+ ''
+ )
+ theForm.appendChild($(''))
+ theForm.appendChild(
+ addGroup('Pen Tablet (unavailable for the moment...)', [
+ [
+ 'enableWacom',
+ 'boolean',
+ 'Enable Wacom plugin / pressure sensitivity support'
+ ],
+ [
+ 'fixTabletPluginGoingAWOL',
+ 'boolean',
+ 'Try to prevent Wacom plugin from disappearing'
+ ]
+ ])
+ )
+ theForm.appendChild(
+ addGroup('Play (most settings are for the new canvas only)', [
+ [
+ 'newCanvas',
+ 'boolean',
+ 'New drawing canvas (also allows watching playback)'
+ ],
+ [
+ 'submitConfirm',
+ 'boolean',
+ 'Confirm submitting if more than a minute is left'
+ ],
+ ['smoothening', 'boolean', 'Smoothing of strokes'],
+ ['hideCross', 'boolean', 'Hide the cross when drawing'],
+ [
+ 'enterToCaption',
+ 'boolean',
+ 'Submit captions (and start games) by pressing Enter'
+ ],
+ [
+ 'backup',
+ 'boolean',
+ 'Save the drawing in case of error and restore it in sandbox'
+ ],
+ [
+ 'timeoutSound',
+ 'boolean',
+ 'Warning sound when only a minute is left (normal games)'
+ ],
+ [
+ 'timeoutSoundBlitz',
+ 'boolean',
+ 'Warning sound when only 5 seconds left (blitz)'
+ ],
+ ['timeoutSoundVolume', 'number', 'Volume of the warning sound, in %'],
+ [
+ 'rememberPosition',
+ 'boolean',
+ 'Show your panel position and track changes in unfinished games list'
+ ],
+ ['colorNumberShortcuts', 'boolean', 'Use 0-9 keys to select the color'],
+ [
+ 'colorUnderCursorHint',
+ 'boolean',
+ 'Show the color under the cursor in the palette'
+ ],
+ [
+ 'colorDoublePress',
+ 'boolean',
+ 'Double press 0-9 keys to select color without pressing shift'
+ ],
+ [
+ 'bookmarkOwnCaptions',
+ 'boolean',
+ 'Automatically bookmark your own captions in case of dustcatchers'
+ ]
+ ])
+ )
+ theForm.appendChild(
+ addGroup('Miscellaneous', [
+ [
+ 'localeTimestamp',
+ 'boolean',
+ `Format timestamps as your system locale (${new Date().toLocaleString()})`
+ ],
+ [
+ 'proxyImgur',
+ 'boolean',
+ 'Replace imgur.com links to filmot.com to load, in case your ISP blocks them'
+ ],
+ ['ajaxRetry', 'boolean', 'Retry failed AJAX requests'],
+ [
+ 'autoplay',
+ 'boolean',
+ 'Automatically start replay when watching playback'
+ ],
+ ['autoBypassNSFW', 'boolean', 'Automatically bypass NSFW game warning'],
+ ['markStalePosts', 'boolean', 'Mark stale forum posts'],
+ [
+ 'maxCommentHeight',
+ 'number',
+ 'Maximum comments and posts height until directly linked (px, 0 = no limit)'
+ ],
+ [
+ 'useOldFont',
+ 'boolean',
+ 'Use old Nunito font (which is usually bolder and less wiggly)'
+ ],
+ ['useOldFontSize', 'boolean', 'Use old, smaller font size'],
+ ['markdownTools', 'boolean', 'Markdown tools for messages'],
+ [
+ 'anbtDarkMode',
+ 'boolean',
+ "Switch between ANBT's and Drawception's dark mode"
+ ]
+ ])
+ )
+ theForm.appendChild(
+ addGroup('Advanced', [
+ [
+ 'newCanvasCSS',
+ 'longstr',
+ 'Custom CSS for new canvas (experimental, get styles here)'
+ ],
+ [
+ 'forumHiddenUsers',
+ 'longstr',
+ 'Comma-separated list of user IDs whose forum posts are hidden'
+ ]
+ ])
+ )
+ $(
+ '
'
+ ).forEach(node => theForm.appendChild(node))
+ $('#main').insertAdjacentHTML('afterbegin', theForm.outerHTML)
+ $('.settingsForm').addEventListener(
+ 'submit',
+ form => updateScriptSettings(form) && false
+ )
+ if ($('input[name="location"]'))
+ $('input[name="location"]').setAttribute('maxlength', '65')
+ }
+
+ const betterPages = {
+ betterCreate,
+ betterForums,
+ betterGame,
+ betterPanel,
+ betterPlayer,
+ betterSettings
+ }
+
+ const bold = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = new RegExp(`\\*\\*(${selection.replace(/\*/g, '')})\\*\\*`)
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (
+ value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selectionEnd += 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else
+ selection = selection.match(/\*\*.+\*\*/g)
+ ? selection.replace(/\*\*/g, '')
+ : `**${selection.replace(/\n/g, '**\n**')}**`
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else
+ selection = selection.match(/\*\*.+\*\*/g)
+ ? selection.replace(/\*\*/g, '')
+ : `**${selection.replace(/\n/g, '**\n**')}**`
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const code = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /^ {4}(.*)/gm
+ if (selection.match(selRegex)) selection = selection.replace(/^ {4}/gm, '')
+ else if (
+ selectionStart === 0 ||
+ value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm)
+ ) {
+ if (selection.match(/^ {4}/gm))
+ selection = selection.replace(/^ {4}/gm, '')
+ else
+ selection = `${
+ selectionStart === 0
+ ? ''
+ : value.substring(selectionStart - 1, selectionEnd).match(/^\n/)
+ ? '\n'
+ : '\n\n'
+ } ${selection.replace(/\n/g, '\n ')}`
+ } else
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n/)
+ ? '\n'
+ : '\n\n'
+ } ${selection.replace(/\n^(.*)/gm, '\n $1')}${
+ value.substring(selectionEnd, selectionEnd + 1).match(/\n/) ? '' : '\n'
+ }`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const heading = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /^#+ .*/gm
+ if (selection.match(selRegex)) {
+ selection = selection.replace(/^# /gm, '')
+ if (selection.match(/^#{2,} /gm)) selection.replace(/(^#*)# /gm, '$1 ')
+ } else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm)
+ )
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }###### ${selection.replace(/\n/g, '\n###### ')}`
+ else if (
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 4
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^#*)# /gm, '$1 ')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 5
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^#*)# /gm, '$1 ')
+ } else selection = `\n###### ${selection.replace(/\n/g, '\n###### ')}`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const highlighter = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = new RegExp(`\`(${selection.replace(/`/g, '')})\``)
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (
+ value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/`.+`/g)
+ ? selection.replace(/`/g, '')
+ : `\`${selection.replace(/\n/g, '`\n`')}\``
+ }
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/`.+`/g)
+ ? selection.replace(/`/g, '')
+ : `\`${selection.replace(/\n/g, '`\n`')}\``
+ }
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const image = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /!\[(.*)\]\((\S*)( ".*")?\)/
+ if (selection.match(selRegex))
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection.replace(selRegex, '$1 $2') +
+ value.substring(selectionEnd, length)
+ else {
+ let link = ''
+ if (!selection.match(/\[(.*)\]\((\S*)( ".*")?\)/)) {
+ link = selection.match(/https?:\/\/\S*/) || ''
+ selection = selection
+ .replace(link[0], '')
+ .replace(/ +/g, ' ')
+ .trim()
+ } else selection = ''
+ const divModal = $(
+ ``
+ )
+ $('.navbar-header>div:last-child').append(divModal)
+ setTimeout(() => {
+ document.body.classList.add('v--modal-block-scroll')
+ $('#markdown').style.opacity = 1
+ }, 1)
+ $('#markdown-text').value = selection ? selection : ''
+ $('#markdown-link').value = link ? link[0] : ''
+ $('.close').addEventListener('click', () => {
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ $('#markdown-done').addEventListener('click', () => {
+ const tag = `.value
+ }${
+ $('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''
+ })`
+ selection = value.substring(selectionStart, selectionEnd)
+ textarea.value =
+ value.substring(0, selectionStart) +
+ (selection.match(/\[(.*)\]\((\S*)( ".*")?\)/)
+ ? selection.replace(/\[.*\]/, `[${tag}]`)
+ : tag) +
+ value.substring(selectionEnd, length)
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ }
+ }
+
+ const italic = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = new RegExp(
+ `\\*(?=\\S*${selection.replace(
+ /(.*)\*(.*)/g,
+ ''
+ )})((?:\\*\\*|\\\\[\\s\\S]|\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|[^\\s\\*\\\\])+?)\\*(?!\\*)`
+ )
+ const italicRegex = /\*(?=\S)((?:\*\*|\\[\s\S]|\s+(?:\\[\s\S]|[^\s\*\\]|\*\*)|[^\s\*\\])+?)\*(?!\*)/g
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (
+ value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(italicRegex)
+ ? selection.replace(italicRegex, '$1')
+ : `*${selection.replace(/\n/g, '*\n*')}*`
+ }
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else
+ selection = selection.match(italicRegex)
+ ? selection.replace(italicRegex, '$1')
+ : `*${selection.replace(/\n/g, '*\n*')}*`
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const link = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /^(?!!)\[(.*)\]\((\S*)( ".*")?\)/
+ if (selection.match(selRegex))
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection.replace(selRegex, '$1 $2') +
+ value.substring(selectionEnd, length)
+ else {
+ let imageLink = ''
+ if (!selection.match(/!\[(.*)\]\((\S*)( ".*")?\)/)) {
+ imageLink = selection.match(/https?:\/\/\S*/) || ''
+ selection = selection
+ .replace(imageLink[0], '')
+ .replace(/ +/g, ' ')
+ .trim()
+ }
+ const divModal = $(
+ ``
+ )
+ $('.navbar-header>div:last-child').append(divModal)
+ setTimeout(() => {
+ document.body.classList.add('v--modal-block-scroll')
+ $('#markdown').style.opacity = 1
+ }, 1)
+ $('#markdown-text').value = selection ? selection : ''
+ $('#markdown-link').value = imageLink ? imageLink[0] : ''
+ $('.close').addEventListener('click', () => {
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ $('#markdown-done').addEventListener('click', () => {
+ selection = `[${$('#markdown-text').value}](${
+ $('#markdown-link').value
+ }${
+ $('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''
+ })`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ }
+ }
+
+ const listOl = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /^( {3})*\d+\. (.*)/gm
+ if (selection.match(selRegex)) {
+ selection = selection.match(/^ {3}/)
+ ? selection.replace(/^ {3}/gm, '')
+ : selection.replace(/^\d+\. /gm, '')
+ } else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/)
+ ) {
+ let countOl = 0
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }0. ${selection.replace(/\n/g, () => {
+ countOl++
+ return `\n${countOl}. `
+ })}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/)
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ } else if (
+ value
+ .substring(selectionStart - 4, selectionEnd)
+ .match(/( {3})*\d+\. (.*)/)
+ ) {
+ selectionStart -= 4
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*(\d+\.) /g, ' $1$2 ')
+ } else if (
+ value
+ .substring(selectionStart - 5, selectionEnd)
+ .match(/( {3})*\d+\. (.*)/)
+ ) {
+ selectionStart -= 5
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*(\d+\.) /g, ' $1$2 ')
+ } else {
+ let countOl = 0
+ selection = `\n0. ${selection.replace(/\n/g, () => {
+ countOl++
+ return `\n${countOl}. `
+ })}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) ||
+ selectionEnd === length
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const listUl = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /^( {3})*- (.*)/
+ if (selection.match(selRegex))
+ selection = selection.match(/^ {3}/)
+ ? selection.replace(/^ {3}/gm, '')
+ : selection.replace(/^- /gm, '')
+ else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/)
+ )
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }- ${selection.replace(/\n/g, '\n- ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/)
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ else if (
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*- /g, '$1 - ')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*- /g, '$1 - ')
+ } else
+ selection = `\n- ${selection.replace(/\n/g, '\n- ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) ||
+ selectionEnd === length
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const quoteRight = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /^>+\s.*/gm
+ if (selection.match(selRegex))
+ selection = selection.match(/^> /gm)
+ ? selection.replace(/^> /gm, '')
+ : selection.replace(/(^>*)> /gm, '$1 ')
+ else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/)
+ )
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }> ${selection.replace(/\n/g, '\n> ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/)
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ else if (
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^>*)\s/gm, '$1> ')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^>*)\s/gm, '$1> ')
+ } else
+ selection = `\n> ${selection.replace(/\n/g, '\n> ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) ||
+ selectionEnd === length
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const strikethrough = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ ) => {
+ const selRegex = /~~((.*\W?)*)~~/
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (selection.match(selRegex)) selection.replace(selRegex, '$1')
+ else if (
+ value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selectionEnd += 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/~~.+~~/g)
+ ? selection.replace(/~~/g, '')
+ : `~~${selection}~~`
+ }
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/~~.+~~/g)
+ ? selection.replace(/~~/g, '')
+ : `~~${selection}~~`
+ }
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ }
+
+ const markdown = {
+ bold: {
+ title: 'bold text',
+ replaceFunc: bold
+ },
+ italic: {
+ title: 'italic text',
+ replaceFunc: italic
+ },
+ heading: {
+ title: 'enlarges/reduces the text',
+ replaceFunc: heading
+ },
+ strikethrough: {
+ title: 'strikethrough text',
+ replaceFunc: strikethrough
+ },
+ highlighter: {
+ title: 'highlighted text',
+ replaceFunc: highlighter
+ },
+ 'list-ul': {
+ title: 'unordered list',
+ replaceFunc: listUl
+ },
+ 'list-ol': {
+ title: 'ordered list',
+ replaceFunc: listOl
+ },
+ 'quote-right': {
+ title: 'quote',
+ replaceFunc: quoteRight
+ },
+ code: {
+ title: 'block of code',
+ replaceFunc: code
+ },
+ link: {
+ title: 'insert link',
+ replaceFunc: link
+ },
+ image: {
+ title: 'insert image',
+ replaceFunc: image
+ }
+ }
+
+ const getSelectedText = event => {
+ const textarea = $('#input-comment')
+ const { value, selectionStart, selectionEnd } = textarea
+ const { length } = value
+ const selection = value.substring(selectionStart, selectionEnd)
+ markdown[`${event.currentTarget.id}`].replaceFunc(
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ )
+ }
+
+ const addMarkdownTools = () => {
+ const textarea = $('#input-comment')
+ if (!textarea) return
+ const markdownDiv = $('')
+ Object.keys(markdown).forEach(toolName =>
+ markdownDiv.appendChild(
+ $(
+ ``
+ )
+ )
+ )
+ textarea.insertAdjacentHTML('beforebegin', markdownDiv.outerHTML)
+ ;[...$('#markdown-editor').children].forEach(children =>
+ children.addEventListener('click', getSelectedText)
+ )
+ }
+
+ const getNotifications = () => {
+ if (!window.notificationsOpened) {
+ $('#user-notify-list').innerHTML =
+ '
'
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', '/notification/view/')
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ $('#user-notify-list').innerHTML = xhr.responseText
+ $('#user-notify-count').textContent = '0'
+ window.notificationsOpened = true
+ } else {
+ $('#user-notify-list').innerHTML = xhr.responseText
+ window.notificationsOpened = true
+ }
+ }
+ }
+ }
+
+ const pageEnhancements = () => {
+ loadScriptSettings()
+ if (typeof DrawceptionPlay === 'undefined') return
+ if (document.getElementById('newcanvasyo')) return
+ try {
+ const tmpuserlink = $('.player-dropdown a[href^="/player/"]')
+ const username = tmpuserlink.querySelector('strong').textContent
+ const userid = tmpuserlink.href.match(/\/player\/(\d+)\//)[1]
+ localStorage.setItem('gpe_lastSeenName', username)
+ localStorage.setItem('gpe_lastSeenId', userid)
+ } catch (e) {}
+ const currentPage = location.href.match(/drawception\.com\/([^/]+)/)
+ if (currentPage) {
+ const page = currentPage[1]
+ const functionName = `better${page.replace(
+ page[0],
+ page[0].toUpperCase()
+ )}`
+ if (betterPages[functionName]) betterPages[functionName]()
+ }
+ addStyle(
+ '.panel-user {width: auto} .panel-details img.loading {display: none}' +
+ '.gpe-wide, .gpe-wide-block {display: none}' +
+ '.gpe-btn {padding: 5px 8px; height: 28px}' +
+ '.gpe-spacer {margin-right: 7px; float:left}' +
+ '@media (min-width:992px) {.navbar-toggle,.btn-menu-player {display: none} .gpe-wide {display: inline} .gpe-wide-block {display: block}}' +
+ '@media (min-width:1200px) {.gpe-btn {padding: 5px 16px;} .gpe-spacer {margin-right: 20px;} .panel-number {left: -30px}}' +
+ '#anbtver {font-size: 10px; position:absolute; opacity:0.3; right:10px; top: 0;}' +
+ '.anbt_paneldel {position:absolute; padding:1px 6px; color:#FFF; background:#d9534f; text-decoration: none !important; right: 18px; border-radius: 5px}' +
+ '.anbt_paneldel:hover {background:#d2322d}' +
+ '.anbt_favpanel {top: 20px; font-weight: normal; padding: 0 2px}' +
+ '.anbt_favpanel:hover {color: #d9534f; cursor:pointer}' +
+ '.anbt_favedpanel {color: #d9534f; border-color: #d9534f}' +
+ '.anbt_replaypanel {top: 55px; font-weight: normal; padding: 0 8px}' +
+ '.anbt_replaypanel:hover {color: #8af; text-decoration: none}' +
+ ".anbt_owncaption:before {content: ''; display: inline-block; background: #5C5; border: 1px solid #080; width: 10px; height: 10px; border-radius: 10px; margin-right: 10px;}" +
+ '.gamepanel, .thumbpanel, .comment-body {word-wrap: break-word}' +
+ '.comment-body img {max-width: 100%}' +
+ '.forum-thread.anbt_hidden {display: none}' +
+ '.anbt_showt .forum-thread.anbt_hidden {display: block; opacity: 0.6}' +
+ ".anbt_unhidet:after {content: ' threads hidden. Show'}" +
+ ".anbt_showt .anbt_unhidet:after {content: ' threads hidden. Hide'}" +
+ ".anbt_hft:after {content: '[hide]'}" +
+ '.anbt_hft, .anbt_unhidet {padding-left: 0.4em; cursor:pointer}' +
+ ".forum-thread.anbt_hidden .anbt_hft:after {content: '[show]'}" +
+ '.anbt_threadtitle {margin: 0 0 10px}' +
+ '.avatar {box-sizing: content-box}' +
+ '.pagination {margin: 0px}' +
+ '#nav-drag {position: fixed; width: 100%; z-index: 2000}' +
+ '#header-bar-container {position: relative; width: 100%; top: 6rem}' +
+ '.wrapper {position: relative; top: 6rem}' +
+ 'footer {position: relative; top: 6rem}' +
+ '.option span:first-child {display: flex; flex-direction: row; justify-content: space-between}' +
+ '.grid-settings div[class^="grid-"] label {display: inline-flex}' +
+ 'input[type="checkbox"], input[type="radio"] {margin:4px 4px 0 0}' +
+ '@-moz-document url-prefix() {input[type="checkbox"], input[type="radio"] {margin:0 4px 0 0}}' +
+ '.tooltip {z-index: 3000;}'
+ )
+ if (options.maxCommentHeight) {
+ const maxHeight = options.maxCommentHeight
+ addStyle(
+ `.comment-holder[id]:not(:target) .comment-body {overflow-y: hidden; max-height: ${maxHeight}px; position:relative}.comment-holder[id]:not(:target) .comment-body:before{content: 'Click to read more'; position:absolute; width:100%; height:50px; left:0; top:${maxHeight -
+ 50}px;text-align: center; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; padding-top: 20px; background:linear-gradient(transparent, rgba(0,0,0,0.4))}`
+ )
+ $('.comment-body', true).forEach(comment =>
+ comment.addEventListener('click', () => {
+ if (
+ comment.clientHeight > maxHeight - 50 &&
+ location.hash.indexOf(comment) === -1
+ )
+ location.hash = `#${comment.parentNode.parentNode.id}`
+ })
+ )
+ }
+ if (options.useOldFontSize) document.body.style.fontSize = '15px'
+ if (options.useOldFont) {
+ const nunito = $("link[href*='Nunito']")
+ nunito.parentNode.removeChild(nunito)
+ addStyle(
+ "@import url('https://fonts.googleapis.com/css?family=Nunito&display=swap')"
+ )
+ }
+ if (options.anbtDarkMode) {
+ if (document.body.classList.contains('theme-night')) {
+ document.body.classList.remove('theme-night')
+ setCookie('theme-night')
+ }
+ }
+ if (options.markdownTools) addMarkdownTools()
+ if (options.newCanvas) {
+ const inSandbox = location.href.match(/drawception\.com\/sandbox\/#?(.*)/)
+ const inPlay = location.href.match(
+ /drawception\.com\/(:?contests\/)?play\/(.*)/
+ )
+ const hasCanvas = document.getElementById('canvas-holder')
+ const hasCanvasOrGameForm = document.querySelector('.playtimer')
+ const captionContest =
+ location.href.match(/contests\/play\//) && !hasCanvas
+ if (!captionContest && (inSandbox || (inPlay && hasCanvasOrGameForm))) {
+ setTimeout(() => setupNewCanvas(inSandbox, location.href), 1)
+ return
+ }
+ $('a[href^="/sandbox/"]', true).forEach(sandboxButton =>
+ sandboxButton.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(true, event.currentTarget.href)
+ })
+ )
+ $('a[href="/play/"]', true).forEach(playButton =>
+ playButton.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(false, event.currentTarget.href)
+ })
+ )
+ }
+ if ($('.navbar-toggle')) {
+ const navbarToggle = $('.navbar-toggle').parentNode
+ const navbarButtonsList = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ''
+ ]
+ navbarButtonsList.forEach(button => navbarToggle.appendChild($(button)))
+ $('#main-menu').insertAdjacentHTML(
+ 'afterbegin',
+ ' Toggle light'
+ )
+ }
+ const menuPlayer = $('.btn-menu-player')
+ if (menuPlayer) {
+ const userlink = $('.player-dropdown a[href^="/player/"]').href
+ const useravatar = $('.btn-menu-player').innerHTML
+ const element = $(
+ `${useravatar}`
+ )
+ menuPlayer.parentNode.appendChild(element)
+ }
+ const num =
+ $('#user-notify-count') && $('#user-notify-count').textContent.trim()
+ addStyle(
+ `#user-notify-list .list-group .list-group-item .fas {color: #888}#user-notify-list .list-group .list-group-item:nth-child(-n+${num}) .fas {color: #2F5}a.wrong-order {color: #F99} div.comment-holder:target {background-color: #DFD}.comment-new a.text-muted:last-child:after {content: 'New'; color: #2F5; font-weight: bold; background-color: #183; border-radius: 9px; display: inline-block; padding: 0px 6px; margin-left: 10px;}`
+ )
+ window.getNotifications = getNotifications
+ let versionDisplay = `ANBT v${versions.scriptVersion}`
+ try {
+ const appver = $('script[src^="/build/app"]').src.match(/(\w+)\.js$/)[1]
+ const runtimever = $('script[src^="/build/runtime"]').src.match(
+ /(\w+)\.js$/
+ )[1]
+ versionDisplay += ` | app ${appver}`
+ if (appver !== versions.siteVersion) versionDisplay += '*'
+ versionDisplay += ` | runtime ${runtimever}`
+ if (runtimever !== versions.runtimeVersion) versionDisplay += '*!!!'
+ } catch (e) {}
+ const wrapperSection = $('.wrapper')
+ if (wrapperSection)
+ wrapperSection.appendChild($(`${versionDisplay}
`))
+ const linkList = [
+ 'ANBT script',
+ 'Wiki',
+ 'Chat (Discord)'
+ ]
+ $('.footer-main .list-unstyled').forEach((list, index) =>
+ list.appendChild($(linkList[index]))
+ )
+ }
+
+ const wrapper = () => {
+ window.options = options
+ const mark = document.createElement('b')
+ mark.id = '_anbt_'
+ mark.style.display = 'none'
+ document.body.appendChild(mark)
+ if (!window.DrawceptionPlay) {
+ const loader = setInterval(() => {
+ if (!window.DrawceptionPlay) return
+ pageEnhancements()
+ clearInterval(loader)
+ }, 100)
+ } else pageEnhancements()
+ }
+
+ addDarkCSS()
+ setDarkMode()
+ if (document && document.body) {
+ if (!document.getElementById('_anbt_')) wrapper()
+ if (window.opera && !getLocalStorageItem('gpe_operaWarning', 0)) {
+ const anbtTitle = document.createElement('h2')
+ anbtTitle.innerHTML =
+ 'ANBT speaking:
Rename your script file so it doesn\'t contain ".user." part for smoother loading!
This warning is only shown once.'
+ const mainSection = document.getElementById('main')
+ mainSection.insertBefore(anbtTitle, mainSection.firstChild)
+ localStorage.setItem('gpe_operaWarning', 1)
+ }
+ }
+ document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+ if (!document.getElementById('_anbt_')) wrapper()
+ },
+ false
+ )
+})()
diff --git a/build/index.html b/build/index.html
new file mode 100644
index 0000000..9fe71c5
--- /dev/null
+++ b/build/index.html
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+ New Canvas - Drawception
+
+
+
+
+
+ Fetching your Drawception game...
+
+
+
+
+
Time's up!
Submit now or miss the game!
+
+
+
![]()
+
+
+
46
+
+
+
+
+ Sandbox
+ Public safe for work game
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Guidelines
+
+ - Simple drawings are welcome! Just give it your best shot
+ - If the phrase is too difficult, use the skip button
+ - Don't draw text - always draw a picture
+ - Explicit (NSFW) drawings will get you banned
+
+
+
Tips
+
+ - No explicit language allowed
+ - Keep descriptions short and simple!
+ - Avoid unnecessary detail, just describe the main subject
+ - No metacommentary (e.g., don't comment on drawing quality)
+ - No idea? Use the skip button
+
+
+
Sandbox
+
+ - Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
+ - Saved drawings will contain your drawing process in PNG format.
+ - After loading a drawing you can continue to draw or watch its playback.
+ - Note that editing the PNG with playback with other programs will likely destroy the playback data.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/newcanvas/index.html b/build/newcanvas/index.html
new file mode 100644
index 0000000..3140894
--- /dev/null
+++ b/build/newcanvas/index.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+ New Canvas - Drawception
+
+
+
+
+
+ Fetching your Drawception game...
+
+
+
+
+
Time's up!
Submit now or miss the game!
+
+
+
![]()
+
+
+
46
+
+
+
+
+ Sandbox
+ Public safe for work game
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Guidelines
+
+ - Simple drawings are welcome! Just give it your best shot
+ - If the phrase is too difficult, use the skip button
+ - Don't draw text - always draw a picture
+ - Explicit (NSFW) drawings will get you banned
+
+
+
Tips
+
+ - No explicit language allowed
+ - Keep descriptions short and simple!
+ - Avoid unnecessary detail, just describe the main subject
+ - No metacommentary (e.g., don't comment on drawing quality)
+ - No idea? Use the skip button
+
+
+
Sandbox
+
+ - Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
+ - Saved drawings will contain your drawing process in PNG format.
+ - After loading a drawing you can continue to draw or watch its playback.
+ - Note that editing the PNG with playback with other programs will likely destroy the playback data.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build/newcanvas/pako.js b/build/newcanvas/pako.js
new file mode 100644
index 0000000..95f74ab
--- /dev/null
+++ b/build/newcanvas/pako.js
@@ -0,0 +1 @@
+!function(){"use strict";var t,e=(function(t,e){var a="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;function i(t,e){return Object.prototype.hasOwnProperty.call(t,e)}e.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(var n in a)i(a,n)&&(t[n]=a[n])}}return t},e.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,a,i,n){if(e.subarray&&t.subarray)t.set(e.subarray(a,a+i),n);else for(var r=0;r=0;)t[e]=0}var o=0,h=1,l=2,d=29,_=256,f=_+1+d,u=30,c=19,w=2*f+1,g=15,b=16,m=7,p=256,v=16,k=17,y=18,x=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],z=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],B=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],S=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],E=new Array(2*(f+2));s(E);var A=new Array(2*u);s(A);var Z=new Array(512);s(Z);var R=new Array(256);s(R);var C=new Array(d);s(C);var N,I,O,D=new Array(u);function T(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length}function U(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}function F(t){return t<256?Z[t]:Z[256+(t>>>7)]}function L(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function H(t,e,a){t.bi_valid>b-a?(t.bi_buf|=e<>b-t.bi_valid,t.bi_valid+=a-b):(t.bi_buf|=e<>>=1,a<<=1}while(--e>0);return a>>>1}function M(t,e,a){var i,n,r=new Array(g+1),s=0;for(i=1;i<=g;i++)r[i]=s=s+a[i-1]<<1;for(n=0;n<=e;n++){var o=t[2*n+1];0!==o&&(t[2*n]=K(r[o]++,o))}}function P(t){var e;for(e=0;e8?L(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function G(t,e,a,i){var n=2*e,r=2*a;return t[n]>1;a>=1;a--)X(t,r,a);n=h;do{a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],X(t,r,1),i=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=i,r[2*n]=r[2*a]+r[2*i],t.depth[n]=(t.depth[a]>=t.depth[i]?t.depth[a]:t.depth[i])+1,r[2*a+1]=r[2*i+1]=n,t.heap[1]=n++,X(t,r,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],function(t,e){var a,i,n,r,s,o,h=e.dyn_tree,l=e.max_code,d=e.stat_desc.static_tree,_=e.stat_desc.has_stree,f=e.stat_desc.extra_bits,u=e.stat_desc.extra_base,c=e.stat_desc.max_length,b=0;for(r=0;r<=g;r++)t.bl_count[r]=0;for(h[2*t.heap[t.heap_max]+1]=0,a=t.heap_max+1;ac&&(r=c,b++),h[2*i+1]=r,i>l||(t.bl_count[r]++,s=0,i>=u&&(s=f[i-u]),o=h[2*i],t.opt_len+=o*(r+s),_&&(t.static_len+=o*(d[2*i+1]+s)));if(0!==b){do{for(r=c-1;0===t.bl_count[r];)r--;t.bl_count[r]--,t.bl_count[r+1]+=2,t.bl_count[c]--,b-=2}while(b>0);for(r=c;0!==r;r--)for(i=t.bl_count[r];0!==i;)(n=t.heap[--a])>l||(h[2*n+1]!==r&&(t.opt_len+=(r-h[2*n+1])*h[2*n],h[2*n+1]=r),i--)}}(t,e),M(r,l,t.bl_count)}function J(t,e,a){var i,n,r=-1,s=e[1],o=0,h=7,l=4;for(0===s&&(h=138,l=3),e[2*(a+1)+1]=65535,i=0;i<=a;i++)n=s,s=e[2*(i+1)+1],++o>=7;i0?(t.strm.data_type===r&&(t.strm.data_type=function(t){var e,a=4093624447;for(e=0;e<=31;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return i;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return n;for(e=32;e<_;e++)if(0!==t.dyn_ltree[2*e])return n;return i}(t)),q(t,t.l_desc),q(t,t.d_desc),u=function(t){var e;for(J(t,t.dyn_ltree,t.l_desc.max_code),J(t,t.dyn_dtree,t.d_desc.max_code),q(t,t.bl_desc),e=c-1;e>=3&&0===t.bl_tree[2*S[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),d=t.opt_len+3+7>>>3,(f=t.static_len+3+7>>>3)<=d&&(d=f)):d=f=s+5,s+4<=d&&-1!==e?$(t,e,s,o):t.strategy===a||f===d?(H(t,(h<<1)+(o?1:0),3),W(t,E,A)):(H(t,(l<<1)+(o?1:0),3),function(t,e,a,i){var n;for(H(t,e-257,5),H(t,a-1,5),H(t,i-4,4),n=0;n>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&a,t.last_lit++,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(R[a]+_+1)]++,t.dyn_dtree[2*F(e)]++),t.last_lit===t.lit_bufsize-1},_tr_align:function(t){H(t,h<<1,3),j(t,p,E),function(t){16===t.bi_valid?(L(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}(t)}};var et=function(t,e,a,i){for(var n=65535&t|0,r=t>>>16&65535|0,s=0;0!==a;){a-=s=a>2e3?2e3:a;do{r=r+(n=n+e[i++]|0)|0}while(--s);n%=65521,r%=65521}return n|r<<16|0};var at=function(){for(var t,e=[],a=0;a<256;a++){t=a;for(var i=0;i<8;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e}();var it,nt=function(t,e,a,i){var n=at,r=i+a;t^=-1;for(var s=i;s>>8^n[255&(t^e[s])];return-1^t},rt={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"},st=0,ot=1,ht=3,lt=4,dt=5,_t=0,ft=1,ut=-2,ct=-3,wt=-5,gt=-1,bt=1,mt=2,pt=3,vt=4,kt=0,yt=2,xt=8,zt=9,Bt=15,St=8,Et=286,At=30,Zt=19,Rt=2*Et+1,Ct=15,Nt=3,It=258,Ot=It+Nt+1,Dt=32,Tt=42,Ut=69,Ft=73,Lt=91,Ht=103,jt=113,Kt=666,Mt=1,Pt=2,Yt=3,Gt=4,Xt=3;function Wt(t,e){return t.msg=rt[e],e}function qt(t){return(t<<1)-(t>4?9:0)}function Jt(t){for(var e=t.length;--e>=0;)t[e]=0}function Qt(t){var a=t.state,i=a.pending;i>t.avail_out&&(i=t.avail_out),0!==i&&(e.arraySet(t.output,a.pending_buf,a.pending_out,i,t.next_out),t.next_out+=i,a.pending_out+=i,t.total_out+=i,t.avail_out-=i,a.pending-=i,0===a.pending&&(a.pending_out=0))}function Vt(t,e){tt._tr_flush_block(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,Qt(t.strm)}function $t(t,e){t.pending_buf[t.pending++]=e}function te(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function ee(t,e){var a,i,n=t.max_chain_length,r=t.strstart,s=t.prev_length,o=t.nice_match,h=t.strstart>t.w_size-Ot?t.strstart-(t.w_size-Ot):0,l=t.window,d=t.w_mask,_=t.prev,f=t.strstart+It,u=l[r+s-1],c=l[r+s];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do{if(l[(a=e)+s]===c&&l[a+s-1]===u&&l[a]===l[r]&&l[++a]===l[r+1]){r+=2,a++;do{}while(l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&l[++r]===l[++a]&&rs){if(t.match_start=e,s=i,i>=o)break;u=l[r+s-1],c=l[r+s]}}}while((e=_[e&d])>h&&0!=--n);return s<=t.lookahead?s:t.lookahead}function ae(t){var a,i,n,r,s,o,h,l,d,_,f=t.w_size;do{if(r=t.window_size-t.lookahead-t.strstart,t.strstart>=f+(f-Ot)){e.arraySet(t.window,t.window,f,f,0),t.match_start-=f,t.strstart-=f,t.block_start-=f,a=i=t.hash_size;do{n=t.head[--a],t.head[a]=n>=f?n-f:0}while(--i);a=i=f;do{n=t.prev[--a],t.prev[a]=n>=f?n-f:0}while(--i);r+=f}if(0===t.strm.avail_in)break;if(o=t.strm,h=t.window,l=t.strstart+t.lookahead,d=r,_=void 0,(_=o.avail_in)>d&&(_=d),i=0===_?0:(o.avail_in-=_,e.arraySet(h,o.input,o.next_in,_,l),1===o.state.wrap?o.adler=et(o.adler,h,_,l):2===o.state.wrap&&(o.adler=nt(o.adler,h,_,l)),o.next_in+=_,o.total_in+=_,_),t.lookahead+=i,t.lookahead+t.insert>=Nt)for(s=t.strstart-t.insert,t.ins_h=t.window[s],t.ins_h=(t.ins_h<=Nt&&(t.ins_h=(t.ins_h<=Nt)if(i=tt._tr_tally(t,t.strstart-t.match_start,t.match_length-Nt),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=Nt){t.match_length--;do{t.strstart++,t.ins_h=(t.ins_h<=Nt&&(t.ins_h=(t.ins_h<4096)&&(t.match_length=Nt-1)),t.prev_length>=Nt&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-Nt,i=tt._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-Nt),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=n&&(t.ins_h=(t.ins_h<15&&(o=2,n-=16),r<1||r>zt||i!==xt||n<8||n>15||a<0||a>9||s<0||s>vt)return Wt(t,ut);8===n&&(n=9);var h=new se;return t.state=h,h.strm=t,h.wrap=o,h.gzhead=null,h.w_bits=n,h.w_size=1<t.pending_buf_size-5&&(a=t.pending_buf_size-5);;){if(t.lookahead<=1){if(ae(t),0===t.lookahead&&e===st)return Mt;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+a;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,Vt(t,!1),0===t.strm.avail_out))return Mt;if(t.strstart-t.block_start>=t.w_size-Ot&&(Vt(t,!1),0===t.strm.avail_out))return Mt}return t.insert=0,e===lt?(Vt(t,!0),0===t.strm.avail_out?Yt:Gt):(t.strstart>t.block_start&&(Vt(t,!1),t.strm.avail_out),Mt)}),new re(4,4,8,4,ie),new re(4,5,16,8,ie),new re(4,6,32,32,ie),new re(4,4,16,16,ne),new re(8,16,32,32,ne),new re(8,16,128,128,ne),new re(8,32,128,256,ne),new re(32,128,258,1024,ne),new re(32,258,258,4096,ne)];var de={deflateInit:function(t,e){return le(t,e,xt,Bt,St,kt)},deflateInit2:le,deflateReset:he,deflateResetKeep:oe,deflateSetHeader:function(t,e){return t&&t.state?2!==t.state.wrap?ut:(t.state.gzhead=e,_t):ut},deflate:function(t,e){var a,i,n,r;if(!t||!t.state||e>dt||e<0)return t?Wt(t,ut):ut;if(i=t.state,!t.output||!t.input&&0!==t.avail_in||i.status===Kt&&e!==lt)return Wt(t,0===t.avail_out?wt:ut);if(i.strm=t,a=i.last_flush,i.last_flush=e,i.status===Tt)if(2===i.wrap)t.adler=0,$t(i,31),$t(i,139),$t(i,8),i.gzhead?($t(i,(i.gzhead.text?1:0)+(i.gzhead.hcrc?2:0)+(i.gzhead.extra?4:0)+(i.gzhead.name?8:0)+(i.gzhead.comment?16:0)),$t(i,255&i.gzhead.time),$t(i,i.gzhead.time>>8&255),$t(i,i.gzhead.time>>16&255),$t(i,i.gzhead.time>>24&255),$t(i,9===i.level?2:i.strategy>=mt||i.level<2?4:0),$t(i,255&i.gzhead.os),i.gzhead.extra&&i.gzhead.extra.length&&($t(i,255&i.gzhead.extra.length),$t(i,i.gzhead.extra.length>>8&255)),i.gzhead.hcrc&&(t.adler=nt(t.adler,i.pending_buf,i.pending,0)),i.gzindex=0,i.status=Ut):($t(i,0),$t(i,0),$t(i,0),$t(i,0),$t(i,0),$t(i,9===i.level?2:i.strategy>=mt||i.level<2?4:0),$t(i,Xt),i.status=jt);else{var s=xt+(i.w_bits-8<<4)<<8;s|=(i.strategy>=mt||i.level<2?0:i.level<6?1:6===i.level?2:3)<<6,0!==i.strstart&&(s|=Dt),s+=31-s%31,i.status=jt,te(i,s),0!==i.strstart&&(te(i,t.adler>>>16),te(i,65535&t.adler)),t.adler=1}if(i.status===Ut)if(i.gzhead.extra){for(n=i.pending;i.gzindex<(65535&i.gzhead.extra.length)&&(i.pending!==i.pending_buf_size||(i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),Qt(t),n=i.pending,i.pending!==i.pending_buf_size));)$t(i,255&i.gzhead.extra[i.gzindex]),i.gzindex++;i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),i.gzindex===i.gzhead.extra.length&&(i.gzindex=0,i.status=Ft)}else i.status=Ft;if(i.status===Ft)if(i.gzhead.name){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),Qt(t),n=i.pending,i.pending===i.pending_buf_size)){r=1;break}r=i.gzindexn&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),0===r&&(i.gzindex=0,i.status=Lt)}else i.status=Lt;if(i.status===Lt)if(i.gzhead.comment){n=i.pending;do{if(i.pending===i.pending_buf_size&&(i.gzhead.hcrc&&i.pending>n&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),Qt(t),n=i.pending,i.pending===i.pending_buf_size)){r=1;break}r=i.gzindexn&&(t.adler=nt(t.adler,i.pending_buf,i.pending-n,n)),0===r&&(i.status=Ht)}else i.status=Ht;if(i.status===Ht&&(i.gzhead.hcrc?(i.pending+2>i.pending_buf_size&&Qt(t),i.pending+2<=i.pending_buf_size&&($t(i,255&t.adler),$t(i,t.adler>>8&255),t.adler=0,i.status=jt)):i.status=jt),0!==i.pending){if(Qt(t),0===t.avail_out)return i.last_flush=-1,_t}else if(0===t.avail_in&&qt(e)<=qt(a)&&e!==lt)return Wt(t,wt);if(i.status===Kt&&0!==t.avail_in)return Wt(t,wt);if(0!==t.avail_in||0!==i.lookahead||e!==st&&i.status!==Kt){var o=i.strategy===mt?function(t,e){for(var a;;){if(0===t.lookahead&&(ae(t),0===t.lookahead)){if(e===st)return Mt;break}if(t.match_length=0,a=tt._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(Vt(t,!1),0===t.strm.avail_out))return Mt}return t.insert=0,e===lt?(Vt(t,!0),0===t.strm.avail_out?Yt:Gt):t.last_lit&&(Vt(t,!1),0===t.strm.avail_out)?Mt:Pt}(i,e):i.strategy===pt?function(t,e){for(var a,i,n,r,s=t.window;;){if(t.lookahead<=It){if(ae(t),t.lookahead<=It&&e===st)return Mt;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=Nt&&t.strstart>0&&(i=s[n=t.strstart-1])===s[++n]&&i===s[++n]&&i===s[++n]){r=t.strstart+It;do{}while(i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&nt.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=Nt?(a=tt._tr_tally(t,1,t.match_length-Nt),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=tt._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(Vt(t,!1),0===t.strm.avail_out))return Mt}return t.insert=0,e===lt?(Vt(t,!0),0===t.strm.avail_out?Yt:Gt):t.last_lit&&(Vt(t,!1),0===t.strm.avail_out)?Mt:Pt}(i,e):it[i.level].func(i,e);if(o!==Yt&&o!==Gt||(i.status=Kt),o===Mt||o===Yt)return 0===t.avail_out&&(i.last_flush=-1),_t;if(o===Pt&&(e===ot?tt._tr_align(i):e!==dt&&(tt._tr_stored_block(i,0,0,!1),e===ht&&(Jt(i.head),0===i.lookahead&&(i.strstart=0,i.block_start=0,i.insert=0))),Qt(t),0===t.avail_out))return i.last_flush=-1,_t}return e!==lt?_t:i.wrap<=0?ft:(2===i.wrap?($t(i,255&t.adler),$t(i,t.adler>>8&255),$t(i,t.adler>>16&255),$t(i,t.adler>>24&255),$t(i,255&t.total_in),$t(i,t.total_in>>8&255),$t(i,t.total_in>>16&255),$t(i,t.total_in>>24&255)):(te(i,t.adler>>>16),te(i,65535&t.adler)),Qt(t),i.wrap>0&&(i.wrap=-i.wrap),0!==i.pending?_t:ft)},deflateEnd:function(t){var e;return t&&t.state?(e=t.state.status)!==Tt&&e!==Ut&&e!==Ft&&e!==Lt&&e!==Ht&&e!==jt&&e!==Kt?Wt(t,ut):(t.state=null,e===jt?Wt(t,ct):_t):ut},deflateSetDictionary:function(t,a){var i,n,r,s,o,h,l,d,_=a.length;if(!t||!t.state)return ut;if(2===(s=(i=t.state).wrap)||1===s&&i.status!==Tt||i.lookahead)return ut;for(1===s&&(t.adler=et(t.adler,a,_,0)),i.wrap=0,_>=i.w_size&&(0===s&&(Jt(i.head),i.strstart=0,i.block_start=0,i.insert=0),d=new e.Buf8(i.w_size),e.arraySet(d,a,_-i.w_size,i.w_size,0),a=d,_=i.w_size),o=t.avail_in,h=t.next_in,l=t.input,t.avail_in=_,t.next_in=0,t.input=a,ae(i);i.lookahead>=Nt;){n=i.strstart,r=i.lookahead-(Nt-1);do{i.ins_h=(i.ins_h<=252?6:ce>=248?5:ce>=240?4:ce>=224?3:ce>=192?2:1;ue[254]=ue[254]=1;function we(t,a){if(a<65534&&(t.subarray&&fe||!t.subarray&&_e))return String.fromCharCode.apply(null,e.shrinkBuf(t,a));for(var i="",n=0;n>>6,a[s++]=128|63&i):i<65536?(a[s++]=224|i>>>12,a[s++]=128|i>>>6&63,a[s++]=128|63&i):(a[s++]=240|i>>>18,a[s++]=128|i>>>12&63,a[s++]=128|i>>>6&63,a[s++]=128|63&i);return a},buf2binstring:function(t){return we(t,t.length)},binstring2buf:function(t){for(var a=new e.Buf8(t.length),i=0,n=a.length;i4)o[i++]=65533,a+=r-1;else{for(n&=2===r?31:3===r?15:7;r>1&&a1?o[i++]=65533:n<65536?o[i++]=n:(n-=65536,o[i++]=55296|n>>10&1023,o[i++]=56320|1023&n)}return we(o,i)},utf8border:function(t,e){var a;for((e=e||t.length)>t.length&&(e=t.length),a=e-1;a>=0&&128==(192&t[a]);)a--;return a<0?e:0===a?e:a+ue[t[a]]>e?a:e}};var be=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0},me=Object.prototype.toString,pe=0,ve=-1,ke=0,ye=8;function xe(t){if(!(this instanceof xe))return new xe(t);this.options=e.assign({level:ve,method:ye,chunkSize:16384,windowBits:15,memLevel:8,strategy:ke,to:""},t||{});var a=this.options;a.raw&&a.windowBits>0?a.windowBits=-a.windowBits:a.gzip&&a.windowBits>0&&a.windowBits<16&&(a.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new be,this.strm.avail_out=0;var i=de.deflateInit2(this.strm,a.level,a.method,a.windowBits,a.memLevel,a.strategy);if(i!==pe)throw new Error(rt[i]);if(a.header&&de.deflateSetHeader(this.strm,a.header),a.dictionary){var n;if(n="string"==typeof a.dictionary?ge.string2buf(a.dictionary):"[object ArrayBuffer]"===me.call(a.dictionary)?new Uint8Array(a.dictionary):a.dictionary,(i=de.deflateSetDictionary(this.strm,n))!==pe)throw new Error(rt[i]);this._dict_set=!0}}function ze(t,e){var a=new xe(e);if(a.push(t,!0),a.err)throw a.msg||rt[a.err];return a.result}xe.prototype.push=function(t,a){var i,n,r=this.strm,s=this.options.chunkSize;if(this.ended)return!1;n=a===~~a?a:!0===a?4:0,"string"==typeof t?r.input=ge.string2buf(t):"[object ArrayBuffer]"===me.call(t)?r.input=new Uint8Array(t):r.input=t,r.next_in=0,r.avail_in=r.input.length;do{if(0===r.avail_out&&(r.output=new e.Buf8(s),r.next_out=0,r.avail_out=s),1!==(i=de.deflate(r,n))&&i!==pe)return this.onEnd(i),this.ended=!0,!1;0!==r.avail_out&&(0!==r.avail_in||4!==n&&2!==n)||("string"===this.options.to?this.onData(ge.buf2binstring(e.shrinkBuf(r.output,r.next_out))):this.onData(e.shrinkBuf(r.output,r.next_out)))}while((r.avail_in>0||0===r.avail_out)&&1!==i);return 4===n?(i=de.deflateEnd(this.strm),this.onEnd(i),this.ended=!0,i===pe):2!==n||(this.onEnd(pe),r.avail_out=0,!0)},xe.prototype.onData=function(t){this.chunks.push(t)},xe.prototype.onEnd=function(t){t===pe&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=e.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Be={Deflate:xe,deflate:ze,deflateRaw:function(t,e){return(e=e||{}).raw=!0,ze(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,ze(t,e)}},Se=function(t,e){var a,i,n,r,s,o,h,l,d,_,f,u,c,w,g,b,m,p,v,k,y,x,z,B,S;a=t.state,i=t.next_in,B=t.input,n=i+(t.avail_in-5),r=t.next_out,S=t.output,s=r-(e-t.avail_out),o=r+(t.avail_out-257),h=a.dmax,l=a.wsize,d=a.whave,_=a.wnext,f=a.window,u=a.hold,c=a.bits,w=a.lencode,g=a.distcode,b=(1<>>=v=p>>>24,c-=v,0===(v=p>>>16&255))S[r++]=65535&p;else{if(!(16&v)){if(0==(64&v)){p=w[(65535&p)+(u&(1<>>=v,c-=v),c<15&&(u+=B[i++]<>>=v=p>>>24,c-=v,!(16&(v=p>>>16&255))){if(0==(64&v)){p=g[(65535&p)+(u&(1<h){t.msg="invalid distance too far back",a.mode=30;break t}if(u>>>=v,c-=v,y>(v=r-s)){if((v=y-v)>d&&a.sane){t.msg="invalid distance too far back",a.mode=30;break t}if(x=0,z=f,0===_){if(x+=l-v,v2;)S[r++]=z[x++],S[r++]=z[x++],S[r++]=z[x++],k-=3;k&&(S[r++]=z[x++],k>1&&(S[r++]=z[x++]))}else{x=r-y;do{S[r++]=S[x++],S[r++]=S[x++],S[r++]=S[x++],k-=3}while(k>2);k&&(S[r++]=S[x++],k>1&&(S[r++]=S[x++]))}break}}break}}while(i>3,u&=(1<<(c-=k<<3))-1,t.next_in=i,t.next_out=r,t.avail_in=i=1&&0===C[y];y--);if(x>y&&(x=y),0===y)return r[s++]=20971520,r[s++]=20971520,h.bits=1,0;for(k=1;k0&&(0===t||1!==y))return-1;for(N[1]=0,p=1;p<15;p++)N[p+1]=N[p]+C[p];for(v=0;v852||2===t&&E>592)return 1;for(;;){w=p-B,o[v]c?(g=I[O+o[v]],b=Z[R+o[v]]):(g=96,b=0),l=1<>B)+(d-=l)]=w<<24|g<<16|b|0}while(0!==d);for(l=1<>=1;if(0!==l?(A&=l-1,A+=l):A=0,v++,0==--C[p]){if(p===y)break;p=a[i+o[v]]}if(p>x&&(A&f)!==_){for(0===B&&(B=x),u+=k,S=1<<(z=p-B);z+B852||2===t&&E>592)return 1;r[_=A&f]=x<<24|z<<16|u-s|0}}return 0!==A&&(r[u+A]=p-B<<24|64<<16|0),h.bits=x,0},Ne=0,Ie=1,Oe=2,De=4,Te=5,Ue=6,Fe=0,Le=1,He=2,je=-2,Ke=-3,Me=-4,Pe=-5,Ye=8,Ge=1,Xe=2,We=3,qe=4,Je=5,Qe=6,Ve=7,$e=8,ta=9,ea=10,aa=11,ia=12,na=13,ra=14,sa=15,oa=16,ha=17,la=18,da=19,_a=20,fa=21,ua=22,ca=23,wa=24,ga=25,ba=26,ma=27,pa=28,va=29,ka=30,ya=31,xa=32,za=852,Ba=592,Sa=15;function Ea(t){return(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function Aa(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new e.Buf16(320),this.work=new e.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function Za(t){var a;return t&&t.state?(a=t.state,t.total_in=t.total_out=a.total=0,t.msg="",a.wrap&&(t.adler=1&a.wrap),a.mode=Ge,a.last=0,a.havedict=0,a.dmax=32768,a.head=null,a.hold=0,a.bits=0,a.lencode=a.lendyn=new e.Buf32(za),a.distcode=a.distdyn=new e.Buf32(Ba),a.sane=1,a.back=-1,Fe):je}function Ra(t){var e;return t&&t.state?((e=t.state).wsize=0,e.whave=0,e.wnext=0,Za(t)):je}function Ca(t,e){var a,i;return t&&t.state?(i=t.state,e<0?(a=0,e=-e):(a=1+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?je:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,Ra(t))):je}function Na(t,e){var a,i;return t?(i=new Aa,t.state=i,i.window=null,(a=Ca(t,e))!==Fe&&(t.state=null),a):je}var Ia,Oa,Da=!0;function Ta(t){if(Da){var a;for(Ia=new e.Buf32(512),Oa=new e.Buf32(32),a=0;a<144;)t.lens[a++]=8;for(;a<256;)t.lens[a++]=9;for(;a<280;)t.lens[a++]=7;for(;a<288;)t.lens[a++]=8;for(Ce(Ie,t.lens,0,288,Ia,0,t.work,{bits:9}),a=0;a<32;)t.lens[a++]=5;Ce(Oe,t.lens,0,32,Oa,0,t.work,{bits:5}),Da=!1}t.lencode=Ia,t.lenbits=9,t.distcode=Oa,t.distbits=5}function Ua(t,a,i,n){var r,s=t.state;return null===s.window&&(s.wsize=1<=s.wsize?(e.arraySet(s.window,a,i-s.wsize,s.wsize,0),s.wnext=0,s.whave=s.wsize):((r=s.wsize-s.wnext)>n&&(r=n),e.arraySet(s.window,a,i-n,r,s.wnext),(n-=r)?(e.arraySet(s.window,a,i-n,n,0),s.wnext=n,s.whave=s.wsize):(s.wnext+=r,s.wnext===s.wsize&&(s.wnext=0),s.whave>>8&255,i.check=nt(i.check,A,2,0),d=0,_=0,i.mode=Xe;break}if(i.flags=0,i.head&&(i.head.done=!1),!(1&i.wrap)||(((255&d)<<8)+(d>>8))%31){t.msg="incorrect header check",i.mode=ka;break}if((15&d)!==Ye){t.msg="unknown compression method",i.mode=ka;break}if(_-=4,x=8+(15&(d>>>=4)),0===i.wbits)i.wbits=x;else if(x>i.wbits){t.msg="invalid window size",i.mode=ka;break}i.dmax=1<>8&1),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,i.check=nt(i.check,A,2,0)),d=0,_=0,i.mode=We;case We:for(;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}i.head&&(i.head.time=d),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,A[2]=d>>>16&255,A[3]=d>>>24&255,i.check=nt(i.check,A,4,0)),d=0,_=0,i.mode=qe;case qe:for(;_<16;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}i.head&&(i.head.xflags=255&d,i.head.os=d>>8),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,i.check=nt(i.check,A,2,0)),d=0,_=0,i.mode=Je;case Je:if(1024&i.flags){for(;_<16;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}i.length=d,i.head&&(i.head.extra_len=d),512&i.flags&&(A[0]=255&d,A[1]=d>>>8&255,i.check=nt(i.check,A,2,0)),d=0,_=0}else i.head&&(i.head.extra=null);i.mode=Qe;case Qe:if(1024&i.flags&&((c=i.length)>h&&(c=h),c&&(i.head&&(x=i.head.extra_len-i.length,i.head.extra||(i.head.extra=new Array(i.head.extra_len)),e.arraySet(i.head.extra,n,s,c,x)),512&i.flags&&(i.check=nt(i.check,n,c,s)),h-=c,s+=c,i.length-=c),i.length))break t;i.length=0,i.mode=Ve;case Ve:if(2048&i.flags){if(0===h)break t;c=0;do{x=n[s+c++],i.head&&x&&i.length<65536&&(i.head.name+=String.fromCharCode(x))}while(x&&c>9&1,i.head.done=!0),t.adler=i.check=0,i.mode=ia;break;case ea:for(;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}t.adler=i.check=Ea(d),d=0,_=0,i.mode=aa;case aa:if(0===i.havedict)return t.next_out=o,t.avail_out=l,t.next_in=s,t.avail_in=h,i.hold=d,i.bits=_,He;t.adler=i.check=1,i.mode=ia;case ia:if(a===Te||a===Ue)break t;case na:if(i.last){d>>>=7&_,_-=7&_,i.mode=ma;break}for(;_<3;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}switch(i.last=1&d,_-=1,3&(d>>>=1)){case 0:i.mode=ra;break;case 1:if(Ta(i),i.mode=_a,a===Ue){d>>>=2,_-=2;break t}break;case 2:i.mode=ha;break;case 3:t.msg="invalid block type",i.mode=ka}d>>>=2,_-=2;break;case ra:for(d>>>=7&_,_-=7&_;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if((65535&d)!=(d>>>16^65535)){t.msg="invalid stored block lengths",i.mode=ka;break}if(i.length=65535&d,d=0,_=0,i.mode=sa,a===Ue)break t;case sa:i.mode=oa;case oa:if(c=i.length){if(c>h&&(c=h),c>l&&(c=l),0===c)break t;e.arraySet(r,n,s,c,o),h-=c,s+=c,l-=c,o+=c,i.length-=c;break}i.mode=ia;break;case ha:for(;_<14;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(i.nlen=257+(31&d),d>>>=5,_-=5,i.ndist=1+(31&d),d>>>=5,_-=5,i.ncode=4+(15&d),d>>>=4,_-=4,i.nlen>286||i.ndist>30){t.msg="too many length or distance symbols",i.mode=ka;break}i.have=0,i.mode=la;case la:for(;i.have>>=3,_-=3}for(;i.have<19;)i.lens[Z[i.have++]]=0;if(i.lencode=i.lendyn,i.lenbits=7,B={bits:i.lenbits},z=Ce(Ne,i.lens,0,19,i.lencode,0,i.work,B),i.lenbits=B.bits,z){t.msg="invalid code lengths set",i.mode=ka;break}i.have=0,i.mode=da;case da:for(;i.have>>16&255,p=65535&E,!((b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(p<16)d>>>=b,_-=b,i.lens[i.have++]=p;else{if(16===p){for(S=b+2;_>>=b,_-=b,0===i.have){t.msg="invalid bit length repeat",i.mode=ka;break}x=i.lens[i.have-1],c=3+(3&d),d>>>=2,_-=2}else if(17===p){for(S=b+3;_>>=b)),d>>>=3,_-=3}else{for(S=b+7;_>>=b)),d>>>=7,_-=7}if(i.have+c>i.nlen+i.ndist){t.msg="invalid bit length repeat",i.mode=ka;break}for(;c--;)i.lens[i.have++]=x}}if(i.mode===ka)break;if(0===i.lens[256]){t.msg="invalid code -- missing end-of-block",i.mode=ka;break}if(i.lenbits=9,B={bits:i.lenbits},z=Ce(Ie,i.lens,0,i.nlen,i.lencode,0,i.work,B),i.lenbits=B.bits,z){t.msg="invalid literal/lengths set",i.mode=ka;break}if(i.distbits=6,i.distcode=i.distdyn,B={bits:i.distbits},z=Ce(Oe,i.lens,i.nlen,i.ndist,i.distcode,0,i.work,B),i.distbits=B.bits,z){t.msg="invalid distances set",i.mode=ka;break}if(i.mode=_a,a===Ue)break t;case _a:i.mode=fa;case fa:if(h>=6&&l>=258){t.next_out=o,t.avail_out=l,t.next_in=s,t.avail_in=h,i.hold=d,i.bits=_,Se(t,u),o=t.next_out,r=t.output,l=t.avail_out,s=t.next_in,n=t.input,h=t.avail_in,d=i.hold,_=i.bits,i.mode===ia&&(i.back=-1);break}for(i.back=0;m=(E=i.lencode[d&(1<>>16&255,p=65535&E,!((b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(m&&0==(240&m)){for(v=b,k=m,y=p;m=(E=i.lencode[y+((d&(1<>v)])>>>16&255,p=65535&E,!(v+(b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}d>>>=v,_-=v,i.back+=v}if(d>>>=b,_-=b,i.back+=b,i.length=p,0===m){i.mode=ba;break}if(32&m){i.back=-1,i.mode=ia;break}if(64&m){t.msg="invalid literal/length code",i.mode=ka;break}i.extra=15&m,i.mode=ua;case ua:if(i.extra){for(S=i.extra;_>>=i.extra,_-=i.extra,i.back+=i.extra}i.was=i.length,i.mode=ca;case ca:for(;m=(E=i.distcode[d&(1<>>16&255,p=65535&E,!((b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(0==(240&m)){for(v=b,k=m,y=p;m=(E=i.distcode[y+((d&(1<>v)])>>>16&255,p=65535&E,!(v+(b=E>>>24)<=_);){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}d>>>=v,_-=v,i.back+=v}if(d>>>=b,_-=b,i.back+=b,64&m){t.msg="invalid distance code",i.mode=ka;break}i.offset=p,i.extra=15&m,i.mode=wa;case wa:if(i.extra){for(S=i.extra;_>>=i.extra,_-=i.extra,i.back+=i.extra}if(i.offset>i.dmax){t.msg="invalid distance too far back",i.mode=ka;break}i.mode=ga;case ga:if(0===l)break t;if(c=u-l,i.offset>c){if((c=i.offset-c)>i.whave&&i.sane){t.msg="invalid distance too far back",i.mode=ka;break}c>i.wnext?(c-=i.wnext,w=i.wsize-c):w=i.wnext-c,c>i.length&&(c=i.length),g=i.window}else g=r,w=o-i.offset,c=i.length;c>l&&(c=l),l-=c,i.length-=c;do{r[o++]=g[w++]}while(--c);0===i.length&&(i.mode=fa);break;case ba:if(0===l)break t;r[o++]=i.length,l--,i.mode=fa;break;case ma:if(i.wrap){for(;_<32;){if(0===h)break t;h--,d|=n[s++]<<_,_+=8}if(u-=l,t.total_out+=u,i.total+=u,u&&(t.adler=i.check=i.flags?nt(i.check,r,u,o-u):et(i.check,r,u,o-u)),u=l,(i.flags?d:Ea(d))!==i.check){t.msg="incorrect data check",i.mode=ka;break}d=0,_=0}i.mode=pa;case pa:if(i.wrap&&i.flags){for(;_<32;){if(0===h)break t;h--,d+=n[s++]<<_,_+=8}if(d!==(4294967295&i.total)){t.msg="incorrect length check",i.mode=ka;break}d=0,_=0}i.mode=va;case va:z=Le;break t;case ka:z=Ke;break t;case ya:return Me;case xa:default:return je}return t.next_out=o,t.avail_out=l,t.next_in=s,t.avail_in=h,i.hold=d,i.bits=_,(i.wsize||u!==t.avail_out&&i.mode=0&&a.windowBits<16&&(a.windowBits=-a.windowBits,0===a.windowBits&&(a.windowBits=-15)),!(a.windowBits>=0&&a.windowBits<16)||t&&t.windowBits||(a.windowBits+=32),a.windowBits>15&&a.windowBits<48&&0==(15&a.windowBits)&&(a.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new be,this.strm.avail_out=0;var i=Fa.inflateInit2(this.strm,a.windowBits);if(i!==La.Z_OK)throw new Error(rt[i]);if(this.header=new Ha,Fa.inflateGetHeader(this.strm,this.header),a.dictionary&&("string"==typeof a.dictionary?a.dictionary=ge.string2buf(a.dictionary):"[object ArrayBuffer]"===ja.call(a.dictionary)&&(a.dictionary=new Uint8Array(a.dictionary)),a.raw&&(i=Fa.inflateSetDictionary(this.strm,a.dictionary))!==La.Z_OK))throw new Error(rt[i])}function Ma(t,e){var a=new Ka(e);if(a.push(t,!0),a.err)throw a.msg||rt[a.err];return a.result}Ka.prototype.push=function(t,a){var i,n,r,s,o,h=this.strm,l=this.options.chunkSize,d=this.options.dictionary,_=!1;if(this.ended)return!1;n=a===~~a?a:!0===a?La.Z_FINISH:La.Z_NO_FLUSH,"string"==typeof t?h.input=ge.binstring2buf(t):"[object ArrayBuffer]"===ja.call(t)?h.input=new Uint8Array(t):h.input=t,h.next_in=0,h.avail_in=h.input.length;do{if(0===h.avail_out&&(h.output=new e.Buf8(l),h.next_out=0,h.avail_out=l),(i=Fa.inflate(h,La.Z_NO_FLUSH))===La.Z_NEED_DICT&&d&&(i=Fa.inflateSetDictionary(this.strm,d)),i===La.Z_BUF_ERROR&&!0===_&&(i=La.Z_OK,_=!1),i!==La.Z_STREAM_END&&i!==La.Z_OK)return this.onEnd(i),this.ended=!0,!1;h.next_out&&(0!==h.avail_out&&i!==La.Z_STREAM_END&&(0!==h.avail_in||n!==La.Z_FINISH&&n!==La.Z_SYNC_FLUSH)||("string"===this.options.to?(r=ge.utf8border(h.output,h.next_out),s=h.next_out-r,o=ge.buf2string(h.output,r),h.next_out=s,h.avail_out=l-s,s&&e.arraySet(h.output,h.output,r,s,0),this.onData(o)):this.onData(e.shrinkBuf(h.output,h.next_out)))),0===h.avail_in&&0===h.avail_out&&(_=!0)}while((h.avail_in>0||0===h.avail_out)&&i!==La.Z_STREAM_END);return i===La.Z_STREAM_END&&(n=La.Z_FINISH),n===La.Z_FINISH?(i=Fa.inflateEnd(this.strm),this.onEnd(i),this.ended=!0,i===La.Z_OK):n!==La.Z_SYNC_FLUSH||(this.onEnd(La.Z_OK),h.avail_out=0,!0)},Ka.prototype.onData=function(t){this.chunks.push(t)},Ka.prototype.onEnd=function(t){t===La.Z_OK&&("string"===this.options.to?this.result=this.chunks.join(""):this.result=e.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var Pa={Inflate:Ka,inflate:Ma,inflateRaw:function(t,e){return(e=e||{}).raw=!0,Ma(t,e)},ungzip:Ma},Ya={};(0,e.assign)(Ya,Be,Pa,La);var Ga=Ya;window.pako=Ga}();
diff --git a/build/newcanvas/pathseg.js b/build/newcanvas/pathseg.js
new file mode 100644
index 0000000..b369b23
--- /dev/null
+++ b/build/newcanvas/pathseg.js
@@ -0,0 +1 @@
+!function(){"use strict";"SVGPathSeg"in window||(window.SVGPathSeg=function(t,e,n){this.pathSegType=t,this.pathSegTypeAsLetter=e,this._owningPathSegList=n},window.SVGPathSeg.prototype.classname="SVGPathSeg",window.SVGPathSeg.PATHSEG_UNKNOWN=0,window.SVGPathSeg.PATHSEG_CLOSEPATH=1,window.SVGPathSeg.PATHSEG_MOVETO_ABS=2,window.SVGPathSeg.PATHSEG_MOVETO_REL=3,window.SVGPathSeg.PATHSEG_LINETO_ABS=4,window.SVGPathSeg.PATHSEG_LINETO_REL=5,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS=6,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL=7,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS=8,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL=9,window.SVGPathSeg.PATHSEG_ARC_ABS=10,window.SVGPathSeg.PATHSEG_ARC_REL=11,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS=12,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL=13,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS=14,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL=15,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS=16,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL=17,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS=18,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL=19,window.SVGPathSeg.prototype._segmentChanged=function(){this._owningPathSegList&&this._owningPathSegList.segmentChanged(this)},window.SVGPathSegClosePath=function(t){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CLOSEPATH,"z",t)},window.SVGPathSegClosePath.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegClosePath.prototype.toString=function(){return"[object SVGPathSegClosePath]"},window.SVGPathSegClosePath.prototype._asPathString=function(){return this.pathSegTypeAsLetter},window.SVGPathSegClosePath.prototype.clone=function(){return new window.SVGPathSegClosePath(void 0)},window.SVGPathSegMovetoAbs=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_MOVETO_ABS,"M",t),this._x=e,this._y=n},window.SVGPathSegMovetoAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegMovetoAbs.prototype.toString=function(){return"[object SVGPathSegMovetoAbs]"},window.SVGPathSegMovetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegMovetoAbs.prototype.clone=function(){return new window.SVGPathSegMovetoAbs(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegMovetoAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegMovetoAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegMovetoRel=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_MOVETO_REL,"m",t),this._x=e,this._y=n},window.SVGPathSegMovetoRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegMovetoRel.prototype.toString=function(){return"[object SVGPathSegMovetoRel]"},window.SVGPathSegMovetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegMovetoRel.prototype.clone=function(){return new window.SVGPathSegMovetoRel(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegMovetoRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegMovetoRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoAbs=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_ABS,"L",t),this._x=e,this._y=n},window.SVGPathSegLinetoAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoAbs.prototype.toString=function(){return"[object SVGPathSegLinetoAbs]"},window.SVGPathSegLinetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegLinetoAbs.prototype.clone=function(){return new window.SVGPathSegLinetoAbs(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegLinetoAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegLinetoAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoRel=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_REL,"l",t),this._x=e,this._y=n},window.SVGPathSegLinetoRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoRel.prototype.toString=function(){return"[object SVGPathSegLinetoRel]"},window.SVGPathSegLinetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegLinetoRel.prototype.clone=function(){return new window.SVGPathSegLinetoRel(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegLinetoRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegLinetoRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicAbs=function(t,e,n,i,o,r,h){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS,"C",t),this._x=e,this._y=n,this._x1=i,this._y1=o,this._x2=r,this._y2=h},window.SVGPathSegCurvetoCubicAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicAbs]"},window.SVGPathSegCurvetoCubicAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicAbs(void 0,this._x,this._y,this._x1,this._y1,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicAbs.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicRel=function(t,e,n,i,o,r,h){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL,"c",t),this._x=e,this._y=n,this._x1=i,this._y1=o,this._x2=r,this._y2=h},window.SVGPathSegCurvetoCubicRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicRel]"},window.SVGPathSegCurvetoCubicRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicRel.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicRel(void 0,this._x,this._y,this._x1,this._y1,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicRel.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticAbs=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS,"Q",t),this._x=e,this._y=n,this._x1=i,this._y1=o},window.SVGPathSegCurvetoQuadraticAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticAbs]"},window.SVGPathSegCurvetoQuadraticAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticAbs(void 0,this._x,this._y,this._x1,this._y1)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticAbs.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticRel=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL,"q",t),this._x=e,this._y=n,this._x1=i,this._y1=o},window.SVGPathSegCurvetoQuadraticRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticRel]"},window.SVGPathSegCurvetoQuadraticRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticRel.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticRel(void 0,this._x,this._y,this._x1,this._y1)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"x1",{get:function(){return this._x1},set:function(t){this._x1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticRel.prototype,"y1",{get:function(){return this._y1},set:function(t){this._y1=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegArcAbs=function(t,e,n,i,o,r,h,s){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_ARC_ABS,"A",t),this._x=e,this._y=n,this._r1=i,this._r2=o,this._angle=r,this._largeArcFlag=h,this._sweepFlag=s},window.SVGPathSegArcAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegArcAbs.prototype.toString=function(){return"[object SVGPathSegArcAbs]"},window.SVGPathSegArcAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y},window.SVGPathSegArcAbs.prototype.clone=function(){return new window.SVGPathSegArcAbs(void 0,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag)},Object.defineProperty(window.SVGPathSegArcAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"r1",{get:function(){return this._r1},set:function(t){this._r1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"r2",{get:function(){return this._r2},set:function(t){this._r2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"angle",{get:function(){return this._angle},set:function(t){this._angle=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag},set:function(t){this._largeArcFlag=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcAbs.prototype,"sweepFlag",{get:function(){return this._sweepFlag},set:function(t){this._sweepFlag=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegArcRel=function(t,e,n,i,o,r,h,s){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_ARC_REL,"a",t),this._x=e,this._y=n,this._r1=i,this._r2=o,this._angle=r,this._largeArcFlag=h,this._sweepFlag=s},window.SVGPathSegArcRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegArcRel.prototype.toString=function(){return"[object SVGPathSegArcRel]"},window.SVGPathSegArcRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y},window.SVGPathSegArcRel.prototype.clone=function(){return new window.SVGPathSegArcRel(void 0,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag)},Object.defineProperty(window.SVGPathSegArcRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"r1",{get:function(){return this._r1},set:function(t){this._r1=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"r2",{get:function(){return this._r2},set:function(t){this._r2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"angle",{get:function(){return this._angle},set:function(t){this._angle=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag},set:function(t){this._largeArcFlag=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegArcRel.prototype,"sweepFlag",{get:function(){return this._sweepFlag},set:function(t){this._sweepFlag=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoHorizontalAbs=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS,"H",t),this._x=e},window.SVGPathSegLinetoHorizontalAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoHorizontalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalAbs]"},window.SVGPathSegLinetoHorizontalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x},window.SVGPathSegLinetoHorizontalAbs.prototype.clone=function(){return new window.SVGPathSegLinetoHorizontalAbs(void 0,this._x)},Object.defineProperty(window.SVGPathSegLinetoHorizontalAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoHorizontalRel=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL,"h",t),this._x=e},window.SVGPathSegLinetoHorizontalRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoHorizontalRel.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalRel]"},window.SVGPathSegLinetoHorizontalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x},window.SVGPathSegLinetoHorizontalRel.prototype.clone=function(){return new window.SVGPathSegLinetoHorizontalRel(void 0,this._x)},Object.defineProperty(window.SVGPathSegLinetoHorizontalRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoVerticalAbs=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS,"V",t),this._y=e},window.SVGPathSegLinetoVerticalAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoVerticalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalAbs]"},window.SVGPathSegLinetoVerticalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y},window.SVGPathSegLinetoVerticalAbs.prototype.clone=function(){return new window.SVGPathSegLinetoVerticalAbs(void 0,this._y)},Object.defineProperty(window.SVGPathSegLinetoVerticalAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegLinetoVerticalRel=function(t,e){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL,"v",t),this._y=e},window.SVGPathSegLinetoVerticalRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegLinetoVerticalRel.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalRel]"},window.SVGPathSegLinetoVerticalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y},window.SVGPathSegLinetoVerticalRel.prototype.clone=function(){return new window.SVGPathSegLinetoVerticalRel(void 0,this._y)},Object.defineProperty(window.SVGPathSegLinetoVerticalRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicSmoothAbs=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS,"S",t),this._x=e,this._y=n,this._x2=i,this._y2=o},window.SVGPathSegCurvetoCubicSmoothAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothAbs]"},window.SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicSmoothAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicSmoothAbs(void 0,this._x,this._y,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothAbs.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoCubicSmoothRel=function(t,e,n,i,o){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL,"s",t),this._x=e,this._y=n,this._x2=i,this._y2=o},window.SVGPathSegCurvetoCubicSmoothRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoCubicSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothRel]"},window.SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y},window.SVGPathSegCurvetoCubicSmoothRel.prototype.clone=function(){return new window.SVGPathSegCurvetoCubicSmoothRel(void 0,this._x,this._y,this._x2,this._y2)},Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"x2",{get:function(){return this._x2},set:function(t){this._x2=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoCubicSmoothRel.prototype,"y2",{get:function(){return this._y2},set:function(t){this._y2=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticSmoothAbs=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS,"T",t),this._x=e,this._y=n},window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothAbs]"},window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticSmoothAbs(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathSegCurvetoQuadraticSmoothRel=function(t,e,n){window.SVGPathSeg.call(this,window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,"t",t),this._x=e,this._y=n},window.SVGPathSegCurvetoQuadraticSmoothRel.prototype=Object.create(window.SVGPathSeg.prototype),window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothRel]"},window.SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y},window.SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone=function(){return new window.SVGPathSegCurvetoQuadraticSmoothRel(void 0,this._x,this._y)},Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype,"x",{get:function(){return this._x},set:function(t){this._x=t,this._segmentChanged()},enumerable:!0}),Object.defineProperty(window.SVGPathSegCurvetoQuadraticSmoothRel.prototype,"y",{get:function(){return this._y},set:function(t){this._y=t,this._segmentChanged()},enumerable:!0}),window.SVGPathElement.prototype.createSVGPathSegClosePath=function(){return new window.SVGPathSegClosePath(void 0)},window.SVGPathElement.prototype.createSVGPathSegMovetoAbs=function(t,e){return new window.SVGPathSegMovetoAbs(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegMovetoRel=function(t,e){return new window.SVGPathSegMovetoRel(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegLinetoAbs=function(t,e){return new window.SVGPathSegLinetoAbs(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegLinetoRel=function(t,e){return new window.SVGPathSegLinetoRel(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs=function(t,e,n,i,o,r){return new window.SVGPathSegCurvetoCubicAbs(void 0,t,e,n,i,o,r)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel=function(t,e,n,i,o,r){return new window.SVGPathSegCurvetoCubicRel(void 0,t,e,n,i,o,r)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs=function(t,e,n,i){return new window.SVGPathSegCurvetoQuadraticAbs(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel=function(t,e,n,i){return new window.SVGPathSegCurvetoQuadraticRel(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegArcAbs=function(t,e,n,i,o,r,h){return new window.SVGPathSegArcAbs(void 0,t,e,n,i,o,r,h)},window.SVGPathElement.prototype.createSVGPathSegArcRel=function(t,e,n,i,o,r,h){return new window.SVGPathSegArcRel(void 0,t,e,n,i,o,r,h)},window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs=function(t){return new window.SVGPathSegLinetoHorizontalAbs(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel=function(t){return new window.SVGPathSegLinetoHorizontalRel(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs=function(t){return new window.SVGPathSegLinetoVerticalAbs(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel=function(t){return new window.SVGPathSegLinetoVerticalRel(void 0,t)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs=function(t,e,n,i){return new window.SVGPathSegCurvetoCubicSmoothAbs(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel=function(t,e,n,i){return new window.SVGPathSegCurvetoCubicSmoothRel(void 0,t,e,n,i)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs=function(t,e){return new window.SVGPathSegCurvetoQuadraticSmoothAbs(void 0,t,e)},window.SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel=function(t,e){return new window.SVGPathSegCurvetoQuadraticSmoothRel(void 0,t,e)},"getPathSegAtLength"in window.SVGPathElement.prototype||(window.SVGPathElement.prototype.getPathSegAtLength=function(t){if(void 0===t||!isFinite(t))throw"Invalid arguments.";var e=document.createElementNS("http://www.w3.org/2000/svg","path");e.setAttribute("d",this.getAttribute("d"));var n=e.pathSegList.numberOfItems-1;if(n<=0)return 0;do{if(e.pathSegList.removeItem(n),t>e.getTotalLength())break;n--}while(n>0);return n})),"SVGPathSegList"in window&&"appendItem"in window.SVGPathSegList.prototype||(window.SVGPathSegList=function(t){this._pathElement=t,this._list=this._parsePath(this._pathElement.getAttribute("d")),this._mutationObserverConfig={attributes:!0,attributeFilter:["d"]},this._pathElementMutationObserver=new MutationObserver(this._updateListFromPathMutations.bind(this)),this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig)},window.SVGPathSegList.prototype.classname="SVGPathSegList",Object.defineProperty(window.SVGPathSegList.prototype,"numberOfItems",{get:function(){return this._checkPathSynchronizedToList(),this._list.length},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"pathSegList",{get:function(){return this._pathSegList||(this._pathSegList=new window.SVGPathSegList(this)),this._pathSegList},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"normalizedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"animatedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),Object.defineProperty(window.SVGPathElement.prototype,"animatedNormalizedPathSegList",{get:function(){return this.pathSegList},enumerable:!0}),window.SVGPathSegList.prototype._checkPathSynchronizedToList=function(){this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords())},window.SVGPathSegList.prototype._updateListFromPathMutations=function(t){if(this._pathElement){var e=!1;t.forEach(function(t){"d"==t.attributeName&&(e=!0)}),e&&(this._list=this._parsePath(this._pathElement.getAttribute("d")))}},window.SVGPathSegList.prototype._writeListToPath=function(){this._pathElementMutationObserver.disconnect(),this._pathElement.setAttribute("d",window.SVGPathSegList._pathSegArrayAsString(this._list)),this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig)},window.SVGPathSegList.prototype.segmentChanged=function(t){this._writeListToPath()},window.SVGPathSegList.prototype.clear=function(){this._checkPathSynchronizedToList(),this._list.forEach(function(t){t._owningPathSegList=null}),this._list=[],this._writeListToPath()},window.SVGPathSegList.prototype.initialize=function(t){return this._checkPathSynchronizedToList(),this._list=[t],t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList.prototype._checkValidIndex=function(t){if(isNaN(t)||t<0||t>=this.numberOfItems)throw"INDEX_SIZE_ERR"},window.SVGPathSegList.prototype.getItem=function(t){return this._checkPathSynchronizedToList(),this._checkValidIndex(t),this._list[t]},window.SVGPathSegList.prototype.insertItemBefore=function(t,e){return this._checkPathSynchronizedToList(),e>this.numberOfItems&&(e=this.numberOfItems),t._owningPathSegList&&(t=t.clone()),this._list.splice(e,0,t),t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList.prototype.replaceItem=function(t,e){return this._checkPathSynchronizedToList(),t._owningPathSegList&&(t=t.clone()),this._checkValidIndex(e),this._list[e]=t,t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList.prototype.removeItem=function(t){this._checkPathSynchronizedToList(),this._checkValidIndex(t);var e=this._list[t];return this._list.splice(t,1),this._writeListToPath(),e},window.SVGPathSegList.prototype.appendItem=function(t){return this._checkPathSynchronizedToList(),t._owningPathSegList&&(t=t.clone()),this._list.push(t),t._owningPathSegList=this,this._writeListToPath(),t},window.SVGPathSegList._pathSegArrayAsString=function(t){var e="",n=!0;return t.forEach(function(t){n?(n=!1,e+=t._asPathString()):e+=" "+t._asPathString()}),e},window.SVGPathSegList.prototype._parsePath=function(t){if(!t||0==t.length)return[];var e=this,n=function(){this.pathSegList=[]};n.prototype.appendSegment=function(t){this.pathSegList.push(t)};var i=function(t){this._string=t,this._currentIndex=0,this._endIndex=this._string.length,this._previousCommand=window.SVGPathSeg.PATHSEG_UNKNOWN,this._skipOptionalSpaces()};i.prototype._isCurrentSpace=function(){var t=this._string[this._currentIndex];return t<=" "&&(" "==t||"\n"==t||"\t"==t||"\r"==t||"\f"==t)},i.prototype._skipOptionalSpaces=function(){for(;this._currentIndex="0"&&t<="9")&&e!=window.SVGPathSeg.PATHSEG_CLOSEPATH?e==window.SVGPathSeg.PATHSEG_MOVETO_ABS?window.SVGPathSeg.PATHSEG_LINETO_ABS:e==window.SVGPathSeg.PATHSEG_MOVETO_REL?window.SVGPathSeg.PATHSEG_LINETO_REL:e:window.SVGPathSeg.PATHSEG_UNKNOWN},i.prototype.initialCommandIsMoveTo=function(){if(!this.hasMoreData())return!0;var t=this.peekSegmentType();return t==window.SVGPathSeg.PATHSEG_MOVETO_ABS||t==window.SVGPathSeg.PATHSEG_MOVETO_REL},i.prototype._parseNumber=function(){var t=0,e=0,n=1,i=0,o=1,r=1,h=this._currentIndex;if(this._skipOptionalSpaces(),this._currentIndex"9")&&"."!=this._string.charAt(this._currentIndex))){for(var s=this._currentIndex;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)this._currentIndex++;if(this._currentIndex!=s)for(var a=this._currentIndex-1,S=1;a>=s;)e+=S*(this._string.charAt(a--)-"0"),S*=10;if(this._currentIndex=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")return;for(;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)n*=10,i+=(this._string.charAt(this._currentIndex)-"0")/n,this._currentIndex+=1}if(this._currentIndex!=h&&this._currentIndex+1=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")return;for(;this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9";)t*=10,t+=this._string.charAt(this._currentIndex)-"0",this._currentIndex++}var u=e+i;if(u*=o,t&&(u*=Math.pow(10,r*t)),h!=this._currentIndex)return this._skipOptionalSpacesOrDelimiter(),u}},i.prototype._parseArcFlag=function(){if(!(this._currentIndex>=this._endIndex)){var t=!1,e=this._string.charAt(this._currentIndex++);if("0"==e)t=!1;else{if("1"!=e)return;t=!0}return this._skipOptionalSpacesOrDelimiter(),t}},i.prototype.parseSegment=function(){var t=this._string[this._currentIndex],n=this._pathSegTypeFromChar(t);if(n==window.SVGPathSeg.PATHSEG_UNKNOWN){if(this._previousCommand==window.SVGPathSeg.PATHSEG_UNKNOWN)return null;if((n=this._nextCommandHelper(t,this._previousCommand))==window.SVGPathSeg.PATHSEG_UNKNOWN)return null}else this._currentIndex++;switch(this._previousCommand=n,n){case window.SVGPathSeg.PATHSEG_MOVETO_REL:return new window.SVGPathSegMovetoRel(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_MOVETO_ABS:return new window.SVGPathSegMovetoAbs(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_REL:return new window.SVGPathSegLinetoRel(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_ABS:return new window.SVGPathSegLinetoAbs(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:return new window.SVGPathSegLinetoHorizontalRel(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:return new window.SVGPathSegLinetoHorizontalAbs(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:return new window.SVGPathSegLinetoVerticalRel(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:return new window.SVGPathSegLinetoVerticalAbs(e,this._parseNumber());case window.SVGPathSeg.PATHSEG_CLOSEPATH:return this._skipOptionalSpaces(),new window.SVGPathSegClosePath(e);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:var i={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new window.SVGPathSegCurvetoCubicRel(e,i.x,i.y,i.x1,i.y1,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:return i={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoCubicAbs(e,i.x,i.y,i.x1,i.y1,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:return i={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoCubicSmoothRel(e,i.x,i.y,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:return i={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoCubicSmoothAbs(e,i.x,i.y,i.x2,i.y2);case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:return i={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoQuadraticRel(e,i.x,i.y,i.x1,i.y1);case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:return i={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegCurvetoQuadraticAbs(e,i.x,i.y,i.x1,i.y1);case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:return new window.SVGPathSegCurvetoQuadraticSmoothRel(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:return new window.SVGPathSegCurvetoQuadraticSmoothAbs(e,this._parseNumber(),this._parseNumber());case window.SVGPathSeg.PATHSEG_ARC_REL:return i={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegArcRel(e,i.x,i.y,i.x1,i.y1,i.arcAngle,i.arcLarge,i.arcSweep);case window.SVGPathSeg.PATHSEG_ARC_ABS:return i={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()},new window.SVGPathSegArcAbs(e,i.x,i.y,i.x1,i.y1,i.arcAngle,i.arcLarge,i.arcSweep);default:throw"Unknown path seg type."}};var o=new n,r=new i(t);if(!r.initialCommandIsMoveTo())return[];for(;r.hasMoreData();){var h=r.parseSegment();if(!h)return[];o.appendSegment(h)}return o.pathSegList})}();
diff --git a/build/newcanvas/script.js b/build/newcanvas/script.js
new file mode 100644
index 0000000..46dfb89
--- /dev/null
+++ b/build/newcanvas/script.js
@@ -0,0 +1 @@
+!function(){"use strict";const e=(e,t)=>{if(t||(t=Z.ctx),t.globalCompositeOperation="eraser"===e.getAttribute("class")?"destination-out":"source-over","path"===e.nodeName){t.strokeStyle=e.getAttribute("stroke"),t.lineWidth=e.getAttribute("stroke-width"),t.beginPath();for(let o=0;o{Z.seekbarMove&&Z.seekbarMove(e)},o=o=>{if(Z.rewindCache.length>=Z.fastUndoLevels&&Z.rewindCache.pop(),Z.rewindCache.unshift(Z.ctx.getImageData(0,0,600,500)),e(o),Z.timeedit&&Z.position!==Z.svg.childNodes.length-1)Z.svg.insertBefore(o,Z.svg.childNodes[Z.position+1]);else{for(let e=Z.svg.childNodes.length-1;e>Z.position;e--)Z.svg.removeChild(Z.svg.childNodes[e]);Z.svg.appendChild(o),Z.position=Z.svg.childNodes.length-1,t(1)}},n=(e,t)=>{const o=document.createElementNS("http://www.w3.org/2000/svg",e);return t&&Object.keys(t).forEach(e=>{t[e]&&o.setAttribute(e,t[e])}),o},a=e=>{o(n("rect",{class:e,x:0,y:0,width:600,height:500,fill:Z.background})),Z.lastrect=Z.position},r=()=>{let e=!1;for(let t=Z.svg.childNodes.length-1;t>0;t--){const o=Z.svg.childNodes[t];e||t>Z.position?Z.svg.removeChild(o):"rect"===o.nodeName&&t<=Z.position&&(e=!0,"eraser"===o.getAttribute("class")&&Z.svg.removeChild(o))}},i=(e,t,o,n)=>{const{ctxDisp:a}=Z;a.strokeStyle=Z.lastcolor,a.lineWidth=Z.size,a.beginPath(),a.moveTo(e,t),a.lineTo(o,n),a.stroke()},s=e=>{e&&Z.svgDisp.insertBefore(Z.path,Z.svgDisp.firstChild)},d=e=>"#"===e[0]?4===e.length?[...e.substr(1,3)+"F"].map(e=>parseInt(e+e,16)):(e+"FF").substr(1,8).match(/.{2}/g).map(e=>parseInt(e,16)):"rgba"===e.substr(0,4)?e.match(/[\d\.]+/g).map((e,t)=>3===t?Math.floor(255*parseFloat(e)):parseInt(e,10)):"rgb"===e.substr(0,3)?(e+255).match(/[\d\.]+/g).map(e=>parseInt(e,10)):[0,0,0,255],l=e=>"#"+e.map((e,t)=>t<3?("0"+e.toString(16)).slice(-2):"").join(""),c=e=>l(d(e)),f=e=>{const[t,o,n]=e.map(e=>e>10?Math.pow((e/255+.055)/1.055,2.4):e/255/12.92),[a,r,i]=[(.4124*t+.3576*o+.1805*n)/.95047,.2126*t+.7152*o+.0722*n,(.0193*t+.1192*o+.9505*n)/1.08883].map(e=>e>.008856?Math.pow(e,1/3):7.787*e+16/116);return[116*r-16,500*(a-r),200*(r-i)]},u=e=>document.getElementById(e),p=(e,t)=>{if(u("newcanvasyo").classList.contains("sandbox")||window.gameInfo&&window.gameInfo.friend)return l([...e]);const o=t.slice(0).map(t=>((e,t)=>{const o=f(e),n=f(t),a=n[0]-o[0],r=n[1]-o[1],i=n[2]-o[2];return Math.sqrt(a**2*2+r**2+i**2)})([...e],d(t))),n=Math.min(...o),a=t[o.indexOf(n)];return c(a)},g=(e,t)=>{const o=Z.ctx.getImageData(e,t,1,1).data;return o[3]>0?p(o,Z.palette):Z.background},h=e=>{e||(e=Z.svg.childNodes.length-1);for(let t=e;t>0;t--){if("rect"===Z.svg.childNodes[t].nodeName)return t}return 0},m=e=>String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,255&e),b=e=>{const t="eraser"===e;Z.transparent=t,Z.canvas.style.background=t?"none":e,e=t?"#ffffff":c(e),Z.background=e,Z.svg.querySelectorAll(".eraser").forEach(t=>t.setAttribute("path"===t.nodeName?"stroke":"fill",e))},w=(e,t)=>{const{length:o}=e;if(o<2)return;if(t.pathSegList.initialize(t.createSVGPathSegMovetoAbs(e[0].x,e[0].y)),!window.options.smoothening){for(let o=1;o1){let e=!1;if(Math.abs(p-l)>=Math.PI/4)t.pathSegList.appendItem(t.createSVGPathSegLinetoAbs(r.x,r.y));else if(e&&c/g>=.4&&c/g<=2.5){const e={x:o.x+Math.cos(n)*c*.4,y:o.y+Math.sin(n)*c*.4},a={x:r.x-Math.cos(h)*g*.4,y:r.y-Math.sin(h)*g*.4};t.pathSegList.appendItem(t.createSVGPathSegCurvetoCubicAbs(r.x,r.y,e.x,e.y,a.x,a.y))}else t.pathSegList.appendItem(t.createSVGPathSegLinetoAbs(r.x,r.y)),e=!0}n=h}const a=e[o-1];t.pathSegList.appendItem(t.createSVGPathSegLinetoAbs(a.x,a.y))},v=e=>new Uint8Array([...e].map(e=>e.charCodeAt(0))),y=(e,t)=>{const o=e<<8|t;return o>32767?o-65536:o},k=e=>{const{pako:t}=window,o=e[0];let a;if(4===o)e=t.inflate(e.subarray(1)),a=0;else if(3===o)e=v(t.inflate(e.subarray(1),{to:"string"})),a=0;else{if(2!==o)throw new Error(`Unsupported version: ${o}`);a=1}const r=n("svg",{xmlns:"http://www.w3.org/2000/svg",version:"1.1",width:600,height:500}),i={color:"#000000",size:14,x:0,y:0,pattern:0},s=[],d=`rgb(${e[a]}, ${e[a+1]}, ${e[a+2]})`;r.background=d,r.appendChild(n("rect",{class:"eraser",x:0,y:0,width:600,height:500,fill:d}));for(let t=a+4;t[...Z.svg.childNodes].splice(Z.lastrecte(t)),S=e=>{const o=new DataView(e),n=o.getUint32(0);if(2303741511!==n)throw new Error(`Invalid PNG format: ${m(n)}`);for(let n=8;n{Z.fileInput||(Z.fileInput=document.createElement("input"),Z.fileInput.style.position="absolute",Z.fileInput.style.top="-1000px",Z.fileInput.type="file",Z.fileInput.accept=".png",document.body.appendChild(Z.fileInput),Z.fileInput.addEventListener("change",e=>{const t=new FileReader;t.onload=()=>S(t.result),e.currentTarget.files[0]&&t.readAsArrayBuffer(e.currentTarget.files[0])},!1)),Z.fileInput.click()},A=e=>{const t=new XMLHttpRequest;if(t.open("GET",e,!0),!("responseType"in t))return alert("Your browser is too old for this");t.responseType="arraybuffer",t.onload=()=>S(t.response),t.send()},x=()=>Z.svg.childNodes.length-1,E=(e,t)=>{Z.locked||(Z.brushCursor||(Z.brushCursor=n("circle",{"stroke-width":"1",stroke:"#000",fill:"none"}),Z.svgDisp.appendChild(Z.brushCursor),Z.brushCursor2=n("circle",{"stroke-width":"1",stroke:"#fff",fill:"none"}),Z.svgDisp.appendChild(Z.brushCursor2),Z.eyedropperCursor=n("image",{width:16,height:16,visibility:"hidden"}),Z.eyedropperCursor.setAttributeNS("http://www.w3.org/1999/xlink","href","data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARklEQVR4XoXRwQoAIAgEUf//pzeGDgq5G3PrCQqVbIAqsDz9WM2qhTX4GZgPV+JpSFxAC0PwbeVZZIpMgXvAMwoj4U9B3wGySxvzk6ZjvwAAAABJRU5ErkJggg=="),Z.svgDisp.appendChild(Z.eyedropperCursor)),void 0!==e&&(Z.brushCursor.setAttribute("cx",e),Z.brushCursor.setAttribute("cy",t),Z.brushCursor2.setAttribute("cx",e),Z.brushCursor2.setAttribute("cy",t),Z.eyedropperCursor.setAttribute("x",e-1),Z.eyedropperCursor.setAttribute("y",t-15)),Z.brushCursor.setAttribute("r",Z.size/2+.5),Z.brushCursor2.setAttribute("r",Z.size/2-.5))},D=(e,t,o)=>{let{x:n,y:a}=t,r=o.x-n,i=o.y-a;if(0!==r||0!==i){var s=((e.x-n)*r+(e.y-a)*i)/(r*r+i*i);s>1?(n=o.x,a=o.y):s>0&&(n+=r*s,a+=i*s)}return(r=e.x-n)*r+(i=e.y-a)*i},T=()=>{if(Z.locked)return;Z.unsaved=!0;const e=Z.points.length>2?(({points:e,smoothening:t})=>{const o=e.length,n=new("undefined"!=typeof Uint8Array?Uint8Array:Array)(o);let a=0,r=o-1;const i=[],s=[];for(n[a]=n[r]=1;r;){let o,s=0;for(let t=a+1;ts&&(o=t,s=n)}s>t&&(n[o]=1,i.push(a,o,o,r)),r=i.pop(),a=i.pop()}for(let t=0;t{Z.isStroking&&T(),Z.locked=!0,E(-100,-100)},N=(e,t)=>{const o=(()=>{const e=[];for(let t=0;t<256;t++){let o=t;for(let e=0;e<8;e++)o=1&o?3988292384^o>>>1:o>>>1;e.push(o)}return e})();let n=-1;for(let t=0;t>>8^o[255&(n^e.charCodeAt(t))];if(t)for(let e=0;e>>8^o[255&(n^t.charCodeAt(e))];return(-1^n)>>>0},P=e=>d(e).map(e=>String.fromCharCode(e)).join(""),M=e=>String.fromCharCode(e>>8&255,255&e),_=e=>{const{pako:t}=window,o=[P(Z.background)],n={color:P("#000000"),size:14,x:-1,y:-1,pattern:0};return e.childNodes.forEach(e=>{if("path"===e.nodeName){const t="eraser"===e.getAttribute("class")?"ÿÿÿ\0":P(e.getAttribute("stroke")),a=e.getAttribute("stroke-width"),r=e.pattern||0;t===n.color&&a===n.size||(o.push(M(-1)),o.push(M(100*a)),o.push(t),n.color=t,n.size=a),r!==n.pattern&&(o.push(M(-3)),o.push(M(r)),o.push("\0\0\0\0"),n.pattern=r),n.x=e.orig[0].x,n.y=e.orig[0].y,o.push(M(n.x)),o.push(M(n.y));for(let t=1;t[...e].map(e=>String.fromCharCode(e)).join(""))(t.deflate(v(o.join(""))))},B=(o,n,a)=>{r(),t(1);const i=document.createElement("canvas");i.width=o,i.height=n;const s=i.getContext("2d");if(Z.transparent||(s.fillStyle=Z.background,s.fillRect(0,0,o,n)),a)s.drawImage(Z.canvas,0,0,o,n);else{s.lineJoin=s.lineCap="round",s.scale(o/600,n/500);for(let t=0;t{Z.isPlaying&&(Z.isAnimating&&(Z.isAnimating=!1,Z.svgDisp.removeChild(Z.path),e(Z.animatePath),Z.position++,o||t(Z.position/(Z.svg.childNodes.length-1))),Z.isPlaying=!1)},j=()=>{if(!Z.isPlaying)return;const o=Z.svg.childNodes.length-1;let{delay:n}=Z,a=0;if(Z.position{if(!Z.locked){if(Z.rewindCache.length=0,Z.position===Z.svg.childNodes.length-1){if(0===Z.position)return t(1);Z.position=0,t(0),e(Z.svg.childNodes[0])}Z.isPlaying=!0,j()}},G=t=>{if(Z.locked)return;let o=-1;if($(!0),t!==Z.position){if(tZ.position&&(o=Z.position);if(-1!==o){if(t-o>=Z.fastUndoLevels)Z.rewindCache.length=0;else{const{length:e}=Z.rewindCache,n=Math.min(e,t-o+e-Z.fastUndoLevels);Z.rewindCache.splice(e-n,n)}for(let n=o+1;n<=t;n++)t-n{if(!Z.locked){var e=Z.svg.childNodes.length-1;Z.position{if(e||(e=Z.pngBase64,t=".png",Z.unsaved=!1),Z.saveLink||(Z.saveLink=document.createElement("a"),document.body.appendChild(Z.saveLink)),"download"in Z.saveLink){Z.saveLink.href=e;const o=new Date;Z.saveLink.download=["DrawingInTime_",o.getFullYear(),"_",(101+o.getMonth()+"").slice(-2),(100+o.getDate()+"").slice(-2),"_",(100+o.getHours()+"").slice(-2),(100+o.getMinutes()+"").slice(-2),(100+o.getSeconds()+"").slice(-2),t].join(""),Z.saveLink.click()}else window.open(e);return!0},H=(e,t)=>Z.colors[e]=t,V=e=>Z.seekbarMove=e,U=e=>{Z.size=e,E()},q=e=>{if(!Z.brushCursor)return;const t=e?"hidden":"visible",o=e?"visible":"hidden";Z.brushCursor.setAttribute("visibility",t),Z.brushCursor2.setAttribute("visibility",t),Z.eyedropperCursor.setAttribute("visibility",o)},K=(e,t)=>{if(Z.locked)return;if(!Z.isStroking)throw new Error("StrokeAdd without StrokeBegin!");const o=Z.points[Z.points.length-1];o.x===e&&o.y===t||(Z.blot&&(Z.path.pathSegList.removeItem(1),Z.blot=!1),Z.path.pathSegList.appendItem(Z.path.createSVGPathSegLinetoAbs(e,t)),navigator.userAgent.match(/\bPresto\b/)?s(!1):i(o.x,o.y,e,t),Z.points.push({x:e,y:t}))},Y=(e,t,o)=>{if(Z.locked)return;o?Z.lastleft=o:o=Z.lastleft;let a=o?Z.colors[0]:Z.colors[1];const r="eraser"===a?a:null;a="eraser"===a?Z.background:a,Z.path=n("path",{class:r,stroke:a,"stroke-width":Z.size,"stroke-linejoin":"round","stroke-linecap":"round",fill:"none"}),Z.lastcolor=a,Z.path.pattern=Z.pattern,Z.path.pathSegList.appendItem(Z.path.createSVGPathSegMovetoAbs(e,t)),Z.path.pathSegList.appendItem(Z.path.createSVGPathSegLinetoAbs(e,t+.001)),navigator.userAgent.match(/\bPresto\b/)?s(!0):i(e,t,e,t+.001),Z.points=[],Z.points.push({x:e,y:t}),Z.blot=!0,Z.isStroking=!0},X=()=>{Z.locked||Z.position>0&&(G(Z.position-1),t(Z.position/(Z.svg.childNodes.length-1)))},F=()=>Z.locked=!1,J=e=>v(atob(e)),W=e=>{const t=new XMLHttpRequest;t.open("POST","https://api.imgur.com/3/image"),t.onload=()=>{let o=t.responseText;try{o=JSON.parse(o)}catch(e){}if(o.success){const e=new XMLHttpRequest;e.open("POST","https://api.imgur.com/3/image/"+o.data.deletehash),e.setRequestHeader("Authorization","Client-ID 4809db83c8897af");const t=new FormData;t.append("description","Playback: http://grompe.org.ru/drawit/#"+o.data.id),e.send(t)}e(o)},t.onerror=t=>e(`error: ${t}`),t.setRequestHeader("Authorization","Client-ID 4809db83c8897af");const o=new FormData;o.append("image",new Blob([J(Z.pngBase64.substr(22)).buffer],{type:"image/png"})),o.append("type","file"),o.append("title","Made with Drawing in Time"),o.append("description","http://grompe.org.ru/drawit/"),t.send(o)},Q={Normal:["#000000","#444444","#999999","#ffffff","#603913","#c69c6d","#ffdab9","#ff0000","#ffd700","#ff6600","#16ff00","#0fad00","#00ffff","#0247fe","#ec008c","#8601af","#fffdc9"],Sepia:["#402305","#503315","#604325","#705335","#806345","#907355","#a08365","#b09375","#bfa284","#cfb294","#dfc2a4","#ffe2c4"],Grayscale:["#000000","#ffffff","#151515","#2a2a2a","#3f3f3f","#555555","#6a6a6a","#7f7f7f","#949494","#aaaaaa","#bfbfbf","#d4d4d4","#e9e9e9"],"Black and white":["#ffffff","#000000"],CGA:["#555555","#000000","#0000aa","#5555ff","#00aa00","#55ff55","#00aaaa","#55ffff","#aa0000","#ff5555","#aa00aa","#ff55ff","#aa5500","#ffff55","#aaaaaa","#ffffff"],Gameboy:["#8bac0f","#9bbc0f","#306230","#0f380f"],Neon:["#ffffff","#000000","#adfd09","#f3f315","#feac09","#fe0bab","#ad0bfb","#00abff"],Thanksgiving:["#673718","#3c2d27","#c23322","#850005","#c67200","#77785b","#5e6524","#cfb178","#f5e9ce"],Holiday_old:["#3d9949","#7bbd82","#7d1a0c","#bf2a23","#fdd017","#00b7f1","#bababa","#ffffff"],"Valentine's":["#2d1014","#ffffff","#600d17","#c2113a","#b71d1d","#e54d5a","#ff7d63","#fd8647","#fed067","#ffe4b7","#fdc0c6"],Halloween:["#444444","#000000","#999999","#ffffff","#603913","#c69c6d","#7a0e0e","#b40528","#fd2119","#fa5b11","#faa611","#ffd700","#602749","#724b97","#bef202","#519548","#b2bb1e"],"the blues":["#b6cbe4","#618abc","#d0d5ce","#82a2a1","#92b8c1","#607884","#c19292","#8c2c2c","#295c6f"],Spring:["#9ed396","#57b947","#4d7736","#365431","#231302","#3e2409","#a66621","#a67e21","#ebbb49","#ffc0cb","#ffffff"],Beach:["#1ca4d2","#65bbe2","#6ab7bf","#94cbda","#9cbf80","#d2e1ab","#b8a593","#d7cfb9","#dc863e","#f7dca2"],"Tide Pool":["#ffe8b9","#fad489","#ffb44c","#d6b1de","#b197a8","#e5f2ff","#a1ffb8","#53e6ef","#3ad3a8","#1ca4d2","#2271a2"],"Colors of 2016":["#91a7d0","#f6cac9","#eb9587","#776a5f","#d1c2ab","#a39d9d","#648589"],Bee:["#000000","#7a5c00","#b58800","#eab618","#f6de97","#ffffff"],"Colors of 2017":["#86af49","#44883d","#1f4478","#0062a3","#00939a","#59c9d5","#8a9a9a","#5f7278"],"Fire and Ice":["#520909","#b40528","#fd2119","#faa611","#ffe96a","#ffffff","#69ddff","#1c8ae5","#0a3fa9","#040526"],"Canyon Sunset":["#fce3ca","#feb789","#f27c8a","#af5081","#8e6dae","#5f4a8b","#2e1b50"],Juice:["#f3ab54","#ec5e66","#ab5871","#f2a19b","#f9f4d4","#fadfb7","#869e3c","#cbdd7e","#fced95"],Tropical:["#f68357","#fbc040","#fefa56","#fef0f5","#90fc51","#07f182","#1d6ab2","#12041b","#2f0946"],"Grimby Grays":["#000000","#ffffff","#2f3032","#252422","#545758","#4b4a46","#797d80","#71706c","#9ea1a4","#979692","#c4c8cb","#d7d6d2","#dee1e4","#f0efeb"],"DawnBringer 16":["#140c1c","#442434","#30346d","#4e4a4e","#854c30","#346524","#d04648","#757161","#597dce","#d27d2c","#8595a1","#6daa2c","#d2aa99","#6dc2ca","#dad45e","#deeed6"],"Fury Road":["#020c16","#023745","#08616d","#36d4b6","#0afef6","#fce173","#e29f30","#b56942","#ad3f16","#893f1d"],Candy:["#06063c","#4f95ff","#68f9ff","#fffef9","#ff96f8","#ff44d3","#793abd"],Holiday:["#e91434","#97200a","#c66a20","#fdbe30","#688625","#004f28","#112825","#1c69bf","#6096d3","#a5c4e6","#f7d9f0","#f6f6f6"],Blues:["#929aa8","#896868","#546c7d","#633d3d","#284660","#421f29","#232e3f","#0f1328"],"Sin City":["#ffffff","#ff0000","#000000"],"Lucky Clover":["#ffffff","#fcf4c4","#f7b307","#fc8404","#cd7a14","#9bf23e","#40d910","#34900b","#0c442c"],"D's Exclusive":["#000000","#717474","#ffffff","#f25b99","#e4965e","#ffc416","#ffe38f","#0074d9","#09a3ec","#12d1ff","#bcf5ff","#0ee446"],"Retina Burn":["#bc0bff","#ff0b11"],Easter:["#9678ba","#bc9ff0","#e4ccff","#ffa1f1","#fbd0ee","#e6f2ff","#aaedfb","#f4dc7b","#fdfabd","#a1ef85","#ddf7a8"],Neapolitan:["#3f3245","#ff5c98","#ecb2a4","#fff7e1"]},Z={container:null,svg:n("svg",{xmlns:"http://www.w3.org/2000/svg",version:"1.1",width:"600",height:"500"}),canvas:document.createElement("canvas"),canvasDisp:document.createElement("canvas"),svgDisp:n("svg",{version:"1.1",width:"600",height:"500","pointer-events":"none"}),svgHist:null,path:null,points:null,pngBase64:null,lastrect:0,position:0,isStroking:!1,isPlaying:!1,size:14,smoothening:1,palette:Q.Normal,patternCache:{},delay:100,unsaved:!1,background:"#fffdc9",transparent:!1,colors:["#000000","eraser"],fastUndoLevels:10,rewindCache:[],bindContainer:e=>{Z.container=e,Z.canvas.width=600,Z.canvas.height=500,Z.canvas.style.background=Z.background,Z.ctx=Z.canvas.getContext("2d"),Z.ctx.lineJoin=Z.ctx.lineCap="round",Z.container.appendChild(Z.canvas),navigator.userAgent.match(/\bPresto\b/)?Z.DrawDispLine=Z.DrawDispLinePresto:(Z.canvasDisp.width=600,Z.canvasDisp.height=500,Z.ctxDisp=Z.canvasDisp.getContext("2d"),Z.ctxDisp.lineJoin=Z.ctxDisp.lineCap="round",Z.container.appendChild(Z.canvasDisp)),Z.container.appendChild(Z.svgDisp);const t=n("rect",{class:"eraser",x:0,y:0,width:600,height:500,fill:Z.background});Z.svg.appendChild(t)},packPlayback:_,unpackPlayback:k,findLastRect:h,cutHistoryBeforeClearAndAfterPosition:r,makePng:B,fromPng:S,fromUrl:A,fromLocalFile:C,setBackground:b,setColor:H,setSize:U,drawSvgElement:e,updateView:L,drawDispLinePresto:s,drawDispLine:i,strokeBegin:Y,strokeEnd:T,strokeAdd:K,clearWithColor:a,addToSvg:o,undo:X,redo:z,moveSeekbar:t,setSeekbarMove:V,getSeekMax:x,seek:G,play:O,playTimer:j,pause:$,moveCursor:E,showEyedropperCursor:q,eyedropper:g,requestSave:R,uploadToImgur:W,lock:I,unlock:F},ee={rectangle:{},touchSingle:!1,lastTouch:{},lastSeenColorToHighlight:Z.background,brushSizes:[2,6,14,42],timerStart:0},te=e=>{e.preventDefault();const t=[...e.currentTarget.classList].filter(e=>e.startsWith("size-"))[0].match(/\d+/)[0];U(t);const o=u("tools").querySelector(".sel");if(o&&o.classList.remove("sel"),e.currentTarget.classList.add("sel"),!Z.isStroking)return;T();const n=Z.points[Z.points.length-1];Y(n.x,n.y)},oe=e=>{e.preventDefault(),u("play").classList.remove("pause"),z()},ne=e=>{ee.chooseBackground=e,e?(u("colors").classList.add("setbackground"),u("setbackground").classList.add("sel")):(u("colors").classList.remove("setbackground"),u("setbackground").classList.remove("sel"))},ae=e=>{e.preventDefault(),ne(!ee.chooseBackground)},re=e=>{e.preventDefault(),a("eraser"),u("newcanvasyo").classList.contains("sandbox")&&(ee.timerStart=Date.now())},ie=e=>{e.preventDefault(),u("play").classList.remove("pause"),X()},se=()=>u("wacom")&&u("wacom").penAPI&&u("wacom").penAPI.isWacom?u("wacom").penAPI.pointerType:0,de=()=>{const{colors:e}=Z;["primary","secondary"].forEach((t,o)=>{"eraser"===e[o]?(u(t).style.backgroundColor="pink",u(t).classList.add("eraser")):(u(t).style.backgroundColor=e[o],u(t).classList.remove("eraser"))})},le=e=>{if(e.touches||0===e.button||2===e.button){e.preventDefault();const t=e.currentTarget;let o=t.style.backgroundColor;ee.chooseBackground?("eraser"!==t.id&&b(o),ne(!1)):("eraser"===t.id&&(o="eraser"),2===e.button||3===se()?H(1,o):H(0,o),de())}},ce=e=>{e.stopPropagation(),e.preventDefault(),Z.isPlaying?(u("play").classList.remove("pause"),$()):(u("play").classList.add("pause"),O())},fe=e=>{e.altKey||(e.currentTarget.classList.remove("hidecursor"),q(!1),e.currentTarget.removeEventListener("mousemove",fe))},ue=e=>{const{options:t}=window;if(document.activeElement instanceof HTMLInputElement)return!0;if(18===e.keyCode)navigator.userAgent.match(/\bPresto\b/)||u("svgContainer").classList.add("hidecursor"),q(!0),u("svgContainer").addEventListener("mousemove",fe);else if(e.keyCode==="Q".charCodeAt(0))e.preventDefault(),t.colorDoublePress=!t.colorDoublePress;else if(e.keyCode==="Z".charCodeAt(0)||8===e.keyCode&&Z.unsaved)e.preventDefault(),u("play").classList.remove("pause"),X();else if(e.keyCode==="Y".charCodeAt(0))e.preventDefault(),u("play").classList.remove("pause"),z();else if(e.keyCode==="X".charCodeAt(0)){e.preventDefault();const[t,o]=Z.color;H(0,o),H(1,t),de()}else if(e.keyCode==="B".charCodeAt(0)){if(u("setbackground").hidden)return;e.preventDefault(),ne(!ee.chooseBackground)}else if(e.keyCode!=="E".charCodeAt(0)||e.ctrlKey||e.metaKey)if(e.keyCode>=48&&e.keyCode<=57&&!e.ctrlKey&&!e.metaKey&&t.colorNumberShortcuts){e.preventDefault();let o=48===e.keyCode?9:e.keyCode-49;(e.shiftKey||t.colorDoublePress&&Z.prevColorKey===o)&&(o+=8),Z.prevColorKey=o,t.colorDoublePress&&(Z.prevColorKeyTimer&&clearTimeout(Z.prevColorKeyTimer),Z.prevColorKeyTimer=setTimeout(()=>Z.prevColorKey=-1,500));const n=u("colors").querySelectorAll("b");if(o=49&&e.keyCode<=52&&(e.ctrlKey||e.metaKey)?(e.preventDefault(),u("brush"+(e.keyCode-49)).click()):32!==e.keyCode||!e.ctrlKey&&!e.metaKey||e.altKey||e.shiftKey||ce(e);else{e.preventDefault();for(let e=0;e{18===e.keyCode&&(u("svgContainer").classList.remove("hidecursor"),q(!1))},ge=()=>{if(Z.position{e.preventDefault(),ge()||(B(600,500,!0),R())},me=e=>{e.preventDefault(),u("svgContainer").classList.add("loading"),C(),u("svgContainer").classList.remove("loading")},be=e=>{e.preventDefault(),ge()||(u("imgur").childNodes[0].nodeValue="Uploading...",u("imgur").disabled=!0,B(600,500,!0),W(e=>{if(u("imgur").childNodes[0].nodeValue="Upload to imgur",u("popup").classList.add("show"),u("popuptitle").childNodes[0].nodeValue="Imgur upload result",e&&e.success)Z.unsaved=!1,u("imgururl").href=`http://imgur.com/${e.data.id}`,u("imgururl").childNodes[0].nodeValue="Uploaded image",u("imgurdelete").href=`http://imgur.com/delete/${e.data.deletehash}`,u("imgurerror").childNodes[0].nodeValue="",window.inforum&&(window.frameElement.ownerDocument.getElementById("input-comment").value+=``);else{const t=e.data?`Imgur error: ${e.data.error}`:`Error: ${e}`;u("imgurerror").childNodes[0].nodeValue=t}u("imgur").disabled=!1}))},we=e=>{e.preventDefault();const t=x();let o=e.touches?e.touches[0].pageX-ee.rectangle.left-34:e.pageX-ee.rectangle.left-pageXOffset-34;o=Math.min(Math.max(-10,o),492);const n=Math.round((o+10)/502*t);o=n/t*502-10,u("knob").classList.add("smooth"),u("knob").style.marginLeft=o+"px",G(n),u("play").classList.remove("pause")},ve=e=>{e.button&&(e.touches||e.touches.length)||(e.preventDefault(),window.removeEventListener("mouseup",ve),window.removeEventListener("touchend",ve),window.removeEventListener("mousemove",we),window.removeEventListener("touchmove",we))},ye=e=>{(0===e.button||e.touches&&1===e.touches.length)&&(ee.rectangle=u("seekbar").getBoundingClientRect(),we(e),window.addEventListener("mouseup",ve),window.addEventListener("touchend",ve),window.addEventListener("mousemove",we),window.addEventListener("touchmove",we))},ke=e=>{const t=Math.floor(502*e-10);e>0?u("knob").classList.add("smooth"):u("knob").classList.remove("smooth"),u("knob").style.marginLeft=t+"px",e>=1&&u("play").classList.remove("pause")},Le=e=>e.preventDefault(),Se=e=>{u("palettename").childNodes[0].nodeValue=e;const t=Q[e];Z.palette=t;const o=u("palette"),n=o.querySelectorAll("b");n.forEach(e=>o.removeChild(e));const a=n[n.length-1];t.forEach(e=>{const t=document.createElement("b");t.style.backgroundColor=e,t.addEventListener("mousedown",le),t.addEventListener("touchend",le),t.addEventListener("contextmenu",Le),o.appendChild(t),o.appendChild(a)})},Ce=e=>{if(e.touches||0===e.button){e.preventDefault();const t=e.currentTarget.childNodes[0].nodeValue;Se(t)}},Ae=e=>{(e.touches||0===e.button)&&(u("palettechooser").classList.remove("open"),window.removeEventListener("mousedown",Ae),window.removeEventListener("touchend",Ae))},xe=e=>{if(e.touches||0===e.button){e.preventDefault();const t=u("palettechooser");t.classList.toggle("open"),t.classList.contains("open")&&setTimeout(()=>{window.addEventListener("mousedown",Ae),window.addEventListener("touchend",Ae)},1);const o=Object.keys(Q);if(t.childNodes.length{n.fillStyle=e,n.fillRect(8*t+1,1,8,8)});const r=document.createElement("div");r.appendChild(document.createTextNode(o[a])),r.style.backgroundImage=`url("${e.toDataURL()}")`,r.style.backgroundRepeat="no-repeat",r.style.backgroundPosition="center 35px",r.addEventListener("mousedown",Ce),r.addEventListener("touchend",Ce),t.appendChild(r)}}}},Ee=e=>{e.preventDefault(),u("popup").classList.remove("show")},De=e=>e.preventDefault(),Te=()=>!!Z.isPlaying&&(Z.Pause(),u("play").classList.remove("pause"),!0),Ie=e=>{if(e.preventDefault(),!Z.isStroking)return;const t=e.pageX-ee.rectangle.left-pageXOffset,o=e.pageY-ee.rectangle.top-pageYOffset;K(t,o)},Ne=e=>{const{options:t}=window;0!==e.button&&2!==e.button||(e.preventDefault(),Z.isStroking&&T(),t.hideCross&&u("svgContainer").classList.remove("hidecursor"),window.removeEventListener("mouseup",Ne),window.removeEventListener("mousemove",Ie))},Pe=e=>{const{options:t}=window;if(0===e.button||2===e.button){if(Z.isStroking)return Ne(e);if(Te())return;e.preventDefault(),ee.rectangle=e.currentTarget.getBoundingClientRect();const o=e.pageX-ee.rectangle.left-pageXOffset,n=e.pageY-ee.rectangle.top-pageYOffset;if(e.altKey)H(e.button?1:0,g(o,n)),de();else{const a=0===e.button&&3!==se();t.hideCross&&u("svgContainer").classList.add("hidecursor"),Y(o,n,a),window.addEventListener("mouseup",Ne),window.addEventListener("mousemove",Ie)}}},Me=()=>E(-100,-100),_e=e=>{const{options:t}=window;ee.rectangle=e.currentTarget.getBoundingClientRect();const o=e.pageX-ee.rectangle.left-pageXOffset,n=e.pageY-ee.rectangle.top-pageYOffset;if(E(o,n),t.colorUnderCursorHint&&!Z.isStroking){const e=g(o,n);if(ee.stSeenColorToHighlight!==e){const t=u("colors").querySelector("b.hint");t&&t.classList.remove("hint");const o=Z.palette.indexOf(e);if(o>=0){u("colors").querySelectorAll("b")[o].classList.add("hint")}}ee.lastSeenColorToHighlight=e}},Be=()=>{if(!ee.touchSingle)return;const e=ee.lastTouch.pageX-ee.rectangle.left,t=ee.lastTouch.pageY-ee.rectangle.top;Y(e,t,!0),ee.touchSingle=!1},$e=e=>{if(1!==e.touches.length)return;if(Be(),e.preventDefault(),!Z.isStroking)return;const t=e.touches[0].pageX-ee.rectangle.left,o=e.touches[0].pageY-ee.rectangle.top;Z.StrokeAdd(t,o)},je=e=>{0===e.touches.length&&(Be(),e.preventDefault(),window.removeEventListener("touchend",je),window.removeEventListener("touchmove",$e),T())},Oe=e=>{if(1===e.changedTouches.length&&1===e.touches.length){const{pageX:t,pageY:o}=e.changedTouches[0];Math.abs(t-ee.lastTouch.pageX)<10&&Math.abs(o-ee.lastTouch.pageY)<10&&(u("play").classList.remove("pause"),t{if(1===e.touches.length){if(Te())return;ee.rectangle=e.currentTarget.getBoundingClientRect(),ee.touchSingle=!0,ee.lastTouch=e.touches[0],window.addEventListener("touchend",je),window.addEventListener("touchmove",$e)}else ee.touchSingle&&3===e.touches.length&&(ee.lastTouch=e.touches[1],window.addEventListener("touchend",Oe)),ee.touchSingle=!1,window.removeEventListener("touchend",je),window.removeEventListener("touchmove",$e),Z.isStroking&&T()},ze=e=>{if(!Z.unsaved)return;const t="You haven't saved the drawing. Abandon?";return e.returnValue=t,t},Re=e=>{Z.isStroking&&e.preventDefault()},He=e=>alert(e),Ve=(e,t,o)=>{const{options:n}=window,a=new XMLHttpRequest;a.open(e,t),o.header&&a.setRequestHeader(o.header[0],o.header[1]),o.retry=5,a.timeout=15e3,a.ontimeout=()=>{if(o.retry>0){if(!n.retryEnabled)return;document.body.style.cursor="progress",o.retry--,Ve(e,t,o)}else document.body.style.cursor="",o.error()},a.onload=()=>{"/play/skip.json"!==t||"Sorry, but we couldn't find your current game."!==a.error?"/play/exit.json"!==t||"Sorry, but we couldn't find your current game."!==a.error?o.load(a.responseText):location.pathname="/":location.reload()},a.onerror=()=>{o.error?o.error(a):o.load(a)},o.obj?a.send(JSON.stringify(o.obj)):a.send(),document.body.style.cursor=""},Ue=e=>{e.preventDefault(),window.frameElement.ownerDocument.querySelector(".v--modal-overlay").outerHTML=""},qe=e=>{const t=document.createElement("textarea");return t.innerHTML=e,t.value},Ke=()=>{const{getLocalStorageItem:e}=window;u("bookmark").disabled=!0;const t=e("gpe_gameBookmarks",{}),o=window.gameInfo.caption;t[window.gameInfo.gameid]={time:Date.now(),caption:o?qe(o):""},localStorage.setItem("gpe_gameBookmarks",JSON.stringify(t))},Ye=e=>{13===e.keyCode&&(e.preventDefault(),u("submitcaption").click())},Xe=()=>{let e=(ee.timerStart-Date.now())/1e3;try{window.timerCallback&&window.timerCallback(e)}catch(e){}e=Math.abs(e);const t=`0${Math.floor(e/60)}`.slice(-2);e=`0${Math.floor(e%60)}`.slice(-2),u("timer").childNodes[0].nodeValue=`${t}:${e}`},Fe=()=>{const{incontest:e,gameInfo:t,drawing_aborted:o,vertitle:n}=window;e&&!o&&Ve("POST","/contests/exit.json",{load:()=>alert("You have missed your contest.")}),t.drawfirst&&!o&&Ve("POST","/play/abort-start.json",{obj:{game_token:t.gameid},load:()=>alert("You have missed your Draw First game.\nIt has been aborted."),error:()=>alert("You have missed your Draw First game.\nI tried aborting it, but an error occured. :(")}),ee.timerStart=Date.now(),u("newcanvasyo").className="sandbox",window.timerCallback=()=>{},Xe(),document.title="Sandbox - Drawception",u("gamemode").innerHTML="Sandbox",u("headerinfo").innerHTML=`Sandbox with ${n}`;try{history.replaceState({},null,"/sandbox/")}catch(e){}F()},Je=()=>{const{gameInfo:e,incontest:t}=window;if(t){if(!confirm("Quit the contest? Entry coins will be lost!"))return;return u("exit").disabled=!0,void Ve("POST","/contests/exit.json",{load:()=>{u("exit").disabled=!1,window.drawing_aborted=!0,Fe(),document.location.pathname="/contests/"},error:()=>{u("exit").disabled=!1,alert("Server error. :( Try again?")}})}if(e.drawfirst){if(!confirm("Abort creating a draw first game?"))return;return u("exit").disabled=!0,void Ve("POST","/play/abort-start.json",{obj:{game_token:e.gameid},load:()=>{u("exit").disabled=!1,window.drawing_aborted=!0,Fe(),document.location.pathname="/create/"},error:()=>{u("exit").disabled=!1,alert("Server error. :( Try again?")}})}confirm("Really exit?")&&(u("exit").disabled=!0,Ve("POST","/play/exit.json",{obj:{game_token:e.gameid},load:()=>{u("exit").disabled=!1,Fe()}}))},We=e=>{e.preventDefault(),window.top.location.href="https://drawception.com/"},Qe=e=>{const t=document.implementation.createHTMLDocument("");t.body.innerHTML=e;const o=t.querySelector("draw-app")||t.querySelector("describe")||{getAttribute:()=>!1},n=e=>t.querySelector(e);return{error:(e=>!!e&&e.src)(n(".error")),gameid:o.getAttribute("game_token"),blitz:"true"===o.getAttribute(":blitz_mode"),nsfw:"true"===o.getAttribute(":nsfw"),friend:"true"!==o.getAttribute(":game_public"),drawfirst:"true"===o.getAttribute(":draw_first"),timeleft:1*o.getAttribute(":seconds"),caption:o.getAttribute("phrase"),image:o.getAttribute("img_url"),palette:o.getAttribute("theme_id"),bgbutton:"true"===o.getAttribute(":bg_layer"),playerurl:"/profile/",avatar:null,coins:"-",pubgames:"-",friendgames:"-",notifications:"-",drawinglink:(e=>!!e&&e.src)(n(".gamepanel img")),drawingbylink:(e=>!!e&&[e.textContent.trim(),e.href])(n("#main p a")),drawncaption:(e=>!!e&&e.src)(n("h1.game-title")),notloggedin:null!==n("form.form-login"),limitreached:!1,html:e}},Ze={default:["Normal","#fffdc9"],theme_thanksgiving:["Thanksgiving","#f5e9ce"],halloween:["Halloween","#444444"],theme_cga:["CGA","#ffff55"],shades_of_grey:["Grayscale","#e9e9e9"],theme_bw:["Black and white","#ffffff"],theme_gameboy:["Gameboy","#9bbc0f"],theme_neon:["Neon","#00abff"],theme_sepia:["Sepia","#ffe2c4"],theme_valentines:["Valentine's","#ffccdf"],theme_blues:["the blues","#295c6f"],theme_spring:["Spring","#ffffff"],theme_beach:["Beach","#f7dca2"],theme_beach_2:["Tide Pool","#2271a2"],theme_coty_2016:["Colors of 2016","#648589"],theme_bee:["Bee","#ffffff"],theme_coty_2017:["Colors of 2017","#5f7278"],theme_fire_ice:["Fire and Ice","#040526"],theme_coty_2018:["Canyon Sunset","#2e1b50"],theme_juice:["Juice","#fced95"],theme_tropical:["Tropical","#2f0946"],theme_grimby_grays:["Grimby Grays","#f0efeb"],theme_fury_road:["Fury Road","#893f1d"],theme_candy:["Candy","#793abd"],theme_holiday_2:["Holiday","#f6f6f6"],theme_blues_2:["Blues","#0f1328"],theme_sin_city:["Sin City","#000000"],theme_lucky_clover:["Lucky Clover","#0c442c"],theme_drawception:["D's Exclusive","#0ee446"],theme_retina_burn:["Retina Burn","#ff0b11"],theme_easter:["Easter","#ddf7a8"],theme_neapolitan:["Neapolitan","#fff7e1"]},et=()=>{const{gameInfo:e,inforum:t}=window;if(e.notloggedin)return u("start").parentNode.innerHTML=' ';e.avatar&&(u("infoavatar").src=e.avatar),u("infoprofile").href=e.playerurl,u("infocoins").innerHTML=e.coins,u("infogames").innerHTML=e.pubgames,u("infofriendgames").innerHTML=e.friendgames||0,u("infonotifications").innerHTML=e.notifications,t&&(document.querySelector(".headerright").hidden=!0)},tt=e=>{const{gameInfo:t}=window;e<1?(document.title="[TIME'S UP!] Playing Drawception",t.image||window.timesup?window.submitting||(t.image?nt():Fe()):(u("newcanvasyo").classList.add("locked"),I(),ee.timerStart+=15e3,Xe(),window.timesup=!0)):document.title=`[${`0${Math.floor(e/60)}`.slice(-2)}:${`0${Math.floor(e%60)}`.slice(-2)}] Playing Drawception`,window.alarm&&!window.playedWarningSound&&e<=(t.blitz?5:61)&&e>0&&(window.alarm.play(),window.playedWarningSound=!0)},ot=()=>{const{options:e,gameInfo:o,incontest:n,vertitle:a}=window;if(u("skip").disabled=o.drawfirst||n,u("report").disabled=o.drawfirst||n,u("exit").disabled=!1,u("start").disabled=!1,u("bookmark").disabled=o.drawfirst||n,u("options").disabled=!0,u("timeplus").disabled=n,u("submit").disabled=!1,u("headerinfo").innerHTML=`Playing with ${a}`,u("drawthis").classList.add("onlyplay"),u("emptytitle").classList.remove("onlyplay"),window.submitting=!1,window.drawing_aborted=!1,o.error)return alert(`Play Error:\n${o.error}`),Fe();if(o.limitreached)return alert("Play limit reached!"),Fe();u("gamemode").innerHTML=n?"Contest":`${(o.friend?"Friend ":"Public ")+(o.nsfw?"Not Safe For Work (18+) ":"safe for work ")+(o.blitz?"BLITZ ":"")}Game`,u("drawthis").innerHTML=o.caption||o.drawfirst&&"(Start your game!)"||"",u("tocaption").src="";const r=u("newcanvasyo");r.className="play",o.friend&&r.classList.add("friend"),u("palettechooser").className=o.friend?"":"onlysandbox",o.nsfw&&r.classList.add("nsfw"),o.blitz&&r.classList.add("blitz"),r.classList.add(o.image?"captioning":"drawing"),Z.isStroking&&T(),F();for(let e=Z.svg.childNodes.length-1;e>0;e--)Z.svg.removeChild(Z.svg.childNodes[e]);G(0),t(1),Z.unsaved=!1;const{palette:i}=o;if(o.image)u("tocaption").src=o.image.length<=30?"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAD6AQMAAAAho+iwAAAABlBMVEWAQED///94jotxAAABiklEQVR4Xu3W0UrCUBjA8eOO5CLK7VxLzDWFrjK6Eaha8FHuppfwBRJvdjlMIK/K3qA3OZBBd/UIm9UL2O2inMJBptNuog/6/h4Q2Y8J387Y2KIoiqIoiqIoiuIxXnbI5cmXSiJjD3LmFyrGY46PqVAx/HPDv9/w3wsJTTgapuDkcEIQMFxzo937S8+F5OkWI2IKymQl3yiZ6j8zYsRY6vUYDcOfGkuMknE5/aQAMczX9O+iKIrKJWuSxliQqT61hOmMucsYK6uzLWfDenF34EXhOX+s377KLCZcs1bxhNXQqnAvrExWM8vvY3amORCNsplu2nZPWKdj1tecTHZZLA97ZnjBB/XrkWIZWT+bsmTowp+7FHSnyMi7CpuMrWcwNsMMxnJzrCUbwwq/2/MLJb8lP4L2zVHJ35Bp1rE8Uc2bALoNHQvcoNG3Yf5Pm6EnHG50Ye0YmiG4V08LmWD7wmF9gJwFgoHbnZzNSDE/Co3orSB2YGsbovAgaD9vlkB/WbkbdQVWMNxR1Ddnf4eSZpHZYAAAAABJRU5ErkJggg==":o.image,u("caption").value="",u("caption").focus(),u("caption").setAttribute("maxlength",45),u("usedchars").textContent="45";else{const e=(e=>{if("theme_roulette"===e){alert("Warning: Drawception roulette didn't give a theme. ANBT will choose a random palette."),delete Q.Roulette;const e=Object.keys(Ze),t=e[e.length*Math.random()<<0];return Q.Roulette=Q[Ze[t][0]],["Roulette",Ze[t][1]]}if(e)return Ze[e.toLowerCase()]})(i);e?(Se(e[0]),b(e[1]),Z.color=[Q[e[0]][0],"eraser"],de()):(i?alert(`Error, please report! Unknown palette: '${i}'.\nAre you using the latest ANBT version?`):alert("Error, please report! Failed to extract the palette.\nAre you using the latest ANBT version?"),u("submit").disabled=!0),u("setbackground").hidden=!o.bgbutton}(e.timeoutSound&&!o.blitz||e.timeoutSoundBlitz&&o.blitz)&&(window.playedWarningSound=!1,window.alarm=new Audio(window.alarmSoundOgg),window.alarm.volume=e.timeoutSoundVolume/100),ee.timerStart=Date.now()+1e3*o.timeleft,window.timerCallback=tt,et(),window.timesup=!1,Xe()},nt=()=>{const{incontest:e,friendgameid:t}=window,o=e?"/contests/play/":`/play/${t?`${t}/`:""}`;try{location.pathname!==o&&history.replaceState({},null,o)}catch(e){}Ve("GET",`${o}?${Date.now()}`,{load:e=>{window.gameInfo=e?Qe(e):{error:"Server returned a blank response :("},ot()},error:e=>{window.gameInfo={error:`Server error: ${e.statusText}`},ot()}})},at=()=>{confirm("Report this panel?")&&Ve("POST","/play/flag.json",{obj:{game_token:window.gameInfo.gameid},load:()=>{u("report").disabled=!1,nt()}})},rt=()=>Z.unsaved&&!confirm("You haven't saved the drawing. Abandon?"),it=()=>{rt()||(u("skip").disabled=!0,Ve("POST","/play/skip.json",{obj:{game_token:window.gameInfo.gameid},load:()=>nt(),error:()=>{u("skip").disabled=!1,nt()}}))},st=()=>{rt()||(u("start").disabled=!0,nt())},dt=()=>{const{incontest:e,gameInfo:t}=window,o=u("caption").value;if(!o)return u("caption").focus(),alert("You haven't entered a caption!");window.submitting=!0,u("submitcaption").disabled=!0,Ve("POST",e?"/contests/submit-caption.json":"/play/describe.json",{obj:{game_token:t.gameid,title:o},load:e=>{try{e=JSON.parse(e)}catch(t){e={error:e}}e.error?(u("submitcaption").disabled=!1,"object"==typeof e.error?alert(`Error! Please report this data:\ngame: ${t.gameid}\n\nresponse: \n${JSON.stringify(e.error)}`):alert(e.error)):e.message?(u("submitcaption").disabled=!1,alert(e.message)):e.url&&((e=>{const{options:t,gameInfo:o}=window;if(!t.bookmarkOwnCaptions)return;const n=window.getLocalStorageItem("gpe_gameBookmarks",{});n[o.gameid]={time:Date.now(),caption:`"${e}"`,own:!0},localStorage.setItem("gpe_gameBookmarks",JSON.stringify(n))})(o),location.replace(e.url))},error:()=>{u("submitcaption").disabled=!1,alert("Server error. :( Try again?")}})},lt=()=>{const{incontest:e,gameInfo:t,options:o}=window,n=ee.timerStart-Date.now()>6e4;o.submitConfirm&&n&&!confirm("Ready to submit this drawing?")||(u("submit").disabled=!0,B(300,250,!0),o.backup&&localStorage.setItem("anbt_drawingbackup_newcanvas",Z.pngBase64),window.submitting=!0,Ve("POST",e?"/contests/submit-drawing.json":"/play/draw.json",{obj:{game_token:t.gameid,panel:Z.pngBase64},load:e=>{try{e=JSON.parse(e)}catch(t){e={error:e}}e.error?(u("submit").disabled=!1,"object"==typeof e.error?alert(`Error! Please report this data:\ngame: ${t.gameid}\n\nresponse:\n${JSON.stringify(e.error)}`):alert(e.error)):e.message?(u("submit").disabled=!1,alert(e.message)):e.url&&(window.onbeforeunload=()=>{},location.replace(e.url))},error:()=>{u("submit").disabled=!1,alert("Server error. :( Try again?")}}))},ct=()=>{let{gameInfo:e}=window;e.friend&&(u("timeplus").disabled=!0,Ve("POST","/play/exit.json",{obj:{game_token:e.gameid},load:()=>{Ve("GET",`/play/${e.gameid}/?${Date.now()}`,{load:t=>{u("timeplus").disabled=!1,e=t?Qe(t):{error:"Server returned a blank response :("},ee.timerStart=Date.now()+1e3*e.timeleft},error:()=>{u("timeplus").disabled=!1,alert("Server error. :( Try again?")}})},error:()=>{u("timeplus").disabled=!1,alert("Server error. :( Try again?")}}))},ft=()=>{u("usedchars").textContent=45-u("caption").value.length},ut=()=>{const{gameInfo:e,vertitle:t,options:o}=window;if(e.drawingbylink){const[t,n]=e.drawingbylink,a=`Drawing`;u("headerinfo").innerHTML=`${a} by ${t}`,document.title=`${t}'s drawing - Drawception`,e.drawncaption&&(u("drawthis").innerHTML=`"${e.drawncaption}"`,u("drawthis").classList.remove("onlyplay"),u("emptytitle").classList.add("onlyplay")),o.autoplay&&O()}else u("headerinfo").innerHTML=`Sandbox with ${t}`,u("drawthis").classList.add("onlyplay");et()};window.needToGoDeeper=()=>{const{options:e,insandbox:t,panelid:o}=window;if(window.onerror=(e,t,o)=>{e.toString().includes("periodsToSeconds")||e.toString().match(/script error/i)||alert(o?`${e}\nline: ${o}`:e)},e.newCanvasCSS){const t=document.getElementsByTagName("head")[0]||document.documentElement,o=document.createElement("style");o.type="text/css";const n=document.createTextNode(e.newCanvasCSS);o.appendChild(n),t.appendChild(o)}if(e.enableWacom){const t=document.createElement("object"),o=u("wacomContainer");t.setAttribute("id","wacom"),t.setAttribute("type","application/x-wacomtabletplugin"),t.setAttribute("width","1"),t.setAttribute("height","1"),o.appendChild(t),e.fixTabletPluginGoingAWOL&&(()=>{const e=u("wacom"),t=u("wacomContainer");window.onblur=()=>{1===t.childNodes.length&&t.removeChild(e)},window.onfocus=()=>{0===t.childNodes.length&&t.appendChild(e)}})()}if((()=>{const{options:e,inforum:t}=window;if(t){u("quit").addEventListener("click",We);const e=document.createElement("button");e.href="/",e.setAttribute("class","submit exit"),e.title="Exit",e.textContent="Exit",e.addEventListener("click",Ue),u("submit").parentNode.insertBefore(e,u("submit").nextSibling)}u("exit").addEventListener("click",Je),u("skip").addEventListener("click",it),u("start").addEventListener("click",st),u("report").addEventListener("click",at),u("bookmark").addEventListener("click",Ke),u("submit").addEventListener("click",lt),u("submitcaption").addEventListener("click",dt),e.enterToCaption&&u("caption").addEventListener("keydown",Ye),u("caption").addEventListener("change",ft),u("caption").addEventListener("keydown",ft),u("caption").addEventListener("input",ft),u("timeplus").addEventListener("click",ct)})(),t){if(o)Ve("GET",`/panel/drawing/${o}/-/`,{load:e=>{window.gameInfo=Qe(e),A(`${window.gameInfo.drawinglink}?anbt`),ut()},error:()=>{alert("Error loading the panel page. Please try again.")}});else if(Ve("GET","/sandbox/",{load:e=>{window.gameInfo=Qe(e),ut()},error:()=>{}}),e.backup){const e=localStorage.getItem("anbt_drawingbackup_newcanvas");e&&(S(J(e.substr(22)).buffer),localStorage.removeItem("anbt_drawingbackup_newcanvas"))}}else u("newcanvasyo").className="play",nt();/iPad|iPhone/.test(navigator.userAgent)&&(Z.fastUndoLevels=3),window.$=()=>{throw alert("Some additional script conflicts with ANBT new canvas, please disable it."),window.$=null,new Error("Script conflict with ANBT new canvas")}},window.options||(window.options={}),Z.bindContainer(u("svgContainer")),u("svgContainer").addEventListener("mousedown",Pe),u("svgContainer").addEventListener("mousemove",_e),u("svgContainer").addEventListener("touchstart",Ge),u("svgContainer").addEventListener("mouseleave",Me),u("svgContainer").addEventListener("contextmenu",De),u("import").addEventListener("click",me),u("export").addEventListener("click",he),u("imgur").addEventListener("click",be),document.querySelectorAll(".brush").forEach((e,t)=>{e.classList.add(`size-${ee.brushSizes[t]}`),e.addEventListener("mousedown",te),e.addEventListener("click",te)}),u("colors").querySelectorAll("b").forEach(e=>{e.addEventListener("mousedown",le),e.addEventListener("touchend",le),e.addEventListener("contextmenu",Le)}),u("setbackground").addEventListener("click",ae),u("undo").addEventListener("click",ie),u("redo").addEventListener("click",oe),u("trash").addEventListener("click",re),V(ke),u("knob").addEventListener("mousedown",ye),u("knob").addEventListener("touchstart",ye),u("seekbar").addEventListener("mousedown",ye),u("seekbar").addEventListener("touchstart",ye),u("play").addEventListener("mousedown",ce),u("play").addEventListener("touchstart",ce),u("palettename").addEventListener("mousedown",xe),u("palettename").addEventListener("touchend",xe),u("popupclose").addEventListener("click",Ee),document.addEventListener("keyup",pe),document.addEventListener("keydown",ue),window.addEventListener("contextmenu",Re),window.addEventListener("error",He),window.addEventListener("beforeunload",ze),ee.timerStart=Date.now(),setInterval(Xe,500),window.anbtReady&&window.anbtReady()}();
diff --git a/build/newcanvas/style.css b/build/newcanvas/style.css
new file mode 100644
index 0000000..0595e62
--- /dev/null
+++ b/build/newcanvas/style.css
@@ -0,0 +1 @@
+html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}body{margin:0;background:#ccc;font-family:"Nunito", sans-serif;background:#555}.noselect{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#newcanvasyo{background:#555}#newcanvasyo.play .onlysandbox,#newcanvasyo.play .onlyfriend,#newcanvasyo.friend .no-friend,#newcanvasyo.nsfw .no-nsfw,#newcanvasyo.sandbox .onlycaption,#newcanvasyo.drawing .onlycaption,#newcanvasyo.captioning .onlydraw,#newcanvasyo.locked .onlyunlocked,#newcanvasyo.sandbox .onlyplay{display:none}#myheader{font-size:15pt;line-height:49px;height:49px;border-bottom:1px solid black;background:#333;text-align:center;color:#999;overflow:hidden;display:block}#myheader img{vertical-align:middle}@media screen and (max-width: 999px){#myheader{font-size:11pt}}#headercont{max-width:1280px;margin:auto}.headerleft{float:left;margin-left:50px}.headerright{float:right;margin-right:50px}#infoavatar{border:1px solid #555}.headerbutton{font-size:10.5pt;font-weight:bold;padding:4px 10px;background:#2e2e2e;vertical-align:middle;text-decoration:none;cursor:pointer}.headerbutton.active{background:#248;color:#eee}.headerbutton.active:hover{background:#236}.headerbutton img{margin:0 3px}a{color:#999}a.prominent{color:#7af}a.prominent:hover{color:#9ff}#menu{display:none;position:absolute;background:#666;color:#ddd;z-index:2;border:2px solid #777;box-shadow:5px 5px 5px rgba(0,0,0,0.5);list-style-type:none;padding:0;margin:0;font-size:13pt;text-align:left}#menu.open{display:block}#menu li{line-height:49px;height:49px;padding:0 10px;cursor:pointer}#menu li:hover{background:#777}#menu hr{margin:5px;border:1px solid #444}hr{border:1px solid #888}#noscriptwarn{color:#800;padding:10px;font-size:15pt;background:#faa;text-align:center}#drawthis{padding:4px;color:#fff;font-size:26pt;font-family:"Yanone Kaffeesatz";text-align:center;vertical-align:middle;display:table-cell;width:1%}#drawthis::before{color:#aaa;font-size:14pt;font-family:"Nunito";content:"Draw this:";padding-right:1em;width:1%;text-align:right;vertical-align:middle;margin-left:-10%}.captioning #drawthis:before{content:"Describe this drawing:";padding-right:0;width:100%;text-align:center;margin-left:0}.captioning #drawingtocaption{display:block}#emptytitle{height:10px}#bl{width:600px;margin:auto;position:relative}#toolpane{text-align:center;padding:0 10px}@media screen and (min-width: 1000px){#toolpane{width:180px;height:500px;position:absolute;margin-left:-200px}}@media screen and (max-width: 999px){#toolpane{width:580px;margin:auto}}@media screen and (max-width: 999px){#toolpane:after{display:block;content:"";clear:both}}#infopanel{text-align:center;padding:0 10px}@media screen and (min-width: 1000px){#infopanel{width:180px;height:500px;position:absolute;margin-left:600px;top:0}}@media screen and (max-width: 999px){#infopanel{width:580px;margin:40px 0}}.panel{border:2px solid #888;border-radius:15px;border-top:none;background:#444;margin:3px 0}#timer{font-size:26pt;color:#aaa;width:176px;float:left}@media screen and (max-width: 999px){#timer{float:right;width:260px}}.blitz #timer{border-color:#dda;color:#dda;background:#982}.locked #timer{border-color:#faa;color:#faa;background:#744}.locked #lockwarn{display:block}#timeplus{background:url("/img/icon-time_plus_60.png") center no-repeat;width:16px;height:16px;vertical-align:middle;padding:10px;margin-left:3px;border-radius:3px;cursor:pointer}#timeplus:hover{background-color:#222}#palettechooser{display:none;position:absolute;width:450px;background:#666;color:#ddd;z-index:1;-webkit-columns:3;-moz-columns:3;columns:3;-webkit-column-gap:0;-moz-column-gap:0;column-gap:0;border:2px solid #777;box-shadow:5px 5px 5px rgba(0,0,0,0.5)}#palettechooser.open{display:block}#palettechooser div{padding:10px 0 20px;cursor:pointer;break-inside:avoid}#palettechooser div:hover{background-color:#777;color:#eee}#colors{width:100px;float:left}#colors b{float:left;display:block;width:50px;height:44px;line-height:42px;cursor:pointer;counter-increment:color}#colors b.hint::after{content:"";position:absolute;border-radius:50%;margin:32px 2px;width:5px;height:5px;background-color:white;border:1px solid #000}#colors b.hint::before{opacity:0.6}#colors b::before{font-size:10pt;opacity:0.3;color:#000;content:counter(color)}#colors b:hover::before{opacity:0.4;color:#fff}#colors b:nth-child(10){counter-reset:color 1}#colors b:nth-child(n+10){counter-increment:color}#colors b:nth-child(n+10)::before{content:"↑" counter(color)}#colors #eraser::before{content:"eraser"}#colors.setbackground b::before{content:"BG " counter(color)}#colors.setbackground b:nth-child(n+10)::before{content:"BG↑" counter(color)}#colors.setbackground #eraser::before{content:"cancel"}#colors div{clear:both}@media screen and (max-width: 999px){#colors{width:300px;margin-left:5px;clear:left;float:left}}#palettename{color:#aaa;font-size:10pt;padding:0 5px;cursor:pointer}#palettename:hover{color:#ccc}#tools{width:68px;float:right;padding:5px 0}#tools hr{width:93%;margin:8px auto}@media screen and (max-width: 999px){#tools button{margin:0}}@media screen and (max-width: 999px){#tools{width:260px}}#tools button,#openmenu{width:55px;height:44px;vertical-align:top;margin:3px 0;padding:0}#brush0,#brush1,#brush2,#brush3,#setbackground,#undo,#redo,#trash,#play,#knob,#eraser,.eraser,#openmenu{background-image:url("data:image/png;base64,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")}#brush0{background-position:-2px -8px}#brush0.sel{background-position:-2px -68px}#brush1{background-position:-62px -8px}#brush1.sel{background-position:-62px -68px}#brush2{background-position:-122px -8px}#brush2.sel{background-position:-122px -68px}#brush3{background-position:-182px -8px}#brush3.sel{background-position:-182px -68px}#setbackground{background-position:-242px -8px}#setbackground.sel{background-position:-242px -68px}#undo{background-position:-302px -8px}#redo{background-position:-362px -8px}#trash{background-position:-422px -8px}#eraser{background-position:-365px -68px;background-color:pink}.eraser{background-position:-425px -68px;background-color:pink}#openmenu{background-position:-425px -68px;background-color:#999}#openmenu::after{content:"\25BC";color:#fff;opacity:0.6}#openmenu:hover{background-color:#57a}#openmenu:active{background-color:#555}button,#play{border:none;border-radius:4px;background-color:#eee}button:hover,#play:hover{background-color:#acf}button:active,#play:active{background-color:#aaa}button:disabled{background-color:#aaa}#primary,#secondary{display:inline-block;width:50px;border-radius:0 0 0 13px;color:#fff}@media screen and (max-width: 999px){#primary,#secondary{width:150px}}#secondary{border-radius:0 0 13px 0}#indicator{display:flex}#gamemode{font-size:13pt;color:#aaa;width:176px;float:left}.nsfw #gamemode{border-color:#faa;color:#faa;background:#744}.nsfw #drawthis{background:#744}#gamebuttons{font-size:13pt;color:#aaa;width:176px;float:left;padding:5px 0}#gamebuttons button{padding:10px;margin:3px 0;font-size:10pt}@media screen and (max-width: 999px){#gamebuttons{width:390px;margin-left:5px}}.submit{font-size:16pt;width:180px;height:50px;background-color:#6d6}.submit:hover{background-color:#1b7}@media screen and (min-width: 1000px){.submit{position:absolute;bottom:10px;left:10px;width:180px;height:50px}}.guidelines{color:#fff;font-size:10pt}.guidelines ul{margin:0;padding:0 1em;text-align:left}.guidelines li{color:#bbb}#seekbar{width:530px;height:0px;margin:20px 50px 0;background:#333;border:10px solid #333;border-radius:10px;cursor:pointer}#knob{position:absolute;width:48px;height:38px;margin-top:-19px;margin-left:492px;border-radius:10px;background-color:#fbb;background-position:-305px -71px}#knob.smooth{transition:margin-left 0.1s ease-out}#knob:hover{background-color:#fdd}#play{background-position:-486px -11px;position:absolute;width:48px;height:38px;margin:-19px -59px;border-radius:4px}#play.pause{background-position:-546px -11px}#wacomContainer{position:absolute;top:-10px;left:-10px}@media screen and (max-width: 999px){.hideonsmall{display:none}}.loadingbg{background:#fffdc9 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAVCAQAAAAJQsNPAAAAkUlEQVR4Xu2WgQbAMBBD9/8/vQ14KpC4UneqYurZ2eNGnnfouWLdsjwce59ikFCdk75ricGIQSEmhekcvRfE8s8jMJ9CdI7camLGYoQ0XzNjJukgRsaJkWFiKqhiLf8x2DAx6EExTV0Mns/sIPZnnBinJJb1h4p21BXrzSOpTZVF3VGCN4lB07U53+6H5Yr1zAdssNOdvbG/lgAAAABJRU5ErkJggg==") center no-repeat}#svgContainer{width:600px;height:500px;margin:auto;cursor:crosshair;touch-action:pinch-zoom}#svgContainer.hidecursor{cursor:none}#svgContainer.loading *{display:none}#svgContainer canvas,#svgContainer svg{position:absolute}#drawingtocaption{display:none;width:550px;height:400px;padding:50px 25px;margin:auto;background:#484848;text-align:center}#tocaption{width:300px;height:250px}#caption{font-family:"Nunito", sans-serif;margin-top:100px;width:100%;font-size:20pt}#usedchars{text-align:right;opacity:0.4}#lockwarn{display:none;position:absolute;width:500px;height:400px;padding:50px;opacity:0.8;background:#aaa;color:#a00;text-align:center;z-index:1;font-size:35pt;text-shadow:1px 1px 1px #fff, -1px -1px 1px #fff, -1px 1px 1px #fff, 1px -1px 1px #fff}#popup{display:none;position:absolute;width:400px;height:300px;margin:50px;padding:50px;opacity:0.95;background:#444;color:#ddd;border-radius:15px;text-align:center;z-index:1;font-size:20pt}#popup.show{display:block}#popuptitle{color:#999;font-size:25pt;margin:10px}#popupclose{display:block;position:absolute;width:50px;height:50px;background:#c44;color:#ddd;right:0;top:0;text-align:center;border-radius:13px;cursor:pointer}#popupclose:hover{background:#e77;color:#eee}#popupclose::before{content:"\d7";font-size:30pt;line-height:40px}
diff --git a/copypost.py b/copypost.py
index 940d479..5d4d089 100644
--- a/copypost.py
+++ b/copypost.py
@@ -1,6 +1,6 @@
import re,subprocess
-template = """#### [Script updated to %s](https://github.com/grompe/Drawception-ANBT#drawception-anbt-):
+template = """#### [Script updated to %s](https://github.com/EnderDragonneau/Drawception-ANBT#drawception-anbt-):
%s
diff --git a/drawception-anbt.user.js b/drawception-anbt.user.js
deleted file mode 100644
index aeb6393..0000000
--- a/drawception-anbt.user.js
+++ /dev/null
@@ -1,2847 +0,0 @@
-// ==UserScript==
-// @name Drawception ANBT
-// @author Grom PE
-// @namespace http://grompe.org.ru/
-// @version 1.156.2019.06
-// @description Enhancement script for Drawception.com - Artists Need Better Tools
-// @downloadURL https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/drawception-anbt.user.js
-// @match http://drawception.com/*
-// @match https://drawception.com/*
-// @grant none
-// @run-at document-start
-// @license Public domain
-// ==/UserScript==
-const wrapped = () => {
- const SCRIPT_VERSION = '1.156.2019.06'
- const NEWCANVAS_VERSION = 53 // Increase to update the cached canvas
- const SITE_VERSION = 'f6b35cce' // Last seen site version
-
- // == DEFAULT OPTIONS ==
-
- const options = {
- enableWacom: 0, // Whether to enable Wacom plugin and thus pressure sensitivity support
- fixTabletPluginGoingAWOL: 1, // Fix pressure sensitivity disappearing in case of stupid/old Wacom plugin
- hideCross: 0, // Whether to hide the cross when drawing
- enterToCaption: 0, // Whether to submit caption by pressing Enter
- pressureExponent: 0.5, // Smaller = softer tablet response, bigger = sharper
- brushSizes: [2, 5, 12, 35], // Brush sizes for choosing via keyboard
- chatAutoConnect: 0, // Whether to automatically connect to the chat
- backup: 1,
- timeoutSound: 0,
- timeoutSoundBlitz: 0,
- timeoutSoundVolume: 100,
- newCanvas: 1,
- proxyImgur: 0,
- rememberPosition: 0,
- ajaxRetry: 1,
- localeTimestamp: 0,
- autoplay: 1, // Whether to automatically start playback of a recorded drawing
- submitConfirm: 1,
- smoothening: 1,
- autoBypassNSFW: 0,
- colorNumberShortcuts: 1,
- colorUnderCursorHint: 1,
- bookmarkOwnCaptions: 0,
- colorDoublePress: 0,
- markStalePosts: 1,
- newCanvasCSS: '',
- forumHiddenUsers: '',
- maxCommentHeight: 1000,
- useOldFont: true,
- useOldFontSize: true,
- markdownTools: 1,
- anbtDarkMode: 1
- }
-
- /*
- == HOW TO USE ==
- - Chrome/Iron: (Recommended: all features, best performance)
- - add the script in Tampermonkey addon
- - or open URL: chrome://extensions then drag and drop this .user.js file on it
- - Firefox: add the script in Greasemonkey addon
- - Opera 12.x: add the script in "site properties"
-
- == FEATURES ==
- General
- - Menu buttons in the header for easier access
- - No temptation to judge
- - An embedded chat
- - Automatically retry failed requests to reduce annoying error messages
- Canvas:
- - Completely new drawing canvas with ability to record and display the drawing process
- View game
- - Add reverse panels button
- - Add "like all" button
- - Track new comments
- - Show when the game was started
- - Ability to favorite panels
- Play
- - Much faster skipping
- - Play modes for those who only caption or only draw
- - Enter pressed in caption mode submits the caption
- - Ability to bookmark games without participating
- - Show your panel position and track changes in unfinished games list
- Forum
- - Better-looking timestamps with correct timezone
- - Clickable drawing panels
- - Clickable links
- - Show and highlight direct links to forum posts
- */
- //let __DEBUG__
- //let prestoOpera
- let username
- let userid
- //let playMode = localStorage.getItem('gpe_playMode')
- let inDark = localStorage.getItem('gpe_inDark')
-
- //playMode = playMode === null ? 0 : parseInt(playMode, 10)
- inDark = inDark === null ? 0 : parseInt(inDark, 10)
-
- //const MODE_ALL = 0
- //const MODE_CAPTION_ONLY = 1
- //const MODE_DRAW_ONLY = 2
- //const availablePlayModes = ['Mode: captions and drawings', 'Mode: only make captions', 'Mode: only draw']
- const alarmSoundOgg = 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABnHAAAAAAAAHQUSFoBHgF2b3JiaXMAAAAAAUSsAAAAAAAAYG0AAAAAAADJAU9nZ1MAAAAAAAAAAAAAZxwAAAEAAABq35G0DxD/////////////////NQN2b3JiaXMAAAAAAAAAAAEFdm9yYmlzH0JDVgEAAAEAFGNWKWaZUpJbihlzmDFnGWPUWoolhBRCKKVzVlurKbWaWsq5xZxzzpViUilFmVJQW4oZY1IpBhlTEltpIYQUQgehcxJbaa2l2FpqObacc62VUk4ppBhTiEromFJMKaQYU4pK6Jxz0DnmnFOMSgg1lVpTyTGFlFtLKXROQgephM5SS7F0kEoHJXRQOms5lRJTKZ1jVkJquaUcU8qtpphzjIHQkFUAAAEAwEAQGrIKAFAAABCGoSiKAoSGrAIAMgAABOAojuIokiI5kmM5FhAasgoAAAIAEAAAwHAUSZEUy9EcTdIszdI8U5ZlWZZlWZZlWZZd13VdIDRkFQAAAQBAKAcZxRgQhJSyEggNWQUAIAAAAIIowxADQkNWAQAAAQAIUR4h5qGj3nvvEXIeIeYdg9577yG0XjnqoaTee++99x5777n33nvvkWFeIeehk9577xFiHBnFmXLee+8hpJwx6J2D3nvvvfeec+451957752j3kHpqdTee++Vk14x6Z2jXnvvJdUeQuqlpN5777333nvvvffee++9955777333nvvrefeau+9995777333nvvvffee++9995777333nvvgdCQVQAAEAAAYRg2iHHHpPfae2GYJ4Zp56T3nnvlqGcMegqx9557773X3nvvvffeeyA0ZBUAAAgAACGEEFJIIYUUUkghhhhiyCGHHIIIKqmkoooqqqiiiiqqLKOMMsook4wyyiyjjjrqqMPOQgoppNJKC620VFtvLdUehBBCCCGEEEIIIYQQvvceCA1ZBQCAAAAwxhhjjEEIIYQQQkgppZRiiimmmAJCQ1YBAIAAAAIAAAAsSZM0R3M8x3M8x1M8R3RER3RER5RESbRETfREUTRFVbRF3dRN3dRNXdVN27VVW7ZlXdddXddlXdZlXdd1Xdd1Xdd1Xdd1XbeB0JBVAAAIAABhkEEGGYQQQkghhZRSijHGGHPOOSA0ZBUAAAgAIAAAAEBxFEdxHMmRJMmyLM3yLM8SNVMzNVNzNVdzRVd1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'
-
- const GM_addStyle = css => {
- const parent = document.getElementsByTagName('head')[0] || document.documentElement,
- style = document.createElement('style'),
- textNode = document.createTextNode(css)
- style.type = 'text/css'
- style.appendChild(textNode)
- parent.appendChild(style)
- }
- /*
- canvas integration todo:
- - autoskipping captions/drawings
- */
-
- // Executed on completely empty page.
- const setupNewCanvas = (insandbox, url, origpage) => {
- const canvasHTML = localStorage.getItem('anbt_canvasHTML')
- const canvasHTMLver = localStorage.getItem('anbt_canvasHTMLver')
- if (!canvasHTML || canvasHTMLver < NEWCANVAS_VERSION || canvasHTML.length < 10000) {
- const req = new XMLHttpRequest()
- req.open('GET', 'https://api.github.com/repos/EnderDragonneau/Drawception-ANBT/contents/newcanvas_embedding.html')
- req.setRequestHeader('Accept', 'application/vnd.github.3.raw')
- req.onload = function() {
- if (this.responseText.length < 10000) {
- alert(`Error: instead of new canvas code, got this response from GitHub:\n${this.responseText}`)
- location.pathname = '/'
- } else {
- localStorage.setItem('anbt_canvasHTML', this.responseText)
- localStorage.setItem('anbt_canvasHTMLver', NEWCANVAS_VERSION)
- setupNewCanvas(insandbox, url)
- }
- }
- req.onerror = () => {
- alert('Error loading the new canvas code. Please try again.')
- location.pathname = '/'
- }
- req.send()
- return
- }
- const inforum = url.match(/forums\//)
- const friendgameid = url.match(/play\/(.+)\//) // Save friend game id if any
- const panelid = url.match(/sandbox\/#?([^\/]+)/)
- const incontest = url.match(/contests\/play\//) && document.getElementById('canvas-holder') // Handle drawing contests only
- const vertitle = `ANBT v${SCRIPT_VERSION}`
-
- // Disable built-in safety warning
- if (incontest) window.onbeforeunload = () => {}
-
- // Show normal address
- const normalurl = insandbox && !inforum ? `/sandbox/${panelid ? `#${panelid[1]}` : ''}` : incontest ? '/contests/play/' : inforum ? url.match(/\/forums\/?.+/) : `/play/${friendgameid ? `${friendgameid[1]}/` : ''}`
- try {
- if (location.pathname + location.hash !== normalurl) history.pushState({}, document.title, normalurl)
- } catch (e) {}
-
- if (inforum) {
- if (document.querySelector('.v--modal-overlay')) document.querySelector('.v--modal-overlay').outerHTML = ''
- const div = document.querySelector('.wrapper').children[1]
- const iframe = document.createElement('iframe')
- const modalOverlay = document.createElement('div')
- modalOverlay.setAttribute('class', 'v--modal-overlay')
- iframe.id = 'iframe'
- iframe.setAttribute('class', 'v--modal-background-click')
- modalOverlay.appendChild(iframe)
- div.appendChild(modalOverlay)
- const iframeContent = iframe.contentWindow
- iframeContent.document.open()
- iframeContent.anbtReady = () => {
- iframeContent.inforum = inforum
- iframeContent.insandbox = insandbox
- iframeContent.incontest = incontest
- iframeContent.options = options
- iframeContent.alarmSoundOgg = alarmSoundOgg
- iframeContent.vertitle = vertitle
- }
- iframeContent.document.write(canvasHTML)
- iframeContent.document.close()
- let iframeFinish = setInterval(() => {
- if (typeof document.getElementById('iframe').contentWindow.ID === 'undefined') return
- const script = document.createElement('script')
- script.textContent = '(' + needToGoDeeper.toString() + ')()'
- iframeContent.document.body.appendChild(script)
- clearInterval(iframeFinish)
- }, 100)
- return
- }
- document.open()
- window.anbtReady = () => {
- if (friendgameid) window.friendgameid = friendgameid[1]
- if (panelid) window.panelid = panelid[1]
- window.inforum = inforum
- window.insandbox = insandbox
- window.incontest = incontest
- window.options = options
- window.alarmSoundOgg = alarmSoundOgg
- window.vertitle = vertitle
- // Vue.js makes current page too different to reuse
- //if (origpage) window.origpage = origpage;
-
- const script = document.createElement('script')
- script.textContent = `(${needToGoDeeper.toString()})()`
- document.body.appendChild(script)
- }
- document.write(canvasHTML)
- document.close()
- }
-
- // To be inserted on new canvas page. No jQuery!
- const needToGoDeeper = () => {
- const ajax = (type, url, params) => {
- const req = new XMLHttpRequest()
- req.open(type, url)
- if (params.header) req.setRequestHeader(params.header[0], params.header[1])
- params.retry = 5
- req.timeout = 15000
- req.ontimeout = () => {
- if (params.retry > 0) {
- if (!options.retryEnabled) return
- document.body.style.cursor = 'progress'
- params.retry--
- ajax(type, url, params)
- } else {
- document.body.style.cursor = ''
- params.error()
- }
- }
- req.onload = () => {
- if (url === '/play/skip.json' && req.error === 'Sorry, but we couldn\u0027t find your current game.') {
- location.reload()
- return
- }
- if (url === '/play/exit.json' && req.error === 'Sorry, but we couldn\u0027t find your current game.') {
- location.pathname = '/'
- return
- }
- params.load(req.responseText)
- }
- req.onerror = () => {
- if (params.error) {
- params.error(req)
- } else {
- params.load(req)
- }
- }
- if (params.obj) {
- req.send(JSON.stringify(params.obj))
- } else {
- req.send()
- }
- document.body.style.cursor = ''
- return
- }
- const extractInfoFromHTML = html => {
- const doc = document.implementation.createHTMLDocument('')
- doc.body.innerHTML = html
- let el
- let drawapp = doc.querySelector('draw-app') || doc.querySelector('describe')
- if (!drawapp)
- drawapp = {
- getAttribute() {
- return false
- }
- }
-
- const getel = query => {
- el = doc.querySelector(query)
- return el
- }
- return {
- error: getel('.error') ? el.textContent.trim() : false,
- gameid: drawapp.getAttribute('game_token'),
- blitz: drawapp.getAttribute(':blitz_mode') == 'true',
- nsfw: drawapp.getAttribute(':nsfw') == 'true',
- friend: drawapp.getAttribute(':game_public') != 'true',
- drawfirst: drawapp.getAttribute(':draw_first') == 'true',
- timeleft: drawapp.getAttribute(':seconds') * 1,
- caption: drawapp.getAttribute('phrase'),
- image: drawapp.getAttribute('img_url'),
- palette: drawapp.getAttribute('theme_id'),
- bgbutton: drawapp.getAttribute(':bg_layer') == 'true',
- playerurl: '/profile/',
- avatar: null,
- coins: '-',
- pubgames: '-',
- friendgames: '-',
- notifications: '-',
- drawinglink: getel('.gamepanel img') ? el.src : false,
- drawingbylink: getel('#main p a') ? [el.textContent.trim(), el.href] : false,
- drawncaption: getel('h1.game-title') ? el.textContent.trim() : false,
- notloggedin: getel('form.form-login') != null,
- limitreached: false, // ??? appears to be redirecting to /play/limit/ which gives "game not found" error
- html
- }
- }
-
- const getParametersFromPlay = () => {
- let url = window.incontest ? '/contests/play/' : '/play/'
- if (window.friendgameid) {
- url += `${window.friendgameid}/`
- window.friendgameid = false
- }
- try {
- if (location.pathname != url) history.replaceState({}, null, url)
- } catch (e) {}
- if (window.origpage) {
- window.gameinfo = extractInfoFromHTML(window.origpage)
- handlePlayParameters()
- window.origpage = null
- return
- }
- // On Firefox, requesting "/play/" url would immediately return a cached error.
- // Firefox, WTF? So we use cache-busting here.
- ajax('GET', `${url}?${Date.now()}`, {
- load: x => {
- if (x === '') {
- window.gameinfo = {
- error: 'Server returned a blank response :('
- }
- } else {
- window.gameinfo = extractInfoFromHTML(x)
- }
- handlePlayParameters()
- },
- error: x => {
- window.gameinfo = {
- error: `Server error: ${x.statusText}`
- }
- handlePlayParameters()
- }
- })
- }
-
- const exitToSandbox = () => {
- if (window.incontext && !window.drawing_aborted) {
- ajax('POST', '/contests/exit.json', {
- load: () => alert('You have missed your contest.')
- })
- }
- if (window.gameinfo.drawfirst && !window.drawing_aborted) {
- ajax('POST', '/play/abort-start.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: () => alert('You have missed your Draw First game.\nIt has been aborted.'),
- error: () => alert('You have missed your Draw First game.\nI tried aborting it, but an error occured. :(')
- })
- }
- timerStart = Date.now()
- ID('newcanvasyo').className = 'sandbox'
- timerCallback = () => {}
- updateTimer()
- document.title = 'Sandbox - Drawception'
- ID('gamemode').innerHTML = 'Sandbox'
- ID('headerinfo').innerHTML = `Sandbox with ${vertitle}`
- try {
- history.replaceState({}, null, '/sandbox/')
- } catch (e) {}
- anbt.Unlock()
- }
-
- const handleCommonParameters = () => {
- if (gameinfo.notloggedin) {
- ID('start').parentNode.innerHTML = ' '
- return
- }
- if (gameinfo.avatar) {
- ID('infoavatar').src = gameinfo.avatar
- }
- ID('infoprofile').href = gameinfo.playerurl
- ID('infocoins').innerHTML = gameinfo.coins
- ID('infogames').innerHTML = gameinfo.pubgames
- ID('infofriendgames').innerHTML = gameinfo.friendgames || 0
- ID('infonotifications').innerHTML = gameinfo.notifications
- if (inforum) {
- document.querySelector('.headerright').hidden = true
- }
- }
-
- const handleSandboxParameters = () => {
- if (gameinfo.drawingbylink) {
- const playername = gameinfo.drawingbylink[0]
- const playerlink = gameinfo.drawingbylink[1]
- const replaylink = `Drawing`
- ID('headerinfo').innerHTML = `${replaylink} by ${playername}`
- document.title = `${playername}'s drawing - Drawception`
- if (gameinfo.drawncaption) {
- ID('drawthis').innerHTML = `"${gameinfo.drawncaption}"`
- ID('drawthis').classList.remove('onlyplay')
- ID('emptytitle').classList.add('onlyplay')
- }
- if (options.autoplay) anbt.Play()
- } else {
- ID('headerinfo').innerHTML = `Sandbox with ${vertitle}`
- ID('drawthis').classList.add('onlyplay')
- }
- handleCommonParameters()
- }
-
- const handlePlayParameters = () => {
- const info = window.gameinfo
- ID('skip').disabled = info.drawfirst || window.incontest
- ID('report').disabled = info.drawfirst || window.incontest
- ID('exit').disabled = false
- ID('start').disabled = false
- ID('bookmark').disabled = info.drawfirst || window.incontest
- ID('options').disabled = true // Not implemented yet!
- ID('timeplus').disabled = window.incontest
- ID('submit').disabled = false
- ID('headerinfo').innerHTML = `Playing with ${vertitle}`
- ID('drawthis').classList.add('onlyplay')
- ID('emptytitle').classList.remove('onlyplay')
- window.submitting = false
- window.drawing_aborted = false
- if (info.error) {
- alert(`Play Error:\n${info.error}`)
- return exitToSandbox()
- }
- if (info.limitreached) {
- alert('Play limit reached!')
- return exitToSandbox()
- }
- if (window.incontest) {
- ID('gamemode').innerHTML = 'Contest'
- } else {
- ID('gamemode').innerHTML = `${(info.friend ? 'Friend ' : 'Public ') + (info.nsfw ? 'Not Safe For Work (18+) ' : 'safe for work ') + (info.blitz ? 'BLITZ ' : '')}Game`
- }
- ID('drawthis').innerHTML = info.caption || (info.drawfirst && '(Start your game!)') || ''
- ID('tocaption').src = ''
- const newcanvas = ID('newcanvasyo')
- newcanvas.className = 'play'
- if (info.friend) newcanvas.classList.add('friend')
- ID('palettechooser').className = info.friend ? '' : 'onlysandbox'
- if (info.nsfw) newcanvas.classList.add('nsfw')
- if (info.blitz) newcanvas.classList.add('blitz')
- newcanvas.classList.add(info.image ? 'captioning' : 'drawing')
- // Clear
- if (anbt.isStroking) anbt.StrokeEnd()
- anbt.Unlock()
- for (let i = anbt.svg.childNodes.length - 1; i > 0; i--) {
- const el = anbt.svg.childNodes[i]
- anbt.svg.removeChild(el)
- }
- anbt.Seek(0)
- anbt.MoveSeekbar(1)
- anbt.unsaved = false
- const palettemap = {
- default: ['Normal', '#fffdc9'],
- theme_thanksgiving: ['Thanksgiving', '#f5e9ce'],
- halloween: ['Halloween', '#444444'],
- theme_cga: ['CGA', '#ffff55'],
- shades_of_grey: ['Grayscale', '#e9e9e9'],
- theme_bw: ['Black and white', '#ffffff'],
- theme_gameboy: ['Gameboy', '#9bbc0f'],
- theme_neon: ['Neon', '#00abff'],
- theme_sepia: ['Sepia', '#ffe2c4'],
- theme_valentines: ["Valentine's", '#ffccdf'],
- theme_blues: ['the blues', '#295c6f'],
- theme_spring: ['Spring', '#ffffff'],
- theme_beach: ['Beach', '#f7dca2'],
- theme_beach_2: ['Tide Pool', '#2271a2'],
- theme_coty_2016: ['Colors of 2016', '#648589'],
- theme_bee: ['Bee', '#ffffff'],
- theme_coty_2017: ['Colors of 2017', '#5f7278'],
- theme_fire_ice: ['Fire and Ice', '#040526'],
- theme_coty_2018: ['Canyon Sunset', '#2e1b50'],
- theme_juice: ['Juice', '#fced95'],
- theme_tropical: ['Tropical', '#2f0946'],
- theme_grimby_grays: ['Grimby Grays', '#f0efeb'],
- theme_fury_road: ['Fury Road', '#893f1d'],
- theme_candy: ['Candy', '#793abd'],
- theme_holiday_2: ['Holiday', '#f6f6f6'],
- theme_blues_2: ['Blues', '#0f1328'],
- theme_sin_city: ['Sin City', '#000000'],
- theme_lucky_clover: ['Lucky Clover', '#0c442c'],
- theme_drawception: ["D's Exclusive", '#0ee446'],
- theme_retina_burn: ['Retina Burn', '#ff0b11'],
- theme_easter: ['Easter', '#ddf7a8'],
- theme_neapolitan: ['Neapolitan', '#fff7e1']
- }
- const pal = info.palette
- let paldata
- if (!info.image) {
- // Drawing
- if (pal == 'theme_roulette') {
- // Since site update, the game reports already chosen palette,
- // but apparently this still happens sometimes. ???
- alert("Warning: Drawception roulette didn't give a theme. ANBT will choose a random palette.")
- delete palettes.Roulette
- const k = Object.keys(palettemap)
- const n = k[(k.length * Math.random()) << 0]
- palettes.Roulette = palettes[palettemap[n][0]]
- paldata = ['Roulette', palettemap[n][1]]
- } else {
- if (pal) paldata = palettemap[pal.toLowerCase()]
- }
- if (!paldata) {
- if (!pal) {
- alert('Error, please report! Failed to extract the palette.\nAre you using the latest ANBT version?')
- } else {
- alert(`Error, please report! Unknown palette: '${pal}'.\nAre you using the latest ANBT version?`)
- }
- // Prevent from drawing with a wrong palette
- anbt.Lock()
- ID('submit').disabled = true
- } else {
- setPaletteByName(paldata[0])
- anbt.SetBackground(paldata[1])
- anbt.color = [palettes[paldata[0]][0], 'eraser']
- updateColorIndicators()
- }
- ID('setbackground').hidden = !info.bgbutton
- } else {
- // Caption
- if (info.image.length <= 30) {
- // Broken drawing =(
- ID('tocaption').src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAD6AQMAAAAho+iwAAAABlBMVEWAQED///94jotxAAABiklEQVR4Xu3W0UrCUBjA8eOO5CLK7VxLzDWFrjK6Eaha8FHuppfwBRJvdjlMIK/K3qA3OZBBd/UIm9UL2O2inMJBptNuog/6/h4Q2Y8J387Y2KIoiqIoiqIoiuIxXnbI5cmXSiJjD3LmFyrGY46PqVAx/HPDv9/w3wsJTTgapuDkcEIQMFxzo937S8+F5OkWI2IKymQl3yiZ6j8zYsRY6vUYDcOfGkuMknE5/aQAMczX9O+iKIrKJWuSxliQqT61hOmMucsYK6uzLWfDenF34EXhOX+s377KLCZcs1bxhNXQqnAvrExWM8vvY3amORCNsplu2nZPWKdj1tecTHZZLA97ZnjBB/XrkWIZWT+bsmTowp+7FHSnyMi7CpuMrWcwNsMMxnJzrCUbwwq/2/MLJb8lP4L2zVHJ35Bp1rE8Uc2bALoNHQvcoNG3Yf5Pm6EnHG50Ye0YmiG4V08LmWD7wmF9gJwFgoHbnZzNSDE/Co3orSB2YGsbovAgaD9vlkB/WbkbdQVWMNxR1Ddnf4eSZpHZYAAAAABJRU5ErkJggg=='
- } else {
- ID('tocaption').src = info.image
- }
- ID('caption').value = ''
- ID('caption').focus()
- ID('caption').setAttribute('maxlength', 45)
- ID('usedchars').textContent = '45'
- }
- if ((options.timeoutSound && !info.blitz) || (options.timeoutSoundBlitz && info.blitz)) {
- window.playedWarningSound = false
- const alarm = new Audio(window.alarmSoundOgg)
- alarm.volume = options.timeoutSoundVolume / 100
- }
- timerStart = Date.now() + 1000 * info.timeleft
- timerCallback = s => {
- if (s < 1) {
- document.title = "[TIME'S UP!] Playing Drawception"
- if (info.image || window.timesup) {
- // If pressed submit before timer expired, let it process or retry in case of error
- if (!window.submitting) {
- if (info.image) {
- getParametersFromPlay()
- } else {
- // Allow to save the drawing after time's up
- exitToSandbox()
- }
- }
- } else {
- newcanvas.classList.add('locked')
- anbt.Lock()
- timerStart += 15000 // 15 seconds to submit
- updateTimer()
- window.timesup = true
- }
- } else {
- let m1 = Math.floor(s / 60)
- let s1 = Math.floor(s % 60)
- m1 = `0${m1}`.slice(-2)
- s1 = `0${s1}`.slice(-2)
- document.title = `[${m1}:${s1}] Playing Drawception`
- }
- if (alarm && !window.playedWarningSound && s <= (info.blitz ? 5 : 61) && s > 0) {
- alarm.play()
- window.playedWarningSound = true
- }
- }
- handleCommonParameters()
- window.timesup = false
- updateTimer()
- }
-
- /*const include = (script, callback) => {
- const tag = document.createElement('script')
- tag.src = script
- tag.onload = callback
- document.body.appendChild(tag)
- }*/
-
- const decodeHTML = html => {
- const txt = document.createElement('textarea')
- txt.innerHTML = html
- return txt.value
- }
-
- const bindCanvasEvents = () => {
- const unsavedStopAction = () => anbt.unsaved && !confirm("You haven't saved the drawing. Abandon?")
- if (window.inforum) {
- ID('quit').addEventListener('click', ({preventDefault}) => {
- preventDefault
- window.top.location.href = 'https://drawception.com/'
- })
- const backForum = document.createElement('button')
- backForum.href = '/'
- backForum.setAttribute('class', 'submit exit')
- backForum.title = 'Exit'
- backForum.textContent = 'Exit'
- backForum.addEventListener('click', ({preventDefault}) => {
- preventDefault
- window.frameElement.ownerDocument.querySelector('.v--modal-overlay').outerHTML = ''
- })
- ID('submit').parentNode.insertBefore(backForum, ID('submit').nextSibling)
- }
- ID('exit').addEventListener('click', () => {
- if (window.incontest) {
- if (!confirm('Quit the contest? Entry coins will be lost!')) return
- ID('exit').disabled = true
- ajax('POST', '/contests/exit.json', {
- load: () => {
- ID('exit').disabled = false
- window.drawing_aborted = true
- exitToSandbox()
- document.location.pathname = '/contests/'
- },
- error: () => {
- ID('exit').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- return
- }
- if (window.gameinfo.drawfirst) {
- if (!confirm('Abort creating a draw first game?')) return
- ID('exit').disabled = true
- ajax('POST', '/play/abort-start.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: () => {
- ID('exit').disabled = false
- window.drawing_aborted = true
- exitToSandbox()
- document.location.pathname = '/create/'
- },
- error: () => {
- ID('exit').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- return
- }
- if (!confirm('Really exit?')) return
- ID('exit').disabled = true
- ajax('POST', '/play/exit.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: () => {
- ID('exit').disabled = false
- exitToSandbox()
- }
- })
- })
- ID('skip').addEventListener('click', () => {
- if (unsavedStopAction()) return
- ID('skip').disabled = true
- ajax('POST', '/play/skip.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: () => getParametersFromPlay(), // Postpone enabling skip until we get game info
- error: () => {
- ID('skip').disabled = false
- getParametersFromPlay()
- }
- })
- })
- ID('start').addEventListener('click', () => {
- if (unsavedStopAction()) return
- ID('start').disabled = true
- getParametersFromPlay()
- })
- ID('report').addEventListener('click', () => {
- if (!confirm('Report this panel?')) return
- ajax('POST', '/play/flag.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: () => {
- ID('report').disabled = false
- getParametersFromPlay()
- }
- })
- })
- ID('bookmark').addEventListener('click', () => {
- ID('bookmark').disabled = true
- let games = localStorage.getItem('gpe_gameBookmarks')
- games = games ? JSON.parse(games) : {}
- const caption = window.gameinfo.caption
- games[window.gameinfo.gameid] = {
- time: Date.now(),
- caption: caption ? decodeHTML(caption) : ''
- }
- localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
- })
- ID('submit').addEventListener('click', () => {
- const moreThanMinuteLeft = timerStart - Date.now() > 60000
- if (options.submitConfirm && moreThanMinuteLeft && !confirm('Ready to submit this drawing?')) return
- ID('submit').disabled = true
- anbt.MakePNG(300, 250, true)
- if (options.backup) {
- localStorage.setItem('anbt_drawingbackup_newcanvas', anbt.pngBase64)
- }
- window.submitting = true
- let url
- if (window.incontest) {
- url = '/contests/submit-drawing.json'
- } else {
- url = '/play/draw.json'
- }
- ajax('POST', url, {
- obj: {
- game_token: window.gameinfo.gameid,
- panel: anbt.pngBase64
- },
- load: x => {
- let o
- try {
- o = JSON.parse(x)
- } catch (e) {
- o = {
- error: x
- }
- }
- if (o.error) {
- ID('submit').disabled = false
- if (typeof o.error === 'object') {
- alert(`Error! Please report this data:\ngame: ${window.gameinfo.gameid}\n\nresponse:\n${JSON.stringify(o.error)}`)
- } else {
- alert(o.error)
- }
- } else if (o.message) {
- ID('submit').disabled = false
- alert(o.message)
- } else if (o.url) {
- window.onbeforeunload = () => {}
- location.replace(o.url)
- }
- },
- error: () => {
- ID('submit').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- })
- ID('submitcaption').addEventListener('click', () => {
- const title = ID('caption').value
- if (!title) {
- ID('caption').focus()
- return alert("You haven't entered a caption!")
- }
- const onCaptionSuccess = () => {
- if (options.bookmarkOwnCaptions) {
- let games = localStorage.getItem('gpe_gameBookmarks')
- games = games ? JSON.parse(games) : {}
- games[window.gameinfo.gameid] = {
- time: Date.now(),
- caption: `"${title}"`,
- own: true
- }
- localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
- }
- }
- window.submitting = true
- ID('submitcaption').disabled = true
- let url
- if (window.incontest) {
- url = '/contests/submit-caption.json'
- } else {
- url = '/play/describe.json'
- }
- ajax('POST', url, {
- obj: {
- game_token: window.gameinfo.gameid,
- title
- },
- load: x => {
- let o
- try {
- o = JSON.parse(x)
- } catch (e) {
- o = {
- error: x
- }
- }
- if (o.error) {
- ID('submitcaption').disabled = false
- if (typeof o.error == 'object') {
- alert(`Error! Please report this data:\ngame: ${window.gameinfo.gameid}\n\nresponse: \n${JSON.stringify(o.error)}`)
- } else {
- alert(o.error)
- }
- } else if (o.message) {
- ID('submitcaption').disabled = false
- alert(o.message)
- } else if (o.url) {
- onCaptionSuccess()
- location.replace(o.url)
- }
- },
- error: () => {
- ID('submitcaption').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- })
- if (options.enterToCaption) {
- ID('caption').addEventListener('keydown', e => {
- if (e.keyCode == 13) {
- e.preventDefault()
- ID('submitcaption').click()
- }
- })
- }
- const updateUsedChars = () => (ID('usedchars').textContent = 45 - ID('caption').value.length)
- ID('caption').addEventListener('change', updateUsedChars)
- ID('caption').addEventListener('keydown', updateUsedChars)
- ID('caption').addEventListener('input', updateUsedChars)
- ID('timeplus').addEventListener('click', () => {
- if (window.gameinfo.friend) {
- ID('timeplus').disabled = true
- ajax('POST', '/play/exit.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: () => {
- ajax('GET', `/play/${window.gameinfo.gameid}/?${Date.now()}`, {
- load: x => {
- ID('timeplus').disabled = false
- if (x === '') {
- window.gameinfo = {
- error: 'Server returned a blank response :('
- }
- } else {
- window.gameinfo = extractInfoFromHTML(x)
- }
- timerStart = Date.now() + 1000 * window.gameinfo.timeleft
- },
- error: () => {
- ID('timeplus').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- },
- error: () => {
- ID('timeplus').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- return
- }
- ID('timeplus').disabled = true
- ajax('POST', '/play/add-time.json', {
- obj: {
- game_token: window.gameinfo.gameid
- },
- load: x => {
- const o = JSON.parse(x)
- if (o.error) {
- alert(o.error)
- } else if (o.callJS == 'updatePlayTime') {
- timerStart += o.data.seconds * 1000
- if (window.timesup) {
- ID('newcanvasyo').classList.remove('locked')
- anbt.Unlock()
- timerStart -= 15000 // remove 15 seconds to submit
- window.timesup = false
- }
- updateTimer()
- ID('timeplus').classList.remove('show')
- // Let play warning sound twice
- window.playedWarningSound = false
- }
- ID('timeplus').disabled = false
- },
- error: () => {
- ID('timeplus').disabled = false
- alert('Server error. :( Try again?')
- }
- })
- })
- const old_getClosestColor = window.getClosestColor
- window.getClosestColor = (rgb, pal) => {
- // Allow any color in friend games
- if (window.gameinfo && window.gameinfo.friend) return rgb2hex(rgb[0], rgb[1], rgb[2])
- return old_getClosestColor(rgb, pal)
- }
- }
-
- const deeper_main = () => {
- window.onerror = (e, file, line) => {
- // Silence the bogus error message from the overwritten page's timer
- if (e.toString().includes('periodsToSeconds')) return
- // Silence the useless error message
- if (e.toString().match(/script error/i)) return
- if (line) {
- alert(`${e}\nline: ${line}`)
- } else {
- alert(e)
- }
- }
- if (options.newCanvasCSS) {
- let parent = document.getElementsByTagName('head')[0]
- if (!parent) parent = document.documentElement
- const style = document.createElement('style')
- style.type = 'text/css'
- const textNode = document.createTextNode(options.newCanvasCSS)
- style.appendChild(textNode)
- parent.appendChild(style)
- }
- if (options.enableWacom) {
- const stupidPlugin = document.createElement('object')
- const container = ID('wacomContainer')
- stupidPlugin.setAttribute('id', 'wacom')
- stupidPlugin.setAttribute('type', 'application/x-wacomtabletplugin')
- stupidPlugin.setAttribute('width', '1')
- stupidPlugin.setAttribute('height', '1')
- container.appendChild(stupidPlugin)
- if (options.fixTabletPluginGoingAWOL) fixPluginGoingAWOL()
- }
- bindCanvasEvents()
- if (window.insandbox) {
- if (window.panelid) {
- ajax('GET', `/panel/drawing/${window.panelid}/-/`, {
- load: x => {
- window.gameinfo = extractInfoFromHTML(x)
- anbt.FromURL(`${gameinfo.drawinglink}?anbt`) // workaround for non-CORS cached image
- handleSandboxParameters()
- },
- error: () => {
- alert('Error loading the panel page. Please try again.')
- }
- })
- } else {
- if (window.origpage) {
- window.gameinfo = extractInfoFromHTML(window.origpage)
- handleSandboxParameters()
- window.origpage = null
- } else {
- ajax('GET', '/sandbox/', {
- load: x => {
- window.gameinfo = extractInfoFromHTML(x)
- handleSandboxParameters()
- },
- error: () => {}
- })
- }
- if (options.backup) {
- const pngdata = localStorage.getItem('anbt_drawingbackup_newcanvas')
- if (pngdata) {
- anbt.FromPNG(base642bytes(pngdata.substr(22)).buffer)
- localStorage.removeItem('anbt_drawingbackup_newcanvas')
- }
- }
- }
- } else {
- ID('newcanvasyo').className = 'play'
- getParametersFromPlay()
- }
- if (!options.smoothening) {
- buildSmoothPath = (points, path) => {
- if (points.length < 2) return
- path.pathSegList.initialize(path.createSVGPathSegMovetoAbs(points[0].x, points[0].y))
- for (let i = 1; i < points.length; i++) {
- const c = points[i]
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y))
- }
- }
- }
- // Poor poor memory devices, let's save on memory to avoid them "crashing"...
- if (/iPad|iPhone/.test(navigator.userAgent)) anbt.fastUndoLevels = 3
- window.$ = () => {
- alert('Some additional script conflicts with ANBT new canvas, please disable it.')
- window.$ = null
- throw new Error('Script conflict with ANBT new canvas')
- }
- }
- deeper_main()
- } // needToGoDeeper end
-
- const $ = (selector, array = false) => {
- if (selector[0] === '<') {
- const elements = [...new DOMParser().parseFromString(selector, 'text/html').body.children]
- return elements.length !== 1 ? elements : elements[0]
- }
- const elements = [...document.querySelectorAll(selector)]
- return elements.length > 1 || array === true ? elements : elements[0]
- }
-
- const setCookie = (name, value, expire) => {
- if (expire) {
- let time = new Date
- time.setTime(time.getTime() + 24 * expire * 60 * 60 * 1e3)
- expire = time.toUTCString()
- }
- document.cookie = `${name}=${value ? JSON.stringify(value) : ''}; expires=${expire ? expire : 'Thu, 01 Jan 1970 00:00:00 UTC'}; path=/`
- }
-
- const getCookie = name => {
- const ca = document.cookie.split(';')
- for (let i = 0; i < ca.length; i++) {
- let c = ca[i]
- while (c.charAt(0) === ' ') c = c.substring(1, c.length)
- if (c.indexOf(`${name}=`) === 0) return c.substring(`${name}=`.length, c.length)
- }
- return null
- }
-
- //const isBlitzInPlay = () => ($('.label-game-mode') && $('.label-game-mode').textContent.match(/blitz/i) ? true : false)
-
- const linkifyNodeText = node => {
- const t = node
- if (!t.textContent.includes('://')) return
- t.innerHTML = t.innerHTML.replace(/([^"]|^)(https?:\/\/(?:(?:(?:[^\s<()]*\([^\s<()]*\))+)|(?:[^\s<()]+)))/g, '$1$2')
- }
-
- //const enhanceCanvas = insandbox => {}
-
- //const empowerPlay = () => {}
-
- // Event functions referred to in HTML must have unwrapped access
-
- const reversePanels = () => {
- const e = $('.gamepanel-holder')[0].parentNode.parentNode
- ;[...e.childNodes].reverse().forEach(x => e.appendChild(x))
- }
- window.reversePanels = reversePanels
-
- const toggleLight = () => {
- if (options.anbtDarkMode) {
- let css = document.getElementById('darkgraycss')
- if (!inDark) {
- if (!css) {
- css = document.createElement('style')
- css.id = 'darkgraycss'
- css.type = 'text/css'
- css.appendChild(document.createTextNode(localStorage.getItem('gpe_darkCSS')))
- }
- document.head.appendChild(css)
- inDark = 1
- } else {
- document.head.removeChild(css)
- inDark = 0
- }
- localStorage.setItem('gpe_inDark', inDark.toString())
- return
- } else {
- if (document.body.classList.contains('theme-night')) {
- document.body.classList.remove('theme-night')
- setCookie('theme-night')
- } else {
- document.body.classList.add('theme-night')
- setCookie('theme-night', 1, 365)
- }
- }
- }
- window.toggleLight = toggleLight
-
- const panelUrlToDate = url => {
- const m = url.match(/\/images\/panels\/(\d+)\/(\d+)-(\d+)\//)
- if (!m) return
- const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
- const day = (100 + parseInt(m[3], 10)).toString().slice(-2)
- return `${monthNames[parseInt(m[2], 10) - 1]} ${day}, ${m[1]}`
- }
-
- const fixLocationToCanonical = m => {
- let ogurl = $('meta[property="og:url"]') ? $('meta[property="og:url"]').content : ''
- if (ogurl && ogurl.match(m)) {
- ogurl = ogurl.replace('https://drawception.com', '')
- try {
- if (location.pathname != ogurl) {
- history.replaceState({}, null, ogurl + location.hash)
- }
- } catch (e) {}
- }
- }
-
- const betterCreateGame = () => {
- if (!options.enterToCaption) {
- if ($('#prompt')) {
- $('#prompt').addEventListener('keydown', e => {
- if (e.keyCode == 13) {
- e.preventDefault()
- }
- })
- }
- }
- }
-
- const betterGame = () => {
- if (document.title == 'Not Safe For Work (18+) Gate') {
- if (options.autoBypassNSFW) {
- DrawceptionPlay.bypassNsfwGate()
- }
- return
- }
-
- fixLocationToCanonical('/game/')
-
- const drawings = $('img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]')
-
- // Show each drawing make date
- if (drawings) drawings.forEach(x => {
- const d = panelUrlToDate(x.src)
- if (d) x.title = `Made on ${d}`
- })
-
- // Fix misaligned panels
- /*const tryNextPanel = x => {
- if (!x.naturalWidth && !x.triedFixing) {
- let pos = x.src.match(/-(\d+)\.png$/)[1] + 1
- x.src = x.src.replace(/-(\d+)\.png$/, `-${pos}.png`)
- x.triedFixing = true
- }
- }
- // TODO: also fix if script is executed after page load
- drawings.forEach(x => x.addEventListener('error', tryNextPanel(x)))*/
-
- // Reverse panels button and like all button
- if ($('#btn-copy-url')) $('#btn-copy-url').insertAdjacentHTML('afterend', ' Reverse')
-
- // Panel favorite buttons
- const favButton = $('')
- $('.panel-number', true).forEach(x => x.insertAdjacentHTML('afterend', favButton.outerHTML))
- $('.gamepanel', true).forEach(x => {
- const t = x.parentNode
- if (t.querySelector('.gamepanel-tools>a:last-child') === null) return
- let panels = localStorage.getItem('gpe_panelFavorites')
- panels = panels ? JSON.parse(panels) : {}
- const id = t.querySelector('.gamepanel-tools>a:last-child').href.match(/\/panel\/[^\/]+\/([^\/]+)\/[^\/]+\//)[1]
- if (panels[id]) {
- t.querySelector('.anbt_favpanel').classList.add('anbt_favedpanel')
- } else {
- t.querySelector('.anbt_favpanel').classList.remove('anbt_favedpanel')
- }
- })
- $('.anbt_favpanel', true).forEach(x => {
- x.addEventListener('click', e => {
- if (x.classList.contains('anbt_favedpanel')) return
- const tp = x.parentNode
- const id = tp.querySelector('.gamepanel-tools>a:last-child').href.match(/\/panel\/[^\/]+\/([^\/]+)\/[^\/]+\//)[1]
-
- let panels = localStorage.getItem('gpe_panelFavorites')
- panels = panels ? JSON.parse(panels) : {}
-
- const panel = {
- time: Date.now(),
- by: tp.querySelector('.panel-user a').textContent
- }
- panel.userLink = tp.querySelector('.panel-user a').href.match(/\/player\/[^\/]+\/[^\/]+\//)[0]
- const img = tp.querySelector('.gamepanel img')
- if (img) {
- // Drawing panel
- panel.image = img.src
- panel.caption = img.alt
- } else {
- // Caption panel
- panel.caption = tp.querySelector('.gamepanel').textContent.trim()
- }
- panels[id] = panel
- localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
- x.classList.add('anbt_favedpanel')
- })
- })
-
- // Panel replay button
- if (options.newCanvas) {
- const addReplayButton = x => {
- if (x.replayAdded) return
- x.replayAdded = true
- const panel = x.parentNode
- const src = x.src
- checkForRecording(src, () => {
- const newid = $(`img[src='${src}']`).parentNode.querySelector('a[href^="/panel/"]').href.match(/\/panel\/[^\/]+\/([^\/]+)/)[1]
- const id = newid.length >= 8 ? newid : scrambleID(panel.id.slice(6))
- const replayButton = $(``)
- replayButton.addEventListener('click', e => {
- if (e.which === 2) return
- e.preventDefault()
- setupNewCanvas(true, `/sandbox/#${id}`)
- })
- panel.insertAdjacentHTML('beforebegin', replayButton.outerHTML)
- })
- }
- if (drawings) drawings.forEach(x => x.addEventListener('load', addReplayButton(x)))
- }
-
- let comments
- // Comments appear dynamically after the page is loaded now
- const betterComments = () => {
- // Linkify the links and add ability to address comment holders again
- comments = [...$('#comments').nextElementSibling.children].slice(1)
- comments.forEach(x => {
- x.parentNode.parentNode.classList.add('comment-holder')
- })
- // Interlink game panels and comments
- const gamePlayers = []
- const playerdata = {}
- $('.gamepanel-holder', true).forEach((x, i) => {
- const det = x.querySelector('.panel-details')
- const gamepanel = x.querySelector('.gamepanel')
- const a = det.querySelector('.panel-user a')
- if (!a) return
- const id = a.href.match(/\/player\/(\d+)\//)[1]
- playerdata[id] = {
- panel_number: i + 1,
- player_anchor: a,
- panel_id: gamepanel.id,
- drew: gamepanel.querySelector('img') !== null,
- comments: 0
- }
- gamePlayers.push(id)
- })
- // Highlight new comments and remember seen comments
- let seenComments = localStorage.getItem('gpe_seenComments')
- seenComments = seenComments === null ? {} : JSON.parse(seenComments)
- const gameid = document.location.href.match(/game\/([^\/]+)\//)[1]
- const holders = comments
- if (holders) {
- // Clear old tracked comments
- const hour = Math.floor(Date.now() / (1000 * 60 * 60)) // timestamp with 1 hour precision
- for (let tempgame in seenComments) {
- // Store game entry for up to a week after last tracked comment
- if (seenComments[tempgame].h + 24 * 7 < hour) {
- delete seenComments[tempgame]
- }
- }
- let maxseenid = 0
- holders.forEach(x => {
- const dateel = x.querySelector('a.text-muted')
- const vue = x.__vue__
- if (vue) {
- const text = dateel.textContent.trim()
- dateel.textContent = `${text}, ${formatTimestamp(vue.comment_date * 1000)}`
- if (vue.edit_date > 0) {
- const el = dateel.parentNode.querySelector('span[rel="tooltip"]')
- let text2 = el.title
- text2 += `, ${formatTimestamp(vue.edit_date * 1000).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart
- el.setAttribute('title', text2)
- }
- }
- const ago = dateel.textContent
- const anchorid = x.id
- const commentid = parseInt(anchorid.slice(1), 10)
- // Also allow linking to specific comment
- dateel.setAttribute('title', 'Link to comment')
- dateel.textContent = `${dateel.textContent.trim()} #${commentid}`
- // Track comments from up to week ago
- if (ago.match(/just now|min|hour|a day| [1-7] day/)) {
- if (!(seenComments[gameid] && seenComments[gameid].id >= commentid)) {
- x.classList.add('comment-new')
- if (maxseenid < commentid) maxseenid = commentid
- }
- }
- // Add game perticipation info
- const m = x.querySelector('.text-bold a') ? x.querySelector('.text-bold a').href.match(/\/player\/(\d+)\//) : ''
- if (m) {
- const id = m[1]
- if (gamePlayers.includes(id)) {
- const drew = playerdata[id].drew ? 'drew' : 'wrote'
- dateel.insertAdjacentHTML('beforebegin', `(${drew} #${playerdata[id].panel_number}) `)
- playerdata[id].comments += 1
- }
- }
- })
- if (maxseenid)
- seenComments[gameid] = {
- h: hour,
- id: maxseenid
- }
- localStorage.setItem('gpe_seenComments', JSON.stringify(seenComments))
- }
- for (let i = 0; i < gamePlayers.length; i++) {
- const data = playerdata[gamePlayers[i]]
- if (data.comments != 0) {
- const cmt = data.comments == 1 ? ' comment' : ' comments'
- const cmt2 = `Player left ${data.comments}${cmt}`
- data.player_anchor.title = cmt2
- data.player_anchor.insertAdjacentHTML('afterend', `${data.comments}`)
- }
- }
- if (options.maxCommentHeight) {
- const h = options.maxCommentHeight
- comments.forEach(x =>
- x.addEventListener('click', x => {
- if (x.clientHeight > h - 50 && !$(location.hash).has(x).length) location.hash = `#${x.parentNode.parentNode.id}`
- })
- )
- }
- }
- const waitForComments = () => {
- const comments = $('#comments') ? [...$('#comments').nextElementSibling.children].slice(1) : ''
- if (comments.length && !comments[0].classList.contains('spinner')) {
- betterComments()
- } else {
- if (comments.length === 0) return
- setTimeout(waitForComments, 1000)
- }
- }
- setTimeout(waitForComments, 200)
- }
- const checkForRecording = (url, yesfunc, retrying) => {
- const xhr = new XMLHttpRequest()
- xhr.open('GET', `${url}?anbt`, true)
- xhr.responseType = 'arraybuffer'
- xhr.onload = () => {
- const buffer = xhr.response
- const dv = new DataView(buffer)
- const magic = dv.getUint32(0)
- if (magic != 0x89504e47) return xhr.onerror() // Drawception started hijacking XHR errors and putting HTML in there
- for (let i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) {
- const chunklen = dv.getUint32(i)
- i += 4
- const chunkname = dv.getUint32(i)
- i += 4
- if (chunkname === 0x73764762) {
- return yesfunc()
- } else {
- if (chunkname === 0x49454e44) break
- i += chunklen
- }
- }
- }
- xhr.onerror = e => {
- console.log('checkForRecording fail (likely due to cache without CORS), retrying')
- if (!retrying) checkForRecording(`${url}?anbt`, yesfunc, true)
- }
- xhr.send()
- }
-
- const betterPanel = () => {
- // Just for quickly opening a panel by its numerical ID
- if ($('.gamepanel') && !$('.gamepanel').length && location.hash) {
- const id = location.hash.match(/\d+/)
- location.pathname = `/panel/-/${scrambleID(id)}/-/`
- }
-
- fixLocationToCanonical('/panel/')
-
- const favButton = $('')
- if ($('.panel-caption-display>.flex,.gamepanel-holder>.gamepanel')) $('.panel-caption-display>.flex,.gamepanel-holder>.gamepanel').insertAdjacentHTML('afterend', favButton.outerHTML)
- const favBtn = $('.btn.btn-info')
- if (favBtn) {
- favBtn.addEventListener('click', e => {
- e.preventDefault()
- let panels = localStorage.getItem('gpe_panelFavorites')
- panels = panels ? JSON.parse(panels) : {}
- const panel = {
- time: Date.now(),
- by: $('.lead a', true)[0].textContent
- }
- const id = document.location.href.match(/\/panel\/[^\/]+\/([^\/]+)\//)[1]
- panel.userLink = $('.lead a', true)[0].href.match(/\/player\/[^\/]+\/[^\/]+\//)[0]
- const img = $('.gamepanel img')
- if (img) {
- // Drawing panel
- panel.image = img.src
- panel.caption = img.alt
- } else {
- // Caption panel
- panel.caption = $('.gamepanel').textContent.trim()
- }
- panels[id] = panel
- localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
- favBtn.setAttribute('disabled', 'disabled')
- favBtn.querySelector('b').textContent = 'Favorited!'
- })
- }
- let panels = localStorage.getItem('gpe_panelFavorites')
- panels = panels ? JSON.parse(panels) : {}
- if (document.location.href.match(/\/panel\/[^\/]+\/([^\/]+)\//) && panels[document.location.href.match(/\/panel\/[^\/]+\/([^\/]+)\//)[1]]) {
- favBtn.setAttribute('disabled', 'disabled')
- favBtn.querySelector('b').textContent = 'Favorited!'
- }
- const panelId = getPanelId(location.pathname)
-
- // Only panels after 14924553 might have a recording
- if (options.newCanvas && panelId && unscrambleID(panelId) >= 14924553) {
- const img = $('.gamepanel img')
- if (img) {
- checkForRecording(img.src, () => {
- const replayLink = $(` Replay `)
- replayLink.addEventListener('click', e => {
- if (e.which === 2) return
- e.preventDefault()
- setupNewCanvas(true, `/sandbox/#${panelId}`)
- })
- $('.gamepanel').insertAdjacentHTML('afterend', replayLink.outerHTML)
- })
- }
- }
- if ($('.btn-primary').length > 1 && $('.btn-primary')[1].textContent === 'Play again') {
- // Allow adding to cover creator
- const ccButton = $('')
- ccButton.addEventListener('click', e => {
- e.preventDefault()
- const id = unscrambleID(panelId)
- const cookie = getCookie('covercreatorids')
- const ids = cookie ? JSON.parse(cookie) : []
- if (!ids.includes(id)) {
- if (ids.length > 98) {
- apprise('Max cover creator drawings selected. Please remove some before adding more.')
- return
- } else {
- ids.push(id.toString())
- }
- } else {
- ccButton.setAttribute('disabled', 'disabled').querySelector('b').textContent = 'Already added!'
- return
- }
- setCookie('covercreatorids', JSON.stringify(ids))
- ccButton.setAttribute('disabled', 'disabled').querySelector('b').textContent = 'Added!'
- })
- $('.gamepanel').insertAdjacentHTML('afterend', ccButton.outerHTML)
- }
-
- /*if (options.rememberPosition && $('.regForm > .lead').textContent.match(/public game/)) {
- // your own panel
- panelPositions.load()
- if (!panelPositions.player[panelId]) {
- const xhr = new XMLHttpReuqest()
- xhr.open('GET', `/player/${userid}/-/`)
- xhr.onreadystatechange = html => {
- if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
- html = html.replace(/
]*>/gi, '') // prevent image preload
- const profilePage = new DOMParser().parseFromString(html, 'text/html')
- const panelProgressText = $(profilePage)
- .querySelector(`a[href='${location.pathname}']`)
- .nextElementSibling.querySelector('.progress-bar-text').textContent
- const panelPosition = parseInt(panelProgressText.match(/\d+/)[0])
- panelPositions.player[panelId] = panelPosition
- panelPositions.clear(profilePage)
- panelPositions.save()
-
- $('.regForm > .lead')
- .appendChild('
')
- .appendChild(($('').textContent = panelProgressText))
- }
- xhr.send()
- }
- }
- }*/
- }
- const panelPositions = {
- player: null,
- last: null,
- load() {
- const loadObj = key => {
- const val = localStorage.getItem(key)
- return (val && JSON.parse(val)) || {}
- }
-
- panelPositions.player = loadObj('gpe_panelPositions')
- panelPositions.last = loadObj('gpe_lastGamePositions')
- },
- save() {
- localStorage.setItem('gpe_panelPositions', JSON.stringify(panelPositions.player))
- localStorage.setItem('gpe_lastGamePositions', JSON.stringify(panelPositions.last))
- },
- clear(page) {
- const clearKeys = (obj, keys) => {
- for (let k in obj) {
- if (!keys.includes(k)) delete obj[k]
- }
- }
-
- const existingIds = $(page)
- .querySelector('.progress-striped')
- .map(x => getPanelId(x.previousElementSibling.href))
- clearKeys(panelPositions.player, existingIds)
- clearKeys(panelPositions.last, existingIds)
- }
- }
-
- const getPanelId = url => {
- const match = url.match(/\/panel\/[^\/]+\/(\w+)\//)
- return match && match[1]
- }
-
- const simpleHash = s =>
- s
- .toString()
- .split('')
- .reduce((a, b) => {
- a = (a << 5) - a + b.charCodeAt(0)
- return a & a
- }, 0)
-
- const rot13 = s =>
- s
- .toString()
- .split('')
- .map(c => {
- c = c.charCodeAt(0)
- if (c >= 97 && c <= 122) c = ((c - 97 + 13) % 26) + 97
- if (c >= 65 && c <= 90) c = ((c - 65 + 13) % 26) + 65
- return String.fromCharCode(c)
- })
- .join('')
-
- const fadeIn = (element, duration = 400) => {
- element.style.display = 'inline'
- duration = duration === 'slow' ? 600 : duration
- element.style.opacity = element.style.opacity ? parseFloat(element.style.opacity) + 0.1 : 0.2
- if (parseFloat(element.style.opacity) > 1) {
- element.style.opacity = 1
- } else {
- setTimeout(() => {
- fadeIn(element, duration)
- }, duration / 10)
- }
- }
-
- const fadeOut = (element, duration = 400) => {
- duration = duration === 'slow' ? 600 : duration
- element.style.opacity = element.style.opacity ? parseFloat(element.style.opacity) - 0.1 : 1
- if (parseFloat(element.style.opacity) < 0) {
- element.style.opacity = 0
- element.style.display = 'none'
- } else {
- setTimeout(() => {
- fadeOut(element, duration)
- }, duration / 10)
- }
- }
-
- const randomGreeting = () => {
- // Spoilers!
- const g = ['Oruvaq lbh!', "Ubcr vg'f abg envavat gbqnl.", 'Jurer vf lbhe tbq abj?', 'Lbh fubhyq srry 5% zber cbjreshy abj.', 'Fhqqrayl, abguvat unccrarq!', '^_^', 'Guvf gnxrf fb ybat gb svavfu...', "Jungrire lbh qb, qba'g ernq guvf grkg.", 'Pyvpx urer sbe 1 serr KC', 'Or cngvrag.', "Whfg qba'g fgneg nal qenzn nobhg vg.", '47726S6Q2069732074686520677265617465737421', 'Cynl fzneg.', 'Cynl avpr.', 'Fzvyr!', "Qba'g sbetrg gb rng.", "V xabj jung lbh'ir qbar.", 'Fpvrapr!', 'Gbqnl vf n tbbq qnl.']
- const change_every_half_day = Math.floor(Date.now() / (1000 * 60 * 60 * 12))
- const rnddata = simpleHash(change_every_half_day + parseInt(userid, 10) + 178889)
- return rot13(g[rnddata % g.length])
- }
-
- const formatTimestamp = d => {
- if (typeof d == 'number') d = new Date(d)
- if (options.localeTimestamp) return d.toLocaleString()
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
- const s = [`${100 + d.getDate()}`.slice(-2), ' ', months[d.getMonth()], ' ', d.getFullYear(), ' ', `${100 + d.getHours()}`.slice(-2), ':', `${100 + d.getMinutes()}`.slice(-2)].join('')
- return s
- }
-
- const viewMyPanelFavorites = () => {
- let panels = localStorage.getItem('gpe_panelFavorites')
- panels = panels ? JSON.parse(panels) : {}
- let result = ''
- let needsupdate = false
- for (let id in panels) {
- if (panels[id].image && panels[id].image.match(/^\/pub\/panels\//)) {
- needsupdate = true
- panels[id].image = panels[id].image.replace('/pub/panels/', 'https://cdn.drawception.com/images/panels/')
- }
- result += ``
- }
- if (needsupdate) localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
- result = result ? `${result}` : "You don't have any favorited panels."
- $('#anbt_userpage').innerHTML = result
- $('#anbt_userpage .anbt_paneldel', true).forEach(x =>
- x.addEventListener('click', e => {
- e.preventDefault()
- const id = x.parentNode.parentNode.id
- fadeOut($(`#${CSS.escape(id)}`))
- delete panels[id]
- localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
- })
- )
- }
- window.viewMyPanelFavorites = viewMyPanelFavorites
-
- const viewMyGameBookmarks = () => {
- const removeButtonHTML = ''
- let games = localStorage.getItem('gpe_gameBookmarks')
- games = games ? JSON.parse(games) : {}
- let result = ''
- for (let id in games) {
- let extraClass = ''
- if (games[id].own) extraClass = 'anbt_owncaption'
- if (id.length > 10) {
- // token, seen lengths: 43, 32; just in case assuming everything > 10 is a token
- result += ``
- const xhr = new XMLHttpRequest()
- xhr.open('GET', `/play/${id}`)
- xhr.onload = () => {
- if (xhr.status === 200) {
- let m = xhr.responseText.match(/Game is not private/) || (xhr.responseText.match(/Problem loading game/) && 'del')
- if (m) {
- let gamename = ''
- if (games[id].caption) gamename += ` ${games[id].caption}`
- if (games[id].own) gamename = ` with your caption${gamename}`
- if (games[id].time) gamename += ` bookmarked on ${formatTimestamp(games[id].time)}`
- if (!gamename) gamename = id
- const status = m === 'del' ? 'Deleted' : 'Unfinished public'
- $(`#${id}`).querySelector('span').textContent = `${status} game${gamename}`
- return
- }
- const title = xhr.responseText.match(/(.+)<\/title>/)[1]
- m = xhr.responseText.match(/\/game\/([^\/]+)\/[^\/]+\//)
- const url = m[0]
- const gameid = m[1]
- delete games[id]
- games[gameid] = {
- title,
- url
- }
- $(`#${id}`).id = gameid
- const spanId = $(`#${gameid}`).querySelector('span')
- spanId.parentNode.replaceChild($(`${title}`), spanId)
- localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
- } else {
- $(`#${id}`).querySelector('span').textContent = `Error while retrieving game: ${xhr.responseText}`
- }
- }
- xhr.send()
- } else if (id.length == 10) result += `` // game ID
- }
- if (!result) result = "You don't have any bookmarked games."
- $('#anbt_userpage').innerHTML = result
- $('#anbt_userpage .anbt_gamedel', true).forEach(x =>
- x.addEventListener('click', e => {
- e.preventDefault()
- const id = x.parentNode.id
- fadeOut($(`#${id}`))
- delete games[id]
- localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
- })
- )
- }
- window.viewMyGameBookmarks = viewMyGameBookmarks
-
- // Convert times
- // Forum time is Florida, GMT-6, to be +1 DST since 08 Mar 2015, 2:00
- // starts on the second Sunday in March and ends on the first Sunday in November
- const isFloridaDST = () => {
- d = new Date(Date.now() - 6 * 60 * 60 * 1000)
- const month = d.getUTCMonth()
- const day = d.getUTCDate()
- const hours = d.getUTCHours()
- const dayofweek = d.getUTCDay()
-
- if (month < 2 || month > 10) return false
- if (month > 2 && month < 10) return true
- if (month == 2) {
- if (day < 8) return false
- if (day > 14) return true
- if (dayofweek == 7) return hours > 1
- return day > dayofweek + 7
- }
- if (month == 10) {
- if (day > 7) return false
- if (dayofweek == 7) return hours < 1
- return day <= dayofweek
- }
- }
-
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
- const convertForumTime = (year, month, day, hours, minutes) => {
- const d = new Date(year, month, day, hours, minutes)
- const tzo = d.getTimezoneOffset() * 60 * 1000
- const dst = isFloridaDST()
- return formatTimestamp(d.getTime() - tzo + (6 - dst) * 60 * 60 * 1000)
- }
-
- const betterPlayer = () => {
- // Linkify the links in location
- const pubinfo = $('.profile-header-info .text-muted > span:last-child')
- if (pubinfo) linkifyNodeText(pubinfo.parentNode)
-
- const loc = document.location.href
- // If it's user's homepage, add new buttons in there
- if (loc.match(new RegExp(`/player/${userid}/[^/]+/(?:$|#)`))) {
- const a = $('ANBT stuff:
')
- a.appendChild($('Panel Favorites'))
- a.appendChild($('Game Bookmarks'))
- const profilemain = $('.profile-layout-content').firstChild
- profilemain.insertAdjacentHTML('afterbegin', `${randomGreeting()}
`)
- profilemain.insertAdjacentHTML('afterbegin', a.outerHTML)
-
- if (document.location.hash.includes('#anbt_panelfavorites')) viewMyPanelFavorites()
- if (document.location.hash.includes('#anbt_gamebookmarks')) viewMyGameBookmarks()
-
- /*if (options.rememberPosition) {
- panelPositions.load();
- panelPositions.clear(document);
-
- $(".progress-striped").forEach(function () {
- const panelId = getPanelId(this.previousElementSibling.href);
- const playerPanelPosition = panelPositions.player[panelId];
- const lastSeenPanelPosition = panelPositions.last[panelId];
- const panelProgress = this.querySelector(".progress-bar-text");
- const panelProgressText = panelProgress.textContent;
- const panelPosition = parseInt(panelProgressText.match(/\d+/)[0]);
- const totalPanelCount = parseInt(panelProgressText.match(/\d+/g)[1]);
-
- panelProgress.style.pointerEvents = "none"; // to make tooltips work under label
- if ((playerPanelPosition || lastSeenPanelPosition || panelPosition) < panelPosition) {
- this.querySelector(".progress-bar")
- .width(`${(playerPanelPosition || lastSeenPanelPosition) / totalPanelCount * 100}%`);
- }
- if (playerPanelPosition && panelPosition > playerPanelPosition && playerPanelPosition < lastSeenPanelPosition) {
- $('')
- .width(`${(Math.min(lastSeenPanelPosition || panelPosition, panelPosition) - playerPanelPosition) / totalPanelCount * 100}%`)
- .insertAdjacentHTML('beforebegin', panelProgress.outerHTML)
- .tooltip();
- }
- if (lastSeenPanelPosition && panelPosition > lastSeenPanelPosition) {
- $('
')
- .width(`${(panelPosition - lastSeenPanelPosition) / totalPanelCount * 100}%`)
- .insertAdjacentHTML('beforebegin', panelProgress.outerHTML)
- .tooltip();
- }
- if (lastSeenPanelPosition && panelPosition < lastSeenPanelPosition) {
- $('
')
- .width(`${1 / totalPanelCount * 100}%`)
- .insertAdjacentHTML('beforebegin', panelProgress.outerHTML)
- .tooltip();
- }
- if (playerPanelPosition) {
- $('
')
- .text(`#${playerPanelPosition}`)
- .insertAdjacentHTML('beforebegin', this.outerHTML)
- .tooltip();
- }
-
- panelPositions.last[panelId] = panelPosition;
- });
-
- panelPositions.save();
- }*/
-
- // Show your exact registration date
- if (window.date) {
- const pubinfo = $('.profile-user-header>div.row>div>h1+p')
- if (pubinfo) {
- const newregdate = formatTimestamp(date)
- ;[...pubinfo.childNodes][4].nodeValue = ` ${newregdate} \xa0`
- }
- }
- } else {
- // Not the current user's profile or not profile homepage
- let drawings = $('img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]', true)
- // Show replayable panels; links are not straightforward to make since there's no panel ID
- if (options.newCanvas) {
- const addReplaySign = x => {
- if (x.replayAdded) return
- x.replayAdded = true
- const panel = x.parentNode.parentNode
- const src = x.src
- checkForRecording(src, () => {
- const newid = src.match(/(\w+).png$/)[1]
- const replaySign = newid.length >= 8 ? $(``) : $('')
- panel.appendChild(replaySign)
- //replaySign.tooltip();
- })
- }
- drawings.forEach(x => x.addEventListener('load', addReplaySign(x)))
- }
-
- // Detect Draw Firsts
- drawings.forEach(({src, parentNode}) => {
- if (src.match(/-1\.png$/)) {
- const drawFirstSign = $('
')
- parentNode.parentNode.appendChild(drawFirstSign)
- //drawFirstSign.tooltip();
- }
- })
- }
-
- // Convert timestamps in user profile's forum posts and game comments
- if (loc.match(/player\/\d+\/[^/]+\/(posts)|(comments)\//)) {
- // Show topic title at the top of the posts instead and display subforum
- // Show game title at the top of the posts
- $('.forum-thread-starter', true).forEach(x => {
- const vue = x.childNodes[0].__vue__
- if (vue) {
- const ts = x.querySelector('a.text-muted').firstChild
- const text = ts.textContent.trim()
- ts.textContent = `${text}, ${formatTimestamp(vue.comment_date * 1000)}`
- if (vue.edit_date > 0) {
- const el = ts.parentNode.parentNode.querySelector('span[rel="tooltip"]')
- let text2 = el.title
- text2 += `, ${formatTimestamp(vue.edit_date * 1000).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart
- el.setAttribute('title', text2)
- }
- }
- const postlink = x.querySelector('.add-margin-top small.text-muted')
- const created = postlink.textContent.match(/^\s*Created/)
- const commented = postlink.textContent.match(/^\s*Commented/)
- const prefix = commented ? 'Comment in the game' : created ? 'New thread' : 'Reply in'
- const n = $(`${prefix}:
`)
- const thread = postlink.querySelector('a')
- n.appendChild(thread)
- x.insertAdjacentHTML('afterbegin', n.outerHTML)
- postlink.parentNode.parentNode.removeChild(postlink.parentNode)
- })
- }
- }
-
- const betterForum = () => {
- const ncPosts = []
- $('span.muted, span.text-muted, small.text-muted', true).forEach((x, index) => {
- const tx = x.textContent
- // Don't touch relative times
- if (tx.includes('ago')) return
- if ((m = tx.match(/^\s*\(last post (...) (\d+).. (\d+):(\d+)([ap]m)\)\s*$/))) {
- const d = new Date()
- const month = months.indexOf(m[1])
- const day = parseInt(m[2], 10)
- let hours = parseInt(m[3], 10) % 12
- const minutes = parseInt(m[4], 10)
- let year = d.getFullYear()
- if (d.getMonth() < 6 && month >= 6) year--
- hours += m[5] === 'pm' ? 12 : 0
- const time = convertForumTime(year, month, day, hours, minutes)
- x.textContent = `(last post ${time})`
- // Track new posts at subforum list
- if (location.href.match(/forums\/$/)) {
- if (time != localStorage.getItem(`anbt_subforum${index}`)) {
- x.parentNode.insertAdjacentHTML('afterbegin', 'NEW ')
- localStorage.setItem(`anbt_subforum${index}`, time)
- }
- }
- }
- })
-
- if (options.markStalePosts) {
- const markStalePost = ({classList}, age) => {
- if (age < 30) return
- let r = 0
- if (age > 60) r = 1
- if (age > 120) r = 2
- if (age > 365) r = 3
- classList.add(`anbt_necropost anbt_necropost${r}`)
- }
- // Skip the first post
- for (let i = 1; i < ncPosts.length; i++) {
- const el = ncPosts[i][0]
- const time = ncPosts[i][1]
- const lastpage = !$('.pagination').length || $('.pagination .active:last-child').length
- const nexttime = ncPosts[i + 1] ? ncPosts[i + 1][1] : lastpage ? Date.now() / 86400000 : 0
- const age = nexttime - time
- if (age > 30) markStalePost($(el).parentNode.parentNode.parentNode.parentNode, age)
- }
-
- GM_addStyle(".anbt_necropost:after {display: block; height: 14px; border-bottom: 2px solid black; content: ' '; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAOCAMAAADOvxanAAAAElBMVEUAAAD///8wLiSYlYNnZFXm5Nfd4sMOAAAAAnRSTlMAAHaTzTgAAABBSURBVHheXchBEoBADAJBMsD/v2yZdS8Oly40k9NIk54ySm/8DYb6NWuuex1ak7VdbAcpNrCU8HmP4/hzd9oAXj6sBgHBLAHrRAAAAABJRU5ErkJggg==)}" + '.anbt_necropost0:after {background: none; border-bottom: 1px solid black}' + '.anbt_necropost1:after {background: none}' + '.anbt_necropost2:after {background-repeat: no-repeat; background-position: center}' + '.anbt_necropost3:after {}' + ".anbt_necropost span.muted:after {content: ' (old post)'}" + 'iframe {border: none}' + '.v--modal-overlay .v--modal-background-click {padding-bottom: 0}')
- }
- // Linkify the links
- $('.comment-body *', true).forEach(x => linkifyNodeText(x))
-
- // Linkify drawing panels
- $('img[src*="/images/panels/"], img[src*="/pub/panels/"]', true).forEach(x => {
- if (x.parentNode.nodeName !== 'A') x.outerHTML = `${x.outerHTML}`
- })
- $('img[src*="/drawings/"]', true).forEach(x => {
- if (x.parentNode.nodeName !== 'A') x.outerHTML = `${x.outerHTML}`
- })
- $('img[src*="/panel/"]', true).forEach(x => {
- if (x.parentNode.nodeName !== 'A') x.outerHTML = `${x.outerHTML}`
- })
- // Linkify full game image
- $('img[src*="/images/games/"], img[src*="/pub/games/"]', true).forEach(x => {
- if (x.parentNode.nodeName !== 'A') x.outerHTML = `${x.outerHTML}`
- })
- // Fix the dead link
- $('img[src*="/display-panel.php?"]', true).forEach(x => {
- if (x.parentNode.nodeName !== 'A') {
- const newsrc = `/panel/drawing/${scrambleID(x.src.match(/x=(\d+)/)[1])}/`
- x.setAttribute('src', newsrc)
- x.outerHTML = `${x.outerHTML}`
- }
- })
-
- // Show posts IDs and link
- if (document.location.pathname.match(/\/forums\/(\w+|-)\/.+/)) {
- const hideuserids = options.forumHiddenUsers ? options.forumHiddenUsers.split(',') : ''
- if (hideuserids !== '') {
- GM_addStyle('.anbt_hideUserPost:not(:target) {opacity: 0.4; margin-bottom: 10px}' + '.anbt_hideUserPost:not(:target) .comment-body, .anbt_hideUserPost:not(:target) .avatar {display: none}' + '')
- }
- let lastid = 0
- $('.comment-avatar', true).forEach(({parentNode}) => {
- const t = parentNode.parentNode.parentNode
- let anch
- let id
- t.classList.add('comment-holder') // No identification for these anymore, this is unhelpful!
- try {
- anch = t.id
- } catch (e) {}
- const ts = t.querySelector('a.text-muted')
- const vue = t.childNodes[0].__vue__
- if (vue) {
- const text = ts.textContent.trim()
- ts.textContent = `${text}, ${formatTimestamp(vue.comment_date * 1000)}`
- if (vue.edit_date > 0) {
- const el = ts.parentNode.querySelector('span[rel="tooltip"]')
- let text2 = el.title
- text2 += `, ${formatTimestamp(vue.edit_date * 1000).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart
- el.setAttribute('title', text2)
- }
- }
- if (anch) {
- id = parseInt(anch.substring(1), 10)
- const text = ts.textContent.trim()
- ts.textContent = `${text} #${id}`
- ts.setAttribute('title', 'Link to post')
- if (id < lastid) {
- ts.classList.add('wrong-order')
- }
- try {
- const h = t.querySelector('a[href^="/player/"]').href
- if (h) {
- const userid = h.match(/\d+/)[0]
- if (hideuserids.includes(userid)) t.classList.add('anbt_hideUserPost')
- }
- } catch (e) {}
- lastid = id
- }
- })
-
- // Warn about posting to another page
- if ($('.comment-holder') && $('.comment-holder').length === 20 && $('#comment-form .btn-primary')) $('#comment-form .btn-primary').insertAdjacentHTML('afterend', 'Note: posting to another page
')
- }
-
- if (options.proxyImgur) $('img[src*="imgur.com/"]', true).forEach(x => x.setAttribute('src', x.src.replace('imgur.com', 'filmot.com').replace('https', 'http')))
-
- const pagination = $('.pagination', true)
- if (pagination.length) $('.breadcrumb').insertAdjacentHTML('afterend', `${pagination[0].outerHTML}
`)
-
- // For the topic list pages only
- if (document.location.pathname.match(/\/forums\/(\w+)\/$/)) {
- let hidden_topics = localStorage.getItem('gpe_forumHiddenTopics')
- hidden_topics = hidden_topics ? JSON.parse(hidden_topics) : []
- let hidden = 0
-
- const tempUnhideLink = $('')
-
- $('.forum-thread', true).forEach(x => {
- const m = x.querySelector('a:first-child').href.match(/\/forums\/\w+\/(\d+)\//)
- // Don't let them hide the ANBT topic ;)
- if (!m || !m[1] || m[1] == 11830) return
-
- const id = m[1]
- if (hidden_topics.includes(id)) {
- x.classList.add('anbt_hidden')
- hidden++
- }
- const hideLink = $('')
- hideLink.addEventListener('click', () => {
- let ht = localStorage.getItem('gpe_forumHiddenTopics')
- ht = ht ? JSON.parse(ht) : []
- if (hidden_topics.includes(id)) {
- if (ht.includes(id)) ht = ht.filter(y => y !== id)
- hidden_topics = hidden_topics.filter(y => y !== id)
- x.classList.remove('anbt_hidden')
- hidden--
- } else {
- if (!ht.includes(id)) ht.push(id)
- hidden_topics.push(id)
- x.classList.add('anbt_hidden')
- hidden++
- tempUnhideLink.style.display = ''
- }
- tempUnhideLink.textContent = hidden
- localStorage.setItem('gpe_forumHiddenTopics', JSON.stringify(ht))
- })
- x.querySelector('p:nth-child(2)').appendChild(hideLink)
- })
- tempUnhideLink.textContent = hidden
- tempUnhideLink.addEventListener('click', () => {
- $('#main').classList.toggle('anbt_showt')
- })
- if (!hidden) tempUnhideLink.style.display = 'none'
- if ($('#js-btn-toggle-thread')) $('#js-btn-toggle-thread').parentNode.appendChild(tempUnhideLink)
- }
- $('.btn.btn-default', true).forEach(x =>
- x.addEventListener('click', e => {
- if (x.textContent === 'Draw') {
- if (!x.classList.contains('click')) {
- let delModal = setInterval(() => {
- if (!$('.v--modal-overlay')) return
- $('.v--modal-overlay').outerHTML = ''
- clearInterval(delModal)
- }, 100)
- x.classList.add('click')
- }
- setupNewCanvas(true, document.location.href)
- }
- })
- )
- }
-
- const loadScriptSettings = () => {
- let result = localStorage.getItem('gpe_anbtSettings')
- if (!result) return
- result = JSON.parse(result)
- for (let i in result) options[i] = result[i]
- }
-
- const updateScriptSettings = theForm => {
- const result = {}
- theForm.querySelectorAll('input,textarea').forEach(x => {
- if (x.type === 'checkbox') {
- result[x.name] = x.checked ? 1 : 0
- } else if (x.getAttribute('data-subtype') === 'number') {
- result[x.name] = parseFloat(x.value) || 0
- } else {
- result[x.name] = x.value
- }
- })
- localStorage.setItem('gpe_anbtSettings', JSON.stringify(result))
- loadScriptSettings()
- fadeIn($('#anbtSettingsOK'), 'slow')
- setTimeout(() => {
- fadeOut($('#anbtSettingsOK'), 'slow')
- }, 800)
- }
- window.updateScriptSettings = updateScriptSettings
-
- const escapeHTML = t =>
- t
- .toString()
- .replace(/&/g, '&')
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''')
-
- const addScriptSettings = () => {
- const theForm = $('')
- theForm.appendChild($(''))
-
- const addGroup = (name, settings) => {
- const div = $('')
- div.appendChild($(``))
- settings.forEach(id => {
- let v = options[id[0]]
- const name = id[0]
- const t = id[1]
- const desc = id[2]
- const c = $('')
- if (t == 'boolean') {
- c.appendChild($(``))
- } else if (t == 'number') {
- if (!v) v = 0
- $(`${desc}:`).forEach(x => c.appendChild(x))
- } else if (t == 'longstr') {
- $(`${desc}:`).forEach(x => c.appendChild(x))
- } else {
- $(`${desc}:`).forEach(x => c.appendChild(x))
- }
- div.appendChild(c)
- })
- theForm.appendChild(div)
- }
- addGroup('Pen Tablet (requires plugin: Windows | Mac OS | Linux)', [
- ['enableWacom', 'boolean', 'Enable Wacom plugin / pressure sensitivity support'],
- ['fixTabletPluginGoingAWOL', 'boolean', 'Try to prevent Wacom plugin from disappearing']
- //["pressureExponent", "number", "Pressure exponent (smaller = softer tablet response, bigger = sharper)"],
- ])
- addGroup('Play (most settings are for the new canvas only)', [['newCanvas', 'boolean', 'New drawing canvas (also allows watching playback)'], ['submitConfirm', 'boolean', 'Confirm submitting if more than a minute is left'], ['smoothening', 'boolean', 'Smoothing of strokes'], ['hideCross', 'boolean', 'Hide the cross when drawing'], ['enterToCaption', 'boolean', 'Submit captions (and start games) by pressing Enter'], ['backup', 'boolean', 'Save the drawing in case of error and restore it in sandbox'], ['timeoutSound', 'boolean', 'Warning sound when only a minute is left (normal games)'], ['timeoutSoundBlitz', 'boolean', 'Warning sound when only 5 seconds left (blitz)'], ['timeoutSoundVolume', 'number', 'Volume of the warning sound, in %'], ['rememberPosition', 'boolean', 'Show your panel position and track changes in unfinished games list'], ['colorNumberShortcuts', 'boolean', 'Use 0-9 keys to select the color'], ['colorUnderCursorHint', 'boolean', 'Show the color under the cursor in the palette'], ['colorDoublePress', 'boolean', 'Double press 0-9 keys to select color without pressing shift'], ['bookmarkOwnCaptions', 'boolean', 'Automatically bookmark your own captions in case of dustcatchers']])
- addGroup('Miscellaneous', [['localeTimestamp', 'boolean', `Format timestamps as your system locale (${new Date().toLocaleString()})`], ['proxyImgur', 'boolean', 'Replace imgur.com links to filmot.com to load, in case your ISP blocks them'], ['ajaxRetry', 'boolean', 'Retry failed AJAX requests'], ['autoplay', 'boolean', 'Automatically start replay when watching playback'], ['autoBypassNSFW', 'boolean', 'Automatically bypass NSFW game warning'], ['markStalePosts', 'boolean', 'Mark stale forum posts'], ['maxCommentHeight', 'number', 'Maximum comments and posts height until directly linked (px, 0 = no limit)'], ['useOldFont', 'boolean', 'Use old Nunito font (which is usually bolder and less wiggly)'], ['useOldFontSize', 'boolean', 'Use old, smaller font size'], ['markdownTools', 'boolean', 'Markdown tools for messages'], ['anbtDarkMode', 'boolean', 'Switch between ANBT\'s and Drawception\'s dark mode']])
- addGroup('Advanced', [['newCanvasCSS', 'longstr', 'Custom CSS for new canvas (experimental, get styles here)'], ['forumHiddenUsers', 'longstr', 'Comma-separated list of user IDs whose forum posts are hidden']])
- $('
').forEach(x => theForm.appendChild(x))
- $('#main').insertAdjacentHTML('afterbegin', theForm.outerHTML)
-
- // Extend "location" input to max server-accepted 65 characters
- if ($('input[name="location"]')) $('input[name="location"]').setAttribute('maxlength', '65')
- }
-
- /*const autoSkip = (reason) => {
- const autoSkipInfo = $(`(CLICK TO CANCEL)
Auto-skipping in 3...
Reason: ${reason}
`)
- $('.play-instruction').appendChild(autoSkipInfo)
- autoSkipInfo.addEventListener('click', (e) => {
- e.preventDefault()
- $('#autoSkipCounter').countdown('pause')
- autoSkipInfo.style.display = 'none'
- })
- $('#autoSkipCounter').countdown({
- until: 3,
- compact: 1,
- format: 'S',
- onExpiry: timesUp,
- })
- }*/
-
- const theAlphabet = '36QtfkmuFds0UjlvCGIXZ125bEMhz48JSYgipwKn7OVHRBPoy9DLWaceqxANTr'
- // Game IDs will never contain these symbols: u 0U lv I J i V o
- // So they are base 52 for some reason...
-
- const decTo62 = n => {
- const b = theAlphabet
- let result = ''
- const bLen = b.length
- while (n != 0) {
- const q = n % bLen
- result = b[q] + result
- n = (n - q) / bLen
- }
- return result
- }
-
- const _62ToDec = n => {
- n = n.toString()
- const b = theAlphabet
- const cache_pos = {}
- const bLen = b.length
- let result = 0
- let pow = 1
- for (let i = n.length - 1; i >= 0; i--) {
- const c = n[i]
- if (typeof cache_pos[c] === 'undefined') {
- cache_pos[c] = b.indexOf(c)
- }
- result += pow * cache_pos[c]
- pow *= bLen
- }
- return result
- }
-
- const scrambleID = num => {
- if (isNaN(num)) throw new Error('Invalid panel ID')
- return decTo62(parseInt(num, 10) + 3521614606208)
- .split('')
- .reverse()
- .join('')
- }
- window.scrambleID = scrambleID
-
- const unscrambleID = str =>
- _62ToDec(
- str
- .split('')
- .reverse()
- .join('')
- ) - 3521614606208
- window.unscrambleID = unscrambleID
-
- const stalkNextPanel = forward => {
- if (!forward) forward = 1
- const sid = location.href.match(/\/panel\/[^\/]+\/(\w+)\/[^\/]+\//)[1]
- const sid2 = scrambleID(unscrambleID(sid) + 1 * forward)
- location.href = location.href.replace(sid, sid2)
- }
- window.stalkNextPanel = stalkNextPanel
-
- /*const valueToHex = val => (Math.floor(val / 16) % 16).toString(16) + (Math.floor(val) % 16).toString(16)
-
- const eyedropper = (x, y) => {
- const p = drawApp.context.getImageData(x, y, 1, 1).data
- return p[3] > 0 ? `#${valueToHex(p[0])}${valueToHex(p[1])}${valueToHex(p[2])}` : null
- }
-
- const invertColor = c => {
- // Support only hex color
- if (c.charAt(0) != '#') return c
- c = c.substring(1)
- // Ensure it's in long form
- if (c.length == 3) c = c.charAt(0) + c.charAt(0) + c.charAt(1) + c.charAt(1) + c.charAt(2) + c.charAt(2)
- return `#${`000000${(parseInt(c, 16) ^ 0xffffff).toString(16)}`.slice(-6)}`
- }*/
-
- const pagodaBoxError = () => {
- if (document.title === 'Pagoda Box' && (document.body.innerHTML.match('All Routes' + ' Down.') || document.body.innerHTML.match('There appears to be an error' + ' with this site.'))) {
- GM_addStyle('body {background: #755 !important}' + '')
- div = document.createElement('div')
- div.innerHTML = 'ANBT speaking:
' + 'Meanwhile, you can visit the chat: ' + 'http://chat.grompe.org.ru/#drawception
' + 'Or use the new sandbox: http://grompe.org.ru/drawit/'
- document.body.appendChild(div)
- }
- }
-
- const pageEnhancements = () => {
- const loc = document.location.href
- loadScriptSettings()
-
- if (typeof DrawceptionPlay === 'undefined') return // Firefox Greasemonkey seems to call pageEnhancements() after document.write...
- if (document.getElementById('newcanvasyo')) return // Chrome, I'm looking at you too...
-
- //__DEBUG__ = document.getElementById('_debug_')
- //prestoOpera = navigator.userAgent.match(/\bPresto\b/)
-
- // Stop tracking me! Best to block
- // api.mixpanel.com and cdn.mxpnl.com
- if (typeof mixpanel !== 'undefined')
- mixpanel = {
- track: () => {},
- identify: () => {}
- }
-
- try {
- const tmpuserlink = $('.player-dropdown a[href^="/player/"]')
- username = tmpuserlink.querySelector('strong').textContent
- userid = tmpuserlink.href.match(/\/player\/(\d+)\//)[1]
- localStorage.setItem('gpe_lastSeenName', username)
- localStorage.setItem('gpe_lastSeenId', userid)
- } catch (e) {}
-
- const insandbox = loc.match(/drawception\.com\/sandbox\/#?(.*)/)
- const inplay = loc.match(/drawception\.com\/(:?contests\/)?play\/(.*)/)
- if (options.newCanvas) {
- const hasCanvas = document.getElementById('canvas-holder')
- // If created a friend game, the link won't present playable canvas
- const hasCanvasOrGameForm = $('.playtimer')
- const captioncontest = loc.match(/contests\/play\//) && !hasCanvas
- if (!captioncontest && (insandbox || (inplay && hasCanvasOrGameForm)) /* || __DEBUG__*/) {
- setTimeout(() => {
- setupNewCanvas(insandbox, loc, document.body.innerHTML)
- }, 1)
- return
- }
- }
- /*else {
- if (insandbox || inplay || __DEBUG__) {
- enhanceCanvas(insandbox)
- }
- if (inplay || __DEBUG__) {
- empowerPlay()
- }
- }*/
- if (loc.match(/drawception\.com\/game\//)) {
- betterGame()
- }
- if (loc.match(/drawception\.com\/panel\//)) {
- betterPanel()
- }
- if (loc.match(/drawception\.com\/player\//)) {
- betterPlayer()
- }
- if (loc.match(/drawception\.com\/forums\//)) {
- betterForum()
- }
- if (loc.match(/drawception\.com\/settings\//)) {
- addScriptSettings()
- }
- if (loc.match(/drawception\.com\/create/)) {
- betterCreateGame()
- }
- GM_addStyle('.panel-user {width: auto} .panel-details img.loading {display: none}' + '.gpe-wide, .gpe-wide-block {display: none}' + '.gpe-btn {padding: 5px 8px; height: 28px}' + '.gpe-spacer {margin-right: 7px; float:left}' + '@media (min-width:992px) {.navbar-toggle,.btn-menu-player {display: none} .gpe-wide {display: inline} .gpe-wide-block {display: block}}' + '@media (min-width:1200px) {.gpe-btn {padding: 5px 16px;} .gpe-spacer {margin-right: 20px;} .panel-number {left: -30px}}' + '#anbtver {font-size: 10px; position:absolute; opacity:0.3; right:10px; top: 0;}' + '.anbt_paneldel {position:absolute; padding:1px 6px; color:#FFF; background:#d9534f; text-decoration: none !important; right: 18px; border-radius: 5px}' + '.anbt_paneldel:hover {background:#d2322d}' + '.anbt_favpanel {top: 20px; font-weight: normal; padding: 0 2px}' + '.anbt_favpanel:hover {color: #d9534f; cursor:pointer}' + '.anbt_favedpanel {color: #d9534f; border-color: #d9534f}' + '.anbt_replaypanel {top: 55px; font-weight: normal; padding: 0 8px}' + '.anbt_replaypanel:hover {color: #8af; text-decoration: none}' + ".anbt_owncaption:before {content: ''; display: inline-block; background: #5C5; border: 1px solid #080; width: 10px; height: 10px; border-radius: 10px; margin-right: 10px;}" + '.gamepanel, .thumbpanel, .comment-body {word-wrap: break-word}' + '.comment-body img {max-width: 100%}' + '.forum-thread.anbt_hidden {display: none}' + '.anbt_showt .forum-thread.anbt_hidden {display: block; opacity: 0.6}' + ".anbt_unhidet:after {content: ' threads hidden. Show'}" + ".anbt_showt .anbt_unhidet:after {content: ' threads hidden. Hide'}" + ".anbt_hft:after {content: '[hide]'}" + '.anbt_hft, .anbt_unhidet {padding-left: 0.4em; cursor:pointer}' + ".forum-thread.anbt_hidden .anbt_hft:after {content: '[show]'}" + '.anbt_threadtitle {margin: 0 0 10px}' + '.avatar {box-sizing: content-box}' + '.pagination {margin: 0px}' + '#nav-drag {position: fixed; width: 100%; z-index: 2000}' + '#header-bar-container {position: relative; width: 100%; top: 6rem}' + '.wrapper {position: relative; top: 6rem}' + 'footer {position: relative; top: 6rem}' + '.option span:first-child {display: flex; flex-direction: row; justify-content: space-between}' + '.grid-settings div[class^="grid-"] label {display: inline-flex}' + 'input[type="checkbox"], input[type="radio"] {margin:4px 4px 0 0}' + '@-moz-document url-prefix() {input[type="checkbox"], input[type="radio"] {margin:0 4px 0 0}}' + '.tooltip {z-index: 3000;}')
- if (options.maxCommentHeight) {
- const h = options.maxCommentHeight
- GM_addStyle(`.comment-holder[id]:not(:target) .comment-body {overflow-y: hidden; max-height: ${h}px; position:relative}.comment-holder[id]:not(:target) .comment-body:before{content: 'Click to read more'; position:absolute; width:100%; height:50px; left:0; top:${h - 50}px;text-align: center; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; padding-top: 20px; background:linear-gradient(transparent, rgba(0,0,0,0.4))}`)
- $('.comment-body', true).forEach(x =>
- x.addEventListener('click', () => {
- if (x.clientHeight > h - 50 && location.hash.indexOf(x) === -1) location.hash = `#${x.parentNode.parentNode.id}`
- })
- )
- }
- if (options.useOldFontSize) {
- document.body.style.fontSize = '15px'
- }
- if (options.useOldFont) {
- const nunito = $("link[href*='Nunito']")
- nunito.parentNode.removeChild(nunito)
- GM_addStyle("@font-face { font-family: 'Nunito'; font-style: normal; font-weight: 400; src: local('Nunito Regular'), local('Nunito-Regular'), " + 'url(data:application/font-woff;base64,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) format('woff');}")
- }
- if (options.anbtDarkMode) {
- if (document.body.classList.contains('theme-night')) {
- document.body.classList.remove('theme-night')
- setCookie('theme-night')
- }
- }
- if (options.markdownTools) {
- const markdown = {
- bold: {
- title: 'bold text',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = new RegExp(`\\*\\*(${sel.replace(/\*/g, '')})\\*\\*`)
- if (sel.match(selRegex)) {
- sel = sel.replace(selRegex, '$1')
- } else if (start > 0 && end < len) {
- if (val.substring(start - 1, end + 1).match(selRegex)) {
- start -= 1
- end += 1
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else if (val.substring(start - 2, end + 2).match(selRegex)) {
- start -= 2
- end += 2
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(/\*\*.+\*\*/g)) {
- sel = sel.replace(/\*\*/g, '')
- } else {
- sel = `**${sel.replace(/\n/g, '**\n**')}**`
- }
- }
- } else {
- if (!start && val.substring(start, end + 1).match(selRegex)) {
- end++
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else if (end === len && val.substring(start - 1, end).match(selRegex)) {
- start--
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(/\*\*.+\*\*/g)) {
- sel = sel.replace(/\*\*/g, '')
- } else {
- sel = `**${sel.replace(/\n/g, '**\n**')}**`
- }
- }
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- italic: {
- title: 'italic text',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = new RegExp(`\\*(?=\\S*${sel.replace(/(.*)\*(.*)/g, '')})((?:\\*\\*|\\\\[\\s\\S]|\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|[^\\s\\*\\\\])+?)\\*(?!\\*)`)
- const italicRegex = /\*(?=\S)((?:\*\*|\\[\s\S]|\s+(?:\\[\s\S]|[^\s\*\\]|\*\*)|[^\s\*\\])+?)\*(?!\*)/g
- if (sel.match(selRegex)) {
- sel = sel.replace(selRegex, '$1')
- } else if (start > 0 && end < len) {
- if (val.substring(start - 1, end + 1).match(selRegex)) {
- start -= 1
- end += 1
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(italicRegex)) {
- sel = sel.replace(italicRegex, '$1')
- } else {
- sel = `*${sel.replace(/\n/g, '*\n*')}*`
- }
- }
- } else {
- if (!start && val.substring(start, end + 1).match(selRegex)) {
- end++
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else if (end === len && val.substring(start - 1, end).match(selRegex)) {
- start--
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(italicRegex)) {
- sel = sel.replace(italicRegex, '$1')
- } else {
- sel = `*${sel.replace(/\n/g, '*\n*')}*`
- }
- }
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- heading: {
- title: 'enlarges/reduces the text',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /^#+ .*/gm
- if (sel.match(selRegex)) {
- if (sel.match(/^#{2,} /gm)) {
- sel = sel.replace(/^# /gm, '')
- sel = sel.replace(/(^#*)# /gm, '$1 ')
- } else {
- sel = sel.replace(/^# /gm, '')
- }
- } else if (!start || val.substring(start - 1, end).match(/\n.*/gm)) {
- sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}###### ${sel.replace(/\n/g, '\n###### ')}`
- } else if (val.substring(start - 1, end).match(selRegex)) {
- start -= 4
- sel = val.substring(start, end).replace(/(^#*)# /gm, '$1 ')
- } else if (val.substring(start - 2, end).match(selRegex)) {
- start -= 5
- sel = val.substring(start, end).replace(/(^#*)# /gm, '$1 ')
- } else {
- sel = `\n###### ${sel.replace(/\n/g, '\n###### ')}`
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- strikethrough: {
- title: 'strikethrough text',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /~~((.*\W?)*)~~/
- if (sel.match(selRegex)) {
- sel = sel.replace(selRegex, '$1')
- } else if (start > 0 && end < len) {
- if (sel.match(selRegex)) {
- sel.replace(selRegex, '$1')
- } else if (val.substring(start - 1, end + 1).match(selRegex)) {
- start -= 1
- end += 1
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else if (val.substring(start - 2, end + 2).match(selRegex)) {
- start -= 2
- end += 2
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(/~~.+~~/g)) {
- sel = sel.replace(/~~/g, '')
- } else {
- sel = `~~${sel}~~`
- }
- }
- } else {
- if (!start && val.substring(start, end + 1).match(selRegex)) {
- end++
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else if (end === len && val.substring(start - 1, end).match(selRegex)) {
- start--
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(/~~.+~~/g)) {
- sel = sel.replace(/~~/g, '')
- } else {
- sel = `~~${sel}~~`
- }
- }
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- highlighter: {
- title: 'highlighted text',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = new RegExp(`\`(${sel.replace(/`/g, '')})\``)
- if (sel.match(selRegex)) {
- sel = sel.replace(selRegex, '$1')
- } else if (start > 0 && end < len) {
- if (val.substring(start - 1, end + 1).match(selRegex)) {
- start -= 1
- end += 1
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(/`.+`/g)) {
- sel = sel.replace(/`/g, '')
- } else {
- sel = `\`${sel.replace(/\n/g, '`\n`')}\``
- }
- }
- } else {
- if (!start && val.substring(start, end + 1).match(selRegex)) {
- end++
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else if (end === len && val.substring(start - 1, end).match(selRegex)) {
- start--
- sel = val.substring(start, end).replace(selRegex, '$1')
- } else {
- if (sel.match(/`.+`/g)) {
- sel = sel.replace(/`/g, '')
- } else {
- sel = `\`${sel.replace(/\n/g, '`\n`')}\``
- }
- }
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- 'list-ul': {
- title: 'unordered list',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /^( {3})*- (.*)/
- if (sel.match(selRegex)) {
- if (sel.match(/^ {3}/)) {
- sel = sel.replace(/^ {3}/gm, '')
- } else {
- sel = sel.replace(/^- /gm, '')
- }
- } else if (!start || val.substring(start - 1, end).match(/^\n.*/)) {
- sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}- ${sel.replace(/\n/g, '\n- ')}${val.substring(end, end + 2).match(/\n\n/) ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}`
- } else if (val.substring(start - 1, end).match(selRegex)) {
- start--
- sel = val.substring(start, end).replace(/( {3})*- /g, '$1 - ')
- } else if (val.substring(start - 2, end).match(selRegex)) {
- start -= 2
- sel = val.substring(start, end).replace(/( {3})*- /g, '$1 - ')
- } else {
- sel = `\n- ${sel.replace(/\n/g, '\n- ')}${val.substring(end, end + 2).match(/\n\n/) || end === len ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}`
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- 'list-ol': {
- title: 'ordered list',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /^( {3})*\d+\. (.*)/gm
- if (sel.match(selRegex)) {
- if (sel.match(/^ {3}/)) {
- sel = sel.replace(/^ {3}/gm, '')
- } else {
- sel = sel.replace(/^\d+\. /gm, '')
- }
- } else if (!start || val.substring(start - 1, end).match(/^\n.*/)) {
- let countOl = 0
- sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}0. ${sel.replace(/\n/g, () => {
- countOl++
- return `\n${countOl}. `
- })}${val.substring(end, end + 2).match(/\n\n/) ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}`
- } else if (val.substring(start - 4, end).match(/( {3})*\d+\. (.*)/)) {
- start -= 4
- sel = val.substring(start, end).replace(/( {3})*(\d+\.) /g, ' $1$2 ')
- } else if (val.substring(start - 5, end).match(/( {3})*\d+\. (.*)/)) {
- start -= 5
- sel = val.substring(start, end).replace(/( {3})*(\d+\.) /g, ' $1$2 ')
- } else {
- let countOl = 0
- sel = `\n0. ${sel.replace(/\n/g, () => {
- countOl++
- return `\n${countOl}. `
- })}${val.substring(end, end + 2).match(/\n\n/) || end === len ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}`
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- 'quote-right': {
- title: 'quote',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /^>+\s.*/gm
- if (sel.match(selRegex)) {
- if (sel.match(/^> /gm)) {
- sel = sel.replace(/^> /gm, '')
- } else {
- sel = sel.replace(/(^>*)> /gm, '$1 ')
- }
- } else if (!start || val.substring(start - 1, end).match(/^\n.*/)) {
- sel = `${val.substring(start - 1, end).match(/\n^.*/gm) || !start ? '' : '\n'}> ${sel.replace(/\n/g, '\n> ')}${val.substring(end, end + 2).match(/\n\n/) ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}`
- } else if (val.substring(start - 1, end).match(selRegex)) {
- start--
- sel = val.substring(start, end).replace(/(^>*)\s/gm, '$1> ')
- } else if (val.substring(start - 2, end).match(selRegex)) {
- start -= 2
- sel = val.substring(start, end).replace(/(^>*)\s/gm, '$1> ')
- } else {
- sel = `\n> ${sel.replace(/\n/g, '\n> ')}${val.substring(end, end + 2).match(/\n\n/) || end === len ? '' : val.substring(end, end + 1).match(/\n/) ? '\n' : '\n\n'}`
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- code: {
- title: 'block of code',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = new RegExp(`^ {4}(.*)`, 'gm')
- if (sel.match(selRegex)) {
- sel = sel.replace(/^ {4}/gm, '')
- } else if (start === 0 || val.substring(start - 1, end).match(/\n.*/gm)) {
- if (sel.match(/^ {4}/gm)) {
- sel = sel.replace(/^ {4}/gm, '')
- } else {
- sel = `${start === 0 ? '' : val.substring(start - 1, end).match(/^\n/) ? '\n' : '\n\n'} ${sel.replace(/\n/g, '\n ')}`
- }
- } else {
- sel = `${val.substring(start - 1, end).match(/^\n/) ? '\n' : '\n\n'} ${sel.replace(/\n^(.*)/gm, '\n $1')}${val.substring(end, end + 1).match(/\n/) ? '' : '\n'}`
- }
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- }
- },
- link: {
- title: 'insert link',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /^(?!!)\[(.*)\]\((\S*)( ".*")?\)/
- if (sel.match(selRegex)) {
- textarea.value = val.substring(0, start) + sel.replace(selRegex, '$1 $2') + val.substring(end, len)
- } else {
- let link = ''
- if (!sel.match(/!\[(.*)\]\((\S*)( ".*")?\)/)) {
- link = sel.match(/https?:\/\/\S*/) || ''
- sel = sel
- .replace(link[0], '')
- .replace(/ +/g, ' ')
- .trim()
- }
- const divModal = $(``)
- $('.navbar-header>div:last-child').append(divModal)
- setTimeout(() => {
- document.body.classList.add('v--modal-block-scroll')
- $('#markdown').style.opacity = 1
- }, 1)
- $('#markdown-text').value = sel ? sel : ''
- $('#markdown-link').value = link ? link[0] : ''
- $('.close').addEventListener('click', () => {
- document.body.classList.remove('v--modal-block-scroll')
- $('#markdown').outerHTML = ''
- })
- $('#markdown-done').addEventListener('click', () => {
- sel = `[${$('#markdown-text').value}](${$('#markdown-link').value}${$('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''})`
- textarea.value = val.substring(0, start) + sel + val.substring(end, len)
- document.body.classList.remove('v--modal-block-scroll')
- $('#markdown').outerHTML = ''
- })
- }
- }
- },
- image: {
- title: 'insert image',
- replaceFunc: (val, len, start, end, sel) => {
- const selRegex = /!\[(.*)\]\((\S*)( ".*")?\)/
- if (sel.match(selRegex)) {
- textarea.value = val.substring(0, start) + sel.replace(selRegex, '$1 $2') + val.substring(end, len)
- } else {
- let link = ''
- if (!sel.match(/\[(.*)\]\((\S*)( ".*")?\)/)) {
- link = sel.match(/https?:\/\/\S*/) || ''
- sel = sel
- .replace(link[0], '')
- .replace(/ +/g, ' ')
- .trim()
- } else {
- sel = ''
- }
- const divModal = $(``)
- $('.navbar-header>div:last-child').append(divModal)
- setTimeout(() => {
- document.body.classList.add('v--modal-block-scroll')
- $('#markdown').style.opacity = 1
- }, 1)
- $('#markdown-text').value = sel ? sel : ''
- $('#markdown-link').value = link ? link[0] : ''
- $('.close').addEventListener('click', () => {
- document.body.classList.remove('v--modal-block-scroll')
- $('#markdown').outerHTML = ''
- })
- $('#markdown-done').addEventListener('click', () => {
- const tag = `.value}${$('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''})`
- sel = val.substring(start, end)
- if (!sel.match(/\[(.*)\]\((\S*)( ".*")?\)/)) {
- textarea.value = val.substring(0, start) + tag + val.substring(end, len)
- } else {
- textarea.value = val.substring(0, start) + sel.replace(/\[.*\]/, `[${tag}]`) + val.substring(end, len)
- }
- document.body.classList.remove('v--modal-block-scroll')
- $('#markdown').outerHTML = ''
- })
- }
- }
- }
- }
- const textarea = $('#input-comment')
- if (textarea) {
- const getSelectedText = e => {
- const val = textarea.value
- const len = val.length
- const start = textarea.selectionStart
- const end = textarea.selectionEnd
- const sel = val.substring(start, end)
- markdown[`${e.target.id}`].replaceFunc(val, len, start, end, sel)
- }
- const markdownDiv = $('')
- Object.keys(markdown).forEach(x => markdownDiv.appendChild($(``)))
- textarea.insertAdjacentHTML('beforebegin', markdownDiv.outerHTML)
- ;[...$('#markdown-editor').children].forEach(x => x.addEventListener('click', getSelectedText))
- }
- }
- // Enhance menu for higher resolutions
- let p = $('.navbar-toggle').parentNode
-
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- p.appendChild($(''))
- // Let users with screens narrow enough so top bar isn't visible still use toggle light function
- $('#main-menu').insertAdjacentHTML('afterbegin', ' Toggle light')
-
- p = $('.btn-menu-player')
- if (p) {
- const userlink = $('.player-dropdown a[href^="/player/"]').href
- const useravatar = $('.btn-menu-player').innerHTML
- const element = $(`${useravatar}`)
- p.parentNode.appendChild(element)
- }
-
- // Tell to look at settings if freshly installed
- /*const newSettingsSeen = localStorage.getItem('anbt_newSettingsSeen')
- if (!newSettingsSeen && window.innerWidth > 974) {
- const freshSettingsHint = 'Thanks for choosing ANBT! Script settings are on the settings page, click to remove this hint.'
- if (!options.newCanvas) {
- freshSettingsHint += " Don't forget to try the new canvas!"
- }
- $('#menusettings').setAttribute('title', '')
- $("#menusettings").tooltip({
- container: "body",
- placement: "bottom",
- title: freshSettingsHint
- });
- //$("#menusettings").tooltip("show");
- const freshHintRemove = () => {
- localStorage.setItem('anbt_newSettingsSeen', 1)
- }
- $('#menusettings').addEventListener('click', freshHintRemove)
- $('#menusettings').addEventListener('mousedown', freshHintRemove)
- $('#menusettings').addEventListener('touchstart', freshHintRemove)
- }*/
- // Make new notifications actually discernable from the old ones
- let num = $('#user-notify-count')
- if (num) num = num.textContent.trim()
- GM_addStyle(`#user-notify-list .list-group .list-group-item .fas {color: #888}#user-notify-list .list-group .list-group-item:nth-child(-n+${num}) .fas {color: #2F5}a.wrong-order {color: #F99} div.comment-holder:target {background-color: #DFD}.comment-new a.text-muted:last-child:after {content: 'New'; color: #2F5; font-weight: bold; background-color: #183; border-radius: 9px; display: inline-block; padding: 0px 6px; margin-left: 10px;}`)
-
- // Show an error if it occurs instead of "loading forever"
- window.getNotifications = () => {
- if (!notificationsOpened) {
- $('#user-notify-list').innerHTML = '
'
- const xhr = new XMLHttpRequest()
- xhr.open('GET', '/notification/view/')
- xhr.onload = () => {
- if (xhr.status === 200) {
- $('#user-notify-list').innerHTML = xhr.responseText
- $('#user-notify-count').textContent = '0'
- notificationsOpened = true
- } else {
- '#user-notify-list'.innerHTML = xhr.responseText
- notificationsOpened = true
- }
- }
- }
- }
-
- let versionDisplay
- try {
- const appver = $('script[src^="/build/app"]').src.match(/(\w+)\.js$/)[1]
- const runtimever = $('script[src^="/build/runtime"]').src.match(/(\w+)\.js$/)[1]
- versionDisplay = `ANBT v${SCRIPT_VERSION} | app ${appver}`
- if (appver !== SITE_VERSION) versionDisplay += '*'
- versionDisplay += ` | runtime ${runtimever}`
- if (runtimever !== '1ba6bf05') versionDisplay += '*!!!' // didn't break with one update, hurray
- } catch (e) {
- versionDisplay = `ANBT v${SCRIPT_VERSION}`
- }
- const h = $('.wrapper')
- if (h) h.appendChild($(`${versionDisplay}
`))
-
- $('.footer-main .list-unstyled')[0].appendChild($('ANBT script'))
- $('.footer-main .list-unstyled')[1].appendChild($('Wiki'))
- $('.footer-main .list-unstyled')[2].appendChild($('Chat (Discord)'))
-
- if (options.newCanvas) {
- const directToNewSandbox = function(e) {
- if (e.which === 2) return
- e.preventDefault()
- setupNewCanvas(true, this.href)
- }
- const directToNewPlay = function(e) {
- if (e.which === 2) return
- e.preventDefault()
- setupNewCanvas(false, this.href)
- }
- $('a[href^="/sandbox/"]', true).forEach(x => x.addEventListener('click', directToNewSandbox))
- $('a[href="/play/"]', true).forEach(x => x.addEventListener('click', directToNewPlay))
- }
- }
-
- const mark = document.createElement('b')
- mark.id = '_anbt_'
- mark.style.display = 'none'
- document.body.appendChild(mark)
- if (pagodaBoxError()) return
-
- if (typeof DrawceptionPlay === 'undefined') {
- // Fix for Chrome new loading algorithm, apparently
- let loader = setInterval(() => {
- if (typeof DrawceptionPlay === 'undefined') return
- pageEnhancements()
- clearInterval(loader)
- }, 100)
- } else {
- pageEnhancements()
- }
-} // wrapped
-
-// From http://userstyles.org/styles/93911/dark-gray-style-for-drawception-com
-localStorage.setItem(
- 'gpe_darkCSS',
- (
- 'a{color:#77c0ff$}.wrapper{~#444$}#nav-drag{~#353535$}.btn-default{~#7f7f7f$;border-bottom-color:#666$;border-left-color:#666$;border-right-color:#666$;border-top-color:#666$;color:#CCC$}' +
- '.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{~#757575$;border-bottom-color:#565656$;border-left-color:#565656$;border-right-color:#565656$;border-top-color:#565656$;color:#DDD$}' +
- '.btn-success{~#2e2e2e$;border-bottom-color:#262626$;border-left-color:#262626$;border-right-color:#262626$;border-top-color:#262626$;color:#CCC$}' +
- '.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{~#232323$;border-bottom-color:#1c1c1c$;border-left-color:#1c1c1c$;border-right-color:#1c1c1c$;border-top-color:#1c1c1c$;color:#DDD$}' +
- '.btn-primary{~#213184$;border-bottom-color:#1a1a68$;border-left-color:#1a1a68$;border-right-color:#1a1a68$;border-top-color:#1a1a68$;color:#CCC$}' +
- '.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{~#191964$;border-bottom-color:#141450$;border-left-color:#141450$;border-right-color:#141450$;border-top-color:#141450$;color:#DDD$}' +
- '.btn-info{~#2d7787$;border-bottom-color:#236969$;border-left-color:#236969$;border-right-color:#236969$;border-top-color:#236969$;color:#CCC$}' +
- '.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{~#1c5454$;border-bottom-color:#133939$;border-left-color:#133939$;border-right-color:#133939$;border-top-color:#133939$;color:#DDD$}' +
- '.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{~#3b3b3b$}.navbar-toggle{~#393939$}.navbar{border-bottom:1px solid #000$}.forum-thread-starter,.breadcrumb,.regForm{~#555$}' +
- '.form-control{~#666$;border:1px solid #333$;color:#EEE$}code,pre{~#656$;color:#FCC$}body{color:#EEE$}footer{~#333$;border-top:1px solid #000$}' +
- '.pagination>li:not(.disabled):not(.active),.pagination>li:not(.disabled):not(.active)>a:hover,.pagination>li:not(.disabled):not(.active)>span:hover,.pagination>li:not(.disabled):not(.active)>a:focus,.pagination>li:not(.disabled):not(.active)>span:focus{~#444$}.pagination>li>a,.pagination>li>span{~#555$;border:1px solid #000$}' +
- '.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{~#666$;border-top:1px solid #444$;border-bottom:1px solid #444$}' +
- '.drawingForm{~#555$}.well{~#666$;border:1px solid #333$}#timeleft{color:#AAA$}legend{border-bottom:1px solid #000$}.thumbpanel{color:#EEE;~#555$}.thumbpanel img{~#fffdc9$}.panel-number,.modal-content,.profile-user-header{~#555$}' +
- '#commentForm{~#555$;border:1px solid #000$}.modal-header,.nav-tabs{border-bottom:1px solid #000$}hr,.modal-footer{border-top:1px solid #000$}' +
- '.store-item{background:#666$;~-moz-linear-gradient(top,#666 0,#333 100%)$;~-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(100%,#333))$;~-webkit-linear-gradient(top,#666 0,#333 100%)$;~-o-linear-gradient(top,#666 0,#333 100%)$;~-ms-linear-gradient(top,#666 0,#333 100%)$;~linear-gradient(to bottom,#666 0,#333 100%)$;border:1px solid #222$}' +
- '.store-item:hover{border:1px solid #000$}.store-item-title{~#222$;color:#DDD$}.store-title-link{color:#DDD$}.profile-award{~#222$}.profile-award-unlocked{~#888$}.progress-bar{color:#CCC$;~#214565$}.progress{~#333$}' +
- '.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(0,0,0,0.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(0,0,0,0.15)),color-stop(.75,rgba(0,0,0,0.15)),color-stop(.75,transparent),to(transparent))$;background-image:-webkit-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:-moz-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$}' +
- '.progress-bar-success{~#363$}.progress-bar-info{~#367$}.progress-bar-warning{~#863$}.progress-bar-danger{~#733$}' +
- '.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#DDD$;~#555$;border:1px solid #222$}.nav>li>a:hover,.nav>li>a:focus{~#333$;border-bottom-color:#222$;border-left-color:#111$;border-right-color:#111$;border-top-color:#111$}' +
- '.nav>li.disabled>a,.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#555$}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{~#333$}' +
- '.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{~#555$}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{border-top:1px solid #333$}.news-alert{~#555$;border:2px solid #444$}' +
- '.btn-menu{~#2e2e2e$}.btn-menu:hover{~#232323$}.btn-yellow{~#8a874e$}.btn-yellow:hover{~#747034$}' +
- 'a.label{color:#fff$}.text-muted,a.text-muted{color:#999$}a.wrong-order{color:#F99$}div.comment-holder:target{~#454$}' +
- '.popover{~#777$}.popover-title{~#666$;border-bottom:1px solid #444$}.popover.top .arrow:after{border-top-color:#777$}.popover.right .arrow:after{border-right-color:#777$}.popover.bottom .arrow:after{border-bottom-color:#777$}.popover.left .arrow:after{border-left-color:#777$}' +
- '.label-fancy{~#444$;border-color:#333$;color:#FFF$}' +
- '.avatar,.profile-avatar{~#444$;border:1px solid #777$;}' +
- '.bg-lifesupport{~#444$}body{~#555$}.snap-content{~#333$}' +
- 'select,textarea{color:#000$}.help-block{color:#ddd$}.jumbotron{~#444$}' +
- '.navbar-dropdown{~#444$}a.list-group-item{~#444$;color:#fff$;border:1px solid #222$}a.list-group-item:hover,a.list-group-item:focus{~#222$}' +
- '.likebutton.btn-success{color:#050$;~#5A5$}.likebutton.btn-success:hover{~#494$}' +
- ".thumbnail[style*='background-color: rgb(255, 255, 255)']{~#555$}" +
- '.popup,.v--modal{~#666$;border:1px solid #222$}.btn-reaction{~#666$;border:none$;color:#AAA$}@media(min-width:625px){.create-game-wrapper{~#555$}}' +
- '.profile-header{~#555$}.profile-nav > li > a{~#333$}.profile-nav>li.active>a,.profile-nav>li>a:hover{~#555$}' +
- '.gsc-control-cse{~#444$;border-color:#333$}.gsc-above-wrapper-area,.gsc-result{border:none$}.gs-snippet{color:#AAA$}.gs-visibleUrl{color:#8A8$}a.gs-title b,.gs-visibleUrl b{color:#EEE$}.gsc-adBlock{display:none$}.gsc-input{~#444$;border-color:#333$;color:#EEE$}' +
- '.comment-highlight{border:none$;background:#454$}#header-emotes{~#555$}#header-bar-container{border:none$}.paypal-button-tag-content{color:#EEE$}.numlikes{color:#EEE$}.gsc-input-box{~#444$;border-color:#333$}.gsc-completion-container{~#333$;border-color:#000$}.gsc-completion-selected{~#222$}.gsc-completion-container b{color:#AAA$}.alert-nice{~#4a4a4a$}.store-buy-coins{~#777$}.store-buy-coins:hover{~#666$}.store-buy-coins>h2,.store-buy-coins>h2>small{color:#EEE$}.store-package-selector{~#888$}.store-package-selector>label{color:#EEE$}.label-stat{~#444$;color:#EEE$;border:1px solid #555$}.label-stat.disabled{~#333$}.option{padding:4px 8px$;~#666$;color:#EEE$;border-color:#333$}.option.selected{border-color:#EEE$}.sleek-select{~#666$;border-color:#333$}select{color:#EEE$}.modal-note{color:#EEE$}.vue-dialog-button{~#555$;border:none$}.vue-dialog-button:hover{~#5a5a5a$}.vue-dialog-buttons{border-top:1px solid #222$}.dashboard-item{~#333$}legend{color:#EEE$}.list-group-item{~#444$;color:#EEE$;border:1px solid #222$}.alert-warning{color:#EEE$;~#555$;border-color:#555$}.btn-reaction.active{border:1px solid #EEE$}.bg-shadow-box{~#333$}.btn-gray{~#222$;border:none$}.btn-gray:hover{color:#EEE$;~#1a1a1a$}.btn-bright{~#333$;color:#EEE$}' +
- '.player-name-new{color:#33b73f$}.gsc-tabsArea>div{overflow:hidden$}.gs-image-popup-box{~#333$;border-color:#222$}.gs-size{color:#6f6f6f$}.gsc-result-info{color:#EEE$}.gsc-refinementsArea{border:none$}.gsc-tabsArea{border-bottom-color:#333$}.gsc-cursor-page{color:#EEE$}.gsc-cursor-current-page{color:#AAA$}.profile-nav>.disabled>a{color:#555$;~#3a3a3a$}.profile-nav>.disabled>a:hover{~#3a3a3a$}.sleek-select:hover{border-color:#EEE$}' +
- '.btn-menu{border-color:#1e1e1e$;text-shadow:0px 0px 3px #777$}.btn-menu:hover{border-color:#1e1e1e$}.pagination>.active>span{color:#EEE$}#btn-notifications{color:#EEE$}.btn-warning{color:#EEE$}.alert-nice{color:#EEE$}.emotes-popup{~#2e2e2e$}.navbar-toggle{~#2e2e2e$;border-color:#1e1e1e$}.navbar-toggle .icon-bar{~#EEE$}' +
- '.gamepanel-highlight{box-shadow:0 0 20px #111$;~#222$}' +
- // We have entered specificity hell...
- 'a.anbt_replaypanel:hover{color:#8af$}' +
- '.anbt_favedpanel{color:#d9534f$}' +
- // Some lamey compression method!
- ''
- )
- .replace(/~/g, 'background-color:')
- .replace(/\$/g, ' !important')
-)
-
-let settings = localStorage.getItem('gpe_anbtSettings')
-settings = settings ? JSON.parse(settings) : {}
-
-if (settings.anbtDarkMode || typeof settings.anbtDarkMode === 'undefined') {
- if (parseInt(localStorage.getItem('gpe_inDark'), 10) == 1) {
- const css = document.createElement('style')
- css.id = 'darkgraycss'
- css.type = 'text/css'
- css.appendChild(document.createTextNode(localStorage.getItem('gpe_darkCSS')))
- if (document.head) {
- document.head.appendChild(css)
- } else {
- let darkLoad = setInterval(() => {
- if (!document.head) return
- document.head.appendChild(css)
- clearInterval(darkLoad)
- }, 100)
- }
- }
-}
-
-const anbtLoad = () => {
- if (document.getElementById('_anbt_')) return
- const script = document.createElement('script')
- script.textContent = `(${wrapped.toString()})()`
- document.body.appendChild(script)
-}
-
-if (document && document.body) {
- anbtLoad()
- if (window.opera && parseInt(localStorage.getItem('gpe_operaWarning'), 10) !== 1) {
- const w = document.createElement('h2')
- w.innerHTML = 'ANBT speaking:
Rename your script file so it doesn\'t contain ".user." part for smoother loading!
This warning is only shown once.'
- const m = document.getElementById('main')
- m.insertBefore(w, m.firstChild)
- localStorage.setItem('gpe_operaWarning', 1)
- }
-}
-document.addEventListener('DOMContentLoaded', anbtLoad, false)
diff --git a/newcanvas/drawit.css b/newcanvas/drawit.css
deleted file mode 100644
index d4cec62..0000000
--- a/newcanvas/drawit.css
+++ /dev/null
@@ -1,526 +0,0 @@
-@font-face {
- font-family: 'Yanone Kaffeesatz';
- font-style: normal;
- font-weight: 400;
- src: local('Yanone Kaffeesatz Regular'), local('YanoneKaffeesatz-Regular'), url('yanone.woff') format('woff');
-}
-@font-face {
- font-family: 'Nunito';
- font-style: normal;
- font-weight: 400;
- src: local('Nunito-Regular'), url('nunito.woff') format('woff');
-}
-body {
- margin: 0;
- font-family: 'Nunito', sans-serif;
- background: #555;
-}
-.noselect {
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
-}
-#newcanvasyo {
- background: #555;
-}
-#newcanvasyo.play .onlysandbox,
-#newcanvasyo.sandbox .onlyplay {
- display: none;
-}
-#myheader {
- font-size: 15pt;
- line-height: 49px;
- height: 49px;
- border-bottom: 1px solid black;
- background: #333;
- text-align: center;
- vertical-align: middle;
- color: #999;
- overflow: hidden;
- display: block;
-}
-a {
- color: #999;
-}
-a.prominent {
- color: #7af;
-}
-a.prominent:hover {
- color: #9ff;
-}
-#menu {
- display: none;
- position: absolute;
- background: #666;
- color: #ddd;
- z-index: 2;
- border: 2px solid #777;
- box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
- list-style-type: none;
- padding: 0;
- margin: 0;
- font-size: 13pt;
- text-align: left;
-}
-#menu.open {
- display: block;
-}
-#menu li {
- line-height: 49px;
- height: 49px;
- padding: 0 10px;
- cursor: pointer;
-}
-#menu li:hover {
- background: #777;
-}
-#menu hr {
- margin: 5px;
- border: 1px solid #444;
-}
-#noscriptwarn {
- color: #800;
- padding: 10px;
- font-size: 15pt;
- background: #faa;
- text-align: center;
-}
-#drawthis {
- padding: 4px;
- color: #fff;
- font-size: 26pt;
- font-family: 'Yanone Kaffeesatz';
- text-align: center;
- vertical-align: middle;
- display: table-cell;
- width: 1%;
-}
-#drawthis:before {
- color: #aaa;
- font-size: 14pt;
- font-family: 'Nunito';
- content: 'Draw this:';
- padding-right: 1em;
- width: 1%;
- text-align: right;
- vertical-align: middle;
- margin-left: -10%;
-}
-#emptytitle {
- height: 10px;
-}
-#bl {
- width: 600px;
- margin: auto;
- position: relative;
-}
-#toolpane {
- text-align: center;
- padding: 0 10px;
-}
-#infopane {
- text-align: center;
- padding: 0 10px;
-}
-.panel {
- border: 2px solid #888;
- border-radius: 15px;
- border-top: none;
- background: #444;
- margin: 3px 0;
-}
-#timer {
- font-size: 26pt;
- color: #aaa;
- width: 176px;
- float: left;
-}
-#palettechooser {
- display: none;
- position: absolute;
- width: 300px;
- background: #666;
- color: #ddd;
- z-index: 1;
- -webkit-columns: 2;
- -moz-columns: 2;
- columns: 2;
- -webkit-column-gap: 0;
- -moz-column-gap: 0;
- column-gap: 0;
- border: 2px solid #777;
- box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
-}
-#palettechooser.open {
- display: block;
-}
-#palettechooser div {
- padding: 10px 0 20px;
- cursor: pointer;
- break-inside: avoid;
-}
-#palettechooser div:hover {
- background-color: #777;
- color: #eee;
-}
-#colors {
- width: 100px;
- float: left;
-}
-#colors b {
- float: left;
- display: block;
- width: 50px;
- height: 44px;
- line-height: 42px;
- cursor: pointer;
-}
-#colors b.hint:after {
- content: '';
- position: absolute;
- border-radius: 50%;
- margin: 32px 2px;
- width: 5px;
- height: 5px;
- background-color: white;
- border: 1px solid #000;
-}
-#colors b.hint:before {
- opacity: 0.6;
-}
-#colors b:before {
- font-size: 10pt;
- opacity: 0.3;
- color: #000;
-}
-#colors b:hover:before {
- opacity: 0.4;
- color: #fff;
-}
-#palettename {
- color: #aaa;
- font-size: 10pt;
- padding: 0 5px;
- cursor: pointer;
-}
-#palettename:hover {
- color: #ccc;
-}
-.sandbox #palettename:after {
- content: ' \25BC';
-}
-#colors #eraser:before {
- content: 'eraser';
-}
-#colors b {
- counter-increment: color;
-}
-#colors b:before {
- content: counter(color);
-}
-#colors b:nth-child(10) {
- counter-reset: color 1;
-}
-#colors b:nth-child(n+10) {
- counter-increment: color;
-}
-#colors b:nth-child(n+10):before {
- content: '\2191' counter(color);
-}
-#colors.setbackground b:before {
- content: 'BG ' counter(color);
-}
-#colors.setbackground b:nth-child(n+10):before {
- content: 'BG\2191' counter(color);
-}
-#colors.setbackground #eraser:before {
- content: 'cancel';
-}
-#colors div {
- clear: both;
-}
-#tools {
- width: 68px;
- float: right;
- padding: 5px 0;
-}
-#tools button, #openmenu {
- width: 55px;
- height: 44px;
- vertical-align: top;
- margin: 3px 0;
- padding: 0;
-}
-#brush0, #brush1, #brush2, #brush3,
-#setbackground, #undo, #redo, #trash,
-#play, #knob, #eraser, .eraser, #openmenu
-{
- background-image: url("newcanvasicons.png");
-}
-#brush0 { background-position: -2px -8px; }
-#brush1 { background-position: -62px -8px; }
-#brush2 { background-position: -122px -8px; }
-#brush3 { background-position: -182px -8px; }
-#brush0.sel { background-position: -2px -68px; }
-#brush1.sel { background-position: -62px -68px; }
-#brush2.sel { background-position: -122px -68px; }
-#brush3.sel { background-position: -182px -68px; }
-#setbackground { background-position: -242px -8px; }
-#setbackground.sel { background-position: -242px -68px; }
-#undo { background-position: -302px -8px; }
-#redo { background-position: -362px -8px; }
-#trash { background-position: -422px -8px; }
-#eraser { background-position: -365px -68px; background-color: pink; }
-.eraser { background-position: -425px -68px; background-color: pink; }
-#openmenu { background-position: -425px -68px; }
-button, #play {
- border: none;
- border-radius: 4px;
- background-color: #eee;
-}
-button:hover, #play:hover {
- background-color: #acf;
-}
-button:active, #play:active {
- background-color: #aaa;
-}
-button:disabled {
- background-color: #aaa;
-}
-#openmenu:after {
- content: "\2630";
- color: #fff;
- font-size: 16pt;
- opacity: 0.6;
-}
-#openmenu {
- background-color: #999;
-}
-#openmenu:hover {
- background-color: #57a;
-}
-#openmenu:active {
- background-color: #555;
-}
-#tools hr {
- border: 1px solid #888;
- width: 93%;
- margin: 8px auto;
-}
-#primary, #secondary {
- display:inline-block;
- width: 50px;
- border-radius: 0 0 0 13px;
- color: #fff;
-}
-#secondary {
- border-radius: 0 0 13px 0;
-}
-#gamemode {
- font-size: 13pt;
- color: #aaa;
- width: 176px;
- float: left;
-}
-#gamebuttons {
- font-size: 13pt;
- color: #aaa;
- width: 176px;
- float: left;
- padding: 5px 0;
-}
-#gamebuttons button {
- padding: 10px;
- margin: 3px 0;
- font-size: 10pt;
-}
-#submit {
- font-size: 16pt;
- width: 180px;
- height: 50px;
- background-color: #6d6;
-}
-#submit:hover {
- background-color: #1b7;
-}
-.guidelines {
- color: #fff;
- font-size: 10pt;
-}
-.guidelines ul {
- margin: 0;
- padding: 0 1em;
- text-align: left;
-}
-.guidelines li {
- color: #bbb;
-}
-#seekbar {
- width: 530px;
- height: 0px;
- margin: 20px 50px 0;
- background: #333;
- border: 10px solid #333;
- border-radius: 10px;
- cursor: pointer;
-}
-#knob {
- position: absolute;
- width: 48px;
- height: 38px;
- margin-top: -19px;
- margin-left: 492px; /* -10px min */
- border-radius: 10px;
- background-color: #fbb;
- background-position: -305px -71px;
-}
-#knob.smooth {
- transition: margin-left 0.1s ease-out;
-}
-#knob:hover {
- background-color: #fdd;
-}
-#play {
- background-position: -486px -11px;
- position: absolute;
- width: 48px;
- height: 38px;
- margin: -19px -59px;
- border-radius: 4px;
-}
-#play.pause {
- background-position: -546px -11px;
-}
-#wacomContainer {
- position: absolute;
- top: -10px;
- left: -10px;
-}
-@media screen and (min-width: 1000px) {
- #toolpane {
- width: 180px;
- height: 500px;
- position: absolute;
- margin-left: -200px;
- }
- #infopane {
- width: 180px;
- height: 500px;
- position: absolute;
- margin-left: 600px;
- top: 0;
- }
- #submit {
- position: absolute;
- bottom: 10px;
- left: 10px;
- width: 180px;
- height: 50px;
- }
-}
-@media screen and (max-width: 999px) {
- #myheader {
- font-size: 11pt;
- }
- #toolpane {
- width: 580px;
- margin: auto;
- }
- #toolpane:after {
- display: block;
- content: '';
- clear: both;
- }
- #infopane {
- width: 580px;
- margin: 40px 0;
- }
- #timer {
- float: right;
- width: 260px;
- }
- #colors {
- width: 300px;
- margin-left: 5px;
- clear: left;
- float: left;
- }
- #primary, #secondary {
- width: 150px;
- }
- #tools {
- width: 260px;
- }
- #tools button {
- margin: 0;
- }
- #gamebuttons {
- width: 390px;
- margin-left: 5px;
- }
-}
-#svgContainer {
- width: 600px;
- height: 500px;
- margin: auto;
- background: #fffdc9 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAVCAQAAAAJQsNPAAAAkUlEQVR4Xu2WgQbAMBBD9/8/vQ14KpC4UneqYurZ2eNGnnfouWLdsjwce59ikFCdk75ricGIQSEmhekcvRfE8s8jMJ9CdI7camLGYoQ0XzNjJukgRsaJkWFiKqhiLf8x2DAx6EExTV0Mns/sIPZnnBinJJb1h4p21BXrzSOpTZVF3VGCN4lB07U53+6H5Yr1zAdssNOdvbG/lgAAAABJRU5ErkJggg==') center no-repeat;
- cursor: crosshair;
-}
-#svgContainer.hidecursor {
- cursor: none;
-}
-#svgContainer.loading * {
- display: none;
-}
-#svgContainer canvas, #svgContainer svg {
- position: absolute;
-}
-#popup {
- display: none;
- position: absolute;
- width: 400px;
- height: 300px;
- margin: 50px;
- padding: 50px;
- opacity: 0.95;
- background: #444;
- color: #ddd;
- border-radius: 15px;
- text-align: center;
- z-index: 1;
- font-size: 20pt;
-}
-#popup.show {
- display: block;
-}
-#popup hr {
- border: 1px solid #888;
-}
-#popuptitle {
- color: #999;
- font-size: 25pt;
- margin: 10px;
-}
-#popupclose {
- display: block;
- position: absolute;
- width: 50px;
- height: 50px;
- background: #c44;
- color: #ddd;
- right: 0;
- top: 0;
- text-align: center;
- border-radius: 13px;
- cursor: pointer;
-}
-#popupclose:hover {
- background: #e77;
- color: #eee;
-}
-#popupclose:before {
- content: "\d7";
- font-size: 30pt;
- line-height: 40px;
-}
diff --git a/newcanvas/drawit.js b/newcanvas/drawit.js
deleted file mode 100644
index e1d03ba..0000000
--- a/newcanvas/drawit.js
+++ /dev/null
@@ -1,2761 +0,0 @@
-// Drawing in Time by Grom PE. Public domain.
-// Utilities
-function ID(id) {return document.getElementById(id);}
-function svgElement(name, attrs)
-{
- var el = document.createElementNS("http://www.w3.org/2000/svg", name);
- if (attrs)
- {
- var keys = Object.keys(attrs);
- for (var i = 0; i < keys.length; i++)
- {
- if (attrs[keys[i]] !== null) el.setAttribute(keys[i], attrs[keys[i]]);
- }
- }
- return el;
-}
-function require(script, callback)
-{
- var tag = document.querySelector('script[src="' + script + '"]');
- if (tag) return callback();
- tag = document.createElement("script");
- tag.src = script;
- tag.onload = callback;
- document.body.appendChild(tag);
-}
-function bytes2string(bytes)
-{
- var len = bytes.length;
- var arr = [];
- for (var i = 0; i < len; i++)
- {
- arr.push(String.fromCharCode(bytes[i]));
- }
- return arr.join("");
-}
-function string2bytes(binary_string)
-{
- var len = binary_string.length;
- var bytes = new Uint8Array(len);
- for (var i = 0; i < len; i++)
- {
- var ascii = binary_string.charCodeAt(i);
- bytes[i] = ascii;
- }
- return bytes;
-}
-function base642bytes(base64)
-{
- return string2bytes(atob(base64));
-}
-function node2string(node)
-{
- if ('outerHTML' in node)
- {
- return node.outerHTML;
- } else {
- var div = document.createElement("div");
- div.appendChild(node.cloneNode(true));
- return div.innerHTML;
- }
-}
-function unpack_uint16be(s)
-{
- return s.charCodeAt(0) << 8 | s.charCodeAt(1);
-}
-function unpack_int16be(s)
-{
- var v = unpack_uint16be(s);
- return v > 32767 ? v - 65536 : v;
-}
-function int16be(b1, b2)
-{
- var v = b1 << 8 | b2;
- return v > 32767 ? v - 65536 : v;
-}
-function unpack_uint32be(s)
-{
- return s.charCodeAt(0) << 24 | s.charCodeAt(1) << 16 | s.charCodeAt(2) << 8 | s.charCodeAt(3);
-}
-function pack_uint16be(n)
-{
- return String.fromCharCode(n >> 8 & 0xff, n & 0xff);
-}
-function pack_uint32be(n)
-{
- return String.fromCharCode(n >> 24 & 0xff, n >> 16 & 0xff, n >> 8 & 0xff, n & 0xff);
-}
-function color2rgba(color)
-{
- var r = 0, g = 0, b = 0, a = 255;
- if (color.substr(0, 1) == "#")
- {
- if (color.length == 4)
- {
- r = color.substr(1, 1);
- g = color.substr(2, 1);
- b = color.substr(3, 1);
- r = parseInt(r + r, 16);
- g = parseInt(g + g, 16);
- b = parseInt(b + b, 16);
- } else {
- r = parseInt(color.substr(1, 2), 16);
- g = parseInt(color.substr(3, 2), 16);
- b = parseInt(color.substr(5, 2), 16);
- }
- } else if (color.substr(0, 4) == "rgba")
- {
- var tmp = color.split(/([\d\.]+)/);
- r = parseInt(tmp[1], 10);
- g = parseInt(tmp[3], 10);
- b = parseInt(tmp[5], 10);
- a = Math.floor(parseFloat(tmp[7]) * 255);
- }
- else if (color.substr(0, 3) == "rgb")
- {
- var tmp = color.split(/([\d\.]+)/);
- r = parseInt(tmp[1], 10);
- g = parseInt(tmp[3], 10);
- b = parseInt(tmp[5], 10);
- } else {
- // ?!
- }
- return [r, g, b, a];
-}
-function color2dword(color)
-{
- var c = color2rgba(color);
- return String.fromCharCode(c[0], c[1], c[2], c[3]);
-}
-function value2hex(val)
-{
- return (Math.floor(val/16)%16).toString(16)+(Math.floor(val)%16).toString(16);
-}
-function rgb2hex(r, g, b)
-{
- return "#" + value2hex(r) + value2hex(g) + value2hex(b);
-}
-function color2hex(color)
-{
- var c = color2rgba(color);
- return rgb2hex(c[0], c[1], c[2]);
-}
-function randomItem(l)
-{
- return l[Math.floor(Math.random() * l.length)];
-}
-function makeCRCTable()
-{
- var c;
- var crcTable = [];
- for(var n = 0; n < 256; n++)
- {
- c = n;
- for(var k = 0; k < 8; k++)
- {
- c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
- }
- crcTable[n] = c;
- }
- return crcTable;
-}
-function crc32(str, str2)
-{
- var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
- var crc = 0 ^ (-1);
- for (var i = 0; i < str.length; i++)
- {
- crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
- }
- if (str2)
- {
- for (var i = 0; i < str2.length; i++)
- {
- crc = (crc >>> 8) ^ crcTable[(crc ^ str2.charCodeAt(i)) & 0xFF];
- }
- }
- return (crc ^ (-1)) >>> 0;
-}
-/*
- (c) 2013, Vladimir Agafonkin
- Simplify.js, a high-performance JS polyline simplification library
- mourner.github.io/simplify-js
-*/
-// square distance between 2 points
-function getSqDist(p1, p2)
-{
- var dx = p1.x - p2.x,
- dy = p1.y - p2.y;
- return dx * dx + dy * dy;
-}
-// square distance from a point to a segment
-function getSqSegDist(p, p1, p2)
-{
- var x = p1.x,
- y = p1.y,
- dx = p2.x - x,
- dy = p2.y - y;
- if (dx !== 0 || dy !== 0)
- {
- var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
- if (t > 1)
- {
- x = p2.x;
- y = p2.y;
- } else if (t > 0) {
- x += dx * t;
- y += dy * t;
- }
- }
- dx = p.x - x;
- dy = p.y - y;
- return dx * dx + dy * dy;
-}
-// simplification using optimized Douglas-Peucker algorithm with recursion elimination
-function simplifyDouglasPeucker(points, sqTolerance)
-{
- var len = points.length,
- MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
- markers = new MarkerArray(len),
- first = 0,
- last = len - 1,
- stack = [],
- newPoints = [],
- i, maxSqDist, sqDist, index;
- markers[first] = markers[last] = 1;
- while (last)
- {
- maxSqDist = 0;
- for (i = first + 1; i < last; i++)
- {
- sqDist = getSqSegDist(points[i], points[first], points[last]);
- if (sqDist > maxSqDist)
- {
- index = i;
- maxSqDist = sqDist;
- }
- }
- if (maxSqDist > sqTolerance)
- {
- markers[index] = 1;
- stack.push(first, index, index, last);
- }
- last = stack.pop();
- first = stack.pop();
- }
- for (i = 0; i < len; i++)
- {
- if (markers[i]) newPoints.push(points[i]);
- }
- return newPoints;
-}
-function buildSmoothPath(points, path)
-{
- var dist1, dist2, angle1, angle2, prevtangent, tangent, t1, t2, x, y, p, c, n, l = points.length;
- var good;
- if (l < 2) return;
- path.pathSegList.initialize(path.createSVGPathSegMovetoAbs(points[0].x, points[0].y));
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(points[1].x, points[1].y));
- if (l < 3) return;
- for (var i = 1; i < l - 1; i++)
- {
- p = points[i - 1];
- c = points[i];
- n = points[i + 1];
- x = c.x - p.x;
- y = c.y - p.y;
- angle1 = Math.atan2(y, x);
- dist1 = Math.sqrt(x * x + y * y);
- x = n.x - c.x;
- y = n.y - c.y;
- angle2 = Math.atan2(y, x);
- dist2 = Math.sqrt(x * x + y * y);
- tangent = (angle1 + angle2) / 2;
- if (i > 1)
- {
- if (Math.abs(angle2 - angle1) >= Math.PI / 4)
- {
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y));
- good = false;
- } else {
- if (good && dist1 / dist2 >= 0.4 && dist1 / dist2 <= 2.5)
- {
- t1 = {x: p.x + Math.cos(prevtangent) * dist1 * 0.4, y: p.y + Math.sin(prevtangent) * dist1 * 0.4};
- t2 = {x: c.x - Math.cos(tangent) * dist2 * 0.4, y: c.y - Math.sin(tangent) * dist2 * 0.4};
- path.pathSegList.appendItem(path.createSVGPathSegCurvetoCubicAbs(c.x, c.y, t1.x, t1.y, t2.x, t2.y));
- } else {
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y));
- good = true;
- }
- }
- }
- prevtangent = tangent;
- }
- c = points[l - 1];
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y));
-}
-
-var random_things = ['hobo', 'shoe', 'log', 'bun', 'sandwich', 'bull', 'beer', 'hair',
- 'hill', 'beans', 'man', 'sofa', 'dinosaur', 'road', 'plank', 'hole', 'food',
- 'hedgehog', 'pine', 'toad', 'tooth', 'candy', 'rock', 'drop', 'book', 'button', 'carpet',
- 'wheel', 'computer', 'box', 'cat', 'rat', 'hook', 'chunk', 'boat', 'spade', 'sack',
- 'hammer', 'face', 'soap', 'nose', 'finger', 'steam', 'spring', 'hand', 'fish',
- 'elephant', 'dog', 'chair', 'bag', 'phone', 'robot', 'axe', 'grass', 'crack', 'teacher',
- 'breadcrumb', 'fridge', 'worm', 'nut', 'cloth', 'apple', 'tongue', 'jar'];
-var random_acts = ['crazy from', 'thanks', 'hits', 'lies around on', 'sees', 'grows in',
- 'attaches to', 'flies from', 'crawls from', 'chews', 'walks on', 'squishes', 'pecks',
- 'wobbles in', 'smokes from', 'smokes', 'rides', 'eats', 'squeals from under',
- 'is lost in', 'spins in', 'stuck in', 'hooks', 'angry at', 'bends', 'drips on',
- 'rolls on', 'digs', 'crawls in', 'flies at', 'massages', 'dreams of', 'kills', 'pulls',
- "doesn't want", 'licks', 'shoots', 'falls off', 'falls in', 'crawls on', 'turns into',
- 'stuck to', 'jumps on', 'hides', 'hides in', 'disassembles', 'rips', 'dissolves',
- 'stretches', 'crushes', 'pushes', 'drowns in', 'pokes', 'runs away from', 'wants',
- 'scratches', 'throws', 'and', 'confused by', 'unimpressed by'];
-var random_descs = ['white', 'concrete', 'shiny', 'ill', 'big', 'ex', 'fast', 'happy',
- 'inside-out', 'hot', 'burning', 'thick', 'wooden', 'long', 'good', 'tattered', 'iron',
- 'liquid', 'frozen', 'green', 'evil', 'bent', 'rough', 'pretty', 'red', 'round',
- 'shaggy', 'bald', 'slow', 'wet', 'wrinkly', 'meaty', 'impudent', 'real', 'distraught',
- 'sharp', 'plastic', 'gift', 'squished', 'chubby', 'crumbling', 'horned', 'angry',
- 'sitting', 'stranded', 'dry', 'hard', 'thin', 'killer', 'walking', 'cold', 'wheezing',
- 'grunting', 'chirping', 'wide', 'electric', 'nuclear', 'confused', 'unimpressed'];
-function randomPhrase()
-{
- function randomBase()
- {
- switch (Math.floor(Math.random() * 3))
- {
- case 0: return [
- randomItem(random_descs),
- randomItem(random_things),
- ].join(' ');
- case 1: return [
- randomItem(random_descs),
- randomItem(random_things),
- randomItem(random_acts),
- randomItem(random_things),
- ].join(' ');
- case 2: return [
- randomItem(random_descs),
- randomItem(random_things),
- randomItem(random_acts),
- randomItem(random_descs),
- randomItem(random_things),
- ].join(' ');
- }
- return "Error!";
- }
- var s = randomBase();
- return s.charAt(0).toUpperCase() + s.substr(1);
-}
-function rgb2lab(rgb)
-{
- var r = rgb[0] / 255,
- g = rgb[1] / 255,
- b = rgb[2] / 255,
- x, y, z, l, a, b;
-
- r = rgb[0] > 10 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92);
- g = rgb[1] > 10 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92);
- b = rgb[2] > 10 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92);
-
- x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805);
- y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722);
- z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505);
-
- x /= 0.95047;
- z /= 1.08883;
-
- x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
- y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
- z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);
-
- l = (116 * y) - 16;
- a = 500 * (x - y);
- b = 200 * (y - z);
-
- return [l, a, b];
-}
-function getColorDistance(rgb1, rgb2)
-{
- var lab1 = rgb2lab(rgb1);
- var lab2 = rgb2lab(rgb2);
- var l = lab2[0] - lab1[0];
- var a = lab2[1] - lab1[1];
- var b = lab2[2] - lab1[2];
- return Math.sqrt(l * l * 2 + a * a + b * b);
-}
-function getClosestColor(rgb, pal)
-{
- // Allow any color in sandbox
- if (ID("newcanvasyo").classList.contains("sandbox")) return rgb2hex(rgb[0], rgb[1], rgb[2]);
- var c, d, idx = 0, min = 999;
- for (var i = 0; i < pal.length; i++)
- {
- d = getColorDistance(rgb, color2rgba(pal[i]));
- if (d < min)
- {
- min = d;
- idx = i;
- }
- }
- c = color2rgba(pal[idx]);
- return rgb2hex(c[0], c[1], c[2]);
-}
-function getColorDistanceLab(lab1, lab2)
-{
- var l = lab2[0] - lab1[0];
- var a = lab2[1] - lab1[1];
- var b = lab2[2] - lab1[2];
- return Math.sqrt(l * l * 2 + a * a + b * b);
-}
-function getClosestColorLab(lab, pal)
-{
- var c, d, idx = 0, min = 999;
- for (var i = 0; i < pal.length; i++)
- {
- d = getColorDistanceLab(lab, rgb2lab(color2rgba(pal[i])));
- if (d < min)
- {
- min = d;
- idx = i;
- }
- }
- c = color2rgba(pal[idx]);
- return rgb2hex(c[0], c[1], c[2]);
-}
-function getColorAverage(c1, c2, bias)
-{
- // Bias:
- // 0 = c1
- // 0.5 = average
- // 1 = c2
- return [
- Math.round(c1[0] * bias + c2[0] * (1 - bias)),
- Math.round(c1[1] * bias + c2[1] * (1 - bias)),
- Math.round(c1[2] * bias + c2[2] * (1 - bias)),
- ];
-}
-
-var palettes = {
- "Normal": ['#000000', '#444444', '#999999', '#ffffff', '#603913', '#c69c6d',
- '#ffdab9', '#ff0000', '#ffd700', '#ff6600', '#16ff00', '#0fad00',
- '#00ffff', '#0247fe', '#ec008c', '#8601af', '#fffdc9'],
- "Sepia": ['#402305', '#503315', '#604325', '#705335', '#806345', '#907355',
- '#a08365', '#b09375', '#bfa284', '#cfb294', '#dfc2a4', '#ffe2c4'],
- "Grayscale": ['#000000', '#111111', '#222222', '#333333', '#444444', '#555555', '#666666',
- '#777777', '#888888', '#999999', '#c0c0c0', '#ffffff', '#eeeeee' ],
- "Black and white": ['#ffffff', '#000000'],
- "CGA": ['#555555', '#000000', '#0000aa', '#5555ff', '#00aa00', '#55ff55', '#00aaaa', '#55ffff',
- '#aa0000', '#ff5555', '#aa00aa', '#ff55ff', '#aa5500', '#ffff55', '#aaaaaa', '#ffffff'],
- "Gameboy": ['#8bac0f', '#9bbc0f', '#306230', '#0f380f'],
- "Neon": ['#ffffff', '#000000', '#adfd09', '#feac09', '#fe0bab', '#ad0bfb', '#00abff'],
- "Thanksgiving": ['#673718', '#3c2d27', '#c23322', '#850005', '#c67200', '#77785b',
- '#5e6524', '#cfb178', '#f5e9ce'],
- "Holiday": ['#3d9949', '#7bbd82', '#7d1a0c', '#bf2a23',
- '#fdd017', '#00b7f1', '#bababa', '#ffffff'],
- "Valentine's": ['#8b2158', '#a81f61', '#bb1364', '#ce0e62', '#e40f64', '#ff0000',
- '#f5afc8', '#ffccdf', '#e7e7e7', '#ffffff'],
- "Halloween": ['#444444', '#000000', '#999999', '#ffffff', '#603913', '#c69c6d',
- '#7a0e0e', '#b40528', '#fd2119', '#fa5b11', '#faa611', '#ffd700',
- '#602749', '#724b97', '#bef202', '#519548', '#b2bb1e'],
- "the blues": ['#b6cbe4', '#618abc', '#d0d5ce', '#82a2a1', '#92b8c1', '#607884',
- '#c19292', '#8c2c2c', '#295c6f'],
- "Spring": ['#9ed396', '#57b947', '#4d7736', '#365431', '#231302',
- '#3e2409', '#a66621', '#a67e21', '#ebbb49', '#ffc0cb', '#ffffff'],
- "Beach": ['#1ca4d2', '#65bbe2', '#6ab7bf', '#94cbda', '#9cbf80', '#d2e1ab',
- '#b8a593', '#d7cfb9', '#dc863e', '#f7dca2'],
- "DawnBringer 16": ['#140c1c', '#442434', '#30346d', '#4e4a4e', '#854c30', '#346524',
- '#d04648', '#757161', '#597dce', '#d27d2c', '#8595a1', '#6daa2c',
- '#d2aa99', '#6dc2ca', '#dad45e', '#deeed6'],
- "Freedom": ['#000000', '#2c3539', '#2b3856', '#002a6c', '#800080', '#a52a2a',
- '#c2113a', '#ff0000', '#ffd700', '#ffff00', '#ffffff'],
-
-};
-
-var anbt =
-{
- container: null,
- svg: svgElement("svg",
- {
- // Even though Opera complains to have failed to set xmlns attribute:
- // > Failed attribute on svg element: xmlns="http://www.w3.org/2000/svg".
- // this is necessary for loading a saved SVG which otherwise wouldn't
- // bind correct prototypes for functions such as path.pathSegList
- xmlns: "http://www.w3.org/2000/svg",
- version: "1.1",
- width: "600", height: "500",
- }),
- canvas: document.createElement("canvas"),
- canvasDisp: document.createElement("canvas"),
- svgDisp: svgElement("svg",
- {
- //xmlns: "http://www.w3.org/2000/svg",
- version: "1.1",
- width: "600", height: "500",
- "pointer-events": "none",
- }),
- svgHist: null,
- path: null,
- points: null,
- pngBase64: null,
- lastrect: 0,
- position: 0,
- isStroking: false,
- isPlaying: false,
- size: 14.4,
- smoothening: 1,
- palette: palettes.Normal,
- patternCache: {},
- delay: 100,
- unsaved: false,
- background: '#fffdc9',
- transparent: false,
- color: ['#000000', "eraser"],
- fastUndoLevels: 10,
- rewindCache: [],
- snap: false,
- fillNext: false,
- BindContainer: function(el)
- {
- this.container = el;
- this.canvas.width = 600;
- this.canvas.height = 500;
- this.canvas.style.background = this.background;
- this.ctx = this.canvas.getContext("2d");
- this.ctx.lineJoin = "round";
- this.ctx.lineCap = "round";
- this.container.appendChild(this.canvas);
- if (!navigator.userAgent.match(/\bPresto\b/))
- {
- this.canvasDisp.width = 600;
- this.canvasDisp.height = 500;
- this.ctxDisp = this.canvasDisp.getContext("2d");
- this.ctxDisp.lineJoin = "round";
- this.ctxDisp.lineCap = "round";
- this.container.appendChild(this.canvasDisp);
- } else {
- // Opera Presto is faster with SVG redrawing
- this.DrawDispLine = this.DrawDispLinePresto;
- }
- this.container.appendChild(this.svgDisp);
- var rect = svgElement("rect",
- {
- "class": "eraser",
- x: 0,
- y: 0,
- width: 600,
- height: 500,
- fill: this.background,
- }
- );
- this.svg.appendChild(rect);
- },
- FromOldSVG: function(buf) // TODO: This function is messy
- {
- var arr = [];
- for (var i = 0; i < buf.length; i++)
- {
- arr.push(String.fromCharCode(buf[i]));
- }
- var svgdata = arr.join("");
-
- var parser = new DOMParser();
- var svg = parser.parseFromString(svgdata, 'text/xml').documentElement;
- for (var i = 0; i < svg.childNodes.length; i++)
- {
- var el = svg.childNodes[i];
- if (el.nodeName == "path")
- {
- el.setAttribute("stroke-linejoin", "round");
- el.setAttribute("stroke-linecap", "round");
- el.setAttribute("fill", "none");
- points = [];
- var x, y;
- for (var j = 0; j < el.pathSegList.numberOfItems; j++)
- {
- var seg = el.pathSegList.getItem(j);
- if (seg.pathSegTypeAsLetter != "l")
- {
- x = seg.x;
- y = seg.y;
- points.push({x: x, y: y});
- } else {
- el.pathSegList.replaceItem(el.createSVGPathSegLinetoAbs(x, y + 0.001), j);
- }
- }
- if (points.length > 2)
- {
- points = simplifyDouglasPeucker(points, this.smoothening);
- buildSmoothPath(points, el);
- }
- el.orig = points;
- }
- }
- this.svg = svg;
- this.lastrect = 0;
- this.rewindCache.length = 0;
- this.position = this.svg.childNodes.length - 1;
- this.UpdateView();
- this.MoveSeekbar(1);
- // Here we assume first element of svg is background rect
- this.SetBackground(this.svg.childNodes[0].getAttribute("fill"));
- },
- PackPlayback: function(svg)
- {
- var arr = [color2dword(this.background)];
- var lastcolor = color2dword("#000000");
- var lastsize = 14.4;
- var lastx = -1, lasty = -1;
- var lastpattern = 0;
-
- for (var i = 1; i < svg.childNodes.length; i++)
- {
- var el = svg.childNodes[i];
- if (el.nodeName == "path")
- {
- var color = color2dword(el.getAttribute("stroke"));
- if (el.getAttribute("class") == "eraser") color = "\xFF\xFF\xFF\x00";
- var size = el.getAttribute("stroke-width");
- var fill = el.getAttribute("fill");
- var pattern = el.pattern || 0;
- if (color != lastcolor || size != lastsize)
- {
- arr.push(pack_uint16be(-1));
- arr.push(pack_uint16be(size * 100));
- arr.push(color);
- lastcolor = color;
- lastsize = size;
- }
- if (fill != "none")
- {
- arr.push(pack_uint16be(-4));
- arr.push(pack_uint16be(0));
- arr.push(color2dword(fill));
- }
- if (pattern != lastpattern)
- {
- arr.push(pack_uint16be(-3));
- arr.push(pack_uint16be(pattern));
- arr.push("\x00\x00\x00\x00"); // reserved for the future
- lastpattern = pattern;
- }
- lastx = el.orig[0].x;
- lasty = el.orig[0].y;
- arr.push(pack_uint16be(lastx));
- arr.push(pack_uint16be(lasty));
- for (var j = 1; j < el.orig.length; j++)
- {
- var dx = Math.round(el.orig[j].x - lastx);
- var dy = Math.round(el.orig[j].y - lasty);
- // Ignore repeating points
- if (dx === 0 && dy === 0) continue;
- arr.push(pack_uint16be(dx));
- arr.push(pack_uint16be(dy));
- lastx = el.orig[j].x;
- lasty = el.orig[j].y;
- }
- arr.push("\x00\x00\x00\x00");
- } else if (el.nodeName == "rect")
- {
- var color = color2dword(el.getAttribute("fill"));
- arr.push(pack_uint16be(-2));
- arr.push(pack_uint16be(0)); // reserved for the future
- arr.push(color);
- } else {
- throw new Error("Unknown node name: " + el.nodeName);
- }
- }
- var result = "\x05" + bytes2string(pako.deflate(string2bytes(arr.join(""))));
- return result;
- },
- UnpackPlayback: function(bytes)
- {
- var version = bytes[0];
- var start;
- if (version == 4 || version == 5)
- {
- bytes = pako.inflate(bytes.subarray(1));
- start = 0;
- } else if (version == 3)
- {
- bytes = string2bytes(pako.inflate(bytes.subarray(1), {to: "string"}));
- start = 0;
- } else if (version == 2)
- {
- start = 1;
- } else {
- throw new Error("Unsupported version: " + version);
- }
- var svg = svgElement("svg",
- {
- xmlns: "http://www.w3.org/2000/svg",
- version: "1.1",
- width: "600", height: "500",
- });
- var color = "#000000";
- var fill;
- var size = 14.4;
- var lastx, lasty, x, y;
- var pattern = 0;
- var points = [];
- // Ignore background alpha
- var background = [
- "rgb(",
- bytes[start],
- ',',
- bytes[start + 1],
- ',',
- bytes[start + 2],
- ')'
- ].join("");
- svg.background = background;
-
- svg.appendChild(svgElement("rect",
- {
- "class": "eraser",
- x: 0,
- y: 0,
- width: 600,
- height: 500,
- fill: background,
- }
- ));
-
- for (var i = start + 4; i < bytes.length;)
- {
- x = int16be(bytes[i], bytes[i + 1]);
- i += 2;
- y = int16be(bytes[i], bytes[i + 1]);
- i += 2;
- if (points.length)
- {
- if (x === 0 && y === 0)
- {
- var path = svgElement("path",
- {
- "class": color == "eraser" ? color : null,
- stroke: color == "eraser" ? background : color,
- "stroke-width": size,
- "stroke-linejoin": "round",
- "stroke-linecap": "round",
- fill: fill ? fill : "none",
- }
- );
- // Restore blots
- if (points.length === 1)
- {
- path.pathSegList.appendItem(path.createSVGPathSegMovetoAbs(lastx, lasty));
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(lastx, lasty + 0.001));
- } else {
- buildSmoothPath(points, path);
- }
- path.orig = points;
- path.pattern = pattern;
- svg.appendChild(path);
- points = [];
- fill = null;
- } else {
- x = x + lastx;
- y = y + lasty;
- lastx = x;
- lasty = y;
- points.push({x: x, y: y});
- }
- } else {
- if (x < 0)
- {
- if (x === -1 || x === -2)
- {
- color = [
- "rgba(",
- bytes[i],
- ',',
- bytes[i + 1],
- ',',
- bytes[i + 2],
- ',',
- bytes[i + 3] / 255,
- ')'
- ].join("");
- // TODO: fix ugly code
- if (color == "rgba(255,255,255,0)") color = "eraser";
- i += 4;
- if (x === -1)
- {
- size = y / 100;
- } else {
- svg.appendChild(svgElement("rect",
- {
- "class": color == "eraser" ? color : null,
- x: 0,
- y: 0,
- width: 600,
- height: 500,
- fill: color == "eraser" ? background : color,
- }
- ));
- }
- } else if (x === -3) {
- pattern = y;
- i += 4;
- } else if (x === -4) {
- fill = [
- "rgba(",
- bytes[i],
- ',',
- bytes[i + 1],
- ',',
- bytes[i + 2],
- ',',
- bytes[i + 3] / 255,
- ')'
- ].join("");
- i += 4;
- }
- } else {
- points.push({x: x, y: y});
- lastx = x;
- lasty = y;
- }
- }
- }
- return svg;
- },
- FindLastRect: function(endpos)
- {
- if (!endpos) endpos = this.svg.childNodes.length - 1;
- for (var i = endpos; i > 0; i--)
- {
- var el = this.svg.childNodes[i];
- if (el.nodeName == "rect") return i;
- }
- return 0;
- },
- CutHistoryBeforePosition: function()
- {
- for (var i = this.position - 1; i > 0; i--)
- {
- var el = this.svg.childNodes[i];
- this.svg.removeChild(el);
- }
- },
- CutHistoryBeforeClearAndAfterPosition: function()
- {
- var removing = false;
- for (var i = this.svg.childNodes.length - 1; i > 0; i--)
- {
- var el = this.svg.childNodes[i];
- if (removing || i > this.position)
- {
- this.svg.removeChild(el);
- }
- else if (el.nodeName == "rect" && i <= this.position)
- {
- removing = true;
- // Optimize out two eraser rectangles next to each other
- if (el.getAttribute("class") == "eraser")
- {
- this.svg.removeChild(el);
- }
- }
- }
- },
- MakePNG: function(width, height, fromBuffer)
- {
- // Cut all needless SVG data that comes before clearing whole canvas
- this.CutHistoryBeforeClearAndAfterPosition();
- this.MoveSeekbar(1);
-
- var canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = height;
- var context = canvas.getContext("2d");
- if (!this.transparent)
- {
- context.fillStyle = this.background;
- context.fillRect(0, 0, width, height);
- }
- if (fromBuffer)
- {
- context.drawImage(this.canvas, 0, 0, width, height);
- } else {
- context.lineJoin = "round";
- context.lineCap = "round";
- context.save();
- context.scale(width / 600, height / 500);
- // Skip background rect
- for (var i = 1; i < this.svg.childNodes.length; i++)
- {
- this.DrawSVGElement(this.svg.childNodes[i], context);
- }
- context.restore();
- context.globalCompositeOperation = "destination-over";
- context.fillStyle = this.background;
- context.fillRect(0, 0, width, height);
- }
- this.pngBase64 = canvas.toDataURL("image/png");
-
- var version = "svGb";
- var svgstr = this.PackPlayback(this.svg);
- var padding = this.pngBase64.substr(-2);
- var prepend, custom, iend;
- // To append the custom chunk, we need to decode the end of the base64-encoded PNG
- // and then reattach as btoa((prepend) + (custom data) + (iend)).
- // As base64 encoding chunks are 3 bytes, iend chunk can start in the middle of those,
- // so (prepend) contains the data before iend chunk that we had to cut.
- if (padding == "==")
- {
- // Two padding characters
- cut = 1;
- } else if (padding[1] == "=")
- {
- // One padding character
- cut = 2;
- } else {
- // No padding
- cut = 3;
- }
- iend = atob(this.pngBase64.substr(-20)).substr(cut);
- prepend = atob(this.pngBase64.substr(-20)).substr(0, cut);
- custom = [
- prepend,
- pack_uint32be(svgstr.length),
- version,
- svgstr,
- pack_uint32be(crc32(version, svgstr)),
- iend
- ].join("");
- this.pngBase64 = this.pngBase64.substr(0, this.pngBase64.length - 20) + btoa(custom);
- },
- FromPNG: function(buffer)
- {
- var dv = new DataView(buffer);
- var magic = dv.getUint32(0);
- if (magic != 0x89504e47) throw new Error("Invalid PNG format: " + pack_uint32be(magic));
- for (var i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */)
- {
- var chunklen = dv.getUint32(i);
- i += 4;
- var chunkname = pack_uint32be(dv.getUint32(i));
- i += 4;
- if (chunkname == "svGb")
- {
- var newsvg = this.UnpackPlayback(new Uint8Array(buffer, i, chunklen));
- this.svg = newsvg;
- // Assume saved data is always optimized and is cut before last rect
- //this.lastrect = this.FindLastRect();
- this.lastrect = 0;
- this.rewindCache.length = 0;
- this.position = this.svg.childNodes.length - 1;
- this.UpdateView();
- this.MoveSeekbar(1);
- // Here we assume first element of svg is background rect
- this.SetBackground(this.svg.background);
- return;
- } else {
- if (chunkname == "IEND") break;
- i += chunklen;
- }
- }
- throw new Error("No vector data found!");
- },
- FromURL: function(url)
- {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', url, true);
- if ('responseType' in xhr)
- {
- xhr.responseType = 'arraybuffer';
- } else {
- alert("Your browser is too old for this");
- return;
- }
- var _anbt = this;
- xhr.onload = function()
- {
- _anbt.FromPNG(this.response);
- };
- xhr.onerror = function()
- {
- alert("Error loading an image. Wrong URL?");
- };
- xhr.send();
- },
- FromLocalFile: function(forceAltMethod)
- {
- if (!this.fileInput)
- {
- this.fileInput = document.createElement("input");
- this.fileInput.style.position = "absolute";
- this.fileInput.style.top = "-1000px";
- this.fileInput.type = "file";
- this.fileInput.accept = ".png";
- document.body.appendChild(this.fileInput);
- var _anbt = this;
- this.fileInput.addEventListener("change", function(e)
- {
- var reader = new FileReader();
- reader.onload = function()
- {
- _anbt.FromPNG(this.result);
- };
- if (e.target.files[0])
- {
- reader.readAsArrayBuffer(e.target.files[0]);
- }
- },
- false
- );
- }
- if (!navigator.userAgent.match(/\bPresto\b/) && !forceAltMethod)
- {
- var clickEvent = document.createEvent("MouseEvent");
- clickEvent.initMouseEvent("click", true, true, window, 1,
- 0, 0, 0, 0, false, false, false, false, 0, null);
- this.fileInput.dispatchEvent(clickEvent);
- } else {
- setTimeout(this.fileInput.click.bind(this.fileInput), 1);
- }
- },
- SetBackground: function(color)
- {
- var transparent = color == "eraser";
- this.transparent = transparent;
- this.canvas.style.background = transparent ? "none" : color;
- // Normalize the color representation
- color = transparent ? "#ffffff" : color2hex(color);
- this.background = color;
- var erased = this.svg.querySelectorAll(".eraser");
- for (var i = 0; i < erased.length; i++)
- {
- if (erased[i].nodeName == "path")
- {
- erased[i].setAttribute("stroke", color);
- } else {
- erased[i].setAttribute("fill", color);
- }
- }
- },
- SetColor: function(num, color)
- {
- this.color[num] = color;
- },
- SetSize: function(size)
- {
- this.size = size;
- this.MoveCursor();
- },
- DrawSVGElement: function(el, ctx)
- {
- if (!ctx) ctx = this.ctx;
- if (el.getAttribute("class") == "eraser")
- {
- ctx.globalCompositeOperation = "destination-out";
- } else {
- ctx.globalCompositeOperation = "source-over";
- }
- if (el.nodeName == "path")
- {
- var c = el.getAttribute("stroke");
- ctx.strokeStyle = el.pattern ? this.MakePattern(c, el.pattern) : c;
- ctx.lineWidth = el.getAttribute("stroke-width");
- ctx.beginPath();
- for (var i = 0; i < el.pathSegList.numberOfItems; i++)
- {
- var seg = el.pathSegList.getItem(i);
- if (seg.pathSegTypeAsLetter == "M")
- {
- ctx.moveTo(seg.x, seg.y);
- } else if (seg.pathSegTypeAsLetter == "L")
- {
- ctx.lineTo(seg.x, seg.y);
- } else if (seg.pathSegTypeAsLetter == "Q")
- {
- ctx.quadraticCurveTo(seg.x1, seg.y1, seg.x, seg.y);
- } else if (seg.pathSegTypeAsLetter == "C")
- {
- ctx.bezierCurveTo(seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y);
- }
- }
- var fill = el.getAttribute("fill");
- if (fill && fill != "none")
- {
- ctx.closePath();
- ctx.fillStyle = el.pattern ? this.MakePattern(fill, el.pattern) : fill;
- ctx.fill();
- }
- ctx.stroke();
- }
- else if (el.nodeName == "rect")
- {
- ctx.fillStyle = el.getAttribute("fill");
- var x = el.getAttribute("x");
- var y = el.getAttribute("y");
- var w = el.getAttribute("width");
- var h = el.getAttribute("height");
- ctx.fillRect(x, y, w, h);
- }
- },
- UpdateView: function()
- {
- var start = this.lastrect < this.position ? this.lastrect : 0;
- for (var i = start; i <= this.position; i++)
- {
- this.DrawSVGElement(this.svg.childNodes[i]);
- }
- },
- DrawDispLinePresto: function(x1, y1, x2, y2, first)
- {
- if (first) this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild);
- },
- DrawDispLine: function(x1, y1, x2, y2, first)
- {
- var ctx = this.ctxDisp;
- var c = this.lastcolor;
- ctx.strokeStyle = this.pattern ? this.MakePattern(c, this.pattern) : c;
- ctx.lineWidth = this.size;
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y2);
- ctx.stroke();
- },
- StrokeBegin: function(x, y, left)
- {
- if (left === undefined)
- {
- left = this.lastleft;
- } else {
- this.lastleft = left;
- }
- if (this.snap)
- {
- x = Math.round(x / this.snap) * this.snap;
- y = Math.round(y / this.snap) * this.snap;
- }
- var cls = null;
- var color = left ? this.color[0] : this.color[1];
- if (color == "eraser")
- {
- color = this.background;
- cls = "eraser";
- }
- this.path = svgElement("path",
- {
- "class": cls,
- stroke: color,
- "stroke-width": this.size,
- "stroke-linejoin": "round",
- "stroke-linecap": "round",
- fill: this.fillNext ? color : "none",
- }
- );
- this.fillNext = false;
-
- this.lastcolor = color;
- this.path.pattern = this.pattern;
- //this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild);
- this.path.pathSegList.appendItem(this.path.createSVGPathSegMovetoAbs(x, y));
- this.path.pathSegList.appendItem(this.path.createSVGPathSegLinetoAbs(x, y + 0.001));
- this.DrawDispLine(x, y, x, y + 0.001, true);
- this.points = [];
- this.points.push({x: x, y: y});
- this.blot = true;
- this.isStroking = true;
- },
- StrokeEnd: function()
- {
- this.unsaved = true;
- var p = this.points;
- if (p.length > 2)
- {
- p = simplifyDouglasPeucker(p, this.smoothening);
- buildSmoothPath(p, this.path);
- }
- this.path.orig = p;
- this.Add(this.path);
- this.ctxDisp && this.ctxDisp.clearRect(0, 0, 600, 500);
- this.isStroking = false;
- },
- StrokeAdd: function(x, y)
- {
- if (!this.isStroking) throw new Error("StrokeAdd without StrokeBegin!");
- if (this.snap)
- {
- x = Math.round(x / this.snap) * this.snap;
- y = Math.round(y / this.snap) * this.snap;
- }
- var p = this.points[this.points.length - 1];
- if (p.x == x && p.y == y) return;
- if (this.blot)
- {
- this.path.pathSegList.removeItem(1);
- this.blot = false;
- }
- this.path.pathSegList.appendItem(this.path.createSVGPathSegLinetoAbs(x, y));
- this.DrawDispLine(p.x, p.y, x, y);
- this.points.push({x: x, y: y});
- // Todo: realtime smoothening
- /*
- p = this.points;
- if (p.length > 2)
- {
- p = simplifyDouglasPeucker(p, this.smoothening);
- buildSmoothPath(p, this.path);
- }
- */
- },
- // Experimental, for making polylines like in Photoshop
- // Caveat: undo will erase whole polyline
- StrokeBeginModifyLast: function(x, y, left)
- {
- if (this.position == 0 || !this.points) return anbt.StrokeBegin(x, y, left);
- if (this.snap)
- {
- x = Math.round(x / this.snap) * this.snap;
- y = Math.round(y / this.snap) * this.snap;
- }
- this.path = this.svg.childNodes[this.position];
- this.points = this.path.orig;
- this.Seek(this.position - 1);
- this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild);
- this.path.pathSegList.appendItem(this.path.createSVGPathSegLinetoAbs(x, y));
- this.points.push({x: x, y: y});
- this.isStroking = true;
- },
- ClearWithColor: function(color)
- {
- this.Add(svgElement("rect",
- {
- "class": color == "eraser" ? color : null,
- x: 0,
- y: 0,
- width: 600,
- height: 500,
- fill: color == "eraser" ? this.background : color,
- }
- ));
- this.lastrect = this.position;
- },
- Add: function(el)
- {
- if (this.rewindCache.length >= this.fastUndoLevels)
- {
- this.rewindCache.pop();
- }
- this.rewindCache.unshift(this.ctx.getImageData(0, 0, 600, 500));
-
- this.DrawSVGElement(el);
- if (!this.timeedit || this.position == this.svg.childNodes.length - 1)
- {
- // Remove everything past current position
- for (var i = this.svg.childNodes.length - 1; i > this.position; i--)
- {
- this.svg.removeChild(this.svg.childNodes[i]);
- }
- this.svg.appendChild(el);
- this.position = this.svg.childNodes.length - 1;
- this.MoveSeekbar(1);
- } else {
- this.svg.insertBefore(el, this.svg.childNodes[this.position + 1]);
- }
- },
- Undo: function()
- {
- // Prevent "undoing" the background rectangle
- if (this.position > 0)
- {
- this.Seek(this.position - 1);
- this.MoveSeekbar(this.position / (this.svg.childNodes.length - 1));
- }
- },
- Redo: function()
- {
- var posmax = this.svg.childNodes.length - 1;
- if (this.position < posmax)
- {
- this.Seek(this.position + 1);
- this.MoveSeekbar(this.position / posmax);
- }
- },
- MoveSeekbar: function(pos)
- {
- if (this.seekbarMove)
- {
- this.seekbarMove(pos);
- }
- },
- SetSeekbarMove: function(func)
- {
- this.seekbarMove = func;
- },
- GetSeekMax: function()
- {
- return this.svg.childNodes.length - 1;
- },
- Seek: function(newpos)
- {
- var start = -1;
- this.Pause(true);
- if (newpos == this.position) return;
- if (newpos < this.position)
- {
- var rewindSteps = this.position - newpos;
- if (rewindSteps <= this.rewindCache.length)
- {
- // Draw from cached
- this.ctx.putImageData(this.rewindCache[rewindSteps - 1], 0, 0);
- this.rewindCache.splice(0, rewindSteps);
- } else {
- // Not cached; rebuild cache
- start = 0;
- if (this.lastrect <= newpos)
- {
- start = this.lastrect;
- } else {
- start = this.FindLastRect(newpos);
- }
- this.DrawSVGElement(this.svg.childNodes[start]);
- }
- } else if (newpos > this.position) {
- start = this.position;
- }
- if (start != -1)
- {
- var forwardSteps = newpos - start;
- if (forwardSteps >= this.fastUndoLevels)
- {
- this.rewindCache.length = 0;
- } else {
- // Ex: 3 cached, 10 max, 8 steps to play => delete 1 from the end
- var len = this.rewindCache.length;
- var numRemove = Math.min(len, newpos - start + len - this.fastUndoLevels);
- this.rewindCache.splice(len - numRemove, numRemove);
- }
- for (var i = start + 1; i <= newpos; i++)
- {
- if (newpos - i < this.fastUndoLevels)
- {
- this.rewindCache.unshift(this.ctx.getImageData(0, 0, 600, 500));
- }
- this.DrawSVGElement(this.svg.childNodes[i]);
- }
- }
- this.position = newpos;
- },
- Play: function()
- {
- this.rewindCache.length = 0; // TODO: make rewind data remember its position
- if (this.position == this.svg.childNodes.length - 1)
- {
- if (this.position === 0)
- {
- // To make button revert to play
- this.MoveSeekbar(1);
- return;
- }
- this.position = 0;
- this.MoveSeekbar(0);
- // Assume first svg child is background rect
- this.DrawSVGElement(this.svg.childNodes[0]);
- }
- this.isPlaying = true;
- this.playTimer = this.PlayTimer.bind(this);
- this.playTimer();
- },
- PlayTimer: function()
- {
- if (!this.isPlaying) return;
- var posmax = this.svg.childNodes.length - 1;
- var delay = this.delay;
- var maxidx = 0;
- if (this.position < posmax || this.isAnimating)
- {
- if (this.isAnimating)
- {
- maxidx = this.animatePath.pathSegList.numberOfItems - 1;
- if (this.animateIndex < maxidx)
- {
- // There doesn't seem to be a simplier way to copy the pathSeg
- var seg = this.animatePath.pathSegList.getItem(this.animateIndex);
- var newseg;
- if (seg.pathSegTypeAsLetter == "L")
- {
- newseg = this.path.createSVGPathSegLinetoAbs(seg.x, seg.y);
- } else if (seg.pathSegTypeAsLetter == "Q")
- {
- newseg = this.path.createSVGPathSegCurvetoQuadraticAbs(seg.x, seg.y, seg.x1, seg.y1);
- } else if (seg.pathSegTypeAsLetter == "C")
- {
- newseg = this.path.createSVGPathSegCurvetoCubicAbs(seg.x, seg.y, seg.x1, seg.y1, seg.x2, seg.y2);
- }
- this.path.pathSegList.appendItem(newseg);
- this.animateIndex++;
- } else {
- this.isAnimating = false;
- this.svgDisp.removeChild(this.path);
- this.DrawSVGElement(this.animatePath);
- this.position++;
- this.animateIndex = 0;
- }
- delay = this.delay / 6;
- } else {
- var el = this.svg.childNodes[this.position + 1];
- if (el.nodeName == "path")
- {
- this.isAnimating = true;
- this.animatePath = el;
- this.animateIndex = 1;
- this.path = el.cloneNode(true);
- var seg = el.pathSegList.getItem(0);
- this.path.pathSegList.initialize(this.path.createSVGPathSegMovetoAbs(seg.x, seg.y));
- this.svgDisp.insertBefore(this.path, this.svgDisp.firstChild);
- } else {
- this.DrawSVGElement(el);
- this.position++;
- }
- }
- }
- this.MoveSeekbar((this.position + (maxidx ? this.animateIndex / maxidx : 0)) / posmax);
- if (this.position < posmax)
- {
- setTimeout(this.playTimer, delay);
- } else {
- this.Pause();
- }
- },
- Pause: function(noSeekbar)
- {
- if (this.isPlaying)
- {
- if (this.isAnimating)
- {
- this.isAnimating = false;
- this.svgDisp.removeChild(this.path);
- this.DrawSVGElement(this.animatePath);
- this.position++;
- if (!noSeekbar)
- {
- this.MoveSeekbar(this.position / (this.svg.childNodes.length - 1));
- }
- }
- this.isPlaying = false;
- }
- },
- MoveCursor: function(x, y)
- {
- if (!this.brushCursor)
- {
- this.brushCursor = svgElement("circle",
- {
- "stroke-width": "0.5",
- stroke: "#000",
- fill: "none",
- }
- );
- this.svgDisp.appendChild(this.brushCursor);
- this.brushCursor2 = svgElement("circle",
- {
- "stroke-width": "0.5",
- stroke: "#fff",
- fill: "none",
- }
- );
- this.svgDisp.appendChild(this.brushCursor2);
- this.eyedropperCursor = svgElement("image",
- {
- width: 16,
- height: 16,
- visibility: "hidden",
- }
- );
- this.eyedropperCursor.setAttributeNS("http://www.w3.org/1999/xlink", "href", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARklEQVR4XoXRwQoAIAgEUf//pzeGDgq5G3PrCQqVbIAqsDz9WM2qhTX4GZgPV+JpSFxAC0PwbeVZZIpMgXvAMwoj4U9B3wGySxvzk6ZjvwAAAABJRU5ErkJggg==");
- this.svgDisp.appendChild(this.eyedropperCursor);
- }
- // Assume just size change if called with no parameters
- if (typeof x != "undefined")
- {
- if (this.snap)
- {
- x = Math.round(x / this.snap) * this.snap;
- y = Math.round(y / this.snap) * this.snap;
- }
- this.brushCursor.setAttribute("cx", x);
- this.brushCursor.setAttribute("cy", y);
- this.brushCursor2.setAttribute("cx", x);
- this.brushCursor2.setAttribute("cy", y);
- this.eyedropperCursor.setAttribute("x", x - 1);
- this.eyedropperCursor.setAttribute("y", y - 15);
- }
- this.brushCursor.setAttribute("r", this.size / 2 + 0.5);
- this.brushCursor2.setAttribute("r", this.size / 2 - 0.5);
- },
- ShowEyedropperCursor: function(isEyedropper)
- {
- if (!this.brushCursor) return;
- var vis = isEyedropper ? "hidden" : "visible";
- var vis2 = isEyedropper ? "visible" : "hidden";
- this.brushCursor.setAttribute("visibility", vis);
- this.brushCursor2.setAttribute("visibility", vis);
- this.eyedropperCursor.setAttribute("visibility", vis2);
- },
- Eyedropper: function(x, y)
- {
- var p = this.ctx.getImageData(x, y, 1, 1).data;
- if (p[3] > 0)
- {
- return getClosestColor(p, this.palette);
- } else {
- return this.background;
- }
- },
- RequestSave: function(dataurl, extension)
- {
- if (!dataurl)
- {
- dataurl = this.pngBase64;
- extension = ".png";
- this.unsaved = false;
- }
- if (!this.saveLink)
- {
- this.saveLink = document.createElement("a");
- document.body.appendChild(this.saveLink);
- }
- if ("download" in this.saveLink)
- {
- this.saveLink.href = dataurl;
- var d = new Date();
- this.saveLink.download =
- [
- "DrawingInTime_",
- d.getFullYear(),
- "_",
- (101 + d.getMonth() + "").slice(-2),
- (100 + d.getDate() + "").slice(-2),
- "_",
- (100 + d.getHours() + "").slice(-2),
- (100 + d.getMinutes() + "").slice(-2),
- (100 + d.getSeconds() + "").slice(-2),
- extension
- ].join("");
- this.saveLink.click();
- } else {
- window.open(dataurl);
- }
- return true;
- },
- UploadToImgur: function(callback)
- {
- var xhr = new XMLHttpRequest();
- xhr.open("POST", "https://api.imgur.com/3/image");
- xhr.onload = function()
- {
- var res = xhr.responseText;
- try
- {
- res = JSON.parse(res);
- }
- catch(e) {}
- if (res.success)
- {
- // To set description
- var xhr2 = new XMLHttpRequest();
- xhr2.open("POST", "https://api.imgur.com/3/image/" + res.data.deletehash);
- xhr2.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af');
- var fd = new FormData();
- fd.append("description", "Playback: http://grompe.org.ru/drawit/#" + res.data.id);
- xhr2.send(fd);
- }
- callback(res);
- };
- xhr.onerror = function(e)
- {
- callback("error: " + e);
- };
- xhr.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af');
- var fd = new FormData();
- fd.append("image", new Blob([base642bytes(this.pngBase64.substr(22)).buffer], {type: "image/png"}));
- fd.append("type", "file");
- fd.append("title", "Made with Drawing in Time");
- fd.append("description", "http://grompe.org.ru/drawit/");
- xhr.send(fd);
- },
- FromImgur: function(id)
- {
- // https link to prevent recompression by various optimizing proxies
- this.FromURL("https://i.imgur.com/" + id + ".png");
- },
- MakePattern: function(color, patid)
- {
- if (this.patternCache[color] && this.patternCache[color][patid])
- {
- return this.patternCache[color][patid];
- } else {
- if (!this.patternCache[color])
- {
- this.patternCache[color] = [];
- }
- if (!this.patternCanvas)
- {
- this.patternCanvas = document.createElement("canvas");
- this.patternCanvas.width = 16;
- this.patternCanvas.height = 16;
- }
- var ctx = this.patternCanvas.getContext("2d");
- ctx.fillStyle = color;
- ctx.clearRect(0, 0, 16, 16);
- if (patid == 1)
- {
- ctx.beginPath();
- ctx.arc(2, 2, 2, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.beginPath();
- ctx.arc(6, 6, 2, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.drawImage(this.patternCanvas, 8, 0);
- ctx.drawImage(this.patternCanvas, 0, 8);
- } else if (patid == 2) {
- ctx.beginPath();
- ctx.arc(2, 2, 1, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.beginPath();
- ctx.arc(6, 6, 1, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.drawImage(this.patternCanvas, 8, 0);
- ctx.drawImage(this.patternCanvas, 0, 8);
- } else if (patid == 3) {
- ctx.beginPath();
- ctx.arc(4, 4, 2, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.beginPath();
- ctx.arc(4, 12, 2, 0, 2 * Math.PI, false);
- ctx.fill();
- ctx.drawImage(this.patternCanvas, 8, -4);
- ctx.drawImage(this.patternCanvas, 8, 12);
- } else if (patid == 4) {
- ctx.fillRect(0, 0, 2, 2);
- ctx.fillRect(4, 0, 2, 2);
- ctx.fillRect(0, 4, 2, 2);
- ctx.fillRect(4, 4, 2, 2);
- ctx.drawImage(this.patternCanvas, 8, 0);
- ctx.drawImage(this.patternCanvas, 0, 8);
- } else if (patid == 5) {
- ctx.fillRect(0, 0, 1, 1);
- ctx.fillRect(4, 0, 1, 1);
- ctx.fillRect(0, 4, 1, 1);
- ctx.fillRect(4, 4, 1, 1);
- ctx.drawImage(this.patternCanvas, 8, 0);
- ctx.drawImage(this.patternCanvas, 0, 8);
- }
- var pat = this.ctx.createPattern(this.patternCanvas, 'repeat');
- return (this.patternCache[color][patid] = pat);
- }
- },
- SetPattern: function(patid)
- {
- this.pattern = patid;
- },
- ExportWebM: function()
- {
- var anbt = this;
- require("whammy.min.js", function()
- {
- var canvas = document.createElement("canvas");
- canvas.width = 600;
- canvas.height = 500;
- var context = canvas.getContext("2d");
- context.fillStyle = anbt.background;
- var encoder = new Whammy.Video(15);
- var maxpos = anbt.svg.childNodes.length - 1;
- var i = 0;
- var nextFrame = function()
- {
- anbt.Seek(i);
- anbt.MoveSeekbar(anbt.position / maxpos);
- context.fillRect(0, 0, 600, 500);
- context.drawImage(anbt.canvas, 0, 0, 600, 500);
- encoder.add(canvas);
- i++;
- if (i <= maxpos)
- {
- setTimeout(nextFrame, 1);
- } else {
- var output = encoder.compile();
- var url = (window.webkitURL || window.URL).createObjectURL(output);
- anbt.RequestSave(url, ".webm");
- }
- }
- nextFrame();
- });
- },
-};
-
-var timerStart, timerCallback;
-
-function bindEvents()
-{
- var wacom = ID("wacom");
- var getPointerType = function()
- {
- return wacom && wacom.penAPI && wacom.penAPI.isWacom ? wacom.penAPI.pointerType : 0;
- };
-
- var checkPlayingAndStop = function()
- {
- if (anbt.isPlaying)
- {
- anbt.Pause();
- ID("play").classList.remove("pause");
- return true;
- }
- return false;
- };
- var rect;
- var mouseMove = function(e)
- {
- e.preventDefault();
- var x = e.pageX - rect.left - pageXOffset;
- var y = e.pageY - rect.top - pageYOffset;
- anbt.StrokeAdd(x, y);
- };
- var mouseUp = function(e)
- {
- if (e.button === 0 || e.button === 2)
- {
- e.preventDefault();
- if (anbt.isStroking) anbt.StrokeEnd();
- if (options.hideCross) ID("svgContainer").classList.remove("hidecursor");
- window.removeEventListener('mouseup', mouseUp);
- window.removeEventListener('mousemove', mouseMove);
- }
- };
- ID("svgContainer").addEventListener('mousedown', function(e)
- {
- if (e.button === 0 || e.button === 2)
- {
- if (anbt.isStroking) return mouseUp(e);
- if (checkPlayingAndStop()) return;
- e.preventDefault();
- rect = this.getBoundingClientRect();
- var x = e.pageX - rect.left - pageXOffset;
- var y = e.pageY - rect.top - pageYOffset;
-
- if (e.altKey)
- {
- var whichcolor = e.button === 0 ? 0 : 1;
- if (e.shiftKey && (anbt.color[whichcolor] != "eraser"))
- {
- var alpha = Math.round(color2rgba(anbt.color[whichcolor])[3] / 2.55) / 100;
- var c = color2rgba(anbt.Eyedropper(x, y));
- anbt.SetColor(whichcolor, "rgba(" + c[0] + "," + c[1] + "," + c[2] + "," + alpha + ")");
- } else {
- anbt.SetColor(whichcolor, anbt.Eyedropper(x, y));
- }
- updateColorIndicators();
- } else {
- // PointerType == 3 is pen tablet eraser
- var left = e.button === 0 && getPointerType() !== 3;
- if (options.hideCross) ID("svgContainer").classList.add("hidecursor");
- if (e.shiftKey)
- {
- anbt.StrokeBeginModifyLast(x, y, left);
- } else {
- anbt.StrokeBegin(x, y, left);
- }
- window.addEventListener('mouseup', mouseUp);
- window.addEventListener('mousemove', mouseMove);
- }
- }
- });
- var lastSeenColorToHighlight = anbt.background;
- ID("svgContainer").addEventListener('mousemove', function(e)
- {
- rect = this.getBoundingClientRect();
- var x = e.pageX - rect.left - pageXOffset;
- var y = e.pageY - rect.top - pageYOffset;
- anbt.MoveCursor(x, y);
- // Highlight color we're pointing at
- if (options.colorUnderCursorHint && !anbt.isStroking)
- {
- var color = anbt.Eyedropper(x, y);
- if (lastSeenColorToHighlight != color)
- {
- var el = ID("colors").querySelector("b.hint");
- if (el) el.classList.remove("hint");
- var coloridx = anbt.palette.indexOf(color);
- if (coloridx >= 0)
- {
- var els = ID("colors").querySelectorAll("b");
- els[coloridx].classList.add("hint");
- }
- }
- lastSeenColorToHighlight = color;
- }
- });
- window.addEventListener('contextmenu', function(e)
- {
- if (anbt.isStroking) e.preventDefault();
- });
-
- var touchSingle = false;
- var lastTouch;
- var simulateSingleTouchStart = function()
- {
- if (touchSingle)
- {
- var x = lastTouch.pageX - rect.left - pageXOffset;
- var y = lastTouch.pageY - rect.top - pageYOffset;
- anbt.StrokeBegin(x, y, true);
- touchSingle = false;
- }
- };
- var touchMove = function(e)
- {
- if (e.touches.length === 1)
- {
- simulateSingleTouchStart();
- e.preventDefault();
- if (anbt.isStroking)
- {
- var x = e.touches[0].pageX - rect.left - pageXOffset;
- var y = e.touches[0].pageY - rect.top - pageYOffset;
- anbt.StrokeAdd(x, y);
- }
- }
- };
- var touchEnd = function(e)
- {
- if (e.touches.length === 0)
- {
- simulateSingleTouchStart();
- e.preventDefault();
- anbt.StrokeEnd();
- window.removeEventListener('touchend', touchEnd);
- window.removeEventListener('touchmove', touchMove);
- }
- };
- var touchUndoRedo = function(e)
- {
- if (e.changedTouches.length === 1 && e.touches.length === 1)
- {
- var ch = e.changedTouches[0];
- if (Math.abs(ch.pageX - lastTouch.pageX) < 10 &&
- Math.abs(ch.pageY - lastTouch.pageY) < 10)
- {
- ID("play").classList.remove("pause");
- if (ch.pageX < e.touches[0].pageX)
- {
- anbt.Undo();
- } else {
- anbt.Redo();
- }
- }
- }
- window.removeEventListener('touchend', touchUndoRedo);
- };
- ID("svgContainer").addEventListener('touchstart', function(e)
- {
- if (e.touches.length === 1)
- {
- if (checkPlayingAndStop()) return;
- // Let two-finger scrolling, pinching, etc. work.
- // This requires moving dot-drawing to simulateSingleTouchStart()
- rect = this.getBoundingClientRect();
- touchSingle = true;
- lastTouch = e.touches[0];
- window.addEventListener('touchend', touchEnd);
- window.addEventListener('touchmove', touchMove);
- } else {
- // Enable two-finger undo and redo:
- // 1 o o
- // 2 o o o o
- // 3 , o o .
- // Undo Redo
- if (touchSingle && e.touches.length === 3)
- {
- lastTouch = e.touches[1];
- window.addEventListener('touchend', touchUndoRedo);
- }
- if (anbt.isStroking)
- {
- anbt.StrokeEnd();
- }
- touchSingle = false;
- window.removeEventListener('touchend', touchEnd);
- window.removeEventListener('touchmove', touchMove);
- }
- });
-
- ID("svgContainer").addEventListener('mouseleave', function(e)
- {
- // Hide brush cursor
- anbt.MoveCursor(-100, -100);
- });
- ID("svgContainer").addEventListener('contextmenu', function(e)
- {
- e.preventDefault();
- });
-
- ID("import").addEventListener('click', function(e)
- {
- e.preventDefault();
- ID("svgContainer").classList.add("loading");
- anbt.FromLocalFile(e.shiftKey || e.ctrlKey);
- ID("svgContainer").classList.remove("loading");
- });
- var warnStrokesAfterPos = function()
- {
- return (anbt.position < anbt.GetSeekMax() && !confirm("Strokes after current position will be discarded. Continue?"));
- };
- var doExport = function(e)
- {
- e.preventDefault();
- if (warnStrokesAfterPos()) return;
- anbt.MakePNG(600, 500, true);
- anbt.RequestSave();
- };
- ID("export").addEventListener('click', doExport);
- ID("imgur").addEventListener('click', function(e)
- {
- e.preventDefault();
- if (warnStrokesAfterPos()) return;
- ID("imgur").childNodes[0].nodeValue = "Uploading...";
- ID("imgur").disabled = true;
- anbt.MakePNG(600, 500, true);
- anbt.UploadToImgur(function(r)
- {
- ID("imgur").childNodes[0].nodeValue = "Upload to imgur";
- ID("popup").classList.add("show");
- ID("popuptitle").childNodes[0].nodeValue = "Imgur upload result";
- if (r && r.success)
- {
- anbt.unsaved = false;
- history.replaceState(null, null, "#" + r.data.id);
- ID("imgururl").href = "http://imgur.com/" + r.data.id;
- ID("imgururl").childNodes[0].nodeValue = "Uploaded image";
- ID("imgurdelete").href = "http://imgur.com/delete/" + r.data.deletehash;
- ID("imgurerror").childNodes[0].nodeValue = "";
- } else {
- var err = r.data ? ("Imgur error: " + r.data.error) : ("Error: " + r);
- ID("imgurerror").childNodes[0].nodeValue = err;
- }
- ID("imgur").disabled = false;
- });
- });
- window.addEventListener('keydown', function(e)
- {
- });
- function makeBrushFunc(size)
- {
- var s = size;
- return function(e)
- {
- e.preventDefault();
- anbt.SetSize(s);
- var el = ID("tools").querySelector(".sel");
- if (el) el.classList.remove("sel");
- this.classList.add("sel");
- if (anbt.isStroking)
- {
- anbt.StrokeEnd();
- var p = anbt.points[anbt.points.length - 1];
- anbt.StrokeBegin(p.x, p.y);
- }
- };
- }
- var brushSizes = [2.4, 6, 14.4, 42];
-
- for (var i = 0; i < brushSizes.length; i++)
- {
- ID("brush" + i).addEventListener('click', makeBrushFunc(brushSizes[i]), false);
- }
- var updateColorIndicators = function()
- {
- var c0 = anbt.color[0];
- var c1 = anbt.color[1];
- if (c0 == 'eraser')
- {
- ID("primary").style.backgroundColor = 'pink';
- ID("primary").classList.add('eraser');
- } else {
- ID("primary").style.backgroundColor = c0;
- ID("primary").classList.remove('eraser');
- }
- if (c1 == 'eraser')
- {
- ID("secondary").style.backgroundColor = 'pink';
- ID("secondary").classList.add('eraser');
- } else {
- ID("secondary").style.backgroundColor = c1;
- ID("secondary").classList.remove('eraser');
- }
- };
- var chooseBackground = false;
- var updateChooseBackground = function(b)
- {
- chooseBackground = b;
- if (b)
- {
- ID("colors").classList.add("setbackground");
- ID("setbackground").classList.add("sel");
- } else {
- ID("colors").classList.remove("setbackground");
- ID("setbackground").classList.remove("sel");
- }
- };
- var colorClick = function(e)
- {
- if (e.touches || e.button === 0 || e.button === 2)
- {
- e.preventDefault();
- var color = this.style.backgroundColor;
- if (chooseBackground)
- {
- if (this.id != "eraser")
- {
- anbt.SetBackground(color);
- }
- updateChooseBackground(false);
- } else {
- var showcolor = color;
- if (this.id == "eraser")
- {
- color = "eraser";
- showcolor = "pink";
- }
- // PointerType == 3 is pen tablet eraser
- if (e.button === 2 || getPointerType() === 3)
- {
- anbt.SetColor(1, color);
- } else {
- anbt.SetColor(0, color);
- }
- updateColorIndicators();
- }
- }
- };
- var noDefault = function(e)
- {
- e.preventDefault();
- };
- var els = ID("colors").querySelectorAll("b");
- for (var i = 0; i < els.length; i++)
- {
- els[i].addEventListener('mousedown', colorClick);
- els[i].addEventListener('touchend', colorClick);
- els[i].addEventListener('contextmenu', noDefault);
- }
- ID("setbackground").addEventListener('click', function(e)
- {
- e.preventDefault();
- updateChooseBackground(!chooseBackground);
- });
- ID("undo").addEventListener('click', function(e)
- {
- e.preventDefault();
- ID("play").classList.remove("pause");
- anbt.Undo();
- });
- ID("redo").addEventListener('click', function(e)
- {
- e.preventDefault();
- ID("play").classList.remove("pause");
- anbt.Redo();
- });
- ID("trash").addEventListener('click', function(e)
- {
- e.preventDefault();
- anbt.ClearWithColor("eraser");
- if (ID("newcanvasyo").classList.contains("sandbox"))
- {
- timerStart = Date.now();
- }
- });
-
- var knobMove = function(fraction)
- {
- var x = Math.floor(fraction * 502 - 10);
- if (fraction > 0)
- {
- ID("knob").classList.add("smooth");
- } else {
- ID("knob").classList.remove("smooth");
- }
- ID("knob").style.marginLeft = x + 'px';
- if (fraction >= 1)
- {
- ID("play").classList.remove("pause");
- }
- };
- anbt.SetSeekbarMove(knobMove);
- var knobCommonMove = function(e)
- {
- e.preventDefault();
- var len = anbt.GetSeekMax();
- var x = (e.touches ? e.touches[0].pageX : e.pageX) - rect.left - pageXOffset - 34;
- x = Math.min(Math.max(-10, x), 492);
- var pos = Math.round((x + 10) / 502 * len);
- x = pos / len * 502 - 10;
- ID("knob").classList.add("smooth");
- ID("knob").style.marginLeft = x + 'px';
- anbt.Seek(pos);
- ID("play").classList.remove("pause");
- };
- var knobCommonUp = function(e)
- {
- if (e.button === 0 || e.touches && e.touches.length === 0)
- {
- e.preventDefault();
- window.removeEventListener('mouseup', knobCommonUp);
- window.removeEventListener('touchend', knobCommonUp);
- window.removeEventListener('mousemove', knobCommonMove);
- window.removeEventListener('touchmove', knobCommonMove);
- }
- };
- var knobCommonDown = function(e)
- {
- if (e.button === 0 || e.touches && e.touches.length === 1)
- {
- rect = ID("seekbar").getBoundingClientRect();
- knobCommonMove(e);
- window.addEventListener('mouseup', knobCommonUp);
- window.addEventListener('touchend', knobCommonUp);
- window.addEventListener('mousemove', knobCommonMove);
- window.addEventListener('touchmove', knobCommonMove);
- }
- };
- ID("knob").addEventListener('mousedown', knobCommonDown);
- ID("knob").addEventListener('touchstart', knobCommonDown);
- ID("seekbar").addEventListener('mousedown', knobCommonDown);
- ID("seekbar").addEventListener('touchstart', knobCommonDown);
-
- var playCommonDown = function(e)
- {
- e.stopPropagation();
- e.preventDefault();
- if (anbt.isPlaying)
- {
- ID("play").classList.remove("pause");
- anbt.Pause();
- } else {
- ID("play").classList.add("pause");
- anbt.Play();
- }
- };
- ID("play").addEventListener('mousedown', playCommonDown);
- ID("play").addEventListener('touchstart', playCommonDown);
-
- var choosePalette = function(e)
- {
- if (e.touches || e.button === 0)
- {
- e.preventDefault();
- var name = this.childNodes[0].nodeValue;
- ID("palettename").childNodes[0].nodeValue = name;
- var colors = palettes[name];
- anbt.palette = colors;
- var pal = ID("palette");
- var els = pal.querySelectorAll("b");
- // Remove all current colors except for the eraser
- for (var i = 0; i < els.length - 1; i++)
- {
- pal.removeChild(els[i]);
- }
- var eraser = els[els.length - 1];
- for (var i = 0; i < colors.length; i++)
- {
- var b = document.createElement("b");
- b.style.backgroundColor = colors[i];
- b.addEventListener('mousedown', colorClick);
- b.addEventListener('touchend', colorClick);
- b.addEventListener('contextmenu', noDefault);
- pal.appendChild(b);
- // Eraser got on the front, put it on the back
- pal.appendChild(eraser);
- }
- }
- };
- var closePaletteList = function(e)
- {
- if (e.touches || e.button === 0)
- {
- ID("palettechooser").classList.remove("open");
- window.removeEventListener('mousedown', closePaletteList);
- window.removeEventListener('touchend', closePaletteList);
- }
- };
- var openPaletteList = function(e)
- {
- if (e.touches || e.button === 0)
- {
- e.preventDefault();
- var chooser = ID("palettechooser");
- chooser.classList.toggle("open");
- if (chooser.classList.contains("open"))
- {
- setTimeout(function()
- {
- window.addEventListener('mousedown', closePaletteList);
- window.addEventListener('touchend', closePaletteList);
- }, 1);
- }
- var keys = Object.keys(palettes);
- if (chooser.childNodes.length < keys.length)
- {
- var canvas = document.createElement("canvas");
- canvas.height = 10;
- var ctx = canvas.getContext("2d");
- for (var i = chooser.childNodes.length; i < keys.length; i++)
- {
- canvas.width = 8 * palettes[keys[i]].length + 2;
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.globalAlpha = 0.5;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.globalAlpha = 1;
- for (var j = 0; j < palettes[keys[i]].length; j++)
- {
- ctx.fillStyle = palettes[keys[i]][j];
- ctx.fillRect(j * 8 + 1, 1, 8, 8);
- }
- var div = document.createElement("div");
- div.appendChild(document.createTextNode(keys[i]));
- div.style.backgroundImage = 'url("' + canvas.toDataURL() + '")';
- div.style.backgroundRepeat = 'no-repeat';
- div.style.backgroundPosition = 'center 35px';
- div.addEventListener('mousedown', choosePalette);
- div.addEventListener('touchend', choosePalette);
- chooser.appendChild(div);
- }
- }
- }
- };
- ID("palettename").addEventListener('mousedown', openPaletteList);
- ID("palettename").addEventListener('touchend', openPaletteList);
-
- // TODO: refactor: generalize with palette chooser menu
- var closeMenu = function(e)
- {
- if (e.touches || e.button === 0)
- {
- ID("menu").classList.remove("open");
- window.removeEventListener('mousedown', closeMenu);
- window.removeEventListener('touchend', closeMenu);
- }
- };
- var openMenu = function(e)
- {
- if (e.touches || e.button === 0)
- {
- e.preventDefault();
- var menu = ID("menu");
- menu.classList.toggle("open");
- if (menu.classList.contains("open"))
- {
- setTimeout(function()
- {
- window.addEventListener('mousedown', closeMenu);
- window.addEventListener('touchend', closeMenu);
- }, 1);
- }
-
- }
- };
- ID("openmenu").addEventListener('mousedown', openMenu);
- ID("openmenu").addEventListener('touchend', openMenu);
-
- var exitPlay = function(e)
- {
- e.preventDefault();
- ID("newcanvasyo").classList.add("sandbox");
- ID("newcanvasyo").classList.remove("play");
- timerStart -= 10*60*1000;
- timerCallback = null;
- };
- ID("exit").addEventListener('click', exitPlay);
- ID("submit").addEventListener('click', exitPlay);
-
- // Menu items
- var simulatePlay = function(e)
- {
- e.preventDefault();
- ID("newcanvasyo").classList.remove("sandbox");
- ID("newcanvasyo").classList.add("play");
- timerStart = Date.now() + 10*60*1000;
- timerCallback = function(s)
- {
- if (s < 0) exitPlay(e);
- };
- ID("drawthis").childNodes[0].nodeValue = randomPhrase();
- };
- ID("simplay").addEventListener('mousedown', simulatePlay);
- ID("simplay").addEventListener('touchend', simulatePlay);
- ID("skip").addEventListener('click', simulatePlay);
-
- var exportCustomPNG = function(e)
- {
- e.preventDefault();
- if (warnStrokesAfterPos()) return;
- var m = prompt("PNG size to export? Enter only width to maintain aspect ratio.", "600x500");
- if (!m) return;
- m = m.match(/(\d+)/g);
- if (!m)
- {
- alert("Invalid size.");
- return;
- }
- var width = m[0];
- var height = m[1] || Math.round(width / 1.2);
- anbt.MakePNG(width, height);
- anbt.RequestSave();
- };
- ID("custompng").addEventListener('mousedown', exportCustomPNG);
- ID("custompng").addEventListener('touchend', exportCustomPNG);
-
- var setTransparentBackground = function(e)
- {
- e.preventDefault();
- anbt.SetBackground("eraser");
- };
- ID("transparentbg").addEventListener('mousedown', setTransparentBackground);
- ID("transparentbg").addEventListener('touchend', setTransparentBackground);
-
- var enablePatterns = function(e)
- {
- e.preventDefault();
- ID("enablepatterns").childNodes[0].nodeValue = "(patterns are enabled)"
- ID("enablepatterns").removeEventListener('mousedown', enablePatterns);
- ID("enablepatterns").removeEventListener('touchend', enablePatterns);
- var div = document.createElement('div');
- div.className = "panel";
- div.appendChild(document.createTextNode("pattern:"));
-
- var canvas = document.createElement("canvas");
- canvas.width = 48;
- canvas.height = 48;
- var ctx = canvas.getContext("2d");
- var makePatternPreview = function(pattern)
- {
- ctx.clearRect(0, 0, 48, 48);
- ctx.fillStyle = anbt.MakePattern("#000", pattern);
- ctx.fillRect(0, 0, 48, 48);
- return canvas.toDataURL();
- };
- var makePatternClick = function(pattern)
- {
- return function() { anbt.SetPattern(pattern); }
- };
- for (var i = 0; i < 6; i++)
- {
- var button = document.createElement('button');
- button.style.width = "55px";
- button.style.height = "44px";
- button.style.margin = "0 2px";
- button.style.verticalAlign = "top";
- if (i === 0) button.appendChild(document.createTextNode("(none)"));
- if (i > 0) button.style.backgroundImage = 'url("' + makePatternPreview(i) + '")';
- button.addEventListener('click', makePatternClick(i));
- div.appendChild(button);
- }
- ID("newcanvasyo").appendChild(div);
- };
- ID("enablepatterns").addEventListener('mousedown', enablePatterns);
- ID("enablepatterns").addEventListener('touchend', enablePatterns);
-
- var exportWebM = function(e)
- {
- e.preventDefault();
- anbt.ExportWebM();
- }
- ID("exportwebm").addEventListener('mousedown', exportWebM);
- ID("exportwebm").addEventListener('touchend', exportWebM);
-
- var usageTips = function(e)
- {
- e.preventDefault();
- alert("Read tooltips on the buttons!\n\n" +
- "Press Alt to pick colors (with Shift to preserve opacity)\n" +
- "Press T to halve primary color opacity\n" +
- "Press X to swap colors\n\n" +
- "On touchscreen:\n" +
- "put one finger, tap with second finger on the left to undo,\non the right to redo");
- }
- ID("usagetips").addEventListener('mousedown', usageTips);
- ID("usagetips").addEventListener('touchend', usageTips);
-
- var drawTransitions = function(e)
- {
- if (anbt.unsaved && !confirm("You haven't saved the drawing. Continue?")) return;
- e.preventDefault();
- anbt.SetBackground(anbt.palette[0]);
- anbt.ctx.clearRect(0, 0, 600, 500);
- var numcolors = anbt.palette.length;
- var w = 36;
- var h = 30;
- var offsetx = 300 - numcolors * 18 + 30;
- var offsety = 250 - numcolors * 15 - 24;
- var color1, color2, c;
- for (var y = 1; y <= numcolors; y++)
- {
- for (var x = -1; x < numcolors - 1; x++)
- {
- if (x == -1 || x == numcolors)
- {
- if (y == -1 || y == numcolors) continue;
- if (y == 0)
- {
- anbt.ctx.fillStyle = anbt.palette[numcolors - 1]
- anbt.ctx.fillRect(x * w + offsetx - 1, y * h + offsety - 1, w + 1, h + 1);
- }
- anbt.ctx.fillStyle = anbt.palette[y];
- anbt.ctx.fillRect(x * w + offsetx, y * h + offsety, w - 1, h - 1);
- } else if (y == -1 || y == numcolors)
- {
- if (x == 0)
- {
- anbt.ctx.fillStyle = anbt.palette[numcolors - 1]
- anbt.ctx.fillRect(x * w + offsetx - 1, y * h + offsety - 1, w + 1, h + 1);
- }
- anbt.ctx.fillStyle = anbt.palette[x];
- anbt.ctx.fillRect(x * w + offsetx, y * h + offsety, w - 1, h - 1);
- } else {
- if (x == y)
- {
- anbt.ctx.fillStyle = anbt.palette[x];
- anbt.ctx.fillRect(x * w + offsetx, y * h + offsety, w - 1, h - 1);
- }
- if (x >= y) continue;
- color1 = rgb2lab(color2rgba(anbt.palette[x]));
- color2 = rgb2lab(color2rgba(anbt.palette[y]));
- for (var xx = 34; xx >= 0; xx--)
- {
- c = getColorAverage(color1, color2, xx / 34);
- c = getClosestColorLab(c, anbt.palette);
- anbt.ctx.fillStyle = c;
- anbt.ctx.fillRect(x * w + offsetx + xx, y * h + offsety, 1, h - 1);
- //anbt.ctx.beginPath();
- //anbt.ctx.arc(x * w + w/4 + xx/4 + offsetx, y * h + h/4 + xx/4 + offsety, xx / 2, 0, Math.PI * 2, true);
- //anbt.ctx.fill();
- }
- }
- }
- }
- }
- ID("drawtransitions").addEventListener('mousedown', drawTransitions);
- ID("drawtransitions").addEventListener('touchend', drawTransitions);
-
- var setBackgroundImage = function(e)
- {
- if (anbt.unsaved && !confirm("You haven't saved the drawing. Continue?")) return;
- e.preventDefault();
- anbt.SetBackground("eraser");
- ID("svgContainer").style.backgroundImage = 'url(' + prompt('Enter image URL to use as a background:') + ')';
- ID("svgContainer").style.backgroundSize = '100%';
- };
- ID("setbackgroundimage").addEventListener('mousedown', setBackgroundImage);
- ID("setbackgroundimage").addEventListener('touchend', setBackgroundImage);
-
- var toggleSmooth = function(e)
- {
- if (window.toggleSmooth)
- {
- buildSmoothPath = old_buildSmoothPath;
- } else {
- window.old_buildSmoothPath = buildSmoothPath;
- buildSmoothPath = function(points, path)
- {
- if (points.length < 2) return;
- path.pathSegList.initialize(path.createSVGPathSegMovetoAbs(points[0].x, points[0].y));
- for (var i = 1; i < points.length; i++)
- {
- var c = points[i];
- path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y));
- }
- };
- }
- window.toggleSmooth = !window.toggleSmooth;
- ID("togglesmooth").childNodes[0].nodeValue =
- (window.toggleSmooth ? "Enable" : "Disable") + " stroke smoothening";
- };
- ID("togglesmooth").addEventListener('mousedown', toggleSmooth);
- ID("togglesmooth").addEventListener('touchend', toggleSmooth);
-
- var setSnap = function(e)
- {
- var m = prompt("Grid size in pixels (6 makes brush 2 and brush 4 exact; 0 to disable):", "0");
- if (!m) return;
- m = m.match(/(\d+)/g);
- if (!m) return;
- var a = parseInt(m[0], 10);
- anbt.snap = a ? a : false;
- };
- ID("setsnap").addEventListener('mousedown', setSnap);
- ID("setsnap").addEventListener('touchend', setSnap);
-
- // ---
-
- ID("popupclose").addEventListener('click', function(e)
- {
- e.preventDefault();
- ID("popup").classList.remove("show");
- });
-
-
- document.addEventListener('keyup', function(e)
- {
- if (e.keyCode == 18) // Alt
- {
- ID("svgContainer").classList.remove("hidecursor");
- anbt.ShowEyedropperCursor(false);
- }
- });
- document.addEventListener('keydown', function(e)
- {
- if (document.activeElement instanceof HTMLInputElement) return true;
- if (e.keyCode == 18) // Alt
- {
- // Opera Presto refuses to hide cursor =(
- if (!navigator.userAgent.match(/\bPresto\b/))
- {
- ID("svgContainer").classList.add("hidecursor");
- }
- anbt.ShowEyedropperCursor(true);
- // The following is needed in case of Alt+Tab causing eyedropper to be stuck
- var removeEyedropper = function(e)
- {
- if (!e.altKey)
- {
- this.classList.remove("hidecursor");
- anbt.ShowEyedropperCursor(false);
- this.removeEventListener('mousemove', removeEyedropper);
- }
- };
- ID("svgContainer").addEventListener('mousemove', removeEyedropper);
- }
- else if (e.keyCode == "Q".charCodeAt(0))
- {
- e.preventDefault();
- options.colorDoublePress = !options.colorDoublePress;
- }
- else if (e.keyCode == "C".charCodeAt(0) && !e.ctrlKey && !e.metaKey)
- {
- e.preventDefault();
- options.hideCross = !options.hideCross;
- }
- else if (e.keyCode == "Z".charCodeAt(0) || ((e.keyCode == 8) && anbt.unsaved))
- {
- e.preventDefault();
- ID("play").classList.remove("pause");
- anbt.Undo();
- }
- else if (e.keyCode == "Y".charCodeAt(0))
- {
- e.preventDefault();
- ID("play").classList.remove("pause");
- anbt.Redo();
- }
- else if (e.keyCode == "X".charCodeAt(0))
- {
- e.preventDefault();
- var c0 = anbt.color[0];
- var c1 = anbt.color[1];
- anbt.SetColor(0, c1);
- anbt.SetColor(1, c0);
- updateColorIndicators();
- }
- else if (e.keyCode == "B".charCodeAt(0))
- {
- e.preventDefault();
- updateChooseBackground(!chooseBackground);
- }
- else if (e.keyCode == "E".charCodeAt(0) && !e.ctrlKey && !e.metaKey)
- {
- e.preventDefault();
- anbt.SetColor(0, "eraser");
- updateColorIndicators();
- }
- else if (e.keyCode >= 48 && e.keyCode <= 57 && !e.ctrlKey && !e.metaKey && options.colorNumberShortcuts)
- {
- e.preventDefault();
- var i = (e.keyCode == 48) ? 9 : e.keyCode - 49;
- if (e.shiftKey || (options.colorDoublePress && (anbt.prevColorKey == i))) i += 8;
- anbt.prevColorKey = i;
- if (options.colorDoublePress)
- {
- if (anbt.prevColorKeyTimer) clearTimeout(anbt.prevColorKeyTimer);
- anbt.prevColorKeyTimer = setTimeout(function() {anbt.prevColorKey = -1}, 500);
- }
- var els = ID("colors").querySelectorAll("b");
- if (i < els.length)
- {
- var color = els[i].style.backgroundColor;
- if (els[i].id == "eraser") color = "eraser";
- if (chooseBackground)
- {
- if (color != "eraser")
- {
- anbt.SetBackground(color);
- }
- updateChooseBackground(false);
- } else {
- anbt.SetColor(0, color);
- updateColorIndicators();
- }
- }
- if (anbt.isStroking)
- {
- anbt.StrokeEnd();
- var p = anbt.points[anbt.points.length - 1];
- anbt.StrokeBegin(p.x, p.y);
- }
- }
- else if (e.keyCode == "T".charCodeAt(0) && !e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey)
- {
- e.preventDefault();
-
- if (anbt.color[0] != "eraser")
- {
- var c = color2rgba(anbt.color[0]);
- var alpha = Math.round(Math.max(c[3]/510, 0.06) * 100) / 100;
- anbt.SetColor(0, "rgba(" + c[0] + "," + c[1] + "," + c[2] + "," + alpha + ")");
- }
- updateColorIndicators();
- }
- else if (e.keyCode == "F".charCodeAt(0) && !e.shiftKey)
- {
- e.preventDefault();
- anbt.fillNext = true;
- }
- else if (e.keyCode == "F".charCodeAt(0) && e.shiftKey)
- {
- e.preventDefault();
- anbt.ClearWithColor(anbt.color[0]);
- }
- else if ((e.keyCode == 189 || e.keyCode == 219 || e.keyCode == 188) && !e.ctrlKey && !e.metaKey) // - or [ or ,
- {
- e.preventDefault();
- for (var i = 1; i < brushSizes.length; i++)
- {
- if (anbt.size - brushSizes[i] < 0.01)
- {
- ID("brush" + (i - 1)).click();
- break;
- }
- }
- }
- else if ((e.keyCode == 187 || e.keyCode == 221 || e.keyCode == 190) && !e.ctrlKey && !e.metaKey) // = or ] or .
- {
- e.preventDefault();
- for (var i = 0; i < brushSizes.length - 1; i++)
- {
- if (anbt.size - brushSizes[i] < 0.01)
- {
- ID("brush" + (i + 1)).click();
- break;
- }
- }
- }
- else if (e.keyCode >= 49 && e.keyCode <= 52 && (e.ctrlKey || e.metaKey)) // Ctrl+1,2,3,4
- {
- e.preventDefault();
- ID("brush" + (e.keyCode - 49)).click();
- }
- else if (e.keyCode == 32 && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey)
- {
- playCommonDown(e);
- }
- });
- window.onerror = function(e)
- {
- alert(e);
- };
- window.onbeforeunload = function(e)
- {
- if (anbt.unsaved)
- {
- var msg = "You haven't saved the drawing. Abandon?";
- e.returnValue = msg;
- return msg;
- }
- };
-}
-
-function fixPluginGoingAWOL()
-{
- var stupidPlugin = ID("wacom");
- var container = ID("wacomContainer");
- window.onblur = function(e)
- {
- if (container.childNodes.length === 1) container.removeChild(stupidPlugin);
- };
- window.onfocus = function(e)
- {
- if (container.childNodes.length === 0) container.appendChild(stupidPlugin);
- };
-}
-
-function runTimer()
-{
- setInterval(function()
- {
- var s = (timerStart - Date.now()) / 1000;
- if (timerCallback) timerCallback(s);
- s = Math.abs(s);
- var m = Math.floor(s / 60);
- s = Math.floor(s % 60);
- if (m < 10) m = '0' + m;
- if (s < 10) s = '0' + s;
- ID("timer").childNodes[0].nodeValue = m + ':' + s;
- },
- 500);
-}
-
-function main()
-{
- if (!window.options) window.options = {};
- if (options.enableWacom == "auto")
- {
- options.enableWacom = 0;
- for (var i = 0; i < navigator.plugins.length; i++)
- {
- if (navigator.plugins[i].name.match(/wacom/i))
- {
- options.enableWacom = 1;
- break;
- }
- }
- }
- if (options.enableWacom)
- {
- var stupidPlugin = document.createElement("object");
- var container = ID("wacomContainer");
- stupidPlugin.setAttribute("id", "wacom");
- stupidPlugin.setAttribute("type", "application/x-wacomtabletplugin");
- stupidPlugin.setAttribute("width", "1");
- stupidPlugin.setAttribute("height", "1");
- container.appendChild(stupidPlugin);
- if (options.fixTabletPluginGoingAWOL) fixPluginGoingAWOL();
- }
- anbt.BindContainer(ID("svgContainer"));
- bindEvents();
- ID("svgContainer").style.background = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAHElEQVR4AWPYgAM8wAFoo2FUAy4JXAbRRMOoBgD42lgf5s146gAAAABJRU5ErkJggg==")';
- if (window.location.hash.length > 7)
- {
- var id = window.location.hash.substr(1);
- var m = id.match(/drawception\/(\w{8})/);
- if (m)
- {
- anbt.FromURL("drawception-get-panel.php?panelid=" + m[1]);
- } else {
- anbt.FromImgur(id);
- }
- }
- timerStart = Date.now();
- runTimer();
-}
-
-if (!("SVGPathSeg" in window))
-{
- require("pathseg.min.js", main)
-} else {
- main();
-}
diff --git a/newcanvas/favicon.png b/newcanvas/favicon.png
deleted file mode 100644
index 804326f..0000000
Binary files a/newcanvas/favicon.png and /dev/null differ
diff --git a/newcanvas/index.html b/newcanvas/index.html
deleted file mode 100644
index 5a22254..0000000
--- a/newcanvas/index.html
+++ /dev/null
@@ -1,138 +0,0 @@
-
-
-
-
-
-
-Drawing in Time!
-
-
-
-
- A cow jumping over the moon
-
-
-
-
-
-
-
-
- Sandbox
- Public safe for work game
-
-
-
-
-
-
-
-
-
-
-
Guidelines
-
- - Simple drawings are welcome! Just give it your best shot
- - If the phrase is too difficult, use the skip button
- - Don't draw text - always draw a picture
- - Explicit (NSFW) drawings will get you banned
-
-
-
Sandbox
-
- - Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
- - Saved drawings will contain your drawing process in PNG format.
- - After loading a drawing you can continue to draw or watch its playback.
- - Note that editing the PNG with playback with other programs will likely destroy the playback data.
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/newcanvas/newcanvasicons.png b/newcanvas/newcanvasicons.png
deleted file mode 100644
index ffc9fc8..0000000
Binary files a/newcanvas/newcanvasicons.png and /dev/null differ
diff --git a/newcanvas/nunito.woff b/newcanvas/nunito.woff
deleted file mode 100644
index 1e9545d..0000000
Binary files a/newcanvas/nunito.woff and /dev/null differ
diff --git a/newcanvas/pako.min.js b/newcanvas/pako.min.js
deleted file mode 100644
index 28c0a2a..0000000
--- a/newcanvas/pako.min.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/* pako 0.2.5 nodeca/pako */
-!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.pako=t()}}(function(){return function t(e,a,i){function n(s,o){if(!a[s]){if(!e[s]){var l="function"==typeof require&&require;if(!o&&l)return l(s,!0);if(r)return r(s,!0);throw new Error("Cannot find module '"+s+"'")}var h=a[s]={exports:{}};e[s][0].call(h.exports,function(t){var a=e[s][1][t];return n(a?a:t)},h,h.exports,t,e,a,i)}return a[s].exports}for(var r="function"==typeof require&&require,s=0;s0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new d,this.strm.avail_out=0;var a=s.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==u)throw new Error(h[a]);e.header&&s.deflateSetHeader(this.strm,e.header)};w.prototype.push=function(t,e){var a,i,n=this.strm,r=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:e===!0?_:f,n.input="string"==typeof t?l.string2buf(t):t,n.next_in=0,n.avail_in=n.input.length;do{if(0===n.avail_out&&(n.output=new o.Buf8(r),n.next_out=0,n.avail_out=r),a=s.deflate(n,i),a!==c&&a!==u)return this.onEnd(a),this.ended=!0,!1;(0===n.avail_out||0===n.avail_in&&i===_)&&this.onData("string"===this.options.to?l.buf2binstring(o.shrinkBuf(n.output,n.next_out)):o.shrinkBuf(n.output,n.next_out))}while((n.avail_in>0||0===n.avail_out)&&a!==c);return i===_?(a=s.deflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===u):!0},w.prototype.onData=function(t){this.chunks.push(t)},w.prototype.onEnd=function(t){t===u&&(this.result="string"===this.options.to?this.chunks.join(""):o.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},a.Deflate=w,a.deflate=i,a.deflateRaw=n,a.gzip=r},{"./utils/common":4,"./utils/strings":5,"./zlib/deflate.js":9,"./zlib/messages":14,"./zlib/zstream":16}],3:[function(t,e,a){"use strict";function i(t,e){var a=new _(e);if(a.push(t,!0),a.err)throw a.msg;return a.result}function n(t,e){return e=e||{},e.raw=!0,i(t,e)}var r=t("./zlib/inflate.js"),s=t("./utils/common"),o=t("./utils/strings"),l=t("./zlib/constants"),h=t("./zlib/messages"),d=t("./zlib/zstream"),f=t("./zlib/gzheader"),_=function(t){this.options=s.assign({chunkSize:16384,windowBits:0,to:""},t||{});var e=this.options;e.raw&&e.windowBits>=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0===(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg="",this.ended=!1,this.chunks=[],this.strm=new d,this.strm.avail_out=0;var a=r.inflateInit2(this.strm,e.windowBits);if(a!==l.Z_OK)throw new Error(h[a]);this.header=new f,r.inflateGetHeader(this.strm,this.header)};_.prototype.push=function(t,e){var a,i,n,h,d,f=this.strm,_=this.options.chunkSize;if(this.ended)return!1;i=e===~~e?e:e===!0?l.Z_FINISH:l.Z_NO_FLUSH,f.input="string"==typeof t?o.binstring2buf(t):t,f.next_in=0,f.avail_in=f.input.length;do{if(0===f.avail_out&&(f.output=new s.Buf8(_),f.next_out=0,f.avail_out=_),a=r.inflate(f,l.Z_NO_FLUSH),a!==l.Z_STREAM_END&&a!==l.Z_OK)return this.onEnd(a),this.ended=!0,!1;f.next_out&&(0===f.avail_out||a===l.Z_STREAM_END||0===f.avail_in&&i===l.Z_FINISH)&&("string"===this.options.to?(n=o.utf8border(f.output,f.next_out),h=f.next_out-n,d=o.buf2string(f.output,n),f.next_out=h,f.avail_out=_-h,h&&s.arraySet(f.output,f.output,n,h,0),this.onData(d)):this.onData(s.shrinkBuf(f.output,f.next_out)))}while(f.avail_in>0&&a!==l.Z_STREAM_END);return a===l.Z_STREAM_END&&(i=l.Z_FINISH),i===l.Z_FINISH?(a=r.inflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===l.Z_OK):!0},_.prototype.onData=function(t){this.chunks.push(t)},_.prototype.onEnd=function(t){t===l.Z_OK&&(this.result="string"===this.options.to?this.chunks.join(""):s.flattenChunks(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg},a.Inflate=_,a.inflate=i,a.inflateRaw=n,a.ungzip=i},{"./utils/common":4,"./utils/strings":5,"./zlib/constants":7,"./zlib/gzheader":10,"./zlib/inflate.js":12,"./zlib/messages":14,"./zlib/zstream":16}],4:[function(t,e,a){"use strict";var i="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Int32Array;a.assign=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var a=e.shift();if(a){if("object"!=typeof a)throw new TypeError(a+"must be non-object");for(var i in a)a.hasOwnProperty(i)&&(t[i]=a[i])}}return t},a.shrinkBuf=function(t,e){return t.length===e?t:t.subarray?t.subarray(0,e):(t.length=e,t)};var n={arraySet:function(t,e,a,i,n){if(e.subarray&&t.subarray)return void t.set(e.subarray(a,a+i),n);for(var r=0;i>r;r++)t[n+r]=e[a+r]},flattenChunks:function(t){var e,a,i,n,r,s;for(i=0,e=0,a=t.length;a>e;e++)i+=t[e].length;for(s=new Uint8Array(i),n=0,e=0,a=t.length;a>e;e++)r=t[e],s.set(r,n),n+=r.length;return s}},r={arraySet:function(t,e,a,i,n){for(var r=0;i>r;r++)t[n+r]=e[a+r]},flattenChunks:function(t){return[].concat.apply([],t)}};a.setTyped=function(t){t?(a.Buf8=Uint8Array,a.Buf16=Uint16Array,a.Buf32=Int32Array,a.assign(a,n)):(a.Buf8=Array,a.Buf16=Array,a.Buf32=Array,a.assign(a,r))},a.setTyped(i)},{}],5:[function(t,e,a){"use strict";function i(t,e){if(65537>e&&(t.subarray&&s||!t.subarray&&r))return String.fromCharCode.apply(null,n.shrinkBuf(t,e));for(var a="",i=0;e>i;i++)a+=String.fromCharCode(t[i]);return a}var n=t("./common"),r=!0,s=!0;try{String.fromCharCode.apply(null,[0])}catch(o){r=!1}try{String.fromCharCode.apply(null,new Uint8Array(1))}catch(o){s=!1}for(var l=new n.Buf8(256),h=0;256>h;h++)l[h]=h>=252?6:h>=248?5:h>=240?4:h>=224?3:h>=192?2:1;l[254]=l[254]=1,a.string2buf=function(t){var e,a,i,r,s,o=t.length,l=0;for(r=0;o>r;r++)a=t.charCodeAt(r),55296===(64512&a)&&o>r+1&&(i=t.charCodeAt(r+1),56320===(64512&i)&&(a=65536+(a-55296<<10)+(i-56320),r++)),l+=128>a?1:2048>a?2:65536>a?3:4;for(e=new n.Buf8(l),s=0,r=0;l>s;r++)a=t.charCodeAt(r),55296===(64512&a)&&o>r+1&&(i=t.charCodeAt(r+1),56320===(64512&i)&&(a=65536+(a-55296<<10)+(i-56320),r++)),128>a?e[s++]=a:2048>a?(e[s++]=192|a>>>6,e[s++]=128|63&a):65536>a?(e[s++]=224|a>>>12,e[s++]=128|a>>>6&63,e[s++]=128|63&a):(e[s++]=240|a>>>18,e[s++]=128|a>>>12&63,e[s++]=128|a>>>6&63,e[s++]=128|63&a);return e},a.buf2binstring=function(t){return i(t,t.length)},a.binstring2buf=function(t){for(var e=new n.Buf8(t.length),a=0,i=e.length;i>a;a++)e[a]=t.charCodeAt(a);return e},a.buf2string=function(t,e){var a,n,r,s,o=e||t.length,h=new Array(2*o);for(n=0,a=0;o>a;)if(r=t[a++],128>r)h[n++]=r;else if(s=l[r],s>4)h[n++]=65533,a+=s-1;else{for(r&=2===s?31:3===s?15:7;s>1&&o>a;)r=r<<6|63&t[a++],s--;s>1?h[n++]=65533:65536>r?h[n++]=r:(r-=65536,h[n++]=55296|r>>10&1023,h[n++]=56320|1023&r)}return i(h,n)},a.utf8border=function(t,e){var a;for(e=e||t.length,e>t.length&&(e=t.length),a=e-1;a>=0&&128===(192&t[a]);)a--;return 0>a?e:0===a?e:a+l[t[a]]>e?a:e}},{"./common":4}],6:[function(t,e){"use strict";function a(t,e,a,i){for(var n=65535&t|0,r=t>>>16&65535|0,s=0;0!==a;){s=a>2e3?2e3:a,a-=s;do n=n+e[i++]|0,r=r+n|0;while(--s);n%=65521,r%=65521}return n|r<<16|0}e.exports=a},{}],7:[function(t,e){e.exports={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8}},{}],8:[function(t,e){"use strict";function a(){for(var t,e=[],a=0;256>a;a++){t=a;for(var i=0;8>i;i++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e}function i(t,e,a,i){var r=n,s=i+a;t=-1^t;for(var o=i;s>o;o++)t=t>>>8^r[255&(t^e[o])];return-1^t}var n=a();e.exports=i},{}],9:[function(t,e,a){"use strict";function i(t,e){return t.msg=I[e],e}function n(t){return(t<<1)-(t>4?9:0)}function r(t){for(var e=t.length;--e>=0;)t[e]=0}function s(t){var e=t.state,a=e.pending;a>t.avail_out&&(a=t.avail_out),0!==a&&(A.arraySet(t.output,e.pending_buf,e.pending_out,a,t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))}function o(t,e){Z._tr_flush_block(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,s(t.strm)}function l(t,e){t.pending_buf[t.pending++]=e}function h(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e}function d(t,e,a,i){var n=t.avail_in;return n>i&&(n=i),0===n?0:(t.avail_in-=n,A.arraySet(e,t.input,t.next_in,n,a),1===t.state.wrap?t.adler=R(t.adler,e,n,a):2===t.state.wrap&&(t.adler=C(t.adler,e,n,a)),t.next_in+=n,t.total_in+=n,n)}function f(t,e){var a,i,n=t.max_chain_length,r=t.strstart,s=t.prev_length,o=t.nice_match,l=t.strstart>t.w_size-he?t.strstart-(t.w_size-he):0,h=t.window,d=t.w_mask,f=t.prev,_=t.strstart+le,u=h[r+s-1],c=h[r+s];t.prev_length>=t.good_match&&(n>>=2),o>t.lookahead&&(o=t.lookahead);do if(a=e,h[a+s]===c&&h[a+s-1]===u&&h[a]===h[r]&&h[++a]===h[r+1]){r+=2,a++;do;while(h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&_>r);if(i=le-(_-r),r=_-le,i>s){if(t.match_start=e,s=i,i>=o)break;u=h[r+s-1],c=h[r+s]}}while((e=f[e&d])>l&&0!==--n);return s<=t.lookahead?s:t.lookahead}function _(t){var e,a,i,n,r,s=t.w_size;do{if(n=t.window_size-t.lookahead-t.strstart,t.strstart>=s+(s-he)){A.arraySet(t.window,t.window,s,s,0),t.match_start-=s,t.strstart-=s,t.block_start-=s,a=t.hash_size,e=a;do i=t.head[--e],t.head[e]=i>=s?i-s:0;while(--a);a=s,e=a;do i=t.prev[--e],t.prev[e]=i>=s?i-s:0;while(--a);n+=s}if(0===t.strm.avail_in)break;if(a=d(t.strm,t.window,t.strstart+t.lookahead,n),t.lookahead+=a,t.lookahead+t.insert>=oe)for(r=t.strstart-t.insert,t.ins_h=t.window[r],t.ins_h=(t.ins_h<t.pending_buf_size-5&&(a=t.pending_buf_size-5);;){if(t.lookahead<=1){if(_(t),0===t.lookahead&&e===N)return we;if(0===t.lookahead)break}t.strstart+=t.lookahead,t.lookahead=0;var i=t.block_start+a;if((0===t.strstart||t.strstart>=i)&&(t.lookahead=t.strstart-i,t.strstart=i,o(t,!1),0===t.strm.avail_out))return we;if(t.strstart-t.block_start>=t.w_size-he&&(o(t,!1),0===t.strm.avail_out))return we}return t.insert=0,e===D?(o(t,!0),0===t.strm.avail_out?ve:ke):t.strstart>t.block_start&&(o(t,!1),0===t.strm.avail_out)?we:we}function c(t,e){for(var a,i;;){if(t.lookahead=oe&&(t.ins_h=(t.ins_h<=oe)if(i=Z._tr_tally(t,t.strstart-t.match_start,t.match_length-oe),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=oe){t.match_length--;do t.strstart++,t.ins_h=(t.ins_h<=oe&&(t.ins_h=(t.ins_h<4096)&&(t.match_length=oe-1)),t.prev_length>=oe&&t.match_length<=t.prev_length){n=t.strstart+t.lookahead-oe,i=Z._tr_tally(t,t.strstart-1-t.prev_match,t.prev_length-oe),t.lookahead-=t.prev_length-1,t.prev_length-=2;do++t.strstart<=n&&(t.ins_h=(t.ins_h<=oe&&t.strstart>0&&(n=t.strstart-1,i=s[n],i===s[++n]&&i===s[++n]&&i===s[++n])){r=t.strstart+le;do;while(i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&i===s[++n]&&r>n);t.match_length=le-(r-n),t.match_length>t.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=oe?(a=Z._tr_tally(t,1,t.match_length-oe),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=Z._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(o(t,!1),0===t.strm.avail_out))return we}return t.insert=0,e===D?(o(t,!0),0===t.strm.avail_out?ve:ke):t.last_lit&&(o(t,!1),0===t.strm.avail_out)?we:pe}function m(t,e){for(var a;;){if(0===t.lookahead&&(_(t),0===t.lookahead)){if(e===N)return we;break}if(t.match_length=0,a=Z._tr_tally(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(o(t,!1),0===t.strm.avail_out))return we}return t.insert=0,e===D?(o(t,!0),0===t.strm.avail_out?ve:ke):t.last_lit&&(o(t,!1),0===t.strm.avail_out)?we:pe}function w(t){t.window_size=2*t.w_size,r(t.head),t.max_lazy_match=E[t.level].max_lazy,t.good_match=E[t.level].good_length,t.nice_match=E[t.level].nice_length,t.max_chain_length=E[t.level].max_chain,t.strstart=0,t.block_start=0,t.lookahead=0,t.insert=0,t.match_length=t.prev_length=oe-1,t.match_available=0,t.ins_h=0}function p(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=J,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new A.Buf16(2*re),this.dyn_dtree=new A.Buf16(2*(2*ie+1)),this.bl_tree=new A.Buf16(2*(2*ne+1)),r(this.dyn_ltree),r(this.dyn_dtree),r(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new A.Buf16(se+1),this.heap=new A.Buf16(2*ae+1),r(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new A.Buf16(2*ae+1),r(this.depth),this.l_buf=0,this.lit_bufsize=0,this.last_lit=0,this.d_buf=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}function v(t){var e;return t&&t.state?(t.total_in=t.total_out=0,t.data_type=W,e=t.state,e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=e.wrap?fe:ge,t.adler=2===e.wrap?0:1,e.last_flush=N,Z._tr_init(e),L):i(t,H)}function k(t){var e=v(t);return e===L&&w(t.state),e}function x(t,e){return t&&t.state?2!==t.state.wrap?H:(t.state.gzhead=e,L):H}function y(t,e,a,n,r,s){if(!t)return H;var o=1;if(e===K&&(e=6),0>n?(o=0,n=-n):n>15&&(o=2,n-=16),1>r||r>Q||a!==J||8>n||n>15||0>e||e>9||0>s||s>G)return i(t,H);8===n&&(n=9);var l=new p;return t.state=l,l.strm=t,l.wrap=o,l.gzhead=null,l.w_bits=n,l.w_size=1<>1,l.l_buf=3*l.lit_bufsize,l.level=e,l.strategy=s,l.method=a,k(t)}function z(t,e){return y(t,e,J,V,$,X)}function B(t,e){var a,o,d,f;if(!t||!t.state||e>F||0>e)return t?i(t,H):H;if(o=t.state,!t.output||!t.input&&0!==t.avail_in||o.status===me&&e!==D)return i(t,0===t.avail_out?M:H);if(o.strm=t,a=o.last_flush,o.last_flush=e,o.status===fe)if(2===o.wrap)t.adler=0,l(o,31),l(o,139),l(o,8),o.gzhead?(l(o,(o.gzhead.text?1:0)+(o.gzhead.hcrc?2:0)+(o.gzhead.extra?4:0)+(o.gzhead.name?8:0)+(o.gzhead.comment?16:0)),l(o,255&o.gzhead.time),l(o,o.gzhead.time>>8&255),l(o,o.gzhead.time>>16&255),l(o,o.gzhead.time>>24&255),l(o,9===o.level?2:o.strategy>=q||o.level<2?4:0),l(o,255&o.gzhead.os),o.gzhead.extra&&o.gzhead.extra.length&&(l(o,255&o.gzhead.extra.length),l(o,o.gzhead.extra.length>>8&255)),o.gzhead.hcrc&&(t.adler=C(t.adler,o.pending_buf,o.pending,0)),o.gzindex=0,o.status=_e):(l(o,0),l(o,0),l(o,0),l(o,0),l(o,0),l(o,9===o.level?2:o.strategy>=q||o.level<2?4:0),l(o,xe),o.status=ge);else{var _=J+(o.w_bits-8<<4)<<8,u=-1;u=o.strategy>=q||o.level<2?0:o.level<6?1:6===o.level?2:3,_|=u<<6,0!==o.strstart&&(_|=de),_+=31-_%31,o.status=ge,h(o,_),0!==o.strstart&&(h(o,t.adler>>>16),h(o,65535&t.adler)),t.adler=1}if(o.status===_e)if(o.gzhead.extra){for(d=o.pending;o.gzindex<(65535&o.gzhead.extra.length)&&(o.pending!==o.pending_buf_size||(o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending!==o.pending_buf_size));)l(o,255&o.gzhead.extra[o.gzindex]),o.gzindex++;o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),o.gzindex===o.gzhead.extra.length&&(o.gzindex=0,o.status=ue)}else o.status=ue;if(o.status===ue)if(o.gzhead.name){d=o.pending;do{if(o.pending===o.pending_buf_size&&(o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending===o.pending_buf_size)){f=1;break}f=o.gzindexd&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),0===f&&(o.gzindex=0,o.status=ce)}else o.status=ce;if(o.status===ce)if(o.gzhead.comment){d=o.pending;do{if(o.pending===o.pending_buf_size&&(o.gzhead.hcrc&&o.pending>d&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),s(t),d=o.pending,o.pending===o.pending_buf_size)){f=1;break}f=o.gzindexd&&(t.adler=C(t.adler,o.pending_buf,o.pending-d,d)),0===f&&(o.status=be)}else o.status=be;if(o.status===be&&(o.gzhead.hcrc?(o.pending+2>o.pending_buf_size&&s(t),o.pending+2<=o.pending_buf_size&&(l(o,255&t.adler),l(o,t.adler>>8&255),t.adler=0,o.status=ge)):o.status=ge),0!==o.pending){if(s(t),0===t.avail_out)return o.last_flush=-1,L}else if(0===t.avail_in&&n(e)<=n(a)&&e!==D)return i(t,M);if(o.status===me&&0!==t.avail_in)return i(t,M);if(0!==t.avail_in||0!==o.lookahead||e!==N&&o.status!==me){var c=o.strategy===q?m(o,e):o.strategy===Y?g(o,e):E[o.level].func(o,e);if((c===ve||c===ke)&&(o.status=me),c===we||c===ve)return 0===t.avail_out&&(o.last_flush=-1),L;if(c===pe&&(e===O?Z._tr_align(o):e!==F&&(Z._tr_stored_block(o,0,0,!1),e===T&&(r(o.head),0===o.lookahead&&(o.strstart=0,o.block_start=0,o.insert=0))),s(t),0===t.avail_out))return o.last_flush=-1,L}return e!==D?L:o.wrap<=0?U:(2===o.wrap?(l(o,255&t.adler),l(o,t.adler>>8&255),l(o,t.adler>>16&255),l(o,t.adler>>24&255),l(o,255&t.total_in),l(o,t.total_in>>8&255),l(o,t.total_in>>16&255),l(o,t.total_in>>24&255)):(h(o,t.adler>>>16),h(o,65535&t.adler)),s(t),o.wrap>0&&(o.wrap=-o.wrap),0!==o.pending?L:U)}function S(t){var e;return t&&t.state?(e=t.state.status,e!==fe&&e!==_e&&e!==ue&&e!==ce&&e!==be&&e!==ge&&e!==me?i(t,H):(t.state=null,e===ge?i(t,j):L)):H}var E,A=t("../utils/common"),Z=t("./trees"),R=t("./adler32"),C=t("./crc32"),I=t("./messages"),N=0,O=1,T=3,D=4,F=5,L=0,U=1,H=-2,j=-3,M=-5,K=-1,P=1,q=2,Y=3,G=4,X=0,W=2,J=8,Q=9,V=15,$=8,te=29,ee=256,ae=ee+1+te,ie=30,ne=19,re=2*ae+1,se=15,oe=3,le=258,he=le+oe+1,de=32,fe=42,_e=69,ue=73,ce=91,be=103,ge=113,me=666,we=1,pe=2,ve=3,ke=4,xe=3,ye=function(t,e,a,i,n){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=i,this.func=n};E=[new ye(0,0,0,0,u),new ye(4,4,8,4,c),new ye(4,5,16,8,c),new ye(4,6,32,32,c),new ye(4,4,16,16,b),new ye(8,16,32,32,b),new ye(8,16,128,128,b),new ye(8,32,128,256,b),new ye(32,128,258,1024,b),new ye(32,258,258,4096,b)],a.deflateInit=z,a.deflateInit2=y,a.deflateReset=k,a.deflateResetKeep=v,a.deflateSetHeader=x,a.deflate=B,a.deflateEnd=S,a.deflateInfo="pako deflate (from Nodeca project)"},{"../utils/common":4,"./adler32":6,"./crc32":8,"./messages":14,"./trees":15}],10:[function(t,e){"use strict";function a(){this.text=0,this.time=0,this.xflags=0,this.os=0,this.extra=null,this.extra_len=0,this.name="",this.comment="",this.hcrc=0,this.done=!1}e.exports=a},{}],11:[function(t,e){"use strict";var a=30,i=12;e.exports=function(t,e){var n,r,s,o,l,h,d,f,_,u,c,b,g,m,w,p,v,k,x,y,z,B,S,E,A;n=t.state,r=t.next_in,E=t.input,s=r+(t.avail_in-5),o=t.next_out,A=t.output,l=o-(e-t.avail_out),h=o+(t.avail_out-257),d=n.dmax,f=n.wsize,_=n.whave,u=n.wnext,c=n.window,b=n.hold,g=n.bits,m=n.lencode,w=n.distcode,p=(1<g&&(b+=E[r++]<>>24,b>>>=x,g-=x,x=k>>>16&255,0===x)A[o++]=65535&k;else{if(!(16&x)){if(0===(64&x)){k=m[(65535&k)+(b&(1<g&&(b+=E[r++]<>>=x,g-=x),15>g&&(b+=E[r++]<>>24,b>>>=x,g-=x,x=k>>>16&255,!(16&x)){if(0===(64&x)){k=w[(65535&k)+(b&(1<g&&(b+=E[r++]<g&&(b+=E[r++]<d){t.msg="invalid distance too far back",n.mode=a;break t}if(b>>>=x,g-=x,x=o-l,z>x){if(x=z-x,x>_&&n.sane){t.msg="invalid distance too far back",n.mode=a;break t}if(B=0,S=c,0===u){if(B+=f-x,y>x){y-=x;do A[o++]=c[B++];while(--x);B=o-z,S=A}}else if(x>u){if(B+=f+u-x,x-=u,y>x){y-=x;do A[o++]=c[B++];while(--x);if(B=0,y>u){x=u,y-=x;do A[o++]=c[B++];while(--x);B=o-z,S=A}}}else if(B+=u-x,y>x){y-=x;do A[o++]=c[B++];while(--x);B=o-z,S=A}for(;y>2;)A[o++]=S[B++],A[o++]=S[B++],A[o++]=S[B++],y-=3;y&&(A[o++]=S[B++],y>1&&(A[o++]=S[B++]))}else{B=o-z;do A[o++]=A[B++],A[o++]=A[B++],A[o++]=A[B++],y-=3;while(y>2);y&&(A[o++]=A[B++],y>1&&(A[o++]=A[B++]))}break}}break}}while(s>r&&h>o);y=g>>3,r-=y,g-=y<<3,b&=(1<r?5+(s-r):5-(r-s),t.avail_out=h>o?257+(h-o):257-(o-h),n.hold=b,n.bits=g}},{}],12:[function(t,e,a){"use strict";function i(t){return(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)}function n(){this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new m.Buf16(320),this.work=new m.Buf16(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}function r(t){var e;return t&&t.state?(e=t.state,t.total_in=t.total_out=e.total=0,t.msg="",e.wrap&&(t.adler=1&e.wrap),e.mode=D,e.last=0,e.havedict=0,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new m.Buf32(ce),e.distcode=e.distdyn=new m.Buf32(be),e.sane=1,e.back=-1,A):C}function s(t){var e;return t&&t.state?(e=t.state,e.wsize=0,e.whave=0,e.wnext=0,r(t)):C}function o(t,e){var a,i;return t&&t.state?(i=t.state,0>e?(a=0,e=-e):(a=(e>>4)+1,48>e&&(e&=15)),e&&(8>e||e>15)?C:(null!==i.window&&i.wbits!==e&&(i.window=null),i.wrap=a,i.wbits=e,s(t))):C}function l(t,e){var a,i;return t?(i=new n,t.state=i,i.window=null,a=o(t,e),a!==A&&(t.state=null),a):C}function h(t){return l(t,me)}function d(t){if(we){var e;for(b=new m.Buf32(512),g=new m.Buf32(32),e=0;144>e;)t.lens[e++]=8;for(;256>e;)t.lens[e++]=9;for(;280>e;)t.lens[e++]=7;for(;288>e;)t.lens[e++]=8;for(k(y,t.lens,0,288,b,0,t.work,{bits:9}),e=0;32>e;)t.lens[e++]=5;k(z,t.lens,0,32,g,0,t.work,{bits:5}),we=!1}t.lencode=b,t.lenbits=9,t.distcode=g,t.distbits=5}function f(t,e,a,i){var n,r=t.state;return null===r.window&&(r.wsize=1<=r.wsize?(m.arraySet(r.window,e,a-r.wsize,r.wsize,0),r.wnext=0,r.whave=r.wsize):(n=r.wsize-r.wnext,n>i&&(n=i),m.arraySet(r.window,e,a-i,n,r.wnext),i-=n,i?(m.arraySet(r.window,e,a-i,i,0),r.wnext=i,r.whave=r.wsize):(r.wnext+=n,r.wnext===r.wsize&&(r.wnext=0),r.whaveu;){if(0===l)break t;l--,_+=n[s++]<>>8&255,a.check=p(a.check,Ee,2,0),_=0,u=0,a.mode=F;break}if(a.flags=0,a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&_)<<8)+(_>>8))%31){t.msg="incorrect header check",a.mode=fe;break}if((15&_)!==T){t.msg="unknown compression method",a.mode=fe;break}if(_>>>=4,u-=4,xe=(15&_)+8,0===a.wbits)a.wbits=xe;else if(xe>a.wbits){t.msg="invalid window size",a.mode=fe;break}a.dmax=1<u;){if(0===l)break t;l--,_+=n[s++]<>8&1),512&a.flags&&(Ee[0]=255&_,Ee[1]=_>>>8&255,a.check=p(a.check,Ee,2,0)),_=0,u=0,a.mode=L;case L:for(;32>u;){if(0===l)break t;l--,_+=n[s++]<>>8&255,Ee[2]=_>>>16&255,Ee[3]=_>>>24&255,a.check=p(a.check,Ee,4,0)),_=0,u=0,a.mode=U;case U:for(;16>u;){if(0===l)break t;l--,_+=n[s++]<>8),512&a.flags&&(Ee[0]=255&_,Ee[1]=_>>>8&255,a.check=p(a.check,Ee,2,0)),_=0,u=0,a.mode=H;case H:if(1024&a.flags){for(;16>u;){if(0===l)break t;l--,_+=n[s++]<>>8&255,a.check=p(a.check,Ee,2,0)),_=0,u=0}else a.head&&(a.head.extra=null);a.mode=j;case j:if(1024&a.flags&&(g=a.length,g>l&&(g=l),g&&(a.head&&(xe=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Array(a.head.extra_len)),m.arraySet(a.head.extra,n,s,g,xe)),512&a.flags&&(a.check=p(a.check,n,g,s)),l-=g,s+=g,a.length-=g),a.length))break t;a.length=0,a.mode=M;case M:if(2048&a.flags){if(0===l)break t;g=0;do xe=n[s+g++],a.head&&xe&&a.length<65536&&(a.head.name+=String.fromCharCode(xe));while(xe&&l>g);if(512&a.flags&&(a.check=p(a.check,n,g,s)),l-=g,s+=g,xe)break t}else a.head&&(a.head.name=null);a.length=0,a.mode=K;case K:if(4096&a.flags){if(0===l)break t;g=0;do xe=n[s+g++],a.head&&xe&&a.length<65536&&(a.head.comment+=String.fromCharCode(xe));while(xe&&l>g);if(512&a.flags&&(a.check=p(a.check,n,g,s)),l-=g,s+=g,xe)break t}else a.head&&(a.head.comment=null);a.mode=P;case P:if(512&a.flags){for(;16>u;){if(0===l)break t;l--,_+=n[s++]<>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=G;break;case q:for(;32>u;){if(0===l)break t;l--,_+=n[s++]<>>=7&u,u-=7&u,a.mode=le;break}for(;3>u;){if(0===l)break t;l--,_+=n[s++]<>>=1,u-=1,3&_){case 0:a.mode=W;break;case 1:if(d(a),a.mode=ee,e===E){_>>>=2,u-=2;break t}break;case 2:a.mode=V;break;case 3:t.msg="invalid block type",a.mode=fe}_>>>=2,u-=2;break;case W:for(_>>>=7&u,u-=7&u;32>u;){if(0===l)break t;l--,_+=n[s++]<>>16^65535)){t.msg="invalid stored block lengths",a.mode=fe;break}if(a.length=65535&_,_=0,u=0,a.mode=J,e===E)break t;case J:a.mode=Q;case Q:if(g=a.length){if(g>l&&(g=l),g>h&&(g=h),0===g)break t;m.arraySet(r,n,s,g,o),l-=g,s+=g,h-=g,o+=g,a.length-=g;break}a.mode=G;break;case V:for(;14>u;){if(0===l)break t;l--,_+=n[s++]<>>=5,u-=5,a.ndist=(31&_)+1,_>>>=5,u-=5,a.ncode=(15&_)+4,_>>>=4,u-=4,a.nlen>286||a.ndist>30){t.msg="too many length or distance symbols",a.mode=fe;break}a.have=0,a.mode=$;case $:for(;a.haveu;){if(0===l)break t;l--,_+=n[s++]<>>=3,u-=3}for(;a.have<19;)a.lens[Ae[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,ze={bits:a.lenbits},ye=k(x,a.lens,0,19,a.lencode,0,a.work,ze),a.lenbits=ze.bits,ye){t.msg="invalid code lengths set",a.mode=fe;break}a.have=0,a.mode=te;case te:for(;a.have>>24,me=Se>>>16&255,we=65535&Se,!(u>=ge);){if(0===l)break t;l--,_+=n[s++]<we)_>>>=ge,u-=ge,a.lens[a.have++]=we;else{if(16===we){for(Be=ge+2;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=ge,u-=ge,0===a.have){t.msg="invalid bit length repeat",a.mode=fe;break}xe=a.lens[a.have-1],g=3+(3&_),_>>>=2,u-=2}else if(17===we){for(Be=ge+3;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=ge,u-=ge,xe=0,g=3+(7&_),_>>>=3,u-=3}else{for(Be=ge+7;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=ge,u-=ge,xe=0,g=11+(127&_),_>>>=7,u-=7}if(a.have+g>a.nlen+a.ndist){t.msg="invalid bit length repeat",a.mode=fe;break}for(;g--;)a.lens[a.have++]=xe}}if(a.mode===fe)break;if(0===a.lens[256]){t.msg="invalid code -- missing end-of-block",a.mode=fe;break}if(a.lenbits=9,ze={bits:a.lenbits},ye=k(y,a.lens,0,a.nlen,a.lencode,0,a.work,ze),a.lenbits=ze.bits,ye){t.msg="invalid literal/lengths set",a.mode=fe;break}if(a.distbits=6,a.distcode=a.distdyn,ze={bits:a.distbits},ye=k(z,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,ze),a.distbits=ze.bits,ye){t.msg="invalid distances set",a.mode=fe;
-break}if(a.mode=ee,e===E)break t;case ee:a.mode=ae;case ae:if(l>=6&&h>=258){t.next_out=o,t.avail_out=h,t.next_in=s,t.avail_in=l,a.hold=_,a.bits=u,v(t,b),o=t.next_out,r=t.output,h=t.avail_out,s=t.next_in,n=t.input,l=t.avail_in,_=a.hold,u=a.bits,a.mode===G&&(a.back=-1);break}for(a.back=0;Se=a.lencode[_&(1<>>24,me=Se>>>16&255,we=65535&Se,!(u>=ge);){if(0===l)break t;l--,_+=n[s++]<>pe)],ge=Se>>>24,me=Se>>>16&255,we=65535&Se,!(u>=pe+ge);){if(0===l)break t;l--,_+=n[s++]<>>=pe,u-=pe,a.back+=pe}if(_>>>=ge,u-=ge,a.back+=ge,a.length=we,0===me){a.mode=oe;break}if(32&me){a.back=-1,a.mode=G;break}if(64&me){t.msg="invalid literal/length code",a.mode=fe;break}a.extra=15&me,a.mode=ie;case ie:if(a.extra){for(Be=a.extra;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=a.extra,u-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=ne;case ne:for(;Se=a.distcode[_&(1<>>24,me=Se>>>16&255,we=65535&Se,!(u>=ge);){if(0===l)break t;l--,_+=n[s++]<>pe)],ge=Se>>>24,me=Se>>>16&255,we=65535&Se,!(u>=pe+ge);){if(0===l)break t;l--,_+=n[s++]<>>=pe,u-=pe,a.back+=pe}if(_>>>=ge,u-=ge,a.back+=ge,64&me){t.msg="invalid distance code",a.mode=fe;break}a.offset=we,a.extra=15&me,a.mode=re;case re:if(a.extra){for(Be=a.extra;Be>u;){if(0===l)break t;l--,_+=n[s++]<>>=a.extra,u-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg="invalid distance too far back",a.mode=fe;break}a.mode=se;case se:if(0===h)break t;if(g=b-h,a.offset>g){if(g=a.offset-g,g>a.whave&&a.sane){t.msg="invalid distance too far back",a.mode=fe;break}g>a.wnext?(g-=a.wnext,ce=a.wsize-g):ce=a.wnext-g,g>a.length&&(g=a.length),be=a.window}else be=r,ce=o-a.offset,g=a.length;g>h&&(g=h),h-=g,a.length-=g;do r[o++]=be[ce++];while(--g);0===a.length&&(a.mode=ae);break;case oe:if(0===h)break t;r[o++]=a.length,h--,a.mode=ae;break;case le:if(a.wrap){for(;32>u;){if(0===l)break t;l--,_|=n[s++]<u;){if(0===l)break t;l--,_+=n[s++]<=Z;Z++)j[Z]=0;for(R=0;c>R;R++)j[e[u+R]]++;for(N=A,I=i;I>=1&&0===j[I];I--);if(N>I&&(N=I),0===I)return b[g++]=20971520,b[g++]=20971520,w.bits=1,0;for(C=1;I>C&&0===j[C];C++);for(C>N&&(N=C),D=1,Z=1;i>=Z;Z++)if(D<<=1,D-=j[Z],0>D)return-1;if(D>0&&(t===s||1!==I))return-1;for(M[1]=0,Z=1;i>Z;Z++)M[Z+1]=M[Z]+j[Z];for(R=0;c>R;R++)0!==e[u+R]&&(m[M[e[u+R]]++]=R);if(t===s?(U=K=m,z=19):t===o?(U=h,H-=257,K=d,P-=257,z=256):(U=f,K=_,z=-1),L=0,R=0,Z=C,y=g,O=N,T=0,k=-1,F=1<n||t===l&&F>r)return 1;for(var q=0;;){q++,B=Z-T,m[R]z?(S=K[P+m[R]],E=U[H+m[R]]):(S=96,E=0),p=1<>T)+v]=B<<24|S<<16|E|0;while(0!==v);for(p=1<>=1;if(0!==p?(L&=p-1,L+=p):L=0,R++,0===--j[Z]){if(Z===I)break;Z=e[u+m[R]]}if(Z>N&&(L&x)!==k){for(0===T&&(T=N),y+=C,O=Z-T,D=1<O+T&&(D-=j[O+T],!(0>=D));)O++,D<<=1;if(F+=1<n||t===l&&F>r)return 1;k=L&x,b[k]=N<<24|O<<16|y-g|0}}return 0!==L&&(b[y+L]=Z-T<<24|64<<16|0),w.bits=N,0}},{"../utils/common":4}],14:[function(t,e){"use strict";e.exports={2:"need dictionary",1:"stream end",0:"","-1":"file error","-2":"stream error","-3":"data error","-4":"insufficient memory","-5":"buffer error","-6":"incompatible version"}},{}],15:[function(t,e,a){"use strict";function i(t){for(var e=t.length;--e>=0;)t[e]=0}function n(t){return 256>t?se[t]:se[256+(t>>>7)]}function r(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255}function s(t,e,a){t.bi_valid>G-a?(t.bi_buf|=e<>G-t.bi_valid,t.bi_valid+=a-G):(t.bi_buf|=e<>>=1,a<<=1;while(--e>0);return a>>>1}function h(t){16===t.bi_valid?(r(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}function d(t,e){var a,i,n,r,s,o,l=e.dyn_tree,h=e.max_code,d=e.stat_desc.static_tree,f=e.stat_desc.has_stree,_=e.stat_desc.extra_bits,u=e.stat_desc.extra_base,c=e.stat_desc.max_length,b=0;for(r=0;Y>=r;r++)t.bl_count[r]=0;for(l[2*t.heap[t.heap_max]+1]=0,a=t.heap_max+1;q>a;a++)i=t.heap[a],r=l[2*l[2*i+1]+1]+1,r>c&&(r=c,b++),l[2*i+1]=r,i>h||(t.bl_count[r]++,s=0,i>=u&&(s=_[i-u]),o=l[2*i],t.opt_len+=o*(r+s),f&&(t.static_len+=o*(d[2*i+1]+s)));if(0!==b){do{for(r=c-1;0===t.bl_count[r];)r--;t.bl_count[r]--,t.bl_count[r+1]+=2,t.bl_count[c]--,b-=2}while(b>0);for(r=c;0!==r;r--)for(i=t.bl_count[r];0!==i;)n=t.heap[--a],n>h||(l[2*n+1]!==r&&(t.opt_len+=(r-l[2*n+1])*l[2*n],l[2*n+1]=r),i--)}}function f(t,e,a){var i,n,r=new Array(Y+1),s=0;for(i=1;Y>=i;i++)r[i]=s=s+a[i-1]<<1;for(n=0;e>=n;n++){var o=t[2*n+1];0!==o&&(t[2*n]=l(r[o]++,o))}}function _(){var t,e,a,i,n,r=new Array(Y+1);for(a=0,i=0;H-1>i;i++)for(le[i]=a,t=0;t<1<<$[i];t++)oe[a++]=i;for(oe[a-1]=i,n=0,i=0;16>i;i++)for(he[i]=n,t=0;t<1<>=7;K>i;i++)for(he[i]=n<<7,t=0;t<1<=e;e++)r[e]=0;for(t=0;143>=t;)ne[2*t+1]=8,t++,r[8]++;for(;255>=t;)ne[2*t+1]=9,t++,r[9]++;for(;279>=t;)ne[2*t+1]=7,t++,r[7]++;for(;287>=t;)ne[2*t+1]=8,t++,r[8]++;for(f(ne,M+1,r),t=0;K>t;t++)re[2*t+1]=5,re[2*t]=l(t,5);de=new ue(ne,$,j+1,M,Y),fe=new ue(re,te,0,K,Y),_e=new ue(new Array(0),ee,0,P,X)}function u(t){var e;for(e=0;M>e;e++)t.dyn_ltree[2*e]=0;for(e=0;K>e;e++)t.dyn_dtree[2*e]=0;for(e=0;P>e;e++)t.bl_tree[2*e]=0;t.dyn_ltree[2*W]=1,t.opt_len=t.static_len=0,t.last_lit=t.matches=0}function c(t){t.bi_valid>8?r(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0}function b(t,e,a,i){c(t),i&&(r(t,a),r(t,~a)),R.arraySet(t.pending_buf,t.window,e,a,t.pending),t.pending+=a}function g(t,e,a,i){var n=2*e,r=2*a;return t[n]a;a++)0!==r[2*a]?(t.heap[++t.heap_len]=h=a,t.depth[a]=0):r[2*a+1]=0;for(;t.heap_len<2;)n=t.heap[++t.heap_len]=2>h?++h:0,r[2*n]=1,t.depth[n]=0,t.opt_len--,o&&(t.static_len-=s[2*n+1]);for(e.max_code=h,a=t.heap_len>>1;a>=1;a--)m(t,r,a);n=l;do a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],m(t,r,1),i=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=i,r[2*n]=r[2*a]+r[2*i],t.depth[n]=(t.depth[a]>=t.depth[i]?t.depth[a]:t.depth[i])+1,r[2*a+1]=r[2*i+1]=n,t.heap[1]=n++,m(t,r,1);while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],d(t,e),f(r,h,t.bl_count)}function v(t,e,a){var i,n,r=-1,s=e[1],o=0,l=7,h=4;for(0===s&&(l=138,h=3),e[2*(a+1)+1]=65535,i=0;a>=i;i++)n=s,s=e[2*(i+1)+1],++oo?t.bl_tree[2*n]+=o:0!==n?(n!==r&&t.bl_tree[2*n]++,t.bl_tree[2*J]++):10>=o?t.bl_tree[2*Q]++:t.bl_tree[2*V]++,o=0,r=n,0===s?(l=138,h=3):n===s?(l=6,h=3):(l=7,h=4))}function k(t,e,a){var i,n,r=-1,l=e[1],h=0,d=7,f=4;for(0===l&&(d=138,f=3),i=0;a>=i;i++)if(n=l,l=e[2*(i+1)+1],!(++hh){do o(t,n,t.bl_tree);while(0!==--h)}else 0!==n?(n!==r&&(o(t,n,t.bl_tree),h--),o(t,J,t.bl_tree),s(t,h-3,2)):10>=h?(o(t,Q,t.bl_tree),s(t,h-3,3)):(o(t,V,t.bl_tree),s(t,h-11,7));h=0,r=n,0===l?(d=138,f=3):n===l?(d=6,f=3):(d=7,f=4)}}function x(t){var e;for(v(t,t.dyn_ltree,t.l_desc.max_code),v(t,t.dyn_dtree,t.d_desc.max_code),p(t,t.bl_desc),e=P-1;e>=3&&0===t.bl_tree[2*ae[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}function y(t,e,a,i){var n;for(s(t,e-257,5),s(t,a-1,5),s(t,i-4,4),n=0;i>n;n++)s(t,t.bl_tree[2*ae[n]+1],3);k(t,t.dyn_ltree,e-1),k(t,t.dyn_dtree,a-1)}function z(t){var e,a=4093624447;for(e=0;31>=e;e++,a>>>=1)if(1&a&&0!==t.dyn_ltree[2*e])return I;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return N;for(e=32;j>e;e++)if(0!==t.dyn_ltree[2*e])return N;return I}function B(t){be||(_(),be=!0),t.l_desc=new ce(t.dyn_ltree,de),t.d_desc=new ce(t.dyn_dtree,fe),t.bl_desc=new ce(t.bl_tree,_e),t.bi_buf=0,t.bi_valid=0,u(t)}function S(t,e,a,i){s(t,(T<<1)+(i?1:0),3),b(t,e,a,!0)}function E(t){s(t,D<<1,3),o(t,W,ne),h(t)}function A(t,e,a,i){var n,r,o=0;t.level>0?(t.strm.data_type===O&&(t.strm.data_type=z(t)),p(t,t.l_desc),p(t,t.d_desc),o=x(t),n=t.opt_len+3+7>>>3,r=t.static_len+3+7>>>3,n>=r&&(n=r)):n=r=a+5,n>=a+4&&-1!==e?S(t,e,a,i):t.strategy===C||r===n?(s(t,(D<<1)+(i?1:0),3),w(t,ne,re)):(s(t,(F<<1)+(i?1:0),3),y(t,t.l_desc.max_code+1,t.d_desc.max_code+1,o+1),w(t,t.dyn_ltree,t.dyn_dtree)),u(t),i&&c(t)}function Z(t,e,a){return t.pending_buf[t.d_buf+2*t.last_lit]=e>>>8&255,t.pending_buf[t.d_buf+2*t.last_lit+1]=255&e,t.pending_buf[t.l_buf+t.last_lit]=255&a,t.last_lit++,0===e?t.dyn_ltree[2*a]++:(t.matches++,e--,t.dyn_ltree[2*(oe[a]+j+1)]++,t.dyn_dtree[2*n(e)]++),t.last_lit===t.lit_bufsize-1}var R=t("../utils/common"),C=4,I=0,N=1,O=2,T=0,D=1,F=2,L=3,U=258,H=29,j=256,M=j+1+H,K=30,P=19,q=2*M+1,Y=15,G=16,X=7,W=256,J=16,Q=17,V=18,$=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0],te=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],ee=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7],ae=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],ie=512,ne=new Array(2*(M+2));i(ne);var re=new Array(2*K);i(re);var se=new Array(ie);i(se);var oe=new Array(U-L+1);i(oe);var le=new Array(H);i(le);var he=new Array(K);i(he);var de,fe,_e,ue=function(t,e,a,i,n){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=i,this.max_length=n,this.has_stree=t&&t.length},ce=function(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e},be=!1;a._tr_init=B,a._tr_stored_block=S,a._tr_flush_block=A,a._tr_tally=Z,a._tr_align=E},{"../utils/common":4}],16:[function(t,e){"use strict";function a(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg="",this.state=null,this.data_type=2,this.adler=0}e.exports=a},{}]},{},[1])(1)});
\ No newline at end of file
diff --git a/newcanvas/pathseg.min.js b/newcanvas/pathseg.min.js
deleted file mode 100644
index ad59914..0000000
--- a/newcanvas/pathseg.min.js
+++ /dev/null
@@ -1,173 +0,0 @@
-// SVGPathSeg API polyfill
-// https://github.com/progers/pathseg
-//
-// This is a drop-in replacement for the SVGPathSeg and SVGPathSegList APIs that were removed from
-// SVG2 (https://lists.w3.org/Archives/Public/www-svg/2015Jun/0044.html), including the latest spec
-// changes which were implemented in Firefox 43 and Chrome 46.
-(function(){"use strict";if(!("SVGPathSeg"in window)){window.SVGPathSeg=function(type,typeAsLetter,owningPathSegList){this.pathSegType=type;this.pathSegTypeAsLetter=typeAsLetter;this._owningPathSegList=owningPathSegList;}
-SVGPathSeg.PATHSEG_UNKNOWN=0;SVGPathSeg.PATHSEG_CLOSEPATH=1;SVGPathSeg.PATHSEG_MOVETO_ABS=2;SVGPathSeg.PATHSEG_MOVETO_REL=3;SVGPathSeg.PATHSEG_LINETO_ABS=4;SVGPathSeg.PATHSEG_LINETO_REL=5;SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS=6;SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL=7;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS=8;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL=9;SVGPathSeg.PATHSEG_ARC_ABS=10;SVGPathSeg.PATHSEG_ARC_REL=11;SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS=12;SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL=13;SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS=14;SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL=15;SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS=16;SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL=17;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS=18;SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL=19;SVGPathSeg.prototype._segmentChanged=function(){if(this._owningPathSegList)
-this._owningPathSegList.segmentChanged(this);}
-window.SVGPathSegClosePath=function(owningPathSegList){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CLOSEPATH,"z",owningPathSegList);}
-SVGPathSegClosePath.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegClosePath.prototype.toString=function(){return"[object SVGPathSegClosePath]";}
-SVGPathSegClosePath.prototype._asPathString=function(){return this.pathSegTypeAsLetter;}
-SVGPathSegClosePath.prototype.clone=function(){return new SVGPathSegClosePath(undefined);}
-window.SVGPathSegMovetoAbs=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_MOVETO_ABS,"M",owningPathSegList);this._x=x;this._y=y;}
-SVGPathSegMovetoAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegMovetoAbs.prototype.toString=function(){return"[object SVGPathSegMovetoAbs]";}
-SVGPathSegMovetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;}
-SVGPathSegMovetoAbs.prototype.clone=function(){return new SVGPathSegMovetoAbs(undefined,this._x,this._y);}
-Object.defineProperty(SVGPathSegMovetoAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegMovetoAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegMovetoRel=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_MOVETO_REL,"m",owningPathSegList);this._x=x;this._y=y;}
-SVGPathSegMovetoRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegMovetoRel.prototype.toString=function(){return"[object SVGPathSegMovetoRel]";}
-SVGPathSegMovetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;}
-SVGPathSegMovetoRel.prototype.clone=function(){return new SVGPathSegMovetoRel(undefined,this._x,this._y);}
-Object.defineProperty(SVGPathSegMovetoRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegMovetoRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoAbs=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_ABS,"L",owningPathSegList);this._x=x;this._y=y;}
-SVGPathSegLinetoAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoAbs.prototype.toString=function(){return"[object SVGPathSegLinetoAbs]";}
-SVGPathSegLinetoAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;}
-SVGPathSegLinetoAbs.prototype.clone=function(){return new SVGPathSegLinetoAbs(undefined,this._x,this._y);}
-Object.defineProperty(SVGPathSegLinetoAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegLinetoAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoRel=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_REL,"l",owningPathSegList);this._x=x;this._y=y;}
-SVGPathSegLinetoRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoRel.prototype.toString=function(){return"[object SVGPathSegLinetoRel]";}
-SVGPathSegLinetoRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;}
-SVGPathSegLinetoRel.prototype.clone=function(){return new SVGPathSegLinetoRel(undefined,this._x,this._y);}
-Object.defineProperty(SVGPathSegLinetoRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegLinetoRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicAbs=function(owningPathSegList,x,y,x1,y1,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS,"C",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;this._x2=x2;this._y2=y2;}
-SVGPathSegCurvetoCubicAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicAbs]";}
-SVGPathSegCurvetoCubicAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoCubicAbs.prototype.clone=function(){return new SVGPathSegCurvetoCubicAbs(undefined,this._x,this._y,this._x1,this._y1,this._x2,this._y2);}
-Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicAbs.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicRel=function(owningPathSegList,x,y,x1,y1,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL,"c",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;this._x2=x2;this._y2=y2;}
-SVGPathSegCurvetoCubicRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicRel]";}
-SVGPathSegCurvetoCubicRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoCubicRel.prototype.clone=function(){return new SVGPathSegCurvetoCubicRel(undefined,this._x,this._y,this._x1,this._y1,this._x2,this._y2);}
-Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicRel.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticAbs=function(owningPathSegList,x,y,x1,y1){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS,"Q",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;}
-SVGPathSegCurvetoQuadraticAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticAbs]";}
-SVGPathSegCurvetoQuadraticAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoQuadraticAbs.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticAbs(undefined,this._x,this._y,this._x1,this._y1);}
-Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticAbs.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticRel=function(owningPathSegList,x,y,x1,y1){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL,"q",owningPathSegList);this._x=x;this._y=y;this._x1=x1;this._y1=y1;}
-SVGPathSegCurvetoQuadraticRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticRel]";}
-SVGPathSegCurvetoQuadraticRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x1+" "+this._y1+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoQuadraticRel.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticRel(undefined,this._x,this._y,this._x1,this._y1);}
-Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"x1",{get:function(){return this._x1;},set:function(x1){this._x1=x1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticRel.prototype,"y1",{get:function(){return this._y1;},set:function(y1){this._y1=y1;this._segmentChanged();},enumerable:true});window.SVGPathSegArcAbs=function(owningPathSegList,x,y,r1,r2,angle,largeArcFlag,sweepFlag){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_ARC_ABS,"A",owningPathSegList);this._x=x;this._y=y;this._r1=r1;this._r2=r2;this._angle=angle;this._largeArcFlag=largeArcFlag;this._sweepFlag=sweepFlag;}
-SVGPathSegArcAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegArcAbs.prototype.toString=function(){return"[object SVGPathSegArcAbs]";}
-SVGPathSegArcAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y;}
-SVGPathSegArcAbs.prototype.clone=function(){return new SVGPathSegArcAbs(undefined,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag);}
-Object.defineProperty(SVGPathSegArcAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"r1",{get:function(){return this._r1;},set:function(r1){this._r1=r1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"r2",{get:function(){return this._r2;},set:function(r2){this._r2=r2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"angle",{get:function(){return this._angle;},set:function(angle){this._angle=angle;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag;},set:function(largeArcFlag){this._largeArcFlag=largeArcFlag;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcAbs.prototype,"sweepFlag",{get:function(){return this._sweepFlag;},set:function(sweepFlag){this._sweepFlag=sweepFlag;this._segmentChanged();},enumerable:true});window.SVGPathSegArcRel=function(owningPathSegList,x,y,r1,r2,angle,largeArcFlag,sweepFlag){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_ARC_REL,"a",owningPathSegList);this._x=x;this._y=y;this._r1=r1;this._r2=r2;this._angle=angle;this._largeArcFlag=largeArcFlag;this._sweepFlag=sweepFlag;}
-SVGPathSegArcRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegArcRel.prototype.toString=function(){return"[object SVGPathSegArcRel]";}
-SVGPathSegArcRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._r1+" "+this._r2+" "+this._angle+" "+(this._largeArcFlag?"1":"0")+" "+(this._sweepFlag?"1":"0")+" "+this._x+" "+this._y;}
-SVGPathSegArcRel.prototype.clone=function(){return new SVGPathSegArcRel(undefined,this._x,this._y,this._r1,this._r2,this._angle,this._largeArcFlag,this._sweepFlag);}
-Object.defineProperty(SVGPathSegArcRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"r1",{get:function(){return this._r1;},set:function(r1){this._r1=r1;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"r2",{get:function(){return this._r2;},set:function(r2){this._r2=r2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"angle",{get:function(){return this._angle;},set:function(angle){this._angle=angle;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"largeArcFlag",{get:function(){return this._largeArcFlag;},set:function(largeArcFlag){this._largeArcFlag=largeArcFlag;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegArcRel.prototype,"sweepFlag",{get:function(){return this._sweepFlag;},set:function(sweepFlag){this._sweepFlag=sweepFlag;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoHorizontalAbs=function(owningPathSegList,x){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS,"H",owningPathSegList);this._x=x;}
-SVGPathSegLinetoHorizontalAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoHorizontalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalAbs]";}
-SVGPathSegLinetoHorizontalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x;}
-SVGPathSegLinetoHorizontalAbs.prototype.clone=function(){return new SVGPathSegLinetoHorizontalAbs(undefined,this._x);}
-Object.defineProperty(SVGPathSegLinetoHorizontalAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoHorizontalRel=function(owningPathSegList,x){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL,"h",owningPathSegList);this._x=x;}
-SVGPathSegLinetoHorizontalRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoHorizontalRel.prototype.toString=function(){return"[object SVGPathSegLinetoHorizontalRel]";}
-SVGPathSegLinetoHorizontalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x;}
-SVGPathSegLinetoHorizontalRel.prototype.clone=function(){return new SVGPathSegLinetoHorizontalRel(undefined,this._x);}
-Object.defineProperty(SVGPathSegLinetoHorizontalRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoVerticalAbs=function(owningPathSegList,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS,"V",owningPathSegList);this._y=y;}
-SVGPathSegLinetoVerticalAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoVerticalAbs.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalAbs]";}
-SVGPathSegLinetoVerticalAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y;}
-SVGPathSegLinetoVerticalAbs.prototype.clone=function(){return new SVGPathSegLinetoVerticalAbs(undefined,this._y);}
-Object.defineProperty(SVGPathSegLinetoVerticalAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegLinetoVerticalRel=function(owningPathSegList,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL,"v",owningPathSegList);this._y=y;}
-SVGPathSegLinetoVerticalRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegLinetoVerticalRel.prototype.toString=function(){return"[object SVGPathSegLinetoVerticalRel]";}
-SVGPathSegLinetoVerticalRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._y;}
-SVGPathSegLinetoVerticalRel.prototype.clone=function(){return new SVGPathSegLinetoVerticalRel(undefined,this._y);}
-Object.defineProperty(SVGPathSegLinetoVerticalRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicSmoothAbs=function(owningPathSegList,x,y,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS,"S",owningPathSegList);this._x=x;this._y=y;this._x2=x2;this._y2=y2;}
-SVGPathSegCurvetoCubicSmoothAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothAbs]";}
-SVGPathSegCurvetoCubicSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoCubicSmoothAbs.prototype.clone=function(){return new SVGPathSegCurvetoCubicSmoothAbs(undefined,this._x,this._y,this._x2,this._y2);}
-Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothAbs.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoCubicSmoothRel=function(owningPathSegList,x,y,x2,y2){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL,"s",owningPathSegList);this._x=x;this._y=y;this._x2=x2;this._y2=y2;}
-SVGPathSegCurvetoCubicSmoothRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoCubicSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoCubicSmoothRel]";}
-SVGPathSegCurvetoCubicSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x2+" "+this._y2+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoCubicSmoothRel.prototype.clone=function(){return new SVGPathSegCurvetoCubicSmoothRel(undefined,this._x,this._y,this._x2,this._y2);}
-Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"x2",{get:function(){return this._x2;},set:function(x2){this._x2=x2;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoCubicSmoothRel.prototype,"y2",{get:function(){return this._y2;},set:function(y2){this._y2=y2;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticSmoothAbs=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS,"T",owningPathSegList);this._x=x;this._y=y;}
-SVGPathSegCurvetoQuadraticSmoothAbs.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticSmoothAbs.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothAbs]";}
-SVGPathSegCurvetoQuadraticSmoothAbs.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoQuadraticSmoothAbs.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined,this._x,this._y);}
-Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothAbs.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});window.SVGPathSegCurvetoQuadraticSmoothRel=function(owningPathSegList,x,y){SVGPathSeg.call(this,SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL,"t",owningPathSegList);this._x=x;this._y=y;}
-SVGPathSegCurvetoQuadraticSmoothRel.prototype=Object.create(SVGPathSeg.prototype);SVGPathSegCurvetoQuadraticSmoothRel.prototype.toString=function(){return"[object SVGPathSegCurvetoQuadraticSmoothRel]";}
-SVGPathSegCurvetoQuadraticSmoothRel.prototype._asPathString=function(){return this.pathSegTypeAsLetter+" "+this._x+" "+this._y;}
-SVGPathSegCurvetoQuadraticSmoothRel.prototype.clone=function(){return new SVGPathSegCurvetoQuadraticSmoothRel(undefined,this._x,this._y);}
-Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype,"x",{get:function(){return this._x;},set:function(x){this._x=x;this._segmentChanged();},enumerable:true});Object.defineProperty(SVGPathSegCurvetoQuadraticSmoothRel.prototype,"y",{get:function(){return this._y;},set:function(y){this._y=y;this._segmentChanged();},enumerable:true});SVGPathElement.prototype.createSVGPathSegClosePath=function(){return new SVGPathSegClosePath(undefined);}
-SVGPathElement.prototype.createSVGPathSegMovetoAbs=function(x,y){return new SVGPathSegMovetoAbs(undefined,x,y);}
-SVGPathElement.prototype.createSVGPathSegMovetoRel=function(x,y){return new SVGPathSegMovetoRel(undefined,x,y);}
-SVGPathElement.prototype.createSVGPathSegLinetoAbs=function(x,y){return new SVGPathSegLinetoAbs(undefined,x,y);}
-SVGPathElement.prototype.createSVGPathSegLinetoRel=function(x,y){return new SVGPathSegLinetoRel(undefined,x,y);}
-SVGPathElement.prototype.createSVGPathSegCurvetoCubicAbs=function(x,y,x1,y1,x2,y2){return new SVGPathSegCurvetoCubicAbs(undefined,x,y,x1,y1,x2,y2);}
-SVGPathElement.prototype.createSVGPathSegCurvetoCubicRel=function(x,y,x1,y1,x2,y2){return new SVGPathSegCurvetoCubicRel(undefined,x,y,x1,y1,x2,y2);}
-SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticAbs=function(x,y,x1,y1){return new SVGPathSegCurvetoQuadraticAbs(undefined,x,y,x1,y1);}
-SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticRel=function(x,y,x1,y1){return new SVGPathSegCurvetoQuadraticRel(undefined,x,y,x1,y1);}
-SVGPathElement.prototype.createSVGPathSegArcAbs=function(x,y,r1,r2,angle,largeArcFlag,sweepFlag){return new SVGPathSegArcAbs(undefined,x,y,r1,r2,angle,largeArcFlag,sweepFlag);}
-SVGPathElement.prototype.createSVGPathSegArcRel=function(x,y,r1,r2,angle,largeArcFlag,sweepFlag){return new SVGPathSegArcRel(undefined,x,y,r1,r2,angle,largeArcFlag,sweepFlag);}
-SVGPathElement.prototype.createSVGPathSegLinetoHorizontalAbs=function(x){return new SVGPathSegLinetoHorizontalAbs(undefined,x);}
-SVGPathElement.prototype.createSVGPathSegLinetoHorizontalRel=function(x){return new SVGPathSegLinetoHorizontalRel(undefined,x);}
-SVGPathElement.prototype.createSVGPathSegLinetoVerticalAbs=function(y){return new SVGPathSegLinetoVerticalAbs(undefined,y);}
-SVGPathElement.prototype.createSVGPathSegLinetoVerticalRel=function(y){return new SVGPathSegLinetoVerticalRel(undefined,y);}
-SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothAbs=function(x,y,x2,y2){return new SVGPathSegCurvetoCubicSmoothAbs(undefined,x,y,x2,y2);}
-SVGPathElement.prototype.createSVGPathSegCurvetoCubicSmoothRel=function(x,y,x2,y2){return new SVGPathSegCurvetoCubicSmoothRel(undefined,x,y,x2,y2);}
-SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothAbs=function(x,y){return new SVGPathSegCurvetoQuadraticSmoothAbs(undefined,x,y);}
-SVGPathElement.prototype.createSVGPathSegCurvetoQuadraticSmoothRel=function(x,y){return new SVGPathSegCurvetoQuadraticSmoothRel(undefined,x,y);}}
-if(!("SVGPathSegList"in window)){window.SVGPathSegList=function(pathElement){this._pathElement=pathElement;this._list=this._parsePath(this._pathElement.getAttribute("d"));this._mutationObserverConfig={"attributes":true,"attributeFilter":["d"]};this._pathElementMutationObserver=new MutationObserver(this._updateListFromPathMutations.bind(this));this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig);}
-Object.defineProperty(SVGPathSegList.prototype,"numberOfItems",{get:function(){this._checkPathSynchronizedToList();return this._list.length;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"pathSegList",{get:function(){if(!this._pathSegList)
-this._pathSegList=new SVGPathSegList(this);return this._pathSegList;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"normalizedPathSegList",{get:function(){return this.pathSegList;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"animatedPathSegList",{get:function(){return this.pathSegList;},enumerable:true});Object.defineProperty(SVGPathElement.prototype,"animatedNormalizedPathSegList",{get:function(){return this.pathSegList;},enumerable:true});SVGPathSegList.prototype._checkPathSynchronizedToList=function(){this._updateListFromPathMutations(this._pathElementMutationObserver.takeRecords());}
-SVGPathSegList.prototype._updateListFromPathMutations=function(mutationRecords){if(!this._pathElement)
-return;var hasPathMutations=false;mutationRecords.forEach(function(record){if(record.attributeName=="d")
-hasPathMutations=true;});if(hasPathMutations)
-this._list=this._parsePath(this._pathElement.getAttribute("d"));}
-SVGPathSegList.prototype._writeListToPath=function(){this._pathElementMutationObserver.disconnect();this._pathElement.setAttribute("d",SVGPathSegList._pathSegArrayAsString(this._list));this._pathElementMutationObserver.observe(this._pathElement,this._mutationObserverConfig);}
-SVGPathSegList.prototype.segmentChanged=function(pathSeg){this._writeListToPath();}
-SVGPathSegList.prototype.clear=function(){this._checkPathSynchronizedToList();this._list.forEach(function(pathSeg){pathSeg._owningPathSegList=null;});this._list=[];this._writeListToPath();}
-SVGPathSegList.prototype.initialize=function(newItem){this._checkPathSynchronizedToList();this._list=[newItem];newItem._owningPathSegList=this;this._writeListToPath();return newItem;}
-SVGPathSegList.prototype._checkValidIndex=function(index){if(isNaN(index)||index<0||index>=this.numberOfItems)
-throw"INDEX_SIZE_ERR";}
-SVGPathSegList.prototype.getItem=function(index){this._checkPathSynchronizedToList();this._checkValidIndex(index);return this._list[index];}
-SVGPathSegList.prototype.insertItemBefore=function(newItem,index){this._checkPathSynchronizedToList();if(index>this.numberOfItems)
-index=this.numberOfItems;if(newItem._owningPathSegList){newItem=newItem.clone();}
-this._list.splice(index,0,newItem);newItem._owningPathSegList=this;this._writeListToPath();return newItem;}
-SVGPathSegList.prototype.replaceItem=function(newItem,index){this._checkPathSynchronizedToList();if(newItem._owningPathSegList){newItem=newItem.clone();}
-this._checkValidIndex(index);this._list[index]=newItem;newItem._owningPathSegList=this;this._writeListToPath();return newItem;}
-SVGPathSegList.prototype.removeItem=function(index){this._checkPathSynchronizedToList();this._checkValidIndex(index);var item=this._list[index];this._list.splice(index,1);this._writeListToPath();return item;}
-SVGPathSegList.prototype.appendItem=function(newItem){this._checkPathSynchronizedToList();if(newItem._owningPathSegList){newItem=newItem.clone();}
-this._list.push(newItem);newItem._owningPathSegList=this;this._writeListToPath();return newItem;}
-SVGPathSegList._pathSegArrayAsString=function(pathSegArray){var string="";var first=true;pathSegArray.forEach(function(pathSeg){if(first){first=false;string+=pathSeg._asPathString();}else{string+=" "+pathSeg._asPathString();}});return string;}
-SVGPathSegList.prototype._parsePath=function(string){if(!string||string.length==0)
-return[];var owningPathSegList=this;var Builder=function(){this.pathSegList=[];}
-Builder.prototype.appendSegment=function(pathSeg){this.pathSegList.push(pathSeg);}
-var Source=function(string){this._string=string;this._currentIndex=0;this._endIndex=this._string.length;this._previousCommand=SVGPathSeg.PATHSEG_UNKNOWN;this._skipOptionalSpaces();}
-Source.prototype._isCurrentSpace=function(){var character=this._string[this._currentIndex];return character<=" "&&(character==" "||character=="\n"||character=="\t"||character=="\r"||character=="\f");}
-Source.prototype._skipOptionalSpaces=function(){while(this._currentIndex="0"&&lookahead<="9"))&&previousCommand!=SVGPathSeg.PATHSEG_CLOSEPATH){if(previousCommand==SVGPathSeg.PATHSEG_MOVETO_ABS)
-return SVGPathSeg.PATHSEG_LINETO_ABS;if(previousCommand==SVGPathSeg.PATHSEG_MOVETO_REL)
-return SVGPathSeg.PATHSEG_LINETO_REL;return previousCommand;}
-return SVGPathSeg.PATHSEG_UNKNOWN;}
-Source.prototype.initialCommandIsMoveTo=function(){if(!this.hasMoreData())
-return true;var command=this.peekSegmentType();return command==SVGPathSeg.PATHSEG_MOVETO_ABS||command==SVGPathSeg.PATHSEG_MOVETO_REL;}
-Source.prototype._parseNumber=function(){var exponent=0;var integer=0;var frac=1;var decimal=0;var sign=1;var expsign=1;var startIndex=this._currentIndex;this._skipOptionalSpaces();if(this._currentIndex"9")&&this._string.charAt(this._currentIndex)!="."))
-return undefined;var startIntPartIndex=this._currentIndex;while(this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9")
-this._currentIndex++;if(this._currentIndex!=startIntPartIndex){var scanIntPartIndex=this._currentIndex-1;var multiplier=1;while(scanIntPartIndex>=startIntPartIndex){integer+=multiplier*(this._string.charAt(scanIntPartIndex--)-"0");multiplier*=10;}}
-if(this._currentIndex=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")
-return undefined;while(this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9")
-decimal+=(this._string.charAt(this._currentIndex++)-"0")*(frac*=0.1);}
-if(this._currentIndex!=startIndex&&this._currentIndex+1=this._endIndex||this._string.charAt(this._currentIndex)<"0"||this._string.charAt(this._currentIndex)>"9")
-return undefined;while(this._currentIndex="0"&&this._string.charAt(this._currentIndex)<="9"){exponent*=10;exponent+=(this._string.charAt(this._currentIndex)-"0");this._currentIndex++;}}
-var number=integer+decimal;number*=sign;if(exponent)
-number*=Math.pow(10,expsign*exponent);if(startIndex==this._currentIndex)
-return undefined;this._skipOptionalSpacesOrDelimiter();return number;}
-Source.prototype._parseArcFlag=function(){if(this._currentIndex>=this._endIndex)
-return undefined;var flag=false;var flagChar=this._string.charAt(this._currentIndex++);if(flagChar=="0")
-flag=false;else if(flagChar=="1")
-flag=true;else
-return undefined;this._skipOptionalSpacesOrDelimiter();return flag;}
-Source.prototype.parseSegment=function(){var lookahead=this._string[this._currentIndex];var command=this._pathSegTypeFromChar(lookahead);if(command==SVGPathSeg.PATHSEG_UNKNOWN){if(this._previousCommand==SVGPathSeg.PATHSEG_UNKNOWN)
-return null;command=this._nextCommandHelper(lookahead,this._previousCommand);if(command==SVGPathSeg.PATHSEG_UNKNOWN)
-return null;}else{this._currentIndex++;}
-this._previousCommand=command;switch(command){case SVGPathSeg.PATHSEG_MOVETO_REL:return new SVGPathSegMovetoRel(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_MOVETO_ABS:return new SVGPathSegMovetoAbs(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_REL:return new SVGPathSegLinetoRel(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_ABS:return new SVGPathSegLinetoAbs(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_REL:return new SVGPathSegLinetoHorizontalRel(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_HORIZONTAL_ABS:return new SVGPathSegLinetoHorizontalAbs(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_VERTICAL_REL:return new SVGPathSegLinetoVerticalRel(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_LINETO_VERTICAL_ABS:return new SVGPathSegLinetoVerticalAbs(owningPathSegList,this._parseNumber());case SVGPathSeg.PATHSEG_CLOSEPATH:this._skipOptionalSpaces();return new SVGPathSegClosePath(owningPathSegList);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_REL:var points={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicRel(owningPathSegList,points.x,points.y,points.x1,points.y1,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:var points={x1:this._parseNumber(),y1:this._parseNumber(),x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicAbs(owningPathSegList,points.x,points.y,points.x1,points.y1,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:var points={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicSmoothRel(owningPathSegList,points.x,points.y,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:var points={x2:this._parseNumber(),y2:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoCubicSmoothAbs(owningPathSegList,points.x,points.y,points.x2,points.y2);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_REL:var points={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoQuadraticRel(owningPathSegList,points.x,points.y,points.x1,points.y1);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_ABS:var points={x1:this._parseNumber(),y1:this._parseNumber(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegCurvetoQuadraticAbs(owningPathSegList,points.x,points.y,points.x1,points.y1);case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:return new SVGPathSegCurvetoQuadraticSmoothRel(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:return new SVGPathSegCurvetoQuadraticSmoothAbs(owningPathSegList,this._parseNumber(),this._parseNumber());case SVGPathSeg.PATHSEG_ARC_REL:var points={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegArcRel(owningPathSegList,points.x,points.y,points.x1,points.y1,points.arcAngle,points.arcLarge,points.arcSweep);case SVGPathSeg.PATHSEG_ARC_ABS:var points={x1:this._parseNumber(),y1:this._parseNumber(),arcAngle:this._parseNumber(),arcLarge:this._parseArcFlag(),arcSweep:this._parseArcFlag(),x:this._parseNumber(),y:this._parseNumber()};return new SVGPathSegArcAbs(owningPathSegList,points.x,points.y,points.x1,points.y1,points.arcAngle,points.arcLarge,points.arcSweep);default:throw"Unknown path seg type."}}
-var builder=new Builder();var source=new Source(string);if(!source.initialCommandIsMoveTo())
-return[];while(source.hasMoreData()){var pathSeg=source.parseSegment();if(!pathSeg)
-return[];builder.appendSegment(pathSeg);}
-return builder.pathSegList;}}}());
\ No newline at end of file
diff --git a/newcanvas/whammy.min.js b/newcanvas/whammy.min.js
deleted file mode 100644
index ce72bff..0000000
--- a/newcanvas/whammy.min.js
+++ /dev/null
@@ -1,39 +0,0 @@
-
-window.Whammy=(function(){function toWebM(frames,outputAsArray){var info=checkFrames(frames);var CLUSTER_MAX_DURATION=30000;var EBML=[{"id":0x1a45dfa3,"data":[{"data":1,"id":0x4286},{"data":1,"id":0x42f7},{"data":4,"id":0x42f2},{"data":8,"id":0x42f3},{"data":"webm","id":0x4282},{"data":2,"id":0x4287},{"data":2,"id":0x4285}]},{"id":0x18538067,"data":[{"id":0x1549a966,"data":[{"data":1e6,"id":0x2ad7b1},{"data":"whammy","id":0x4d80},{"data":"whammy","id":0x5741},{"data":doubleToString(info.duration),"id":0x4489}]},{"id":0x1654ae6b,"data":[{"id":0xae,"data":[{"data":1,"id":0xd7},{"data":1,"id":0x63c5},{"data":0,"id":0x9c},{"data":"und","id":0x22b59c},{"data":"V_VP8","id":0x86},{"data":"VP8","id":0x258688},{"data":1,"id":0x83},{"id":0xe0,"data":[{"data":info.width,"id":0xb0},{"data":info.height,"id":0xba}]}]}]},]}];var frameNumber=0;var clusterTimecode=0;while(frameNumber0x7fff)throw"Frame "+(i+1)+" has a weird duration (must be between 0 and 32767)";duration+=frames[i].duration;}
-return{duration:duration,width:width,height:height};}
-function numToBuffer(num){var parts=[];while(num>0){parts.push(num&0xff)
-num=num>>8}
-return new Uint8Array(parts.reverse());}
-function strToBuffer(str){var arr=new Uint8Array(str.length);for(var i=0;i127){throw"TrackNumber > 127 not supported";}
-var out=[data.trackNum|0x80,data.timecode>>8,data.timecode&0xff,flags].map(function(e){return String.fromCharCode(e)}).join('')+data.frame;return out;}
-function parseWebP(riff){var VP8=riff.RIFF[0].WEBP[0];var frame_start=VP8.indexOf('\x9d\x01\x2a');for(var i=0,c=[];i<4;i++)c[i]=VP8.charCodeAt(frame_start+3+i);var width,horizontal_scale,height,vertical_scale,tmp;tmp=(c[1]<<8)|c[0];width=tmp&0x3FFF;horizontal_scale=tmp>>14;tmp=(c[3]<<8)|c[2];height=tmp&0x3FFF;vertical_scale=tmp>>14;return{width:width,height:height,data:VP8,riff:riff}}
-function parseRIFF(string){var offset=0;var chunks={};while(offset
-
-
-
-
-
-
-New Canvas - Drawception
-
-
- Fetching your Drawception game...
-
-
-
-
-
Time's up!
Submit now or miss the game!
-
-
-
![]()
-
-
-
46
-
-
-
-
- Sandbox
- Public safe for work game
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Guidelines
-
- - Simple drawings are welcome! Just give it your best shot
- - If the phrase is too difficult, use the skip button
- - Don't draw text - always draw a picture
- - Explicit (NSFW) drawings will get you banned
-
-
-
Tips
-
- - No explicit language allowed
- - Keep descriptions short and simple!
- - Avoid unnecessary detail, just describe the main subject
- - No metacommentary (e.g., don't comment on drawing quality)
- - No idea? Use the skip button
-
-
-
Sandbox
-
- - Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
- - Saved drawings will contain your drawing process in PNG format.
- - After loading a drawing you can continue to draw or watch its playback.
- - Note that editing the PNG with playback with other programs will likely destroy the playback data.
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d1375b4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "drawception-anbt",
+ "version": "1.156.2019.6",
+ "description": "ANBT is a script that will improve the comfort in drawing.",
+ "main": "index.js",
+ "scripts": {
+ "build": "rollup -c"
+ },
+ "author": "GromPE",
+ "license": "ISC",
+ "devDependencies": {
+ "@babel/core": "^7.5.5",
+ "rollup": "^1.17.0",
+ "rollup-plugin-babel": "^4.3.3",
+ "rollup-plugin-cleanup": "^3.1.1",
+ "rollup-plugin-clear": "^2.0.7",
+ "rollup-plugin-commonjs": "^10.0.1",
+ "rollup-plugin-copy": "^3.0.0",
+ "rollup-plugin-eslint": "^6.0.0",
+ "rollup-plugin-node-resolve": "^5.2.0",
+ "rollup-plugin-prettier": "^0.6.0",
+ "rollup-plugin-replace-html-vars": "^1.0.3",
+ "rollup-plugin-scss": "^1.0.1",
+ "rollup-plugin-terser": "^5.1.1",
+ "rollup-pluginutils": "^2.8.1"
+ },
+ "dependencies": {
+ "pako": "^1.0.10",
+ "pathseg": "^1.2.0"
+ }
+}
diff --git a/rollup.config.js b/rollup.config.js
new file mode 100644
index 0000000..3a5df26
--- /dev/null
+++ b/rollup.config.js
@@ -0,0 +1,125 @@
+import fs from 'fs'
+import cleanup from 'rollup-plugin-cleanup'
+import clear from 'rollup-plugin-clear'
+import commonjs from 'rollup-plugin-commonjs'
+import copy from 'rollup-plugin-copy'
+import { eslint } from 'rollup-plugin-eslint'
+import resolve from 'rollup-plugin-node-resolve'
+import prettier from 'rollup-plugin-prettier'
+import replaceHtmlVars from 'rollup-plugin-replace-html-vars'
+import scss from 'rollup-plugin-scss'
+import { terser } from 'rollup-plugin-terser'
+import banner from './src/extension/banner'
+
+const waitFile = path => {
+ return new Promise(resolve => {
+ const interval = setInterval(() => {
+ if (!fs.existsSync(path)) return
+ clearInterval(interval)
+ resolve(path)
+ }, 100)
+ })
+}
+
+export default [
+ {
+ input: 'src/newcanvas/js/pako.js',
+ output: {
+ format: 'iife',
+ file: 'build/newcanvas/pako.js'
+ },
+ plugins: [
+ clear({
+ targets: ['build/'],
+ watch: true
+ }),
+ commonjs(),
+ resolve(),
+ cleanup(),
+ eslint({
+ exclude: ['/build/*.js']
+ }),
+ terser()
+ ]
+ },
+ {
+ input: 'src/newcanvas/js/pathseg.js',
+ output: {
+ format: 'iife',
+ file: 'build/newcanvas/pathseg.js'
+ },
+ plugins: [
+ commonjs(),
+ resolve(),
+ cleanup(),
+ eslint({
+ exclude: ['/build/*.js']
+ }),
+ terser()
+ ]
+ },
+ {
+ input: 'src/newcanvas.js',
+ output: {
+ format: 'iife',
+ file: 'build/newcanvas/script.js'
+ },
+ plugins: [
+ scss({
+ output: 'build/newcanvas/style.css',
+ outputStyle: 'compressed'
+ }),
+ commonjs(),
+ resolve(),
+ cleanup(),
+ eslint({
+ exclude: ['/build/*.js']
+ }),
+ terser(),
+ copy({
+ targets: [
+ { src: 'src/newcanvas/html/index.html', dest: 'build/newcanvas' }
+ ]
+ })
+ ]
+ },
+ {
+ input: 'src/extension.js',
+ output: {
+ format: 'iife',
+ file: 'build/drawception-anbt.user.js',
+ banner
+ },
+ plugins: [
+ commonjs(),
+ resolve(),
+ cleanup(),
+ eslint({
+ exclude: ['/build/*.js']
+ }),
+ prettier({
+ cwd: __dirname
+ }),
+ copy({
+ targets: [{ src: 'build/newcanvas/index.html', dest: 'build/' }]
+ }),
+ waitFile('build/index.html').then(file => {
+ replaceHtmlVars({
+ files: file,
+ from: [
+ '/*NEWCANVAS_STYLE*/',
+ '/*PAKO_SCRIPT*/',
+ '/*PATHSEG_SCRIPT*/',
+ '/*NEWCANVAS_SCRIPT*/'
+ ],
+ to: [
+ fs.readFileSync('build/newcanvas/style.css', 'utf8'),
+ fs.readFileSync('build/newcanvas/pako.js', 'utf8'),
+ fs.readFileSync('build/newcanvas/pathseg.js', 'utf8'),
+ fs.readFileSync('build/newcanvas/script.js', 'utf8')
+ ]
+ })
+ })
+ ]
+ }
+]
diff --git a/src/extension.js b/src/extension.js
new file mode 100644
index 0000000..2dd360c
--- /dev/null
+++ b/src/extension.js
@@ -0,0 +1,25 @@
+import addDarkCSS from './extension/functions/darkMode/addDarkCSS'
+import setDarkMode from './extension/functions/darkMode/setDarkMode'
+import getLocalStorageItem from './extension/functions/getLocalStorageItem'
+import wrapper from './extension/wrapper'
+
+addDarkCSS()
+setDarkMode()
+if (document && document.body) {
+ if (!document.getElementById('_anbt_')) wrapper()
+ if (window.opera && !getLocalStorageItem('gpe_operaWarning', 0)) {
+ const anbtTitle = document.createElement('h2')
+ anbtTitle.innerHTML =
+ 'ANBT speaking:
Rename your script file so it doesn\'t contain ".user." part for smoother loading!
This warning is only shown once.'
+ const mainSection = document.getElementById('main')
+ mainSection.insertBefore(anbtTitle, mainSection.firstChild)
+ localStorage.setItem('gpe_operaWarning', 1)
+ }
+}
+document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+ if (!document.getElementById('_anbt_')) wrapper()
+ },
+ false
+)
diff --git a/src/extension/banner.js b/src/extension/banner.js
new file mode 100644
index 0000000..81bb861
--- /dev/null
+++ b/src/extension/banner.js
@@ -0,0 +1,17 @@
+import versions from './versions'
+
+const banner = `// ==UserScript==
+// @name Drawception ANBT
+// @author Grom PE
+// @namespace http://grompe.org.ru/
+// @version ${versions.scriptVersion}
+// @description Enhancement script for Drawception.com - Artists Need Better Tools
+// @downloadURL https://raw.github.com/EnderDragonneau/Drawception-ANBT/master/build/drawception-anbt.user.js
+// @match http://drawception.com/*
+// @match https://drawception.com/*
+// @grant none
+// @run-at document-start
+// @license Public domain
+// ==/UserScript==`
+
+export default banner
diff --git a/src/extension/betterPages.js b/src/extension/betterPages.js
new file mode 100644
index 0000000..0d78b02
--- /dev/null
+++ b/src/extension/betterPages.js
@@ -0,0 +1,17 @@
+import betterCreate from './functions/betterPages/betterCreate'
+import betterForums from './functions/betterPages/betterForums'
+import betterGame from './functions/betterPages/betterGame'
+import betterPanel from './functions/betterPages/betterPanel'
+import betterPlayer from './functions/betterPages/betterPlayer'
+import betterSettings from './functions/betterPages/betterSettings'
+
+const betterPages = {
+ betterCreate,
+ betterForums,
+ betterGame,
+ betterPanel,
+ betterPlayer,
+ betterSettings
+}
+
+export default betterPages
diff --git a/src/extension/functions/addMarkdownTools.js b/src/extension/functions/addMarkdownTools.js
new file mode 100644
index 0000000..ad4a68c
--- /dev/null
+++ b/src/extension/functions/addMarkdownTools.js
@@ -0,0 +1,24 @@
+import markdown from '../markdown'
+import getSelectedText from './getSelectedText'
+import $ from './selector'
+
+const addMarkdownTools = () => {
+ const textarea = $('#input-comment')
+ if (!textarea) return
+ const markdownDiv = $('')
+ Object.keys(markdown).forEach(toolName =>
+ markdownDiv.appendChild(
+ $(
+ ``
+ )
+ )
+ )
+ textarea.insertAdjacentHTML('beforebegin', markdownDiv.outerHTML)
+ ;[...$('#markdown-editor').children].forEach(children =>
+ children.addEventListener('click', getSelectedText)
+ )
+}
+
+export default addMarkdownTools
diff --git a/src/extension/functions/addStyle.js b/src/extension/functions/addStyle.js
new file mode 100644
index 0000000..61611cd
--- /dev/null
+++ b/src/extension/functions/addStyle.js
@@ -0,0 +1,10 @@
+const addStyle = css => {
+ const parent =
+ document.getElementsByTagName('head')[0] || document.documentElement
+ const style = document.createElement('style')
+ style.type = 'text/css'
+ style.appendChild(document.createTextNode(css))
+ parent.appendChild(style)
+}
+
+export default addStyle
diff --git a/src/extension/functions/base62ToDecimal.js b/src/extension/functions/base62ToDecimal.js
new file mode 100644
index 0000000..4070cec
--- /dev/null
+++ b/src/extension/functions/base62ToDecimal.js
@@ -0,0 +1,19 @@
+import globals from '../globals'
+
+const base62ToDecimal = number => {
+ number = number.toString()
+ const cachePosition = {}
+ let result = 0
+ let pow = 1
+ for (let i = number.length - 1; i >= 0; i--) {
+ const character = number[i]
+ if (typeof cachePosition[character] === 'undefined') {
+ cachePosition[character] = globals.alphabet.indexOf(character)
+ }
+ result += pow * cachePosition[character]
+ pow *= 62
+ }
+ return result
+}
+
+export default base62ToDecimal
diff --git a/src/extension/functions/betterPages/betterCreate.js b/src/extension/functions/betterPages/betterCreate.js
new file mode 100644
index 0000000..6f387ce
--- /dev/null
+++ b/src/extension/functions/betterPages/betterCreate.js
@@ -0,0 +1,13 @@
+import options from '../../options'
+import $ from '../selector'
+
+const betterCreate = () => {
+ if (!options.enterToCaption) {
+ if ($('#prompt'))
+ $('#prompt').addEventListener('keydown', event => {
+ if (event.keyCode === 13) event.preventDefault()
+ })
+ }
+}
+
+export default betterCreate
diff --git a/src/extension/functions/betterPages/betterForums.js b/src/extension/functions/betterPages/betterForums.js
new file mode 100644
index 0000000..2de7e3e
--- /dev/null
+++ b/src/extension/functions/betterPages/betterForums.js
@@ -0,0 +1,151 @@
+import options from '../../options'
+import addStyle from '../addStyle'
+import getLocalStorageItem from '../getLocalStorageItem'
+import linkifyDrawingPanels from '../linkifyDrawingPanels'
+import linkifyNodeText from '../linkifyNodeText'
+import $ from '../selector'
+import setupNewCanvas from '../setupNewCanvas'
+import formatTimestamp from '../time/formatTimestamp'
+
+const betterForums = () => {
+ // Linkify the links
+ $('.comment-body *', true).forEach(comment => linkifyNodeText(comment))
+
+ // Linkify drawing panels
+ $('img', true).forEach(img => linkifyDrawingPanels(img))
+
+ // Show posts IDs and link
+ if (document.location.pathname.match(/\/forums\/(\w+|-)\/.+/)) {
+ const hideUserIds = options.forumHiddenUsers
+ ? options.forumHiddenUsers.split(',')
+ : ''
+ if (hideUserIds)
+ addStyle(
+ '.anbt_hideUserPost:not(:target) {opacity: 0.4; margin-bottom: 10px}' +
+ '.anbt_hideUserPost:not(:target) .comment-body, .anbt_hideUserPost:not(:target) .avatar {display: none}' +
+ ''
+ )
+ let lastid = 0
+ $('.comment-avatar', true).forEach(({ parentNode }) => {
+ const commentHolder = parentNode.parentNode.parentNode
+ const anch = commentHolder.id || ''
+ commentHolder.classList.add('comment-holder') // No identification for these anymore, this is unhelpful!
+ const textMuted = commentHolder.querySelector('a.text-muted')
+ const vue = commentHolder.childNodes[0].__vue__
+ if (vue) {
+ textMuted.textContent = `${textMuted.textContent.trim()}, ${formatTimestamp(
+ vue.comment_date * 1000
+ )}`
+ if (vue.edit_date > 0) {
+ const el = textMuted.parentNode.querySelector('span[rel="tooltip"]')
+ const text = `${el.title}, ${formatTimestamp(
+ vue.edit_date * 1000
+ ).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart
+ el.setAttribute('title', text)
+ }
+ }
+ if (anch) {
+ const id = parseInt(anch.substring(1), 10)
+ const text = textMuted.textContent.trim()
+ textMuted.textContent = `${text} #${id}`
+ textMuted.setAttribute('title', 'Link to post')
+ if (id < lastid) textMuted.classList.add('wrong-order')
+ try {
+ const { href } = commentHolder.querySelector('a[href^="/player/"]')
+ if (href) {
+ const userId = href.match(/\d+/)[0]
+ if (hideUserIds.includes(userId))
+ commentHolder.classList.add('anbt_hideUserPost')
+ }
+ } catch (e) {}
+ lastid = id
+ }
+ })
+
+ // Warn about posting to another page
+ if (
+ $('.comment-holder') &&
+ $('.comment-holder').length === 20 &&
+ $('#comment-form .btn-primary')
+ )
+ $('#comment-form .btn-primary').insertAdjacentHTML(
+ 'afterend',
+ 'Note: posting to another page
'
+ )
+ }
+
+ if (options.proxyImgur)
+ $('img[src*="imgur.com/"]', true).forEach(img =>
+ img.setAttribute(
+ 'src',
+ img.src.replace('imgur.com', 'filmot.com').replace('https', 'http')
+ )
+ )
+
+ const pagination = $('.pagination', true)
+ if (pagination.length)
+ $('.breadcrumb').insertAdjacentHTML(
+ 'afterend',
+ `${pagination[0].outerHTML}
`
+ )
+
+ // For the topic list pages only
+ if (document.location.pathname.match(/\/forums\/(\w+)\/$/)) {
+ const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', [])
+ let hidden = 0
+
+ const tempUnhideLink = $('')
+
+ $('.forum-thread', true).forEach(thread => {
+ const href = thread
+ .querySelector('a:first-child')
+ .href.match(/\/forums\/\w+\/(\d+)\//)
+ // Don't let them hide the ANBT topic ;)
+ if (!href || !href[1] || href[1] === 11830) return
+
+ const id = href[1]
+ if (hiddenTopics.includes(id)) {
+ thread.classList.add('anbt_hidden')
+ hidden++
+ }
+ const hideLink = $('')
+ hideLink.addEventListener('click', () => {
+ const hiddenTopics = getLocalStorageItem('gpe_forumHiddenTopics', [])
+ if (hiddenTopics.includes(id)) {
+ if (hiddenTopics.includes(id))
+ hiddenTopics.splice(hiddenTopics.indexOf(id), 1)
+ hiddenTopics.splice(hiddenTopics.indexOf(id), 1)
+ thread.classList.remove('anbt_hidden')
+ hidden--
+ } else {
+ if (!hiddenTopics.includes(id)) hiddenTopics.push(id)
+ hiddenTopics.push(id)
+ thread.classList.add('anbt_hidden')
+ hidden++
+ tempUnhideLink.style.display = ''
+ }
+ tempUnhideLink.textContent = hidden
+ localStorage.setItem(
+ 'gpe_forumHiddenTopics',
+ JSON.stringify(hiddenTopics)
+ )
+ })
+ thread.querySelector('p:nth-child(2)').appendChild(hideLink)
+ })
+ tempUnhideLink.textContent = hidden
+ tempUnhideLink.addEventListener('click', () => {
+ $('#main').classList.toggle('anbt_showt')
+ })
+ if (!hidden) tempUnhideLink.style.display = 'none'
+ if ($('#js-btn-toggle-thread'))
+ $('#js-btn-toggle-thread').parentNode.appendChild(tempUnhideLink)
+ }
+ $('.btn.btn-default', true).forEach(button =>
+ button.addEventListener('click', () => {
+ if (button.textContent === 'Draw')
+ setupNewCanvas(true, document.location.href)
+ })
+ )
+}
+
+export default betterForums
diff --git a/src/extension/functions/betterPages/betterGame.js b/src/extension/functions/betterPages/betterGame.js
new file mode 100644
index 0000000..ba30331
--- /dev/null
+++ b/src/extension/functions/betterPages/betterGame.js
@@ -0,0 +1,88 @@
+import options from '../../options'
+import waitForComments from '../comment/waitForComments'
+import getLocalStorageItem from '../getLocalStorageItem'
+import addReplayButton from '../replay/addReplayButton'
+import reversePanels from '../reversePanels'
+import $ from '../selector'
+
+const betterGame = () => {
+ if (document.title === 'Not Safe For Work (18+) Gate') {
+ if (options.autoBypassNSFW) window.DrawceptionPlay.bypassNsfwGate()
+ return
+ }
+ const drawings = $(
+ 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]'
+ )
+
+ // Reverse panels button
+ if ($('#btn-copy-url'))
+ $('#btn-copy-url').insertAdjacentHTML(
+ 'afterend',
+ ' Reverse'
+ )
+
+ $('.reversePanels').addEventListener('click', reversePanels)
+
+ // Panel favorite buttons
+ const favButton = $(
+ ''
+ )
+ $('.panel-number', true).forEach(panelNumber =>
+ panelNumber.insertAdjacentHTML('afterend', favButton.outerHTML)
+ )
+ $('.gamepanel', true).forEach(({ parentNode }) => {
+ if (parentNode.querySelector('.gamepanel-tools>a:last-child') === null)
+ return
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ const id = parentNode
+ .querySelector('.gamepanel-tools>a:last-child')
+ .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1]
+ if (panels[id])
+ parentNode
+ .querySelector('.anbt_favpanel')
+ .classList.add('anbt_favedpanel')
+ })
+ $('.anbt_favpanel', true).forEach(favPanelButton => {
+ favPanelButton.addEventListener('click', () => {
+ if (favPanelButton.classList.contains('anbt_favedpanel')) return
+ const { parentNode } = favPanelButton
+ const id = parentNode
+ .querySelector('.gamepanel-tools>a:last-child')
+ .href.match(/\/panel\/[^/]+\/([^/]+)\/[^/]+\//)[1]
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ const panel = {
+ time: Date.now(),
+ by: parentNode.querySelector('.panel-user a').textContent
+ }
+ panel.userLink = parentNode
+ .querySelector('.panel-user a')
+ .href.match(/\/player\/[^/]+\/[^/]+\//)[0]
+ const img = parentNode.querySelector('.gamepanel img')
+ if (img) {
+ // Drawing panel
+ panel.image = img.src
+ panel.caption = img.alt
+ } else {
+ // Caption panel
+ panel.caption = parentNode
+ .querySelector('.gamepanel')
+ .textContent.trim()
+ }
+ panels[id] = panel
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ favPanelButton.classList.add('anbt_favedpanel')
+ })
+ })
+
+ // Panel replay button
+ if (options.newCanvas) {
+ if (drawings)
+ drawings.forEach(drawing =>
+ drawing.addEventListener('load', addReplayButton(drawing))
+ )
+ }
+
+ setTimeout(waitForComments, 200)
+}
+
+export default betterGame
diff --git a/src/extension/functions/betterPages/betterPanel.js b/src/extension/functions/betterPages/betterPanel.js
new file mode 100644
index 0000000..1962d23
--- /dev/null
+++ b/src/extension/functions/betterPages/betterPanel.js
@@ -0,0 +1,108 @@
+import options from '../../options'
+import getCookie from '../cookie/getCookie'
+import setCookie from '../cookie/setCookie'
+import getLocalStorageItem from '../getLocalStorageItem'
+import getPanelId from '../getPanelId'
+import checkForRecording from '../replay/checkForRecording'
+import $ from '../selector'
+import setupNewCanvas from '../setupNewCanvas'
+import unscrambleID from '../unscrambleID'
+
+const betterPanel = () => {
+ const favButton = $(
+ ''
+ )
+ const gamePanel = $(
+ '.panel-caption-display>.flex,.gamepanel-holder>.gamepanel'
+ )
+ if (gamePanel) gamePanel.insertAdjacentHTML('afterend', favButton.outerHTML)
+ const favBtn = $('.btn.btn-info')
+ if (favBtn) {
+ favBtn.addEventListener('click', event => {
+ event.preventDefault()
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ const panel = {
+ time: Date.now(),
+ by: $('.lead a', true)[0].textContent,
+ userLink: $('.lead a', true)[0].href.match(
+ /\/player\/[^/]+\/[^/]+\//
+ )[0]
+ }
+ const id = document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1]
+ const img = $('.gamepanel img')
+ if (img) {
+ // Drawing panel
+ panel.image = img.src
+ panel.caption = img.alt
+ } else {
+ // Caption panel
+ panel.caption = $('.gamepanel').textContent.trim()
+ }
+ panels[id] = panel
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ favBtn.setAttribute('disabled', 'disabled')
+ favBtn.querySelector('b').textContent = 'Favorited!'
+ })
+ }
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ if (
+ document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//) &&
+ panels[document.location.href.match(/\/panel\/[^/]+\/([^/]+)\//)[1]]
+ ) {
+ favBtn.setAttribute('disabled', 'disabled')
+ favBtn.querySelector('b').textContent = 'Favorited!'
+ }
+ const panelId = getPanelId(location.pathname)
+
+ // Only panels after 14924553 might have a recording
+ if (options.newCanvas && panelId && unscrambleID(panelId) >= 14924553) {
+ const img = $('.gamepanel img')
+ if (img)
+ checkForRecording(img.src, () => {
+ const replayLink = $(
+ ` Replay `
+ )
+ replayLink.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(true, `/sandbox/#${panelId}`)
+ })
+ $('.gamepanel').insertAdjacentHTML('afterend', replayLink.outerHTML)
+ })
+ }
+ if (
+ $('.btn-primary').length > 1 &&
+ $('.btn-primary')[1].textContent === 'Play again'
+ ) {
+ // Allow adding to cover creator
+ const ccButton = $(
+ ''
+ )
+ ccButton.addEventListener('click', event => {
+ event.preventDefault()
+ const id = unscrambleID(panelId)
+ const cookie = getCookie('covercreatorids')
+ const ids = cookie ? JSON.parse(cookie) : []
+ if (!ids.includes(id)) {
+ if (ids.length > 98) {
+ window.apprise(
+ 'Max cover creator drawings selected. Please remove some before adding more.'
+ )
+ return
+ } else ids.push(id.toString())
+ } else {
+ ccButton
+ .setAttribute('disabled', 'disabled')
+ .querySelector('b').textContent = 'Already added!'
+ return
+ }
+ setCookie('covercreatorids', JSON.stringify(ids))
+ ccButton
+ .setAttribute('disabled', 'disabled')
+ .querySelector('b').textContent = 'Added!'
+ })
+ $('.gamepanel').insertAdjacentHTML('afterend', ccButton.outerHTML)
+ }
+}
+
+export default betterPanel
diff --git a/src/extension/functions/betterPages/betterPlayer.js b/src/extension/functions/betterPages/betterPlayer.js
new file mode 100644
index 0000000..bae3157
--- /dev/null
+++ b/src/extension/functions/betterPages/betterPlayer.js
@@ -0,0 +1,118 @@
+import globals from '../../globals'
+import options from '../../options'
+import linkifyNodeText from '../linkifyNodeText'
+import randomGreeting from '../randomGreeting'
+import addReplaySign from '../replay/addReplaySign'
+import $ from '../selector'
+import formatTimestamp from '../time/formatTimestamp'
+import viewMyGameBookmarks from '../viewMyGameBookmarks'
+import viewMyPanelFavorites from '../viewMyPanelFavorites'
+
+const betterPlayer = () => {
+ // Linkify the links in location
+ const pubinfo = $('.profile-header-info .text-muted > span:last-child')
+ if (pubinfo) linkifyNodeText(pubinfo.parentNode)
+ const loc = document.location.href
+ // If it's user's homepage, add new buttons in there
+ if (loc.match(new RegExp(`/player/${globals.userId}/[^/]+/(?:$|#)`))) {
+ const anbtSection = $('ANBT stuff:
')
+ const panelFavoritesButton = $(
+ 'Panel Favorites'
+ )
+ const gameBookmarks = $(
+ 'Game Bookmarks'
+ )
+ anbtSection.appendChild(panelFavoritesButton)
+ anbtSection.appendChild(gameBookmarks)
+ const profilemain = $('.profile-layout-content').firstChild
+ profilemain.insertAdjacentHTML(
+ 'afterbegin',
+ `${randomGreeting()}
`
+ )
+ profilemain.insertAdjacentHTML('afterbegin', anbtSection.outerHTML)
+ $('.viewFavorites').addEventListener('click', event => {
+ event.preventDefault()
+ viewMyPanelFavorites()
+ })
+ $('.viewBookmarks').addEventListener('click', event => {
+ event.preventDefault()
+ viewMyGameBookmarks()
+ })
+
+ if (document.location.hash.includes('#anbt_panelfavorites'))
+ viewMyPanelFavorites()
+ if (document.location.hash.includes('#anbt_gamebookmarks'))
+ viewMyGameBookmarks()
+
+ // Show your exact registration date
+ if (window.date) {
+ const pubinfo = $('.profile-user-header>div.row>div>h1+p')
+ if (pubinfo)
+ [...pubinfo.childNodes][4].nodeValue = ` ${formatTimestamp(
+ window.date
+ )} \xa0`
+ }
+ } else {
+ // Not the current user's profile or not profile homepage
+ const drawings = $(
+ 'img[src^="https://cdn.drawception.com/images/panels/"],img[src^="https://cdn.drawception.com/drawings/"]',
+ true
+ )
+ // Show replayable panels; links are not straightforward to make since there's no panel ID
+ if (options.newCanvas)
+ drawings.forEach(drawing =>
+ drawing.addEventListener('load', addReplaySign(drawing))
+ )
+
+ // Detect Draw Firsts
+ drawings.forEach(({ src, parentNode }) => {
+ if (src.match(/-1\.png$/))
+ parentNode.parentNode.appendChild(
+ $(
+ '
'
+ )
+ )
+ })
+ }
+
+ // Convert timestamps in user profile's forum posts and game comments
+ if (loc.match(/player\/\d+\/[^/]+\/(posts)|(comments)\//)) {
+ // Show topic title at the top of the posts instead and display subforum
+ // Show game title at the top of the posts
+ $('.forum-thread-starter', true).forEach(threadStarter => {
+ const vue = threadStarter.childNodes[0].__vue__
+ if (vue) {
+ const ts = threadStarter.querySelector('a.text-muted').firstChild
+ ts.textContent = `${ts.textContent.trim()}, ${formatTimestamp(
+ vue.comment_date * 1000
+ )}`
+ if (vue.edit_date > 0) {
+ const el = ts.parentNode.parentNode.querySelector(
+ 'span[rel="tooltip"]'
+ )
+ const text = `${el.title}, ${formatTimestamp(
+ vue.edit_date * 1000
+ ).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart
+ el.setAttribute('title', text)
+ }
+ }
+ const postlink = threadStarter.querySelector(
+ '.add-margin-top small.text-muted'
+ )
+ const created = postlink.textContent.match(/^\s*Created/)
+ const commented = postlink.textContent.match(/^\s*Commented/)
+ const prefix = commented
+ ? 'Comment in the game'
+ : created
+ ? 'New thread'
+ : 'Reply in'
+ const n = $(`${prefix}:
`)
+ const thread = postlink.querySelector('a')
+ n.appendChild(thread)
+ threadStarter.insertAdjacentHTML('afterbegin', n.outerHTML)
+ postlink.parentNode.parentNode.removeChild(postlink.parentNode)
+ })
+ }
+}
+
+export default betterPlayer
diff --git a/src/extension/functions/betterPages/betterSettings.js b/src/extension/functions/betterPages/betterSettings.js
new file mode 100644
index 0000000..dc2ba32
--- /dev/null
+++ b/src/extension/functions/betterPages/betterSettings.js
@@ -0,0 +1,150 @@
+import $ from '../selector'
+import addGroup from '../settings/addGroup'
+import updateScriptSettings from '../settings/updateScriptSettings'
+
+const betterSettings = () => {
+ const theForm = $(
+ ''
+ )
+ theForm.appendChild($(''))
+ theForm.appendChild(
+ addGroup('Pen Tablet (unavailable for the moment...)', [
+ [
+ 'enableWacom',
+ 'boolean',
+ 'Enable Wacom plugin / pressure sensitivity support'
+ ],
+ [
+ 'fixTabletPluginGoingAWOL',
+ 'boolean',
+ 'Try to prevent Wacom plugin from disappearing'
+ ]
+ //["pressureExponent", "number", "Pressure exponent (smaller = softer tablet response, bigger = sharper)"],
+ ])
+ )
+ theForm.appendChild(
+ addGroup('Play (most settings are for the new canvas only)', [
+ [
+ 'newCanvas',
+ 'boolean',
+ 'New drawing canvas (also allows watching playback)'
+ ],
+ [
+ 'submitConfirm',
+ 'boolean',
+ 'Confirm submitting if more than a minute is left'
+ ],
+ ['smoothening', 'boolean', 'Smoothing of strokes'],
+ ['hideCross', 'boolean', 'Hide the cross when drawing'],
+ [
+ 'enterToCaption',
+ 'boolean',
+ 'Submit captions (and start games) by pressing Enter'
+ ],
+ [
+ 'backup',
+ 'boolean',
+ 'Save the drawing in case of error and restore it in sandbox'
+ ],
+ [
+ 'timeoutSound',
+ 'boolean',
+ 'Warning sound when only a minute is left (normal games)'
+ ],
+ [
+ 'timeoutSoundBlitz',
+ 'boolean',
+ 'Warning sound when only 5 seconds left (blitz)'
+ ],
+ ['timeoutSoundVolume', 'number', 'Volume of the warning sound, in %'],
+ [
+ 'rememberPosition',
+ 'boolean',
+ 'Show your panel position and track changes in unfinished games list'
+ ],
+ ['colorNumberShortcuts', 'boolean', 'Use 0-9 keys to select the color'],
+ [
+ 'colorUnderCursorHint',
+ 'boolean',
+ 'Show the color under the cursor in the palette'
+ ],
+ [
+ 'colorDoublePress',
+ 'boolean',
+ 'Double press 0-9 keys to select color without pressing shift'
+ ],
+ [
+ 'bookmarkOwnCaptions',
+ 'boolean',
+ 'Automatically bookmark your own captions in case of dustcatchers'
+ ]
+ ])
+ )
+ theForm.appendChild(
+ addGroup('Miscellaneous', [
+ [
+ 'localeTimestamp',
+ 'boolean',
+ `Format timestamps as your system locale (${new Date().toLocaleString()})`
+ ],
+ [
+ 'proxyImgur',
+ 'boolean',
+ 'Replace imgur.com links to filmot.com to load, in case your ISP blocks them'
+ ],
+ ['ajaxRetry', 'boolean', 'Retry failed AJAX requests'],
+ [
+ 'autoplay',
+ 'boolean',
+ 'Automatically start replay when watching playback'
+ ],
+ ['autoBypassNSFW', 'boolean', 'Automatically bypass NSFW game warning'],
+ ['markStalePosts', 'boolean', 'Mark stale forum posts'],
+ [
+ 'maxCommentHeight',
+ 'number',
+ 'Maximum comments and posts height until directly linked (px, 0 = no limit)'
+ ],
+ [
+ 'useOldFont',
+ 'boolean',
+ 'Use old Nunito font (which is usually bolder and less wiggly)'
+ ],
+ ['useOldFontSize', 'boolean', 'Use old, smaller font size'],
+ ['markdownTools', 'boolean', 'Markdown tools for messages'],
+ [
+ 'anbtDarkMode',
+ 'boolean',
+ "Switch between ANBT's and Drawception's dark mode"
+ ]
+ ])
+ )
+ theForm.appendChild(
+ addGroup('Advanced', [
+ [
+ 'newCanvasCSS',
+ 'longstr',
+ 'Custom CSS for new canvas (experimental, get styles here)'
+ ],
+ [
+ 'forumHiddenUsers',
+ 'longstr',
+ 'Comma-separated list of user IDs whose forum posts are hidden'
+ ]
+ ])
+ )
+ $(
+ '
'
+ ).forEach(node => theForm.appendChild(node))
+ $('#main').insertAdjacentHTML('afterbegin', theForm.outerHTML)
+ $('.settingsForm').addEventListener(
+ 'submit',
+ form => updateScriptSettings(form) && false
+ )
+
+ // Extend "location" input to max server-accepted 65 characters
+ if ($('input[name="location"]'))
+ $('input[name="location"]').setAttribute('maxlength', '65')
+}
+
+export default betterSettings
diff --git a/src/extension/functions/comment/betterComments.js b/src/extension/functions/comment/betterComments.js
new file mode 100644
index 0000000..40f743e
--- /dev/null
+++ b/src/extension/functions/comment/betterComments.js
@@ -0,0 +1,123 @@
+import options from '../../options'
+import getLocalStorageItem from '../getLocalStorageItem'
+import $ from '../selector'
+import formatTimestamp from '../time/formatTimestamp'
+
+const betterComments = () => {
+ // Linkify the links and add ability to address comment holders again
+ const comments = [...$('#comments').nextElementSibling.children].slice(1)
+ comments.forEach(x => {
+ x.parentNode.parentNode.classList.add('comment-holder')
+ })
+ // Interlink game panels and comments
+ const gamePlayers = []
+ const playerdata = {}
+ $('.gamepanel-holder', true).forEach((gamePanel, index) => {
+ const detail = gamePanel.querySelector('.panel-details')
+ const gamepanel = gamePanel.querySelector('.gamepanel')
+ const playerLink = detail.querySelector('.panel-user a')
+ if (!playerLink) return
+ const id = playerLink.href.match(/\/player\/(\d+)\//)[1]
+ playerdata[id] = {
+ panel_number: index + 1,
+ player_anchor: playerLink,
+ panel_id: gamepanel.id,
+ drew: gamepanel.querySelector('img') !== null,
+ comments: 0
+ }
+ gamePlayers.push(id)
+ })
+ // Highlight new comments and remember seen comments
+ const seenComments = getLocalStorageItem('gpe_seenComments', {})
+ const gameid = document.location.href.match(/game\/([^/]+)\//)[1]
+ if (comments) {
+ // Clear old tracked comments
+ const hour = Math.floor(Date.now() / (1000 * 60 * 60)) // timestamp with 1 hour precision
+ // Store game entry for up to a week after last tracked comment
+ for (const tempgame in seenComments)
+ if (seenComments[tempgame].h + 24 * 7 < hour)
+ delete seenComments[tempgame]
+ let maxseenid = 0
+ comments.forEach(holder => {
+ const dateElement = holder.querySelector('a.text-muted')
+ const vue = holder.__vue__
+ if (vue) {
+ const text = dateElement.textContent.trim()
+ dateElement.textContent = `${text}, ${formatTimestamp(
+ vue.comment_date * 1000
+ )}`
+ if (vue.edit_date > 0) {
+ const element = dateElement.parentNode.querySelector(
+ 'span[rel="tooltip"]'
+ )
+ const title = `${element.title}, ${formatTimestamp(
+ vue.edit_date * 1000
+ ).replace(/ /g, '\u00A0')}` // prevent the short tooltip width from breaking date apart
+ element.setAttribute('title', title)
+ }
+ }
+ const ago = dateElement.textContent
+ const commentid = parseInt(holder.id.slice(1), 10)
+ // Also allow linking to specific comment
+ dateElement.setAttribute('title', 'Link to comment')
+ dateElement.textContent = `${dateElement.textContent.trim()} #${commentid}`
+ // Track comments from up to week ago
+ if (ago.match(/just now|min|hour|a day| [1-7] day/)) {
+ if (!(seenComments[gameid] && seenComments[gameid].id >= commentid)) {
+ holder.classList.add('comment-new')
+ if (maxseenid < commentid) maxseenid = commentid
+ }
+ }
+ // Add game perticipation info
+ const link = holder.querySelector('.text-bold a')
+ ? holder.querySelector('.text-bold a').href.match(/\/player\/(\d+)\//)
+ : ''
+ if (link) {
+ const id = link[1]
+ if (gamePlayers.includes(id)) {
+ const drew = playerdata[id].drew ? 'drew' : 'wrote'
+ dateElement.insertAdjacentHTML(
+ 'beforebegin',
+ `(${drew} #${
+ playerdata[id].panel_number
+ }) `
+ )
+ playerdata[id].comments++
+ }
+ }
+ })
+ if (maxseenid)
+ seenComments[gameid] = {
+ h: hour,
+ id: maxseenid
+ }
+ localStorage.setItem('gpe_seenComments', JSON.stringify(seenComments))
+ }
+ for (const playerId in gamePlayers) {
+ const data = playerdata[playerId]
+ if (data && data.comments) {
+ const cmt2 = `Player left ${data.comments} comment${
+ data.comments > 1 ? 's' : ''
+ }`
+ data.player_anchor.title = cmt2
+ data.player_anchor.insertAdjacentHTML(
+ 'afterend',
+ `${data.comments}`
+ )
+ }
+ }
+ if (options.maxCommentHeight) {
+ const h = options.maxCommentHeight
+ comments.forEach(comment =>
+ comment.addEventListener('click', () => {
+ if (
+ comment.clientHeight > h - 50 &&
+ !$(location.hash).has(comment).length
+ )
+ location.hash = `#${comment.parentNode.parentNode.id}`
+ })
+ )
+ }
+}
+
+export default betterComments
diff --git a/src/extension/functions/comment/waitForComments.js b/src/extension/functions/comment/waitForComments.js
new file mode 100644
index 0000000..0435c96
--- /dev/null
+++ b/src/extension/functions/comment/waitForComments.js
@@ -0,0 +1,16 @@
+import $ from '../selector'
+import betterComments from './betterComments'
+
+const waitForComments = () => {
+ const comments = $('#comments')
+ ? [...$('#comments').nextElementSibling.children].slice(1)
+ : ''
+ if (comments.length && !comments[0].classList.contains('spinner'))
+ betterComments()
+ else {
+ if (comments.length === 0) return
+ setTimeout(waitForComments, 1000)
+ }
+}
+
+export default waitForComments
diff --git a/src/extension/functions/cookie/getCookie.js b/src/extension/functions/cookie/getCookie.js
new file mode 100644
index 0000000..f894099
--- /dev/null
+++ b/src/extension/functions/cookie/getCookie.js
@@ -0,0 +1,4 @@
+const getCookie = name =>
+ document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`))[2] || null
+
+export default getCookie
diff --git a/src/extension/functions/cookie/setCookie.js b/src/extension/functions/cookie/setCookie.js
new file mode 100644
index 0000000..e29c6b2
--- /dev/null
+++ b/src/extension/functions/cookie/setCookie.js
@@ -0,0 +1,12 @@
+const setCookie = (name, value, expire) => {
+ if (expire) {
+ const time = new Date()
+ time.setTime(time.getTime() + 24 * expire * 60 * 60 * 1e3)
+ expire = time.toUTCString()
+ }
+ document.cookie = `${name}=${value ? JSON.stringify(value) : ''}; expires=${
+ expire ? expire : 'Thu, 01 Jan 1970 00:00:00 UTC'
+ }; path=/`
+}
+
+export default setCookie
diff --git a/src/extension/functions/darkMode/addDarkCSS.js b/src/extension/functions/darkMode/addDarkCSS.js
new file mode 100644
index 0000000..0d8e37e
--- /dev/null
+++ b/src/extension/functions/darkMode/addDarkCSS.js
@@ -0,0 +1,53 @@
+const addDarkCSS = () =>
+ localStorage.setItem(
+ 'gpe_darkCSS',
+ (
+ 'a{color:#77c0ff$}.wrapper{~#444$}#nav-drag{~#353535$}.btn-default{~#7f7f7f$;border-bottom-color:#666$;border-left-color:#666$;border-right-color:#666$;border-top-color:#666$;color:#CCC$}' +
+ '.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{~#757575$;border-bottom-color:#565656$;border-left-color:#565656$;border-right-color:#565656$;border-top-color:#565656$;color:#DDD$}' +
+ '.btn-success{~#2e2e2e$;border-bottom-color:#262626$;border-left-color:#262626$;border-right-color:#262626$;border-top-color:#262626$;color:#CCC$}' +
+ '.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{~#232323$;border-bottom-color:#1c1c1c$;border-left-color:#1c1c1c$;border-right-color:#1c1c1c$;border-top-color:#1c1c1c$;color:#DDD$}' +
+ '.btn-primary{~#213184$;border-bottom-color:#1a1a68$;border-left-color:#1a1a68$;border-right-color:#1a1a68$;border-top-color:#1a1a68$;color:#CCC$}' +
+ '.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{~#191964$;border-bottom-color:#141450$;border-left-color:#141450$;border-right-color:#141450$;border-top-color:#141450$;color:#DDD$}' +
+ '.btn-info{~#2d7787$;border-bottom-color:#236969$;border-left-color:#236969$;border-right-color:#236969$;border-top-color:#236969$;color:#CCC$}' +
+ '.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{~#1c5454$;border-bottom-color:#133939$;border-left-color:#133939$;border-right-color:#133939$;border-top-color:#133939$;color:#DDD$}' +
+ '.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{~#3b3b3b$}.navbar-toggle{~#393939$}.navbar{border-bottom:1px solid #000$}.forum-thread-starter,.breadcrumb,.regForm{~#555$}' +
+ '.form-control{~#666$;border:1px solid #333$;color:#EEE$}code,pre{~#656$;color:#FCC$}body{color:#EEE$}footer{~#333$;border-top:1px solid #000$}' +
+ '.pagination>li:not(.disabled):not(.active),.pagination>li:not(.disabled):not(.active)>a:hover,.pagination>li:not(.disabled):not(.active)>span:hover,.pagination>li:not(.disabled):not(.active)>a:focus,.pagination>li:not(.disabled):not(.active)>span:focus{~#444$}.pagination>li>a,.pagination>li>span{~#555$;border:1px solid #000$}' +
+ '.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{~#666$;border-top:1px solid #444$;border-bottom:1px solid #444$}' +
+ '.drawingForm{~#555$}.well{~#666$;border:1px solid #333$}#timeleft{color:#AAA$}legend{border-bottom:1px solid #000$}.thumbpanel{color:#EEE;~#555$}.thumbpanel img{~#fffdc9$}.panel-number,.modal-content,.profile-user-header{~#555$}' +
+ '#commentForm{~#555$;border:1px solid #000$}.modal-header,.nav-tabs{border-bottom:1px solid #000$}hr,.modal-footer{border-top:1px solid #000$}' +
+ '.store-item{background:#666$;~-moz-linear-gradient(top,#666 0,#333 100%)$;~-webkit-gradient(linear,left top,left bottom,color-stop(0,#666),color-stop(100%,#333))$;~-webkit-linear-gradient(top,#666 0,#333 100%)$;~-o-linear-gradient(top,#666 0,#333 100%)$;~-ms-linear-gradient(top,#666 0,#333 100%)$;~linear-gradient(to bottom,#666 0,#333 100%)$;border:1px solid #222$}' +
+ '.store-item:hover{border:1px solid #000$}.store-item-title{~#222$;color:#DDD$}.store-title-link{color:#DDD$}.profile-award{~#222$}.profile-award-unlocked{~#888$}.progress-bar{color:#CCC$;~#214565$}.progress{~#333$}' +
+ '.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(.25,rgba(0,0,0,0.15)),color-stop(.25,transparent),color-stop(.5,transparent),color-stop(.5,rgba(0,0,0,0.15)),color-stop(.75,rgba(0,0,0,0.15)),color-stop(.75,transparent),to(transparent))$;background-image:-webkit-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:-moz-linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$;background-image:linear-gradient(45deg,rgba(0,0,0,0.15) 25%,transparent 25%,transparent 50%,rgba(0,0,0,0.15) 50%,rgba(0,0,0,0.15) 75%,transparent 75%,transparent)$}' +
+ '.progress-bar-success{~#363$}.progress-bar-info{~#367$}.progress-bar-warning{~#863$}.progress-bar-danger{~#733$}' +
+ '.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#DDD$;~#555$;border:1px solid #222$}.nav>li>a:hover,.nav>li>a:focus{~#333$;border-bottom-color:#222$;border-left-color:#111$;border-right-color:#111$;border-top-color:#111$}' +
+ '.nav>li.disabled>a,.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#555$}.table-striped>tbody>tr:nth-child(2n+1)>td,.table-striped>tbody>tr:nth-child(2n+1)>th{~#333$}' +
+ '.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{~#555$}.table thead>tr>th,.table tbody>tr>th,.table tfoot>tr>th,.table thead>tr>td,.table tbody>tr>td,.table tfoot>tr>td{border-top:1px solid #333$}.news-alert{~#555$;border:2px solid #444$}' +
+ '.btn-menu{~#2e2e2e$}.btn-menu:hover{~#232323$}.btn-yellow{~#8a874e$}.btn-yellow:hover{~#747034$}' +
+ 'a.label{color:#fff$}.text-muted,a.text-muted{color:#999$}a.wrong-order{color:#F99$}div.comment-holder:target{~#454$}' +
+ '.popover{~#777$}.popover-title{~#666$;border-bottom:1px solid #444$}.popover.top .arrow:after{border-top-color:#777$}.popover.right .arrow:after{border-right-color:#777$}.popover.bottom .arrow:after{border-bottom-color:#777$}.popover.left .arrow:after{border-left-color:#777$}' +
+ '.label-fancy{~#444$;border-color:#333$;color:#FFF$}' +
+ '.avatar,.profile-avatar{~#444$;border:1px solid #777$;}' +
+ '.bg-lifesupport{~#444$}body{~#555$}.snap-content{~#333$}' +
+ 'select,textarea{color:#000$}.help-block{color:#ddd$}.jumbotron{~#444$}' +
+ '.navbar-dropdown{~#444$}a.list-group-item{~#444$;color:#fff$;border:1px solid #222$}a.list-group-item:hover,a.list-group-item:focus{~#222$}' +
+ '.likebutton.btn-success{color:#050$;~#5A5$}.likebutton.btn-success:hover{~#494$}' +
+ ".thumbnail[style*='background-color: rgb(255, 255, 255)']{~#555$}" +
+ '.popup,.v--modal{~#666$;border:1px solid #222$}.btn-reaction{~#666$;border:none$;color:#AAA$}@media(min-width:625px){.create-game-wrapper{~#555$}}' +
+ '.profile-header{~#555$}.profile-nav > li > a{~#333$}.profile-nav>li.active>a,.profile-nav>li>a:hover{~#555$}' +
+ '.gsc-control-cse{~#444$;border-color:#333$}.gsc-above-wrapper-area,.gsc-result{border:none$}.gs-snippet{color:#AAA$}.gs-visibleUrl{color:#8A8$}a.gs-title b,.gs-visibleUrl b{color:#EEE$}.gsc-adBlock{display:none$}.gsc-input{~#444$;border-color:#333$;color:#EEE$}' +
+ '.comment-highlight{border:none$;background:#454$}#header-emotes{~#555$}#header-bar-container{border:none$}.paypal-button-tag-content{color:#EEE$}.numlikes{color:#EEE$}.gsc-input-box{~#444$;border-color:#333$}.gsc-completion-container{~#333$;border-color:#000$}.gsc-completion-selected{~#222$}.gsc-completion-container b{color:#AAA$}.alert-nice{~#4a4a4a$}.store-buy-coins{~#777$}.store-buy-coins:hover{~#666$}.store-buy-coins>h2,.store-buy-coins>h2>small{color:#EEE$}.store-package-selector{~#888$}.store-package-selector>label{color:#EEE$}.label-stat{~#444$;color:#EEE$;border:1px solid #555$}.label-stat.disabled{~#333$}.option{padding:4px 8px$;~#666$;color:#EEE$;border-color:#333$}.option.selected{border-color:#EEE$}.sleek-select{~#666$;border-color:#333$}select{color:#EEE$}.modal-note{color:#EEE$}.vue-dialog-button{~#555$;border:none$}.vue-dialog-button:hover{~#5a5a5a$}.vue-dialog-buttons{border-top:1px solid #222$}.dashboard-item{~#333$}legend{color:#EEE$}.list-group-item{~#444$;color:#EEE$;border:1px solid #222$}.alert-warning{color:#EEE$;~#555$;border-color:#555$}.btn-reaction.active{border:1px solid #EEE$}.bg-shadow-box{~#333$}.btn-gray{~#222$;border:none$}.btn-gray:hover{color:#EEE$;~#1a1a1a$}.btn-bright{~#333$;color:#EEE$}' +
+ '.player-name-new{color:#33b73f$}.gsc-tabsArea>div{overflow:hidden$}.gs-image-popup-box{~#333$;border-color:#222$}.gs-size{color:#6f6f6f$}.gsc-result-info{color:#EEE$}.gsc-refinementsArea{border:none$}.gsc-tabsArea{border-bottom-color:#333$}.gsc-cursor-page{color:#EEE$}.gsc-cursor-current-page{color:#AAA$}.profile-nav>.disabled>a{color:#555$;~#3a3a3a$}.profile-nav>.disabled>a:hover{~#3a3a3a$}.sleek-select:hover{border-color:#EEE$}' +
+ '.btn-menu{border-color:#1e1e1e$;text-shadow:0px 0px 3px #777$}.btn-menu:hover{border-color:#1e1e1e$}.pagination>.active>span{color:#EEE$}#btn-notifications{color:#EEE$}.btn-warning{color:#EEE$}.alert-nice{color:#EEE$}.emotes-popup{~#2e2e2e$}.navbar-toggle{~#2e2e2e$;border-color:#1e1e1e$}.navbar-toggle .icon-bar{~#EEE$}' +
+ '.gamepanel-highlight{box-shadow:0 0 20px #111$;~#222$}' +
+ // We have entered specificity hell...
+ 'a.anbt_replaypanel:hover{color:#8af$}' +
+ '.anbt_favedpanel{color:#d9534f$}' +
+ // Some lamey compression method!
+ ''
+ )
+ .replace(/~/g, 'background-color:')
+ .replace(/\$/g, ' !important')
+ )
+
+export default addDarkCSS
diff --git a/src/extension/functions/darkMode/setDarkMode.js b/src/extension/functions/darkMode/setDarkMode.js
new file mode 100644
index 0000000..ddf6604
--- /dev/null
+++ b/src/extension/functions/darkMode/setDarkMode.js
@@ -0,0 +1,25 @@
+import getLocalStorageItem from '../getLocalStorageItem'
+
+const setDarkMode = () => {
+ const settings = getLocalStorageItem('gpe_anbtSettings', {})
+ if (settings.anbtDarkMode || typeof settings.anbtDarkMode === 'undefined') {
+ if (getLocalStorageItem('gpe_inDark', 0)) {
+ const css = document.createElement('style')
+ css.id = 'darkgraycss'
+ css.type = 'text/css'
+ css.appendChild(
+ document.createTextNode(getLocalStorageItem('gpe_darkCSS'))
+ )
+ if (document.head) document.head.appendChild(css)
+ else {
+ const darkLoad = setInterval(() => {
+ if (!document.head) return
+ document.head.appendChild(css)
+ clearInterval(darkLoad)
+ }, 100)
+ }
+ }
+ }
+}
+
+export default setDarkMode
diff --git a/src/extension/functions/darkMode/toggleLight.js b/src/extension/functions/darkMode/toggleLight.js
new file mode 100644
index 0000000..c8d35d3
--- /dev/null
+++ b/src/extension/functions/darkMode/toggleLight.js
@@ -0,0 +1,31 @@
+import options from '../../options'
+import setCookie from '../cookie/setCookie'
+import getLocalStorageItem from '../getLocalStorageItem'
+
+const toggleLight = () => {
+ if (options.anbtDarkMode) {
+ const inDark = getLocalStorageItem('gpe_inDark', 0)
+ if (!inDark) {
+ const css = document.createElement('style')
+ css.id = 'darkgraycss'
+ css.type = 'text/css'
+ css.appendChild(
+ document.createTextNode(getLocalStorageItem('gpe_darkCSS'))
+ )
+ document.head.appendChild(css)
+ } else {
+ document.head.removeChild(document.getElementById('darkgraycss'))
+ }
+ localStorage.setItem('gpe_inDark', `${inDark ? 0 : 1}`)
+ } else {
+ if (document.body.classList.contains('theme-night')) {
+ document.body.classList.remove('theme-night')
+ setCookie('theme-night')
+ } else {
+ document.body.classList.add('theme-night')
+ setCookie('theme-night', 1, 365)
+ }
+ }
+}
+
+export default toggleLight
diff --git a/src/extension/functions/decimalToBase62.js b/src/extension/functions/decimalToBase62.js
new file mode 100644
index 0000000..e5ddefa
--- /dev/null
+++ b/src/extension/functions/decimalToBase62.js
@@ -0,0 +1,13 @@
+import globals from '../globals'
+
+const decimalToBase62 = number => {
+ let result = ''
+ while (number !== 0) {
+ const quotient = number % 62
+ result = globals.alphabet[quotient] + result
+ number = (number - quotient) / 62
+ }
+ return result
+}
+
+export default decimalToBase62
diff --git a/src/extension/functions/fade/fadeIn.js b/src/extension/functions/fade/fadeIn.js
new file mode 100644
index 0000000..7ec62c0
--- /dev/null
+++ b/src/extension/functions/fade/fadeIn.js
@@ -0,0 +1,14 @@
+const fadeIn = (element, duration = 400) => {
+ element.style.display = 'inline'
+ duration = duration === 'slow' ? 600 : duration
+ element.style.opacity = element.style.opacity
+ ? parseFloat(element.style.opacity) + 0.1
+ : 0.2
+ if (parseFloat(element.style.opacity) > 1) element.style.opacity = 1
+ else
+ setTimeout(() => {
+ fadeIn(element, duration)
+ }, duration / 10)
+}
+
+export default fadeIn
diff --git a/src/extension/functions/fade/fadeOut.js b/src/extension/functions/fade/fadeOut.js
new file mode 100644
index 0000000..42e0aca
--- /dev/null
+++ b/src/extension/functions/fade/fadeOut.js
@@ -0,0 +1,15 @@
+const fadeOut = (element, duration = 400) => {
+ duration = duration === 'slow' ? 600 : duration
+ element.style.opacity = element.style.opacity
+ ? parseFloat(element.style.opacity) - 0.1
+ : 1
+ if (parseFloat(element.style.opacity) < 0) {
+ element.style.opacity = 0
+ element.style.display = 'none'
+ } else
+ setTimeout(() => {
+ fadeOut(element, duration)
+ }, duration / 10)
+}
+
+export default fadeOut
diff --git a/src/extension/functions/getLocalStorageItem.js b/src/extension/functions/getLocalStorageItem.js
new file mode 100644
index 0000000..bba6b3a
--- /dev/null
+++ b/src/extension/functions/getLocalStorageItem.js
@@ -0,0 +1,10 @@
+const getLocalStorageItem = (name, empty) => {
+ const item = localStorage.getItem(name)
+ try {
+ return item ? JSON.parse(item) : empty || ''
+ } catch (e) {
+ return item ? item : empty || ''
+ }
+}
+
+export default getLocalStorageItem
diff --git a/src/extension/functions/getNotifications.js b/src/extension/functions/getNotifications.js
new file mode 100644
index 0000000..1c15fa3
--- /dev/null
+++ b/src/extension/functions/getNotifications.js
@@ -0,0 +1,22 @@
+import $ from './selector'
+
+const getNotifications = () => {
+ if (!window.notificationsOpened) {
+ $('#user-notify-list').innerHTML =
+ '
'
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', '/notification/view/')
+ xhr.onload = () => {
+ if (xhr.status === 200) {
+ $('#user-notify-list').innerHTML = xhr.responseText
+ $('#user-notify-count').textContent = '0'
+ window.notificationsOpened = true
+ } else {
+ $('#user-notify-list').innerHTML = xhr.responseText
+ window.notificationsOpened = true
+ }
+ }
+ }
+}
+
+export default getNotifications
diff --git a/src/extension/functions/getPanelId.js b/src/extension/functions/getPanelId.js
new file mode 100644
index 0000000..b6fd02a
--- /dev/null
+++ b/src/extension/functions/getPanelId.js
@@ -0,0 +1,6 @@
+const getPanelId = url => {
+ const match = url.match(/\/panel\/[^/]+\/(\w+)\//)
+ if (match) return match[1]
+}
+
+export default getPanelId
\ No newline at end of file
diff --git a/src/extension/functions/getSelectedText.js b/src/extension/functions/getSelectedText.js
new file mode 100644
index 0000000..e3f1373
--- /dev/null
+++ b/src/extension/functions/getSelectedText.js
@@ -0,0 +1,19 @@
+import markdown from '../markdown'
+import $ from './selector'
+
+const getSelectedText = event => {
+ const textarea = $('#input-comment')
+ const { value, selectionStart, selectionEnd } = textarea
+ const { length } = value
+ const selection = value.substring(selectionStart, selectionEnd)
+ markdown[`${event.currentTarget.id}`].replaceFunc(
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+ )
+}
+
+export default getSelectedText
diff --git a/src/extension/functions/linkifyDrawingPanels.js b/src/extension/functions/linkifyDrawingPanels.js
new file mode 100644
index 0000000..474256f
--- /dev/null
+++ b/src/extension/functions/linkifyDrawingPanels.js
@@ -0,0 +1,33 @@
+import scrambleID from './scrambleID'
+
+const linkifyDrawingPanels = img => {
+ if (img.parentNode.nodeName !== 'A') {
+ if (img.src.match(/\/images\/panels\//) || img.src.match(/\/pub\/panels\//))
+ img.outerHTML = `${img.outerHTML}`
+
+ if (img.src.match(/\/drawings\//))
+ img.outerHTML = `${img.outerHTML}`
+
+ if (img.src.match(/\/panel\//))
+ img.outerHTML = `${img.outerHTML}`
+
+ if (img.src.match(/\/images\/games\//) || img.src.match(/\/pub\/games\//))
+ img.outerHTML = `${img.outerHTML}`
+
+ if (img.src.match(/\/display-panel.php?/)) {
+ const newsrc = `/panel/drawing/${scrambleID(
+ img.src.match(/x=(\d+)/)[1]
+ )}/`
+ img.setAttribute('src', newsrc)
+ img.outerHTML = `${img.outerHTML}`
+ }
+ }
+}
+
+export default linkifyDrawingPanels
diff --git a/src/extension/functions/linkifyNodeText.js b/src/extension/functions/linkifyNodeText.js
new file mode 100644
index 0000000..6c22565
--- /dev/null
+++ b/src/extension/functions/linkifyNodeText.js
@@ -0,0 +1,9 @@
+const linkifyNodeText = node => {
+ if (node.textContent.includes('://'))
+ node.innerHTML = node.innerHTML.replace(
+ /([^"]|^)(https?:\/\/(?:(?:(?:[^\s<()]*\([^\s<()]*\))+)|(?:[^\s<()]+)))/g,
+ '$1$2'
+ )
+}
+
+export default linkifyNodeText
diff --git a/src/extension/functions/markdown/bold.js b/src/extension/functions/markdown/bold.js
new file mode 100644
index 0000000..ed1c6fa
--- /dev/null
+++ b/src/extension/functions/markdown/bold.js
@@ -0,0 +1,58 @@
+const bold = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = new RegExp(`\\*\\*(${selection.replace(/\*/g, '')})\\*\\*`)
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selectionEnd += 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else
+ selection = selection.match(/\*\*.+\*\*/g)
+ ? selection.replace(/\*\*/g, '')
+ : `**${selection.replace(/\n/g, '**\n**')}**`
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else
+ selection = selection.match(/\*\*.+\*\*/g)
+ ? selection.replace(/\*\*/g, '')
+ : `**${selection.replace(/\n/g, '**\n**')}**`
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default bold
diff --git a/src/extension/functions/markdown/code.js b/src/extension/functions/markdown/code.js
new file mode 100644
index 0000000..ff77a33
--- /dev/null
+++ b/src/extension/functions/markdown/code.js
@@ -0,0 +1,38 @@
+const code = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /^ {4}(.*)/gm
+ if (selection.match(selRegex)) selection = selection.replace(/^ {4}/gm, '')
+ else if (
+ selectionStart === 0 ||
+ value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm)
+ ) {
+ if (selection.match(/^ {4}/gm)) selection = selection.replace(/^ {4}/gm, '')
+ else
+ selection = `${
+ selectionStart === 0
+ ? ''
+ : value.substring(selectionStart - 1, selectionEnd).match(/^\n/)
+ ? '\n'
+ : '\n\n'
+ } ${selection.replace(/\n/g, '\n ')}`
+ } else
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n/)
+ ? '\n'
+ : '\n\n'
+ } ${selection.replace(/\n^(.*)/gm, '\n $1')}${
+ value.substring(selectionEnd, selectionEnd + 1).match(/\n/) ? '' : '\n'
+ }`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default code
diff --git a/src/extension/functions/markdown/heading.js b/src/extension/functions/markdown/heading.js
new file mode 100644
index 0000000..4b157fe
--- /dev/null
+++ b/src/extension/functions/markdown/heading.js
@@ -0,0 +1,42 @@
+const heading = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /^#+ .*/gm
+ if (selection.match(selRegex)) {
+ selection = selection.replace(/^# /gm, '')
+ if (selection.match(/^#{2,} /gm)) selection.replace(/(^#*)# /gm, '$1 ')
+ } else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/\n.*/gm)
+ )
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }###### ${selection.replace(/\n/g, '\n###### ')}`
+ else if (value.substring(selectionStart - 1, selectionEnd).match(selRegex)) {
+ selectionStart -= 4
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^#*)# /gm, '$1 ')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 5
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^#*)# /gm, '$1 ')
+ } else selection = `\n###### ${selection.replace(/\n/g, '\n###### ')}`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default heading
diff --git a/src/extension/functions/markdown/highlighter.js b/src/extension/functions/markdown/highlighter.js
new file mode 100644
index 0000000..0b5c665
--- /dev/null
+++ b/src/extension/functions/markdown/highlighter.js
@@ -0,0 +1,52 @@
+const highlighter = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = new RegExp(`\`(${selection.replace(/`/g, '')})\``)
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/`.+`/g)
+ ? selection.replace(/`/g, '')
+ : `\`${selection.replace(/\n/g, '`\n`')}\``
+ }
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/`.+`/g)
+ ? selection.replace(/`/g, '')
+ : `\`${selection.replace(/\n/g, '`\n`')}\``
+ }
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default highlighter
diff --git a/src/extension/functions/markdown/image.js b/src/extension/functions/markdown/image.js
new file mode 100644
index 0000000..5448a6b
--- /dev/null
+++ b/src/extension/functions/markdown/image.js
@@ -0,0 +1,57 @@
+import $ from '../selector'
+
+const image = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /!\[(.*)\]\((\S*)( ".*")?\)/
+ if (selection.match(selRegex))
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection.replace(selRegex, '$1 $2') +
+ value.substring(selectionEnd, length)
+ else {
+ let link = ''
+ if (!selection.match(/\[(.*)\]\((\S*)( ".*")?\)/)) {
+ link = selection.match(/https?:\/\/\S*/) || ''
+ selection = selection
+ .replace(link[0], '')
+ .replace(/ +/g, ' ')
+ .trim()
+ } else selection = ''
+ const divModal = $(
+ ``
+ )
+ $('.navbar-header>div:last-child').append(divModal)
+ setTimeout(() => {
+ document.body.classList.add('v--modal-block-scroll')
+ $('#markdown').style.opacity = 1
+ }, 1)
+ $('#markdown-text').value = selection ? selection : ''
+ $('#markdown-link').value = link ? link[0] : ''
+ $('.close').addEventListener('click', () => {
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ $('#markdown-done').addEventListener('click', () => {
+ const tag = `.value
+ }${$('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''})`
+ selection = value.substring(selectionStart, selectionEnd)
+ textarea.value =
+ value.substring(0, selectionStart) +
+ (selection.match(/\[(.*)\]\((\S*)( ".*")?\)/)
+ ? selection.replace(/\[.*\]/, `[${tag}]`)
+ : tag) +
+ value.substring(selectionEnd, length)
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ }
+}
+
+export default image
diff --git a/src/extension/functions/markdown/italic.js b/src/extension/functions/markdown/italic.js
new file mode 100644
index 0000000..3a200fe
--- /dev/null
+++ b/src/extension/functions/markdown/italic.js
@@ -0,0 +1,57 @@
+const italic = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = new RegExp(
+ `\\*(?=\\S*${selection.replace(
+ /(.*)\*(.*)/g,
+ ''
+ )})((?:\\*\\*|\\\\[\\s\\S]|\\s+(?:\\\\[\\s\\S]|[^\\s\\*\\\\]|\\*\\*)|[^\\s\\*\\\\])+?)\\*(?!\\*)`
+ )
+ const italicRegex = /\*(?=\S)((?:\*\*|\\[\s\S]|\s+(?:\\[\s\S]|[^\s\*\\]|\*\*)|[^\s\*\\])+?)\*(?!\*)/g
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(italicRegex)
+ ? selection.replace(italicRegex, '$1')
+ : `*${selection.replace(/\n/g, '*\n*')}*`
+ }
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else
+ selection = selection.match(italicRegex)
+ ? selection.replace(italicRegex, '$1')
+ : `*${selection.replace(/\n/g, '*\n*')}*`
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default italic
diff --git a/src/extension/functions/markdown/link.js b/src/extension/functions/markdown/link.js
new file mode 100644
index 0000000..df4cb0b
--- /dev/null
+++ b/src/extension/functions/markdown/link.js
@@ -0,0 +1,54 @@
+import $ from '../selector'
+
+const link = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /^(?!!)\[(.*)\]\((\S*)( ".*")?\)/
+ if (selection.match(selRegex))
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection.replace(selRegex, '$1 $2') +
+ value.substring(selectionEnd, length)
+ else {
+ let imageLink = ''
+ if (!selection.match(/!\[(.*)\]\((\S*)( ".*")?\)/)) {
+ imageLink = selection.match(/https?:\/\/\S*/) || ''
+ selection = selection
+ .replace(imageLink[0], '')
+ .replace(/ +/g, ' ')
+ .trim()
+ }
+ const divModal = $(
+ ``
+ )
+ $('.navbar-header>div:last-child').append(divModal)
+ setTimeout(() => {
+ document.body.classList.add('v--modal-block-scroll')
+ $('#markdown').style.opacity = 1
+ }, 1)
+ $('#markdown-text').value = selection ? selection : ''
+ $('#markdown-link').value = imageLink ? imageLink[0] : ''
+ $('.close').addEventListener('click', () => {
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ $('#markdown-done').addEventListener('click', () => {
+ selection = `[${$('#markdown-text').value}](${$('#markdown-link').value}${
+ $('#markdown-hover').value ? ` "${$('#markdown-hover').value}"` : ''
+ })`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+ document.body.classList.remove('v--modal-block-scroll')
+ $('#markdown').outerHTML = ''
+ })
+ }
+}
+
+export default link
diff --git a/src/extension/functions/markdown/listOl.js b/src/extension/functions/markdown/listOl.js
new file mode 100644
index 0000000..dd43a3a
--- /dev/null
+++ b/src/extension/functions/markdown/listOl.js
@@ -0,0 +1,68 @@
+const listOl = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /^( {3})*\d+\. (.*)/gm
+ if (selection.match(selRegex)) {
+ selection = selection.match(/^ {3}/)
+ ? selection.replace(/^ {3}/gm, '')
+ : selection.replace(/^\d+\. /gm, '')
+ } else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/)
+ ) {
+ let countOl = 0
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }0. ${selection.replace(/\n/g, () => {
+ countOl++
+ return `\n${countOl}. `
+ })}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/)
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ } else if (
+ value.substring(selectionStart - 4, selectionEnd).match(/( {3})*\d+\. (.*)/)
+ ) {
+ selectionStart -= 4
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*(\d+\.) /g, ' $1$2 ')
+ } else if (
+ value.substring(selectionStart - 5, selectionEnd).match(/( {3})*\d+\. (.*)/)
+ ) {
+ selectionStart -= 5
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*(\d+\.) /g, ' $1$2 ')
+ } else {
+ let countOl = 0
+ selection = `\n0. ${selection.replace(/\n/g, () => {
+ countOl++
+ return `\n${countOl}. `
+ })}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) ||
+ selectionEnd === length
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default listOl
diff --git a/src/extension/functions/markdown/listUl.js b/src/extension/functions/markdown/listUl.js
new file mode 100644
index 0000000..e616369
--- /dev/null
+++ b/src/extension/functions/markdown/listUl.js
@@ -0,0 +1,57 @@
+const listUl = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /^( {3})*- (.*)/
+ if (selection.match(selRegex))
+ selection = selection.match(/^ {3}/)
+ ? selection.replace(/^ {3}/gm, '')
+ : selection.replace(/^- /gm, '')
+ else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/)
+ )
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }- ${selection.replace(/\n/g, '\n- ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/)
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ else if (value.substring(selectionStart - 1, selectionEnd).match(selRegex)) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*- /g, '$1 - ')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/( {3})*- /g, '$1 - ')
+ } else
+ selection = `\n- ${selection.replace(/\n/g, '\n- ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) ||
+ selectionEnd === length
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default listUl
diff --git a/src/extension/functions/markdown/quoteRight.js b/src/extension/functions/markdown/quoteRight.js
new file mode 100644
index 0000000..b8475f6
--- /dev/null
+++ b/src/extension/functions/markdown/quoteRight.js
@@ -0,0 +1,57 @@
+const quoteRight = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /^>+\s.*/gm
+ if (selection.match(selRegex))
+ selection = selection.match(/^> /gm)
+ ? selection.replace(/^> /gm, '')
+ : selection.replace(/(^>*)> /gm, '$1 ')
+ else if (
+ !selectionStart ||
+ value.substring(selectionStart - 1, selectionEnd).match(/^\n.*/)
+ )
+ selection = `${
+ value.substring(selectionStart - 1, selectionEnd).match(/\n^.*/gm) ||
+ !selectionStart
+ ? ''
+ : '\n'
+ }> ${selection.replace(/\n/g, '\n> ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/)
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ else if (value.substring(selectionStart - 1, selectionEnd).match(selRegex)) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^>*)\s/gm, '$1> ')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(/(^>*)\s/gm, '$1> ')
+ } else
+ selection = `\n> ${selection.replace(/\n/g, '\n> ')}${
+ value.substring(selectionEnd, selectionEnd + 2).match(/\n\n/) ||
+ selectionEnd === length
+ ? ''
+ : value.substring(selectionEnd, selectionEnd + 1).match(/\n/)
+ ? '\n'
+ : '\n\n'
+ }`
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default quoteRight
diff --git a/src/extension/functions/markdown/strikethrough.js b/src/extension/functions/markdown/strikethrough.js
new file mode 100644
index 0000000..a2047d3
--- /dev/null
+++ b/src/extension/functions/markdown/strikethrough.js
@@ -0,0 +1,63 @@
+const strikethrough = (
+ value,
+ length,
+ selectionStart,
+ selectionEnd,
+ selection,
+ textarea
+) => {
+ const selRegex = /~~((.*\W?)*)~~/
+ if (selection.match(selRegex)) selection = selection.replace(selRegex, '$1')
+ else if (selectionStart > 0 && selectionEnd < length) {
+ if (selection.match(selRegex)) selection.replace(selRegex, '$1')
+ else if (
+ value.substring(selectionStart - 1, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionStart--
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ value.substring(selectionStart - 2, selectionEnd + 2).match(selRegex)
+ ) {
+ selectionStart -= 2
+ selectionEnd += 2
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/~~.+~~/g)
+ ? selection.replace(/~~/g, '')
+ : `~~${selection}~~`
+ }
+ } else {
+ if (
+ !selectionStart &&
+ value.substring(selectionStart, selectionEnd + 1).match(selRegex)
+ ) {
+ selectionEnd++
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else if (
+ selectionEnd === length &&
+ value.substring(selectionStart - 1, selectionEnd).match(selRegex)
+ ) {
+ selectionStart--
+ selection = value
+ .substring(selectionStart, selectionEnd)
+ .replace(selRegex, '$1')
+ } else {
+ selection = selection.match(/~~.+~~/g)
+ ? selection.replace(/~~/g, '')
+ : `~~${selection}~~`
+ }
+ }
+ textarea.value =
+ value.substring(0, selectionStart) +
+ selection +
+ value.substring(selectionEnd, length)
+}
+
+export default strikethrough
diff --git a/src/extension/functions/pageEnhancements.js b/src/extension/functions/pageEnhancements.js
new file mode 100644
index 0000000..cc206d7
--- /dev/null
+++ b/src/extension/functions/pageEnhancements.js
@@ -0,0 +1,197 @@
+import betterPages from '../betterPages'
+import options from '../options'
+import versions from '../versions'
+import addMarkdownTools from './addMarkdownTools'
+import addStyle from './addStyle'
+import setCookie from './cookie/setCookie'
+import getNotifications from './getNotifications'
+import $ from './selector'
+import loadScriptSettings from './settings/loadScriptSettings'
+import setupNewCanvas from './setupNewCanvas'
+
+const pageEnhancements = () => {
+ loadScriptSettings()
+ if (typeof DrawceptionPlay === 'undefined') return // Firefox Greasemonkey seems to call pageEnhancements() after document.write...
+ if (document.getElementById('newcanvasyo')) return // Chrome, I'm looking at you too...
+ try {
+ const tmpuserlink = $('.player-dropdown a[href^="/player/"]')
+ const username = tmpuserlink.querySelector('strong').textContent
+ const userid = tmpuserlink.href.match(/\/player\/(\d+)\//)[1]
+ localStorage.setItem('gpe_lastSeenName', username)
+ localStorage.setItem('gpe_lastSeenId', userid)
+ } catch (e) {}
+ const currentPage = location.href.match(/drawception\.com\/([^/]+)/)
+ if (currentPage) {
+ const page = currentPage[1]
+ const functionName = `better${page.replace(page[0], page[0].toUpperCase())}`
+ if (betterPages[functionName]) betterPages[functionName]()
+ }
+
+ addStyle(
+ '.panel-user {width: auto} .panel-details img.loading {display: none}' +
+ '.gpe-wide, .gpe-wide-block {display: none}' +
+ '.gpe-btn {padding: 5px 8px; height: 28px}' +
+ '.gpe-spacer {margin-right: 7px; float:left}' +
+ '@media (min-width:992px) {.navbar-toggle,.btn-menu-player {display: none} .gpe-wide {display: inline} .gpe-wide-block {display: block}}' +
+ '@media (min-width:1200px) {.gpe-btn {padding: 5px 16px;} .gpe-spacer {margin-right: 20px;} .panel-number {left: -30px}}' +
+ '#anbtver {font-size: 10px; position:absolute; opacity:0.3; right:10px; top: 0;}' +
+ '.anbt_paneldel {position:absolute; padding:1px 6px; color:#FFF; background:#d9534f; text-decoration: none !important; right: 18px; border-radius: 5px}' +
+ '.anbt_paneldel:hover {background:#d2322d}' +
+ '.anbt_favpanel {top: 20px; font-weight: normal; padding: 0 2px}' +
+ '.anbt_favpanel:hover {color: #d9534f; cursor:pointer}' +
+ '.anbt_favedpanel {color: #d9534f; border-color: #d9534f}' +
+ '.anbt_replaypanel {top: 55px; font-weight: normal; padding: 0 8px}' +
+ '.anbt_replaypanel:hover {color: #8af; text-decoration: none}' +
+ ".anbt_owncaption:before {content: ''; display: inline-block; background: #5C5; border: 1px solid #080; width: 10px; height: 10px; border-radius: 10px; margin-right: 10px;}" +
+ '.gamepanel, .thumbpanel, .comment-body {word-wrap: break-word}' +
+ '.comment-body img {max-width: 100%}' +
+ '.forum-thread.anbt_hidden {display: none}' +
+ '.anbt_showt .forum-thread.anbt_hidden {display: block; opacity: 0.6}' +
+ ".anbt_unhidet:after {content: ' threads hidden. Show'}" +
+ ".anbt_showt .anbt_unhidet:after {content: ' threads hidden. Hide'}" +
+ ".anbt_hft:after {content: '[hide]'}" +
+ '.anbt_hft, .anbt_unhidet {padding-left: 0.4em; cursor:pointer}' +
+ ".forum-thread.anbt_hidden .anbt_hft:after {content: '[show]'}" +
+ '.anbt_threadtitle {margin: 0 0 10px}' +
+ '.avatar {box-sizing: content-box}' +
+ '.pagination {margin: 0px}' +
+ '#nav-drag {position: fixed; width: 100%; z-index: 2000}' +
+ '#header-bar-container {position: relative; width: 100%; top: 6rem}' +
+ '.wrapper {position: relative; top: 6rem}' +
+ 'footer {position: relative; top: 6rem}' +
+ '.option span:first-child {display: flex; flex-direction: row; justify-content: space-between}' +
+ '.grid-settings div[class^="grid-"] label {display: inline-flex}' +
+ 'input[type="checkbox"], input[type="radio"] {margin:4px 4px 0 0}' +
+ '@-moz-document url-prefix() {input[type="checkbox"], input[type="radio"] {margin:0 4px 0 0}}' +
+ '.tooltip {z-index: 3000;}'
+ )
+ if (options.maxCommentHeight) {
+ const maxHeight = options.maxCommentHeight
+ addStyle(
+ `.comment-holder[id]:not(:target) .comment-body {overflow-y: hidden; max-height: ${maxHeight}px; position:relative}.comment-holder[id]:not(:target) .comment-body:before{content: 'Click to read more'; position:absolute; width:100%; height:50px; left:0; top:${maxHeight -
+ 50}px;text-align: center; font-weight: bold; color: #fff; text-shadow: 0 0 2px #000; padding-top: 20px; background:linear-gradient(transparent, rgba(0,0,0,0.4))}`
+ )
+ $('.comment-body', true).forEach(comment =>
+ comment.addEventListener('click', () => {
+ if (
+ comment.clientHeight > maxHeight - 50 &&
+ location.hash.indexOf(comment) === -1
+ )
+ location.hash = `#${comment.parentNode.parentNode.id}`
+ })
+ )
+ }
+ if (options.useOldFontSize) document.body.style.fontSize = '15px'
+ if (options.useOldFont) {
+ const nunito = $("link[href*='Nunito']")
+ nunito.parentNode.removeChild(nunito)
+ addStyle(
+ "@import url('https://fonts.googleapis.com/css?family=Nunito&display=swap')"
+ )
+ }
+ if (options.anbtDarkMode) {
+ if (document.body.classList.contains('theme-night')) {
+ document.body.classList.remove('theme-night')
+ setCookie('theme-night')
+ }
+ }
+ if (options.markdownTools) addMarkdownTools()
+ if (options.newCanvas) {
+ const inSandbox = location.href.match(/drawception\.com\/sandbox\/#?(.*)/)
+ const inPlay = location.href.match(
+ /drawception\.com\/(:?contests\/)?play\/(.*)/
+ )
+ const hasCanvas = document.getElementById('canvas-holder')
+ // If created a friend game, the link won't present playable canvas
+ const hasCanvasOrGameForm = document.querySelector('.playtimer')
+ const captionContest = location.href.match(/contests\/play\//) && !hasCanvas
+ if (!captionContest && (inSandbox || (inPlay && hasCanvasOrGameForm))) {
+ setTimeout(() => setupNewCanvas(inSandbox, location.href), 1)
+ return
+ }
+ $('a[href^="/sandbox/"]', true).forEach(sandboxButton =>
+ sandboxButton.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(true, event.currentTarget.href)
+ })
+ )
+ $('a[href="/play/"]', true).forEach(playButton =>
+ playButton.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(false, event.currentTarget.href)
+ })
+ )
+ }
+ // Enhance menu for higher resolutions
+ if ($('.navbar-toggle')) {
+ const navbarToggle = $('.navbar-toggle').parentNode
+ const navbarButtonsList = [
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ ''
+ ]
+ navbarButtonsList.forEach(button => navbarToggle.appendChild($(button)))
+ // Let users with screens narrow enough so top bar isn't visible still use toggle light function
+ $('#main-menu').insertAdjacentHTML(
+ 'afterbegin',
+ ' Toggle light'
+ )
+ }
+
+ const menuPlayer = $('.btn-menu-player')
+ if (menuPlayer) {
+ const userlink = $('.player-dropdown a[href^="/player/"]').href
+ const useravatar = $('.btn-menu-player').innerHTML
+ const element = $(
+ `${useravatar}`
+ )
+ menuPlayer.parentNode.appendChild(element)
+ }
+
+ // Make new notifications actually discernable from the old ones
+ const num =
+ $('#user-notify-count') && $('#user-notify-count').textContent.trim()
+ addStyle(
+ `#user-notify-list .list-group .list-group-item .fas {color: #888}#user-notify-list .list-group .list-group-item:nth-child(-n+${num}) .fas {color: #2F5}a.wrong-order {color: #F99} div.comment-holder:target {background-color: #DFD}.comment-new a.text-muted:last-child:after {content: 'New'; color: #2F5; font-weight: bold; background-color: #183; border-radius: 9px; display: inline-block; padding: 0px 6px; margin-left: 10px;}`
+ )
+
+ // Show an error if it occurs instead of "loading forever"
+ window.getNotifications = getNotifications
+
+ let versionDisplay = `ANBT v${versions.scriptVersion}`
+ try {
+ const appver = $('script[src^="/build/app"]').src.match(/(\w+)\.js$/)[1]
+ const runtimever = $('script[src^="/build/runtime"]').src.match(
+ /(\w+)\.js$/
+ )[1]
+ versionDisplay += ` | app ${appver}`
+ if (appver !== versions.siteVersion) versionDisplay += '*'
+ versionDisplay += ` | runtime ${runtimever}`
+ if (runtimever !== versions.runtimeVersion) versionDisplay += '*!!!' // didn't break with one update, hurray
+ } catch (e) {}
+ const wrapperSection = $('.wrapper')
+ if (wrapperSection)
+ wrapperSection.appendChild($(`${versionDisplay}
`))
+
+ const linkList = [
+ 'ANBT script',
+ 'Wiki',
+ 'Chat (Discord)'
+ ]
+ $('.footer-main .list-unstyled').forEach((list, index) =>
+ list.appendChild($(linkList[index]))
+ )
+}
+
+export default pageEnhancements
diff --git a/src/extension/functions/randomGreeting.js b/src/extension/functions/randomGreeting.js
new file mode 100644
index 0000000..9a05618
--- /dev/null
+++ b/src/extension/functions/randomGreeting.js
@@ -0,0 +1,13 @@
+import globals from '../globals'
+import rot13 from './rot13'
+import simpleHash from './simpleHash'
+
+const randomGreeting = () => {
+ const change_every_half_day = Math.floor(Date.now() / (1000 * 60 * 60 * 12))
+ const rnddata = simpleHash(
+ change_every_half_day + parseInt(globals.userid, 10) + 178889
+ )
+ return rot13(globals.greetings[rnddata % globals.greetings.length])
+}
+
+export default randomGreeting
diff --git a/src/extension/functions/replay/addReplayButton.js b/src/extension/functions/replay/addReplayButton.js
new file mode 100644
index 0000000..3a5af06
--- /dev/null
+++ b/src/extension/functions/replay/addReplayButton.js
@@ -0,0 +1,27 @@
+import scrambleID from '../scrambleID'
+import $ from '../selector'
+import setupNewCanvas from '../setupNewCanvas'
+import checkForRecording from './checkForRecording'
+
+const addReplayButton = drawing => {
+ if (drawing.replayAdded) return
+ drawing.replayAdded = true
+ const { parentNode, src } = drawing
+ checkForRecording(src, () => {
+ const newid = $(`img[src='${src}']`)
+ .parentNode.querySelector('a[href^="/panel/"]')
+ .href.match(/\/panel\/[^/]+\/([^/]+)/)[1]
+ const id = newid.length >= 8 ? newid : scrambleID(parentNode.id.slice(6))
+ const replayButton = $(
+ ``
+ )
+ replayButton.addEventListener('click', event => {
+ if (event.which === 2) return
+ event.preventDefault()
+ setupNewCanvas(true, `/sandbox/#${id}`)
+ })
+ parentNode.insertAdjacentHTML('beforebegin', replayButton.outerHTML)
+ })
+}
+
+export default addReplayButton
diff --git a/src/extension/functions/replay/addReplaySign.js b/src/extension/functions/replay/addReplaySign.js
new file mode 100644
index 0000000..49ec294
--- /dev/null
+++ b/src/extension/functions/replay/addReplaySign.js
@@ -0,0 +1,24 @@
+import $ from '../selector'
+import checkForRecording from './checkForRecording'
+
+const addReplaySign = drawing => {
+ if (drawing.replayAdded) return
+ drawing.replayAdded = true
+ const panel = drawing.parentNode.parentNode
+ const { src } = drawing
+ checkForRecording(src, () => {
+ const newid = src.match(/(\w+).png$/)[1]
+ const replaySign =
+ newid.length >= 8
+ ? $(
+ ``
+ )
+ : $(
+ ''
+ )
+ panel.appendChild(replaySign)
+ // replaySign.tooltip();
+ })
+}
+
+export default addReplaySign
diff --git a/src/extension/functions/replay/checkForRecording.js b/src/extension/functions/replay/checkForRecording.js
new file mode 100644
index 0000000..3fd98ab
--- /dev/null
+++ b/src/extension/functions/replay/checkForRecording.js
@@ -0,0 +1,31 @@
+const checkForRecording = (url, success, retrying) => {
+ const request = new XMLHttpRequest()
+ request.open('GET', `${url}?anbt`, true)
+ request.responseType = 'arraybuffer'
+ request.onload = () => {
+ const buffer = request.response
+ const dataView = new window.DataView(buffer)
+ const magic = dataView.getUint32(0)
+ if (magic != 0x89504e47) return request.onerror() // Drawception started hijacking XHR errors and putting HTML in there
+ for (let i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) {
+ const chunklen = dataView.getUint32(i)
+ i += 4
+ const chunkname = dataView.getUint32(i)
+ i += 4
+ if (chunkname === 0x73764762) return success()
+ else {
+ if (chunkname === 0x49454e44) break
+ i += chunklen
+ }
+ }
+ }
+ request.onerror = () => {
+ console.log(
+ 'checkForRecording fail (likely due to cache without CORS), retrying'
+ )
+ if (!retrying) checkForRecording(`${url}?anbt`, success, true)
+ }
+ request.send()
+}
+
+export default checkForRecording
\ No newline at end of file
diff --git a/src/extension/functions/reversePanels.js b/src/extension/functions/reversePanels.js
new file mode 100644
index 0000000..9405750
--- /dev/null
+++ b/src/extension/functions/reversePanels.js
@@ -0,0 +1,10 @@
+import $ from './selector'
+
+const reversePanels = () => {
+ const element = $('.gamepanel-holder')[0].parentNode.parentNode
+ ;[...element.childNodes]
+ .reverse()
+ .forEach(child => element.appendChild(child))
+}
+
+export default reversePanels
diff --git a/src/extension/functions/rot13.js b/src/extension/functions/rot13.js
new file mode 100644
index 0000000..6f4bad6
--- /dev/null
+++ b/src/extension/functions/rot13.js
@@ -0,0 +1,11 @@
+const rot13 = number =>
+ [...number.toString()]
+ .map(character => {
+ character = character.charCodeAt(0)
+ if (character >= 97 && character <= 122) character = ((character - 97 + 13) % 26) + 97
+ if (character >= 65 && character <= 90) character = ((character - 65 + 13) % 26) + 65
+ return String.fromCharCode(character)
+ })
+ .join('')
+
+export default rot13
diff --git a/src/extension/functions/scrambleID.js b/src/extension/functions/scrambleID.js
new file mode 100644
index 0000000..f27f872
--- /dev/null
+++ b/src/extension/functions/scrambleID.js
@@ -0,0 +1,10 @@
+import decimalToBase62 from './decimalToBase62'
+
+const scrambleID = number => {
+ if (isNaN(number)) throw new Error('Invalid panel ID')
+ return [...decimalToBase62(parseInt(number, 10) + 3521614606208)]
+ .reverse()
+ .join('')
+}
+
+export default scrambleID
diff --git a/src/extension/functions/selector.js b/src/extension/functions/selector.js
new file mode 100644
index 0000000..040eab6
--- /dev/null
+++ b/src/extension/functions/selector.js
@@ -0,0 +1,8 @@
+const $ = (selector, array = false) => {
+ const elements = selector.startsWith('<')
+ ? [...new DOMParser().parseFromString(selector, 'text/html').body.children]
+ : [...document.querySelectorAll(selector)]
+ return elements.length > 1 || array ? elements : elements[0]
+}
+
+export default $
diff --git a/src/extension/functions/settings/addGroup.js b/src/extension/functions/settings/addGroup.js
new file mode 100644
index 0000000..1667bc6
--- /dev/null
+++ b/src/extension/functions/settings/addGroup.js
@@ -0,0 +1,44 @@
+import options from '../../options'
+import $ from '../selector'
+import escapeHTML from './escapeHTML'
+
+const addGroup = (name, settings) => {
+ const controlGroup = $('')
+ controlGroup.appendChild($(``))
+ settings.forEach(setting => {
+ const value = options[setting[0]]
+ const [name, type, description] = setting
+ const controls = $('')
+ if (type === 'boolean') {
+ controls.appendChild(
+ $(
+ ``
+ )
+ )
+ } else if (type === 'number') {
+ $(
+ `${description}:`
+ ).forEach(node => controls.appendChild(node))
+ } else if (type === 'longstr') {
+ $(
+ `${description}:`
+ ).forEach(node => controls.appendChild(node))
+ } else {
+ $(
+ `${description}:`
+ ).forEach(node => controls.appendChild(node))
+ }
+ controlGroup.appendChild(controls)
+ })
+ return controlGroup
+}
+
+export default addGroup
diff --git a/src/extension/functions/settings/escapeHTML.js b/src/extension/functions/settings/escapeHTML.js
new file mode 100644
index 0000000..309771b
--- /dev/null
+++ b/src/extension/functions/settings/escapeHTML.js
@@ -0,0 +1,10 @@
+const escapeHTML = value =>
+ value
+ .toString()
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+
+export default escapeHTML
\ No newline at end of file
diff --git a/src/extension/functions/settings/loadScriptSettings.js b/src/extension/functions/settings/loadScriptSettings.js
new file mode 100644
index 0000000..32171cb
--- /dev/null
+++ b/src/extension/functions/settings/loadScriptSettings.js
@@ -0,0 +1,9 @@
+import getLocalStorageItem from '../getLocalStorageItem'
+
+const loadScriptSettings = () => {
+ const result = getLocalStorageItem('gpe_anbtSettings', null)
+ if (!result) return
+ for (const i in result) window.options[i] = result[i]
+}
+
+export default loadScriptSettings
diff --git a/src/extension/functions/settings/updateScriptSettings.js b/src/extension/functions/settings/updateScriptSettings.js
new file mode 100644
index 0000000..ad8d4b9
--- /dev/null
+++ b/src/extension/functions/settings/updateScriptSettings.js
@@ -0,0 +1,23 @@
+import fadeIn from '../fade/fadeIn'
+import fadeOut from '../fade/fadeOut'
+import $ from '../selector'
+import loadScriptSettings from './loadScriptSettings'
+
+const updateScriptSettings = ({ currentTarget: theForm }) => {
+ const result = {}
+ theForm.querySelectorAll('input,textarea').forEach(fromField => {
+ if (fromField.type === 'checkbox')
+ result[fromField.name] = fromField.checked ? 1 : 0
+ else if (fromField.getAttribute('data-subtype') === 'number')
+ result[fromField.name] = parseFloat(fromField.value) || 0
+ else result[fromField.name] = fromField.value
+ })
+ localStorage.setItem('gpe_anbtSettings', JSON.stringify(result))
+ loadScriptSettings()
+ fadeIn($('#anbtSettingsOK'), 'slow')
+ setTimeout(() => {
+ fadeOut($('#anbtSettingsOK'), 'slow')
+ }, 800)
+}
+
+export default updateScriptSettings
diff --git a/src/extension/functions/setupNewCanvas.js b/src/extension/functions/setupNewCanvas.js
new file mode 100644
index 0000000..7968f1b
--- /dev/null
+++ b/src/extension/functions/setupNewCanvas.js
@@ -0,0 +1,116 @@
+import options from '../options'
+import versions from '../versions'
+import getLocalStorageItem from './getLocalStorageItem'
+
+const setupNewCanvas = (insandbox, url) => {
+ const canvasHTML = localStorage.getItem('anbt_canvasHTML')
+ const canvasHTMLver = localStorage.getItem('anbt_canvasHTMLver')
+ if (
+ !canvasHTML ||
+ canvasHTMLver < versions.newCanvasVersion ||
+ canvasHTML.length < 10000
+ ) {
+ const request = new XMLHttpRequest()
+ request.open(
+ 'GET',
+ 'https://api.github.com/repos/EnderDragonneau/Drawception-ANBT/contents/build/index.html'
+ )
+ request.setRequestHeader('Accept', 'application/vnd.github.3.raw')
+ request.onload = () => {
+ if (request.responseText.length < 10000) {
+ alert(
+ `Error: instead of new canvas code, got this response from GitHub:\n${
+ request.responseText
+ }`
+ )
+ location.pathname = '/'
+ } else {
+ localStorage.setItem('anbt_canvasHTML', request.responseText)
+ localStorage.setItem('anbt_canvasHTMLver', versions.newCanvasVersion)
+ setupNewCanvas(insandbox, url)
+ }
+ }
+ request.onerror = () => {
+ alert('Error loading the new canvas code. Please try again.')
+ location.pathname = '/'
+ }
+ request.send()
+ return
+ }
+ const inforum = url.match(/forums\//)
+ const friendgameid = url.match(/play\/(.+)\//) // Save friend game id if any
+ const panelid = url.match(/sandbox\/#?([^/]+)/)
+ const incontest =
+ url.match(/contests\/play\//) && document.getElementById('canvas-holder') // Handle drawing contests only
+ const vertitle = `ANBT v${versions.scriptVersion}`
+
+ // Disable built-in safety warning
+ if (incontest) window.onbeforeunload = () => {}
+
+ // Show normal address
+ const normalurl =
+ insandbox && !inforum
+ ? `/sandbox/${panelid ? `#${panelid[1]}` : ''}`
+ : incontest
+ ? '/contests/play/'
+ : inforum
+ ? url.match(/\/forums\/?.+/)
+ : `/play/${friendgameid ? `${friendgameid[1]}/` : ''}`
+ try {
+ if (location.pathname + location.hash !== normalurl)
+ history.pushState({}, document.title, normalurl)
+ } catch (e) {}
+
+ const alarmSoundOgg =
+ 'data:audio/ogg;base64,T2dnUwACAAAAAAAAAABnHAAAAAAAAHQUSFoBHgF2b3JiaXMAAAAAAUSsAAAAAAAAYG0AAAAAAADJAU9nZ1MAAAAAAAAAAAAAZxwAAAEAAABq35G0DxD/////////////////NQN2b3JiaXMAAAAAAAAAAAEFdm9yYmlzH0JDVgEAAAEAFGNWKWaZUpJbihlzmDFnGWPUWoolhBRCKKVzVlurKbWaWsq5xZxzzpViUilFmVJQW4oZY1IpBhlTEltpIYQUQgehcxJbaa2l2FpqObacc62VUk4ppBhTiEromFJMKaQYU4pK6Jxz0DnmnFOMSgg1lVpTyTGFlFtLKXROQgephM5SS7F0kEoHJXRQOms5lRJTKZ1jVkJquaUcU8qtpphzjIHQkFUAAAEAwEAQGrIKAFAAABCGoSiKAoSGrAIAMgAABOAojuIokiI5kmM5FhAasgoAAAIAEAAAwHAUSZEUy9EcTdIszdI8U5ZlWZZlWZZlWZZd13VdIDRkFQAAAQBAKAcZxRgQhJSyEggNWQUAIAAAAIIowxADQkNWAQAAAQAIUR4h5qGj3nvvEXIeIeYdg9577yG0XjnqoaTee++99x5777n33nvvkWFeIeehk9577xFiHBnFmXLee+8hpJwx6J2D3nvvvfeec+451957752j3kHpqdTee++Vk14x6Z2jXnvvJdUeQuqlpN5777333nvvvffee++9955777333nvvrefeau+9995777333nvvvffee++9995777333nvvgdCQVQAAEAAAYRg2iHHHpPfae2GYJ4Zp56T3nnvlqGcMegqx9557773X3nvvvffeeyA0ZBUAAAgAACGEEFJIIYUUUkghhhhiyCGHHIIIKqmkoooqqqiiiiqqLKOMMsook4wyyiyjjjrqqMPOQgoppNJKC620VFtvLdUehBBCCCGEEEIIIYQQvvceCA1ZBQCAAAAwxhhjjEEIIYQQQkgppZRiiimmmAJCQ1YBAIAAAAIAAAAsSZM0R3M8x3M8x1M8R3RER3RER5RESbRETfREUTRFVbRF3dRN3dRNXdVN27VVW7ZlXdddXddlXdZlXdd1Xdd1Xdd1Xdd1XbeB0JBVAAAIAABhkEEGGYQQQkghhZRSijHGGHPOOSA0ZBUAAAgAIAAAAEBxFEdxHMmRJMmyLM3yLM8SNVMzNVNzNVdzRVd1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'
+
+ if (inforum) {
+ if (document.querySelector('.v--modal-overlay'))
+ document.querySelector('.v--modal-overlay').outerHTML = ''
+ const div = document.querySelector('.wrapper').children[1]
+ const iframe = document.createElement('iframe')
+ const modalOverlay = document.createElement('div')
+ modalOverlay.setAttribute('class', 'v--modal-overlay')
+ iframe.id = 'iframe'
+ iframe.setAttribute('class', 'v--modal-background-click')
+ modalOverlay.appendChild(iframe)
+ div.appendChild(modalOverlay)
+ iframe.contentWindow.document.open()
+ iframe.contentWindow.anbtReady = () => {
+ iframe.contentWindow.inforum = inforum
+ iframe.contentWindow.insandbox = insandbox
+ iframe.contentWindow.incontest = incontest
+ iframe.contentWindow.options = options
+ iframe.contentWindow.alarmSoundOgg = alarmSoundOgg
+ iframe.contentWindow.vertitle = vertitle
+ iframe.contentWindow.getLocalStorageItem = getLocalStorageItem
+ iframe.contentWindow.needToGoDeeper()
+ }
+ iframe.contentWindow.document.write(canvasHTML)
+ iframe.contentWindow.document.close()
+ return
+ }
+ document.open()
+ window.anbtReady = () => {
+ if (friendgameid) window.friendgameid = friendgameid[1]
+ if (panelid) window.panelid = panelid[1]
+ window.inforum = inforum
+ window.insandbox = insandbox
+ window.incontest = incontest
+ window.options = options
+ window.alarmSoundOgg = alarmSoundOgg
+ window.vertitle = vertitle
+ window.getLocalStorageItem = getLocalStorageItem
+ window.needToGoDeeper()
+ }
+ document.write(canvasHTML)
+ document.close()
+}
+
+export default setupNewCanvas
diff --git a/src/extension/functions/simpleHash.js b/src/extension/functions/simpleHash.js
new file mode 100644
index 0000000..57cc5f6
--- /dev/null
+++ b/src/extension/functions/simpleHash.js
@@ -0,0 +1,10 @@
+const simpleHash = number =>
+ number
+ .toString()
+ .split('')
+ .reduce((a, b) => {
+ a = (a << 5) - a + b.charCodeAt(0)
+ return a & a
+ }, 0)
+
+export default simpleHash
diff --git a/src/extension/functions/time/convertForumTime.js b/src/extension/functions/time/convertForumTime.js
new file mode 100644
index 0000000..aa85a30
--- /dev/null
+++ b/src/extension/functions/time/convertForumTime.js
@@ -0,0 +1,11 @@
+import formatTimestamp from './formatTimestamp'
+import isFloridaDST from './isFloridaDST'
+
+const convertForumTime = (year, month, day, hours, minutes) => {
+ const date = new Date(year, month, day, hours, minutes)
+ const tzo = date.getTimezoneOffset() * 60 * 1000
+ const dst = isFloridaDST()
+ return formatTimestamp(date.getTime() - tzo + (6 - dst) * 60 * 60 * 1000)
+}
+
+export default convertForumTime
diff --git a/src/extension/functions/time/formatTimestamp.js b/src/extension/functions/time/formatTimestamp.js
new file mode 100644
index 0000000..bebcad9
--- /dev/null
+++ b/src/extension/functions/time/formatTimestamp.js
@@ -0,0 +1,14 @@
+import globals from '../../globals'
+import options from '../../options'
+
+const formatTimestamp = date => {
+ if (typeof date === 'number') date = new Date(date)
+ if (options.localeTimestamp) return date.toLocaleString()
+ return `${('0' + date.getDate()).slice(-2)} ${
+ globals.months[date.getMonth()]
+ } ${date.getFullYear()} ${('0' + date.getHours()).slice(-2)}:${(
+ '0' + date.getMinutes()
+ ).slice(-2)}`
+}
+
+export default formatTimestamp
diff --git a/src/extension/functions/time/isFloridaDST.js b/src/extension/functions/time/isFloridaDST.js
new file mode 100644
index 0000000..8c72163
--- /dev/null
+++ b/src/extension/functions/time/isFloridaDST.js
@@ -0,0 +1,23 @@
+const isFloridaDST = () => {
+ const date = new Date(Date.now() - 6 * 60 * 60 * 1000)
+ const month = date.getUTCMonth()
+ const day = date.getUTCDate()
+ const hours = date.getUTCHours()
+ const dayOfWeef = date.getUTCDay()
+
+ if (month < 2 || month > 10) return false
+ if (month > 2 && month < 10) return true
+ if (month === 2) {
+ if (day < 8) return false
+ if (day > 14) return true
+ if (dayOfWeef === 7) return hours > 1
+ return day > dayOfWeef + 7
+ }
+ if (month === 10) {
+ if (day > 7) return false
+ if (dayOfWeef === 7) return hours < 1
+ return day <= dayOfWeef
+ }
+}
+
+export default isFloridaDST
\ No newline at end of file
diff --git a/src/extension/functions/unscrambleID.js b/src/extension/functions/unscrambleID.js
new file mode 100644
index 0000000..5ef306e
--- /dev/null
+++ b/src/extension/functions/unscrambleID.js
@@ -0,0 +1,6 @@
+import base62ToDecimal from './base62ToDecimal'
+
+const unscrambleID = string =>
+ base62ToDecimal([...string].reverse().join('')) - 3521614606208
+
+export default unscrambleID
diff --git a/src/extension/functions/viewMyGameBookmarks.js b/src/extension/functions/viewMyGameBookmarks.js
new file mode 100644
index 0000000..87450de
--- /dev/null
+++ b/src/extension/functions/viewMyGameBookmarks.js
@@ -0,0 +1,87 @@
+import fadeOut from './fade/fadeOut'
+import getLocalStorageItem from './getLocalStorageItem'
+import $ from './selector'
+import formatTimestamp from './time/formatTimestamp'
+
+const viewMyGameBookmarks = () => {
+ const removeButtonHTML =
+ ''
+ const games = getLocalStorageItem('gpe_gameBookmarks', {})
+ const result = []
+ for (let id in games) {
+ const extraClass = games[id].own ? ' anbt_owncaption' : ''
+ if (id.length > 10) {
+ // token, seen lengths: 43, 32; just in case assuming everything > 10 is a token
+ result.push(
+ ``
+ )
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', `/play/${id}`)
+ xhr.onload = () => {
+ const { responseText, status } = xhr
+ if (status === 200) {
+ const m =
+ responseText.match(/Game is not private/) ||
+ (responseText.match(/Problem loading game/) && 'del')
+ if (m) {
+ const gamename =
+ `${
+ games[id].own
+ ? ` with your caption${
+ games[id].caption ? ` ${games[id].caption}` : ''
+ }`
+ : ''
+ }${
+ games[id].time
+ ? ` bookmarked on ${formatTimestamp(games[id].time)}`
+ : ''
+ }` || id
+ const status = m === 'del' ? 'Deleted' : 'Unfinished public'
+ $(`#${id}`).querySelector(
+ 'span'
+ ).textContent = `${status} game${gamename}`
+ return
+ }
+ const title = responseText.match(/(.+)<\/title>/)[1]
+ const [url, gameId] = responseText.match(/\/game\/([^/]+)\/[^/]+\//)
+ delete games[id]
+ games[gameId] = {
+ title,
+ url
+ }
+ $(`#${id}`).id = gameId
+ const spanId = $(`#${gameId}`).querySelector('span')
+ spanId.parentNode.replaceChild(
+ $(`${title}`),
+ spanId
+ )
+ localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
+ } else {
+ $(`#${id}`).querySelector(
+ 'span'
+ ).textContent = `Error while retrieving game: ${responseText}`
+ }
+ }
+ xhr.send()
+ } else if (id.length === 10)
+ result.push(
+ ``
+ ) // game ID
+ }
+ $('#anbt_userpage').innerHTML = result.length
+ ? result.join('')
+ : "You don't have any bookmarked games."
+ $('#anbt_userpage .anbt_gamedel', true).forEach(gameDelete =>
+ gameDelete.addEventListener('click', event => {
+ event.preventDefault()
+ const { id } = gameDelete.parentNode
+ fadeOut($(`#${id}`))
+ delete games[id]
+ localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
+ })
+ )
+}
+
+export default viewMyGameBookmarks
diff --git a/src/extension/functions/viewMyPanelFavorites.js b/src/extension/functions/viewMyPanelFavorites.js
new file mode 100644
index 0000000..1e8b750
--- /dev/null
+++ b/src/extension/functions/viewMyPanelFavorites.js
@@ -0,0 +1,52 @@
+import fadeOut from './fade/fadeOut'
+import getLocalStorageItem from './getLocalStorageItem'
+import $ from './selector'
+import formatTimestamp from './time/formatTimestamp'
+
+const viewMyPanelFavorites = () => {
+ const panels = getLocalStorageItem('gpe_panelFavorites', {})
+ let result = ''
+ let needsUpdate = false
+ for (const id in panels) {
+ if (panels[id].image && panels[id].image.match(/^\/pub\/panels\//)) {
+ needsUpdate = true
+ panels[id].image = panels[id].image.replace(
+ '/pub/panels/',
+ 'https://cdn.drawception.com/images/panels/'
+ )
+ }
+ result += ``
+ }
+
+ if (needsUpdate)
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ result = result
+ ? `${result}`
+ : "You don't have any favorited panels."
+ $('#anbt_userpage').innerHTML = result
+ $('#anbt_userpage .anbt_paneldel', true).forEach(panelDelete =>
+ panelDelete.addEventListener('click', event => {
+ event.preventDefault()
+ const { id } = panelDelete.parentNode.parentNode
+ fadeOut($(`#${CSS.escape(id)}`))
+ delete panels[id]
+ localStorage.setItem('gpe_panelFavorites', JSON.stringify(panels))
+ })
+ )
+}
+
+export default viewMyPanelFavorites
diff --git a/src/extension/globals.js b/src/extension/globals.js
new file mode 100644
index 0000000..27e6d2b
--- /dev/null
+++ b/src/extension/globals.js
@@ -0,0 +1,50 @@
+import getLocalStorageItem from './functions/getLocalStorageItem'
+import $ from './functions/selector'
+
+const globals = {
+ userId: getLocalStorageItem(
+ 'gpe_lastSeenId',
+ $('.player-dropdown a[href^="/player/"]') &&
+ $('.player-dropdown a[href^="/player/"]').href.match(
+ /\/player\/(\d+)\//
+ )[1]
+ ),
+ months: [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'Apr',
+ 'May',
+ 'Jun',
+ 'Jul',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec'
+ ],
+ alphabet: '36QtfkmuFds0UjlvCGIXZ125bEMhz48JSYgipwKn7OVHRBPoy9DLWaceqxANTr',
+ greetings: [
+ 'Oruvaq lbh!',
+ "Ubcr vg'f abg envavat gbqnl.",
+ 'Jurer vf lbhe tbq abj?',
+ 'Lbh fubhyq srry 5% zber cbjreshy abj.',
+ 'Fhqqrayl, abguvat unccrarq!',
+ '^_^',
+ 'Guvf gnxrf fb ybat gb svavfu...',
+ "Jungrire lbh qb, qba'g ernq guvf grkg.",
+ 'Pyvpx urer sbe 1 serr KC',
+ 'Or cngvrag.',
+ "Whfg qba'g fgneg nal qenzn nobhg vg.",
+ '47726S6Q2069732074686520677265617465737421',
+ 'Cynl fzneg.',
+ 'Cynl avpr.',
+ 'Fzvyr!',
+ "Qba'g sbetrg gb rng.",
+ "V xabj jung lbh'ir qbar.",
+ 'Fpvrapr!',
+ 'Gbqnl vf n tbbq qnl.'
+ ]
+}
+
+export default globals
diff --git a/src/extension/markdown.js b/src/extension/markdown.js
new file mode 100644
index 0000000..29f8304
--- /dev/null
+++ b/src/extension/markdown.js
@@ -0,0 +1,60 @@
+import bold from './functions/markdown/bold'
+import code from './functions/markdown/code'
+import heading from './functions/markdown/heading'
+import highlighter from './functions/markdown/highlighter'
+import image from './functions/markdown/image'
+import italic from './functions/markdown/italic'
+import link from './functions/markdown/link'
+import listOl from './functions/markdown/listOl'
+import listUl from './functions/markdown/listUl'
+import quoteRight from './functions/markdown/quoteRight'
+import strikethrough from './functions/markdown/strikethrough'
+
+const markdown = {
+ bold: {
+ title: 'bold text',
+ replaceFunc: bold
+ },
+ italic: {
+ title: 'italic text',
+ replaceFunc: italic
+ },
+ heading: {
+ title: 'enlarges/reduces the text',
+ replaceFunc: heading
+ },
+ strikethrough: {
+ title: 'strikethrough text',
+ replaceFunc: strikethrough
+ },
+ highlighter: {
+ title: 'highlighted text',
+ replaceFunc: highlighter
+ },
+ 'list-ul': {
+ title: 'unordered list',
+ replaceFunc: listUl
+ },
+ 'list-ol': {
+ title: 'ordered list',
+ replaceFunc: listOl
+ },
+ 'quote-right': {
+ title: 'quote',
+ replaceFunc: quoteRight
+ },
+ code: {
+ title: 'block of code',
+ replaceFunc: code
+ },
+ link: {
+ title: 'insert link',
+ replaceFunc: link
+ },
+ image: {
+ title: 'insert image',
+ replaceFunc: image
+ }
+}
+
+export default markdown
diff --git a/src/extension/options.js b/src/extension/options.js
new file mode 100644
index 0000000..aff30d0
--- /dev/null
+++ b/src/extension/options.js
@@ -0,0 +1,32 @@
+const options = {
+ enableWacom: 0, // Whether to enable Wacom plugin and thus pressure sensitivity support
+ fixTabletPluginGoingAWOL: 1, // Fix pressure sensitivity disappearing in case of stupid/old Wacom plugin
+ hideCross: 0, // Whether to hide the cross when drawing
+ enterToCaption: 0, // Whether to submit caption by pressing Enter
+ pressureExponent: 0.5, // Smaller = softer tablet response, bigger = sharper
+ backup: 1,
+ timeoutSound: 0,
+ timeoutSoundBlitz: 0,
+ timeoutSoundVolume: 100,
+ newCanvas: 1,
+ proxyImgur: 0,
+ ajaxRetry: 1,
+ localeTimestamp: 0,
+ autoplay: 1, // Whether to automatically start playback of a recorded drawing
+ submitConfirm: 1,
+ smoothening: 1,
+ autoBypassNSFW: 0,
+ colorNumberShortcuts: 1,
+ colorUnderCursorHint: 1,
+ bookmarkOwnCaptions: 0,
+ colorDoublePress: 0,
+ newCanvasCSS: '',
+ forumHiddenUsers: '',
+ maxCommentHeight: 1000,
+ useOldFont: true,
+ useOldFontSize: true,
+ markdownTools: 1,
+ anbtDarkMode: 1
+}
+
+export default options
\ No newline at end of file
diff --git a/src/extension/versions.js b/src/extension/versions.js
new file mode 100644
index 0000000..f73286d
--- /dev/null
+++ b/src/extension/versions.js
@@ -0,0 +1,7 @@
+const versions = {
+ scriptVersion: "1.156.2019.06",
+ newCanvasVersion: 53, // Increase to update the cached canvas
+ siteVersion: "f6b35cce", // Last seen site version
+ runtimeVersion: "1ba6bf05" // Last seen runtime version
+}
+export default versions
diff --git a/src/extension/wrapper.js b/src/extension/wrapper.js
new file mode 100644
index 0000000..4c3ff2f
--- /dev/null
+++ b/src/extension/wrapper.js
@@ -0,0 +1,21 @@
+import pageEnhancements from './functions/pageEnhancements'
+import options from './options'
+
+const wrapper = () => {
+ window.options = options
+ const mark = document.createElement('b')
+ mark.id = '_anbt_'
+ mark.style.display = 'none'
+ document.body.appendChild(mark)
+
+ if (!window.DrawceptionPlay) {
+ // Fix for Chrome new loading algorithm, apparently
+ const loader = setInterval(() => {
+ if (!window.DrawceptionPlay) return
+ pageEnhancements()
+ clearInterval(loader)
+ }, 100)
+ } else pageEnhancements()
+}
+
+export default wrapper
diff --git a/src/newcanvas.js b/src/newcanvas.js
new file mode 100644
index 0000000..839e8ba
--- /dev/null
+++ b/src/newcanvas.js
@@ -0,0 +1,15 @@
+import './newcanvas/css/style.scss'
+import anbt from './newcanvas/js/anbt'
+import bindEvents from './newcanvas/js/functions/bindEvents'
+import ID from './newcanvas/js/functions/idSelector'
+import needToGoDeeper from './newcanvas/js/functions/needToGoDeeper'
+import updateTimer from './newcanvas/js/functions/updateTimer'
+import globals from './newcanvas/js/globals'
+
+window.needToGoDeeper = needToGoDeeper
+if (!window.options) window.options = {}
+anbt.bindContainer(ID('svgContainer'))
+bindEvents()
+globals.timerStart = Date.now()
+setInterval(updateTimer, 500)
+if (window.anbtReady) window.anbtReady()
diff --git a/src/newcanvas/css/style.scss b/src/newcanvas/css/style.scss
new file mode 100644
index 0000000..3f2811e
--- /dev/null
+++ b/src/newcanvas/css/style.scss
@@ -0,0 +1,832 @@
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol,
+ul {
+ list-style: none;
+}
+blockquote,
+q {
+ quotes: none;
+}
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+ content: "";
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+body {
+ margin: 0;
+ background: #ccc;
+ font-family: "Nunito", sans-serif;
+ background: #555;
+}
+.noselect {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+#newcanvasyo {
+ background: #555;
+ &.play .onlysandbox,
+ &.play .onlyfriend,
+ &.friend .no-friend,
+ &.nsfw .no-nsfw,
+ &.sandbox .onlycaption,
+ &.drawing .onlycaption,
+ &.captioning .onlydraw,
+ &.locked .onlyunlocked,
+ &.sandbox .onlyplay {
+ display: none;
+ }
+}
+#myheader {
+ font-size: 15pt;
+ line-height: 49px;
+ height: 49px;
+ border-bottom: 1px solid black;
+ background: #333;
+ text-align: center;
+ color: #999;
+ overflow: hidden;
+ display: block;
+ img {
+ vertical-align: middle;
+ }
+ @media screen and (max-width: 999px) {
+ font-size: 11pt;
+ }
+}
+#headercont {
+ max-width: 1280px;
+ margin: auto;
+}
+.headerleft {
+ float: left;
+ margin-left: 50px;
+}
+.headerright {
+ float: right;
+ margin-right: 50px;
+}
+#infoavatar {
+ border: 1px solid #555;
+}
+.headerbutton {
+ font-size: 10.5pt;
+ font-weight: bold;
+ padding: 4px 10px;
+ background: #2e2e2e;
+ vertical-align: middle;
+ text-decoration: none;
+ cursor: pointer;
+ &.active {
+ background: #248;
+ color: #eee;
+ &:hover {
+ background: #236;
+ }
+ }
+ img {
+ margin: 0 3px;
+ }
+}
+a {
+ color: #999;
+ &.prominent {
+ color: #7af;
+ &:hover {
+ color: #9ff;
+ }
+ }
+}
+#menu {
+ display: none;
+ position: absolute;
+ background: #666;
+ color: #ddd;
+ z-index: 2;
+ border: 2px solid #777;
+ box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
+ list-style-type: none;
+ padding: 0;
+ margin: 0;
+ font-size: 13pt;
+ text-align: left;
+ &.open {
+ display: block;
+ }
+ li {
+ line-height: 49px;
+ height: 49px;
+ padding: 0 10px;
+ cursor: pointer;
+ &:hover {
+ background: #777;
+ }
+ }
+ hr {
+ margin: 5px;
+ border: 1px solid #444;
+ }
+}
+hr {
+ border: 1px solid #888;
+}
+#noscriptwarn {
+ color: #800;
+ padding: 10px;
+ font-size: 15pt;
+ background: #faa;
+ text-align: center;
+}
+#drawthis {
+ padding: 4px;
+ color: #fff;
+ font-size: 26pt;
+ font-family: "Yanone Kaffeesatz";
+ text-align: center;
+ vertical-align: middle;
+ display: table-cell;
+ width: 1%;
+ &::before {
+ color: #aaa;
+ font-size: 14pt;
+ font-family: "Nunito";
+ content: "Draw this:";
+ padding-right: 1em;
+ width: 1%;
+ text-align: right;
+ vertical-align: middle;
+ margin-left: -10%;
+ }
+}
+.captioning {
+ #drawthis:before {
+ content: "Describe this drawing:";
+ padding-right: 0;
+ width: 100%;
+ text-align: center;
+ margin-left: 0;
+ }
+ #drawingtocaption {
+ display: block;
+ }
+}
+#emptytitle {
+ height: 10px;
+}
+#bl {
+ width: 600px;
+ margin: auto;
+ position: relative;
+}
+#toolpane {
+ text-align: center;
+ padding: 0 10px;
+ @media screen and (min-width: 1000px) {
+ width: 180px;
+ height: 500px;
+ position: absolute;
+ margin-left: -200px;
+ }
+ @media screen and (max-width: 999px) {
+ width: 580px;
+ margin: auto;
+ }
+ &:after {
+ @media screen and (max-width: 999px) {
+ display: block;
+ content: "";
+ clear: both;
+ }
+ }
+}
+#infopanel {
+ text-align: center;
+ padding: 0 10px;
+ @media screen and (min-width: 1000px) {
+ width: 180px;
+ height: 500px;
+ position: absolute;
+ margin-left: 600px;
+ top: 0;
+ }
+ @media screen and (max-width: 999px) {
+ width: 580px;
+ margin: 40px 0;
+ }
+}
+.panel {
+ border: 2px solid #888;
+ border-radius: 15px;
+ border-top: none;
+ background: #444;
+ margin: 3px 0;
+}
+#timer {
+ font-size: 26pt;
+ color: #aaa;
+ width: 176px;
+ float: left;
+ @media screen and (max-width: 999px) {
+ float: right;
+ width: 260px;
+ }
+}
+.blitz #timer {
+ border-color: #dda;
+ color: #dda;
+ background: #982;
+}
+.locked {
+ #timer {
+ border-color: #faa;
+ color: #faa;
+ background: #744;
+ }
+ #lockwarn {
+ display: block;
+ }
+}
+#timeplus {
+ background: url("/img/icon-time_plus_60.png") center no-repeat;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+ padding: 10px;
+ margin-left: 3px;
+ border-radius: 3px;
+ cursor: pointer;
+ &:hover {
+ background-color: #222;
+ }
+}
+#palettechooser {
+ display: none;
+ position: absolute;
+ width: 450px;
+ background: #666;
+ color: #ddd;
+ z-index: 1;
+ -webkit-columns: 3;
+ -moz-columns: 3;
+ columns: 3;
+ -webkit-column-gap: 0;
+ -moz-column-gap: 0;
+ column-gap: 0;
+ border: 2px solid #777;
+ box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
+ &.open {
+ display: block;
+ }
+ div {
+ padding: 10px 0 20px;
+ cursor: pointer;
+ break-inside: avoid;
+ &:hover {
+ background-color: #777;
+ color: #eee;
+ }
+ }
+}
+#colors {
+ width: 100px;
+ float: left;
+ b {
+ float: left;
+ display: block;
+ width: 50px;
+ height: 44px;
+ line-height: 42px;
+ cursor: pointer;
+ counter-increment: color;
+ &.hint {
+ &::after {
+ content: "";
+ position: absolute;
+ border-radius: 50%;
+ margin: 32px 2px;
+ width: 5px;
+ height: 5px;
+ background-color: white;
+ border: 1px solid #000;
+ }
+ &::before {
+ opacity: 0.6;
+ }
+ }
+ &::before {
+ font-size: 10pt;
+ opacity: 0.3;
+ color: #000;
+ content: counter(color);
+ }
+ &:hover {
+ &::before {
+ opacity: 0.4;
+ color: #fff;
+ }
+ }
+ &:nth-child(10) {
+ counter-reset: color 1;
+ }
+ &:nth-child(n + 10) {
+ counter-increment: color;
+ &::before {
+ content: "\2191"counter(color);
+ }
+ }
+ }
+ #eraser {
+ &::before {
+ content: "eraser";
+ }
+ }
+ &.setbackground {
+ b {
+ &::before {
+ content: "BG " counter(color);
+ }
+ &:nth-child(n + 10) {
+ &::before {
+ content: "BG\2191"counter(color);
+ }
+ }
+ }
+ #eraser {
+ &::before {
+ content: "cancel";
+ }
+ }
+ }
+ div {
+ clear: both;
+ }
+ @media screen and (max-width: 999px) {
+ width: 300px;
+ margin-left: 5px;
+ clear: left;
+ float: left;
+ }
+}
+#palettename {
+ color: #aaa;
+ font-size: 10pt;
+ padding: 0 5px;
+ cursor: pointer;
+ &:hover {
+ color: #ccc;
+ }
+}
+#tools {
+ width: 68px;
+ float: right;
+ padding: 5px 0;
+ hr {
+ width: 93%;
+ margin: 8px auto;
+ }
+ button {
+ @media screen and (max-width: 999px) {
+ margin: 0;
+ }
+ }
+ @media screen and (max-width: 999px) {
+ width: 260px;
+ }
+}
+#tools button,
+#openmenu {
+ width: 55px;
+ height: 44px;
+ vertical-align: top;
+ margin: 3px 0;
+ padding: 0;
+}
+#brush0,
+#brush1,
+#brush2,
+#brush3,
+#setbackground,
+#undo,
+#redo,
+#trash,
+#play,
+#knob,
+#eraser,
+.eraser,
+#openmenu {
+ background-image: url("data:image/png;base64,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");
+}
+#brush0 {
+ background-position: -2px -8px;
+ &.sel {
+ background-position: -2px -68px;
+ }
+}
+#brush1 {
+ background-position: -62px -8px;
+ &.sel {
+ background-position: -62px -68px;
+ }
+}
+#brush2 {
+ background-position: -122px -8px;
+ &.sel {
+ background-position: -122px -68px;
+ }
+}
+#brush3 {
+ background-position: -182px -8px;
+ &.sel {
+ background-position: -182px -68px;
+ }
+}
+#setbackground {
+ background-position: -242px -8px;
+ &.sel {
+ background-position: -242px -68px;
+ }
+}
+#undo {
+ background-position: -302px -8px;
+}
+#redo {
+ background-position: -362px -8px;
+}
+#trash {
+ background-position: -422px -8px;
+}
+#eraser {
+ background-position: -365px -68px;
+ background-color: pink;
+}
+.eraser {
+ background-position: -425px -68px;
+ background-color: pink;
+}
+#openmenu {
+ background-position: -425px -68px;
+ background-color: #999;
+ &::after {
+ content: "\25BC";
+ color: #fff;
+ opacity: 0.6;
+ }
+ &:hover {
+ background-color: #57a;
+ }
+ &:active {
+ background-color: #555;
+ }
+}
+button,
+#play {
+ border: none;
+ border-radius: 4px;
+ background-color: #eee;
+ &:hover {
+ background-color: #acf;
+ }
+ &:active {
+ background-color: #aaa;
+ }
+}
+button:disabled {
+ background-color: #aaa;
+}
+#primary,
+#secondary {
+ display: inline-block;
+ width: 50px;
+ border-radius: 0 0 0 13px;
+ color: #fff;
+ @media screen and (max-width: 999px) {
+ width: 150px;
+ }
+}
+#secondary {
+ border-radius: 0 0 13px 0;
+}
+#indicator {
+ display: flex;
+}
+#gamemode {
+ font-size: 13pt;
+ color: #aaa;
+ width: 176px;
+ float: left;
+}
+.nsfw {
+ #gamemode {
+ border-color: #faa;
+ color: #faa;
+ background: #744;
+ }
+ #drawthis {
+ background: #744;
+ }
+}
+#gamebuttons {
+ font-size: 13pt;
+ color: #aaa;
+ width: 176px;
+ float: left;
+ padding: 5px 0;
+ button {
+ padding: 10px;
+ margin: 3px 0;
+ font-size: 10pt;
+ }
+ @media screen and (max-width: 999px) {
+ width: 390px;
+ margin-left: 5px;
+ }
+}
+.submit {
+ font-size: 16pt;
+ width: 180px;
+ height: 50px;
+ background-color: #6d6;
+ &:hover {
+ background-color: #1b7;
+ }
+ @media screen and (min-width: 1000px) {
+ position: absolute;
+ bottom: 10px;
+ left: 10px;
+ width: 180px;
+ height: 50px;
+ }
+}
+.guidelines {
+ color: #fff;
+ font-size: 10pt;
+ ul {
+ margin: 0;
+ padding: 0 1em;
+ text-align: left;
+ }
+ li {
+ color: #bbb;
+ }
+}
+#seekbar {
+ width: 530px;
+ height: 0px;
+ margin: 20px 50px 0;
+ background: #333;
+ border: 10px solid #333;
+ border-radius: 10px;
+ cursor: pointer;
+}
+#knob {
+ position: absolute;
+ width: 48px;
+ height: 38px;
+ margin-top: -19px;
+ margin-left: 492px; /* -10px min */
+ border-radius: 10px;
+ background-color: #fbb;
+ background-position: -305px -71px;
+ &.smooth {
+ transition: margin-left 0.1s ease-out;
+ }
+ &:hover {
+ background-color: #fdd;
+ }
+}
+#play {
+ background-position: -486px -11px;
+ position: absolute;
+ width: 48px;
+ height: 38px;
+ margin: -19px -59px;
+ border-radius: 4px;
+ &.pause {
+ background-position: -546px -11px;
+ }
+}
+#wacomContainer {
+ position: absolute;
+ top: -10px;
+ left: -10px;
+}
+.hideonsmall {
+ @media screen and (max-width: 999px) {
+ display: none;
+ }
+}
+.loadingbg {
+ background: #fffdc9
+ url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGwAAAAVCAQAAAAJQsNPAAAAkUlEQVR4Xu2WgQbAMBBD9/8/vQ14KpC4UneqYurZ2eNGnnfouWLdsjwce59ikFCdk75ricGIQSEmhekcvRfE8s8jMJ9CdI7camLGYoQ0XzNjJukgRsaJkWFiKqhiLf8x2DAx6EExTV0Mns/sIPZnnBinJJb1h4p21BXrzSOpTZVF3VGCN4lB07U53+6H5Yr1zAdssNOdvbG/lgAAAABJRU5ErkJggg==")
+ center no-repeat;
+}
+#svgContainer {
+ width: 600px;
+ height: 500px;
+ margin: auto;
+ cursor: crosshair;
+ touch-action: pinch-zoom;
+ &.hidecursor {
+ cursor: none;
+ }
+ &.loading * {
+ display: none;
+ }
+ canvas,
+ svg {
+ position: absolute;
+ }
+}
+#drawingtocaption {
+ display: none;
+ width: 550px;
+ height: 400px;
+ padding: 50px 25px;
+ margin: auto;
+ background: #484848;
+ text-align: center;
+}
+#tocaption {
+ width: 300px;
+ height: 250px;
+}
+#caption {
+ font-family: "Nunito", sans-serif;
+ margin-top: 100px;
+ width: 100%;
+ font-size: 20pt;
+}
+#usedchars {
+ text-align: right;
+ opacity: 0.4;
+}
+#lockwarn {
+ display: none;
+ position: absolute;
+ width: 500px;
+ height: 400px;
+ padding: 50px;
+ opacity: 0.8;
+ background: #aaa;
+ color: #a00;
+ text-align: center;
+ z-index: 1;
+ font-size: 35pt;
+ text-shadow: 1px 1px 1px #fff, -1px -1px 1px #fff, -1px 1px 1px #fff, 1px -1px 1px #fff;
+}
+#popup {
+ display: none;
+ position: absolute;
+ width: 400px;
+ height: 300px;
+ margin: 50px;
+ padding: 50px;
+ opacity: 0.95;
+ background: #444;
+ color: #ddd;
+ border-radius: 15px;
+ text-align: center;
+ z-index: 1;
+ font-size: 20pt;
+ &.show {
+ display: block;
+ }
+}
+#popuptitle {
+ color: #999;
+ font-size: 25pt;
+ margin: 10px;
+}
+#popupclose {
+ display: block;
+ position: absolute;
+ width: 50px;
+ height: 50px;
+ background: #c44;
+ color: #ddd;
+ right: 0;
+ top: 0;
+ text-align: center;
+ border-radius: 13px;
+ cursor: pointer;
+ &:hover {
+ background: #e77;
+ color: #eee;
+ }
+ &::before {
+ content: "\d7";
+ font-size: 30pt;
+ line-height: 40px;
+ }
+}
diff --git a/src/newcanvas/html/index.html b/src/newcanvas/html/index.html
new file mode 100644
index 0000000..3140894
--- /dev/null
+++ b/src/newcanvas/html/index.html
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+ New Canvas - Drawception
+
+
+
+
+
+ Fetching your Drawception game...
+
+
+
+
+
Time's up!
Submit now or miss the game!
+
+
+
![]()
+
+
+
46
+
+
+
+
+ Sandbox
+ Public safe for work game
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Guidelines
+
+ - Simple drawings are welcome! Just give it your best shot
+ - If the phrase is too difficult, use the skip button
+ - Don't draw text - always draw a picture
+ - Explicit (NSFW) drawings will get you banned
+
+
+
Tips
+
+ - No explicit language allowed
+ - Keep descriptions short and simple!
+ - Avoid unnecessary detail, just describe the main subject
+ - No metacommentary (e.g., don't comment on drawing quality)
+ - No idea? Use the skip button
+
+
+
Sandbox
+
+ - Welcome to the sandbox! Here you can play with all the palettes and drawing tools.
+ - Saved drawings will contain your drawing process in PNG format.
+ - After loading a drawing you can continue to draw or watch its playback.
+ - Note that editing the PNG with playback with other programs will likely destroy the playback data.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/newcanvas/js/anbt.js b/src/newcanvas/js/anbt.js
new file mode 100644
index 0000000..172211c
--- /dev/null
+++ b/src/newcanvas/js/anbt.js
@@ -0,0 +1,120 @@
+import addToSvg from './functions/anbt/addToSvg'
+import bindContainer from './functions/anbt/bindContainer'
+import clearWithColor from './functions/anbt/clearWithColor'
+import cutHistoryBeforeClearAndAfterPosition from './functions/anbt/cutHistoryBeforeClearAndAfterPosition'
+import drawDispLine from './functions/anbt/drawDispLine'
+import drawDispLinePresto from './functions/anbt/drawDispLinePresto'
+import drawSvgElement from './functions/anbt/drawSvgElement'
+import eyedropper from './functions/anbt/eyedropper'
+import findLastRect from './functions/anbt/findLastRect'
+import fromLocalFile from './functions/anbt/fromLocalFile'
+import fromPng from './functions/anbt/fromPng'
+import fromUrl from './functions/anbt/fromUrl'
+import getSeekMax from './functions/anbt/getSeekMax'
+import lock from './functions/anbt/lock'
+import makePng from './functions/anbt/makePng'
+import moveCursor from './functions/anbt/moveCursor'
+import moveSeekbar from './functions/anbt/moveSeekbar'
+import packPlayback from './functions/anbt/packPlayback'
+import pause from './functions/anbt/pause'
+import play from './functions/anbt/play'
+import playTimer from './functions/anbt/playTimer'
+import redo from './functions/anbt/redo'
+import requestSave from './functions/anbt/requestSave'
+import seek from './functions/anbt/seek'
+import setBackground from './functions/anbt/setBackground'
+import setColor from './functions/anbt/setColor'
+import setSeekbarMove from './functions/anbt/setSeekbarMove'
+import setSize from './functions/anbt/setSize'
+import showEyedropperCursor from './functions/anbt/showEyedropperCursor'
+import strokeAdd from './functions/anbt/strokeAdd'
+import strokeBegin from './functions/anbt/strokeBegin'
+import strokeEnd from './functions/anbt/strokeEnd'
+import undo from './functions/anbt/undo'
+import unlock from './functions/anbt/unlock'
+import unpackPlayback from './functions/anbt/unpackPlayback'
+import updateView from './functions/anbt/updateView'
+import uploadToImgur from './functions/anbt/uploadToImgur'
+import createSvgElement from './functions/createSvgElement'
+import palettes from './palettes'
+
+const anbt = {
+ container: null,
+ svg: createSvgElement('svg', {
+ // Even though Opera complains to have failed to set xmlns attribute:
+ // > Failed attribute on svg element: xmlns="http://www.w3.org/2000/svg".
+ // this is necessary for loading a saved SVG which otherwise wouldn't
+ // bind correct prototypes for functions such as path.pathSegList
+ xmlns: 'http://www.w3.org/2000/svg',
+ version: '1.1',
+ width: '600',
+ height: '500'
+ }),
+ canvas: document.createElement('canvas'),
+ canvasDisp: document.createElement('canvas'),
+ svgDisp: createSvgElement('svg', {
+ //xmlns: "http://www.w3.org/2000/svg",
+ version: '1.1',
+ width: '600',
+ height: '500',
+ 'pointer-events': 'none'
+ }),
+ svgHist: null,
+ path: null,
+ points: null,
+ pngBase64: null,
+ lastrect: 0,
+ position: 0,
+ isStroking: false,
+ isPlaying: false,
+ size: 14,
+ smoothening: 1,
+ palette: palettes.Normal,
+ patternCache: {},
+ delay: 100,
+ unsaved: false,
+ background: '#fffdc9',
+ transparent: false,
+ colors: ['#000000', 'eraser'],
+ fastUndoLevels: 10,
+ rewindCache: [],
+ bindContainer,
+ packPlayback,
+ unpackPlayback,
+ findLastRect,
+ cutHistoryBeforeClearAndAfterPosition,
+ makePng,
+ fromPng,
+ fromUrl,
+ fromLocalFile,
+ setBackground,
+ setColor,
+ setSize,
+ drawSvgElement,
+ updateView,
+ drawDispLinePresto,
+ drawDispLine,
+ strokeBegin,
+ strokeEnd,
+ strokeAdd,
+ clearWithColor,
+ addToSvg,
+ undo,
+ redo,
+ moveSeekbar,
+ setSeekbarMove,
+ getSeekMax,
+ seek,
+ play,
+ playTimer,
+ pause,
+ moveCursor,
+ showEyedropperCursor,
+ eyedropper,
+ requestSave,
+ uploadToImgur,
+ lock,
+ unlock
+}
+
+export default anbt
diff --git a/src/newcanvas/js/functions/anbt/addToSvg.js b/src/newcanvas/js/functions/anbt/addToSvg.js
new file mode 100644
index 0000000..8648f23
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/addToSvg.js
@@ -0,0 +1,19 @@
+import anbt from '../../anbt'
+import drawSvgElement from './drawSvgElement'
+import moveSeekbar from './moveSeekbar'
+
+const addToSvg = element => {
+ if (anbt.rewindCache.length >= anbt.fastUndoLevels) anbt.rewindCache.pop()
+ anbt.rewindCache.unshift(anbt.ctx.getImageData(0, 0, 600, 500))
+ drawSvgElement(element)
+ if (!anbt.timeedit || anbt.position === anbt.svg.childNodes.length - 1) {
+ // Remove everything past current position
+ for (let i = anbt.svg.childNodes.length - 1; i > anbt.position; i--)
+ anbt.svg.removeChild(anbt.svg.childNodes[i])
+ anbt.svg.appendChild(element)
+ anbt.position = anbt.svg.childNodes.length - 1
+ moveSeekbar(1)
+ } else anbt.svg.insertBefore(element, anbt.svg.childNodes[anbt.position + 1])
+}
+
+export default addToSvg
diff --git a/src/newcanvas/js/functions/anbt/bindContainer.js b/src/newcanvas/js/functions/anbt/bindContainer.js
new file mode 100644
index 0000000..388f37d
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/bindContainer.js
@@ -0,0 +1,31 @@
+import anbt from '../../anbt'
+import createSvgElement from '../createSvgElement'
+
+const bindContainer = element => {
+ anbt.container = element
+ anbt.canvas.width = 600
+ anbt.canvas.height = 500
+ anbt.canvas.style.background = anbt.background
+ anbt.ctx = anbt.canvas.getContext('2d')
+ anbt.ctx.lineJoin = anbt.ctx.lineCap = 'round'
+ anbt.container.appendChild(anbt.canvas)
+ if (!navigator.userAgent.match(/\bPresto\b/)) {
+ anbt.canvasDisp.width = 600
+ anbt.canvasDisp.height = 500
+ anbt.ctxDisp = anbt.canvasDisp.getContext('2d')
+ anbt.ctxDisp.lineJoin = anbt.ctxDisp.lineCap = 'round'
+ anbt.container.appendChild(anbt.canvasDisp)
+ } else anbt.DrawDispLine = anbt.DrawDispLinePresto // Opera Presto is faster with SVG redrawing
+ anbt.container.appendChild(anbt.svgDisp)
+ const rect = createSvgElement('rect', {
+ class: 'eraser',
+ x: 0,
+ y: 0,
+ width: 600,
+ height: 500,
+ fill: anbt.background
+ })
+ anbt.svg.appendChild(rect)
+}
+
+export default bindContainer
diff --git a/src/newcanvas/js/functions/anbt/clearWithColor.js b/src/newcanvas/js/functions/anbt/clearWithColor.js
new file mode 100644
index 0000000..3ad60c0
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/clearWithColor.js
@@ -0,0 +1,19 @@
+import anbt from '../../anbt'
+import createSvgElement from '../createSvgElement'
+import addToSvg from './addToSvg'
+
+const clearWithColor = color => {
+ addToSvg(
+ createSvgElement('rect', {
+ class: color,
+ x: 0,
+ y: 0,
+ width: 600,
+ height: 500,
+ fill: anbt.background
+ })
+ )
+ anbt.lastrect = anbt.position
+}
+
+export default clearWithColor
diff --git a/src/newcanvas/js/functions/anbt/cutHistoryBeforeClearAndAfterPosition.js b/src/newcanvas/js/functions/anbt/cutHistoryBeforeClearAndAfterPosition.js
new file mode 100644
index 0000000..aa5cb33
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/cutHistoryBeforeClearAndAfterPosition.js
@@ -0,0 +1,17 @@
+import anbt from '../../anbt'
+
+const cutHistoryBeforeClearAndAfterPosition = () => {
+ let removing = false
+ for (let i = anbt.svg.childNodes.length - 1; i > 0; i--) {
+ const element = anbt.svg.childNodes[i]
+ if (removing || i > anbt.position) anbt.svg.removeChild(element)
+ else if (element.nodeName === 'rect' && i <= anbt.position) {
+ removing = true
+ // Optimize out two eraser rectangles next to each other
+ if (element.getAttribute('class') === 'eraser')
+ anbt.svg.removeChild(element)
+ }
+ }
+}
+
+export default cutHistoryBeforeClearAndAfterPosition
diff --git a/src/newcanvas/js/functions/anbt/drawDispLine.js b/src/newcanvas/js/functions/anbt/drawDispLine.js
new file mode 100644
index 0000000..3f69f70
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/drawDispLine.js
@@ -0,0 +1,15 @@
+import anbt from '../../anbt'
+
+const drawDispLine = (x1, y1, x2, y2) => {
+ const { ctxDisp } = anbt
+ // var c = this.lastcolor;
+ // ctx.strokeStyle = anbt.pattern ? anbt.MakePattern(c, anbt.pattern) : c;
+ ctxDisp.strokeStyle = anbt.lastcolor
+ ctxDisp.lineWidth = anbt.size
+ ctxDisp.beginPath()
+ ctxDisp.moveTo(x1, y1)
+ ctxDisp.lineTo(x2, y2)
+ ctxDisp.stroke()
+}
+
+export default drawDispLine
diff --git a/src/newcanvas/js/functions/anbt/drawDispLinePresto.js b/src/newcanvas/js/functions/anbt/drawDispLinePresto.js
new file mode 100644
index 0000000..66c4370
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/drawDispLinePresto.js
@@ -0,0 +1,7 @@
+import anbt from '../../anbt';
+
+const drawDispLinePresto = first => {
+ if (first) anbt.svgDisp.insertBefore(anbt.path, anbt.svgDisp.firstChild)
+}
+
+export default drawDispLinePresto
diff --git a/src/newcanvas/js/functions/anbt/drawSvgElement.js b/src/newcanvas/js/functions/anbt/drawSvgElement.js
new file mode 100644
index 0000000..3b3cd5d
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/drawSvgElement.js
@@ -0,0 +1,35 @@
+import anbt from '../../anbt'
+
+const drawSvgElement = (element, ctx) => {
+ if (!ctx) ctx = anbt.ctx
+ ctx.globalCompositeOperation =
+ element.getAttribute('class') === 'eraser'
+ ? 'destination-out'
+ : 'source-over'
+ if (element.nodeName === 'path') {
+ //var c = el.getAttribute("stroke");
+ //ctx.strokeStyle = el.pattern ? anbt.MakePattern(c, el.pattern) : c;
+ ctx.strokeStyle = element.getAttribute('stroke')
+ ctx.lineWidth = element.getAttribute('stroke-width')
+ ctx.beginPath()
+ for (let i = 0; i < element.pathSegList.numberOfItems; i++) {
+ const seg = element.pathSegList.getItem(i)
+ if (seg.pathSegTypeAsLetter === 'M') ctx.moveTo(seg.x, seg.y)
+ else if (seg.pathSegTypeAsLetter === 'L') ctx.lineTo(seg.x, seg.y)
+ else if (seg.pathSegTypeAsLetter === 'Q')
+ ctx.quadraticCurveTo(seg.x1, seg.y1, seg.x, seg.y)
+ else if (seg.pathSegTypeAsLetter === 'C')
+ ctx.bezierCurveTo(seg.x1, seg.y1, seg.x2, seg.y2, seg.x, seg.y)
+ }
+ ctx.stroke()
+ } else if (element.nodeName === 'rect') {
+ ctx.fillStyle = element.getAttribute('fill')
+ const x = element.getAttribute('x')
+ const y = element.getAttribute('y')
+ const width = element.getAttribute('width')
+ const height = element.getAttribute('height')
+ ctx.fillRect(x, y, width, height)
+ }
+}
+
+export default drawSvgElement
diff --git a/src/newcanvas/js/functions/anbt/eyedropper.js b/src/newcanvas/js/functions/anbt/eyedropper.js
new file mode 100644
index 0000000..5ae88b6
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/eyedropper.js
@@ -0,0 +1,11 @@
+import anbt from '../../anbt'
+import getClosestColor from '../getClosestColor'
+
+const eyedropper = (x, y) => {
+ const pixelColor = anbt.ctx.getImageData(x, y, 1, 1).data
+ return pixelColor[3] > 0
+ ? getClosestColor(pixelColor, anbt.palette)
+ : anbt.background
+}
+
+export default eyedropper
diff --git a/src/newcanvas/js/functions/anbt/findLastRect.js b/src/newcanvas/js/functions/anbt/findLastRect.js
new file mode 100644
index 0000000..3998f84
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/findLastRect.js
@@ -0,0 +1,12 @@
+import anbt from '../../anbt'
+
+const findLastRect = endPosition => {
+ if (!endPosition) endPosition = anbt.svg.childNodes.length - 1
+ for (let i = endPosition; i > 0; i--) {
+ const element = anbt.svg.childNodes[i]
+ if (element.nodeName === 'rect') return i
+ }
+ return 0
+}
+
+export default findLastRect
diff --git a/src/newcanvas/js/functions/anbt/fromLocalFile.js b/src/newcanvas/js/functions/anbt/fromLocalFile.js
new file mode 100644
index 0000000..3f9081b
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/fromLocalFile.js
@@ -0,0 +1,26 @@
+import anbt from '../../anbt'
+import fromPng from './fromPng'
+
+const fromLocalFile = () => {
+ if (!anbt.fileInput) {
+ anbt.fileInput = document.createElement('input')
+ anbt.fileInput.style.position = 'absolute'
+ anbt.fileInput.style.top = '-1000px'
+ anbt.fileInput.type = 'file'
+ anbt.fileInput.accept = '.png'
+ document.body.appendChild(anbt.fileInput)
+ anbt.fileInput.addEventListener(
+ 'change',
+ event => {
+ const reader = new FileReader()
+ reader.onload = () => fromPng(reader.result)
+ if (event.currentTarget.files[0])
+ reader.readAsArrayBuffer(event.currentTarget.files[0])
+ },
+ false
+ )
+ }
+ anbt.fileInput.click()
+}
+
+export default fromLocalFile
diff --git a/src/newcanvas/js/functions/anbt/fromPng.js b/src/newcanvas/js/functions/anbt/fromPng.js
new file mode 100644
index 0000000..2be46a2
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/fromPng.js
@@ -0,0 +1,36 @@
+import anbt from '../../anbt'
+import packUint32be from '../pack/packUint32be'
+import moveSeekbar from './moveSeekbar'
+import setBackground from './setBackground'
+import unpackPlayback from './unpackPlayback'
+import updateView from './updateView'
+
+const fromPng = buffer => {
+ const dv = new DataView(buffer)
+ const magic = dv.getUint32(0)
+ if (magic !== 0x89504e47)
+ throw new Error(`Invalid PNG format: ${packUint32be(magic)}`)
+ for (let i = 8; i < buffer.byteLength; i += 4 /* Skip CRC */) {
+ const chunklen = dv.getUint32(i)
+ i += 4
+ const chunkname = packUint32be(dv.getUint32(i))
+ i += 4
+ if (chunkname === 'svGb') {
+ anbt.svg = unpackPlayback(new Uint8Array(buffer, i, chunklen))
+ anbt.lastrect = 0
+ anbt.rewindCache.length = 0
+ anbt.position = anbt.svg.childNodes.length - 1
+ updateView()
+ moveSeekbar(1)
+ // Here we assume first element of svg is background rect
+ setBackground(anbt.svg.background)
+ return
+ } else {
+ if (chunkname === 'IEND') break
+ i += chunklen
+ }
+ }
+ throw new Error('No vector data found!')
+}
+
+export default fromPng
diff --git a/src/newcanvas/js/functions/anbt/fromUrl.js b/src/newcanvas/js/functions/anbt/fromUrl.js
new file mode 100644
index 0000000..c058f6a
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/fromUrl.js
@@ -0,0 +1,12 @@
+import fromPng from './fromPng'
+
+const fromUrl = url => {
+ const xhr = new XMLHttpRequest()
+ xhr.open('GET', url, true)
+ if ('responseType' in xhr) xhr.responseType = 'arraybuffer'
+ else return alert('Your browser is too old for this')
+ xhr.onload = () => fromPng(xhr.response)
+ xhr.send()
+}
+
+export default fromUrl
diff --git a/src/newcanvas/js/functions/anbt/getSeekMax.js b/src/newcanvas/js/functions/anbt/getSeekMax.js
new file mode 100644
index 0000000..6519bc9
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/getSeekMax.js
@@ -0,0 +1,5 @@
+import anbt from '../../anbt'
+
+const getSeekMax = () => anbt.svg.childNodes.length - 1
+
+export default getSeekMax
diff --git a/src/newcanvas/js/functions/anbt/lock.js b/src/newcanvas/js/functions/anbt/lock.js
new file mode 100644
index 0000000..4df8e28
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/lock.js
@@ -0,0 +1,11 @@
+import anbt from '../../anbt'
+import moveCursor from './moveCursor'
+import strokeEnd from './strokeEnd'
+
+const lock = () => {
+ if (anbt.isStroking) strokeEnd()
+ anbt.locked = true
+ moveCursor(-100, -100)
+}
+
+export default lock
diff --git a/src/newcanvas/js/functions/anbt/makePng.js b/src/newcanvas/js/functions/anbt/makePng.js
new file mode 100644
index 0000000..8fee57d
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/makePng.js
@@ -0,0 +1,54 @@
+import anbt from '../../anbt'
+import crc32 from '../crc32'
+import packUint32be from '../pack/packUint32be'
+import cutHistoryBeforeClearAndAfterPosition from './cutHistoryBeforeClearAndAfterPosition'
+import drawSvgElement from './drawSvgElement'
+import moveSeekbar from './moveSeekbar'
+import packPlayback from './packPlayback'
+
+const makePng = (width, height, fromBuffer) => {
+ // Cut all needless SVG data that comes before clearing whole canvas
+ cutHistoryBeforeClearAndAfterPosition()
+ moveSeekbar(1)
+
+ const canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height
+ const context = canvas.getContext('2d')
+ if (!anbt.transparent) {
+ context.fillStyle = anbt.background
+ context.fillRect(0, 0, width, height)
+ }
+ if (fromBuffer) context.drawImage(anbt.canvas, 0, 0, width, height)
+ else {
+ context.lineJoin = context.lineCap = 'round'
+ context.scale(width / 600, height / 500)
+ for (let i = 0; i < anbt.svg.childNodes.length; i++) {
+ drawSvgElement(anbt.svg.childNodes[i], context)
+ }
+ }
+ anbt.pngBase64 = canvas.toDataURL('image/png')
+
+ const version = 'svGb'
+ const svgString = packPlayback(anbt.svg)
+ const padding = anbt.pngBase64.substr(-2)
+ // To append the custom chunk, we need to decode the end of the base64-encoded PNG
+ // and then reattach as btoa((prepend) + (custom data) + (iend)).
+ // As base64 encoding chunks are 3 bytes, iend chunk can start in the middle of those,
+ // so (prepend) contains the data before iend chunk that we had to cut.
+ const cut = padding === '==' ? 1 : padding[1] === '=' ? 2 : 3
+ const indexEnd = atob(anbt.pngBase64.substr(-20)).substr(cut)
+ const prepend = atob(anbt.pngBase64.substr(-20)).substr(0, cut)
+ const custom = [
+ prepend,
+ packUint32be(svgString.length),
+ version,
+ svgString,
+ packUint32be(crc32(version, svgString)),
+ indexEnd
+ ].join('')
+ anbt.pngBase64 =
+ anbt.pngBase64.substr(0, anbt.pngBase64.length - 20) + btoa(custom)
+}
+
+export default makePng
diff --git a/src/newcanvas/js/functions/anbt/moveCursor.js b/src/newcanvas/js/functions/anbt/moveCursor.js
new file mode 100644
index 0000000..5376e49
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/moveCursor.js
@@ -0,0 +1,44 @@
+import anbt from '../../anbt'
+import createSvgElement from '../createSvgElement'
+
+const moveCursor = (x, y) => {
+ if (anbt.locked) return
+ if (!anbt.brushCursor) {
+ anbt.brushCursor = createSvgElement('circle', {
+ 'stroke-width': '1',
+ stroke: '#000',
+ fill: 'none'
+ })
+ anbt.svgDisp.appendChild(anbt.brushCursor)
+ anbt.brushCursor2 = createSvgElement('circle', {
+ 'stroke-width': '1',
+ stroke: '#fff',
+ fill: 'none'
+ })
+ anbt.svgDisp.appendChild(anbt.brushCursor2)
+ anbt.eyedropperCursor = createSvgElement('image', {
+ width: 16,
+ height: 16,
+ visibility: 'hidden'
+ })
+ anbt.eyedropperCursor.setAttributeNS(
+ 'http://www.w3.org/1999/xlink',
+ 'href',
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAARklEQVR4XoXRwQoAIAgEUf//pzeGDgq5G3PrCQqVbIAqsDz9WM2qhTX4GZgPV+JpSFxAC0PwbeVZZIpMgXvAMwoj4U9B3wGySxvzk6ZjvwAAAABJRU5ErkJggg=='
+ )
+ anbt.svgDisp.appendChild(anbt.eyedropperCursor)
+ }
+ // Assume just size change if called with no parameters
+ if (typeof x !== 'undefined') {
+ anbt.brushCursor.setAttribute('cx', x)
+ anbt.brushCursor.setAttribute('cy', y)
+ anbt.brushCursor2.setAttribute('cx', x)
+ anbt.brushCursor2.setAttribute('cy', y)
+ anbt.eyedropperCursor.setAttribute('x', x - 1)
+ anbt.eyedropperCursor.setAttribute('y', y - 15)
+ }
+ anbt.brushCursor.setAttribute('r', anbt.size / 2 + 0.5)
+ anbt.brushCursor2.setAttribute('r', anbt.size / 2 - 0.5)
+}
+
+export default moveCursor
diff --git a/src/newcanvas/js/functions/anbt/moveSeekbar.js b/src/newcanvas/js/functions/anbt/moveSeekbar.js
new file mode 100644
index 0000000..20c19d4
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/moveSeekbar.js
@@ -0,0 +1,7 @@
+import anbt from '../../anbt'
+
+const moveSeekbar = position => {
+ if (anbt.seekbarMove) anbt.seekbarMove(position)
+}
+
+export default moveSeekbar
diff --git a/src/newcanvas/js/functions/anbt/packPlayback.js b/src/newcanvas/js/functions/anbt/packPlayback.js
new file mode 100644
index 0000000..31bb3e3
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/packPlayback.js
@@ -0,0 +1,63 @@
+import anbt from '../../anbt';
+import bytesToStrings from '../conversions/bytesToStrings';
+import colorToDword from '../conversions/colorToDword';
+import stringToBytes from '../conversions/stringToBytes';
+import packUint16be from '../pack/packUint16be';
+
+const packPlayback = svg => {
+ const { pako } = window
+ const array = [colorToDword(anbt.background)]
+ const last = {
+ color: colorToDword('#000000'),
+ size: 14,
+ x: -1,
+ y: -1,
+ pattern: 0
+ }
+ svg.childNodes.forEach(element => {
+ if (element.nodeName === 'path') {
+ const color =
+ element.getAttribute('class') === 'eraser'
+ ? '\xFF\xFF\xFF\x00'
+ : colorToDword(element.getAttribute('stroke'))
+ const size = element.getAttribute('stroke-width')
+ const pattern = element.pattern || 0
+ if (color !== last.color || size !== last.size) {
+ array.push(packUint16be(-1))
+ array.push(packUint16be(size * 100))
+ array.push(color)
+ last.color = color
+ last.size = size
+ }
+ if (pattern !== last.pattern) {
+ array.push(packUint16be(-3))
+ array.push(packUint16be(pattern))
+ array.push('\x00\x00\x00\x00') // reserved for the future
+ last.pattern = pattern
+ }
+ last.x = element.orig[0].x
+ last.y = element.orig[0].y
+ array.push(packUint16be(last.x))
+ array.push(packUint16be(last.y))
+ for (let j = 1; j < element.orig.length; j++) {
+ const dx = element.orig[j].x - last.x
+ const dy = element.orig[j].y - last.y
+ // Ignore repeating points
+ if (!dx && !dy) continue
+ array.push(packUint16be(dx))
+ array.push(packUint16be(dy))
+ last.x = element.orig[j].x
+ last.y = element.orig[j].y
+ }
+ array.push('\x00\x00\x00\x00')
+ } else if (element.nodeName === 'rect') {
+ const color = colorToDword(element.getAttribute('fill'))
+ array.push(packUint16be(-2))
+ array.push(packUint16be(0)) // reserved for the future
+ array.push(color)
+ } else throw new Error('Unknown node name: ' + element.nodeName)
+ })
+ return '\x04' + bytesToStrings(pako.deflate(stringToBytes(array.join(''))))
+}
+
+export default packPlayback
diff --git a/src/newcanvas/js/functions/anbt/pause.js b/src/newcanvas/js/functions/anbt/pause.js
new file mode 100644
index 0000000..83654be
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/pause.js
@@ -0,0 +1,19 @@
+import anbt from '../../anbt'
+import drawSvgElement from './drawSvgElement'
+import moveSeekbar from './moveSeekbar'
+
+const pause = noSeekbar => {
+ if (anbt.isPlaying) {
+ if (anbt.isAnimating) {
+ anbt.isAnimating = false
+ anbt.svgDisp.removeChild(anbt.path)
+ drawSvgElement(anbt.animatePath)
+ anbt.position++
+ if (!noSeekbar)
+ moveSeekbar(anbt.position / (anbt.svg.childNodes.length - 1))
+ }
+ anbt.isPlaying = false
+ }
+}
+
+export default pause
diff --git a/src/newcanvas/js/functions/anbt/play.js b/src/newcanvas/js/functions/anbt/play.js
new file mode 100644
index 0000000..89b6dbc
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/play.js
@@ -0,0 +1,20 @@
+import anbt from '../../anbt'
+import drawSvgElement from './drawSvgElement'
+import moveSeekbar from './moveSeekbar'
+import playTimer from './playTimer'
+
+const play = () => {
+ if (anbt.locked) return
+ anbt.rewindCache.length = 0 // TODO: make rewind data remember its position
+ if (anbt.position === anbt.svg.childNodes.length - 1) {
+ if (anbt.position === 0) return moveSeekbar(1) // To make button revert to play
+ anbt.position = 0
+ moveSeekbar(0)
+ // Assume first svg child is background rect
+ drawSvgElement(anbt.svg.childNodes[0])
+ }
+ anbt.isPlaying = true
+ playTimer()
+}
+
+export default play
diff --git a/src/newcanvas/js/functions/anbt/playTimer.js b/src/newcanvas/js/functions/anbt/playTimer.js
new file mode 100644
index 0000000..4351470
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/playTimer.js
@@ -0,0 +1,71 @@
+import anbt from '../../anbt'
+import drawSvgElement from './drawSvgElement'
+import moveSeekbar from './moveSeekbar'
+import pause from './pause'
+
+const playTimer = () => {
+ if (!anbt.isPlaying) return
+ const posmax = anbt.svg.childNodes.length - 1
+ let { delay } = anbt
+ let maxidx = 0
+ if (anbt.position < posmax || anbt.isAnimating) {
+ if (anbt.isAnimating) {
+ maxidx = anbt.animatePath.pathSegList.numberOfItems - 1
+ if (anbt.animateIndex < maxidx) {
+ // There doesn't seem to be a simplier way to copy the pathSeg
+ const segment = anbt.animatePath.pathSegList.getItem(anbt.animateIndex)
+ const newSegment =
+ segment.pathSegTypeAsLetter === 'L'
+ ? anbt.path.createSVGPathSegLinetoAbs(segment.x, segment.y)
+ : segment.pathSegTypeAsLetter === 'Q'
+ ? anbt.path.createSVGPathSegCurvetoQuadraticAbs(
+ segment.x,
+ segment.y,
+ segment.x1,
+ segment.y1
+ )
+ : segment.pathSegTypeAsLetter === 'C' &&
+ anbt.path.createSVGPathSegCurvetoCubicAbs(
+ segment.x,
+ segment.y,
+ segment.x1,
+ segment.y1,
+ segment.x2,
+ segment.y2
+ )
+ anbt.path.pathSegList.appendItem(newSegment)
+ anbt.animateIndex++
+ } else {
+ anbt.isAnimating = false
+ anbt.svgDisp.removeChild(anbt.path)
+ drawSvgElement(anbt.animatePath)
+ anbt.position++
+ anbt.animateIndex = 0
+ }
+ delay /= 6
+ } else {
+ const element = anbt.svg.childNodes[anbt.position + 1]
+ if (element.nodeName === 'path') {
+ anbt.isAnimating = true
+ anbt.animatePath = element
+ anbt.animateIndex = 1
+ anbt.path = element.cloneNode(true)
+ const segment = element.pathSegList.getItem(0)
+ anbt.path.pathSegList.initialize(
+ anbt.path.createSVGPathSegMovetoAbs(segment.x, segment.y)
+ )
+ anbt.svgDisp.insertBefore(anbt.path, anbt.svgDisp.firstChild)
+ } else {
+ drawSvgElement(element)
+ anbt.position++
+ }
+ }
+ }
+ moveSeekbar(
+ (anbt.position + (maxidx ? anbt.animateIndex / maxidx : 0)) / posmax
+ )
+ if (anbt.position < posmax) setTimeout(anbt.playTimer, delay)
+ else pause()
+}
+
+export default playTimer
diff --git a/src/newcanvas/js/functions/anbt/redo.js b/src/newcanvas/js/functions/anbt/redo.js
new file mode 100644
index 0000000..185fd36
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/redo.js
@@ -0,0 +1,14 @@
+import anbt from '../../anbt'
+import moveSeekbar from './moveSeekbar'
+import seek from './seek'
+
+const redo = () => {
+ if (anbt.locked) return
+ var posmax = anbt.svg.childNodes.length - 1
+ if (anbt.position < posmax) {
+ seek(anbt.position + 1)
+ moveSeekbar(anbt.position / posmax)
+ }
+}
+
+export default redo
diff --git a/src/newcanvas/js/functions/anbt/requestSave.js b/src/newcanvas/js/functions/anbt/requestSave.js
new file mode 100644
index 0000000..c5772ee
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/requestSave.js
@@ -0,0 +1,33 @@
+import anbt from '../../anbt'
+
+const requestSave = (dataUrl, extension) => {
+ if (!dataUrl) {
+ dataUrl = anbt.pngBase64
+ extension = '.png'
+ anbt.unsaved = false
+ }
+ if (!anbt.saveLink) {
+ anbt.saveLink = document.createElement('a')
+ document.body.appendChild(anbt.saveLink)
+ }
+ if ('download' in anbt.saveLink) {
+ anbt.saveLink.href = dataUrl
+ const date = new Date()
+ anbt.saveLink.download = [
+ 'DrawingInTime_',
+ date.getFullYear(),
+ '_',
+ (101 + date.getMonth() + '').slice(-2),
+ (100 + date.getDate() + '').slice(-2),
+ '_',
+ (100 + date.getHours() + '').slice(-2),
+ (100 + date.getMinutes() + '').slice(-2),
+ (100 + date.getSeconds() + '').slice(-2),
+ extension
+ ].join('')
+ anbt.saveLink.click()
+ } else window.open(dataUrl)
+ return true
+}
+
+export default requestSave
diff --git a/src/newcanvas/js/functions/anbt/seek.js b/src/newcanvas/js/functions/anbt/seek.js
new file mode 100644
index 0000000..48d761b
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/seek.js
@@ -0,0 +1,46 @@
+import anbt from '../../anbt';
+import drawSvgElement from './drawSvgElement';
+import findLastRect from './findLastRect';
+import pause from './pause';
+
+const seek = newPosition => {
+ if (anbt.locked) return
+ let start = -1
+ pause(true)
+ if (newPosition === anbt.position) return
+ if (newPosition < anbt.position) {
+ const rewindSteps = anbt.position - newPosition
+ if (rewindSteps <= anbt.rewindCache.length) {
+ // Draw from cached
+ anbt.ctx.putImageData(anbt.rewindCache[rewindSteps - 1], 0, 0)
+ anbt.rewindCache.splice(0, rewindSteps)
+ } else {
+ // Not cached; rebuild cache
+ start = 0
+ if (anbt.lastrect <= newPosition) start = anbt.lastrect
+ else start = findLastRect(newPosition)
+ drawSvgElement(anbt.svg.childNodes[start])
+ }
+ } else if (newPosition > anbt.position) start = anbt.position
+ if (start !== -1) {
+ const forwardSteps = newPosition - start
+ if (forwardSteps >= anbt.fastUndoLevels) anbt.rewindCache.length = 0
+ else {
+ // Ex: 3 cached, 10 max, 8 steps to play => delete 1 from the end
+ const { length } = anbt.rewindCache
+ const numRemove = Math.min(
+ length,
+ newPosition - start + length - anbt.fastUndoLevels
+ )
+ anbt.rewindCache.splice(length - numRemove, numRemove)
+ }
+ for (let i = start + 1; i <= newPosition; i++) {
+ if (newPosition - i < anbt.fastUndoLevels)
+ anbt.rewindCache.unshift(anbt.ctx.getImageData(0, 0, 600, 500))
+ drawSvgElement(anbt.svg.childNodes[i])
+ }
+ }
+ anbt.position = newPosition
+}
+
+export default seek
diff --git a/src/newcanvas/js/functions/anbt/setBackground.js b/src/newcanvas/js/functions/anbt/setBackground.js
new file mode 100644
index 0000000..ecefed8
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/setBackground.js
@@ -0,0 +1,18 @@
+import anbt from '../../anbt'
+import colorToHex from '../conversions/colorToHex'
+
+const setBackground = color => {
+ const transparent = color === 'eraser'
+ anbt.transparent = transparent
+ anbt.canvas.style.background = transparent ? 'none' : color
+ // Normalize the color representation
+ color = transparent ? '#ffffff' : colorToHex(color)
+ anbt.background = color
+ anbt.svg
+ .querySelectorAll('.eraser')
+ .forEach(erased =>
+ erased.setAttribute(erased.nodeName === 'path' ? 'stroke' : 'fill', color)
+ )
+}
+
+export default setBackground
diff --git a/src/newcanvas/js/functions/anbt/setColor.js b/src/newcanvas/js/functions/anbt/setColor.js
new file mode 100644
index 0000000..d6a0cf7
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/setColor.js
@@ -0,0 +1,5 @@
+import anbt from '../../anbt'
+
+const setColor = (number, color) => (anbt.colors[number] = color)
+
+export default setColor
diff --git a/src/newcanvas/js/functions/anbt/setSeekbarMove.js b/src/newcanvas/js/functions/anbt/setSeekbarMove.js
new file mode 100644
index 0000000..4663225
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/setSeekbarMove.js
@@ -0,0 +1,5 @@
+import anbt from '../../anbt';
+
+const setSeekbarMove = func => (anbt.seekbarMove = func)
+
+export default setSeekbarMove
diff --git a/src/newcanvas/js/functions/anbt/setSize.js b/src/newcanvas/js/functions/anbt/setSize.js
new file mode 100644
index 0000000..92dec7e
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/setSize.js
@@ -0,0 +1,9 @@
+import anbt from '../../anbt'
+import moveCursor from './moveCursor'
+
+const setSize = size => {
+ anbt.size = size
+ moveCursor()
+}
+
+export default setSize
diff --git a/src/newcanvas/js/functions/anbt/showEyedropperCursor.js b/src/newcanvas/js/functions/anbt/showEyedropperCursor.js
new file mode 100644
index 0000000..f6366f9
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/showEyedropperCursor.js
@@ -0,0 +1,12 @@
+import anbt from '../../anbt'
+
+const showEyedropperCursor = isEyedropper => {
+ if (!anbt.brushCursor) return
+ const vis = isEyedropper ? 'hidden' : 'visible'
+ const vis2 = isEyedropper ? 'visible' : 'hidden'
+ anbt.brushCursor.setAttribute('visibility', vis)
+ anbt.brushCursor2.setAttribute('visibility', vis)
+ anbt.eyedropperCursor.setAttribute('visibility', vis2)
+}
+
+export default showEyedropperCursor
diff --git a/src/newcanvas/js/functions/anbt/strokeAdd.js b/src/newcanvas/js/functions/anbt/strokeAdd.js
new file mode 100644
index 0000000..2064c59
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/strokeAdd.js
@@ -0,0 +1,20 @@
+import anbt from '../../anbt'
+import drawDispLine from './drawDispLine'
+import drawDispLinePresto from './drawDispLinePresto'
+
+const strokeAdd = (x, y) => {
+ if (anbt.locked) return
+ if (!anbt.isStroking) throw new Error('StrokeAdd without StrokeBegin!')
+ const point = anbt.points[anbt.points.length - 1]
+ if (point.x === x && point.y === y) return
+ if (anbt.blot) {
+ anbt.path.pathSegList.removeItem(1)
+ anbt.blot = false
+ }
+ anbt.path.pathSegList.appendItem(anbt.path.createSVGPathSegLinetoAbs(x, y))
+ if (navigator.userAgent.match(/\bPresto\b/)) drawDispLinePresto(false)
+ else drawDispLine(point.x, point.y, x, y)
+ anbt.points.push({ x, y })
+}
+
+export default strokeAdd
diff --git a/src/newcanvas/js/functions/anbt/strokeBegin.js b/src/newcanvas/js/functions/anbt/strokeBegin.js
new file mode 100644
index 0000000..c625f10
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/strokeBegin.js
@@ -0,0 +1,36 @@
+import anbt from '../../anbt'
+import createSvgElement from '../createSvgElement'
+import drawDispLine from './drawDispLine'
+import drawDispLinePresto from './drawDispLinePresto'
+
+const strokeBegin = (x, y, left) => {
+ if (anbt.locked) return
+ if (!left) left = anbt.lastleft
+ else anbt.lastleft = left
+ let color = left ? anbt.colors[0] : anbt.colors[1]
+ const cls = color === 'eraser' ? color : null
+ color = color === 'eraser' ? anbt.background : color
+ anbt.path = createSvgElement('path', {
+ class: cls,
+ stroke: color,
+ 'stroke-width': anbt.size,
+ 'stroke-linejoin': 'round',
+ 'stroke-linecap': 'round',
+ fill: 'none'
+ })
+ anbt.lastcolor = color
+ anbt.path.pattern = anbt.pattern
+ //anbt.svgDisp.insertBefore(anbt.path, anbt.svgDisp.firstChild);
+ anbt.path.pathSegList.appendItem(anbt.path.createSVGPathSegMovetoAbs(x, y))
+ anbt.path.pathSegList.appendItem(
+ anbt.path.createSVGPathSegLinetoAbs(x, y + 0.001)
+ )
+ if (navigator.userAgent.match(/\bPresto\b/)) drawDispLinePresto(true)
+ else drawDispLine(x, y, x, y + 0.001)
+ anbt.points = []
+ anbt.points.push({ x, y })
+ anbt.blot = true
+ anbt.isStroking = true
+}
+
+export default strokeBegin
diff --git a/src/newcanvas/js/functions/anbt/strokeEnd.js b/src/newcanvas/js/functions/anbt/strokeEnd.js
new file mode 100644
index 0000000..debbfd5
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/strokeEnd.js
@@ -0,0 +1,18 @@
+import anbt from '../../anbt'
+import buildSmoothPath from '../buildSmoothPath'
+import simplifyDouglasPeucker from '../simplifyDouglasPeucker'
+import addToSvg from './addToSvg'
+
+const strokeEnd = () => {
+ if (anbt.locked) return
+ anbt.unsaved = true
+ const points =
+ anbt.points.length > 2 ? simplifyDouglasPeucker(anbt) : anbt.points
+ buildSmoothPath(points, anbt.path)
+ anbt.path.orig = points
+ addToSvg(anbt.path)
+ anbt.ctxDisp && anbt.ctxDisp.clearRect(0, 0, 600, 500)
+ anbt.isStroking = false
+}
+
+export default strokeEnd
diff --git a/src/newcanvas/js/functions/anbt/undo.js b/src/newcanvas/js/functions/anbt/undo.js
new file mode 100644
index 0000000..7121876
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/undo.js
@@ -0,0 +1,14 @@
+import anbt from '../../anbt'
+import moveSeekbar from './moveSeekbar'
+import seek from './seek'
+
+const undo = () => {
+ if (anbt.locked) return
+ // Prevent "undoing" the background rectangle
+ if (anbt.position > 0) {
+ seek(anbt.position - 1)
+ moveSeekbar(anbt.position / (anbt.svg.childNodes.length - 1))
+ }
+}
+
+export default undo
diff --git a/src/newcanvas/js/functions/anbt/unlock.js b/src/newcanvas/js/functions/anbt/unlock.js
new file mode 100644
index 0000000..6354086
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/unlock.js
@@ -0,0 +1,5 @@
+import anbt from '../../anbt'
+
+const unlock = () => (anbt.locked = false)
+
+export default unlock
diff --git a/src/newcanvas/js/functions/anbt/unpackPlayback.js b/src/newcanvas/js/functions/anbt/unpackPlayback.js
new file mode 100644
index 0000000..eb1eccc
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/unpackPlayback.js
@@ -0,0 +1,117 @@
+import buildSmoothPath from '../buildSmoothPath'
+import stringToBytes from '../conversions/stringToBytes'
+import createSvgElement from '../createSvgElement'
+import int16be from '../int16be'
+
+const unpackPlayback = bytes => {
+ const { pako } = window
+ const version = bytes[0]
+ let start
+ if (version === 4) {
+ bytes = pako.inflate(bytes.subarray(1))
+ start = 0
+ } else if (version === 3) {
+ bytes = stringToBytes(pako.inflate(bytes.subarray(1), { to: 'string' }))
+ start = 0
+ } else if (version === 2) start = 1
+ else throw new Error(`Unsupported version: ${version}`)
+ const svg = createSvgElement('svg', {
+ xmlns: 'http://www.w3.org/2000/svg',
+ version: '1.1',
+ width: 600,
+ height: 500
+ })
+ const last = {
+ color: '#000000',
+ size: 14,
+ x: 0,
+ y: 0,
+ pattern: 0
+ }
+ const points = []
+ // Ignore background alpha
+ const background = `rgb(${bytes[start]}, ${bytes[start + 1]}, ${
+ bytes[start + 2]
+ })`
+ svg.background = background
+
+ svg.appendChild(
+ createSvgElement('rect', {
+ class: 'eraser',
+ x: 0,
+ y: 0,
+ width: 600,
+ height: 500,
+ fill: background
+ })
+ )
+
+ for (let i = start + 4; i < bytes.length; ) {
+ let x = int16be(bytes[i], bytes[i + 1])
+ i += 2
+ let y = int16be(bytes[i], bytes[i + 1])
+ i += 2
+ if (points.length) {
+ if (!x && !y) {
+ const path = createSvgElement('path', {
+ class: last.color === 'eraser' ? last.color : null,
+ stroke: last.color === 'eraser' ? background : last.color,
+ 'stroke-width': last.size,
+ 'stroke-linejoin': 'round',
+ 'stroke-linecap': 'round',
+ fill: 'none'
+ })
+ // Restore blots
+ if (points.length === 1) {
+ path.pathSegList.appendItem(
+ path.createSVGPathSegMovetoAbs(last.x, last.y)
+ )
+ path.pathSegList.appendItem(
+ path.createSVGPathSegLinetoAbs(last.x, last.y + 0.001)
+ )
+ } else buildSmoothPath(points, path)
+ path.orig = points
+ path.pattern = last.pattern
+ svg.appendChild(path)
+ points.length = 0
+ } else {
+ last.x = x += last.x
+ last.y = y += last.y
+ points.push({ x, y })
+ }
+ } else {
+ if (x < 0) {
+ if (x === -1 || x === -2) {
+ last.color = `rgba(${bytes[i]}, ${bytes[i + 1]}, ${
+ bytes[i + 2]
+ }, ${bytes[i + 3] / 255}`
+ // TODO: fix ugly code
+ if (last.color === 'rgba(255,255,255,0)') last.color = 'eraser'
+ i += 4
+ if (x === -1) last.size = y / 100
+ else
+ svg.appendChild(
+ createSvgElement('rect', {
+ class: last.color === 'eraser' ? last.color : null,
+ x: 0,
+ y: 0,
+ width: 600,
+ height: 500,
+ fill: last.color === 'eraser' ? background : last.color
+ })
+ )
+ } else if (x === -3) {
+ last.pattern = y
+ i += 4
+ }
+ } else {
+ points.push({ x, y })
+ last.x = x
+ last.y = y
+ }
+ }
+ }
+ return svg
+}
+
+export default unpackPlayback
diff --git a/src/newcanvas/js/functions/anbt/updateView.js b/src/newcanvas/js/functions/anbt/updateView.js
new file mode 100644
index 0000000..ae47023
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/updateView.js
@@ -0,0 +1,9 @@
+import anbt from '../../anbt'
+import drawSvgElement from './drawSvgElement'
+
+const updateView = () =>
+ [...anbt.svg.childNodes]
+ .splice(anbt.lastrect < anbt.position ? anbt.lastrect : 0)
+ .forEach(child => drawSvgElement(child))
+
+export default updateView
diff --git a/src/newcanvas/js/functions/anbt/uploadToImgur.js b/src/newcanvas/js/functions/anbt/uploadToImgur.js
new file mode 100644
index 0000000..16f4dc2
--- /dev/null
+++ b/src/newcanvas/js/functions/anbt/uploadToImgur.js
@@ -0,0 +1,44 @@
+import anbt from '../../anbt'
+import base64ToBytes from '../conversions/base64ToBytes'
+
+const uploadToImgur = callback => {
+ const request = new XMLHttpRequest()
+ request.open('POST', 'https://api.imgur.com/3/image')
+ request.onload = () => {
+ let response = request.responseText
+ try {
+ response = JSON.parse(response)
+ } catch (e) {}
+ if (response.success) {
+ // To set description
+ const request2 = new XMLHttpRequest()
+ request2.open(
+ 'POST',
+ 'https://api.imgur.com/3/image/' + response.data.deletehash
+ )
+ request2.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af')
+ const formData = new FormData()
+ formData.append(
+ 'description',
+ 'Playback: http://grompe.org.ru/drawit/#' + response.data.id
+ )
+ request2.send(formData)
+ }
+ callback(response)
+ }
+ request.onerror = error => callback(`error: ${error}`)
+ request.setRequestHeader('Authorization', 'Client-ID 4809db83c8897af')
+ const formData = new FormData()
+ formData.append(
+ 'image',
+ new Blob([base64ToBytes(anbt.pngBase64.substr(22)).buffer], {
+ type: 'image/png'
+ })
+ )
+ formData.append('type', 'file')
+ formData.append('title', 'Made with Drawing in Time')
+ formData.append('description', 'http://grompe.org.ru/drawit/')
+ request.send(formData)
+}
+
+export default uploadToImgur
diff --git a/src/newcanvas/js/functions/bindEvents.js b/src/newcanvas/js/functions/bindEvents.js
new file mode 100644
index 0000000..de70b7a
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents.js
@@ -0,0 +1,72 @@
+import globals from '../globals'
+import setSeekbarMove from './anbt/setSeekbarMove'
+import changeBrushSize from './bindEvents/changeBrushSize'
+import clickRedo from './bindEvents/clickRedo'
+import clickSetBackground from './bindEvents/clickSetBackground'
+import clickTrash from './bindEvents/clickTrash'
+import clickUndo from './bindEvents/clickUndo'
+import colorClick from './bindEvents/colorClick'
+import keyDown from './bindEvents/documentEvents/keyDown'
+import keyUp from './bindEvents/documentEvents/keyUp'
+import doExport from './bindEvents/doExport'
+import doImport from './bindEvents/doImport'
+import exportToImgur from './bindEvents/exportToImgur'
+import knobCommonDown from './bindEvents/knob/knobCommomDown'
+import knobMove from './bindEvents/knob/knobMove'
+import noDefault from './bindEvents/noDefault'
+import openPaletteList from './bindEvents/palette/openPaletteList'
+import playCommonDown from './bindEvents/playCommonDown'
+import popupClose from './bindEvents/popupClose'
+import svgContextMenu from './bindEvents/svgContainerEvents/contextMenu'
+import mouseDown from './bindEvents/svgContainerEvents/mouseDown'
+import mouseLeave from './bindEvents/svgContainerEvents/mouseLeave'
+import svgMouseMove from './bindEvents/svgContainerEvents/mouseMove'
+import touchStart from './bindEvents/svgContainerEvents/touchStart'
+import beforeUnload from './bindEvents/windowEvents/beforeUnload'
+import windowContextMenu from './bindEvents/windowEvents/contextMenu'
+import error from './bindEvents/windowEvents/error'
+import ID from './idSelector'
+
+const bindEvents = () => {
+ ID('svgContainer').addEventListener('mousedown', mouseDown)
+ ID('svgContainer').addEventListener('mousemove', svgMouseMove)
+ ID('svgContainer').addEventListener('touchstart', touchStart)
+ ID('svgContainer').addEventListener('mouseleave', mouseLeave)
+ ID('svgContainer').addEventListener('contextmenu', svgContextMenu)
+ ID('import').addEventListener('click', doImport)
+ ID('export').addEventListener('click', doExport)
+ ID('imgur').addEventListener('click', exportToImgur)
+ document.querySelectorAll('.brush').forEach((brush, index) => {
+ brush.classList.add(`size-${globals.brushSizes[index]}`)
+ brush.addEventListener('mousedown', changeBrushSize)
+ brush.addEventListener('click', changeBrushSize)
+ })
+ ID('colors')
+ .querySelectorAll('b')
+ .forEach(color => {
+ color.addEventListener('mousedown', colorClick)
+ color.addEventListener('touchend', colorClick)
+ color.addEventListener('contextmenu', noDefault)
+ })
+ ID('setbackground').addEventListener('click', clickSetBackground)
+ ID('undo').addEventListener('click', clickUndo)
+ ID('redo').addEventListener('click', clickRedo)
+ ID('trash').addEventListener('click', clickTrash)
+ setSeekbarMove(knobMove)
+ ID('knob').addEventListener('mousedown', knobCommonDown)
+ ID('knob').addEventListener('touchstart', knobCommonDown)
+ ID('seekbar').addEventListener('mousedown', knobCommonDown)
+ ID('seekbar').addEventListener('touchstart', knobCommonDown)
+ ID('play').addEventListener('mousedown', playCommonDown)
+ ID('play').addEventListener('touchstart', playCommonDown)
+ ID('palettename').addEventListener('mousedown', openPaletteList)
+ ID('palettename').addEventListener('touchend', openPaletteList)
+ ID('popupclose').addEventListener('click', popupClose)
+ document.addEventListener('keyup', keyUp)
+ document.addEventListener('keydown', keyDown)
+ window.addEventListener('contextmenu', windowContextMenu)
+ window.addEventListener('error', error)
+ window.addEventListener('beforeunload', beforeUnload)
+}
+
+export default bindEvents
diff --git a/src/newcanvas/js/functions/bindEvents/changeBrushSize.js b/src/newcanvas/js/functions/bindEvents/changeBrushSize.js
new file mode 100644
index 0000000..5bccda5
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/changeBrushSize.js
@@ -0,0 +1,22 @@
+import anbt from '../../anbt'
+import setSize from '../anbt/setSize'
+import strokeBegin from '../anbt/strokeBegin'
+import strokeEnd from '../anbt/strokeEnd'
+import ID from '../idSelector'
+
+const changeBrushSize = event => {
+ event.preventDefault()
+ const size = [...event.currentTarget.classList]
+ .filter(htmlClass => htmlClass.startsWith('size-'))[0]
+ .match(/\d+/)[0]
+ setSize(size)
+ const element = ID('tools').querySelector('.sel')
+ if (element) element.classList.remove('sel')
+ event.currentTarget.classList.add('sel')
+ if (!anbt.isStroking) return
+ strokeEnd()
+ const lastPoint = anbt.points[anbt.points.length - 1]
+ strokeBegin(lastPoint.x, lastPoint.y)
+}
+
+export default changeBrushSize
diff --git a/src/newcanvas/js/functions/bindEvents/checkPlayingAndStop.js b/src/newcanvas/js/functions/bindEvents/checkPlayingAndStop.js
new file mode 100644
index 0000000..2188b85
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/checkPlayingAndStop.js
@@ -0,0 +1,13 @@
+import anbt from '../../anbt'
+import ID from '../idSelector'
+
+const checkPlayingAndStop = () => {
+ if (anbt.isPlaying) {
+ anbt.Pause()
+ ID('play').classList.remove('pause')
+ return true
+ }
+ return false
+}
+
+export default checkPlayingAndStop
diff --git a/src/newcanvas/js/functions/bindEvents/clickRedo.js b/src/newcanvas/js/functions/bindEvents/clickRedo.js
new file mode 100644
index 0000000..2a8a05a
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/clickRedo.js
@@ -0,0 +1,10 @@
+import redo from '../anbt/redo'
+import ID from '../idSelector'
+
+const clickRedo = event => {
+ event.preventDefault()
+ ID('play').classList.remove('pause')
+ redo()
+}
+
+export default clickRedo
diff --git a/src/newcanvas/js/functions/bindEvents/clickSetBackground.js b/src/newcanvas/js/functions/bindEvents/clickSetBackground.js
new file mode 100644
index 0000000..c050c2f
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/clickSetBackground.js
@@ -0,0 +1,9 @@
+import globals from '../../globals'
+import updateChooseBackground from './updateChooseBackground'
+
+const clickSetBackground = event => {
+ event.preventDefault()
+ updateChooseBackground(!globals.chooseBackground)
+}
+
+export default clickSetBackground
diff --git a/src/newcanvas/js/functions/bindEvents/clickTrash.js b/src/newcanvas/js/functions/bindEvents/clickTrash.js
new file mode 100644
index 0000000..0641dd1
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/clickTrash.js
@@ -0,0 +1,12 @@
+import globals from '../../globals'
+import clearWithColor from '../anbt/clearWithColor'
+import ID from '../idSelector'
+
+const clickTrash = event => {
+ event.preventDefault()
+ clearWithColor('eraser')
+ if (ID('newcanvasyo').classList.contains('sandbox'))
+ globals.timerStart = Date.now()
+}
+
+export default clickTrash
diff --git a/src/newcanvas/js/functions/bindEvents/clickUndo.js b/src/newcanvas/js/functions/bindEvents/clickUndo.js
new file mode 100644
index 0000000..b4fe608
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/clickUndo.js
@@ -0,0 +1,10 @@
+import undo from '../anbt/undo'
+import ID from '../idSelector'
+
+const clickUndo = event => {
+ event.preventDefault()
+ ID('play').classList.remove('pause')
+ undo()
+}
+
+export default clickUndo
diff --git a/src/newcanvas/js/functions/bindEvents/colorClick.js b/src/newcanvas/js/functions/bindEvents/colorClick.js
new file mode 100644
index 0000000..4e23327
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/colorClick.js
@@ -0,0 +1,26 @@
+import globals from '../../globals';
+import setBackground from '../anbt/setBackground';
+import setColor from '../anbt/setColor';
+import getPointerType from './getPointerType';
+import updateChooseBackground from './updateChooseBackground';
+import updateColorIndicators from './updateColorIndicators';
+
+const colorClick = event => {
+ if (event.touches || event.button === 0 || event.button === 2) {
+ event.preventDefault()
+ const colorButton = event.currentTarget
+ let color = colorButton.style.backgroundColor
+ if (globals.chooseBackground) {
+ if (colorButton.id !== 'eraser') setBackground(color)
+ updateChooseBackground(false)
+ } else {
+ if (colorButton.id === 'eraser') color = 'eraser'
+ // PointerType == 3 is pen tablet eraser
+ if (event.button === 2 || getPointerType() === 3) setColor(1, color)
+ else setColor(0, color)
+ updateColorIndicators()
+ }
+ }
+}
+
+export default colorClick
diff --git a/src/newcanvas/js/functions/bindEvents/doExport.js b/src/newcanvas/js/functions/bindEvents/doExport.js
new file mode 100644
index 0000000..4d03049
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/doExport.js
@@ -0,0 +1,12 @@
+import makePng from '../anbt/makePng'
+import requestSave from '../anbt/requestSave'
+import warnStrokesAfterPosition from './warnStrokesAfterPosition'
+
+const doExport = event => {
+ event.preventDefault()
+ if (warnStrokesAfterPosition()) return
+ makePng(600, 500, true)
+ requestSave()
+}
+
+export default doExport
diff --git a/src/newcanvas/js/functions/bindEvents/doImport.js b/src/newcanvas/js/functions/bindEvents/doImport.js
new file mode 100644
index 0000000..0ca30ad
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/doImport.js
@@ -0,0 +1,11 @@
+import fromLocalFile from '../anbt/fromLocalFile'
+import ID from '../idSelector'
+
+const doImport = event => {
+ event.preventDefault()
+ ID('svgContainer').classList.add('loading')
+ fromLocalFile()
+ ID('svgContainer').classList.remove('loading')
+}
+
+export default doImport
diff --git a/src/newcanvas/js/functions/bindEvents/documentEvents/keyDown.js b/src/newcanvas/js/functions/bindEvents/documentEvents/keyDown.js
new file mode 100644
index 0000000..091a2f1
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/documentEvents/keyDown.js
@@ -0,0 +1,140 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import redo from '../../anbt/redo'
+import setBackground from '../../anbt/setBackground'
+import setColor from '../../anbt/setColor'
+import showEyedropperCursor from '../../anbt/showEyedropperCursor'
+import strokeBegin from '../../anbt/strokeBegin'
+import strokeEnd from '../../anbt/strokeEnd'
+import undo from '../../anbt/undo'
+import ID from '../../idSelector'
+import playCommonDown from '../playCommonDown'
+import removeEyedropper from '../removeEyedropper'
+import updateChooseBackground from '../updateChooseBackground'
+import updateColorIndicators from '../updateColorIndicators'
+
+const keyDown = event => {
+ const { options } = window
+ if (document.activeElement instanceof HTMLInputElement) return true
+ // Alt
+ if (event.keyCode === 18) {
+ // Opera Presto refuses to hide cursor =(
+ if (!navigator.userAgent.match(/\bPresto\b/))
+ ID('svgContainer').classList.add('hidecursor')
+ showEyedropperCursor(true)
+ // The following is needed in case of Alt+Tab causing eyedropper to be stuck
+ ID('svgContainer').addEventListener('mousemove', removeEyedropper)
+ } else if (event.keyCode === 'Q'.charCodeAt(0)) {
+ event.preventDefault()
+ options.colorDoublePress = !options.colorDoublePress
+ } else if (
+ event.keyCode === 'Z'.charCodeAt(0) ||
+ (event.keyCode === 8 && anbt.unsaved)
+ ) {
+ event.preventDefault()
+ ID('play').classList.remove('pause')
+ undo()
+ } else if (event.keyCode === 'Y'.charCodeAt(0)) {
+ event.preventDefault()
+ ID('play').classList.remove('pause')
+ redo()
+ } else if (event.keyCode === 'X'.charCodeAt(0)) {
+ event.preventDefault()
+ const [color0, color1] = anbt.color
+ setColor(0, color1)
+ setColor(1, color0)
+ updateColorIndicators()
+ } else if (event.keyCode === 'B'.charCodeAt(0)) {
+ if (ID('setbackground').hidden) return
+ event.preventDefault()
+ updateChooseBackground(!globals.chooseBackground)
+ } else if (
+ event.keyCode === 'E'.charCodeAt(0) &&
+ !event.ctrlKey &&
+ !event.metaKey
+ ) {
+ event.preventDefault()
+ setColor(0, 'eraser')
+ updateColorIndicators()
+ } else if (
+ event.keyCode >= 48 &&
+ event.keyCode <= 57 &&
+ !event.ctrlKey &&
+ !event.metaKey &&
+ options.colorNumberShortcuts
+ ) {
+ event.preventDefault()
+ let index = event.keyCode === 48 ? 9 : event.keyCode - 49
+ if (
+ event.shiftKey ||
+ (options.colorDoublePress && anbt.prevColorKey === index)
+ )
+ index += 8
+ anbt.prevColorKey = index
+ if (options.colorDoublePress) {
+ if (anbt.prevColorKeyTimer) clearTimeout(anbt.prevColorKeyTimer)
+ anbt.prevColorKeyTimer = setTimeout(() => (anbt.prevColorKey = -1), 500)
+ }
+ const elements = ID('colors').querySelectorAll('b')
+ if (index < elements.length) {
+ const color =
+ elements[index].id === 'eraser'
+ ? 'eraser'
+ : elements[index].style.backgroundColor
+ if (globals.chooseBackground) {
+ if (color !== 'eraser') setBackground(color)
+ updateChooseBackground(false)
+ } else {
+ setColor(0, color)
+ updateColorIndicators()
+ }
+ }
+ if (anbt.isStroking) {
+ strokeEnd()
+ const lastPoint = anbt.points[anbt.points.length - 1]
+ strokeBegin(lastPoint.x, lastPoint.y)
+ }
+ } else if (
+ (event.keyCode === 189 || event.keyCode === 219 || event.keyCode === 188) &&
+ !event.ctrlKey &&
+ !event.metaKey
+ ) {
+ // - or [ or ,
+ event.preventDefault()
+ for (let i = 1; i < globals.brushSizes.length; i++) {
+ if (anbt.size - globals.brushSizes[i] < 0.01) {
+ ID('brush' + (i - 1)).click()
+ break
+ }
+ }
+ } else if (
+ (event.keyCode === 187 || event.keyCode === 221 || event.keyCode === 190) &&
+ !event.ctrlKey &&
+ !event.metaKey
+ ) {
+ // = or ] or .
+ event.preventDefault()
+ for (let i = 0; i < globals.brushSizes.length - 1; i++) {
+ if (anbt.size - globals.brushSizes[i] < 0.01) {
+ ID('brush' + (i + 1)).click()
+ break
+ }
+ }
+ } else if (
+ event.keyCode >= 49 &&
+ event.keyCode <= 52 &&
+ (event.ctrlKey || event.metaKey)
+ ) {
+ // Ctrl+1,2,3,4
+ event.preventDefault()
+ ID('brush' + (event.keyCode - 49)).click()
+ } else if (
+ event.keyCode === 32 &&
+ (event.ctrlKey || event.metaKey) &&
+ !event.altKey &&
+ !event.shiftKey
+ )
+ playCommonDown(event)
+}
+
+export default keyDown
diff --git a/src/newcanvas/js/functions/bindEvents/documentEvents/keyUp.js b/src/newcanvas/js/functions/bindEvents/documentEvents/keyUp.js
new file mode 100644
index 0000000..7fb77df
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/documentEvents/keyUp.js
@@ -0,0 +1,10 @@
+import showEyedropperCursor from '../../anbt/showEyedropperCursor'
+import ID from '../../idSelector'
+
+const keyUp = event => {
+ if (event.keyCode !== 18) return // return if not Alt
+ ID('svgContainer').classList.remove('hidecursor')
+ showEyedropperCursor(false)
+}
+
+export default keyUp
diff --git a/src/newcanvas/js/functions/bindEvents/exportToImgur.js b/src/newcanvas/js/functions/bindEvents/exportToImgur.js
new file mode 100644
index 0000000..acc1ffd
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/exportToImgur.js
@@ -0,0 +1,40 @@
+import anbt from '../../anbt'
+import makePng from '../anbt/makePng'
+import uploadToImgur from '../anbt/uploadToImgur'
+import ID from '../idSelector'
+import warnStrokesAfterPosition from './warnStrokesAfterPosition'
+
+const exportToImgur = event => {
+ event.preventDefault()
+ if (warnStrokesAfterPosition()) return
+ ID('imgur').childNodes[0].nodeValue = 'Uploading...'
+ ID('imgur').disabled = true
+ makePng(600, 500, true)
+ uploadToImgur(request => {
+ ID('imgur').childNodes[0].nodeValue = 'Upload to imgur'
+ ID('popup').classList.add('show')
+ ID('popuptitle').childNodes[0].nodeValue = 'Imgur upload result'
+ if (request && request.success) {
+ anbt.unsaved = false
+ //history.replaceState(null, null, "#" + r.data.id);
+ ID('imgururl').href = `http://imgur.com/${request.data.id}`
+ ID('imgururl').childNodes[0].nodeValue = 'Uploaded image'
+ ID('imgurdelete').href = `http://imgur.com/delete/${
+ request.data.deletehash
+ }`
+ ID('imgurerror').childNodes[0].nodeValue = ''
+ if (window.inforum)
+ window.frameElement.ownerDocument.getElementById(
+ 'input-comment'
+ ).value += ``
+ } else {
+ const error = request.data
+ ? `Imgur error: ${request.data.error}`
+ : `Error: ${request}`
+ ID('imgurerror').childNodes[0].nodeValue = error
+ }
+ ID('imgur').disabled = false
+ })
+}
+
+export default exportToImgur
diff --git a/src/newcanvas/js/functions/bindEvents/getPointerType.js b/src/newcanvas/js/functions/bindEvents/getPointerType.js
new file mode 100644
index 0000000..84f84d3
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/getPointerType.js
@@ -0,0 +1,8 @@
+import ID from '../idSelector';
+
+const getPointerType = () =>
+ ID('wacom') && ID('wacom').penAPI && ID('wacom').penAPI.isWacom
+ ? ID('wacom').penAPI.pointerType
+ : 0
+
+export default getPointerType
diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobCommoUp.js b/src/newcanvas/js/functions/bindEvents/knob/knobCommoUp.js
new file mode 100644
index 0000000..4eaca37
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/knob/knobCommoUp.js
@@ -0,0 +1,13 @@
+import knobCommonMove from './knobCommonMove'
+
+const knobCommonUp = event => {
+ if (!event.button || (!event.touches && !event.touches.length)) {
+ event.preventDefault()
+ window.removeEventListener('mouseup', knobCommonUp)
+ window.removeEventListener('touchend', knobCommonUp)
+ window.removeEventListener('mousemove', knobCommonMove)
+ window.removeEventListener('touchmove', knobCommonMove)
+ }
+}
+
+export default knobCommonUp
diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobCommomDown.js b/src/newcanvas/js/functions/bindEvents/knob/knobCommomDown.js
new file mode 100644
index 0000000..320e520
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/knob/knobCommomDown.js
@@ -0,0 +1,17 @@
+import globals from '../../../globals'
+import ID from '../../idSelector'
+import knobCommonMove from './knobCommonMove'
+import knobCommonUp from './knobCommoUp'
+
+const knobCommonDown = event => {
+ if (event.button === 0 || (event.touches && event.touches.length === 1)) {
+ globals.rectangle = ID('seekbar').getBoundingClientRect()
+ knobCommonMove(event)
+ window.addEventListener('mouseup', knobCommonUp)
+ window.addEventListener('touchend', knobCommonUp)
+ window.addEventListener('mousemove', knobCommonMove)
+ window.addEventListener('touchmove', knobCommonMove)
+ }
+}
+
+export default knobCommonDown
diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobCommonMove.js b/src/newcanvas/js/functions/bindEvents/knob/knobCommonMove.js
new file mode 100644
index 0000000..05a6549
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/knob/knobCommonMove.js
@@ -0,0 +1,21 @@
+import globals from '../../../globals'
+import getSeekMax from '../../anbt/getSeekMax'
+import seek from '../../anbt/seek'
+import ID from '../../idSelector'
+
+const knobCommonMove = event => {
+ event.preventDefault()
+ const length = getSeekMax()
+ let x = event.touches
+ ? event.touches[0].pageX - globals.rectangle.left /*- pageXOffset*/ - 34
+ : event.pageX - globals.rectangle.left - pageXOffset - 34
+ x = Math.min(Math.max(-10, x), 492)
+ const position = Math.round(((x + 10) / 502) * length)
+ x = (position / length) * 502 - 10
+ ID('knob').classList.add('smooth')
+ ID('knob').style.marginLeft = x + 'px'
+ seek(position)
+ ID('play').classList.remove('pause')
+}
+
+export default knobCommonMove
diff --git a/src/newcanvas/js/functions/bindEvents/knob/knobMove.js b/src/newcanvas/js/functions/bindEvents/knob/knobMove.js
new file mode 100644
index 0000000..be003d7
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/knob/knobMove.js
@@ -0,0 +1,16 @@
+import ID from '../../idSelector'
+
+const knobMove = fraction => {
+ const x = Math.floor(fraction * 502 - 10)
+ if (fraction > 0) {
+ ID('knob').classList.add('smooth')
+ } else {
+ ID('knob').classList.remove('smooth')
+ }
+ ID('knob').style.marginLeft = x + 'px'
+ if (fraction >= 1) {
+ ID('play').classList.remove('pause')
+ }
+}
+
+export default knobMove
diff --git a/src/newcanvas/js/functions/bindEvents/noDefault.js b/src/newcanvas/js/functions/bindEvents/noDefault.js
new file mode 100644
index 0000000..09c2790
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/noDefault.js
@@ -0,0 +1,3 @@
+const noDefault = event => event.preventDefault()
+
+export default noDefault
diff --git a/src/newcanvas/js/functions/bindEvents/palette/choosePalette.js b/src/newcanvas/js/functions/bindEvents/palette/choosePalette.js
new file mode 100644
index 0000000..c1e8995
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/palette/choosePalette.js
@@ -0,0 +1,11 @@
+import setPaletteByName from './setPaletteByName'
+
+const choosePalette = event => {
+ if (event.touches || event.button === 0) {
+ event.preventDefault()
+ const name = event.currentTarget.childNodes[0].nodeValue
+ setPaletteByName(name)
+ }
+}
+
+export default choosePalette
diff --git a/src/newcanvas/js/functions/bindEvents/palette/closePaletteList.js b/src/newcanvas/js/functions/bindEvents/palette/closePaletteList.js
new file mode 100644
index 0000000..8d2c85c
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/palette/closePaletteList.js
@@ -0,0 +1,11 @@
+import ID from '../../idSelector'
+
+const closePaletteList = event => {
+ if (event.touches || event.button === 0) {
+ ID('palettechooser').classList.remove('open')
+ window.removeEventListener('mousedown', closePaletteList)
+ window.removeEventListener('touchend', closePaletteList)
+ }
+}
+
+export default closePaletteList
diff --git a/src/newcanvas/js/functions/bindEvents/palette/openPaletteList.js b/src/newcanvas/js/functions/bindEvents/palette/openPaletteList.js
new file mode 100644
index 0000000..f637455
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/palette/openPaletteList.js
@@ -0,0 +1,45 @@
+import palettes from '../../../palettes'
+import ID from '../../idSelector'
+import choosePalette from './choosePalette'
+import closePaletteList from './closePaletteList'
+
+const openPaletteList = event => {
+ if (event.touches || event.button === 0) {
+ event.preventDefault()
+ const chooser = ID('palettechooser')
+ chooser.classList.toggle('open')
+ if (chooser.classList.contains('open')) {
+ setTimeout(() => {
+ window.addEventListener('mousedown', closePaletteList)
+ window.addEventListener('touchend', closePaletteList)
+ }, 1)
+ }
+ const keys = Object.keys(palettes)
+ if (chooser.childNodes.length < keys.length) {
+ const canvas = document.createElement('canvas')
+ canvas.height = 10
+ const ctx = canvas.getContext('2d')
+ for (let i = chooser.childNodes.length; i < keys.length; i++) {
+ canvas.width = 8 * palettes[keys[i]].length + 2
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+ ctx.globalAlpha = 0.5
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
+ ctx.globalAlpha = 1
+ palettes[keys[i]].forEach((color, index) => {
+ ctx.fillStyle = color
+ ctx.fillRect(index * 8 + 1, 1, 8, 8)
+ })
+ const div = document.createElement('div')
+ div.appendChild(document.createTextNode(keys[i]))
+ div.style.backgroundImage = `url("${canvas.toDataURL()}")`
+ div.style.backgroundRepeat = 'no-repeat'
+ div.style.backgroundPosition = 'center 35px'
+ div.addEventListener('mousedown', choosePalette)
+ div.addEventListener('touchend', choosePalette)
+ chooser.appendChild(div)
+ }
+ }
+ }
+}
+
+export default openPaletteList
diff --git a/src/newcanvas/js/functions/bindEvents/palette/setPaletteByName.js b/src/newcanvas/js/functions/bindEvents/palette/setPaletteByName.js
new file mode 100644
index 0000000..0f32d59
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/palette/setPaletteByName.js
@@ -0,0 +1,28 @@
+import anbt from '../../../anbt'
+import palettes from '../../../palettes'
+import ID from '../../idSelector'
+import colorClick from '../colorClick'
+import noDefault from '../noDefault'
+
+const setPaletteByName = name => {
+ ID('palettename').childNodes[0].nodeValue = name
+ const colors = palettes[name]
+ anbt.palette = colors
+ const palette = ID('palette')
+ const elements = palette.querySelectorAll('b')
+ // Remove all current colors except for the eraser
+ elements.forEach(element => palette.removeChild(element))
+ const eraser = elements[elements.length - 1]
+ colors.forEach(color => {
+ const bElement = document.createElement('b')
+ bElement.style.backgroundColor = color
+ bElement.addEventListener('mousedown', colorClick)
+ bElement.addEventListener('touchend', colorClick)
+ bElement.addEventListener('contextmenu', noDefault)
+ palette.appendChild(bElement)
+ // Eraser got on the front, put it on the back
+ palette.appendChild(eraser)
+ })
+}
+
+export default setPaletteByName
diff --git a/src/newcanvas/js/functions/bindEvents/playCommonDown.js b/src/newcanvas/js/functions/bindEvents/playCommonDown.js
new file mode 100644
index 0000000..e7f9b9f
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/playCommonDown.js
@@ -0,0 +1,18 @@
+import anbt from '../../anbt'
+import pause from '../anbt/pause'
+import play from '../anbt/play'
+import ID from '../idSelector'
+
+const playCommonDown = event => {
+ event.stopPropagation()
+ event.preventDefault()
+ if (anbt.isPlaying) {
+ ID('play').classList.remove('pause')
+ pause()
+ } else {
+ ID('play').classList.add('pause')
+ play()
+ }
+}
+
+export default playCommonDown
diff --git a/src/newcanvas/js/functions/bindEvents/popupClose.js b/src/newcanvas/js/functions/bindEvents/popupClose.js
new file mode 100644
index 0000000..5b1b1f7
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/popupClose.js
@@ -0,0 +1,8 @@
+import ID from '../idSelector'
+
+const popupClose = event => {
+ event.preventDefault()
+ ID('popup').classList.remove('show')
+}
+
+export default popupClose
diff --git a/src/newcanvas/js/functions/bindEvents/removeEyedropper.js b/src/newcanvas/js/functions/bindEvents/removeEyedropper.js
new file mode 100644
index 0000000..f58a9c5
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/removeEyedropper.js
@@ -0,0 +1,10 @@
+import showEyedropperCursor from '../anbt/showEyedropperCursor'
+
+const removeEyedropper = event => {
+ if (event.altKey) return
+ event.currentTarget.classList.remove('hidecursor')
+ showEyedropperCursor(false)
+ event.currentTarget.removeEventListener('mousemove', removeEyedropper)
+}
+
+export default removeEyedropper
diff --git a/src/newcanvas/js/functions/bindEvents/simulateSingleTouchStart.js b/src/newcanvas/js/functions/bindEvents/simulateSingleTouchStart.js
new file mode 100644
index 0000000..bedcd8d
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/simulateSingleTouchStart.js
@@ -0,0 +1,12 @@
+import globals from '../../globals'
+import strokeBegin from '../anbt/strokeBegin'
+
+const simulateSingleTouchStart = () => {
+ if (!globals.touchSingle) return
+ const x = globals.lastTouch.pageX - globals.rectangle.left /*- pageXOffset*/
+ const y = globals.lastTouch.pageY - globals.rectangle.top /*- pageYOffset*/
+ strokeBegin(x, y, true)
+ globals.touchSingle = false
+}
+
+export default simulateSingleTouchStart
diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/contextMenu.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/contextMenu.js
new file mode 100644
index 0000000..2726d80
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/contextMenu.js
@@ -0,0 +1,3 @@
+const svgContextMenu = event => event.preventDefault()
+
+export default svgContextMenu
\ No newline at end of file
diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseDown.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseDown.js
new file mode 100644
index 0000000..03bcb40
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseDown.js
@@ -0,0 +1,37 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import eyedropper from '../../anbt/eyedropper'
+import setColor from '../../anbt/setColor'
+import strokeBegin from '../../anbt/strokeBegin'
+import ID from '../../idSelector'
+import checkPlayingAndStop from '../checkPlayingAndStop'
+import getPointerType from '../getPointerType'
+import updateColorIndicators from '../updateColorIndicators'
+import windowMouseMove from '../windowEvents/mouseMove'
+import mouseUp from './mouseUp'
+
+const mouseDown = event => {
+ const { options } = window
+ if (event.button === 0 || event.button === 2) {
+ if (anbt.isStroking) return mouseUp(event)
+ if (checkPlayingAndStop()) return
+ event.preventDefault()
+ globals.rectangle = event.currentTarget.getBoundingClientRect()
+ const x = event.pageX - globals.rectangle.left - pageXOffset
+ const y = event.pageY - globals.rectangle.top - pageYOffset
+
+ if (event.altKey) {
+ setColor(event.button ? 1 : 0, eyedropper(x, y))
+ updateColorIndicators()
+ } else {
+ // PointerType == 3 is pen tablet eraser
+ const left = event.button === 0 && getPointerType() !== 3
+ if (options.hideCross) ID('svgContainer').classList.add('hidecursor')
+ strokeBegin(x, y, left)
+ window.addEventListener('mouseup', mouseUp)
+ window.addEventListener('mousemove', windowMouseMove)
+ }
+ }
+}
+
+export default mouseDown
diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseLeave.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseLeave.js
new file mode 100644
index 0000000..a77f887
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseLeave.js
@@ -0,0 +1,5 @@
+import moveCursor from '../../anbt/moveCursor'
+
+const mouseLeave = () => moveCursor(-100, -100) // Hide brush cursor
+
+export default mouseLeave
diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseMove.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseMove.js
new file mode 100644
index 0000000..9d4c409
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseMove.js
@@ -0,0 +1,29 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import eyedropper from '../../anbt/eyedropper'
+import moveCursor from '../../anbt/moveCursor'
+import ID from '../../idSelector'
+
+const svgMouseMove = event => {
+ const { options } = window
+ globals.rectangle = event.currentTarget.getBoundingClientRect()
+ const x = event.pageX - globals.rectangle.left - pageXOffset
+ const y = event.pageY - globals.rectangle.top - pageYOffset
+ moveCursor(x, y)
+ // Highlight color we're pointing at
+ if (options.colorUnderCursorHint && !anbt.isStroking) {
+ const color = eyedropper(x, y)
+ if (globals.stSeenColorToHighlight !== color) {
+ const element = ID('colors').querySelector('b.hint')
+ if (element) element.classList.remove('hint')
+ const colorIndex = anbt.palette.indexOf(color)
+ if (colorIndex >= 0) {
+ const elements = ID('colors').querySelectorAll('b')
+ elements[colorIndex].classList.add('hint')
+ }
+ }
+ globals.lastSeenColorToHighlight = color
+ }
+}
+
+export default svgMouseMove
diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseUp.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseUp.js
new file mode 100644
index 0000000..b7c9910
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/mouseUp.js
@@ -0,0 +1,17 @@
+import anbt from '../../../anbt'
+import strokeEnd from '../../anbt/strokeEnd'
+import ID from '../../idSelector'
+import windowMouseMove from '../windowEvents/mouseMove'
+
+const mouseUp = event => {
+ const { options } = window
+ if (event.button === 0 || event.button === 2) {
+ event.preventDefault()
+ if (anbt.isStroking) strokeEnd()
+ if (options.hideCross) ID('svgContainer').classList.remove('hidecursor')
+ window.removeEventListener('mouseup', mouseUp)
+ window.removeEventListener('mousemove', windowMouseMove)
+ }
+}
+
+export default mouseUp
diff --git a/src/newcanvas/js/functions/bindEvents/svgContainerEvents/touchStart.js b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/touchStart.js
new file mode 100644
index 0000000..6472810
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/svgContainerEvents/touchStart.js
@@ -0,0 +1,36 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import strokeEnd from '../../anbt/strokeEnd'
+import checkPlayingAndStop from '../checkPlayingAndStop'
+import touchEnd from '../windowEvents/touchEnd'
+import touchMove from '../windowEvents/touchMove'
+import touchUndoRedo from '../windowEvents/touchUndoRedo'
+
+const touchStart = event => {
+ if (event.touches.length === 1) {
+ if (checkPlayingAndStop()) return
+ // Let two-finger scrolling, pinching, etc. work.
+ // This requires moving dot-drawing to simulateSingleTouchStart()
+ globals.rectangle = event.currentTarget.getBoundingClientRect()
+ globals.touchSingle = true
+ globals.lastTouch = event.touches[0]
+ window.addEventListener('touchend', touchEnd)
+ window.addEventListener('touchmove', touchMove)
+ } else {
+ // Enable two-finger undo and redo:
+ // 1 o o
+ // 2 o o o o
+ // 3 . o o .
+ // Undo Redo
+ if (globals.touchSingle && event.touches.length === 3) {
+ globals.lastTouch = event.touches[1]
+ window.addEventListener('touchend', touchUndoRedo)
+ }
+ globals.touchSingle = false
+ window.removeEventListener('touchend', touchEnd)
+ window.removeEventListener('touchmove', touchMove)
+ if (anbt.isStroking) strokeEnd()
+ }
+}
+
+export default touchStart
diff --git a/src/newcanvas/js/functions/bindEvents/updateChooseBackground.js b/src/newcanvas/js/functions/bindEvents/updateChooseBackground.js
new file mode 100644
index 0000000..817f48c
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/updateChooseBackground.js
@@ -0,0 +1,15 @@
+import globals from '../../globals';
+import ID from '../idSelector';
+
+const updateChooseBackground = chooseBackground => {
+ globals.chooseBackground = chooseBackground
+ if (chooseBackground) {
+ ID('colors').classList.add('setbackground')
+ ID('setbackground').classList.add('sel')
+ } else {
+ ID('colors').classList.remove('setbackground')
+ ID('setbackground').classList.remove('sel')
+ }
+}
+
+export default updateChooseBackground
diff --git a/src/newcanvas/js/functions/bindEvents/updateColorIndicators.js b/src/newcanvas/js/functions/bindEvents/updateColorIndicators.js
new file mode 100644
index 0000000..76eeecc
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/updateColorIndicators.js
@@ -0,0 +1,17 @@
+import anbt from '../../anbt';
+import ID from '../idSelector';
+
+const updateColorIndicators = () => {
+ const { colors } = anbt
+ ;['primary', 'secondary'].forEach((id, index) => {
+ if (colors[index] === 'eraser') {
+ ID(id).style.backgroundColor = 'pink'
+ ID(id).classList.add('eraser')
+ } else {
+ ID(id).style.backgroundColor = colors[index]
+ ID(id).classList.remove('eraser')
+ }
+ })
+}
+
+export default updateColorIndicators
diff --git a/src/newcanvas/js/functions/bindEvents/warnStrokesAfterPosition.js b/src/newcanvas/js/functions/bindEvents/warnStrokesAfterPosition.js
new file mode 100644
index 0000000..b4e7e9c
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/warnStrokesAfterPosition.js
@@ -0,0 +1,11 @@
+import anbt from '../../anbt'
+import getSeekMax from '../anbt/getSeekMax'
+
+const warnStrokesAfterPosition = () => {
+ if (anbt.position < getSeekMax())
+ return !confirm(
+ 'Strokes after current position wi)ll be discarded. Continue?'
+ )
+}
+
+export default warnStrokesAfterPosition
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/beforeUnload.js b/src/newcanvas/js/functions/bindEvents/windowEvents/beforeUnload.js
new file mode 100644
index 0000000..f3147a0
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/beforeUnload.js
@@ -0,0 +1,10 @@
+import anbt from '../../../anbt'
+
+const beforeUnload = event => {
+ if (!anbt.unsaved) return
+ const message = "You haven't saved the drawing. Abandon?"
+ event.returnValue = message
+ return message
+}
+
+export default beforeUnload
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/contextMenu.js b/src/newcanvas/js/functions/bindEvents/windowEvents/contextMenu.js
new file mode 100644
index 0000000..60791ec
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/contextMenu.js
@@ -0,0 +1,7 @@
+import anbt from '../../../anbt'
+
+const windowContextMenu = event => {
+ if (anbt.isStroking) event.preventDefault()
+}
+
+export default windowContextMenu
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/error.js b/src/newcanvas/js/functions/bindEvents/windowEvents/error.js
new file mode 100644
index 0000000..bd262f5
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/error.js
@@ -0,0 +1,3 @@
+const error = event => alert(event)
+
+export default error
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/mouseMove.js b/src/newcanvas/js/functions/bindEvents/windowEvents/mouseMove.js
new file mode 100644
index 0000000..85f156e
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/mouseMove.js
@@ -0,0 +1,13 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import strokeAdd from '../../anbt/strokeAdd'
+
+const windowMouseMove = event => {
+ event.preventDefault()
+ if (!anbt.isStroking) return
+ const x = event.pageX - globals.rectangle.left - pageXOffset
+ const y = event.pageY - globals.rectangle.top - pageYOffset
+ strokeAdd(x, y)
+}
+
+export default windowMouseMove
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/touchEnd.js b/src/newcanvas/js/functions/bindEvents/windowEvents/touchEnd.js
new file mode 100644
index 0000000..7c33dba
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/touchEnd.js
@@ -0,0 +1,14 @@
+import strokeEnd from '../../anbt/strokeEnd'
+import simulateSingleTouchStart from '../simulateSingleTouchStart'
+import touchMove from './touchMove'
+
+const touchEnd = event => {
+ if (event.touches.length !== 0) return
+ simulateSingleTouchStart()
+ event.preventDefault()
+ window.removeEventListener('touchend', touchEnd)
+ window.removeEventListener('touchmove', touchMove)
+ strokeEnd()
+}
+
+export default touchEnd
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/touchMove.js b/src/newcanvas/js/functions/bindEvents/windowEvents/touchMove.js
new file mode 100644
index 0000000..d4b5184
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/touchMove.js
@@ -0,0 +1,15 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import simulateSingleTouchStart from '../simulateSingleTouchStart'
+
+const touchMove = event => {
+ if (event.touches.length !== 1) return
+ simulateSingleTouchStart()
+ event.preventDefault()
+ if (!anbt.isStroking) return
+ const x = event.touches[0].pageX - globals.rectangle.left /*- pageXOffset*/
+ const y = event.touches[0].pageY - globals.rectangle.top /*- pageYOffset*/
+ anbt.StrokeAdd(x, y)
+}
+
+export default touchMove
diff --git a/src/newcanvas/js/functions/bindEvents/windowEvents/touchUndoRedo.js b/src/newcanvas/js/functions/bindEvents/windowEvents/touchUndoRedo.js
new file mode 100644
index 0000000..c426074
--- /dev/null
+++ b/src/newcanvas/js/functions/bindEvents/windowEvents/touchUndoRedo.js
@@ -0,0 +1,21 @@
+import globals from '../../../globals'
+import redo from '../../anbt/redo'
+import undo from '../../anbt/undo'
+import ID from '../../idSelector'
+
+const touchUndoRedo = event => {
+ if (event.changedTouches.length === 1 && event.touches.length === 1) {
+ const { pageX, pageY } = event.changedTouches[0]
+ if (
+ Math.abs(pageX - globals.lastTouch.pageX) < 10 &&
+ Math.abs(pageY - globals.lastTouch.pageY) < 10
+ ) {
+ ID('play').classList.remove('pause')
+ if (pageX < event.touches[0].pageX) undo()
+ else redo()
+ }
+ }
+ window.removeEventListener('touchend', touchUndoRedo)
+}
+
+export default touchUndoRedo
diff --git a/src/newcanvas/js/functions/buildSmoothPath.js b/src/newcanvas/js/functions/buildSmoothPath.js
new file mode 100644
index 0000000..9c0e284
--- /dev/null
+++ b/src/newcanvas/js/functions/buildSmoothPath.js
@@ -0,0 +1,72 @@
+const buildSmoothPath = (points, path) => {
+ const { length } = points
+ if (length < 2) return
+ path.pathSegList.initialize(
+ path.createSVGPathSegMovetoAbs(points[0].x, points[0].y)
+ )
+ if (!window.options.smoothening) {
+ for (let i = 1; i < points.length; i++)
+ path.pathSegList.appendItem(
+ path.createSVGPathSegLinetoAbs(points[i].x, points[i].y)
+ )
+ return
+ }
+ path.pathSegList.appendItem(
+ path.createSVGPathSegLinetoAbs(points[1].x, points[1].y)
+ )
+ if (length < 3) return
+ let prevtangent
+ for (let i = 1; i < length - 1; i++) {
+ const previousPoint = points[i - 1]
+ const currentPoint = points[i]
+ const nextPoint = points[i + 1]
+ const dx1 = currentPoint.x - previousPoint.x
+ const dy1 = currentPoint.y - previousPoint.y
+ const angle1 = Math.atan2(dy1, dx1)
+ const dist1 = Math.sqrt(dx1 ** 2 + dy1 ** 2)
+ const dx2 = nextPoint.x - currentPoint.x
+ const dy2 = nextPoint.y - currentPoint.y
+ const angle2 = Math.atan2(dy2, dx2)
+ const dist2 = Math.sqrt(dx2 ** 2 + dy2 ** 2)
+ const tangent = (angle1 + angle2) / 2
+ if (i > 1) {
+ let good = false
+ if (Math.abs(angle2 - angle1) >= Math.PI / 4) {
+ path.pathSegList.appendItem(
+ path.createSVGPathSegLinetoAbs(currentPoint.x, currentPoint.y)
+ )
+ } else {
+ if (good && dist1 / dist2 >= 0.4 && dist1 / dist2 <= 2.5) {
+ const t1 = {
+ x: previousPoint.x + Math.cos(prevtangent) * dist1 * 0.4,
+ y: previousPoint.y + Math.sin(prevtangent) * dist1 * 0.4
+ }
+ const t2 = {
+ x: currentPoint.x - Math.cos(tangent) * dist2 * 0.4,
+ y: currentPoint.y - Math.sin(tangent) * dist2 * 0.4
+ }
+ path.pathSegList.appendItem(
+ path.createSVGPathSegCurvetoCubicAbs(
+ currentPoint.x,
+ currentPoint.y,
+ t1.x,
+ t1.y,
+ t2.x,
+ t2.y
+ )
+ )
+ } else {
+ path.pathSegList.appendItem(
+ path.createSVGPathSegLinetoAbs(currentPoint.x, currentPoint.y)
+ )
+ good = true
+ }
+ }
+ }
+ prevtangent = tangent
+ }
+ const c = points[length - 1]
+ path.pathSegList.appendItem(path.createSVGPathSegLinetoAbs(c.x, c.y))
+}
+
+export default buildSmoothPath
diff --git a/src/newcanvas/js/functions/conversions/base64ToBytes.js b/src/newcanvas/js/functions/conversions/base64ToBytes.js
new file mode 100644
index 0000000..019131e
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/base64ToBytes.js
@@ -0,0 +1,5 @@
+import stringToBytes from './stringToBytes'
+
+const base64ToBytes = base64 => stringToBytes(atob(base64))
+
+export default base64ToBytes
diff --git a/src/newcanvas/js/functions/conversions/bytesToStrings.js b/src/newcanvas/js/functions/conversions/bytesToStrings.js
new file mode 100644
index 0000000..d97d28f
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/bytesToStrings.js
@@ -0,0 +1,4 @@
+const bytesToString = bytes =>
+ [...bytes].map(byte => String.fromCharCode(byte)).join('')
+
+export default bytesToString
\ No newline at end of file
diff --git a/src/newcanvas/js/functions/conversions/colorToDword.js b/src/newcanvas/js/functions/conversions/colorToDword.js
new file mode 100644
index 0000000..1838904
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/colorToDword.js
@@ -0,0 +1,8 @@
+import colorToRgba from './colorToRgba'
+
+const colorToDword = color =>
+ colorToRgba(color)
+ .map(value => String.fromCharCode(value))
+ .join('')
+
+export default colorToDword
diff --git a/src/newcanvas/js/functions/conversions/colorToHex.js b/src/newcanvas/js/functions/conversions/colorToHex.js
new file mode 100644
index 0000000..e6d1286
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/colorToHex.js
@@ -0,0 +1,6 @@
+import colorToRgba from './colorToRgba'
+import rgbToHex from './rgbToHex'
+
+const colorToHex = color => rgbToHex(colorToRgba(color))
+
+export default colorToHex
diff --git a/src/newcanvas/js/functions/conversions/colorToRgba.js b/src/newcanvas/js/functions/conversions/colorToRgba.js
new file mode 100644
index 0000000..890ac0f
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/colorToRgba.js
@@ -0,0 +1,19 @@
+const colorToRgba = color =>
+ color[0] === '#'
+ ? color.length === 4
+ ? [...(color.substr(1, 3) + 'F')].map(rgb => parseInt(rgb + rgb, 16))
+ : (color + 'FF')
+ .substr(1, 8)
+ .match(/.{2}/g)
+ .map(rgb => parseInt(rgb, 16))
+ : color.substr(0, 4) === 'rgba'
+ ? color
+ .match(/[\d\.]+/g)
+ .map((rgba, index) =>
+ index === 3 ? Math.floor(parseFloat(rgba) * 255) : parseInt(rgba, 10)
+ )
+ : color.substr(0, 3) === 'rgb'
+ ? (color + 255).match(/[\d\.]+/g).map(rgba => parseInt(rgba, 10))
+ : [0, 0, 0, 255]
+
+export default colorToRgba
diff --git a/src/newcanvas/js/functions/conversions/rgbToHex.js b/src/newcanvas/js/functions/conversions/rgbToHex.js
new file mode 100644
index 0000000..6a421fe
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/rgbToHex.js
@@ -0,0 +1,4 @@
+const rgbToHex = rgb =>
+ '#' + rgb.map((value, index) => index < 3 ? ('0' + value.toString(16)).slice(-2) : '').join('')
+
+export default rgbToHex
diff --git a/src/newcanvas/js/functions/conversions/rgbToLab.js b/src/newcanvas/js/functions/conversions/rgbToLab.js
new file mode 100644
index 0000000..783863e
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/rgbToLab.js
@@ -0,0 +1,17 @@
+const rgbToLab = rgb => {
+ const [r, g, b] = rgb.map(value =>
+ value > 10
+ ? Math.pow((value / 255 + 0.055) / 1.055, 2.4)
+ : value / 255 / 12.92
+ )
+ const [x, y, z] = [
+ (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047,
+ r * 0.2126 + g * 0.7152 + b * 0.0722,
+ (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883
+ ].map(value =>
+ value > 0.008856 ? Math.pow(value, 1 / 3) : 7.787 * value + 16 / 116
+ )
+ return [116 * y - 16, 500 * (x - y), 200 * (y - z)]
+}
+
+export default rgbToLab
diff --git a/src/newcanvas/js/functions/conversions/stringToBytes.js b/src/newcanvas/js/functions/conversions/stringToBytes.js
new file mode 100644
index 0000000..c2585ab
--- /dev/null
+++ b/src/newcanvas/js/functions/conversions/stringToBytes.js
@@ -0,0 +1,4 @@
+const stringToBytes = binaryString =>
+ new Uint8Array([...binaryString].map(character => character.charCodeAt(0)))
+
+export default stringToBytes
diff --git a/src/newcanvas/js/functions/crc32.js b/src/newcanvas/js/functions/crc32.js
new file mode 100644
index 0000000..1c45d4c
--- /dev/null
+++ b/src/newcanvas/js/functions/crc32.js
@@ -0,0 +1,15 @@
+import makeCRCTable from './makeCRCTable'
+
+const crc32 = (string, string2) => {
+ const crcTable = makeCRCTable()
+ let crc = -1
+ for (let i = 0; i < string.length; i++)
+ crc = (crc >>> 8) ^ crcTable[(crc ^ string.charCodeAt(i)) & 0xff]
+ if (string2) {
+ for (let i = 0; i < string2.length; i++)
+ crc = (crc >>> 8) ^ crcTable[(crc ^ string2.charCodeAt(i)) & 0xff]
+ }
+ return (crc ^ -1) >>> 0
+}
+
+export default crc32
diff --git a/src/newcanvas/js/functions/createSvgElement.js b/src/newcanvas/js/functions/createSvgElement.js
new file mode 100644
index 0000000..f232454
--- /dev/null
+++ b/src/newcanvas/js/functions/createSvgElement.js
@@ -0,0 +1,11 @@
+const createSvgElement = (name, attributs) => {
+ const element = document.createElementNS('http://www.w3.org/2000/svg', name)
+ if (attributs)
+ Object.keys(attributs).forEach(attribut => {
+ if (attributs[attribut])
+ element.setAttribute(attribut, attributs[attribut])
+ })
+ return element
+}
+
+export default createSvgElement
\ No newline at end of file
diff --git a/src/newcanvas/js/functions/fixTabletPluginGoingAwol.js b/src/newcanvas/js/functions/fixTabletPluginGoingAwol.js
new file mode 100644
index 0000000..8f6c957
--- /dev/null
+++ b/src/newcanvas/js/functions/fixTabletPluginGoingAwol.js
@@ -0,0 +1,14 @@
+import ID from './idSelector'
+
+const fixTabletPluginGoingAwol = () => {
+ const stupidPlugin = ID('wacom')
+ const container = ID('wacomContainer')
+ window.onblur = () => {
+ if (container.childNodes.length === 1) container.removeChild(stupidPlugin)
+ }
+ window.onfocus = () => {
+ if (container.childNodes.length === 0) container.appendChild(stupidPlugin)
+ }
+}
+
+export default fixTabletPluginGoingAwol
diff --git a/src/newcanvas/js/functions/getClosestColor.js b/src/newcanvas/js/functions/getClosestColor.js
new file mode 100644
index 0000000..33c2282
--- /dev/null
+++ b/src/newcanvas/js/functions/getClosestColor.js
@@ -0,0 +1,22 @@
+import colorToHex from './conversions/colorToHex'
+import colorToRgba from './conversions/colorToRgba'
+import rgbToHex from './conversions/rgbToHex'
+import getColorDistance from './getColorDistance'
+import ID from './idSelector'
+
+const getClosestColor = (rgb, palette) => {
+ // Allow any color in sandbox and friend games
+ if (
+ ID('newcanvasyo').classList.contains('sandbox') ||
+ (window.gameInfo && window.gameInfo.friend)
+ )
+ return rgbToHex([...rgb])
+ const distances = palette
+ .slice(0)
+ .map(color => getColorDistance([...rgb], colorToRgba(color)))
+ const minimum = Math.min(...distances)
+ const closestColor = palette[distances.indexOf(minimum)]
+ return colorToHex(closestColor)
+}
+
+export default getClosestColor
diff --git a/src/newcanvas/js/functions/getColorDistance.js b/src/newcanvas/js/functions/getColorDistance.js
new file mode 100644
index 0000000..fb93c32
--- /dev/null
+++ b/src/newcanvas/js/functions/getColorDistance.js
@@ -0,0 +1,12 @@
+import rgbToLab from './conversions/rgbToLab'
+
+const getColorDistance = (rgb1, rgb2) => {
+ const lab1 = rgbToLab(rgb1)
+ const lab2 = rgbToLab(rgb2)
+ const l = lab2[0] - lab1[0]
+ const a = lab2[1] - lab1[1]
+ const b = lab2[2] - lab1[2]
+ return Math.sqrt(l ** 2 * 2 + a ** 2 + b ** 2)
+}
+
+export default getColorDistance
diff --git a/src/newcanvas/js/functions/getSqSegDist.js b/src/newcanvas/js/functions/getSqSegDist.js
new file mode 100644
index 0000000..6f0d622
--- /dev/null
+++ b/src/newcanvas/js/functions/getSqSegDist.js
@@ -0,0 +1,20 @@
+const getSqSegDist = (point, point1, point2) => {
+ let { x, y } = point1
+ let dx = point2.x - x
+ let dy = point2.y - y
+ if (dx !== 0 || dy !== 0) {
+ var t = ((point.x - x) * dx + (point.y - y) * dy) / (dx * dx + dy * dy)
+ if (t > 1) {
+ x = point2.x
+ y = point2.y
+ } else if (t > 0) {
+ x += dx * t
+ y += dy * t
+ }
+ }
+ dx = point.x - x
+ dy = point.y - y
+ return dx * dx + dy * dy
+}
+
+export default getSqSegDist
diff --git a/src/newcanvas/js/functions/idSelector.js b/src/newcanvas/js/functions/idSelector.js
new file mode 100644
index 0000000..93b06f6
--- /dev/null
+++ b/src/newcanvas/js/functions/idSelector.js
@@ -0,0 +1,3 @@
+const ID = id => document.getElementById(id)
+
+export default ID
\ No newline at end of file
diff --git a/src/newcanvas/js/functions/int16be.js b/src/newcanvas/js/functions/int16be.js
new file mode 100644
index 0000000..5a78861
--- /dev/null
+++ b/src/newcanvas/js/functions/int16be.js
@@ -0,0 +1,6 @@
+const int16be = (byte1, byte2) => {
+ const v = (byte1 << 8) | byte2
+ return v > 32767 ? v - 65536 : v
+}
+
+export default int16be
diff --git a/src/newcanvas/js/functions/makeCRCTable.js b/src/newcanvas/js/functions/makeCRCTable.js
new file mode 100644
index 0000000..9208fef
--- /dev/null
+++ b/src/newcanvas/js/functions/makeCRCTable.js
@@ -0,0 +1,11 @@
+const makeCRCTable = () => {
+ const crcTable = []
+ for (let n = 0; n < 256; n++) {
+ let c = n
+ for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
+ crcTable.push(c)
+ }
+ return crcTable
+}
+
+export default makeCRCTable
diff --git a/src/newcanvas/js/functions/needToGoDeeper.js b/src/newcanvas/js/functions/needToGoDeeper.js
new file mode 100644
index 0000000..01e499b
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper.js
@@ -0,0 +1,85 @@
+import anbt from '../anbt';
+import fromPng from './anbt/fromPng';
+import fromUrl from './anbt/fromUrl';
+import base64ToBytes from './conversions/base64ToBytes';
+import fixTabletPluginGoingAwol from './fixTabletPluginGoingAwol';
+import ID from './idSelector';
+import ajax from './needToGoDeeper/ajax';
+import bindCanvasEvents from './needToGoDeeper/bindCanvasEvents';
+import extractInfoFromHTML from './needToGoDeeper/extractInfoFromHTML';
+import getParametersFromPlay from './needToGoDeeper/getParametersFromPlay';
+import handleSandboxParameters from './needToGoDeeper/handleSandboxParameters';
+
+const needToGoDeeper = () => {
+ const { options, insandbox, panelid } = window
+ window.onerror = (error, file, line) => {
+ // Silence the bogus error message from the overwritten page's timer
+ if (error.toString().includes('periodsToSeconds')) return
+ // Silence the useless error message
+ if (error.toString().match(/script error/i)) return
+ alert(line ? `${error}\nline: ${line}` : error)
+ }
+ if (options.newCanvasCSS) {
+ const parent =
+ document.getElementsByTagName('head')[0] || document.documentElement
+ const style = document.createElement('style')
+ style.type = 'text/css'
+ const textNode = document.createTextNode(options.newCanvasCSS)
+ style.appendChild(textNode)
+ parent.appendChild(style)
+ }
+ if (options.enableWacom) {
+ const stupidPlugin = document.createElement('object')
+ const container = ID('wacomContainer')
+ stupidPlugin.setAttribute('id', 'wacom')
+ stupidPlugin.setAttribute('type', 'application/x-wacomtabletplugin')
+ stupidPlugin.setAttribute('width', '1')
+ stupidPlugin.setAttribute('height', '1')
+ container.appendChild(stupidPlugin)
+ if (options.fixTabletPluginGoingAWOL) fixTabletPluginGoingAwol()
+ }
+ bindCanvasEvents()
+ if (insandbox) {
+ if (panelid)
+ ajax('GET', `/panel/drawing/${panelid}/-/`, {
+ load: response => {
+ window.gameInfo = extractInfoFromHTML(response)
+ fromUrl(`${window.gameInfo.drawinglink}?anbt`) // workaround for non-CORS cached image
+ handleSandboxParameters()
+ },
+ error: () => {
+ alert('Error loading the panel page. Please try again.')
+ }
+ })
+ else {
+ ajax('GET', '/sandbox/', {
+ load: response => {
+ window.gameInfo = extractInfoFromHTML(response)
+ handleSandboxParameters()
+ },
+ error: () => {}
+ })
+ if (options.backup) {
+ const pngdata = localStorage.getItem('anbt_drawingbackup_newcanvas')
+ if (pngdata) {
+ fromPng(base64ToBytes(pngdata.substr(22)).buffer)
+ localStorage.removeItem('anbt_drawingbackup_newcanvas')
+ }
+ }
+ }
+ } else {
+ ID('newcanvasyo').className = 'play'
+ getParametersFromPlay()
+ }
+ // Poor poor memory devices, let's save on memory to avoid them "crashing"...
+ if (/iPad|iPhone/.test(navigator.userAgent)) anbt.fastUndoLevels = 3
+ window.$ = () => {
+ alert(
+ 'Some additional script conflicts with ANBT new canvas, please disable it.'
+ )
+ window.$ = null
+ throw new Error('Script conflict with ANBT new canvas')
+ }
+}
+
+export default needToGoDeeper
diff --git a/src/newcanvas/js/functions/needToGoDeeper/ajax.js b/src/newcanvas/js/functions/needToGoDeeper/ajax.js
new file mode 100644
index 0000000..40357fa
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/ajax.js
@@ -0,0 +1,47 @@
+const ajax = (type, url, params) => {
+ const { options } = window
+ const request = new XMLHttpRequest()
+ request.open(type, url)
+ if (params.header)
+ request.setRequestHeader(params.header[0], params.header[1])
+ params.retry = 5
+ request.timeout = 15000
+ request.ontimeout = () => {
+ if (params.retry > 0) {
+ if (!options.retryEnabled) return
+ document.body.style.cursor = 'progress'
+ params.retry--
+ ajax(type, url, params)
+ } else {
+ document.body.style.cursor = ''
+ params.error()
+ }
+ }
+ request.onload = () => {
+ if (
+ url === '/play/skip.json' &&
+ request.error === 'Sorry, but we couldn\u0027t find your current game.'
+ ) {
+ location.reload()
+ return
+ }
+ if (
+ url === '/play/exit.json' &&
+ request.error === 'Sorry, but we couldn\u0027t find your current game.'
+ ) {
+ location.pathname = '/'
+ return
+ }
+ params.load(request.responseText)
+ }
+ request.onerror = () => {
+ if (params.error) params.error(request)
+ else params.load(request)
+ }
+ if (params.obj) request.send(JSON.stringify(params.obj))
+ else request.send()
+ document.body.style.cursor = ''
+ return
+}
+
+export default ajax
diff --git a/src/newcanvas/js/functions/needToGoDeeper/bindCanvasEvents.js b/src/newcanvas/js/functions/needToGoDeeper/bindCanvasEvents.js
new file mode 100644
index 0000000..0d8b64b
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/bindCanvasEvents.js
@@ -0,0 +1,41 @@
+import ID from '../idSelector'
+import backToForum from './events/backToForum'
+import bookmark from './events/bookmark'
+import caption from './events/caption'
+import exit from './events/exit'
+import quit from './events/quit'
+import report from './events/report'
+import skip from './events/skip'
+import start from './events/start'
+import submitCaption from './events/submitCaption'
+import submitDrawing from './events/submitDrawing'
+import timePlus from './events/timePlus'
+import updateUsedChars from './events/updateUsedChars'
+
+const bindCanvasEvents = () => {
+ const { options, inforum } = window
+ if (inforum) {
+ ID('quit').addEventListener('click', quit)
+ const backForum = document.createElement('button')
+ backForum.href = '/'
+ backForum.setAttribute('class', 'submit exit')
+ backForum.title = 'Exit'
+ backForum.textContent = 'Exit'
+ backForum.addEventListener('click', backToForum)
+ ID('submit').parentNode.insertBefore(backForum, ID('submit').nextSibling)
+ }
+ ID('exit').addEventListener('click', exit)
+ ID('skip').addEventListener('click', skip)
+ ID('start').addEventListener('click', start)
+ ID('report').addEventListener('click', report)
+ ID('bookmark').addEventListener('click', bookmark)
+ ID('submit').addEventListener('click', submitDrawing)
+ ID('submitcaption').addEventListener('click', submitCaption)
+ if (options.enterToCaption) ID('caption').addEventListener('keydown', caption)
+ ID('caption').addEventListener('change', updateUsedChars)
+ ID('caption').addEventListener('keydown', updateUsedChars)
+ ID('caption').addEventListener('input', updateUsedChars)
+ ID('timeplus').addEventListener('click', timePlus)
+}
+
+export default bindCanvasEvents
diff --git a/src/newcanvas/js/functions/needToGoDeeper/decodeHTML.js b/src/newcanvas/js/functions/needToGoDeeper/decodeHTML.js
new file mode 100644
index 0000000..061b98e
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/decodeHTML.js
@@ -0,0 +1,7 @@
+const decodeHTML = html => {
+ const txt = document.createElement('textarea')
+ txt.innerHTML = html
+ return txt.value
+}
+
+export default decodeHTML
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/backToForum.js b/src/newcanvas/js/functions/needToGoDeeper/events/backToForum.js
new file mode 100644
index 0000000..e7ec940
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/backToForum.js
@@ -0,0 +1,8 @@
+const backToForum = event => {
+ event.preventDefault()
+ window.frameElement.ownerDocument.querySelector(
+ '.v--modal-overlay'
+ ).outerHTML = ''
+}
+
+export default backToForum
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/bookmark.js b/src/newcanvas/js/functions/needToGoDeeper/events/bookmark.js
new file mode 100644
index 0000000..e224cca
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/bookmark.js
@@ -0,0 +1,16 @@
+import decodeHTML from '../decodeHTML'
+import ID from '../../idSelector'
+
+const bookmark = () => {
+ const { getLocalStorageItem } = window
+ ID('bookmark').disabled = true
+ const games = getLocalStorageItem('gpe_gameBookmarks', {})
+ const caption = window.gameInfo.caption
+ games[window.gameInfo.gameid] = {
+ time: Date.now(),
+ caption: caption ? decodeHTML(caption) : ''
+ }
+ localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
+}
+
+export default bookmark
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/caption.js b/src/newcanvas/js/functions/needToGoDeeper/events/caption.js
new file mode 100644
index 0000000..f9a5631
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/caption.js
@@ -0,0 +1,9 @@
+import ID from '../../idSelector'
+
+const caption = event => {
+ if (event.keyCode !== 13) return
+ event.preventDefault()
+ ID('submitcaption').click()
+}
+
+export default caption
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/exit.js b/src/newcanvas/js/functions/needToGoDeeper/events/exit.js
new file mode 100644
index 0000000..689808c
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/exit.js
@@ -0,0 +1,57 @@
+import ID from '../../idSelector'
+import ajax from '../ajax'
+import exitToSandbox from '../exitToSandbox'
+
+const exit = () => {
+ const { gameInfo, incontest } = window
+ if (incontest) {
+ if (!confirm('Quit the contest? Entry coins will be lost!')) return
+ ID('exit').disabled = true
+ ajax('POST', '/contests/exit.json', {
+ load: () => {
+ ID('exit').disabled = false
+ window.drawing_aborted = true
+ exitToSandbox()
+ document.location.pathname = '/contests/'
+ },
+ error: () => {
+ ID('exit').disabled = false
+ alert('Server error. :( Try again?')
+ }
+ })
+ return
+ }
+ if (gameInfo.drawfirst) {
+ if (!confirm('Abort creating a draw first game?')) return
+ ID('exit').disabled = true
+ ajax('POST', '/play/abort-start.json', {
+ obj: {
+ game_token: gameInfo.gameid
+ },
+ load: () => {
+ ID('exit').disabled = false
+ window.drawing_aborted = true
+ exitToSandbox()
+ document.location.pathname = '/create/'
+ },
+ error: () => {
+ ID('exit').disabled = false
+ alert('Server error. :( Try again?')
+ }
+ })
+ return
+ }
+ if (!confirm('Really exit?')) return
+ ID('exit').disabled = true
+ ajax('POST', '/play/exit.json', {
+ obj: {
+ game_token: gameInfo.gameid
+ },
+ load: () => {
+ ID('exit').disabled = false
+ exitToSandbox()
+ }
+ })
+}
+
+export default exit
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/quit.js b/src/newcanvas/js/functions/needToGoDeeper/events/quit.js
new file mode 100644
index 0000000..7315f92
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/quit.js
@@ -0,0 +1,6 @@
+const quit = event => {
+ event.preventDefault()
+ window.top.location.href = 'https://drawception.com/'
+}
+
+export default quit
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/report.js b/src/newcanvas/js/functions/needToGoDeeper/events/report.js
new file mode 100644
index 0000000..36492fb
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/report.js
@@ -0,0 +1,18 @@
+import ID from '../../idSelector'
+import ajax from '../ajax'
+import getParametersFromPlay from '../getParametersFromPlay'
+
+const report = () => {
+ if (!confirm('Report this panel?')) return
+ ajax('POST', '/play/flag.json', {
+ obj: {
+ game_token: window.gameInfo.gameid
+ },
+ load: () => {
+ ID('report').disabled = false
+ getParametersFromPlay()
+ }
+ })
+}
+
+export default report
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/skip.js b/src/newcanvas/js/functions/needToGoDeeper/events/skip.js
new file mode 100644
index 0000000..6bd2f50
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/skip.js
@@ -0,0 +1,21 @@
+import ID from '../../idSelector'
+import ajax from '../ajax'
+import getParametersFromPlay from '../getParametersFromPlay'
+import unsavedStopAction from '../unsavedStopAction'
+
+const skip = () => {
+ if (unsavedStopAction()) return
+ ID('skip').disabled = true
+ ajax('POST', '/play/skip.json', {
+ obj: {
+ game_token: window.gameInfo.gameid
+ },
+ load: () => getParametersFromPlay(), // Postpone enabling skip until we get game info
+ error: () => {
+ ID('skip').disabled = false
+ getParametersFromPlay()
+ }
+ })
+}
+
+export default skip
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/start.js b/src/newcanvas/js/functions/needToGoDeeper/events/start.js
new file mode 100644
index 0000000..9ec53c6
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/start.js
@@ -0,0 +1,11 @@
+import ID from '../../idSelector'
+import getParametersFromPlay from '../getParametersFromPlay'
+import unsavedStopAction from '../unsavedStopAction'
+
+const start = () => {
+ if (unsavedStopAction()) return
+ ID('start').disabled = true
+ getParametersFromPlay()
+}
+
+export default start
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/submitCaption.js b/src/newcanvas/js/functions/needToGoDeeper/events/submitCaption.js
new file mode 100644
index 0000000..77b17a4
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/submitCaption.js
@@ -0,0 +1,54 @@
+import ID from '../../idSelector'
+import ajax from '../ajax'
+import onCaptionSuccess from '../onCaptionSuccess'
+
+const submitCaption = () => {
+ const { incontest, gameInfo } = window
+ const title = ID('caption').value
+ if (!title) {
+ ID('caption').focus()
+ return alert("You haven't entered a caption!")
+ }
+ window.submitting = true
+ ID('submitcaption').disabled = true
+ const url = incontest
+ ? '/contests/submit-caption.json'
+ : '/play/describe.json'
+ ajax('POST', url, {
+ obj: {
+ game_token: gameInfo.gameid,
+ title
+ },
+ load: response => {
+ try {
+ response = JSON.parse(response)
+ } catch (e) {
+ response = {
+ error: response
+ }
+ }
+ if (response.error) {
+ ID('submitcaption').disabled = false
+ if (typeof response.error === 'object')
+ alert(
+ `Error! Please report this data:\ngame: ${
+ gameInfo.gameid
+ }\n\nresponse: \n${JSON.stringify(response.error)}`
+ )
+ else alert(response.error)
+ } else if (response.message) {
+ ID('submitcaption').disabled = false
+ alert(response.message)
+ } else if (response.url) {
+ onCaptionSuccess(title)
+ location.replace(response.url)
+ }
+ },
+ error: () => {
+ ID('submitcaption').disabled = false
+ alert('Server error. :( Try again?')
+ }
+ })
+}
+
+export default submitCaption
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/submitDrawing.js b/src/newcanvas/js/functions/needToGoDeeper/events/submitDrawing.js
new file mode 100644
index 0000000..15f65b0
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/submitDrawing.js
@@ -0,0 +1,59 @@
+import anbt from '../../../anbt'
+import globals from '../../../globals'
+import makePng from '../../anbt/makePng'
+import ID from '../../idSelector'
+import ajax from '../ajax'
+
+const submitDrawing = () => {
+ const { incontest, gameInfo, options } = window
+ const moreThanMinuteLeft = globals.timerStart - Date.now() > 60000
+ if (
+ options.submitConfirm &&
+ moreThanMinuteLeft &&
+ !confirm('Ready to submit this drawing?')
+ )
+ return
+ ID('submit').disabled = true
+ makePng(300, 250, true)
+ if (options.backup)
+ localStorage.setItem('anbt_drawingbackup_newcanvas', anbt.pngBase64)
+ window.submitting = true
+ const url = incontest ? '/contests/submit-drawing.json' : '/play/draw.json'
+ ajax('POST', url, {
+ obj: {
+ game_token: gameInfo.gameid,
+ panel: anbt.pngBase64
+ },
+ load: response => {
+ try {
+ response = JSON.parse(response)
+ } catch (e) {
+ response = {
+ error: response
+ }
+ }
+ if (response.error) {
+ ID('submit').disabled = false
+ if (typeof response.error === 'object')
+ alert(
+ `Error! Please report this data:\ngame: ${
+ gameInfo.gameid
+ }\n\nresponse:\n${JSON.stringify(response.error)}`
+ )
+ else alert(response.error)
+ } else if (response.message) {
+ ID('submit').disabled = false
+ alert(response.message)
+ } else if (response.url) {
+ window.onbeforeunload = () => {}
+ location.replace(response.url)
+ }
+ },
+ error: () => {
+ ID('submit').disabled = false
+ alert('Server error. :( Try again?')
+ }
+ })
+}
+
+export default submitDrawing
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/timePlus.js b/src/newcanvas/js/functions/needToGoDeeper/events/timePlus.js
new file mode 100644
index 0000000..636e0ea
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/timePlus.js
@@ -0,0 +1,38 @@
+import globals from '../../../globals'
+import ID from '../../idSelector'
+import ajax from '../ajax'
+import extractInfoFromHTML from '../extractInfoFromHTML'
+
+const timePlus = () => {
+ let { gameInfo } = window
+ if (!gameInfo.friend) return
+ ID('timeplus').disabled = true
+ ajax('POST', '/play/exit.json', {
+ obj: {
+ game_token: gameInfo.gameid
+ },
+ load: () => {
+ ajax('GET', `/play/${gameInfo.gameid}/?${Date.now()}`, {
+ load: response => {
+ ID('timeplus').disabled = false
+ gameInfo = response
+ ? extractInfoFromHTML(response)
+ : {
+ error: 'Server returned a blank response :('
+ }
+ globals.timerStart = Date.now() + 1000 * gameInfo.timeleft
+ },
+ error: () => {
+ ID('timeplus').disabled = false
+ alert('Server error. :( Try again?')
+ }
+ })
+ },
+ error: () => {
+ ID('timeplus').disabled = false
+ alert('Server error. :( Try again?')
+ }
+ })
+}
+
+export default timePlus
diff --git a/src/newcanvas/js/functions/needToGoDeeper/events/updateUsedChars.js b/src/newcanvas/js/functions/needToGoDeeper/events/updateUsedChars.js
new file mode 100644
index 0000000..f3c5279
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/events/updateUsedChars.js
@@ -0,0 +1,7 @@
+import ID from '../../idSelector'
+
+const updateUsedChars = () => {
+ ID('usedchars').textContent = 45 - ID('caption').value.length
+}
+
+export default updateUsedChars
diff --git a/src/newcanvas/js/functions/needToGoDeeper/exitToSandbox.js b/src/newcanvas/js/functions/needToGoDeeper/exitToSandbox.js
new file mode 100644
index 0000000..fb3b0ba
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/exitToSandbox.js
@@ -0,0 +1,39 @@
+import globals from '../../globals'
+import unlock from '../anbt/unlock'
+import ID from '../idSelector'
+import updateTimer from '../updateTimer'
+import ajax from './ajax'
+
+const exitToSandbox = () => {
+ const { incontest, gameInfo, drawing_aborted, vertitle } = window
+ if (incontest && !drawing_aborted)
+ ajax('POST', '/contests/exit.json', {
+ load: () => alert('You have missed your contest.')
+ })
+ if (gameInfo.drawfirst && !drawing_aborted) {
+ ajax('POST', '/play/abort-start.json', {
+ obj: {
+ game_token: gameInfo.gameid
+ },
+ load: () =>
+ alert('You have missed your Draw First game.\nIt has been aborted.'),
+ error: () =>
+ alert(
+ 'You have missed your Draw First game.\nI tried aborting it, but an error occured. :('
+ )
+ })
+ }
+ globals.timerStart = Date.now()
+ ID('newcanvasyo').className = 'sandbox'
+ window.timerCallback = () => {}
+ updateTimer()
+ document.title = 'Sandbox - Drawception'
+ ID('gamemode').innerHTML = 'Sandbox'
+ ID('headerinfo').innerHTML = `Sandbox with ${vertitle}`
+ try {
+ history.replaceState({}, null, '/sandbox/')
+ } catch (e) {}
+ unlock()
+}
+
+export default exitToSandbox
diff --git a/src/newcanvas/js/functions/needToGoDeeper/extractInfoFromHTML.js b/src/newcanvas/js/functions/needToGoDeeper/extractInfoFromHTML.js
new file mode 100644
index 0000000..2a0127f
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/extractInfoFromHTML.js
@@ -0,0 +1,43 @@
+const extractInfoFromHTML = html => {
+ const doc = document.implementation.createHTMLDocument('')
+ doc.body.innerHTML = html
+ const drawapp = doc.querySelector('draw-app') ||
+ doc.querySelector('describe') || {
+ getAttribute: () => false
+ }
+ const getElement = query => doc.querySelector(query)
+ return {
+ error: (element => (element ? element.src : false))(getElement('.error')),
+ gameid: drawapp.getAttribute('game_token'),
+ blitz: drawapp.getAttribute(':blitz_mode') === 'true',
+ nsfw: drawapp.getAttribute(':nsfw') === 'true',
+ friend: drawapp.getAttribute(':game_public') !== 'true',
+ drawfirst: drawapp.getAttribute(':draw_first') === 'true',
+ timeleft: drawapp.getAttribute(':seconds') * 1,
+ caption: drawapp.getAttribute('phrase'),
+ image: drawapp.getAttribute('img_url'),
+ palette: drawapp.getAttribute('theme_id'),
+ bgbutton: drawapp.getAttribute(':bg_layer') === 'true',
+ playerurl: '/profile/',
+ avatar: null,
+ coins: '-',
+ pubgames: '-',
+ friendgames: '-',
+ notifications: '-',
+ drawinglink: (element => (element ? element.src : false))(
+ getElement('.gamepanel img')
+ ),
+ drawingbylink: (element =>
+ element ? [element.textContent.trim(), element.href] : false)(
+ getElement('#main p a')
+ ),
+ drawncaption: (element => (element ? element.src : false))(
+ getElement('h1.game-title')
+ ),
+ notloggedin: getElement('form.form-login') !== null,
+ limitreached: false, // ??? appears to be redirecting to /play/limit/ which gives "game not found" error
+ html
+ }
+}
+
+export default extractInfoFromHTML
diff --git a/src/newcanvas/js/functions/needToGoDeeper/getPalData.js b/src/newcanvas/js/functions/needToGoDeeper/getPalData.js
new file mode 100644
index 0000000..e42be65
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/getPalData.js
@@ -0,0 +1,21 @@
+import palettemap from '../../palettemap';
+import palettes from '../../palettes';
+
+const getPalData = palette => {
+ if (palette === 'theme_roulette') {
+ // Since site update, the game reports already chosen palette,
+ // but apparently this still happens sometimes. ???
+ alert(
+ "Warning: Drawception roulette didn't give a theme. ANBT will choose a random palette."
+ )
+ delete palettes.Roulette
+ const keys = Object.keys(palettemap)
+ const paletteName = keys[(keys.length * Math.random()) << 0]
+ palettes.Roulette = palettes[palettemap[paletteName][0]]
+ return ['Roulette', palettemap[paletteName][1]]
+ } else {
+ if (palette) return palettemap[palette.toLowerCase()]
+ }
+}
+
+export default getPalData
diff --git a/src/newcanvas/js/functions/needToGoDeeper/getParametersFromPlay.js b/src/newcanvas/js/functions/needToGoDeeper/getParametersFromPlay.js
new file mode 100644
index 0000000..e2ebcf5
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/getParametersFromPlay.js
@@ -0,0 +1,33 @@
+import ajax from './ajax'
+import extractInfoFromHTML from './extractInfoFromHTML'
+import handlePlayParameters from './handlePlayParameters'
+
+const getParametersFromPlay = () => {
+ const { incontest, friendgameid } = window
+ const url = incontest
+ ? '/contests/play/'
+ : `/play/${friendgameid ? `${friendgameid}/` : ''}`
+ try {
+ if (location.pathname !== url) history.replaceState({}, null, url)
+ } catch (e) {}
+ // On Firefox, requesting "/play/" url would immediately return a cached error.
+ // Firefox, WTF? So we use cache-busting here.
+ ajax('GET', `${url}?${Date.now()}`, {
+ load: response => {
+ window.gameInfo = response
+ ? extractInfoFromHTML(response)
+ : {
+ error: 'Server returned a blank response :('
+ }
+ handlePlayParameters()
+ },
+ error: response => {
+ window.gameInfo = {
+ error: `Server error: ${response.statusText}`
+ }
+ handlePlayParameters()
+ }
+ })
+}
+
+export default getParametersFromPlay
diff --git a/src/newcanvas/js/functions/needToGoDeeper/handleCommonParameters.js b/src/newcanvas/js/functions/needToGoDeeper/handleCommonParameters.js
new file mode 100644
index 0000000..fc82056
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/handleCommonParameters.js
@@ -0,0 +1,17 @@
+import ID from '../idSelector'
+
+const handleCommonParameters = () => {
+ const { gameInfo, inforum } = window
+ if (gameInfo.notloggedin)
+ return (ID('start').parentNode.innerHTML =
+ ' ')
+ if (gameInfo.avatar) ID('infoavatar').src = gameInfo.avatar
+ ID('infoprofile').href = gameInfo.playerurl
+ ID('infocoins').innerHTML = gameInfo.coins
+ ID('infogames').innerHTML = gameInfo.pubgames
+ ID('infofriendgames').innerHTML = gameInfo.friendgames || 0
+ ID('infonotifications').innerHTML = gameInfo.notifications
+ if (inforum) document.querySelector('.headerright').hidden = true
+}
+
+export default handleCommonParameters
diff --git a/src/newcanvas/js/functions/needToGoDeeper/handlePlayParameters.js b/src/newcanvas/js/functions/needToGoDeeper/handlePlayParameters.js
new file mode 100644
index 0000000..3d54b7e
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/handlePlayParameters.js
@@ -0,0 +1,113 @@
+import anbt from '../../anbt'
+import globals from '../../globals'
+import palettes from '../../palettes'
+import lock from '../anbt/lock'
+import moveSeekbar from '../anbt/moveSeekbar'
+import seek from '../anbt/seek'
+import setBackground from '../anbt/setBackground'
+import strokeEnd from '../anbt/strokeEnd'
+import unlock from '../anbt/unlock'
+import setPaletteByName from '../bindEvents/palette/setPaletteByName'
+import updateColorIndicators from '../bindEvents/updateColorIndicators'
+import ID from '../idSelector'
+import updateTimer from '../updateTimer'
+import exitToSandbox from './exitToSandbox'
+import getPalData from './getPalData'
+import handleCommonParameters from './handleCommonParameters'
+import timerCallback from './timerCallback'
+
+const handlePlayParameters = () => {
+ const { options, gameInfo, incontest, vertitle } = window
+ ID('skip').disabled = gameInfo.drawfirst || incontest
+ ID('report').disabled = gameInfo.drawfirst || incontest
+ ID('exit').disabled = false
+ ID('start').disabled = false
+ ID('bookmark').disabled = gameInfo.drawfirst || incontest
+ ID('options').disabled = true // Not implemented yet!
+ ID('timeplus').disabled = incontest
+ ID('submit').disabled = false
+ ID('headerinfo').innerHTML = `Playing with ${vertitle}`
+ ID('drawthis').classList.add('onlyplay')
+ ID('emptytitle').classList.remove('onlyplay')
+ window.submitting = false
+ window.drawing_aborted = false
+ if (gameInfo.error) {
+ alert(`Play Error:\n${gameInfo.error}`)
+ return exitToSandbox()
+ }
+ if (gameInfo.limitreached) {
+ alert('Play limit reached!')
+ return exitToSandbox()
+ }
+ ID('gamemode').innerHTML = incontest
+ ? 'Contest'
+ : `${(gameInfo.friend ? 'Friend ' : 'Public ') +
+ (gameInfo.nsfw ? 'Not Safe For Work (18+) ' : 'safe for work ') +
+ (gameInfo.blitz ? 'BLITZ ' : '')}Game`
+ ID('drawthis').innerHTML =
+ gameInfo.caption || (gameInfo.drawfirst && '(Start your game!)') || ''
+ ID('tocaption').src = ''
+ const newcanvas = ID('newcanvasyo')
+ newcanvas.className = 'play'
+ if (gameInfo.friend) newcanvas.classList.add('friend')
+ ID('palettechooser').className = gameInfo.friend ? '' : 'onlysandbox'
+ if (gameInfo.nsfw) newcanvas.classList.add('nsfw')
+ if (gameInfo.blitz) newcanvas.classList.add('blitz')
+ newcanvas.classList.add(gameInfo.image ? 'captioning' : 'drawing')
+ // Clear
+ if (anbt.isStroking) strokeEnd()
+ unlock()
+ for (let i = anbt.svg.childNodes.length - 1; i > 0; i--)
+ anbt.svg.removeChild(anbt.svg.childNodes[i])
+ seek(0)
+ moveSeekbar(1)
+ anbt.unsaved = false
+ const { palette } = gameInfo
+ if (!gameInfo.image) {
+ const paletteData = getPalData(palette)
+ if (!paletteData) {
+ if (!palette)
+ alert(
+ 'Error, please report! Failed to extract the palette.\nAre you using the latest ANBT version?'
+ )
+ else
+ alert(
+ `Error, please report! Unknown palette: '${palette}'.\nAre you using the latest ANBT version?`
+ )
+ // Prevent from drawing with a wrong palette
+ lock
+ ID('submit').disabled = true
+ } else {
+ setPaletteByName(paletteData[0])
+ setBackground(paletteData[1])
+ anbt.color = [palettes[paletteData[0]][0], 'eraser']
+ updateColorIndicators()
+ }
+ ID('setbackground').hidden = !gameInfo.bgbutton
+ } else {
+ // Check broken drawing
+ ID('tocaption').src =
+ gameInfo.image.length <= 30
+ ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAD6AQMAAAAho+iwAAAABlBMVEWAQED///94jotxAAABiklEQVR4Xu3W0UrCUBjA8eOO5CLK7VxLzDWFrjK6Eaha8FHuppfwBRJvdjlMIK/K3qA3OZBBd/UIm9UL2O2inMJBptNuog/6/h4Q2Y8J387Y2KIoiqIoiqIoiuIxXnbI5cmXSiJjD3LmFyrGY46PqVAx/HPDv9/w3wsJTTgapuDkcEIQMFxzo937S8+F5OkWI2IKymQl3yiZ6j8zYsRY6vUYDcOfGkuMknE5/aQAMczX9O+iKIrKJWuSxliQqT61hOmMucsYK6uzLWfDenF34EXhOX+s377KLCZcs1bxhNXQqnAvrExWM8vvY3amORCNsplu2nZPWKdj1tecTHZZLA97ZnjBB/XrkWIZWT+bsmTowp+7FHSnyMi7CpuMrWcwNsMMxnJzrCUbwwq/2/MLJb8lP4L2zVHJ35Bp1rE8Uc2bALoNHQvcoNG3Yf5Pm6EnHG50Ye0YmiG4V08LmWD7wmF9gJwFgoHbnZzNSDE/Co3orSB2YGsbovAgaD9vlkB/WbkbdQVWMNxR1Ddnf4eSZpHZYAAAAABJRU5ErkJggg=='
+ : gameInfo.image
+ ID('caption').value = ''
+ ID('caption').focus()
+ ID('caption').setAttribute('maxlength', 45)
+ ID('usedchars').textContent = '45'
+ }
+ if (
+ (options.timeoutSound && !gameInfo.blitz) ||
+ (options.timeoutSoundBlitz && gameInfo.blitz)
+ ) {
+ window.playedWarningSound = false
+ window.alarm = new Audio(window.alarmSoundOgg)
+ window.alarm.volume = options.timeoutSoundVolume / 100
+ }
+ globals.timerStart = Date.now() + 1000 * gameInfo.timeleft
+ window.timerCallback = timerCallback
+ handleCommonParameters()
+ window.timesup = false
+ updateTimer()
+}
+
+export default handlePlayParameters
diff --git a/src/newcanvas/js/functions/needToGoDeeper/handleSandboxParameters.js b/src/newcanvas/js/functions/needToGoDeeper/handleSandboxParameters.js
new file mode 100644
index 0000000..6101d61
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/handleSandboxParameters.js
@@ -0,0 +1,29 @@
+import play from '../anbt/play'
+import ID from '../idSelector'
+import handleCommonParameters from './handleCommonParameters'
+
+const handleSandboxParameters = () => {
+ const { gameInfo, vertitle, options } = window
+ if (gameInfo.drawingbylink) {
+ const [playername, playerlink] = gameInfo.drawingbylink
+ const replaylink = `Drawing`
+ ID(
+ 'headerinfo'
+ ).innerHTML = `${replaylink} by ${playername}`
+ document.title = `${playername}'s drawing - Drawception`
+ if (gameInfo.drawncaption) {
+ ID('drawthis').innerHTML = `"${gameInfo.drawncaption}"`
+ ID('drawthis').classList.remove('onlyplay')
+ ID('emptytitle').classList.add('onlyplay')
+ }
+ if (options.autoplay) play()
+ } else {
+ ID('headerinfo').innerHTML = `Sandbox with ${vertitle}`
+ ID('drawthis').classList.add('onlyplay')
+ }
+ handleCommonParameters()
+}
+
+export default handleSandboxParameters
diff --git a/src/newcanvas/js/functions/needToGoDeeper/onCaptionSuccess.js b/src/newcanvas/js/functions/needToGoDeeper/onCaptionSuccess.js
new file mode 100644
index 0000000..29d2467
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/onCaptionSuccess.js
@@ -0,0 +1,13 @@
+const onCaptionSuccess = title => {
+ const { options, gameInfo } = window
+ if (!options.bookmarkOwnCaptions) return
+ const games = window.getLocalStorageItem('gpe_gameBookmarks', {})
+ games[gameInfo.gameid] = {
+ time: Date.now(),
+ caption: `"${title}"`,
+ own: true
+ }
+ localStorage.setItem('gpe_gameBookmarks', JSON.stringify(games))
+}
+
+export default onCaptionSuccess
diff --git a/src/newcanvas/js/functions/needToGoDeeper/timerCallback.js b/src/newcanvas/js/functions/needToGoDeeper/timerCallback.js
new file mode 100644
index 0000000..337fac2
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/timerCallback.js
@@ -0,0 +1,40 @@
+import globals from '../../globals'
+import lock from '../anbt/lock'
+import ID from '../idSelector'
+import updateTimer from '../updateTimer'
+import exitToSandbox from './exitToSandbox'
+import getParametersFromPlay from './getParametersFromPlay'
+
+const timerCallback = seconds => {
+ const { gameInfo } = window
+ if (seconds < 1) {
+ document.title = "[TIME'S UP!] Playing Drawception"
+ if (gameInfo.image || window.timesup) {
+ // If pressed submit before timer expired, let it process or retry in case of error
+ if (!window.submitting) {
+ if (gameInfo.image) getParametersFromPlay()
+ else exitToSandbox() // Allow to save the drawing after time's up
+ }
+ } else {
+ ID('newcanvasyo').classList.add('locked')
+ lock()
+ globals.timerStart += 15000 // 15 seconds to submit
+ updateTimer()
+ window.timesup = true
+ }
+ } else
+ document.title = `[${`0${Math.floor(seconds / 60)}`.slice(
+ -2
+ )}:${`0${Math.floor(seconds % 60)}`.slice(-2)}] Playing Drawception`
+ if (
+ window.alarm &&
+ !window.playedWarningSound &&
+ seconds <= (gameInfo.blitz ? 5 : 61) &&
+ seconds > 0
+ ) {
+ window.alarm.play()
+ window.playedWarningSound = true
+ }
+}
+
+export default timerCallback
diff --git a/src/newcanvas/js/functions/needToGoDeeper/unsavedStopAction.js b/src/newcanvas/js/functions/needToGoDeeper/unsavedStopAction.js
new file mode 100644
index 0000000..c4dcd61
--- /dev/null
+++ b/src/newcanvas/js/functions/needToGoDeeper/unsavedStopAction.js
@@ -0,0 +1,6 @@
+import anbt from '../../anbt'
+
+const unsavedStopAction = () =>
+ anbt.unsaved && !confirm("You haven't saved the drawing. Abandon?")
+
+export default unsavedStopAction
diff --git a/src/newcanvas/js/functions/pack/packUint16be.js b/src/newcanvas/js/functions/pack/packUint16be.js
new file mode 100644
index 0000000..9765fd0
--- /dev/null
+++ b/src/newcanvas/js/functions/pack/packUint16be.js
@@ -0,0 +1,4 @@
+const packUint16be = number =>
+ String.fromCharCode((number >> 8) & 0xff, number & 0xff)
+
+export default packUint16be
\ No newline at end of file
diff --git a/src/newcanvas/js/functions/pack/packUint32be.js b/src/newcanvas/js/functions/pack/packUint32be.js
new file mode 100644
index 0000000..504dcbd
--- /dev/null
+++ b/src/newcanvas/js/functions/pack/packUint32be.js
@@ -0,0 +1,9 @@
+const packUint32be = number =>
+ String.fromCharCode(
+ (number >> 24) & 0xff,
+ (number >> 16) & 0xff,
+ (number >> 8) & 0xff,
+ number & 0xff
+ )
+
+export default packUint32be
diff --git a/src/newcanvas/js/functions/randomBase.js b/src/newcanvas/js/functions/randomBase.js
new file mode 100644
index 0000000..148a6ad
--- /dev/null
+++ b/src/newcanvas/js/functions/randomBase.js
@@ -0,0 +1,15 @@
+import random from '../random'
+import randomItem from './randomItem'
+
+const randomBase = () => {
+ const randomNumber = Math.floor(Math.random() * 3)
+ return `${randomItem(random.description)} ${randomItem(random.things)}${
+ randomNumber >= 1
+ ? ` ${randomItem(random.acts)} ${
+ randomNumber === 2 ? `${randomItem(random.description)} ` : ''
+ }${randomItem(random.things)}`
+ : ''
+ }`
+}
+
+export default randomBase
diff --git a/src/newcanvas/js/functions/randomItem.js b/src/newcanvas/js/functions/randomItem.js
new file mode 100644
index 0000000..eb67d00
--- /dev/null
+++ b/src/newcanvas/js/functions/randomItem.js
@@ -0,0 +1,3 @@
+const randomItem = list => list[Math.floor(Math.random() * list.length)]
+
+export default randomItem
diff --git a/src/newcanvas/js/functions/randomPhrase.js b/src/newcanvas/js/functions/randomPhrase.js
new file mode 100644
index 0000000..d0637b2
--- /dev/null
+++ b/src/newcanvas/js/functions/randomPhrase.js
@@ -0,0 +1,8 @@
+import randomBase from './randomBase'
+
+const randomPhrase = () => {
+ const s = randomBase()
+ return s.charAt(0).toUpperCase() + s.substr(1)
+}
+
+export default randomPhrase
diff --git a/src/newcanvas/js/functions/simplifyDouglasPeucker.js b/src/newcanvas/js/functions/simplifyDouglasPeucker.js
new file mode 100644
index 0000000..1b8e5c6
--- /dev/null
+++ b/src/newcanvas/js/functions/simplifyDouglasPeucker.js
@@ -0,0 +1,33 @@
+import getSqSegDist from './getSqSegDist'
+
+const simplifyDouglasPeucker = ({ points, smoothening: sqTolerance }) => {
+ const length = points.length
+ const MarkerArray = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
+ const markers = new MarkerArray(length)
+ let first = 0
+ let last = length - 1
+ const stack = []
+ const newPoints = []
+ markers[first] = markers[last] = 1
+ while (last) {
+ let maxSqDist = 0
+ let index
+ for (let i = first + 1; i < last; i++) {
+ let sqDist = getSqSegDist(points[i], points[first], points[last])
+ if (sqDist > maxSqDist) {
+ index = i
+ maxSqDist = sqDist
+ }
+ }
+ if (maxSqDist > sqTolerance) {
+ markers[index] = 1
+ stack.push(first, index, index, last)
+ }
+ last = stack.pop()
+ first = stack.pop()
+ }
+ for (let i = 0; i < length; i++) if (markers[i]) newPoints.push(points[i])
+ return newPoints
+}
+
+export default simplifyDouglasPeucker
diff --git a/src/newcanvas/js/functions/updateTimer.js b/src/newcanvas/js/functions/updateTimer.js
new file mode 100644
index 0000000..4dd9d60
--- /dev/null
+++ b/src/newcanvas/js/functions/updateTimer.js
@@ -0,0 +1,15 @@
+import globals from '../globals'
+import ID from './idSelector'
+
+const updateTimer = () => {
+ let seconds = (globals.timerStart - Date.now()) / 1000
+ try {
+ if (window.timerCallback) window.timerCallback(seconds)
+ } catch (e) {}
+ seconds = Math.abs(seconds)
+ const minutes = `0${Math.floor(seconds / 60)}`.slice(-2)
+ seconds = `0${Math.floor(seconds % 60)}`.slice(-2)
+ ID('timer').childNodes[0].nodeValue = `${minutes}:${seconds}`
+}
+
+export default updateTimer
diff --git a/src/newcanvas/js/globals.js b/src/newcanvas/js/globals.js
new file mode 100644
index 0000000..2142b3f
--- /dev/null
+++ b/src/newcanvas/js/globals.js
@@ -0,0 +1,12 @@
+import anbt from './anbt'
+
+const globals = {
+ rectangle: {},
+ touchSingle: false,
+ lastTouch: {},
+ lastSeenColorToHighlight: anbt.background,
+ brushSizes: [2, 6, 14, 42],
+ timerStart: 0
+}
+
+export default globals
diff --git a/src/newcanvas/js/pako.js b/src/newcanvas/js/pako.js
new file mode 100644
index 0000000..01a24e1
--- /dev/null
+++ b/src/newcanvas/js/pako.js
@@ -0,0 +1,2 @@
+import pako from 'pako'
+window.pako = pako
diff --git a/src/newcanvas/js/palettemap.js b/src/newcanvas/js/palettemap.js
new file mode 100644
index 0000000..2d9b800
--- /dev/null
+++ b/src/newcanvas/js/palettemap.js
@@ -0,0 +1,36 @@
+const palettemap = {
+ default: ['Normal', '#fffdc9'],
+ theme_thanksgiving: ['Thanksgiving', '#f5e9ce'],
+ halloween: ['Halloween', '#444444'],
+ theme_cga: ['CGA', '#ffff55'],
+ shades_of_grey: ['Grayscale', '#e9e9e9'],
+ theme_bw: ['Black and white', '#ffffff'],
+ theme_gameboy: ['Gameboy', '#9bbc0f'],
+ theme_neon: ['Neon', '#00abff'],
+ theme_sepia: ['Sepia', '#ffe2c4'],
+ theme_valentines: ["Valentine's", '#ffccdf'],
+ theme_blues: ['the blues', '#295c6f'],
+ theme_spring: ['Spring', '#ffffff'],
+ theme_beach: ['Beach', '#f7dca2'],
+ theme_beach_2: ['Tide Pool', '#2271a2'],
+ theme_coty_2016: ['Colors of 2016', '#648589'],
+ theme_bee: ['Bee', '#ffffff'],
+ theme_coty_2017: ['Colors of 2017', '#5f7278'],
+ theme_fire_ice: ['Fire and Ice', '#040526'],
+ theme_coty_2018: ['Canyon Sunset', '#2e1b50'],
+ theme_juice: ['Juice', '#fced95'],
+ theme_tropical: ['Tropical', '#2f0946'],
+ theme_grimby_grays: ['Grimby Grays', '#f0efeb'],
+ theme_fury_road: ['Fury Road', '#893f1d'],
+ theme_candy: ['Candy', '#793abd'],
+ theme_holiday_2: ['Holiday', '#f6f6f6'],
+ theme_blues_2: ['Blues', '#0f1328'],
+ theme_sin_city: ['Sin City', '#000000'],
+ theme_lucky_clover: ['Lucky Clover', '#0c442c'],
+ theme_drawception: ["D's Exclusive", '#0ee446'],
+ theme_retina_burn: ['Retina Burn', '#ff0b11'],
+ theme_easter: ['Easter', '#ddf7a8'],
+ theme_neapolitan: ['Neapolitan', '#fff7e1']
+}
+
+export default palettemap
diff --git a/src/newcanvas/js/palettes.js b/src/newcanvas/js/palettes.js
new file mode 100644
index 0000000..51d1971
--- /dev/null
+++ b/src/newcanvas/js/palettes.js
@@ -0,0 +1,68 @@
+// prettier-ignore
+const palettes = {
+ "Normal": ['#000000', '#444444', '#999999', '#ffffff', '#603913', '#c69c6d',
+ '#ffdab9', '#ff0000', '#ffd700', '#ff6600', '#16ff00', '#0fad00',
+ '#00ffff', '#0247fe', '#ec008c', '#8601af', '#fffdc9'],
+ "Sepia": ['#402305', '#503315', '#604325', '#705335', '#806345', '#907355',
+ '#a08365', '#b09375', '#bfa284', '#cfb294', '#dfc2a4', '#ffe2c4'],
+ "Grayscale": ['#000000', '#ffffff', '#151515', '#2a2a2a', '#3f3f3f', '#555555', '#6a6a6a',
+ '#7f7f7f', '#949494', '#aaaaaa', '#bfbfbf', '#d4d4d4', '#e9e9e9'],
+ "Black and white": ['#ffffff', '#000000'],
+ "CGA": ['#555555', '#000000', '#0000aa', '#5555ff', '#00aa00', '#55ff55', '#00aaaa', '#55ffff',
+ '#aa0000', '#ff5555', '#aa00aa', '#ff55ff', '#aa5500', '#ffff55', '#aaaaaa', '#ffffff'],
+ "Gameboy": ['#8bac0f', '#9bbc0f', '#306230', '#0f380f'],
+ "Neon": ['#ffffff', '#000000', '#adfd09', '#f3f315', '#feac09', '#fe0bab', '#ad0bfb', '#00abff'],
+ "Thanksgiving": ['#673718', '#3c2d27', '#c23322', '#850005', '#c67200', '#77785b',
+ '#5e6524', '#cfb178', '#f5e9ce'],
+ "Holiday_old": ['#3d9949', '#7bbd82', '#7d1a0c', '#bf2a23',
+ '#fdd017', '#00b7f1', '#bababa', '#ffffff'],
+ "Valentine's": ['#2d1014', '#ffffff', '#600d17', '#c2113a', '#b71d1d', '#e54d5a',
+ '#ff7d63', '#fd8647', '#fed067', '#ffe4b7', '#fdc0c6'],
+ "Halloween": ['#444444', '#000000', '#999999', '#ffffff', '#603913', '#c69c6d',
+ '#7a0e0e', '#b40528', '#fd2119', '#fa5b11', '#faa611', '#ffd700',
+ '#602749', '#724b97', '#bef202', '#519548', '#b2bb1e'],
+ "the blues": ['#b6cbe4', '#618abc', '#d0d5ce', '#82a2a1', '#92b8c1', '#607884',
+ '#c19292', '#8c2c2c', '#295c6f'],
+ "Spring": ['#9ed396', '#57b947', '#4d7736', '#365431', '#231302',
+ '#3e2409', '#a66621', '#a67e21', '#ebbb49', '#ffc0cb', '#ffffff'],
+ "Beach": ['#1ca4d2', '#65bbe2', '#6ab7bf', '#94cbda', '#9cbf80', '#d2e1ab',
+ '#b8a593', '#d7cfb9', '#dc863e', '#f7dca2'],
+ "Tide Pool": ["#ffe8b9", "#fad489", "#ffb44c", "#d6b1de", "#b197a8", "#e5f2ff",
+ "#a1ffb8", "#53e6ef", "#3ad3a8", "#1ca4d2", "#2271a2"],
+ "Colors of 2016": ['#91a7d0', '#f6cac9', '#eb9587', '#776a5f',
+ '#d1c2ab', '#a39d9d', '#648589'],
+ "Bee": ["#000000", "#7a5c00", "#b58800", "#eab618", "#f6de97", "#ffffff"],
+ "Colors of 2017": ['#86af49', '#44883d', '#1f4478', '#0062a3',
+ '#00939a', '#59c9d5', '#8a9a9a', '#5f7278'],
+ "Fire and Ice": ['#520909', '#b40528', '#fd2119', '#faa611', '#ffe96a',
+ '#ffffff', '#69ddff', '#1c8ae5', '#0a3fa9', '#040526'],
+ "Canyon Sunset": ['#fce3ca', '#feb789', '#f27c8a', '#af5081',
+ '#8e6dae', '#5f4a8b', '#2e1b50'],
+ "Juice": ["#f3ab54", "#ec5e66", "#ab5871", "#f2a19b", "#f9f4d4", "#fadfb7",
+ "#869e3c", "#cbdd7e", "#fced95"],
+ "Tropical": ["#f68357", "#fbc040", "#fefa56", "#fef0f5", "#90fc51", "#07f182",
+ "#1d6ab2", "#12041b", "#2f0946"],
+ "Grimby Grays": ['#000000', '#ffffff', '#2f3032', '#252422', '#545758', '#4b4a46', '#797d80',
+ '#71706c', '#9ea1a4', '#979692', '#c4c8cb', '#d7d6d2', '#dee1e4', '#f0efeb'],
+ "DawnBringer 16": ['#140c1c', '#442434', '#30346d', '#4e4a4e', '#854c30', '#346524',
+ '#d04648', '#757161', '#597dce', '#d27d2c', '#8595a1', '#6daa2c',
+ '#d2aa99', '#6dc2ca', '#dad45e', '#deeed6'],
+ "Fury Road": ['#020c16', '#023745', '#08616d', '#36d4b6', '#0afef6',
+ '#fce173', '#e29f30', '#b56942', '#ad3f16', '#893f1d'],
+ "Candy": ['#06063c', '#4f95ff', '#68f9ff', '#fffef9', '#ff96f8', '#ff44d3', '#793abd'],
+ "Holiday": ['#e91434', '#97200a', '#c66a20', '#fdbe30', '#688625', '#004f28',
+ '#112825', '#1c69bf', '#6096d3', '#a5c4e6', '#f7d9f0', '#f6f6f6'],
+ "Blues": ['#929aa8', '#896868', '#546c7d', '#633d3d', '#284660',
+ '#421f29', '#232e3f', '#0f1328'],
+ "Sin City": ['#ffffff', '#ff0000', '#000000'],
+ "Lucky Clover": ['#ffffff', '#fcf4c4', '#f7b307', '#fc8404', '#cd7a14',
+ '#9bf23e', '#40d910', '#34900b', '#0c442c'],
+ "D's Exclusive": ['#000000', '#717474', '#ffffff', '#f25b99', '#e4965e', '#ffc416',
+ '#ffe38f', '#0074d9', '#09a3ec', '#12d1ff', '#bcf5ff', '#0ee446'],
+ "Retina Burn": ['#bc0bff', '#ff0b11'],
+ "Easter": ['#9678ba', '#bc9ff0', '#e4ccff', '#ffa1f1', '#fbd0ee', '#e6f2ff',
+ '#aaedfb', '#f4dc7b', '#fdfabd', '#a1ef85', '#ddf7a8'],
+ "Neapolitan": ['#3f3245', '#ff5c98', '#ecb2a4', '#fff7e1']
+}
+
+export default palettes
diff --git a/src/newcanvas/js/pathseg.js b/src/newcanvas/js/pathseg.js
new file mode 100644
index 0000000..8d395dd
--- /dev/null
+++ b/src/newcanvas/js/pathseg.js
@@ -0,0 +1 @@
+import 'pathseg'
diff --git a/src/newcanvas/js/random.js b/src/newcanvas/js/random.js
new file mode 100644
index 0000000..d3274db
--- /dev/null
+++ b/src/newcanvas/js/random.js
@@ -0,0 +1,34 @@
+// prettier-ignore
+const random = {
+ things: [
+ 'hobo', 'shoe', 'log', 'bun', 'sandwich', 'bull', 'beer', 'hair',
+ 'hill', 'beans', 'man', 'sofa', 'dinosaur', 'road', 'plank', 'hole', 'food',
+ 'hedgehog', 'pine', 'toad', 'tooth', 'candy', 'rock', 'drop', 'book', 'button', 'carpet',
+ 'wheel', 'computer', 'box', 'cat', 'rat', 'hook', 'chunk', 'boat', 'spade', 'sack',
+ 'hammer', 'face', 'soap', 'nose', 'finger', 'steam', 'spring', 'hand', 'fish',
+ 'elephant', 'dog', 'chair', 'bag', 'phone', 'robot', 'axe', 'grass', 'crack', 'teacher',
+ 'breadcrumb', 'fridge', 'worm', 'nut', 'cloth', 'apple', 'tongue', 'jar'
+ ],
+ acts: [
+ 'crazy from', 'thanks', 'hits', 'lies around on', 'sees', 'grows in',
+ 'attaches to', 'flies from', 'crawls from', 'chews', 'walks on', 'squishes', 'pecks',
+ 'wobbles in', 'smokes from', 'smokes', 'rides', 'eats', 'squeals from under',
+ 'is lost in', 'spins in', 'stuck in', 'hooks', 'angry at', 'bends', 'drips on',
+ 'rolls on', 'digs', 'crawls in', 'flies at', 'massages', 'dreams of', 'kills', 'pulls',
+ "doesn't want", 'licks', 'shoots', 'falls off', 'falls in', 'crawls on', 'turns into',
+ 'stuck to', 'jumps on', 'hides', 'hides in', 'disassembles', 'rips', 'dissolves',
+ 'stretches', 'crushes', 'pushes', 'drowns in', 'pokes', 'runs away from', 'wants',
+ 'scratches', 'throws', 'and', 'confused by', 'unimpressed by'
+ ],
+ description: [
+ 'white', 'concrete', 'shiny', 'ill', 'big', 'ex', 'fast', 'happy',
+ 'inside-out', 'hot', 'burning', 'thick', 'wooden', 'long', 'good', 'tattered', 'iron',
+ 'liquid', 'frozen', 'green', 'evil', 'bent', 'rough', 'pretty', 'red', 'round',
+ 'shaggy', 'bald', 'slow', 'wet', 'wrinkly', 'meaty', 'impudent', 'real', 'distraught',
+ 'sharp', 'plastic', 'gift', 'squished', 'chubby', 'crumbling', 'horned', 'angry',
+ 'sitting', 'stranded', 'dry', 'hard', 'thin', 'killer', 'walking', 'cold', 'wheezing',
+ 'grunting', 'chirping', 'wide', 'electric', 'nuclear', 'confused', 'unimpressed'
+ ]
+}
+
+export default random